The Workflow and Agent nodes, CAN_TRIGGER grant, and INVOKES chain that authorize Google Drive access - plus one workflow per call shape.
Chapter 5: Model the wf-drive workflow in the IKG
The gateway authorizes an MCP request only if (a) the subject can CAN_TRIGGER a workflow that invokes the protected agent and (b) the request's delegation chain matches that workflow's modeled agent chain. Both facts live in the Identity Knowledge Graph (IKG). This chapter adds them for the Drive downstream.
1. Register the IdP client
Create a machine-to-machine client indykiteagent-drive in your IdP (client credentials and token exchange flows; no redirect URI). The client ID must exactly match the Agent node's external_id - the gateway matches delegation-chain actors against agent IDs from the graph.
2. Create the nodes
Via the Capture API (POST /capture/v1/nodes) - one Workflow and one Agent:
{
"nodes": [
{ "external_id": "wf-drive", "type": "Workflow", "is_identity": false, "properties": [] },
{ "external_id": "indykiteagent-drive", "type": "Agent", "is_identity": false, "properties": [] }
]
}
3. Create the relationships
Via POST /capture/v1/relationships. Two edges matter: the grant (who may trigger the workflow) and the chain (which agent the workflow invokes). Every INVOKES edge in a chain must carry a workflow_name property equal to the workflow's external_id - the workflow-resolution query filters on it at every hop and silently drops chains without it.
{
"relationships": [
{
"source": { "type": "User", "external_id": "millicent" },
"target": { "type": "Workflow", "external_id": "wf-drive" },
"properties": [],
"type": "CAN_TRIGGER"
},
{
"source": { "type": "Workflow", "external_id": "wf-drive" },
"target": { "type": "Agent", "external_id": "indykiteagent-drive" },
"properties": [ { "type": "workflow_name", "value": "wf-drive" } ],
"type": "INVOKES"
}
]
}
In the demo dataset only millicent gets this grant. Every other user is denied at the gateway - that asymmetry is the demo.
4. Create the KBAC policy
The gateway's AuthZEN check needs one policy answering "can this subject trigger this workflow?". It is generic over all workflows - if you already provisioned it for other gateway instances, there is nothing to add; wf-drive is covered the moment it exists in the graph.
{
"meta": {
"policyVersion": "1.0-kbac"
},
"subject": {
"type": "User"
},
"actions": [
"CAN_TRIGGER"
],
"resource": {
"type": "Workflow"
},
"condition": {
"cypher": "MATCH (subject)-[:CAN_TRIGGER]->(resource:Workflow)"
}
}
The subject type must be listed in the gateway's authzen.subject_types setting (the demo configures User). If you use more subject types, create one policy per type.
The direct-edge condition above is all wf-drive needs. The demo dataset provisions a department-aware variant - MATCH (subject:User)-[:WORKS_IN|CAN_TRIGGER*..3]->(resource:Workflow) - so users also inherit workflow access through their department (a User -WORKS_IN-> Department -CAN_TRIGGER-> Workflow path). That is how the support and trading staff reach wf1 in chapter 7's persona prompts.
5. Create the workflow-resolution ContX IQ query
The gateway's second question - "which workflows invoke the agent I protect, through which chains?" - is answered by a ContX IQ query (the demo names it get-agent-workflows). Given $agent_id, it returns one (workflow, agent_list) pair per allowed chain, keeping only chains whose every hop carries the matching workflow_name:
{
"meta": {
"policy_version": "1.0-ciq"
},
"subject": {
"type": "_Application"
},
"condition": {
"cypher": "MATCH (subject:_Application) MATCH (wf:Workflow)-[rels:INVOKES*]->(a:Agent {external_id: $agent_id}) WHERE ALL(r IN rels WHERE r.workflow_name = wf.external_id AND endNode(r):Agent) WITH subject, wf.external_id AS workflow, [r IN rels | endNode(r).external_id] AS agent_list",
"filter": []
},
"allowed_reads": {
"nodes": [],
"relationships": [],
"aggregate_values": [
"workflow",
"agent_list"
]
}
}
This query is also shared by every gateway instance - record its name or GID once; it goes into the gateway configuration as the ContX IQ query ID (chapter 6). With the graph from this chapter fully modeled, running it for agent_id = indykiteagent-drive returns one row per call shape (the two extra shapes are modeled in the next section):
{
"data": [
{ "aggregate_values": { "workflow": "wf-drive", "agent_list": ["indykiteagent-drive"] } },
{ "aggregate_values": { "workflow": "wf-drive-analyst", "agent_list": ["indykiteagent-4", "indykiteagent-drive"] } },
{ "aggregate_values": { "workflow": "wf-drive-console", "agent_list": ["indykiteagent", "indykiteagent-4", "indykiteagent-drive"] } }
]
}
One workflow per call shape
The gateway resolves one agent chain per workflow. If the same protected agent should be reachable through several different chains - directly, via another agent, via a console orchestrator - model each shape as its own workflow. The demo wires four:
wf-drive |
indykiteagent-drive |
Direct MCP calls to the Drive gateway (chapter 7). |
wf-drive-analyst |
indykiteagent-4 → indykiteagent-drive |
A prompt to the analyst agent, which calls Drive as an MCP backend. |
wf-drive-console |
indykiteagent → indykiteagent-4 → indykiteagent-drive |
The chatbot console: orchestrator → analyst → Drive. |
wf3-console |
indykiteagent → indykiteagent-4 → indykiteagent-mcp |
The console-routed analyst reaching the IndyKite MCP server. |
When two workflows share an agent-to-agent hop (for example indykiteagent-4 → indykiteagent-drive in both the analyst and console shapes), create parallel INVOKES edges - one per workflow - and add a discriminating_property property with value workflow_name to each, so the edges stay distinct per workflow:
{
"source": { "type": "Agent", "external_id": "indykiteagent-4" },
"target": { "type": "Agent", "external_id": "indykiteagent-drive" },
"properties": [
{ "type": "workflow_name", "value": "wf-drive-analyst" },
{ "type": "discriminating_property", "value": "workflow_name" }
],
"type": "INVOKES"
}
Grant CAN_TRIGGER per shape as well - in the demo, millicent holds it on all three wf-drive* workflows.
Provisioning shortcut
The canbank-iag companion app provisions this entire dataset - nodes, relationships, the KBAC policy, and the ContX IQ queries - through pre-filled forms, and the demo repo carries the same payloads as a Bruno collection (bruno/iag-demo/ingest/agent-workflow). Both are safe to re-run: ingestion is an upsert.
What comes next
Chapter 6 configures the gateway instance that enforces all of this: drive-mcp-iag.