Two places where the MCP surface is narrower than the CLI for the same operation. Both are
schema/validation choices, so an agent driven over MCP cannot do what the CLI documents.
1. forgeplan_unlink (MCP) rejects the brownfield relation set
crates/forgeplan-mcp/src/server.rs:1276 defines the accepted argument set as exactly five
variants:
enum RelationKind { Informs, BasedOn, Supersedes, Contradicts, Refines }
The core recognizes two more names as coverage relations - crates/forgeplan-core/src/brownfield.rs:493:
"informs" | "based_on" | "refines" | "demonstrates" | "covers"
The CLI reaches core's wider set (its relation argument is normalized by
link::normalize_relation, crates/forgeplan-cli/src/commands/link.rs:14), so demonstrates and
covers round-trip on the CLI but are rejected at the MCP schema layer as an invalid enum value -
a schema rejection, not a domain error, which makes it look like a bad request rather than an
unsupported relation.
2. forgeplan_ingest (MCP) refuses a directory source; the CLI walks it
phase5_validate_path (crates/forgeplan-mcp/src/server.rs:10398-10405) requires a regular file:
if !canon.is_file() { return Err("path is not a regular file"); }
The CLI's ingest walks directories - crates/forgeplan-cli/src/commands/ingest.rs:355-362
descends into ft.is_dir() entries and collects files. So the same command with a directory source
works from the shell and fails over MCP.
Why it matters
Agent-facing surfaces are where these get hit: an orchestrator that learned a workflow from the CLI
docs writes the same call over MCP and gets a rejection it cannot interpret from the message.
Fix
Either widen the MCP argument types to match core/CLI, or document the narrower contract on the MCP
tool descriptions. Deciding which takes an owner's call: widening changes a security-adjacent
schema, documenting leaves two surfaces that differ.
Revisit trigger
Act if an MCP-driven run reports "invalid value" for a relation the CLI accepts, or if the ingest
engine's MCP surface is described in a release note as equivalent to the CLI's.
Two places where the MCP surface is narrower than the CLI for the same operation. Both are
schema/validation choices, so an agent driven over MCP cannot do what the CLI documents.
1.
forgeplan_unlink(MCP) rejects the brownfield relation setcrates/forgeplan-mcp/src/server.rs:1276defines the accepted argument set as exactly fivevariants:
The core recognizes two more names as coverage relations -
crates/forgeplan-core/src/brownfield.rs:493:The CLI reaches core's wider set (its relation argument is normalized by
link::normalize_relation,crates/forgeplan-cli/src/commands/link.rs:14), sodemonstratesandcoversround-trip on the CLI but are rejected at the MCP schema layer as an invalid enum value -a schema rejection, not a domain error, which makes it look like a bad request rather than an
unsupported relation.
2.
forgeplan_ingest(MCP) refuses a directory source; the CLI walks itphase5_validate_path(crates/forgeplan-mcp/src/server.rs:10398-10405) requires a regular file:The CLI's ingest walks directories -
crates/forgeplan-cli/src/commands/ingest.rs:355-362descends into
ft.is_dir()entries and collects files. So the same command with a directory sourceworks from the shell and fails over MCP.
Why it matters
Agent-facing surfaces are where these get hit: an orchestrator that learned a workflow from the CLI
docs writes the same call over MCP and gets a rejection it cannot interpret from the message.
Fix
Either widen the MCP argument types to match core/CLI, or document the narrower contract on the MCP
tool descriptions. Deciding which takes an owner's call: widening changes a security-adjacent
schema, documenting leaves two surfaces that differ.
Revisit trigger
Act if an MCP-driven run reports "invalid value" for a relation the CLI accepts, or if the ingest
engine's MCP surface is described in a release note as equivalent to the CLI's.