@@ -12,7 +12,7 @@ various aspects of the server behavior.
1212- [ Session Management] ( #session-management )
1313- [ Manual Capability Registration] ( #manual-capability-registration )
1414- [ Service Dependencies] ( #service-dependencies )
15- - [ Custom Capability Handlers] ( #custom-capability -handlers )
15+ - [ Custom Method Handlers] ( #custom-method -handlers )
1616- [ Complete Example] ( #complete-example )
1717- [ Method Reference] ( #method-reference )
1818
@@ -344,102 +344,50 @@ $server = Server::builder()
344344 ->setEventDispatcher($eventDispatcher);
345345```
346346
347- ## Custom Capability Handlers
347+ ## Custom Method Handlers
348348
349- ** Advanced customization for specific use cases.** Override the default capability handlers when you need completely custom
350- behavior for how tools are executed, resources are read, or prompts are generated. Most users should stick with the default implementations.
349+ ** Low-level escape hatch.** Custom method handlers run before the SDK’s built-in handlers and give you total control over
350+ individual JSON-RPC methods. They do not receive the builder’s registry, container, or discovery output unless you pass
351+ those dependencies in yourself.
351352
352- The default handlers work by:
353- 1 . Looking up registered tools/resources/prompts by name/URI
354- 2 . Resolving the handler from the container
355- 3 . Executing the handler with the provided arguments
356- 4 . Formatting the result and handling errors
357-
358- ### Custom Tool Caller
359-
360- Replace how tool execution requests are processed. Your custom ` ToolCallerInterface ` receives a ` CallToolRequest ` (with
361- tool name and arguments) and must return a ` CallToolResult ` .
353+ Attach handlers with ` addMethodHandler() ` (single) or ` addMethodHandlers() ` (multiple). You can call these methods as
354+ many times as needed; each call prepends the handlers so they execute before the defaults:
362355
363356``` php
364- use Mcp\Capability\Tool\ToolCallerInterface;
365- use Mcp\Schema\Request\CallToolRequest;
366- use Mcp\Schema\Result\CallToolResult;
367-
368- class CustomToolCaller implements ToolCallerInterface
369- {
370- public function call(CallToolRequest $request): CallToolResult
371- {
372- // Custom tool routing, execution, authentication, caching, etc.
373- // You handle finding the tool, executing it, and formatting results
374- $toolName = $request->name;
375- $arguments = $request->arguments ?? [];
376-
377- // Your custom logic here
378- return new CallToolResult([/* content */]);
379- }
380- }
381-
382357$server = Server::builder()
383- ->setToolCaller(new CustomToolCaller());
358+ ->addMethodHandler(new AuditHandler())
359+ ->addMethodHandlers([
360+ new CustomListToolsHandler(),
361+ new CustomCallToolHandler(),
362+ ])
363+ ->build();
384364```
385365
386- ### Custom Resource Reader
387-
388- Replace how resource reading requests are processed. Your custom ` ResourceReaderInterface ` receives a ` ReadResourceRequest `
389- (with URI) and must return a ` ReadResourceResult ` .
366+ Custom handlers implement ` MethodHandlerInterface ` :
390367
391368``` php
392- use Mcp\Capability\Resource\ResourceReaderInterface ;
393- use Mcp\Schema\Request\ReadResourceRequest ;
394- use Mcp\Schema\Result\ReadResourceResult ;
369+ use Mcp\Schema\JsonRpc\HasMethodInterface ;
370+ use Mcp\Server\Handler\MethodHandlerInterface ;
371+ use Mcp\Server\Session\SessionInterface ;
395372
396- class CustomResourceReader implements ResourceReaderInterface
373+ interface MethodHandlerInterface
397374{
398- public function read(ReadResourceRequest $request): ReadResourceResult
399- {
400- // Custom resource resolution, caching, access control, etc.
401- $uri = $request->uri;
402-
403- // Your custom logic here
404- return new ReadResourceResult([/* content */]);
405- }
406- }
375+ public function supports(HasMethodInterface $message): bool;
407376
408- $server = Server::builder()
409- ->setResourceReader(new CustomResourceReader());
377+ public function handle(HasMethodInterface $message, SessionInterface $session);
378+ }
410379```
411380
412- ### Custom Prompt Getter
413-
414- Replace how prompt generation requests are processed. Your custom ` PromptGetterInterface ` receives a ` GetPromptRequest `
415- (with prompt name and arguments) and must return a ` GetPromptResult ` .
416-
417- ``` php
418- use Mcp\Capability\Prompt\PromptGetterInterface;
419- use Mcp\Schema\Request\GetPromptRequest;
420- use Mcp\Schema\Result\GetPromptResult;
421-
422- class CustomPromptGetter implements PromptGetterInterface
423- {
424- public function get(GetPromptRequest $request): GetPromptResult
425- {
426- // Custom prompt generation, template engines, dynamic content, etc.
427- $promptName = $request->name;
428- $arguments = $request->arguments ?? [];
429-
430- // Your custom logic here
431- return new GetPromptResult([/* messages */]);
432- }
433- }
381+ - ` supports() ` decides if the handler should look at the incoming message.
382+ - ` handle() ` must return a JSON-RPC ` Response ` , an ` Error ` , or ` null ` .
434383
435- $server = Server::builder()
436- ->setPromptGetter(new CustomPromptGetter());
437- ```
384+ Check out ` examples/custom-method-handlers/server.php ` for a complete example showing how to implement
385+ custom ` tool/list ` and ` tool/call ` methods independently of the registry.
438386
439- > ** Warning** : Custom capability handlers bypass the entire default registration system (discovered attributes, manual
440- > registration, container resolution, etc. ). You become responsible for all aspect of execution, including error handling,
441- > logging, and result formatting. Only use this for very specific advanced use cases like custom authentication, complex
442- > routing, or integration with external systems .
387+ > ** Warning** : Custom method handlers bypass discovery, manual capability registration, and container lookups (unlesss
388+ > you explicitly pass them ). Tools, resources, and prompts you register elsewhere will not show up unless your handler
389+ > loads and executes them manually.
390+ > Reach for this API only when you need that level of control and are comfortable taking on the additional plumbing .
443391
444392## Complete Example
445393
@@ -505,9 +453,8 @@ $server = Server::builder()
505453| ` setLogger() ` | logger | Set PSR-3 logger |
506454| ` setContainer() ` | container | Set PSR-11 container |
507455| ` setEventDispatcher() ` | dispatcher | Set PSR-14 event dispatcher |
508- | ` setToolCaller() ` | caller | Set custom tool caller |
509- | ` setResourceReader() ` | reader | Set custom resource reader |
510- | ` setPromptGetter() ` | getter | Set custom prompt getter |
456+ | ` addMethodHandler() ` | handler | Prepend a single custom method handler |
457+ | ` addMethodHandlers() ` | handlers | Prepend multiple custom method handlers |
511458| ` addTool() ` | handler, name?, description?, annotations?, inputSchema? | Register tool |
512459| ` addResource() ` | handler, uri, name?, description?, mimeType?, size?, annotations? | Register resource |
513460| ` addResourceTemplate() ` | handler, uriTemplate, name?, description?, mimeType?, annotations? | Register resource template |
0 commit comments