Skip to content

MCP tools

No matter how many capabilities you wire in, the model always sees exactly two tools: flowgate.query for all reads and flowgate.command for all state-changing writes. The split follows CQRS — if it has side effects, it’s a command; if it doesn’t, it’s a query.

The tool name carries nearly no decision weight for the model. What does the work is the links[] array every response carries: pre-filled { method, args } objects the model copies verbatim to make the next call. The schema declares what’s valid; the response declares what’s valid right now.


Side-effect-free reads. Covers: catalog browsing (home), full-text search, fragment describe, workflow get, workflow explain, and lexicon reads.

Schema — all fields optional; the combination of fields present selects the operation:

{
"query": "string",
"kind": "string",
"subject": "string",
"workflowId": "string",
"transition": "string",
"limit": "integer"
}
Args presentOperation
(none)home — entry point; links to search and list operations
querysearch — full-text scored results
subject onlydescribe — live-config fragment or capability
subject + workflowIddescribe — against the instance’s pinned snapshot (§8.2)
workflowId aloneget — current workflow state
workflowId + transitionexplain — inspect a transition definition

kind and limit are optional modifiers on search; they don’t change which dispatch row matches. kind filters the result set. The always-available values are workflow, capability, and connection. lexicon is also accepted and routes to a lexicon search. skill and script are authoring-time only and flag-gated — they return INVALID_PARAMS unless the server was started with with_skills_search(true) / with_scripts_search(true). limit caps the result count.

Audit: every describe call emits a guidance.describe_requested audit event — including browse-time describe (subject alone), where the event’s workflowId is null. When the call includes both subject AND workflowId, the event carries that workflowId — i.e., “guidance fetched for use in a running workflow.”


Returns the full capability catalog. Entry point for a cold-start model.

Example request:

{
"name": "flowgate.query",
"arguments": {}
}

Example response:

{
"resource": { "type": "gateway", "id": "home" },
"result": {
"status": "ready",
"message": "Available workflows and proxy capabilities can be discovered here."
},
"links": [
{
"rel": "search",
"title": "Search workflows and capabilities",
"method": "flowgate.query",
"args": { "query": "" },
"inputSchema": {
"type": "object",
"required": ["query"],
"properties": {
"query": { "type": "string" },
"kind": { "type": "string", "enum": ["workflow", "capability", "connection"] },
"limit": { "type": "integer", "default": 10 }
},
"additionalProperties": false
}
},
{
"rel": "list_workflows",
"title": "List configured workflows",
"method": "flowgate.query",
"args": { "query": "", "kind": "workflow" }
},
{
"rel": "list_capabilities",
"title": "List proxy capabilities",
"method": "flowgate.query",
"args": { "query": "", "kind": "capability" }
}
]
}

home is a thin entry point, not a catalog dump. It hands the model three links — search, list_workflows, list_capabilities — and the model follows one to enumerate what’s actually available. The model chains these instead of guessing call shapes.


Full-text search across all indexed capabilities, workflows, skills, scripts, and lexicon entries.

Example request:

{
"name": "flowgate.query",
"arguments": {
"query": "deploy",
"kind": "workflow",
"limit": 5
}
}

Example response:

{
"query": "deploy",
"kind": "workflow",
"items": [
{
"score": 14.0,
"item": {
"id": "deploy_pipeline",
"kind": "workflow",
"title": "Deploy Pipeline",
"description": "Lint, test, build, and deploy a service.",
"tags": ["deploy", "pipeline"],
"links": [
{
"rel": "start",
"method": "flowgate.command",
"args": { "definitionId": "deploy_pipeline", "input": {} }
}
]
}
}
],
"links": [
{ "rel": "home", "method": "flowgate.query", "args": {} }
]
}

Scoring weights:

FieldWeightWhat it matches
title6.0Capability or workflow title
id5.0The identifier
tags3.0Tags array
aliases3.0Search synonyms declared in config
description2.0Description text
text1.0State names, transition names, guidance text

Lexicon search — pass kind: "lexicon" to restrict to the lexicon:

{ "query": "evidence", "kind": "lexicon" }

Returns { "hits": [...] }, where each hit is the full stored lexicon entry with term added — e.g. { "term", "definition_short", "bounded_context", "refs"?, "governance"?, "state"? }. The exact keys depend on what the entry was authored with (an aliases array, for instance, appears only when the entry declares one); a hit is not a fixed subset.


Returns the full details for a single capability, workflow definition, skill, script, or lexicon entry.

Example request:

{
"name": "flowgate.query",
"arguments": {
"subject": "github.list_issues"
}
}

Example response:

For a workflow, capability, or connection, describe wraps the matched DiscoveryItem under item and attaches navigation links:

{
"id": "github.list_issues",
"item": {
"id": "github.list_issues",
"kind": "capability",
"title": "List GitHub issues",
"description": "List issues from a GitHub repository.",
"tags": ["github", "issues", "read"],
"links": [
{
"rel": "start",
"method": "flowgate.command",
"args": { "definitionId": "proxy_default", "input": { "capability": "github.list_issues" } },
"input_schema": {
"type": "object",
"required": ["repo"],
"properties": {
"repo": { "type": "string" }
}
}
}
]
},
"links": [
{ "rel": "home", "method": "flowgate.query", "args": {} },
{ "rel": "search", "method": "flowgate.query", "args": { "query": "" } }
]
}

Guidance (skill) and script subjects return a flatter wire shape instead — { "kind", "subject", "verb", "body", "links", "lexicon"? } — since they carry a guidance body rather than a HATEOAS-driven DiscoveryItem.

Lexicon lookup — use the lexicon: subject prefix to look up a term:

{ "subject": "lexicon:evidence-pack" }

Returns { "term", "entry" } where entry may be null if the term has no definition yet. A lookup against any registered alias returns the same entry as the canonical term.


Check the current state of a running workflow.

Example request:

{
"name": "flowgate.query",
"arguments": {
"workflowId": "wf_a1b2c3d4e5f6"
}
}

Example response:

{
"workflow": {
"id": "wf_a1b2c3d4e5f6",
"definitionId": "deploy_pipeline",
"definitionVersion": 1,
"state": "ready_to_deploy",
"version": 4
},
"result": { "status": "waiting_for_action" },
"context": {
"lintPassed": true,
"testsPassed": true,
"coverage": 87.5,
"artifactId": "art_xyz789"
},
"links": [
{
"rel": "deploy",
"title": "Deploy to environment",
"description": null,
"method": "flowgate.command",
"actor": "agent",
"args": {
"workflowId": "wf_a1b2c3d4e5f6",
"expectedVersion": 4,
"transition": "deploy"
},
"inputSchema": { "type": "object" }
},
{
"rel": "abort",
"title": "Abort deployment",
"description": null,
"method": "flowgate.command",
"actor": "agent",
"args": {
"workflowId": "wf_a1b2c3d4e5f6",
"expectedVersion": 4,
"transition": "abort"
},
"inputSchema": { "type": "object" }
}
],
"evidence": []
}

The instance lives under workflowworkflow.id, workflow.state, workflow.version. The context accumulates data from previous transitions. Each link’s rel is the transition name (deploy, abort), and args.transition repeats it. The links show every legal next move; a link only carries args.arguments when the transition declares prefill.


Inspect a workflow definition or a specific transition without starting anything.

Example request (specific transition):

{
"name": "flowgate.query",
"arguments": {
"workflowId": "wf_a1b2c3d4e5f6",
"transition": "deploy"
}
}

Example response:

{
"workflowId": "wf_a1b2c3d4e5f6",
"currentState": "ready_to_deploy",
"transition": "deploy",
"allowedFromCurrentState": true,
"actor": "agent",
"deterministic": false,
"legalTransitionsNow": ["deploy", "abort"]
}

explain answers “can I fire this transition from where the instance is right now, and what else is legal?” — allowedFromCurrentState is the verdict, legalTransitionsNow lists the transition names available from currentState, and deterministic is true when the transition’s actor is deterministic (the runtime would chain through it automatically). It does not expose the transition’s guards, target state, input schema, or executor.


The subject field admits a colon-prefixed namespace. Only the lexicon: prefix is interpreted specially by query-time describe; everything else is resolved by an exact match on the discovery index id:

PrefixResolves to
(none)guidance fragment, workflow definition, capability, script, or connection — matched by exact id against the discovery index
lexicon:<term>a single lexicon entry (the only prefix the query path special-cases)
workflow:<id>reserved; not resolved as a prefix — the runtime matches workflow:<id> literally as an id and finds nothing unless an item is named that
script:<subject>reserved; not resolved as a prefix (same literal-id behaviour)
skill:<subject>reserved; not resolved as a prefix (same literal-id behaviour)

Pass unprefixed subjects for workflows, capabilities, connections, scripts, and guidance — the discovery index resolves them by id. The lexicon: prefix is the only one that switches resolution path.


State-changing writes. Covers: workflow start, workflow transition submit, and lexicon define (when enabled).

Schema — all fields optional; the combination of fields present selects the operation:

{
"definitionId": "string",
"input": "object",
"workflowId": "string",
"expectedVersion": "integer",
"transition": "string",
"arguments": "object",
"subject": "string",
"definition": "object",
"summary": "string",
"traceId": "string",
"runId": "string",
"intent": "string",
"unknown_subject": "string"
}
Args presentOperation
definitionId, no workflowId, no subjectstart — create a new workflow instance
workflowId + transition + expectedVersionsubmit — fire a transition
subject (namespaced) + definitiondefine — write a lexicon entry

input and runId are modifiers on start. arguments and summary are modifiers on submit. traceId and runId thread through to audit on any operation. intent plus unknown_subject drive the out-of-band cancel_pending_subject resolution (note unknown_subject keeps its snake_case wire key to match the cancel link emitted by SUBJECT_NEEDS_DEFINITION).


Create a new workflow instance at its initial state.

Example request:

{
"name": "flowgate.command",
"arguments": {
"definitionId": "deploy_pipeline",
"input": {
"service": "payments",
"environment": "staging"
}
}
}

Example response:

{
"workflow": {
"id": "wf_a1b2c3d4e5f6",
"definitionId": "deploy_pipeline",
"definitionVersion": 1,
"state": "lint",
"version": 1
},
"result": { "status": "started" },
"context": {},
"links": [
{
"rel": "run_lint",
"title": "Run linter",
"description": null,
"method": "flowgate.command",
"actor": "agent",
"args": {
"workflowId": "wf_a1b2c3d4e5f6",
"expectedVersion": 1,
"transition": "run_lint"
},
"inputSchema": { "type": "object" }
}
],
"evidence": []
}

Each link is a pre-filled flowgate.command submit call — the model copies args verbatim. The link rel is the transition name (run_lint).

If the initial state has actor: deterministic transitions, the runtime chains through them automatically and returns the first state that needs a decision (the chain steps appear under a chain array when any fired).

runId uniqueness: supply runId to assert uniqueness. If an instance with that runId already exists, the call returns RUN_ID_ALREADY_RUNNING (see error shapes below) rather than creating a duplicate. Omit runId to let the runtime mint one.


Advance a workflow by firing a transition.

Example request:

{
"name": "flowgate.command",
"arguments": {
"workflowId": "wf_a1b2c3d4e5f6",
"expectedVersion": 4,
"transition": "deploy",
"arguments": {
"confirm": true
}
}
}

Example response (success):

{
"workflow": {
"id": "wf_a1b2c3d4e5f6",
"definitionId": "deploy_pipeline",
"definitionVersion": 1,
"state": "deployed",
"version": 5
},
"result": { "status": "completed" },
"context": {
"lintPassed": true,
"testsPassed": true,
"coverage": 87.5,
"artifactId": "art_xyz789",
"deploymentId": "dep_abc123"
},
"links": [],
"evidence": []
}

There is no top-level terminal flag. A workflow is terminal when result.status is "completed" and links is empty — the runtime forces result.status to completed for any terminal state. A non-terminal submit returns result.status "executed" (and a non-empty links array of legal next transitions).

Submit error codes:

CodeMeaning
STALE_WORKFLOW_VERSIONexpectedVersion doesn’t match. Fetch current state with flowgate.query({workflowId}) and retry.
ACTOR_MISMATCHTransition requires a human principal but submitter is not human.
GUARD_REJECTEDOne or more guards rejected the transition.
INPUT_SCHEMA_VIOLATIONArguments don’t match the transition’s input schema.
INVALID_TRANSITIONTransition doesn’t exist or isn’t legal from the current state.
EXECUTOR_FAILEDExecutor ran but failed.

Write a lexicon entry. Requires with_lexicon_writes(true) on the server (default off in production deployments).

Example request:

{
"name": "flowgate.command",
"arguments": {
"subject": "lexicon:evidence-pack",
"definition": {
"definition_short": "A structured set of artefacts that justify a decision.",
"boundedContext": "swe-agent",
"refs": ["evidence-packs", "evidence pack"],
"governance": "human-only"
}
}
}

The definition body reads four fields: definition_short (required), boundedContext (camelCase on input), refs, and governance. Synonyms are carried as refs. Any other keys — including a definition_long or aliases — are ignored.

Example response:

{
"term": "evidence-pack",
"entry": {
"definition_short": "A structured set of artefacts that justify a decision.",
"bounded_context": "swe-agent",
"refs": ["evidence-packs", "evidence pack"],
"governance": "human-only"
},
"persisted_to": "overlay"
}

Note the echoed entry stores bounded_context in snake_case — the dispatcher reshapes the camelCase input key on the way in.

Emits a lexicon.defined audit event on success. Defines land in a gateway-lifetime in-memory overlay. Persist them by editing flowgate.yaml and reloading.

Define error codes:

CodeMeaning
LEXICON_DEFINE_REQUIRES_HUMANCaller is an agent; term governance is human-only. Route through an actor: human transition to commit.
LEXICON_WRITES_DISABLEDServer was started without lexicon write support. See operator path in the error’s links[].
INVALID_LEXICON_ENTRYDefinition shape is malformed.
LEXICON_ALIAS_COLLISIONAn alias collides with another entry’s term or alias in the same bounded context.

Ambiguous argument combinations return a structured error — never an MCP protocol error:

Fired when the args don’t match any known dispatch shape (e.g., conflicting dispatch keys are present). The flowgate.command variant returns three example links — one per command shape:

{
"error": {
"code": "AMBIGUOUS_INTENT",
"message": "flowgate.command args do not match a known dispatch shape",
"hint": "see §32 dispatch table: start (definitionId only), submit (workflowId+expectedVersion+transition), define (subject namespaced + definition)"
},
"links": [
{ "rel": "start_example", "method": "flowgate.command", "args": { "definitionId": "<your-workflow>" } },
{ "rel": "submit_example", "method": "flowgate.command", "args": { "workflowId": "<id>", "expectedVersion": 0, "transition": "<name>" } },
{ "rel": "define_example", "method": "flowgate.command", "args": { "subject": "lexicon:<term>", "definition": { "definition_short": "..." } } }
]
}

The flowgate.query variant carries the message "flowgate.query args do not match a known dispatch shape" and two links — { rel: "home" } and { rel: "search" }.

Fired when define is called on a server with lexicon writes off.

{
"error": {
"code": "LEXICON_WRITES_DISABLED",
"message": "This runtime does not accept lexicon define commands.",
"hint": "Operators add lexicon terms via the `flowgate lexicon define` CLI subcommand."
},
"links": [
{ "rel": "operator_path", "method": "cli", "args": { "command": "flowgate lexicon define <term> <definition>" } },
{ "rel": "lookup", "method": "flowgate.query", "args": { "subject": "lexicon:<term>" } }
]
}

The cli rel is informational — it names the operator-facing alternative, not a tool the model can call. The lookup rel keeps the read path discoverable.

Fired when a start command supplies a run_id that already exists.

{
"error": {
"code": "RUN_ID_ALREADY_RUNNING",
"message": "An instance already exists with run_id 'r-abc123'.",
"hint": "Each run_id is single-use. Fetch the existing instance with the linked get, or retry with a fresh run_id."
},
"links": [
{ "rel": "get", "method": "flowgate.query", "args": { "workflowId": "<existing>" } }
]
}

Fired when a workflow start (or mid-workflow transition) encounters a subject that has no lexicon entry. The runtime pauses the command and returns a structured interaction instead of executing:

{
"interaction": {
"kind": "SUBJECT_NEEDS_DEFINITION",
"unknown_subject": "evidence-foo",
"context": {
"encountered_in": "workflow:swe_agent state:retrieving",
"bounded_context": "swe-agent"
},
"candidates": [
{ "term": "evidence-pack", "distance": 2, "match_kind": "fuzzy_close",
"definition_preview": "A bundle of facts the editor uses…" }
]
},
"queued_command": {
"method": "flowgate.command",
"args": { }
},
"links": [
{
"rel": "link_as_alias",
"method": "flowgate.command",
"args": { "subject": "lexicon:evidence-pack", "definition": { "aliases_add": ["evidence-foo"] } },
"hint": "Use this if 'evidence-foo' is a synonym for 'evidence-pack'."
},
{
"rel": "define_new",
"method": "flowgate.command",
"args": { "subject": "lexicon:evidence-foo", "definition": { "definition_short": "<fill in>", "boundedContext": "swe-agent" } },
"hint": "Use this if 'evidence-foo' is a genuinely new concept."
},
{
"rel": "cancel",
"method": "flowgate.command",
"args": { "intent": "cancel_pending_subject", "unknown_subject": "evidence-foo" },
"hint": "Abandon the original command — the subject was a mistake."
}
]
}

Follow one link. Resolution updates the live lexicon (or drops the placeholder for cancel). The client retries the original command. See Lexicon for the full PENDING_DEFINITION / SUBJECT_NEEDS_DEFINITION flow.


Every response — success or error — carries a links[] array:

{ "rel": "string", "method": "flowgate.query" | "flowgate.command", "args": { ... } }

The args object is pre-filled with the exact shape the next operation needs. Models chain by copying link.args verbatim — they never derive the next call from the schema. Static schema declares what’s valid; dynamic links[] declares what’s valid right now.