# MCP contract The experimental MCP gateway exposes every `x-owner` business operation in the assembled [Go API OpenAPI specification](../http/README.md). The reviewed [manifest](manifest.json) gives each operation one stable tool name, scope and effect classification. It currently contains 54 operations, including writes, deletions and the two operator-only Support routes. The manifest is deliberately explicit. Generation fails when an API operation is added or removed without a parity decision, a scope crosses its owner, a write is classified as a read, or an input admits an authentication header. Authentication/session endpoints, internal relays, diagnostics and realtime connections do not become tools. Run `pnpm generate:contracts` after editing sources or generators. `pnpm check:http` checks the bundled specification, generated tool schemas, Go DTOs, HTTP client and closed typed dispatcher. Generated files live in `services/api/adaptors/{mcpcontract,mcpclient}/generated`. ## Inputs and outcomes Inputs retain the OpenAPI names in three optional groups: ```json { "path": {"id": "11111111-1111-4111-8111-111111111111"}, "parameters": { "Idempotency-Key": "22222222-2222-4222-8222-222222222222", "If-Match": "0" }, "body": {"title": "A support request", "body": "Please investigate."} } ``` This is the input to `create_support_ticket`. Tools retain required command headers, canonical identifiers, expected revisions, filters and pagination. Path values are encoded by the generated client; they cannot select an arbitrary origin or operation. Read-only annotations include SSH key validation, which the HTTP API models as a POST. Each result has `operation_id`, HTTP `status`, and either the generated `result` or a bounded `error` containing `code`, `message` and optional `outcome_unknown`. Structured and text results contain the same envelope. The HPC projection keeps its Protobuf representation as `{media_type, base64}`. Declared, schema-valid shared, Run and Pipeline command outcomes retain their `aggregateId`, `version`, `status` and `rejection` in `result`, including HTTP 404, 409 and 422 where that operation declares the corresponding outcome schema. These non-success replies set the MCP error flag while preserving the recorded outcome for recovery. Ordinary and unrecognised error bodies keep the sanitised `error` envelope. Arguments are limited to 64 KiB. API replies are read with a 1 MiB bound; the gateway also bounds the duplicated MCP envelope. Large results require narrower queries or the ordinary API. Unknown provider error details are omitted. Malformed successful replies fail contract validation. The gateway makes one HTTP attempt, refuses redirects and never manufactures idempotency keys or revisions. After an uncertain write, preserve the exact input and command identity for a user-authorised replay or current-state query. Tool annotations and instructions ask clients to obtain approval before writes. The platform enforces identity, scopes and owner permissions; the client owns per-action human approval. ## Authority Scopes use `:read`, `:write` or `:admin`. Tokens contain only resource scopes actually granted through OAuth. Discovery is scoped to each request. The API rechecks permissions for each invocation. An ordinary Party can request member scopes and use the operations allowed by the existing API and owners. The two Support administration operations also require API-side operator enrolment, checked at each call. The broker captures that enrolment at login and checks it on refresh; an old grant cannot silently gain newly enrolled operator rights. The protocol uses three distinct JWT families: | Family | Signer | Audience | Lifetime | | --- | --- | --- | --- | | `at+jwt` | OAuth broker | Exact `MCP_RESOURCE_URL` | At most 600 seconds | | `pharos-mcp-login+jwt` | OAuth broker | `pharos-api-mcp-login:v1` | At most 30 seconds | | `pharos-mcp-assertion+jwt` | Gateway | `pharos-api-mcp:v1` | At most 30 seconds, bounded by the access token | All carry exact `iss`, `aud`, `sub`, `iat`, `exp`, `jti`, `identity_issuer`, `identity_subject` and `identity_voperson_id`. Access tokens additionally require `client_id`, `party_id`, `auth_version` and `scope`. Gateway assertions retain Party authority and scopes, replace `client_id` with an exact `operation_id`, and receive a fresh identity and expiry. Login assertions contain neither a Party nor an operation or scope. The private `POST /internal/mcp/identity` endpoint accepts only a broker login assertion. Its response contains `party_id`, `auth_version` and `operator`. It resolves an existing exact identity through Party Governance, checks current AUP through its established binding and rechecks the Party generation. Missing registration, pending setup or required AUP asks the user to complete ordinary platform login. No account is created by the MCP login query. JWT parsing rejects duplicates, unknown fields, incorrect casing, nulls, missing identity, wrong token families, wrong audiences and invalid lifetimes. Broker and gateway verification keys are explicitly enrolled and must be different. The API's ordinary browser and CLI paths retain their existing credentials. A failed delegated bearer never falls back to a browser cookie. See [ADR 0071](../../docs/decisions/0071-generated-mcp-api-edge.md) and the [runtime guide](../../services/api/services/mcp/README.md).