AI & Agents

MCP Admin & Consumer Servers

Operate a tenant or administer Ductor's product lifecycle through isolated, stateless MCP 2026-07-28 control planes.

Ductor includes two inbound Model Context Protocol (MCP) servers. External AI clients can discover and call typed tools without receiving broader access to the REST API. This is one way an agent becomes a Worker that operates the clearing layer: the same tools a person reaches through the REST API, offered to an agent through a governed, typed surface — shipping today, and the same auth chain either way.

Control planeDefault routeIdentity boundaryPurpose
Consumer/mcpAuthenticated, active tenantOperate the products and workflows that tenant policy exposes.
Admin/mcp/adminAuthenticated platform-adminProvision and govern tenants, members, roles, invites, and tenant API keys.

The routes have different registries, server identities, and authorization chains. Admin tools never appear on the consumer route, and the admin route does not inherit a consumer tenant.

Ductor implements MCP 2026-07-28, sometimes described as the stateless MCP core. Each request contains everything the protocol needs. Ductor does not run an initialization exchange, issue Mcp-Session-Id, or require a client to stay connected to one replica.

Protocol statelessness does not remove durable application state

Tool authorization leases, manifests, chat turns, approvals, jobs, and audit records can still be durable. They are explicit Ductor resources, not hidden MCP transport sessions. This makes retries and recovery observable while the HTTP transport remains stateless.

Enable the control planes

Both planes are off by default and mount on the existing API server. Enable the consumer plane first; configuration validation rejects an admin plane enabled on its own.

ductor.yaml
mcp:
  enabled: true
  path: /mcp
  admin_enabled: true
  admin_path: /mcp/admin
  expose_unwired_tools: false

cors:
  allowed_origins: "https://console.example.com"
  • mcp.enabled mounts the endpoint when set to true.
  • mcp.path sets the mount path. The default is /mcp.
  • mcp.admin_enabled mounts the platform-admin endpoint when set to true.
  • mcp.admin_path sets its distinct mount path. The default is /mcp/admin.
  • mcp.expose_unwired_tools controls whether discovery includes tools without a live backend. Keep it false in production.
  • cors.allowed_origins controls browser origins accepted by the MCP endpoint. Enterprise mode requires an explicit list and rejects *.

The equivalent environment overrides are DUCTOR_MCP_ENABLED, DUCTOR_MCP_PATH, DUCTOR_MCP_ADMIN_ENABLED, and DUCTOR_MCP_ADMIN_PATH. Enterprise mode also requires an audit sink before it will enable admin MCP.

Consumer plane

The gateway derives the tenant from the authenticated principal, confirms that the tenant is active, authorizes MCP access, and applies the tenant's tool exposure manifest. A client cannot select another tenant in tool arguments or replace its identity with an X-Tenant-ID header.

The consumer catalog is assembled from the services wired into the running Ductor process. It can cover workflow and routing operations, connectors, workspaces, environments, governance, usage, reliability, cases, and work assignments. Connector actions and triggers appear only when their catalog and execution boundaries are available.

Call consumer.readiness to see live counts by tool family and mutation class. This reports whether a family is wired; tools/list remains authoritative for the tools this particular identity may discover. Write and destructive tools still require their manifest grants, matching schema hashes, and configured safeguards.

Admin plane

Before MCP dispatch, /mcp/admin requires the dedicated platform-admin role and a successful authorization decision for the fixed platform_admin resource. Every tenant-scoped tool then requires a valid, explicit tenant_id.

The tenant admin role is not sufficient for this endpoint. Tenant API keys cannot mint platform-admin; use a separately provisioned platform machine identity. This keeps a compromised tenant credential from crossing into product-lifecycle administration even when that credential legitimately administers its own tenant.

The admin registry contains 19 tools:

DomainRead operationsMutations
Readinessadmin.readiness
Tenantsadmin.tenant.list, admin.tenant.getadmin.tenant.create, admin.tenant.update, admin.tenant.delete
Membersadmin.member.list, get, roles, scopesassign_role, invite, revoke_invite, disable, reenable, remove
API keysadmin.api_key.listadmin.api_key.create, admin.api_key.revoke

Member operations in the table retain the admin.member. prefix. Call admin.readiness to inspect the live tenant, member, API-key, and audit dependencies. If a service is absent, its tool family is not advertised.

Tenant deletion is retained soft deletion: the record remains with status deleted. MCP does not provide a hard-delete shortcut around retention or audit policy.

Destructive confirmation

Destructive calls require a target-specific confirmation string. A generic approval cannot be replayed against another object.

TargetRequired confirmation
Tenant<tenant_id>
Member<tenant_id>/<member_id>
Invite<tenant_id>/<invite_id>
API key<tenant_id>/<key_id>

Role replacement uses the member form because it can remove existing authority.

Create a tenant API key

admin.api_key.create uses a stateless, two-round approval:

  1. The first call validates the tenant, name, roles, scopes, environment restrictions, and optional expiry.
  2. Ductor returns input_required, an elicitation/create request, and opaque state bound to those arguments.
  3. The client collects typed approval and retries the same call with unchanged arguments, matching requestState, and inputResponses.
  4. Ductor persists the hashed credential and returns plaintext exactly once.

The result is marked io.ductor/sensitive: true and io.ductor/retention: none. Deliver it directly to an approved secret store; do not put it into model context, chat history, logs, traces, certification transcripts, or retry payloads. List operations return only redacted records and prefixes.

Admin audit trail

Every successful admin mutation emits an mcp_admin audit event when an audit sink is configured. The event records the authenticated subject, action, tenant, target identifier, decision, timestamp, and trace ID. Arguments are not copied into the event, so one-time credentials and invite content do not enter the admin audit record.

Enterprise mode refuses to enable admin MCP without the sink. Strict auditing also rejects a mutation before storage changes if the required sink is unavailable.

Supported methods

The server exposes a tools-only MCP surface:

server/discover version, capabilities, cache hints tools/list authorized tool catalog tools/call complete or input_required MCP client Ductor MCP server
MethodPurposeIdentity context
server/discoverReturns supported versions, capabilities, instructions, identity, and cache hints.Authenticated tenant or platform admin
tools/listReturns the deterministic catalog visible to this caller.Authenticated tenant or platform admin
tools/callRe-authorizes and invokes one named tool.Authenticated tenant or platform admin

initialize and ping are not part of this version. An unknown method returns HTTP 404 with JSON-RPC code -32601. Ductor accepts valid notifications with HTTP 202 and no response body.

The current implementation does not advertise prompts, resources, subscriptions, tasks, or server-initiated event streams.

Make a request

Every request must include the protocol version and client capabilities in both the HTTP and JSON-RPC contract. Empty client capabilities are valid; omitting the field is not.

curl https://api.example.com/mcp \
  -H 'Authorization: Bearer <token>' \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  -H 'MCP-Protocol-Version: 2026-07-28' \
  -H 'Mcp-Method: server/discover' \
  --data '{
    "jsonrpc": "2.0",
    "id": "discover-1",
    "method": "server/discover",
    "params": {
      "_meta": {
        "io.modelcontextprotocol/protocolVersion": "2026-07-28",
        "io.modelcontextprotocol/clientCapabilities": {},
        "io.modelcontextprotocol/clientInfo": {
          "name": "my-client",
          "version": "1.0.0"
        }
      }
    }
  }'

Use https://api.example.com/mcp/admin with a dedicated platform-admin credential for the admin plane. Do not reuse that credential in consumer agents.

For tools/call, also send Mcp-Name with the tool name. Ductor verifies the version, method, and name headers against the body before it looks up or runs a tool. Trace context can be sent as traceparent, tracestate, and baggage inside _meta.

Every successful response includes:

  • resultType, either complete or input_required;
  • _meta["io.modelcontextprotocol/serverInfo"]; and
  • method-specific content.

Discovery results include public cache hints. Tool catalogs are private and currently use a zero TTL because visibility can change with tenant policy.

Tool parameter headers

A tool schema can mark a string, number, integer, or boolean input with x-mcp-header:

{
  "type": "object",
  "properties": {
    "region": {
      "type": "string",
      "x-mcp-header": "Region"
    }
  }
}

The client sends the same value in arguments.region and Mcp-Param-Region. Ductor rejects missing required, malformed, or conflicting recognized parameter headers before execution. Values that are unsafe as plain HTTP field values use MCP's Base64 sentinel encoding.

Ductor does not advertise a tool whose input schema has a non-object root or an invalid x-mcp-header annotation. This prevents a client from discovering a tool it cannot call correctly.

Request more input without a session

A tool can pause for typed client work by returning input_required:

{
  "resultType": "input_required",
  "inputRequests": {
    "approval": {
      "method": "elicitation/create",
      "params": {
        "message": "Approve this operation?"
      }
    }
  },
  "requestState": "opaque-tool-state"
}

The client completes the requested operation, then retries the original tools/call with inputResponses and the unchanged requestState. Ductor passes both values to the tool handler; it does not keep a transport session between calls.

The request's client capabilities must cover every input request. Ductor supports elicitation/create, sampling/createMessage, and roots/list for this flow. Missing capabilities return HTTP 400 with code -32021. Unsupported input-request methods fail closed.

Tool catalog and governance

The catalog includes read, trigger, governance, and authoring tools. Ductor also projects connector actions and workflow triggers into the same catalog, so they can be called without a second execution path.

Descriptor-generated API reads

Ductor generates ordinary read tools from the same canonical operation descriptors that produce the REST and OpenAPI surface. Mutations remain bespoke and operations marked for internal use are excluded.

A generated tool is eligible only when the operation is all of the following:

  • available in the running API inventory;
  • lifecycle supported and audience tenant;
  • externally callable, not internal_only;
  • covered by complete authentication and authorization metadata; and
  • classified with the read or preview authorization action.

Tool names use api_ plus the normalized operation id—for example, AIInferenceService_GetUsageStats becomes api_aiinferenceservice_getusagestats. Input JSON Schema is derived from the protobuf request: path fields become required, timestamps retain their format, enums retain their closed values, and unknown fields are rejected.

Tenant and environment identity are never accepted from model arguments. tenantId and environmentId fields are removed from the advertised schema and injected from the authenticated MCP context. Execution forwards the caller's credential to Ductor's fixed loopback REST listener, refuses redirects, applies a bounded timeout, and caps response bodies at 10 MiB.

Automatic projection never turns a mutation into a tool

Write, delete, cancel, execute, publish, promote, and other mutating actions stay bespoke. They appear only when an explicit MCP handler supplies the required approval, confirmation, idempotency, and audit behavior. Adding an OpenAPI mutation does not make it agent-callable automatically.

When tool exposure is enabled, tools/list creates an explicit authorization lease and manifest. The list response includes the identifiers and schema hashes the client must echo on tools/call. Ductor checks the lease, manifest, tool version, schema hash, tenant, and arguments again before execution.

This application-level lease is intentionally visible in the request. It is not an MCP session and does not require connection affinity.

Metering and errors

Every tools/call records a usage event under mcp.tool_calls with one of these outcomes:

OutcomeMeaning
successThe tool completed normally.
input_requiredThe tool returned a typed continuation request.
application_errorThe tool returned isError: true for the client to surface.
errorRequest or handler execution failed.
deniedTool exposure policy rejected the call before execution.

Protocol failures use stable error codes:

FailureHTTPJSON-RPC code
Header and body do not match400-32020
Required client capability is missing400-32021
Protocol version is unsupported400-32022
Method is unknown404-32601
Parameters or tool name are invalid400 or JSON-RPC response-32602

Production checklist

  • Keep both planes disabled until gateway authentication and authorization are configured.
  • Roll out /mcp first, then expose /mcp/admin only through operator ingress.
  • Use a dedicated platform-admin machine identity for admin automation.
  • Set explicit browser origins. Do not use * in production.
  • Keep expose_unwired_tools off.
  • Call both readiness tools and investigate unavailable families.
  • In a sandbox tenant, test wrong and correct destructive confirmations.
  • Create and revoke a short-lived key; confirm plaintext is delivered only to the intended secret store and never appears in audit output.
  • Confirm clients send 2026-07-28 metadata and all required routing headers.
  • Treat tool-lease fields and multi-round state as opaque values.
  • Monitor mcp.tool_calls outcomes and JSON-RPC error codes during rollout.
  • Deploy clients and servers together. Ductor does not include an old-version compatibility path.

What an agent can—and cannot—administer

Together, the two planes let an agent bootstrap a tenant, govern membership and authority, issue and revoke scoped credentials, discover the tenant's allowed product tools, operate those tools, and disable access with auditable outcomes.

They do not expose the host that runs Ductor. Process and deployment control, static environment changes, migrations, raw SQL, backup and restore, raw secret reads, arbitrary HTTP/filesystem/shell execution, authorization-policy replacement, and retention-bypassing hard deletion remain in the deployment control plane or a documented break-glass procedure. This boundary keeps a compromised agent inside Ductor's product authorization model.