Docs Start here

Use the KIFF MCP gateway

Already have an AI agent that can connect to remote MCP servers? You can put KIFF between that agent and the tools it uses:

your agent ──MCP──▶ mcp.kiff.dev ──MCP──▶ your connected tool
                           │
                           └── checks the agent's Card first

KIFF does not host the agent. The agent still plans the work; the connected tool still performs it. KIFF checks each call against the Card before it forwards the call. For this to be a boundary, connect the tool to KIFF and remove the agent’s direct access to that same tool.

This is a different integration from kiff-guard, which you add to code you can change. The gateway is for remote MCP tools.

What you need

  • A KIFF account and an owner or admin API key.
  • A remote MCP server reachable at a public HTTPS URL. Private, loopback, link-local, metadata and reserved addresses are refused.
  • An agent that can connect to a remote MCP server over HTTP. The production flow has been tested with Claude Code and Codex.
  • A bearer credential for the tool server, if it requires one. KIFF stores connected tool credentials encrypted. OAuth and arbitrary non-MCP web APIs are not supported yet.

1. Connect a remote MCP tool

Signed in to KIFF Cloud as the account’s owner or an admin, open Tools and choose Connect a tool:

  1. Enter the server’s URL and, if it needs one, its bearer credential. KIFF lists the server’s tools with that credential; nothing is saved yet.
  2. Choose the tool to connect, the name your agent will see, the amount argument a Card limits (whole-number arguments only), the idempotency argument KIFF fills (text arguments only), and whether the tool only reads.

From the same page you can test a connected tool, replace its credential (the old one is kept unless the new one works) or remove it. The credential is never shown again after it is saved.

With the gateway API

The same steps work with an owner or admin API key. Prepare the request body in a local file readable only by you, such as connection.json:

{
  "name": "issue_refund",
  "url": "https://tools.example.com/mcp",
  "tool": "refund",
  "amount_argument": "amount_eur",
  "idempotency_argument": "idempotency_key",
  "credential": "<tool server bearer token>"
}

Then send it with your owner or admin key:

curl -s -X POST https://mcp.kiff.dev/v1/connections \
  -H "Authorization: Bearer $OWNER_KEY" \
  -H "Content-Type: application/json" \
  --data-binary @connection.json

tool is the name exposed by the remote server; name is the name your agent will see. Set amount_argument to the tool’s integer amount parameter if the Card should limit amounts. Set idempotency_argument if the tool accepts an idempotency key. Omit either field if it does not apply. Use the same units your tool expects when you set Card limits.

KIFF checks that the URL, tool and credential work before saving the connection. The gateway then updates the account’s tool contract. Treat the tool credential as a secret: do not commit the request file or paste a real credential into a command that will be saved in shell history. Remove the local request file after the connection is saved.

2. Give the agent a Card

In KIFF Cloud, open Tools and choose Give an agent a Card on the connected tool. Name the agent and set, in the tool’s own units, the most per call, a total and a number of calls per day, per 24 hours or per hour, what happens to a call outside the Card (held for you, or refused), and how long a held call waits for your answer. Issuing again for the same agent and tool changes its Card; Change limits on the Card starts from its current terms.

Each agent needs its own key bound to that agent and limited to the gateway_agent role. The key must be created by a caller that holds the admin role, because a key can only grant roles its creator already holds.

Signed in as an admin, open the agent under Agents and choose Connect to the KIFF gateway. KIFF creates the key and shows it once, already inside the configuration for Claude Code and Codex; copy it then. In a company account, only an admin can do this, and anyone else is told so.

To do the same with the API, use your admin API key ($ADMIN_KEY):

curl -s -X POST https://api.kiff.dev/v1/keys \
  -H "Authorization: Bearer $ADMIN_KEY" \
  -H "Content-Type: application/json" \
  -d '{"label":"my-agent","roles":["gateway_agent"],"agent_id":"my-agent"}'

A solo account owner has the admin role. To get an admin API key, sign in to KIFF Cloud and create a key with the admin role from your account settings. In a company account, an admin must create both that admin key and the gateway key. Use the same agent ID as the Card’s holder. The response includes the new key; it is shown once. Store it in your agent’s secret store or environment. Do not commit it or give the agent a tool server’s credential.

3. Point the agent at KIFF

Configure the agent’s remote MCP server URL as https://mcp.kiff.dev/mcp and give it the gateway key as a bearer token.

For Claude Code, the MCP server entry has this shape:

{
  "mcpServers": {
    "kiff": {
      "type": "http",
      "url": "https://mcp.kiff.dev/mcp",
      "headers": {
        "Authorization": "Bearer <gateway agent key>"
      }
    }
  }
}

For Codex, set mcp_servers.kiff.url to that URL and mcp_servers.kiff.bearer_token_env_var to the environment variable holding the gateway key. Keep the secret out of the configuration file if that file is checked in.

4. Let KIFF check each call

When the agent calls the connected tool, KIFF checks the agent’s Card first:

  • If the call is within the Card, KIFF forwards it to the tool.
  • If the Card requires an owner decision, KIFF holds the call and returns a link. KIFF also emails the account owner, once per held call, with what the agent asked for and the same link. Open it in KIFF Cloud, signed in, and choose an answer; nothing can be approved from the email itself. Approval authorizes the call; the agent must retry before KIFF forwards it.
  • A held call waits for the owner’s answer for the Card’s hold expiry, 10 minutes unless the Card sets another (from 1 minute to 7 days, hold_expiry when the Card is issued). If nobody answers in time it is refused, nothing is sent, and a late answer is not accepted; the agent has to ask again with a new kiff_operation_id. An answer given in time stands until the agent collects it.
  • If the call is refused, KIFF does not forward it.

The gateway records calls so retries do not forward the same held action twice. If the tool’s response is lost after a call was sent, the result may be unknown; KIFF will not send that action again automatically. When the tool supports idempotency, configure its idempotency argument as shown above.

The gateway adds an optional kiff_operation_id argument to every tool. Give each new action a new id, and reuse the id when retrying the same action:

  • A call with an id KIFF has seen is the same call, at any time: it gets that call’s outcome and is never forwarded again. The same id with different arguments is refused.
  • Without an id, an identical call (same tool, same arguments) within 10 minutes is treated as a retry of the first and gets its outcome, with a note saying so. Two genuinely separate identical actions in that window are done once; give each its own id to do both.
  • A tool connected as read-only is never answered with an earlier call’s result: a second identical read reaches the tool again once the first has finished.

What the agent gets back

A forwarded call returns the tool’s own result, unchanged. Every result KIFF writes itself (held, refused, still being decided, sent with no answer yet, failed, or unknown) has text for the model with the next step, and the same facts for the program running the agent in the result’s _meta, under dev.kiff/call:

{
  "state": "held",
  "invocation": "tc-…",
  "operation_id": "order-1042-refund",
  "sent": "no",
  "exception_id": "exc-…",
  "review_url": "https://app.kiff.dev/needs-you/exc-…",
  "hold_expires_at": "2026-10-01T12:10:00Z",
  "retry": "same_call",
  "retry_after_s": 30
}
  • state: held, refused, failed, unknown, deciding, forwarding, or forwarded on a repeated call.
  • sent: whether the tool received the call: no, yes or unknown.
  • retry: same_call (repeat the identical call to get the answer), new_call (this call is over; a new attempt needs a new kiff_operation_id, after the next step in the text) or none (do not retry, for example when the owner rejected it or the result is unknown).
  • reasons: machine-readable codes, such as no_mandate_issued, mandate_per_action_exceeded or mandate_owner_expired.
  • hold_expires_at: on a held call, when it stops waiting for the owner and is refused.
  • repeat: true when an identical call without an id was answered with an earlier call’s outcome.

Limits to understand

  • Only calls to tools connected through KIFF are governed. A direct connection, shell, browser or another route to the same service sits outside this Card.
  • The gateway currently connects remote MCP servers only. It does not connect arbitrary HTTP APIs or use OAuth.
  • Claude Code and Codex passed production smoke tests with a temporary test tool. That verified the gateway path; it was not a customer transaction or a test of every MCP client and server.
  • Codex prompts for approval before every MCP tool call by default. Set default_tools_approval_mode = "approve" in your Codex config to let it forward calls without pausing.

For the product story and the production test details, see Putting KIFF between an agent and its tools.