About indykite-agent-gateway
A skill for configuring IndyKite Agent Gateway to enforce identity, workflow authorization, and delegation checks before requests reach an A2A agent or MCP server. It covers single- and multi-hop delegation, configuration, and deployment.
Key features
- 01Protect one A2A agent or MCP server
- 02Validate caller identity and workflow access
- 03Check modeled delegation chains
- 04Configure IdP or Token Service exchanges
- 05Deploy and diagnose gateway instances
Use cases
- 01Enforcing authorization before an MCP server
- 02Protecting agents in an A2A workflow
- 03Setting up multi-hop delegation
- 04Investigating gateway authorization failures
MCP support
In `mcp` mode, IAG proxies MCP Streamable HTTP traffic to a protected MCP server and applies the same identity, authorization, and delegation checks used for A2A. It forwards only the specified MCP protocol revisions.
Install
Copy the command and run it in a terminal.
npx skills add https:/ / github.com/ indykite/ skills --skill indykite-agent-gatewayAfter installing, type/indykite-agent-gatewayin Claude Code to confirm it loaded.
Bundled files
References and scripts installed along with SKILL.md.
assets/config-template.yamltoken-service-config-template.yamlreferences/architecture.mdconfiguration.mdtoken-service.mdtroubleshooting.mdDetails
From the project READMEIndyKite Agent Gateway
The Indykite Agent Gateway (IAG) is a standalone service that protects exactly one downstream - an A2A agent or an MCP server. Run one IAG per protected downstream. From the caller's perspective IAG appears as the Target; from the protected downstream's perspective IAG appears as the Source. IAG is not a generic reverse proxy: by default (protocol: a2a) it speaks the A2A protocol and tracks A2A sessions so JSON-RPC streams flow correctly; with protocol: mcp it proxies MCP Streamable HTTP traffic (forwarding Mcp-Session-Id and streaming SSE responses through) and forwards only the MCP protocol revisions 2025-06-18, 2025-11-25, and 2026-07-28. Either way the same authorization runs in front.
For each request IAG validates three things:
- The caller (token introspection at the IdP).
- The workflow (subject
CAN_TRIGGERcheck via AuthZEN/KBAC). Since gateway 2.82.0 the subject comes from ContX IQGET /contx-iq/v1/whoamion the caller's token: its type is theikg_node_typeof the project's Token Introspect configuration that validates the token, its id the token's subject (sub, or the claim named by that configuration'ssub_claim). - The delegation chain (the request's
actchain matches a chain modeled in the IKG).
The delegation token that carries the act chain is minted either by the IdP (RFC 8693 token exchange) or, when the optional token_service section is configured, by the self-hosted IndyKite Token Service (ITS). With ITS the caller's own access token travels untouched in Authorization and the delegation token beside it in X-IK-Token, and a request that arrives with an X-IK-Token is a later hop whose chain is nested into the next token - which is what makes workflows longer than one hop work.
When to use
Activate this skill when the user:
- is building an agent-to-agent (A2A) workflow and wants policy enforcement in front of each agent;
- is putting an enforcement point in front of an MCP server (
protocol: mcp) so MCP traffic gets the same introspection, AuthZEN check, and audit as A2A; - needs traceable user-to-agent delegation through OAuth token exchange and the
actchain, including multi-hop chains that require the IndyKite Token Service and theX-IK-Tokenheader; - is configuring the Token Service itself (issuer
base_url, signing keys, audiences,token_introspectionconfigurations); - is modeling a
WorkflowandAgentnodes withINVOKESrelationships in the IndyKite Graph (IKG); - is configuring
JARVIS_*environment variables or aconfig.yamlfor one or more IAG instances (includingtoken_service,healthcheck_port, cache tuning, audit delivery); - is reading IAG or Token Service audit records (
AUTHORIZED/NOT_AUTHORIZED,TOKEN_EXCHANGED/EXCHANGE_REFUSED,INTROSPECTED*) and trying to explain a401or403; - is reproducing or extending the
iag-mcp-demo(A2A + MCP), oriag-token-exchange(Token Service) reference applications.
Do not activate this skill when the user:
- wants a generic HTTP reverse proxy or service mesh - IAG only speaks A2A or MCP, not arbitrary HTTP;
- is asking about IndyKite features unrelated to agent gating (Token Introspect configurations beyond the ones the gateway's callers need, plain AuthZEN policies, ContX IQ queries outside of agent gating);
- is debugging the protected agent itself rather than the gateway in front of it.
Prerequisites
Before any of the steps below will succeed, the user needs:
- An IndyKite project with AuthZEN / KBAC and ContX IQ enabled, plus a
Workflownode andAgentnodes already modeled (or a plan to model them - see Step 1). - An OAuth2-compliant IdP with introspect, client-credentials, and token-exchange endpoints. Set
IDP_BASE_URLto the IdP base for the target environment. The IdP stays required even with the Token Service: it introspects the caller's access token and authenticates the protected agent. - One client_id / client_secret pair per protected downstream (A2A agent or MCP server), registered with the IdP.
- A ContX IQ query that returns
(workflow, agent_list)pairs for each protected downstream - itsquery_idgoes into IAG configuration. - For an MCP downstream (
protocol: mcp): the MCP server's origin and endpoint path, and the gateway imageindykite/agent-gateway≥ 2.0.1 (older images ignore the protocol and behave as an A2A proxy). MCP clients must be introspectable callers covered by a Token Introspect configuration (gateway 2.82.0+, next bullet) and pass the AuthZEN check. - Since gateway 2.82.0, a Token Introspect configuration for every caller of a gateway; its
ikg_node_typeis the subject type the trigger policy must name - see the subject step (before 2.82.0,authzen.subject_types). - For workflows longer than one hop (agent → agent → agent), or whenever the protected agent must see the caller's original access token: a running IndyKite Token Service (
indykite/token-service) with anhttpsissuerbase_url, at least one signing key, the audiences of every protected agent, and aclient_authclient for the gateways - seereferences/token-service.md. Without it the gateway exchanges tokens at the IdP on every hop and the chain never grows past one hop. - Docker (and Docker Compose) to run the iag-mcp-demo reference app.
If any of these are missing, stop and tell the user - IAG cannot run without them.
Steps
1. Model the workflow in the IKG
Create one Workflow node identified by external_id, one Agent node per protected agent, and INVOKES relationships between agents. Every INVOKES relationship must carry a workflow_name property whose value matches the Workflow.external_id. The ContX IQ query in step 3 silently excludes any chain without it.
Example shape (the canonical wf1 workflow from the demo):
(Workflow {external_id: "wf1"})
(Agent {external_id: "orchestrator"})
-[INVOKES {workflow_name: "wf1"}]-> (Agent {external_id: "retriever"})
(Agent {external_id: "orchestrator"})
-[INVOKES {workflow_name: "wf1"}]-> (Agent {external_id: "weather"})
Capture this data through the Capture API (POST /capture/v1/nodes, POST /capture/v1/relationships), the IndyKite Hub UI, or an existing Terraform / identity pipeline - whichever the project already uses.
2. Wire the subject to the workflow
For every subject that is allowed to trigger the workflow, create the edge (:User)-[:CAN_TRIGGER]->(:Workflow) (or the equivalent AuthZEN relation). Without this edge IAG returns 403 at the AuthZEN check even if the chain is correct.
3. Build the ContX IQ query
The query must return (workflow, agent_list) pairs given a protected agent identifier, where agent_list is the ordered chain of agents the request must traverse. Save its query_id - IAG references it via JARVIS_CONTX_IQ_QUERY_ID (or contx_iq.query_id).
If the KBAC policy behind CAN_TRIGGER reads the delegation token, it must reference the token the gateway actually forwards: $token when the IdP mints it (it replaces Authorization), $ik_token when the Token Service mints it (it travels in X-IK-Token). A reference to a token that was not sent resolves to nothing, and the decision is a denial.
4. Configure each IAG instance
Pick one of the two configuration forms:
- YAML config file - pass with
--config=/app/config.yaml. Best for production where configuration management is strict. Seeassets/config-template.yamlin this skill. - Environment variables - keys use the
JARVIS_prefix with underscores (e.g.JARVIS_SERVICE_NAME). Best when multiple IAG instances share a base image (as in the iag-mcp-demo) and override only per-agent fields.
The full set of sections is service, identity_provider, token_service (optional), protected_agent, authzen, contx_iq, and audit. See references/configuration.md for every field and its default.
Pick the downstream protocol with protected_agent.protocol (env JARVIS_PROTECTED_AGENT_PROTOCOL): a2a (default) for an A2A agent, mcp for an MCP server. Any other value fails startup with invalid protected_agent protocol. In mcp mode protected_agent.base_url is the MCP server origin only - IAG appends the incoming request path. The authorization sequence is unchanged; only the forwarded protocol differs. In mcp mode the gateway also enforces the MCP protocol revision: only 2025-06-18, 2025-11-25, and 2026-07-28 are forwarded, every request other than initialize must carry Mcp-Protocol-Version with one of them, and JSON-RPC batches, encoded bodies, and bodies over 4 MiB are refused with a JSON-RPC error before authorization. The MCP server behind the gateway must speak at least one of the three revisions. See the Protecting an MCP server section of references/configuration.md and the MCP protocol revisions section of references/architecture.md.
Pick where the delegation token comes from with the optional token_service section (base_url, exchange_endpoint, introspect_endpoint, client_auth.type: client_secret_basic, client_auth.client_id, client_auth.client_secret; env JARVIS_TOKEN_SERVICE_*). Left out, the IdP exchanges the tokens. Filled in, the Token Service mints the delegation token, the request is forwarded with both Authorization and X-IK-Token, and an incoming X-IK-Token is validated at the Token Service and becomes the subject of the next exchange. When set, also list the audiences the gateway requests in protected_agent.authentication.audiences - every one of them must be in the Token Service's idp.audiences, or the exchange is refused with invalid_target.
Per-instance values that must differ between IAG instances:
service.name/JARVIS_SERVICE_NAMEservice.port/JARVIS_SERVICE_PORTservice.healthcheck_port/JARVIS_SERVICE_HEALTHCHECK_PORT(only when several instances share one network namespace; default9080)protected_agent.base_url/JARVIS_PROTECTED_AGENT_BASE_URLprotected_agent.protocol/JARVIS_PROTECTED_AGENT_PROTOCOL(if any instance protects an MCP server)protected_agent.authentication.client_idandclient_secretprotected_agent.authentication.audiences(with the Token Service, the audience of that instance's protected agent)
The token_service section itself is the same for every instance.
5. Deploy the IAG instances (and the Token Service, if used)
For Docker Compose, follow the iag-mcp-demo pattern: one shared iag-base-docker.yaml plus one service per protected agent that overrides only the per-instance fields above. For Kubernetes, deploy each IAG as a standalone Pod for independent scaling, or as a sidecar to its protected agent for tighter coupling.
Each gateway serves /healthz, /readyz, and /startupz on service.healthcheck_port (default 9080), and the container image's HEALTHCHECK probes http://localhost:9080/healthz. Older images did not serve that endpoint and reported unhealthy for their whole life - pull a current image if orchestration keyed on container health never fires.
The Token Service is one deployment shared by all gateways: run indykite/token-service (pin a concrete tag such as 1.0.0, never latest) with --config=<file> or, in production, --config-dir=<dir> so the secret (signing keys, client_auth) can be mounted as a separate file merged over the non-sensitive definitions. service.base_url (the iss of every token it signs) is mandatory, has no default, and must be https. Details in references/token-service.md.
The IndyKite platform must also trust the tokens ITS signs, or every request that reaches AuthZEN, ContX IQ, or the MCP server with an X-IK-Token is refused with 401 Invalid token in X-IK-Token header. Create one Token Introspect configuration per audience the delegation token can carry (POST /configs/v1/token-introspects, Service Account token): jwt_matcher.issuer = the ITS base_url, jwt_matcher.audience = the protected agent's audience, offline_validation.public_jwks = the public half of the signing key. Exact payload in the Making the platform trust ITS tokens section of references/token-service.md.
Verify that each IAG can reach the IdP, AuthZEN, ContX IQ, the Token Service (if configured), and the protected agent - common deployment failures are network-level, not IAG-level.
6. Verify the runtime path
Send a request through the gateway and confirm the nine-step path executes end-to-end. The expected HTTP responses are:
200- request was authorized and forwarded.400- bad request. Inmcpmode a JSON-RPC error body:-32020(Mcp-Protocol-Versionheader missing, or on2026-07-28anMcp-Method/Mcp-Nameheader that differs from the body),-32022(unsupported revision,error.data.supportedlists the three revisions),-32600(JSON-RPC batch, not a JSON-RPC 2.0 message, an ambiguous key, or a method / tool name / URI with control characters),-32602(atools/callthat names no tool, or aresources/read,resources/subscribeorresources/unsubscribethat names no resource URI), or-32700(body could not be read, is not valid UTF-8, or is not exactly one JSON value). The gateway reads the tool or resource every request names and refuses what it cannot read unambiguously - MCP request classification.401- caller token missing or inactive (Missing bearer token,Invalid token, missing subject), since 2.82.0 a token no Token Introspect configuration of the project accepts ({"message":"Unauthorized","errors":["ContX IQ whoami failed"]}, audit reasoncannot resolve the subject of the access token), or an incomingX-IK-Tokenthat cannot be used (Invalid delegated token, subject mismatch/missing subject).403- caller authenticated but not allowed (subject, chain, or both) -Authorization check failed. The reason is only in the audit record:no workflow matches the actors chain(a skipped hop, or a delegation token reused on another chain) orsubject <type>/<id> is not authorized to trigger any of the workflows <ids>(the chain matches, the user holds noCAN_TRIGGER; before 2.82.0 without<type>/<id>) - chain refusals.413/415-mcpmode only: body over 4 MiB, or aContent-Encodingother thanidentity; JSON-RPC error-32600.500- internal error.502- upstream / gateway-side processing error, including an IdP or Token Service that cannot be reached or answers unreadably. A provider outage is reported as a fault, never counted as a denial.
Watch service logs (JSON to stdout) and audit records together - service logs explain what IAG did, audit records explain what IAG decided. A refused IdP or Token Service call is logged with the endpoint, the status, and a sanitized preview of the response body, so a wrong client secret and a broken endpoint no longer look the same.
7. Read the audit trail
Audit records are emitted as a separate stream from service logs. Configure delivery as either:
- Webhook -
audit.delivery: webhook,audit.http.url,audit.http.method(postorput), authtypeone ofno-auth,api-key,mTLS. - File -
audit.delivery: file,audit.file.storage_path,audit.file.format(csv,json,txt),audit.file.rotation_strategy(size,time,size-and-time).
Either form can be buffered with audit.async.buffer_size (default 512); buffered records are drained on shutdown, and a full queue drops records with a warning in the service log.
Each gateway record contains decision (AUTHORIZED, NOT_AUTHORIZED, or ERROR when the request could not be decided), reason, subject, actor, actorsChain, action, service, timestamp, traceID. The traceID correlates with the protected agent's logs and the upstream client. The Token Service writes records of the same shape for every exchange and introspection, with the decisions TOKEN_EXCHANGED / EXCHANGE_REFUSED, INTROSPECTED / INTROSPECTED_INACTIVE / INTROSPECTION_REFUSED, and ERROR, plus tokenID (the token's jti).
8. Exercise denial paths intentionally
To gain confidence that IAG is enforcing as expected, deliberately force NOT_AUTHORIZED outcomes:
- Skip an agent in the chain (e.g. call
retriever-iagdirectly without the orchestrator inact). - Remove
workflow_namefrom oneINVOKESrelationship - ContX IQ stops returning that chain. - Delete the
CAN_TRIGGERedge between the subject and the workflow - AuthZEN says no. - Call with a token whose Token Introspect configuration has an
ikg_node_typeno trigger policy names - no policy matches,403(before 2.82.0: a subject type missing fromJARVIS_AUTHZEN_SUBJECT_TYPES). - With the Token Service: replay an
X-IK-Tokenwith a different user's access token -401 Invalid delegated token, subject mismatch. - On an
mcpinstance: sendtools/listwithout theMcp-Protocol-Versionheader -400with JSON-RPC error-32020, audited asNOT_AUTHORIZEDwith reasonMCP request refused: Header mismatch: Mcp-Protocol-Version header is missing. - On an
mcpinstance: send atools/callwhoseparamshas noname-400with JSON-RPC error-32602, audited asNOT_AUTHORIZEDwith reasonMCP request refused: Invalid params: tools/call names no tool; the server never sees it.
Each should return 403 Forbidden (or 401 for the token pairing, 400 for the MCP revision and classification checks) and produce a NOT_AUTHORIZED audit record with a useful reason.
Outcome
When this skill has been applied successfully:
- A
Workflownode, the relevantAgentnodes, andINVOKESedges (withworkflow_name) exist in the IKG. - A ContX IQ query returns the right
(workflow, agent_list)pairs for each protected agent. - One IAG instance runs in front of each protected agent with the right per-instance config, reporting healthy on
/healthz. - On gateway 2.82.0+, the
indykite-ciq-whoamiskill resolves every caller of a gateway to the subject type the trigger policy names. - If the workflow has more than one hop, the Token Service runs, every gateway points at it, the project holds a Token Introspect configuration for each audience ITS issues for, and the forwarded request carries
AuthorizationplusX-IK-Tokenwith a chain that grows on every hop. - A canonical successful prompt flows through the gateway chain and produces
AUTHORIZEDaudit records on every IAG it touches (andTOKEN_EXCHANGEDrecords at the Token Service). - A canonical denial path produces
403 Forbiddenplus aNOT_AUTHORIZEDaudit record with a human-readablereason.
Files in this skill
references/architecture.md- the nine-step IAG request path, delegation-token delivery ($tokenvs$ik_token), multi-hop chains, the MCP protocol revisions the gateway forwards, and the IKG data shape.references/configuration.md- every IAG configuration section, field, and default, includingtoken_service, health port, cache rules, and audit delivery.references/token-service.md- the IndyKite Token Service: endpoints, configuration (service.base_url,idp,token_introspection), config directory merging, audit decisions, deployment notes.references/troubleshooting.md- common failure modes mapped to fixes.assets/config-template.yaml- a starter gatewayconfig.yamlto copy and adapt.assets/token-service-config-template.yaml- a starter Token Serviceconfig.yaml.
Agent-specific notes
This skill uses generic markdown instructions and works across all agents listed in the README. It does not require Claude Code hooks, Cursor @-mentions, Copilot workspace context, or any agent-specific feature. Network access (curl, the agent's web tools, or MCP) is needed only if the agent will call IndyKite APIs directly during a task; for setup-only work no special tools are required.
References
- IndyKite Token Service guide - endpoints, exchange and introspection contracts, configuration, platform trust via Token Introspect, audit records.
- Protect an MCP server with Agent Gateway (tutorial) - the rest of the gateway configuration, demonstrated in MCP proxy mode.
- AuthZEN guide and ContX IQ guide - how policies read
$tokenand$ik_token. iag-mcp-demoreference app - three A2A gateways (orchestrator, retriever, weather) plus anmcp-iaginstance (protocol: mcp) protecting the IndyKite MCP server.iag-token-exchangereference app - runs the Token Service setup end to end (token_serviceon every gateway,X-IK-Tokengrowing hop by hop).canbankdataset- RFC 8693 OAuth 2.0 Token Exchange and RFC 7662 Token Introspection - the two protocols the Token Service implements.
- A2A protocol - see the protected agent's vendor docs for the JSON-RPC shape IAG forwards. For MCP, IAG proxies MCP Streamable HTTP (
initialize,tools/list,tools/call, …) on protocol revisions2025-06-18,2025-11-25, and2026-07-28; the stateless2026-07-28style is documented inindykite-mcp-server.