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 plane | Default route | Identity boundary | Purpose |
|---|---|---|---|
| Consumer | /mcp | Authenticated, active tenant | Operate the products and workflows that tenant policy exposes. |
| Admin | /mcp/admin | Authenticated platform-admin | Provision 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.
mcp:
enabled: true
path: /mcp
admin_enabled: true
admin_path: /mcp/admin
expose_unwired_tools: false
cors:
allowed_origins: "https://console.example.com"mcp.enabledmounts the endpoint when set totrue.mcp.pathsets the mount path. The default is/mcp.mcp.admin_enabledmounts the platform-admin endpoint when set totrue.mcp.admin_pathsets its distinct mount path. The default is/mcp/admin.mcp.expose_unwired_toolscontrols whether discovery includes tools without a live backend. Keep itfalsein production.cors.allowed_originscontrols 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:
| Domain | Read operations | Mutations |
|---|---|---|
| Readiness | admin.readiness | — |
| Tenants | admin.tenant.list, admin.tenant.get | admin.tenant.create, admin.tenant.update, admin.tenant.delete |
| Members | admin.member.list, get, roles, scopes | assign_role, invite, revoke_invite, disable, reenable, remove |
| API keys | admin.api_key.list | admin.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.
| Target | Required 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:
- The first call validates the tenant, name, roles, scopes, environment restrictions, and optional expiry.
- Ductor returns
input_required, anelicitation/createrequest, and opaque state bound to those arguments. - The client collects typed approval and retries the same call with unchanged
arguments, matching
requestState, andinputResponses. - 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:
| Method | Purpose | Identity context |
|---|---|---|
server/discover | Returns supported versions, capabilities, instructions, identity, and cache hints. | Authenticated tenant or platform admin |
tools/list | Returns the deterministic catalog visible to this caller. | Authenticated tenant or platform admin |
tools/call | Re-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, eithercompleteorinput_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
supportedand audiencetenant; - externally callable, not
internal_only; - covered by complete authentication and authorization metadata; and
- classified with the
readorpreviewauthorization 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:
| Outcome | Meaning |
|---|---|
success | The tool completed normally. |
input_required | The tool returned a typed continuation request. |
application_error | The tool returned isError: true for the client to surface. |
error | Request or handler execution failed. |
denied | Tool exposure policy rejected the call before execution. |
Protocol failures use stable error codes:
| Failure | HTTP | JSON-RPC code |
|---|---|---|
| Header and body do not match | 400 | -32020 |
| Required client capability is missing | 400 | -32021 |
| Protocol version is unsupported | 400 | -32022 |
| Method is unknown | 404 | -32601 |
| Parameters or tool name are invalid | 400 or JSON-RPC response | -32602 |
Production checklist
- Keep both planes disabled until gateway authentication and authorization are configured.
- Roll out
/mcpfirst, then expose/mcp/adminonly 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_toolsoff. - 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-28metadata and all required routing headers. - Treat tool-lease fields and multi-round state as opaque values.
- Monitor
mcp.tool_callsoutcomes 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.
Related documentation
AI & Agents
Ductor's governed AI vertical — durable chat and workflow agents, provider-neutral inference, least-privilege tools, presets, and an inbound MCP server.
Agent Tool Security
The least-privilege exposure engine that decides which tools an agent session may see and call — per-session manifests, schema-hash binding, tenant policies, redaction, and decision receipts.