ADR-0007: Vault MCP — Tier 1 local stdio architecture
Context
MCP (Model Context Protocol) enables AI agents to interact with files, systems, and services through a standardized tool interface. Thalix Vault has an existing CLI (tv) with a stable, audited, grant-enforced operational surface for files, folders, templates, workspaces, and tags. The question is how MCP reaches that surface without introducing new credential paths, public APIs, or hosted runtimes, and how a stage can constrain the MCP tools an agent sees.
Decision
Tier 1: Local stdio server
- Entry point:
tv mcp servestarts a local Model Context Protocol v2 stdio server. No separate package, no second distribution channel. The sametvbinary is the only artifact.
- Delegation model: The MCP server never imports CLI internals or makes direct API calls. Every tool call spawns the same
tvexecutable with--jsonoutput, parses the stable CLI envelope (result/error/structured data), and returns it as MCP tool results. This keeps the implementation small and audit-identical to direct CLI usage.
- Credential reuse: The server inherits the existing file-based credential store via an allowlist of environment variables (
HOME,USERPROFILE,APPDATA) plus safe Vault-specific configuration. No new credential type is introduced. The bearer token is never read, printed, returned, or stored by MCP code.
- MCP v2 packages: The server uses the stable
@modelcontextprotocol/serverand@modelcontextprotocol/clientv2.x packages from an internalpackages/mcpworkspace package. The v1 compatibility SDK is not used. Node 20+ is required.
Tier 1: Stage-scoped tool visibility
- Stage contract: Each stage's direct
CONTEXT.mdfile contains an optional YAML frontmatter block: ``yaml --- mcp: tools: - vault_file_info - vault_file_find ---`` This is the authority for which tools that stage exposes via MCP.
- Effective visibility formula: A caller sees tools only if all of the following hold: - The tool is in the stage contract's
mcp.toolslist (or unscoped identity/discovery tools when stage is not specified). - The caller's effective folder-grant rank permits it:readallows info/list/find/get/show/checkout/walk-test;writeadds content upload/metadata/tag tools;manageadds lifecycle/template/workspace creation tools. - Identity and discovery tools (vault_whoami, template/workspace/stage discovery) are always visible when unscoped, to permit context selection.
- Scope enforcement: A new authenticated backend RPC
resolve_mcp_stage_scopereturns the caller's effective grant rank for a stage without exposing hidden/cross-org IDs. A dedicated append-onlymcp_scope_audit_eventstable records visibility decisions: actor, workspace/stage ID, contract file/version, visible tool names, and result. On everytools/listandtools/call, the server re-validates that the tool is still in the cached visible set; if not, it denies the call locally without spawningtv.
Tier 1: Tool registry and execution
- Declarative registry: A single typed registry source owns tool name, description, strict JSON schema, required grant rank, CLI argv template, and whether local filesystem access is required. Every tool maps 1:1 to an implemented shipped
tvcommand. The registry is the contract; no MCP-only data paths exist.
- Audit parity: Each MCP tool invocation spawns the same CLI command and backend RPC that a direct CLI call would. The CLI's normal correlation ID is preserved in the MCP envelope. MCP adds exactly one audit operation: the scope-visibility decision in
mcp_scope_audit_events.
- Launch configuration: The server accepts optional startup flags: -
--workspace <root-id>: Scope to a specific workspace; required to operate on files/folders/versions. ---stage <stage-folder-id|stage-name>: Scope to a stage within the workspace; required for stage-scoped tool filtering. Both flags must be present together for scoped operation. Without them, only discovery tools are available.
Tier 2: Deferred hosted MCP
- Tier 2 scope deferred: Hosted MCP (remote servers, cloud deployment, AI agent runtimes) remains an open epic (#72). No service credentials, public API, or hosted runtime are introduced in Tier 1. When Tier 2 is reconsidered, the credential model, authentication, and authorization boundary must be revisited with fresh security review.
Rationale
Why delegation to tv? The CLI is already audited, authorized, and proven. Delegating via JSON subprocess keeps MCP stateless and shallow—no internal API coupling, no credential management in the server, no bypass of existing checks. The subprocess overhead is acceptable for agent-driven workflows. Audit trails are identical because both paths use the same backend RPC.
Why stage-scoped CONTEXT.md? The stage is the natural ICM (Interpretable Context Methodology) boundary for agent context. Every stage already has a CONTEXT.md that describes its purpose and contents to humans. Using it for MCP tool visibility is a single-source-of-truth: humans and agents see the same authorization scope. YAML frontmatter is parseable, bounded, and separable from human documentation.
Why append-only audit? Scope decisions (which tools are visible to which actor in which stage) are security-critical and must be queryable. A dedicated mcp_scope_audit_events table keeps visibility reasoning separate from operation audit; it records grants, contract state, and visibility decisions without overloading the fixed schema of operation_audit_events.
Why effective-grant intersection? Folder grants are inherited; a stage's grant cannot narrower than its parent's. MCP cannot introduce a new privilege escalation path. The contract list (mcp.tools) is the narrowing authority; inherited folder rank is the floor. This is consistent with Vault's own authorization model.
Why no hosted tier in v1? Hosted MCP requires solving credential distribution (how does a cloud service get auth?), revocation (how is access invalidated?), and agent runtime security (what's the blast radius?). These are not solved in Tier 1. Reopening them in ADR-0008 or later, with explicit security review and architectural approval, avoids premature closure.
Consequences
- Every MCP tool call spawns a subprocess. This is acceptable for agent-driven workflows; real-time or latency-sensitive use cases should use the CLI directly.
- MCP is a read/write surface identical to the CLI. The audit surface grows by one table (
mcp_scope_audit_events); no data replication or consistency issues. - Tool discovery is dynamic: a cached client list is re-validated on every call. This adds a small backend round-trip but prevents stale tool references.
- Stage contract changes are detected on the next scope-state check and notify the client via MCP
notifications/tools/list_changed. - Hosted MCP, public API, and service credentials are explicitly out-of-scope for Tier 1. A future Tier 2 must reopen their security and architectural decisions.
Alternatives considered
Alternative 1: Direct API binding instead of subprocess delegation
Rejected: Importing CLI internals into the MCP server would couple them; server crashes or misbehavior could bypass authorization checks. Subprocess delegation keeps authority in the CLI's main process. Audit trail ambiguity (which path did a call take?) is also eliminated.
Alternative 2: MCP-first data path (no tv delegation)
Rejected: A separate MCP data layer would require reimplementing or exposing internal authorization, grant resolution, audit, and credential handling. This duplicates security logic and creates two audit paths—unacceptable for a security boundary.
Alternative 3: Tier 1 includes hosted MCP
Rejected: Hosted MCP requires solving credential distribution, service authentication, and runtime security boundaries. Deferring these to an explicit later ADR (ADR-0008 or #72 completion) ensures they are not under-specified and receive dedicated review.
Alternative 4: Tool visibility by CLI argument (no stage contract)
Rejected: Every call would require explicit --allow-tools flags, or the server would allow all tools. The stage contract is the single source of truth and makes visibility queryable/auditable.
Security boundaries and rollback
Security boundaries
- Token non-disclosure: The bearer token is never read by MCP code. It is stored in the file-based credential file (ADR-0005). Environment inheritance is via an explicit allowlist; unknown env vars are not forwarded.
- Subprocess isolation:
tvsubprocess runs with the same UID as the parent process; no privilege elevation. Stderr is captured and stripped of sensitive paths before returning to the MCP client.
- Scope validation: Stage contract and effective grant are re-validated on every tool call. A stale cached list is denied locally.
- Audit capture: Scope decisions are recorded in
mcp_scope_audit_events; they include visible tool names and hidden-tool count (not names). Cross-org/hidden workspaces are anti-enumerated via theresolve_mcp_stage_scopebackend RPC, which uses existing access-control rules.
- Input validation: YAML frontmatter is parsed with a safe schema-only parser (aliases and tags disabled, node-count/byte bounds enforced). Unknown tools or duplicate entries fail closed.
Rollback
Code rollback: Normal PR revert. If deployed to production, MCP and workspace routes are disabled in the web/CLI entry points first; the additive database objects can remain dormant until a later cleanup migration.
Database rollback: The migration is additive only—two new functions (resolve_mcp_stage_scope, record_mcp_scope_audit) and one append-only table. Existing files, folders, versions, tags, grants, and audit records are untouched. Rollback requires only code disabling and (optionally, later) dropping the new table.
Credential and auth rollback: No new credential types are introduced. File-based credential storage (ADR-0005) remains the only mechanism. Rollback does not affect credential data.
Validation evidence
Acceptance requires the following per the companion implementation plan:
- #73: ADR states the chosen tier, auth, delegation, scoping, and deferred Tier 2 security decision. ✓
- #74: MCP stdio server initializes, lists tools, and calls
vault_whoamithrough the storedtvsession. Token never appears in stdout, stderr, results, or errors. - #75: Declarative typed schemas cover the bounded shipped file/folder/tag/version/access surface. Tool invocation delegates to
tv; backend grant and audit behavior is identical. Hidden/cross-org resources remain anti-enumerated. - #76: Typed template/workspace tools delegate to
tv. Instantiate/derive retain lineage. Real MCP instantiate→checkout→edit→commit succeeds and records immutable versions/audit. - #77: A direct stage contract determines the dynamic
tools/listresult. Visibility is intersected with effective read/write/manage access. Grant/contract changes are detected by the next scope-state check and notify the client.
Related decisions
- ADR-0006 (ICM plugin): Defines workspace as an ICM entity with lineage and template structure. MCP Tier 1 operates on workspaces and stages.
- ADR-0005 (private CLI credential file): MCP reuses the file-based credential store; no new credential type.
- Issue #70 (workspace seam): Defines checkout/commit surface that MCP delegates to via
tv workspacecommands. - Issue #72 (MCP epic): Contains Tier 2 hosted MCP and the explicit security review that defers it.
- Issues #73–#77: Implementation tasks for Tier 1 (this ADR, workspace contracts, MCP skeleton, registry, template/workspace tools, stage scoping, and audit).