MCP server
Doc type: Reference · Applies to: POSE 1.4.x
pose serve-mcp exposes a read-heavy governance view of a POSE instance to
MCP-capable agents. Transports: stdio
(--stdio, ideal for agent runtimes) and Streamable HTTP (POSE_MCP_ADDR,
default :8790).
The v1.4.3 golden catalog contains 50 tools: 47 project-scoped POSE governance tools and 3 optional Conductor run reporters. The count is a release contract, not a hand-maintained marketing number.
Configuration
| Env var | Purpose |
|---|---|
POSE_PROJECT_ROOT |
Repository root of the default project (must contain .pose/) |
POSE_DEFAULT_PROJECT_ID |
Default project id (derived from the root dir name if empty) |
POSE_PROJECT_ROOTS |
JSON map of additional project_id → root entries |
POSE_MCP_TOKEN |
Bearer token for HTTP transport (empty = dev, auth off) |
POSE_MCP_OPA_URL / POSE_MCP_OPA_PATH |
OPA policy endpoint (empty = allow-all dev mode; failures deny) |
POSE_MCP_REQUIRE_PRINCIPAL |
Deny anonymous tools/call even without OPA |
POSE_MCP_IDENTITY_SECRET |
Verifies run-bound execution identities |
POSE_MCP_STRICT_PROJECT_SELECTION |
Non-empty = fail closed on empty project_id when more than one project is registered (see below) |
POSE_USAGE_DISABLED |
1/true disables local usage event collection; aggregate reads remain available for existing events |
POSE_USAGE_DIR |
Optional absolute local state directory override, useful for a persistent container mount |
The installer seeds .mcp.json when absent. It invokes the native binary
directly and records the installed project's root and project id in the server
environment; no wrapper or second executable is generated.
Worked examples
A single repository with one default root:
{
"POSE_DEFAULT_PROJECT_ID": "project_acme",
"POSE_PROJECT_ROOT": "/path/to/acme",
"POSE_PROJECT_ROOTS": "{\"project_acme\":\"/path/to/acme\"}"
}
Several independent repositories on one server, with explicit selection required:
{
"POSE_DEFAULT_PROJECT_ID": "project_acme",
"POSE_PROJECT_ROOT": "/path/to/acme",
"POSE_PROJECT_ROOTS": "{\"project_beta\":\"/path/to/beta\"}",
"POSE_MCP_STRICT_PROJECT_SELECTION": "1"
}
POSE_PROJECT_ROOTS is an allowlist of project_id → root. The registered
projects are independent: the map grants access, it does not create a
subproject or parent relationship between them. Pass project_id on every call
once more than one root is registered.
Prefer the stdio transport for local agents and let the client start
pose serve-mcp --stdio from .mcp.json; do not keep a manual daemon running
alongside it. Reserve the HTTP transport for an explicitly configured shared
server.
In Codex, mirror the same overlay in .codex/config.toml. Reuse the global
server name to override it at project scope, and neutralize inherited
multi-project values when the repository operates on its own.
Observability
pose serve-mcp can emit OpenTelemetry traces, metrics and correlated logs
for every tools/call (spec pose-otel-observability). Off by default —
POSE stays fully offline unless both of the following are set:
| Env var | Purpose |
|---|---|
POSE_OTEL_ENABLED |
Must be 1/true — POSE's own opt-in gate |
OTEL_EXPORTER_OTLP_ENDPOINT |
OTLP/HTTP collector endpoint — no default is baked into the binary |
OTEL_EXPORTER_OTLP_INSECURE |
true to skip TLS (local/dev collectors) |
OTEL_EXPORTER_OTLP_HEADERS |
key1=value1,key2=value2 — e.g. a collector auth header |
OTEL_TRACES_SAMPLER_ARG |
Trace sample ratio, 0.0–1.0 (default 1.0) |
OTEL_METRIC_EXPORT_INTERVAL |
Metric export interval in milliseconds (default 15000) |
Every span and metric carries only the tool name and its catalog risk
class (read/gate/external-side-effect) — never an argument, path,
repo name or user id. Metrics: pose.mcp.tool.call.duration (histogram),
pose.mcp.policy.denial.count (counter), pose.mcp.tool.call.inflight
(current concurrency). Logs are structured JSON on stderr, correlated to
the active span's trace_id/span_id, with paths and secret-shaped
content redacted before being written. A misconfigured or unreachable
collector never blocks server startup or a tool call — export failures
are logged and swallowed, bounded by the shutdown timeout.
Tools
| Tool | Returns |
|---|---|
pose_list_specs / pose_get_spec |
Spec inventory (lifecycle frontmatter) / full spec |
pose_requirement_trace |
Bidirectional requirement↔evidence trace of one spec (dispositions, refs, missing/orphans) |
pose_capability_state |
Current capability assessment: mechanisms with scores/targets, typed evidence, gaps, evidence-resolution issues and age |
pose_capability_stale |
Mechanisms currently marked assessment-stale: pending reassessment demands (since, trigger, components hit), cleared by pose assess snapshot |
pose_docs_state |
Docs-governance check: manifest presence/profile/roots plus the live pose docs-check result (declared/undeclared/stale by doc_type, per-doc issues, open review-pending marks) |
pose_capability_history |
Append-only assessment snapshots (score vectors), supersede-aware and paginated |
pose_spec_amendments |
Append-only amendment history of one spec plus unacknowledged requirement changes |
pose_spec_readiness |
Is a spec eligible? Resolves depends_on refs (specs, milestones, roadmaps) |
pose_mcp_context |
Active server identity, transport, selection mode, policy-filtered logical project IDs and an optional project-resolution probe; never host paths |
pose_project_state |
Current project state in one call: curated + derived sections (specs/roadmaps, follow-ups, capabilities, decisions/knowledge, validation evidence, architecture), staleness and tamper detection |
pose_closeout_state |
Hierarchical review state, child blockers, next governed action and terminal closeout for a typed scope |
pose_review_plan |
Deterministic component-aware review plan: mapping provenance, selected profiles, criteria, safe native-tool argv, evidence expectations, independence and blockers |
pose_review_bundle |
Read-only sealed review subject and attestation state: semantic/patch/tree identity, freshness, supersession delta and next governed action |
pose_delivery_integrity |
Artifact claims, Git-observed change sets, reverse path provenance and stable findings from one project-scoped graph |
pose_surface_assurance |
Typed delivery targets, production entrypoints, current evidence, explainable composition paths and roadmap criteria from the same graph |
pose_list_roadmaps / pose_get_roadmap |
Governed roadmaps and their milestone DAGs |
pose_get_changelog |
User-facing changelog fragments |
pose_release_status |
Pending queue and evidence-backed immutable release lifecycle |
pose_get_followups |
Aggregated follow-up backlog |
pose_check / pose_lint_spec / pose_skills_check |
Run the deterministic gates |
pose_suggest |
Canonical trail per task type |
pose_get_workflow / pose_get_rules / pose_get_skill |
Operating procedure content |
pose_list_knowledge / pose_get_knowledge |
Operational memory |
pose_list_reports / pose_get_report |
Validation evidence |
pose_component_discover |
Deep discovery audit of a component: LOC count (prod vs test), debt markers (TODO/FIXME/stub/panic), languages and submodules |
pose_list_assessments / pose_get_assessment |
Component assessment markdown inventory (.pose/assessments/) / full assessment markdown content |
pose_integration_check / pose_get_integration_matrix |
Cross-component integration assessment (Protobuf, Kafka, REST, MCP) and provider vs consumer matrix |
pose_tech_debt_check / pose_get_tech_debt_report |
Codebase technical debt audit (TODO, FIXME, stub, panic) with file links and recommended POSE backlog actions |
pose_insights |
Deterministic outcome aggregates by workflow, task or context |
pose_usage |
Privacy-bounded local CLI/MCP usage, semantic outcomes, finding lifecycle and latency; supports since_days, tool and surface filters |
pose_extension_list |
List installed extensions (id, version, kind, digest, signature status) |
pose_validate_request |
Resolve an immutable, digest-pinned validation plan (no execution) |
pose_validate_approve |
Approve/reject a plan, bound to its digest, requiring an Execution Identity |
pose_validate_submit |
Hand an approved plan to the configured Harness executor |
pose_validate_status |
Read a validation request's current state and plan |
pose_validate_cancel |
Cancel a non-terminal validation request |
Every tool above is classified read (repository-owned governance state only)
except pose_check and pose_lint_spec, classified gate (deterministic
local gates — no writes, no network). The advertised catalog is a release-gated
public contract frozen by a golden fixture
(pose-mcp/internal/mcpserver/testdata/tool-catalog.golden.json); removals or
incompatible schema changes require an ADR and a release note.
pose_usage reads the same project-local aggregate as pose usage --json and
does not count its own query. Collection occurs automatically for project-backed
tool calls and is best-effort: a storage failure never changes the MCP result.
Only bounded outcomes, counts, duration and HMAC fingerprints are persisted;
arguments, tool output, paths, repository/project names, principals, run IDs,
source content and raw finding IDs are excluded. No usage event is transmitted
over the network.
Human confirmation of a finding as valid, wont-fix or false-positive is
not inferred from repeated calls. It remains a
tracked evolution; see Analytics and delivery metrics.
Optional tools
Three external-side-effect tools report externally observed runs to a
Harne8 Conductor control plane. They are always advertised in tools/list,
but calls only succeed when the reporter is activated via CONDUCTOR_URL,
CONDUCTOR_RUN_TOKEN and CONDUCTOR_PROJECT_ID; without activation they
return an isError result with configuration guidance.
| Tool | Effect |
|---|---|
conductor_run_open |
Open an observed external run (returns run_id, task_id) |
conductor_run_event |
Append a progress/checkpoint event to an open run |
conductor_run_close |
Close a run with its outcome and cost |
Project scope contract
Every pose_* tool advertises the same project_id schema. A default is
convenience only, never a silent guess. Project-backed tools resolve the
project from, in order, the project_id argument, then the
X-MCP-Project/X-Project-Id header, then the configured default root.
pose_mcp_context is deliberately evaluated before store resolution so it can
diagnose an absent, ambiguous or stale selection; its project IDs are filtered
through the caller's policy and Execution Identity.
Resolution failures are distinct, structured errors (isError: true,
structuredContent.error_code) that never include the resolved filesystem
root — only the caller-supplied logical identifier:
error_code |
Meaning | structuredContent |
|---|---|---|
project_unknown |
project_id does not resolve to any registered root, even after a rescan |
project_id, authorized available_project_ids, reconnect remediation |
project_ambiguous |
project_id was omitted and the server cannot pick one unambiguously |
reason: no-default or multi-project-implicit |
A third case — the resolved project exists but policy denies it — surfaces
through the existing JSON-RPC error -32004 with decision.Metadata()
(policy denied), not through structuredContent.
Connection verification
Treat static configuration and the connected process as separate states:
- Run
pose doctor --jsonto validate the local.mcp.json. Itsmcp.configfinding reportsdiagnostic_scope: static-configurationandconnection_checked: false; it never claims to inspect a client-owned stdio process. - Call
pose_mcp_contextbefore the first governed read. Passproject_idexplicitly to probe the repository expected by the current task. - Restart or reconnect the MCP client after changing
.mcp.json, switching repositories, or upgrading the binary. Callpose_mcp_contextagain and require a new/expected server instance and aresolvedproject probe. - Enable
POSE_MCP_STRICT_PROJECT_SELECTIONwhen more than one project is registered. Do not continue afterproject_unknownorproject_ambiguous; use the returned logical IDs and remediation.
The context response contains process metadata and authorized logical IDs only. It never returns project roots, configuration contents or environment values.
Two limits are worth knowing. Project IDs are filtered through the same policy
as any other call, so with no OPA endpoint configured — the default dev-mode
posture for every tool — discovery returns every registered project; configure
a policy endpoint when registry membership is itself confidential. And one
discovery call evaluates at most 64 projects; a larger registry sets
available_project_ids_truncated: true rather than looking complete.
Compatibility / deprecation window: with POSE_MCP_STRICT_PROJECT_SELECTION
unset (default), an empty project_id always resolves to the configured
default root, even once a deployment has onboarded more than one project —
existing single-project stdio ergonomics are exactly unchanged. Setting the
variable makes that same omission fail closed with
project_ambiguous/multi-project-implicit whenever more than one project
is registered; a genuinely single-project deployment is never affected by
the flag. Multi-project operators should plan to adopt it as project count
grows; it is expected to become the default in a future release.
Pagination and catalog stability
pose_list_specs, pose_list_roadmaps, pose_list_knowledge and
pose_list_reports accept optional cursor/limit arguments and return an
additive next_cursor field (empty when exhausted). Cursors are opaque,
versioned position tokens over each list's fixed deterministic order (spec
slug, roadmap slug, knowledge slug, or generated_at descending) — never
parse or construct one client-side; a malformed or wrong-version cursor is a
tool error, not silently coerced to page 1. Omitting both arguments returns
every item in a single page — the exact response shape from before
pagination existed.
The tool catalog itself never changes within one server process: tools/list
is a pure function of the binary and is byte-identical across calls in the
same session, so capabilities.tools.listChanged: false is verified, not
aspirational. A catalog change only happens across a release (a new
pose-mcp binary, a new serverInfo.version) — clients should reconnect
(re-initialize) after observing that version change; POSE does not (and,
given a static per-process catalog, has no reason to) emit
notifications/tools/list_changed events.
Resources and prompts are deliberately not implemented. Every governed
read POSE exposes — specs, roadmaps, knowledge, reports, workflows, rules,
skills — is already served through typed, schema-validated, project-scoped,
policy-gated tools. A generic MCP resources primitive would let a client
address arbitrary repository content by URI, which is exactly "expose
repository files wholesale" — explicitly out of scope. A generic prompts
primitive risks encoding procedure outside the reviewable
.pose/workflows/*.md files it is meant to expose, which is "turn prompts
into hidden policy" — also out of scope. capabilities therefore advertises
only tools; a tools-only client sees no unimplemented primitive to
misconfigure.
Safe validation orchestration
pose validate stays a local, unrestricted CLI command. Agents that need to
request validation through MCP go through a separate, deliberately narrow
state machine instead of an unsafe direct-execution passthrough:
pose_validate_request → pending_approval
│ (plan digest pins matrix + git HEAD + filters)
▼
pose_validate_approve → approved | rejected
│ (requires a bound Execution Identity; digest must match exactly)
▼
pose_validate_submit → submitted (idempotent; requires a Harness executor)
- The plan is immutable and digest-pinned.
pose_validate_requesthashes the exact validation matrix bytes plus git HEAD and the requested filters;pose_validate_approvemust echo that exactplan_digest, or the call is rejected as plan substitution — the request cannot be silently widened between resolution and approval. - Approval is never anonymous.
pose_validate_approverequires a valid, unexpiredX-MCP-Execution-Identitytoken (ADR-007) regardless of the server's default policy mode — a deployment running OPA in dev/allow-all still cannot approve orchestrated validation anonymously. Because the identity header only exists on the HTTP transport, orchestration approval is an HTTP-transport operation; stdio deployments inherit trust from the spawning client for localpose validate, which is unaffected by any of this. - pose-mcp never executes the plan.
pose_validate_submithands the approved, digest-pinned plan to a pluggableHarnessExecutor— wired withServer.WithHarnessExecutor, the same optional-dependency pattern asWithReporterfor Conductor. Without one configured, submission returns a clear configuration error; nothing runs. Submission is idempotent: resubmitting an already-submitted request returns the sameexecution_idwithout invoking the executor again. - State is local and in-process, not "centrally persisted" — a
production deployment centralizes run state in Conductor and plugs
execution in via
HarnessExecutor, the same relationship the Conductor run reporter tools already have to the Conductor board.pose_validate_statusreads a request's current state;pose_validate_cancelmarks a non-terminal request cancelled (a submitted request's cancellation reaching a running Harness execution is the executor's own responsibility, not pose-mcp's).
Security posture
- Default deny on OPA errors; policy decisions are audited
(
policy.decided/policy.violationstructured logs). - Shared-domain tools run in-process; CLI-backed tools invoke the current native executable. Every argument is validated and shell text is never evaluated.
- Multi-replica deployments need the Redis cursor store (enterprise hardening track); single-node dev needs nothing beyond the binary.