dtwin Gateway
dtwin Gateway · MCP gateway

A tunnel for AI agents.

Claude Code, Cursor or any MCP agent reads your internal Jira, GitLab and SQL — only what you allow. With masking, audit and a kill-switch.

01 · The problem

Don't give agents passwords.

Handing an agent a Jira token and a DB password is easy — but then nobody knows who read what. The gateway gives each agent one personal token.

  • 1Secrets stay in the office
  • 2Every call is logged
The usual way · agent .env
JIRA_TOKEN=jira_••••••••
GITLAB_TOKEN=glpat-••••••
DB_USER=sa
DB_PASSWORD=••••••••••
  • ✕Passwords live in the agent
  • ✕No log
  • ✕No way to stop it
→
With the gateway
Authorization:
Bearer ag_aziz_••••
  • Only allowed tools
  • Every call audited
  • Stops with one button
02 · How it works

One gateway — full control.

The agent only talks to the gateway. The gateway checks, masks, logs and forwards the request through a tunnel to the office connector.

  • 1MCP Streamable HTTP
  • 2The connector listens on 127.0.0.1 only
Office network
tunnel
Claude Code
Cursor
MCP client
gateway.dtwin.uz
token
allowlist
rate
mask
audit
connector
127.0.0.1
Jira
GitLab
SQL
03 · Connect

Three steps — one command.

An admin creates the agent, you get a token and run one command. Claude Code, Cursor or any MCP client.

  • 1The token is shown exactly once
  • 2A JSON config for Cursor, too
1Admin creates the agentpanel → Agents
2You get the tokenag_aziz_…
3One commandclaude mcp add
~/acme-pay — zsh
$ claude mcp add --transport http dtwin https://gateway.dtwin.uz/mcp --header "Authorization: Bearer ag_aziz_q7Lm2X…"
✓ Added HTTP MCP server dtwin with URL: https://gateway.dtwin.uz/mcp
$ claude
● dtwin · connected · 5 tools available
Claude CodeCursorany MCP client
04 · Personal token

A token — once, and personal.

Every agent gets its own ag_<name>_… token. The server stores only its sha256 — the plain token is kept nowhere.

  • 120 failures / 10 min — the IP is blocked
  • 2Rotate at any time

New agent

Name
aziz
Owner
Aziz (backend)
Tools
acme-jira_*, acme-gitlab_mr*, acme-sql_query
Rate
30 / minute
Create
ag_aziz
ag_aziz_q7Lm2Xv9Tc4Rb8Np1Kd6Wf3Hs5Jy0GaE
⚠ Visible only now — copy it
Copy
Stored on the server sha256 · 4f9a1c0e…d27be20d
Brute-force brake
20/ 20failed attempts · one IP
IP blocked for 10 minutes
05 · Live example

One question — three systems.

A developer asks Claude. The agent reads Jira, GitLab and the stage DB on its own and answers with evidence.

  • 1Every step shows up in the admin panel
  • 2Data arrives already masked
claude · ~/acme-paydtwin · MCP
> Why did PAY-142 fail?
● dtwin - acme-jira_issue (key: "PAY-142")
└ Reopened · 4 comments · linked MR !318
● dtwin - acme-gitlab_mr (iid: 318)
└ +12 −3 · InvoiceTotal rounding changed
● dtwin - acme-sql_query (db: "stage", sql: "SELECT TOP 20 Id, Total …")
└ 3 rows · 41 ms · ROLLBACK
● The cause is in !318: totals are now rounded to 2 decimals, and on stage 3 dbo.Invoice rows differ by 0.01. That is exactly the bug Dilnoza (QA) saw.
◆3 tools · 0 denied · 4 fields masked
06 · Call path

Every packet — six checks.

On tools/call the token, allowlist and rate are checked, and the connector's answer comes back masked and logged.

  • 1Denied calls are logged too
  • 2The result says how many fields were masked
{ "jsonrpc": "2.0", "id": 7, "method": "tools/call", "params": { "name": "acme-sql_query", "arguments": { "db": "stage", "sql": "SELECT TOP 20 Id, Total FROM dbo.Invoice WHERE …" } } }
tools/call
Tokensha256 ✓
Allowlistglob match
Rate12 / 30
Connector41 ms
Mask4 fields
AuditJSONL + SSE
result to agent3 rows4 masked63 ms
07 · Permissions

No permission — no tool in sight.

The admin gives each agent a glob list: *, acme-jira_*, acme-sql_query. Tools outside the list are hidden from tools/list and refused on call.

  • 1Calls-per-minute limit, default 30
  • 2Pause a single agent
AZ
aziz
Aziz (backend)
on
Allowlist · glob
acme-jira_*acme-gitlab_mr*acme-sql_query
Rate · per minute
12 / 30
tools/call → acme-sql_schema ✕ denied · not allowed
tools/list · what the agent sees
acme-jira_search✓
acme-jira_issue✓
acme-gitlab_mrs✓
acme-gitlab_mr✓
acme-gitlab_filehidden
acme-gitlab_searchhidden
acme-gitlab_commitshidden
acme-sql_schemahidden
acme-sql_query✓
08 · Masking

Personal data — never reaches the agent.

Every result is checked before it leaves: PINFL, passport, phone, email, card, tokens are hidden. Each call shows how many fields were masked.

  • 1Tax ID (INN) — optional
  • 2Toggle each type in the panel
acme-sql_query · result
phone+998 ** *** ** 67
phone+998 ** *** ** 34
phone+998 ** *** ** 40
pinfl31**********56
inn301234567
4
Masked in this call
phone ×3PINFL ×1
Mask types
PINFL30**********56
PassportAB*****67
Phone+998 ** *** ** 67
Emaila***@mail.uz
Card**** **** **** 1234
JWT / tokenPassword=***
INNoptional
09 · SQL guard

DELETE? No.

The guard lets through a single SELECT or WITH only. INTO, EXEC, sp_/xp_, OPENROWSET, WAITFOR, “;” — the query stops before it reaches the database.

  • 1Refused queries stay in the audit log
  • 2Enforced in the connector
acme-sql_query · stage
DELETE FROM dbo.Invoice WHERE Status = 3
✕ REFUSEDonly a single SELECT / WITH is allowed
BlockedDELETEUPDATEINSERTINTOEXECsp_ / xp_OPENROWSETWAITFOR;
acme-sql_query · stage
SELECT TOP 20 Id, Total FROM dbo.Invoice WHERE Total % 0.01 <> 0
✓ passed · 3 rows · 41 msROLLBACK
10 · Read-only

Writes are impossible — by design.

Even a query the guard accepts runs in a READ UNCOMMITTED transaction and is always rolled back. On prod — a read-only login at the DB level.

  • 130-second timeout
  • 2At most 200 rows
SQL guarda single SELECT / WITH
TransactionREAD UNCOMMITTED
Time30 s timeout
Size≤ 200 rows
Finishalways ROLLBACK
Prodread-only DB login
connector · sql
BEGIN TRAN
SET ISOLATION LEVEL
READ UNCOMMITTED
SELECT TOP 20 Id, Total
FROM dbo.Invoice …
-- 3 rows · 41 ms
ROLLBACK
ROLLBACK ✓
0changes
11 · Audit

Who, what, when — all on record.

Agent, tool, masked arguments, status, ms, bytes and mask count. JSONL kept for 30 days, streamed live to the panel over SSE.

  • 1Filter by agent and day
  • 2Arguments are masked too
timeagenttoolstatusmsmask
14:03:02azizacme-jira_searchok1880jql: "project = PAY AND status = Reopened"
14:02:40dilnozaacme-sql_querydenied20sql: "DELETE FROM dbo.Invoice …"
14:02:15azizacme-sql_queryok414db: "stage", sql: "SELECT TOP 20 Id, Total …"
14:02:13azizacme-gitlab_mrok3880iid: 318
14:02:11azizacme-jira_issueok2120key: "PAY-142"
14:01:57ci-botacme-gitlab_commitsok1640ref: "main", limit: 20
SSEdata/log/2026-11-02.jsonl · kept for 30 days
12 · Live panel

Every call — right in front of you.

Flow from agents to the core and from the core to connectors. A red burst is a denied call, a purple one is masked data.

  • 13D flow + feed + stats
  • 2One page, nothing to type
gateway.dtwin.uz/adminLIVE
gateway core azizdilnozaci-bot acmeacme-qa
1 284calls
17denied
3errors
342masked
84p50 · ms
410p95 · ms
13 · Kill-switch

One button — everything stops.

If something looks wrong, the admin hits the kill-switch: no agent call gets through. You can also pause one agent or disable one connector.

  • 1Turning it back on is one button too
  • 2The reason is logged
gateway.dtwin.uz/admin · kill-switch
Kill-switch on · calls are refused
STOP EVERYTHING
Agents
AZ
azizAziz (backend)
activePause
DI
dilnozaDilnoza (QA)
activePause
CI
ci-botCI
activePause
Connectors
acmetunnel · 9 tools
activeDisable
14 · Secrets stay home

Passwords — never leave the office.

DB passwords and Jira/GitLab tokens live only in a tiny office connector. It listens on 127.0.0.1 and is published to the gateway through tunnel.dtwin.uz.

  • 1The gateway calls the connector with a bearer secret
  • 2tunnel.dtwin.uz is a sister product
Cloud
gateway.dtwin.uz
0secrets
sha256auditpolicy
Office network
connector
127.0.0.1:8095 · Bearer
stage DB · read-only login
Jira token
GitLab token
tunnel.dtwin.uzbearer secret
15 · Tools

Nine tools — all read-only.

Jira, GitLab and SQL — what developers ask about every day. No npm dependencies, one small container.

  • 1Tool names carry the connector name: acme-…
  • 2New connector — new tools
jira_searchsearch by JQL
jira_issuedescription + comments + links
gitlab_mrslist merge requests
gitlab_mrdescription + diff
gitlab_filea file, with a line range
gitlab_searchsearch the code
gitlab_commitscommit history
sql_schematables and columns
sql_querySELECT · ≤ 200 rows
read-only0 npm dependenciesone containerNode 20+
16 · Human approval Soon

Risky step — with a human OK.

When an agent wants to post a Jira comment, the request goes to the panel or Telegram. Nothing is written until an admin approves.

  • 1Write tools only with approval
  • 2Who approved is audited
Approval requestSoon
aziz → acme-jira_comment
PAY-142: fixed in !318, verified on stage.
ApproveReject
✓ Approved · admin · 14:05
claude
● acme-jira_comment
└ waiting for admin approval…
└ ✓ comment added
17 · Roadmap

What's next — already planned.

The gateway keeps growing: write tools, finer policies and new connectors. Marked with a chip.

Human approvalin the panel or TelegramSoon
Write toolse.g. Jira comment — with approvalSoon
Prod DB policiesrow- and column-levelSoon
Log connectorSeq / GrafanaSoon
Tokens & costaccounting per agentSoon
Anomaly alertsstraight to TelegramSoon
OAuth loginMCP auth instead of static tokensSoon
Call replayreplay for reviewSoon
Multi-tenantfor other companiesSoon
Get started

Connect your agent today.

Get a token from your admin and run one command — then ask the agent about any task.

← → or tap · P pause · O overview · F full screen
paused · press P or ▶ to resume

dtwin Gateway · all sections