Part III: The MCP Binding
Status: Normative
This part defines how the abstract Pilotage model specified in Part II is represented over the Model Context Protocol.
It specifies:
- the extension identifier and capability negotiation;
- the Pilotage
_metanamespace; - the declaration of program-valued tool arguments;
- manifest discovery;
- the mapping of every Pilotage operation onto MCP primitives;
- reserved loop-tool argument names;
- result-envelope requirements;
- the mapping of Pilotage errors onto MCP and JSON-RPC;
- session, transport, and state semantics;
- complete wire examples;
- binding-specific conformance requirements.
Every requirement in Part II continues to apply unchanged.
This part adds only the concrete MCP representation.
The conventions defined in Part II Section 1 apply throughout this part, including:
- requirements language;
- field-table notation;
- unknown-member handling;
- unknown-value fallbacks;
- opaque-token handling.
This binding supports MCP revision 2025-06-18 and later, including:
2025-11-25;- the stateless
2026-07-28revision (a release candidate at the time of writing; final scheduled 2026-07-28).
Where the stateless revision changes the applicable mechanism, this part states the difference explicitly.
1. Extension identity#
1.1 Identifier#
The Pilotage extension identifier is:
io.github.jafarsa0/pilotageThis identifier follows the MCP extensions framework identifier form:
vendor-prefix/extension-nameThe vendor prefix is the reverse-domain form of the author-controlled domain:
jafarsa0.github.ioA future revision MAY migrate to a prefix based on a Pilotage-owned domain.
A breaking change that creates a new Pilotage major version (Part II Section 11.1) MUST also create a new extension identifier, as the MCP extensions framework requires for breaking changes.
For example:
io.github.jafarsa0/pilotage-v2Compatibility note (informative)
Earlier Pilotage documents used:
io.github.jafarsa0.pilotageThat dotted form is not wire-normative under this binding. The slash form defined above is the only normative identifier.
1.2 Negotiation#
Pilotage is negotiated through MCP's extensions capability map.
The map associates an extension identifier with a settings object.
An empty settings object means:
supported, with no additional settings
Handshake-based MCP revisions#
On MCP revisions that use initialize, a Client implementing Pilotage MUST declare:
{
"capabilities": {
"extensions": {
"io.github.jafarsa0/pilotage": {}
}
}
}A Server implementing Pilotage MUST declare the same extension in its initialize result:
{
"capabilities": {
"extensions": {
"io.github.jafarsa0/pilotage": {}
}
}
}The settings object MAY include:
{
"version": "1.1"
}The manifest's pilotage field remains the authoritative version declaration.
Receivers MUST ignore unknown settings members.
Stateless MCP revision 2026-07-28#
The stateless revision has no initialize handshake.
The Client's extension capability map is carried with each request in:
_meta["io.modelcontextprotocol/clientCapabilities"]This applies to every request, including tools/list.
The Server's extension declarations are obtained through server/discover.
The Pilotage extension entry has the same form in both locations.
Compatibility across revisions#
The extensions capability is schema-formal only from MCP revision 2026-07-28.
On revisions 2025-06-18 and 2025-11-25, it is an additive member that some intermediaries or SDKs may remove.
For that reason, the pilotage_manifest tool defined in Section 3.1 is the guaranteed detection mechanism.
A Server that:
- exposes
pilotage_manifest; and - returns a valid Pilotage manifest
thereby declares Pilotage support.
A Client MUST NOT conclude that Pilotage is unsupported solely because the extension capability declaration is absent when pilotage_manifest exists and returns a valid manifest.
1.3 Graceful degradation#
The MCP extensions framework requires that when only one party supports an extension, the supporting party revert to core protocol behavior, or reject the request if the extension is mandatory.
Accordingly:
-
A Server MUST NOT alter the behavior of core MCP operations for a Client that did not declare Pilotage.
-
A Server SHOULD expose Pilotage tools to all Clients because they are ordinary MCP tools and degrade naturally.
-
A Server MAY hide those tools from Clients that did not declare Pilotage.
-
A Client that finds neither:
- a Pilotage extension declaration; nor
- a validly answering
pilotage_manifesttool
MUST NOT infer Pilotage semantics from tools with similar names.
In particular, it MUST NOT assume:
- Pilotage manifest semantics;
- closed diagnostic sets;
- Pilotage trace rules;
- Pilotage error routing.
2. The _meta namespace#
Every Pilotage _meta key uses the vendor prefix:
io.github.jafarsa0/This binding defines exactly one Pilotage _meta key:
io.github.jafarsa0/pilotageThe value associated with this key is an object.
Its allowed members depend on where the key appears, as defined in Sections 3 and 5.
The full reverse-domain key is wire-normative.
A sender MUST NOT emit:
pilotageor any other shorthand key.
A receiver MUST NOT interpret a shorthand key as Pilotage metadata.
3. Discovery#
Part II requires the manifest to be retrievable before any other Pilotage interaction.
This binding provides:
- one mandatory discovery path;
- two optional accelerators;
- the cold-start signposts that lead an uninstructed agent into the loop.
3.1 Manifest tool#
A conformant Server MUST expose an MCP tool named:
pilotage_manifestIts input schema is an object with no required members.
Its result contains the Part II manifest in structuredContent, using the result envelope defined in Section 6.
This tool is the guaranteed discovery mechanism because it works across:
- every supported MCP revision;
- every supported transport;
- hosts that do not expose resources to the Model;
- stateless deployments.
3.2 Optional discovery accelerators#
Initialize metadata#
On an MCP revision with initialize, a Server MAY attach the manifest to the initialize result:
{
"_meta": {
"io.github.jafarsa0/pilotage": {
"manifest": {
"pilotage": "1.1",
"languages": [ "… one or more Language entries (Part II 5.1) …" ]
}
}
}
}Instead of an inline manifest, the object MAY contain:
{
"manifest_uri": "pilotage://manifest"
}When the Pilotage _meta key is present in this location, at least one of the following MUST be present:
manifest;manifest_uri.
Manifest resource#
A Server MAY expose the manifest as an MCP resource.
The RECOMMENDED URI is:
pilotage://manifestThe pilotage:// scheme is a convention, not a requirement.
Any resource URI advertised through manifest_uri is valid.
3.3 Precedence and freshness#
When multiple discovery paths are available:
- an inline manifest in initialize metadata takes precedence for the current Session;
- otherwise, the Client calls
pilotage_manifest.
When both an inline manifest and the manifest tool are used and their contents differ, the result from pilotage_manifest is authoritative because it is newer.
Manifest changes during a Session#
When the Server changes its manifest during a Session and declares tools.listChanged, it SHOULD emit:
notifications/tools/list_changedManifest changes that affect the tool surface coincide with a tool-list change.
A Client receiving that notification SHOULD repeat discovery before its next Pilotage interaction.
Stateless deployments#
On MCP revision 2026-07-28:
- the initialize accelerator does not exist;
- notifications may not be deliverable.
Discovery therefore becomes:
server/discover
→ tools/list
→ pilotage_manifestThe Server MAY attach Pilotage discovery metadata to server/discover.
That metadata has the same precedence as initialize metadata.
Without notifications, a Client SHOULD rediscover:
- on a Host-defined policy interval;
- after
unknown_baseline; - after
catalog_drift; - after
not_foundfor a name the Client believed to be current.
3.4 Cold start#
Discovery is designed for the agent that arrives with nothing.
The baseline this binding assumes:
- no client-side instructions about this Server;
- no prior knowledge of its Languages;
- only the connection, the user's request, and whatever the host relays of the standard surfaces (at minimum, the tool list).
For that agent, the trail into the loop is carried by three signposts.
In this section, a tool's stage is the Section 8.2 loop operation it binds.
The welcome text#
On an MCP revision with initialize, whenever the Pilotage tools are
exposed to the Client (Section 1.3), the Server MUST include, in the
initialize result's instructions field, text that:
- names Pilotage (the extension identifier or the word "Pilotage"); and
- directs the agent to call
pilotage_manifestfirst.
On MCP revision 2026-07-28, the same duty applies to the server
description carried by server/discover when that revision provides a
server-description text surface; when it does not, the entry-point
description below carries the whole welcome duty.
The entry-point description#
The pilotage_manifest tool description MUST identify the tool as the
starting point for using this Server's program Languages, sufficient
for an agent reading tools/list alone to conclude: call this first.
This signpost is the load-bearing one. Hosts differ in whether they show
instructions or _meta to the Model; every host shows tool
descriptions.
The stage descriptions#
The description of pilotage_get_guide MUST state that it serves the
learn stage: the guides.
When any Language declares catalog, the description of
pilotage_catalog MUST state that it serves the lookup stage: the
catalog.
pilotage_manifest carries the entry-point duty above and no stage duty.
The description of each declared loop tool MUST state:
- which stage it serves (validate, execute, or trace fetch); and
- the Language name(s) it serves.
Loop-tool names are chosen per Language, so for an uninstructed agent the description is the only reliable signal of what a loop tool is.
Read together, the tool descriptions alone reconstruct the map: what to call first, where to learn, where to look up names, how to check, how to run, and how to inspect.
Content, not wording#
These rules constrain what the signposts must communicate, never their wording. A Server keeps its own voice.
Signposts are Server claims. They carry the trust status defined in Part II Section 9.
4. Tool surface#
Pilotage uses two categories of ordinary MCP tools.
Fixed-name access tools#
The binding defines these names:
pilotage_manifest
pilotage_get_guide
pilotage_catalogLoop tools#
Each Language selects the tool names used for:
validate;execute;trace: present iff the Engine supports fetching stored traces.
Those names are declared in the Language entry's loop object.
All Pilotage tools:
- appear in
tools/list; - are invoked through
tools/call; - return
CallToolResult.
Manifest reference-field binding#
In this MCP binding:
- a Language entry's
guidesfield MUST contain:
pilotage_get_guide
- a Language entry's
catalogfield MUST contain:
pilotage_catalog
The operative access path is always the tool, with the Language selected through the language argument.
A Server that also exposes optional resource surfaces MAY include:
guides_resource;catalog_resource.
Each contains the resource URI of the corresponding index.
A Client MUST NOT assign any other MCP-binding meaning to the manifest's guides, catalog, guides_resource, or catalog_resource fields.
4.1 pilotage_get_guide#
This tool binds the abstract learn operation.
Input schema#
{
"type": "object",
"properties": {
"language": { "type": "string" },
"id": { "type": "string" },
"topic": { "type": "string" }
},
"required": ["language"]
}The language member is REQUIRED, including on a Server exposing only one Language.
Index form#
When id is absent, the tool performs the guide-index form.
structuredContent contains:
{
"guides": []
}Each entry contains every Part II Section 5.2 guide field except the body.
Fetch form#
When id is supplied, structuredContent contains:
{
"guide": {
"id": "guide-id",
"body": "guide content"
}
}The returned guide contains:
- every Part II guide metadata field;
body.
body is a string in the guide's declared format (Part II Section 5.2).
The body field exists only in the fetch form.
Topic form#
When:
idis supplied;topicis supplied;guides.topic_fetchis declared,
the response uses the same guide shape, but body contains only the requested topic.
Errors#
An unknown:
language;id;topic
is a request error under Section 7.
4.2 pilotage_catalog#
This tool binds the abstract lookup operation.
A Server MUST expose it exactly when at least one Language declares catalog.
Input schema#
{
"type": "object",
"properties": {
"language": { "type": "string" },
"verb": { "type": "string", "enum": ["list", "get", "changed_since"] },
"kind": { "type": "string" },
"tag": { "type": "string" },
"q": { "type": "string" },
"cursor": { "type": "string" },
"limit": { "type": "integer", "minimum": 1 },
"id": { "type": "string" },
"catalog_version": { "type": "string" }
},
"required": ["language", "verb"]
}list#
The list verb may use:
kind;tag;cursor;limit;q, whencatalog.searchis declared.
The Server MAY cap limit.
get#
The get verb requires id.
changed_since#
The changed_since verb:
- requires
catalog_version; - is accepted only when
catalog.changed_sinceis declared.
Invalid combinations#
Supplying:
- a member not applicable to the selected verb;
- a verb not enabled by the declared sub-features
is a request error.
Responses carry the Part II Section 6.3 shapes in structuredContent.
4.3 Loop tools#
Each Language entry's loop object declares the Server-selected names of its loop tools.
Example:
{
"loop": {
"validate": "validate_workflow",
"execute": "run_workflow",
"trace": "trace_run"
}
}The following rules apply.
Tool existence and capability consistency#
Every declared name MUST exist in tools/list.
The set of declared names MUST correspond exactly to the effective capability map.
There is no loop member for plan.
The plan is returned by the validate tool.
The trace member names the trace-fetch tool.
One trace, two doors: it rides the execute response; if the run was stored, the same trace can be fetched later by run_id through this tool.
Shared loop tools#
Two Languages MAY share one loop tool only by using distinct program-bearing argument names.
Those argument-to-Language mappings are declared through the languages array defined in Section 5.
For a shared loop tool, a request containing:
- more than one program-bearing member; or
- no program-bearing member
is an invalid_request.
This rule applies to both validate and execute tools.
No other loop-tool sharing mechanism is permitted.
When this mechanism is not used, each Language names its own loop tools.
4.4 Reserved argument names#
Loop tools carry the abstract Part II request members as MCP tool arguments.
The following names are reserved.
| Reserved name | Tool | Abstract meaning |
|---|---|---|
| Language-declared argument | validate, execute | Inline program |
program_ref |
execute | Promoted program reference |
input |
execute | Program input |
context |
validate, execute | Validation or execution context |
trace_level |
execute, trace fetch | On execute: "none", "summary", or "full". On trace fetch: "summary" or "full" only |
expected_catalog_version |
execute | Catalog snapshot token |
idempotency_key |
execute | Idempotency key, where declared |
test_run |
execute | Test-run selector, where declared |
mode |
execute | "immediate" or "deploy" for a Language with execution: "both" |
store |
execute | Run-record retention override: true requires the record be retained for later trace fetch; false forbids retention beyond the response; absent defers to the declared retention policy |
run_id |
trace fetch | Run identifier |
question |
trace fetch | Narration question, where trace_fetch declares narrate; supplying it otherwise is a request error |
mode#
A Language with:
{
"execution": "both"
}MUST accept mode.
For that Language, mode is REQUIRED.
Its recognized values are:
"immediate";"deploy".
The following are invalid_request conditions:
- absent
modewhereexecutionis"both"; - an unrecognized
mode; - supplying
modefor a Language that is not"both".
Reserved-name rules#
- When a loop tool supports one of these members, it MUST use the reserved name.
- The member MUST appear in the tool's
inputSchema. - A Server MUST NOT use a reserved name for another purpose on a loop tool.
- A generic Client MUST be able to form every loop request from:
- the manifest;
- the tool's
inputSchema; - the Section 5 program declaration; - this reserved-name table. - Every execute tool MUST declare
trace_level. - An execute tool for a catalog-bearing Language MUST declare
expected_catalog_version. - Every trace-fetch tool MUST declare
run_idand mark it required. - A validate tool serving one Language MUST mark its program-bearing argument required.
- A shared validate tool MUST mark none of its program-bearing arguments required; selection is enforced after schema validation.
- Supplying unsupported
contextis not aninvalid_request. Part II requires acontext_bindingdiagnostic, so the schema MUST allow the request to reach Pilotage validation.
4.5 Tool annotations#
Pilotage tools SHOULD carry MCP annotations consistent with their semantics.
The following tools SHOULD declare:
{
"readOnlyHint": true
}pilotage_manifest;pilotage_get_guide;pilotage_catalog;- validate tools;
- trace-fetch tools.
An execute tool SHOULD declare annotations reflecting the maximum effect expressible by its Language.
For example, it should declare:
{
"destructiveHint": true
}when any catalog entry carries destructive: true.
MCP annotations remain unverified Server claims.
They have the trust status defined in Part II Section 9.
The Pilotage Host risk gate MUST use risk_hints, not MCP tool annotations.
4.6 Loop-tool schema authoring#
The Pilotage error-channel rules take precedence over eager JSON Schema rejection.
Loop-tool inputSchema definitions MUST therefore remain permissive.
Unknown members and value constraints#
A loop-tool schema:
- MUST NOT set
additionalProperties: false; - MUST NOT constrain reserved members beyond their JSON type.
For example:
{
"trace_level": {
"type": "string"
}
}is valid.
An enum restricting the field to known trace levels is not valid for a conformant loop-tool schema.
This ensures that value and eligibility failures are routed through Pilotage rather than being intercepted as carrier-level schema failures.
Examples include:
- unknown
trace_level; - unsupported
test_run; - unsupported
mode; store: trueon an Engine that declares"retention": "none";- both or neither runnable;
- a member supplied outside the selected capability.
Runnable selection#
An execute tool's schema MUST NOT mark either:
- the program-bearing argument;
program_ref
as required.
Runnable selection is enforced after schema validation.
An execute tool supporting promoted programs MUST still declare program_ref in properties.
Program-bearing schema#
The program-bearing argument SHOULD use a permissive schema, such as:
{}or a broad type constraint.
Invalid program content MUST be reported through diagnostics, not as a tool-schema failure.
Structural versus semantic faults#
A Server MUST NOT return a protocol error for a loop-tool value or eligibility fault.
Only structural faults may be expressed by the schema. These are exactly:
- a missing schema-required member;
- an incorrect JSON type.
Those faults are carrier-level.
5. Program-valued tool declaration#
Every MCP tool accepting a Program MUST carry a Pilotage declaration in its tools/list entry.
This includes every validate and execute loop tool.
Example:
{
"name": "run_workflow",
"inputSchema": {
"type": "object",
"properties": {
"workflow": {},
"program_ref": { "type": "string" },
"input": { "type": "object" },
"trace_level": { "type": "string" },
"expected_catalog_version": { "type": "string" }
}
},
"_meta": {
"io.github.jafarsa0/pilotage": {
"argument": "workflow",
"language": "workflow/v1"
}
}
}The declaration uses:
_meta["io.github.jafarsa0/pilotage"]argument#
argument names the tool argument containing the Program.
The wire-level program argument does not have a universal fixed name.
The Program always appears in the argument declared by the tool.
language#
language contains the corresponding Language identifier from the manifest.
Multi-Language tool#
A tool accepting Programs from more than one Language MUST use:
{
"languages": [
{ "argument": "workflow", "language": "workflow/v1" },
{ "argument": "query", "language": "sql/postgres-15" }
]
}Every argument value in this array MUST be distinct.
A Client MUST read this declaration only from the registered full _meta key.
6. Result envelope#
Every Pilotage tool result is an MCP CallToolResult.
It contains the following fields.
structuredContent#
structuredContent is REQUIRED.
It contains the Part II response object for the operation, exactly as defined in Part II Sections 5–6.
This field is the machine-readable Pilotage contract.
content#
content is RECOMMENDED.
It SHOULD contain one text block carrying either:
- serialized JSON equivalent to
structuredContent; or - a short human-readable summary.
Examples:
valid: max risk safeinvalid: 2 diagnosticsThe JSON form supports MCP hosts that expose only content to the Model.
A Pilotage-aware Client MUST read the machine contract from structuredContent.
It MUST NOT parse a human-readable summary to recover protocol semantics.
isError#
The isError field follows Section 7.
A validation result with:
{
"valid": false
}is a successful tool result.
A Run outcome with:
{
"ok": false
}is also a successful tool result.
For both, isError MUST be false.
Output schemas#
Servers SHOULD declare an MCP outputSchema for every Pilotage tool.
Because MCP requires structuredContent to satisfy the declared output schema even for request-error results, the schema MUST accept every possible structuredContent shape produced by that tool.
It therefore MUST be an anyOf union containing:
- every successful Part II response shape;
- every operation-specific response shape;
- the Section 7.2 request-error shape.
For a multi-verb tool such as pilotage_catalog, the union MUST include all verb response shapes.
Full JSON Schema files for every shape, in this unioned form, are a deliverable of this specification.
7. Error mapping#
This section maps the four Pilotage error channels defined in Part II onto MCP.
7.1 Channel mapping#
| Part II channel | MCP representation |
|---|---|
| Carrier protocol error | JSON-RPC error response |
| Request error | CallToolResult with isError: true |
| Diagnostics | CallToolResult with isError: false |
| Outcome | CallToolResult with isError: false |
Carrier protocol errors#
Carrier errors include JSON-RPC responses such as:
| Code | Meaning |
|---|---|
-32700 |
Parse error |
-32600 |
Invalid request |
-32601 |
Method not found |
-32602 |
Unknown tool or invalid CallToolRequest envelope |
Any JSON-RPC error response, including codes not listed here such as -32603 (Internal error), is a carrier protocol error.
Revision note (informative): MCP
2025-06-18listed invalid arguments under protocol errors;2025-11-25narrowed protocol errors to the envelope level. The deterministic routing below is consistent with both.
Request errors#
A request error is returned as:
{
"isError": true,
"structuredContent": {
"error": {}
}
}Diagnostics#
Program findings appear in structuredContent with:
{
"isError": false
}Outcomes#
Run outcomes also appear in structuredContent with:
{
"isError": false
}Deterministic schema-fault routing#
Pilotage schemas are constructed so each fault class maps to exactly one channel.
Structural faults
The only schema-level faults allowed for conformant Pilotage tools are structural:
- a required member is missing;
- a member has the wrong JSON type.
These are carrier-level faults.
For the fixed access tools, their binding-defined schemas are intentionally strict for structural routing.
For example:
languageis required;verbis required;verbis enumerated.
A schema violation against those access tools is carrier-level.
Value and eligibility faults
The following travel through Pilotage request-error or diagnostic channels rather than JSON-RPC:
- unknown
trace_level; - unsupported
test_run; - unsupported
mode; store: trueon an Engine that retains nothing;- an argument inapplicable to the chosen verb;
- an argument requiring an undeclared sub-feature;
- both-or-neither runnable;
- program invalidity;
- context-binding failure.
This routing is possible because Section 4.6 prohibits loop-tool schemas from enforcing those value constraints.
Mandatory prohibitions
A Server MUST NOT report:
- an invalid program;
- a failed Run
through:
isError: true;- a JSON-RPC error.
A Client MUST NOT treat:
valid: false;ok: false
as a transport failure.
7.2 Request-error shape#
A request error's structuredContent contains:
{
"error": {
"kind": "not_found",
"message": "unknown run: it may have expired"
}
}The kind field belongs to the following closed set.
| Kind | Used for |
|---|---|
not_found |
Unknown guide, topic, catalog entry, Language, promoted reference, or Run, including expired and non-retained Runs; includes unauthorized objects required to be indistinguishable from nonexistent ones |
invalid_request |
Invalid runnable selection, unsupported test_run or mode, unknown trace_level, or a member inapplicable to the verb or declared capabilities |
unknown_baseline |
The Server cannot calculate changed_since from the supplied token |
idempotency_conflict |
The same idempotency key is reused with different runnable or input |
bounds_exceeded |
A validation resource bound was exceeded |
unsupported |
store: true supplied to an Engine that declares "retention": "none"; the Server MUST reject, never silently ignore |
internal |
Validator, Engine, or Server malfunction |
message is Server-authored prose (trust status per Part II Section 9).
A Client MUST branch on kind, not on message.
A minor revision MAY add request-error kinds, under Part II's unknown-value convention.
A Client receiving an unrecognized kind MUST treat it as:
internalOperation-mandated sibling fields#
When Part II requires additional data on a request error, that data appears as a top-level sibling of error.
Example: a failed catalog get still carries catalog_version.
{
"error": {
"kind": "not_found",
"message": "unknown entry"
},
"catalog_version": "cv_9f2c41ab07de"
}Whole-result indistinguishability#
Using one not_found kind is necessary but not sufficient for the indistinguishability Part II requires (Sections 6.6 and 10.3).
Where Part II requires two conditions to be indistinguishable, the entire result MUST be indistinguishable, including:
kind;message;content;- every other field;
- observable response structure.
8. Sessions, transports, and state#
8.1 Session binding#
The Part II Session maps onto MCP as follows.
stdio on handshake-based revisions#
For stdio, this binding defines the Session as the lifetime of the Server process serving the connection.
This is a Pilotage binding definition because MCP itself defines explicit sessions only for Streamable HTTP.
Streamable HTTP#
The Session is the lifetime of the:
Mcp-Session-Idsession.
Stateless revision 2026-07-28#
A stateless deployment has no Session.
A stateless Server MUST NOT declare:
{
"retention": "session"
}It MUST instead declare:
- an ISO 8601 duration; or
"none".
8.2 Transport behavior#
Pilotage adds no transport requirement beyond MCP itself.
The complete loop operates through ordinary request-response tools/call operations:
discover
learn
lookup
validate
execute
traceThese bind the layered loop (manifest → guides → catalog → validate + plan → execute → trace) with the agent at the center deciding what and when to call; the loop is a hub, not a pipeline.
No Server-initiated stream is required.
Long-running execution is outside the scope of this version.
Until a future minor revision aligns execute with the MCP Tasks extension (io.modelcontextprotocol/tasks), execute remains one bounded request-response operation.
The Server MUST bound it under Part II Section 10.1.
8.3 Optional resource surface#
A Server MAY expose guides and catalogs as MCP resources in addition to the required tools.
Recommended URI forms are:
pilotage://guides/{language}
pilotage://guides/{language}/{id}
pilotage://catalog/{language}The Language identifier is percent-encoded as one RFC 3986 path segment.
In particular, / MUST become %2F.
Example:
pilotage://guides/workflow%2Fv1When resources are exposed:
- their content MUST equal the corresponding tool response;
- the Server MAY emit:
-
notifications/resources/list_changed; -notifications/resources/updated; resources/updatedis applicable only when: - the Client subscribed throughresources/subscribe; - the Server declaredresources.subscribe;catalog_versionremains the authoritative drift mechanism;- the tools remain REQUIRED.
Resources never replace the tools because an MCP Host is not required to expose resources to the Model.
8.4 Authorization identity#
The "requesting Client's authority" defined in Part II binds to the identity established by the MCP connection.
For Streamable HTTP, this is the connection's authorization context, such as OAuth identity.
For stdio, it is the ambient identity of the deployment.
How an Engine maps that identity onto:
- users;
- tenants;
- workspaces;
- internal authorization models
is Engine-specific and outside the scope of Pilotage.
9. Wire examples#
Status: Informative
The following examples show complete JSON-RPC 2.0 messages.
Values are illustrative.
9.1 Initialize with negotiation and inline manifest#
Request
{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2025-11-25",
"capabilities": {
"extensions": {
"io.github.jafarsa0/pilotage": {}
}
},
"clientInfo": { "name": "example-agent", "version": "1.0" }
}
}Response
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"protocolVersion": "2025-11-25",
"capabilities": {
"tools": {},
"resources": {},
"extensions": {
"io.github.jafarsa0/pilotage": { "version": "1.1" }
}
},
"serverInfo": { "name": "example-rules-engine", "version": "3.2" },
"_meta": {
"io.github.jafarsa0/pilotage": {
"manifest": {
"pilotage": "1.1",
"languages": [
{
"name": "workflow/v1",
"title": "Workflow documents",
"guides": "pilotage_get_guide",
"catalog": "pilotage_catalog",
"guides_resource": "pilotage://guides/workflow%2Fv1",
"catalog_resource": "pilotage://catalog/workflow%2Fv1",
"loop": {
"validate": "validate_workflow",
"execute": "run_workflow",
"trace": "trace_run"
},
"capabilities": {
"guides": true,
"catalog": { "search": true },
"validate": true,
"plan": true,
"execute": true,
"trace_fetch": true
},
"locator": "json-pointer",
"execution": "immediate",
"trace": "full",
"retention": "PT1H"
}
]
}
}
}
}
}9.2 Execute tool in tools/list#
{
"name": "run_workflow",
"description": "Execute a workflow document or a promoted workflow.",
"inputSchema": {
"type": "object",
"properties": {
"workflow": { "type": "object" },
"program_ref": { "type": "string" },
"input": { "type": "object" },
"trace_level": { "type": "string" },
"expected_catalog_version": { "type": "string" }
}
},
"annotations": { "readOnlyHint": false, "destructiveHint": true },
"_meta": {
"io.github.jafarsa0/pilotage": {
"argument": "workflow",
"language": "workflow/v1"
}
}
}9.3 Invalid program returned as diagnostics#
Request
{
"jsonrpc": "2.0",
"id": 7,
"method": "tools/call",
"params": {
"name": "validate_workflow",
"arguments": {
"workflow": {
"steps": [
{ "id": "profile", "kind": "call", "capability": "cap_read_user_profil" }
]
}
}
}
}Response
{
"jsonrpc": "2.0",
"id": 7,
"result": {
"content": [
{ "type": "text", "text": "invalid: 1 diagnostic" }
],
"structuredContent": {
"valid": false,
"diagnostics": [
{
"severity": "error",
"code": "unknown_capability",
"path": "/steps/0/capability",
"message": "unknown capability 'cap_read_user_profil'",
"hint": "close match: cap_read_user_profile"
}
],
"catalog_version": "cv_9f2c41ab07de"
},
"isError": false
}
}The invalid program is not an MCP tool error.
9.4 Successful execution with full trace#
Request
{
"jsonrpc": "2.0",
"id": 8,
"method": "tools/call",
"params": {
"name": "run_workflow",
"arguments": {
"workflow": {
"steps": [
{
"id": "profile",
"kind": "call",
"capability": "cap_read_user_profile",
"with": { "user_id": "$.input.userId" }
}
],
"output": { "name": "$.steps.profile.output.name" }
},
"input": { "userId": "42" },
"trace_level": "full",
"expected_catalog_version": "cv_9f2c41ab07de"
}
}
}Response
{
"jsonrpc": "2.0",
"id": 8,
"result": {
"content": [
{ "type": "text", "text": "success" }
],
"structuredContent": {
"run_id": "run_7d3f0a9c21be",
"outcome": { "ok": true, "status": "success", "error": null },
"output": { "name": "Amira Hassan" },
"trace": {
"run_id": "run_7d3f0a9c21be",
"level": "full",
"steps": [
{
"id": "profile",
"calls": "cap_read_user_profile",
"input": { "user_id": "42" },
"output": { "name": "Amira Hassan", "tier": "gold" },
"outcome": { "ok": true, "status": "success", "error": null },
"t_ms": 84
}
]
}
},
"isError": false
}
}9.5 Request error for unknown Run#
{
"jsonrpc": "2.0",
"id": 9,
"result": {
"content": [
{ "type": "text", "text": "unknown run" }
],
"structuredContent": {
"error": {
"kind": "not_found",
"message": "unknown run: it may have expired"
}
},
"isError": true
}
}9.6 Carrier error for unknown tool#
{
"jsonrpc": "2.0",
"id": 10,
"error": {
"code": -32602,
"message": "Unknown tool: validate_workflw"
}
}10. Binding conformance#
10.1 Server conformance#
In addition to satisfying Part II Section 12.1, a conformant MCP Server MUST satisfy all of the following requirements.
-
Extension declaration It declares the Pilotage extension identifier on every supported revision path and follows the graceful-degradation rules.
-
Fixed access tools It exposes:
pilotage_manifest;pilotage_get_guide;pilotage_catalogwhen any Language declarescatalog.
Those tools use the Section 4 schemas.
-
Manifest references It binds manifest guide and catalog references to the fixed tool names defined in Section 4.
-
Loop tools It declares loop tools consistently with each Language's effective capability map.
-
Reserved names It uses the reserved argument names and satisfies all declaration obligations in Section 4.4.
-
Permissive loop schemas It authors loop-tool schemas according to Section 4.6.
-
Program declaration Every program-valued tool carries the Section 5 declaration under the full registered
_metakey. -
Result envelope Every Pilotage tool returns the operation result in
structuredContent. -
Error mapping It uses
isErroraccording to Sections 6 and 7 and emits only the closed request-error kinds defined in Section 7.2. -
Session binding It binds retention to the MCP Session according to Section 8.1.
-
Stateless restriction It does not declare
"session"retention on a stateless deployment. -
Resource equality When resources are exposed, their content equals the corresponding tool responses.
-
Cold-start signposts It carries the Section 3.4 signposts: the welcome text, the entry-point description, and the stage descriptions.
10.2 Client conformance#
In addition to satisfying Part II Section 12.2, a conformant MCP Client MUST satisfy all of the following requirements.
- Capability declaration It declares the Pilotage extension in its MCP capability map.
On stateless revisions, it includes that declaration in the required per-request metadata.
-
Discovery precedence It follows the discovery and precedence rules in Section 3.
-
Rediscovery It rediscovers after
tools/list_changedand follows the stateless freshness recommendations where applicable. -
Request construction It constructs loop requests using:
- the manifest;
- the tool's
inputSchema; - the program-valued tool declaration;
- the reserved-name table.
It MUST NOT guess argument names.
- Machine contract
It reads Pilotage results from
structuredContent.
It MUST NOT derive protocol semantics by parsing content.
- Error routing It applies Section 7 exactly.
In particular:
isError: trueis not a program finding;valid: falseis not a transport failure;ok: falseis not a transport failure.
- Unknown request-error kind
It treats an unrecognized request-error
kindas:
text
internal