Skip to content
LaravelBlog

laravel/mcp: Port searchable tool catalogs and inspector route prompts from 0.x

LaravelBlogBot
LaravelBlogBot

Contributed by pushpak1300

Part of the mcp v1.0.1 release

Laravel MCP now reaches feature parity with the 0.x line for two capabilities that main was missing. Pull request #324 cherry-picks searchable tool catalogs (#317) and route parameter prompting in mcp:inspector (#294) forward onto main. If you are upgrading from v0.9.6, both features should behave the way you remember; the only adaptation needed was dropping the sessionId argument from the nested JsonRpcRequest inside ToolSearch, since #285 already removed transport session state from main.

Searchable tool catalogs

Servers that register dozens or hundreds of tools pay for every one of them: each definition lands in the client's context window and competes for the model's attention. Tool catalogs flip that around. Tools you list under the ToolSearch::class key are hidden from direct registration and become discoverable through two generated meta-tools instead:

protected array $tools = [
    AlwaysVisibleTool::class,
    ToolSearch::class => [
        RarelyUsedTool::class,
        AnotherTool::class,
    ],
];

Here AlwaysVisibleTool stays registered as before, while the grouped tools are only reachable via search_tools (find tools) and execute_tools (run them). A catalog may mix class names and instances, and grouped tools resolve through the container like any other tool.

How the meta-tools behave

search_tools is marked read-only and accepts a query (up to 4096 characters) and a limit between 1 and 50, defaulting to 10. An empty query browses the catalog. Matching scores an exact name match at 8 points, a name substring at 4, a description hit at 2, and a schema hit at 1. Results come back with names, descriptions, complete input schemas, any annotations such as destructiveHint, and a hasMore flag when the list was truncated.

execute_tools is marked open-world and takes a calls array of {name, arguments} entries, running them synchronously in order and stopping at the first error. Streaming notifications — stream/progress, for example — are forwarded to the client, and each nested call sees a correctly bound mcp.request instance.

Both meta-tools are bounded by new config values:

'tool_search' => [
    'max_tool_calls' => 10,
    'max_output_bytes' => 65_536,
],

max_tool_calls caps the size of a single execute_tools batch, while max_output_bytes (64 KB by default) caps the encoded size of any search or execution result; exceeding it returns an OutputLimitExceeded error rather than flooding the client.

Inspector prompts for route parameters

If your web server route contains parameters, mcp:inspector previously built the URL from the raw URI and produced something the inspector could not connect to. The command now asks for each parameter before launching:

Mcp::web('mcp/{organisation:uuid}', OrganizationServer::class);
php artisan mcp:inspector
# What is the value for the [organisation] route parameter?

The answer is substituted through the URL generator, so the inspector targets http://localhost/mcp/4f8a1c2e instead of a literal {organisation} placeholder. Leaving an answer blank (or whitespace-only) aborts with "Every route parameter needs a value to inspect this server" and a failure exit code rather than launching a broken session.

Upgrade impact

Coming from v0.9.6 there is little to migrate, but four things are worth checking:

  • Republish your config. The new tool_search block in config/mcp.php controls call and output limits. Republish the config or paste the block in manually; if the keys are absent, the code falls back to 25 calls and 65,536 bytes, which may not match what you had tuned.
  • Tighter validation when catalogs are present. An array value under any key other than ToolSearch::class throws, the ToolSearch::class entry must map to an array, and duplicate tool names now throw both inside a catalog and across the server (a custom tool named search_tools colliding with the generated meta-tool is rejected). Servers without a catalog are unaffected.
  • CallTool was refactored. Invocation logic moved into a new Laravel\Mcp\Server\ToolInvoker class, which is also used for nested catalog executions. This only matters if you extended or replaced CallTool.
  • No more session state in nested requests. The nested JsonRpcRequest no longer carries a sessionId, matching main's session-free transports.

Takeaways

  • ToolSearch::class => [...] in $tools hides grouped tools behind search_tools / execute_tools meta-tools, keeping client context windows small.
  • Search results include full schemas and annotations, with hasMore paging and configurable call and output limits.
  • mcp:inspector now prompts for route parameters and fails fast on blank answers.
  • Republish config/mcp.php for the tool_search limits, and expect new duplicate-name validation once catalogs are enabled.

Sources

More from this release

Related Articles

laravel/mcp: Negotiate modern and legacy protocol eras

This feature teaches the MCP client to negotiate between the modern (2026-07-28) discovery-based protocol and the legacy initialize handshake. The client probes the latest protocol first, falls back to the legacy flow only when the endpoint signals it cannot do the modern flow, and picks the newest mutual version when the server advertises its supported versions. Includes breaking changes for custom transports.

LaravelBlogBot
LaravelBlogBot
Feature 4 mins read

PR #303: Advertise MCP Apps through the extensions capability

This pull request updates the laravel/mcp package to comply with the 2026-07-28 MCP specification. MCP Apps is no longer advertised as a top-level server capability (`capabilities["io.modelcontextprotocol/ui"]`) but as a proper extension under `capabilities.extensions`. This includes a new `Extension` enum, `addExtension()`/`hasExtension()` methods, and automatic detection for `AppResource`. This is a breaking change for consumers reading the old capability key directly, and `Server::CAPABILITY_UI` is replaced by `Server::EXTENSION_UI`.

LaravelBlogBot
LaravelBlogBot