Orchestrator MCP server
harn mcp serve exposes a local Harn orchestrator as an MCP server so any MCP
client can fire triggers, inspect queues, replay events, and read runtime state
without a Harn-specific adapter.
This page is the canonical reference for the orchestrator control-plane MCP server. For the general MCP client/server guide, see MCP, ACP, and A2A integration; for the full protocol routing table, see Protocol support matrix.
The server is aimed at closed-loop agent clients that already know how to speak MCP, including:
- Cursor Composer
- Claude Desktop
- Claude Code
- LangChain MCP adapters
Quickstart
Hook Harn into Cursor Composer in 3 steps:
- Start the server from the workspace that owns your orchestrator manifest.
harn mcp serve --config ./harn.toml --state-dir ./.harn/orchestrator
- Point Cursor at the command as a stdio MCP server.
{
"mcpServers": {
"harn": {
"command": "harn",
"args": ["mcp", "serve", "--config", "/absolute/path/to/harn.toml", "--state-dir", "/absolute/path/to/.harn/orchestrator"]
}
}
}
- Ask the client to call Harn tools such as
harn.trigger.listorharn.orchestrator.inspect.
Example prompts:
- "List the Harn triggers in this workspace."
- "Fire the
cron-oktrigger with an empty payload." - "Show the Harn DLQ and retry the newest entry."
- "Scan this diff for secrets before I open a PR."
Transports
harn mcp serve supports:
stdiofor local spawned clients. This is the default.httpfor remote MCP clients.
HTTP mode exposes:
- Streamable HTTP POST, GET, and DELETE at
--path(default/mcp) - Legacy SSE GET at
--sse-path(default/sse), marked withDeprecation: true - Legacy SSE POST at
--messages-path(default/messages), marked withDeprecation: true
Streamable HTTP clients initialize with POST /mcp. The initialize response
includes an Mcp-Session-Id header, and clients must echo that header on later
POST, GET, and DELETE requests. Requests normally return one
application/json JSON-RPC response; clients that only accept
text/event-stream receive an SSE stream containing message events. GET /mcp opens the server-to-client SSE stream for notifications, and DELETE /mcp terminates the session.
Example:
harn mcp serve \
--config ./harn.toml \
--state-dir ./.harn/orchestrator \
--transport http \
--bind 127.0.0.1:8765
Auth
MCP OAuth applies only to HTTP transports. Local stdio servers are launched by
the client process and should receive credentials through the launch
environment or client-specific secret configuration, not through HTTP
WWW-Authenticate, protected-resource metadata, or bearer-token refresh.
For HTTP transports, prefer OAuth resource-server mode. Set
HARN_MCP_OAUTH_AUTHORIZATION_SERVERS to a comma-separated list of issuer URLs
and configure exactly how the MCP server validates bearer tokens:
HARN_MCP_OAUTH_INTROSPECTION_URLfor opaque-token introspection, optionally withHARN_MCP_OAUTH_INTROSPECTION_CLIENT_ID,HARN_MCP_OAUTH_INTROSPECTION_CLIENT_SECRET, orHARN_MCP_OAUTH_INTROSPECTION_TOKENHARN_MCP_OAUTH_JWKS_URLfor JWT validation against a JWKS endpoint
Recommended production settings:
HARN_MCP_OAUTH_RESOURCEto the canonical MCP resource URI clients pass as the RFC 8707resourcevalue, for examplehttps://mcp.example.com/mcpHARN_MCP_OAUTH_AUDIENCEwhen tokens use an audience value different from the resource URIHARN_MCP_OAUTH_ISSUERwhen tokens should be pinned to one issuerHARN_MCP_OAUTH_SCOPESas a comma- or space-separated list of scopes required for the MCP control plane
When OAuth mode is configured, unauthenticated HTTP requests return 401 with
WWW-Authenticate: Bearer resource_metadata="...". The challenge includes
scope="..." when HARN_MCP_OAUTH_SCOPES is set. The server publishes RFC
9728 protected resource metadata at both:
/.well-known/oauth-protected-resource/.well-known/oauth-protected-resource/<mcp-path>
The metadata includes authorization_servers, the canonical resource, and
scopes_supported when scopes are configured. Access tokens must be sent on
every HTTP request as Authorization: Bearer <token>. Tokens are rejected when
they are inactive, expired, issued for a different audience/resource or issuer,
or missing required scopes.
HARN_ORCHESTRATOR_API_KEYS remains available as a legacy compatibility mode.
Set it to a comma-separated key list to require API keys.
HTTP clients can authenticate with either:
Authorization: Bearer <key>x-api-key: <key>
Legacy clients that do not yet send an Authorization header can authenticate
during initialize using a deprecated Harn extension field:
{
"capabilities": {
"harn": {
"apiKey": "test-key"
}
}
}
The server prints a warning when this apiKey initialize extension is used. If
neither OAuth mode nor HARN_ORCHESTRATOR_API_KEYS is configured, the MCP
server runs without auth.
Tool catalog
MCP list endpoints use cursor pagination. tools/list, resources/list,
resources/templates/list, prompts/list, and tasks/list return up to 100
entries by default and include nextCursor when more entries are available.
Set HARN_MCP_LIST_PAGE_SIZE to a positive integer to change the per-page
limit for large local catalogs.
harn.secret_scan
Scans arbitrary text or diffs for high-signal leaked credentials and returns a
redacted finding list. Use it before commit or PR-open flows. The server also
accepts the legacy alias harn::secret_scan.
Input:
{
"content": "token = \"ghp_example...\""
}
Returns a JSON array of findings. Each finding includes:
detectorsourcetitlelinecolumn_startcolumn_endstart_offsetend_offsetredactedfingerprint
harn.trigger.fire
Dispatch a trigger inline.
Input:
{
"trigger_id": "cron-ok",
"payload": {}
}
Returns the dispatch handle summary including event_id and status.
harn.trigger.list
Lists manifest-backed triggers with:
trigger_idkindproviderwhenhandlerversionstatemetrics
harn.trigger.replay
Replays a historical event. Supports as_of to resolve bindings against a
historical timestamp when needed.
{
"event_id": "trigger_evt_123",
"as_of": "2026-04-19T18:00:00Z"
}
harn.orchestrator.queue
Returns queue counts plus recent head previews for:
- inbox
- outbox
- attempts
- DLQ
harn.orchestrator.dlq.list
Lists pending dead-letter entries.
harn.orchestrator.dlq.retry
Retries one DLQ entry by id.
{
"entry_id": "dlq_123"
}
harn.orchestrator.inspect
Returns the dispatcher snapshot, trigger-centric inspect data, persisted orchestrator snapshot, flow-control state, and recent dispatch records.
harn.trust.query
Placeholder trust-graph surface. Today it returns:
{
"results": []
}
Resources
The server exposes these MCP resources:
harn://manifestharn://topic/trigger.inboxharn://topic/trigger.outboxharn://topic/agent.transcript.<id>harn://event/<event_id>harn://dlq/<entry_id>
harn://event/<event_id> includes the recorded trigger event plus related
outbox/attempt/DLQ/action-graph trace entries.
resources/templates/list advertises the parameterized forms clients can use
after discovering concrete examples:
harn://topic/{name}harn://event/{event_id}harn://dlq/{entry_id}
completion/complete supports those resource template arguments. Topic
completion includes static trigger topics and discovered agent transcript
topics; event and DLQ completion use recorded trigger and pending DLQ ids.
harn://topic/* resources expose recent EventLog entries for subscribable
orchestrator topics. resources/subscribe starts a live EventLog watcher for
the resource URI and the server sends notifications/resources/updated when a
new record is appended. resources/unsubscribe stops delivery for that client
connection. Streamable HTTP clients receive these notifications over GET /mcp; stdio and legacy SSE clients receive the same JSON-RPC notification on
their existing server-to-client channel.
Prompts
The server exposes .harn.prompt files from the project root and from
installed prompt-library packages under .harn/packages/<alias>.
TOML front matter can define display metadata and MCP arguments:
---
id = "review"
description = "Review code"
[[arguments]]
name = "code"
description = "Code to review"
required = true
[[arguments]]
name = "language"
required = false
suggestions = ["rust", "typescript", "python"]
---
Review this:
{{ code }}
prompts/get renders the template with the supplied arguments object.
completion/complete uses the optional suggestions/completions list on
front-matter arguments when a client asks for prompt argument completions.
The server advertises tools.listChanged, resources.listChanged, and
prompts.listChanged. It emits the corresponding notifications/*/list_changed
messages when watched manifest, prompt, lockfile, or package metadata changes.
Protocol support
harn mcp serve negotiates MCP protocol version 2025-11-25. It is a
control-plane server for Harn orchestration state, so it supports tools,
resources, prompts, completions, logging, MCP tasks, cancellation, progress,
and streamable HTTP sessions. It does not expose roots. The
orchestrator-mode catalog does not currently issue
sampling/createMessage against connected clients (Harn's outbound MCP
clients accept inbound sampling — see the
client docs).
| Method or feature | Status |
|---|---|
initialize, notifications/initialized, ping | Supported |
logging/setLevel, notifications/message | Supported; see Logging notifications |
tools/list, tools/call | Supported for the Harn tool catalog above |
notifications/progress, notifications/cancelled | Supported for cancellable work |
resources/list, resources/read | Supported for manifest, EventLog topic, event, and DLQ resources |
resources/templates/list | Supported for EventLog topic, trigger event, and DLQ URI patterns |
resources/subscribe, resources/unsubscribe | Supported for EventLog topic resources; updates emit notifications/resources/updated |
prompts/list | Supported for .harn.prompt files in the project and prompt-library packages |
prompts/get | Supported; renders prompt templates with supplied arguments |
completion/complete | Supported for prompt arguments with front-matter suggestions and orchestrator resource template arguments |
elicitation/create | Supported on script-driven harn run --serve mcp surfaces via the mcp_elicit(...) builtin (see Elicitation). The orchestrator-mode tool catalog does not currently issue elicitations. |
roots/list | Explicitly unsupported |
sampling/createMessage | Server-initiated sampling against the connected client is not emitted by the orchestrator catalog. Harn-as-MCP-client does accept inbound sampling/createMessage (routed to llm_call via the host bridge) — see the client matrix. |
tasks/get, tasks/result, tasks/list, tasks/cancel | Supported for task-augmented orchestrator tool calls |
tools/call with params.task | Supported for tools that advertise optional task execution; rejected with -32602 for tools that advertise execution.taskSupport="forbidden" |
Explicitly unsupported methods return a JSON-RPC error with code -32601 and
error.data.type = "mcp.unsupportedFeature". Tool calls that request
task-augmented execution for a non-taskable tool return -32602 because the
request conflicts with the tool's advertised execution metadata.
sampling/createMessage and elicitation/create are client-bound MCP requests.
When a client sends either method to a Harn MCP server endpoint, Harn returns an
explicit mcp.unsupportedFeature JSON-RPC error instead of treating the request
as an ordinary unknown method.
Elicitation
Script-driven MCP servers (those built with mcp_tools(...) /
mcp_resource(...) / mcp_prompt(...) and started with harn run --serve mcp
or harn serve mcp) can prompt the connected client for structured user
input mid-tool-call via the mcp_elicit(...) builtin:
let answer = mcp_elicit({
message: "Which environment should I deploy to?",
requestedSchema: {
type: "object",
properties: {
env: { type: "string", enum: ["staging", "production"] },
confirm: { type: "boolean" }
},
required: ["env", "confirm"]
}
})
// answer is one of:
// { action: "accept", content: { env: "staging", confirm: true } }
// { action: "decline" }
// { action: "cancel" }
The builtin returns the canonical {action, content?} envelope from the
MCP elicitation spec.
On accept, content is validated against requestedSchema before
returning so scripts can rely on its shape. On decline / cancel,
content is omitted.
mcp_elicit(...) is only valid while a client connection is active. If
called outside a tool / resource / prompt handler — e.g. at pipeline
top-level — it raises a structured error rather than hanging.
When Harn is on the client side of an MCP connection (mcp_connect(...)
or mcp_call(...)) and a remote server sends an elicitation/create
request, Harn dispatches it to the embedder via the HostCallBridge
(capability="mcp", operation="elicit"). If no host bridge is wired
up, Harn responds with { action: "decline" } so the server can fall
back to a sensible default rather than blocking forever.
Progress notifications
Per the MCP progress utility, a client may opt into
progress updates by attaching _meta.progressToken to a request. While
the matching tool call is in flight, Harn's mcp_report_progress(...)
builtin emits notifications/progress notifications carrying that
token:
pub fn import_records(rows: list) -> string {
var i = 0
for row in rows {
i = i + 1
process(row)
mcp_report_progress(i, {total: len(rows), message: "imported " + to_string(i)})
}
return "imported " + to_string(i) + " records"
}
The builtin returns true when a notification was emitted and false
when it was dropped — either because the client did not opt in via
_meta.progressToken, the call sits outside an MCP tool handler, or
the value would not strictly increase per the spec's monotonicity
requirement. Scripts can therefore call it unconditionally without a
preflight check.
opts is optional; supported keys:
total: numeric ceiling so the client can render a progress barmessage: human-readable status stringtoken: override the ambient request token (rarely needed; useful only when fanning progress out for nested work)
The orchestrator-mode harn.trigger.fire tool also emits its own
milestones (loading runtime, preparing event, firing trigger,
trigger complete) so MCP clients can render meaningful progress for
long trigger fan-outs without any user-side wiring.
Observability
Every MCP tool call appends an observability.action_graph event and emits a
stderr log line with:
- MCP client identity
- tool name
- status
- trace id
harn.trigger.fire also injects MCP client identity and trace metadata into the
synthetic event headers so downstream dispatch traces can be tied back to the
calling MCP client.
harn.secret_scan additionally appends audit.secret_scan records with only
redacted findings plus stable fingerprints so future trust-graph consumers can
reason about scan hygiene without storing raw secret material.
Logging notifications
The server advertises the logging capability and forwards Harn's structured
audit and observability streams as MCP notifications/message envelopes. Each
notification carries level, logger, and data fields per the
MCP logging spec.
The data payload is the raw event-log entry: event_id, kind,
occurred_at_ms, headers, and payload.
logging/setLevel updates the per-session minimum severity. Subsequent
notifications below the requested level are dropped before they hit the wire;
the default is info. Levels follow RFC 5424 ordering: debug, info,
notice, warning, error, critical, alert, emergency. The server
returns -32602 for an unknown or missing level.
The streams the orchestrator currently surfaces:
| Logger | Source topic | Default level |
|---|---|---|
harn.audit.secret_scan | audit.secret_scan | notice |
harn.audit.signature_verify | audit.signature_verify | notice |
harn.connectors.egress.audit | connectors.egress.audit | notice |
harn.trigger.operations.audit | trigger.operations.audit | notice |
harn.trigger.dlq | trigger.dlq | warning |
harn.observability.action_graph | observability.action_graph | debug |
A producer can override the level for a specific event by setting an explicit
severity header on the event-log entry; otherwise the server escalates events
whose kind contains error/panic to error and fail/denied/blocked/
rejected/dropped/dlq to warning.