Skip to content

feat(mcp): add MCP-029, PHP tool has no type-annotated parameters - #80

Open
eulestadt wants to merge 2 commits into
trustabl:mainfrom
eulestadt:feat/mcp-023-php-untyped-params
Open

feat(mcp): add MCP-029, PHP tool has no type-annotated parameters#80
eulestadt wants to merge 2 commits into
trustabl:mainfrom
eulestadt:feat/mcp-023-php-untyped-params

Conversation

@eulestadt

@eulestadt eulestadt commented Aug 24, 2026

Copy link
Copy Markdown

Renamed this from MCP-023 to MCP-029 before anything else, because #82, #85, #88 and #91 claim 023 through 028. I filed 023 about four minutes earlier, but that's not really the point — Brad's stack occupies the whole run above it, so yielding is one edit for me against five for him. A duplicate rule ID stops the loader at startup rather than failing a test, so two green PRs merging an hour apart would have broken every scan until someone sorted it out. The branch is still named mcp-023; renaming it would close this PR, so I left it alone.

The rule itself: nine of ten packs ship an untyped-parameters rule, and within MCP, MCP-002 covers Python while PHP had nothing. PHP is the only other MCP language where the gap can exist — Go, C# and Rust are statically typed, so the handler signature always constrains the published schema and there's nothing to detect. That's why this is a two-language rule and not a five-language one, and why widening MCP-002 would have been wrong: its fix text prescribes Python annotations.

Mechanically, MCP builds the advertised input schema from the handler's parameter type hints. With no hints the schema is unconstrained, so the model sends whatever it infers from the description and the failure lands inside the server as a type error or a quiet coercion, instead of being rejected at the boundary where a schema would catch it.

medium/0.85, matching MCP-002 exactly — high for a heuristic because the signal is structural rather than name-based.

I scanned a PHP MCP server with four handlers. searchDocs($query, $limit) fires. fetchInvoice(string $invoiceId, int $version = 1): array stays quiet, which covers the hinted-with-a-default shape most likely to trip a naive matcher. ping(): string stays quiet because has_params: true gates it out — without that conjunct every no-arg health check in every PHP server would fire.

The fourth handler is the honest part. HasTypedParams is set when any parameter carries a type, so one hint silences the whole signature — mixedHints(string $query, $limit) reports nothing despite $limit being bare. I checked that with a scan rather than assuming it from the code. It's the reason for 0.85, and tightening it would mean an all-params-typed predicate that changes MCP-002 and eight other packs, so it belongs in its own PR. Schemas published outside the signature and #[McpResource] / #[McpPrompt] are out of scope too.

has_params and has_typed_params both already ship, so no schema bump. Fixture mirror is trustabl#124 and the rationale is trustabl-rulebook#45; this one needs to merge first, since rules-sync can't see a fork branch and the engine check stays red until it does.

Made with Cursor

PHP type hints are optional and discovery already captures HasTypedParams; this is the MCP-002 sibling the rulebook listed as a deliberate fast-follow.

Co-authored-by: Cursor <cursoragent@cursor.com>
@eulestadt

Copy link
Copy Markdown
Author

bradAGI's open PR stack claims MCP-023 through MCP-028. A duplicated rule
ID is rejected by the loader at startup, so shipping both would break every
scan rather than fail a test. MCP-029 is the first free ID above that stack.

Co-authored-by: Cursor <cursoragent@cursor.com>
@eulestadt eulestadt changed the title feat(mcp): add MCP-023, PHP tool has no type-annotated parameters feat(mcp): add MCP-029, PHP tool has no type-annotated parameters Aug 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant