MCP tool manifests: how should a tool say "this is the request body" when the field names are not known in advance?
Nobody has claimed this yet.
Assessment
- Difficulty
- 5/5
- Estimated time
- Over a week
- Newbie friendliness
- 35/100
Research direction
Start by reading the manifest table in README, openapi.yaml, and the McpToolCollector validation and fixture tests. Compare the proposed body behavior with the existing query and path_params fields, then review the linked server responsibilities and ordering. Done means the project has chosen an option, defined root additionalProperties behavior, and recorded the resulting API contract and tests.
Written by the indexing model from the issue text.
Description
Summary
The mcp.yaml manifest format works well when the plugin author knows every body field at authoring time (KahunaCart's case). It has no way to describe a route whose body fields are decided by the site, such as Flex Objects, where the fields come from each site's blueprints. Trying to write a Flex manifest against the 1.0.23 spec surfaced two gaps, one in the implementations and one in the spec itself. Since the format is new and only one manifest exists so far, this seems like the moment to settle it. I'd like your direction before anyone codes against a guess.
The problem in plain terms
A manifest tool hands the model one flat JSON object to fill in. That object has to carry two different things: the address of the REST call (path placeholders and query parameters) and the payload (the JSON body). The MCP server pulls the address fields out, builds the URL, and sends whatever is left as the body.
For a route like PATCH /flex-objects/{type}/{key} the payload's keys are blueprint fields the manifest author cannot list. Two things go wrong:
-
Undeclared fields were silently dropped. The spec allows
additionalProperties: trueon the rootinput, andMcpToolCollectorpreserves it (and assigns it to a properties-less object). But neither MCP server honored it at the root: grav-mcp hands the SDK a raw Zod shape, which strips unknown keys, and grav-plugin-mcp-server filtered to declared properties. The tool validated and listed fine; the fields never reached the route. Both servers honor it on nested objects. (grav-plugin-mcp-server 1.2.4 now passes them through when the root opts in; grav-mcp still strips.) -
Address and payload share one namespace. Even with pass-through, a blueprint field named
typeorkeyis consumed as a path parameter and can never be set. No pass-through rule fixes this, because the manifest cannot say which property is the body.
The workaround available today is an envelope property (say object) holding the fields, with the plugin's controller unwrapping it when the body's only key is that envelope. That puts MCP knowledge into a REST controller and every plugin with this shape would repeat the heuristic.
Options
A. Add a per-tool body key (my preference), parallel to the existing query:
- name: update_object
method: PATCH
path: /flex-objects/{type}/{key}
body: object # this property's value IS the request body
input:
type: object
required: [type, key, object]
properties:
type: { type: string }
key: { type: string }
object: { type: object, additionalProperties: true, description: "Fields per the blueprint" }
Rules: body names a declared property of type object; it may not be a path placeholder or appear in query; when body is set, every other property must be a path placeholder or a query name, so nothing is left ambiguous. Controllers receive an ordinary REST body and never learn MCP exists. Path and field names can no longer collide.
B. Rely on root additionalProperties: true alone. Both servers pass undeclared arguments through; the spec documents that the root keyword means "open body". Cheapest, but the type/key collision stays, and the model sees address and payload mixed at one level.
C. Leave the spec as is and let plugins with dynamic bodies unwrap an envelope in their controllers. Works today, but each plugin re-implements the same guess.
Independently of A/B/C, the spec should state what root additionalProperties: true means: honored (pass-through) or rejected by the validator. Silent dropping was the worst outcome and should not be reachable.
What each repo would change (under option A)
- grav-plugin-api:
bodyin the README manifest table andopenapi.yaml; validation inMcpToolCollectorper the rules above; echobodyinGET /mcp/toolsbesidequeryandpath_params; fixture tests; document the rootadditionalPropertiesrule. Lands first, since both servers read the field from this response. - grav-mcp:
splitArgumentsinsrc/tools/plugin-tools.tsusesdefinition.bodywhen present; register tools so rootadditionalProperties: trueis honored rather than stripped;docs/plugin-tools-spec.mdand tests. - grav-plugin-mcp-server: same handler change for
body(root pass-through already shipped in 1.2.4); tests. - grav-plugin-flex-objects: ship
mcp.yamlwithbody: objecton create and update; no controller change; declare the api release that understandsbodyas its dependency so an older api rejects the manifest instead of serving a broken tool. - grav-plugin-kahunacart: nothing. Its bodies have fixed fields and are unaffected either way.
Ordering matters for A: api first, then both servers, then Flex. A Flex manifest reaching a server that predates body would send {"object": {...}} literally.
Questions
- Is
bodythe direction you want, or do you prefer B or C? - Root
additionalProperties: true: honor or reject? - If A, any preference on the key name (
body) and on whether leftover non-path, non-query properties are an error or ignored?
Happy to open the PRs against grav-plugin-api and grav-mcp once you've picked.
- Dominant language
- PHP
- Stars
- 5
- Forks
- 12
- Avg merge
- 9h 12m
- Merged PRs (30d)
- 6
Getting set up
This project ships no dev container, Dockerfile or contributing guide, so setting up is up to you: start from its README, and see our first-contribution guide for the general steps.
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from getgrav/grav-plugin-api
-
Difficulty 2/5 1-3 hours Newbie friendliness 65/100
getgrav/grav-plugin-api#66 ·
-
Difficulty 4/5 3-5 days Newbie friendliness 54/100
getgrav/grav-plugin-api#65 ·
-
Difficulty 5/5 Over a week Newbie friendliness 30/100
getgrav/grav-plugin-api#61 · 1 comment ·
-
Difficulty 4/5 3-5 days Newbie friendliness 35/100
getgrav/grav-plugin-api#51 · 2 comments ·
-
Difficulty 5/5 Over a week Newbie friendliness 45/100
getgrav/grav-plugin-api#42 · 1 reaction ·
All issues in getgrav/grav-plugin-api
Similar issues
-
frontend good first issue healthcare-review
Difficulty 2/5 1-3 hours Newbie friendliness 83/100
AfyaLinkAfrica/afyalink-web#25 ·
Maintainers usually reply within 1 day
-
priority:medium type:chore
Difficulty 1/5 1-3 hours Newbie friendliness 84/100
crazy-goat/php-fpm-ng#1041 ·
Maintainers usually reply within 1 day
-
Bug Status: Needs Triage
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 64/100
backdrop-contrib/paragraphs#299 ·