Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
"Mcp\\Example\\StdioDiscoveryCalculator\\": "examples/stdio-discovery-calculator/",
"Mcp\\Example\\StdioEnvVariables\\": "examples/stdio-env-variables/",
"Mcp\\Example\\StdioExplicitRegistration\\": "examples/stdio-explicit-registration/",
"Mcp\\Example\\CustomMethodHandlers\\": "examples/custom-method-handlers/",
"Mcp\\Tests\\": "tests/"
}
},
Expand Down
73 changes: 73 additions & 0 deletions examples/custom-method-handlers/CallToolRequestHandler.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<?php

/*
* This file is part of the official PHP MCP SDK.
*
* A collaboration between Symfony and the PHP Foundation.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Mcp\Example\CustomMethodHandlers;

use Mcp\Schema\Content\TextContent;
use Mcp\Schema\JsonRpc\Error;
use Mcp\Schema\JsonRpc\Request;
use Mcp\Schema\JsonRpc\Response;
use Mcp\Schema\Request\CallToolRequest;
use Mcp\Schema\Result\CallToolResult;
use Mcp\Schema\Tool;
use Mcp\Server\Handler\Request\RequestHandlerInterface;
use Mcp\Server\Session\SessionInterface;

/** @implements RequestHandlerInterface<CallToolResult> */
class CallToolRequestHandler implements RequestHandlerInterface
{
/**
* @param array<string, Tool> $toolDefinitions
*/
public function __construct(private array $toolDefinitions)
{
}

public function supports(Request $request): bool
{
return $request instanceof CallToolRequest;
}

/**
* @return Response<CallToolResult>|Error
*/
public function handle(Request $request, SessionInterface $session): Response|Error
{
\assert($request instanceof CallToolRequest);

$name = $request->name;
$args = $request->arguments ?? [];

if (!isset($this->toolDefinitions[$name])) {
return new Error($request->getId(), Error::METHOD_NOT_FOUND, \sprintf('Tool not found: %s', $name));
}

try {
switch ($name) {
case 'say_hello':
$greetName = (string) ($args['name'] ?? 'world');
$result = [new TextContent(\sprintf('Hello, %s!', $greetName))];
break;
case 'sum':
$a = (float) ($args['a'] ?? 0);
$b = (float) ($args['b'] ?? 0);
$result = [new TextContent((string) ($a + $b))];
break;
default:
$result = [new TextContent('Unknown tool')];
}

return new Response($request->getId(), new CallToolResult($result));
} catch (\Throwable $e) {
return new Response($request->getId(), new CallToolResult([new TextContent('Tool execution failed')], true));
}
}
}
46 changes: 46 additions & 0 deletions examples/custom-method-handlers/ListToolsRequestHandler.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?php

/*
* This file is part of the official PHP MCP SDK.
*
* A collaboration between Symfony and the PHP Foundation.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Mcp\Example\CustomMethodHandlers;

use Mcp\Schema\JsonRpc\Request;
use Mcp\Schema\JsonRpc\Response;
use Mcp\Schema\Request\ListToolsRequest;
use Mcp\Schema\Result\ListToolsResult;
use Mcp\Schema\Tool;
use Mcp\Server\Handler\Request\RequestHandlerInterface;
use Mcp\Server\Session\SessionInterface;

/** @implements RequestHandlerInterface<ListToolsResult> */
class ListToolsRequestHandler implements RequestHandlerInterface
{
/**
* @param array<string, Tool> $toolDefinitions
*/
public function __construct(private array $toolDefinitions)
{
}

public function supports(Request $request): bool
{
return $request instanceof ListToolsRequest;
}

/**
* @return Response<ListToolsResult>
*/
public function handle(Request $request, SessionInterface $session): Response
{
\assert($request instanceof ListToolsRequest);

return new Response($request->getId(), new ListToolsResult(array_values($this->toolDefinitions), null));
}
}
81 changes: 4 additions & 77 deletions examples/custom-method-handlers/server.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,11 @@
require_once dirname(__DIR__).'/bootstrap.php';
chdir(__DIR__);

use Mcp\Schema\Content\TextContent;
use Mcp\Schema\JsonRpc\Error;
use Mcp\Schema\JsonRpc\Request;
use Mcp\Schema\JsonRpc\Response;
use Mcp\Schema\Request\CallToolRequest;
use Mcp\Schema\Request\ListToolsRequest;
use Mcp\Schema\Result\CallToolResult;
use Mcp\Schema\Result\ListToolsResult;
use Mcp\Example\CustomMethodHandlers\CallToolRequestHandler;
use Mcp\Example\CustomMethodHandlers\ListToolsRequestHandler;
use Mcp\Schema\ServerCapabilities;
use Mcp\Schema\Tool;
use Mcp\Server;
use Mcp\Server\Handler\Request\RequestHandlerInterface;
use Mcp\Server\Session\SessionInterface;
use Mcp\Server\Transport\StdioTransport;

logger()->info('Starting MCP Custom Method Handlers (Stdio) Server...');
Expand Down Expand Up @@ -58,73 +50,8 @@
),
];

$listToolsHandler = new class($toolDefinitions) implements RequestHandlerInterface {
/**
* @param array<string, Tool> $toolDefinitions
*/
public function __construct(private array $toolDefinitions)
{
}

public function supports(Request $request): bool
{
return $request instanceof ListToolsRequest;
}

public function handle(Request $request, SessionInterface $session): Response
{
assert($request instanceof ListToolsRequest);

return new Response($request->getId(), new ListToolsResult(array_values($this->toolDefinitions), null));
}
};

$callToolHandler = new class($toolDefinitions) implements RequestHandlerInterface {
/**
* @param array<string, Tool> $toolDefinitions
*/
public function __construct(private array $toolDefinitions)
{
}

public function supports(Request $request): bool
{
return $request instanceof CallToolRequest;
}

public function handle(Request $request, SessionInterface $session): Response|Error
{
assert($request instanceof CallToolRequest);

$name = $request->name;
$args = $request->arguments ?? [];

if (!isset($this->toolDefinitions[$name])) {
return new Error($request->getId(), Error::METHOD_NOT_FOUND, sprintf('Tool not found: %s', $name));
}

try {
switch ($name) {
case 'say_hello':
$greetName = (string) ($args['name'] ?? 'world');
$result = [new TextContent(sprintf('Hello, %s!', $greetName))];
break;
case 'sum':
$a = (float) ($args['a'] ?? 0);
$b = (float) ($args['b'] ?? 0);
$result = [new TextContent((string) ($a + $b))];
break;
default:
$result = [new TextContent('Unknown tool')];
}

return new Response($request->getId(), new CallToolResult($result));
} catch (Throwable $e) {
return new Response($request->getId(), new CallToolResult([new TextContent('Tool execution failed')], true));
}
}
};

$listToolsHandler = new ListToolsRequestHandler($toolDefinitions);
$callToolHandler = new CallToolRequestHandler($toolDefinitions);
$capabilities = new ServerCapabilities(tools: true, resources: false, prompts: false);

$server = Server::builder()
Expand Down
124 changes: 124 additions & 0 deletions examples/http-client-communication/server.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
<?php

/*
* This file is part of the official PHP MCP SDK.
*
* A collaboration between Symfony and the PHP Foundation.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

require_once dirname(__DIR__).'/bootstrap.php';
chdir(__DIR__);

use Laminas\HttpHandlerRunner\Emitter\SapiEmitter;
use Mcp\Schema\Content\TextContent;
use Mcp\Schema\Enum\LoggingLevel;
use Mcp\Schema\JsonRpc\Error as JsonRpcError;
use Mcp\Schema\ServerCapabilities;
use Mcp\Server;
use Mcp\Server\ClientGateway;
use Mcp\Server\Session\FileSessionStore;
use Mcp\Server\Transport\StreamableHttpTransport;
use Nyholm\Psr7\Factory\Psr17Factory;
use Nyholm\Psr7Server\ServerRequestCreator;

$psr17Factory = new Psr17Factory();
$creator = new ServerRequestCreator($psr17Factory, $psr17Factory, $psr17Factory, $psr17Factory);
$request = $creator->fromGlobals();

$sessionDir = __DIR__.'/sessions';
$capabilities = new ServerCapabilities(logging: true, tools: true);

$server = Server::builder()
->setServerInfo('HTTP Client Communication Demo', '1.0.0')
->setLogger(logger())
->setContainer(container())
->setSession(new FileSessionStore($sessionDir))
->setCapabilities($capabilities)
->addTool(
function (string $projectName, array $milestones, ClientGateway $client): array {
$client->log(LoggingLevel::Info, sprintf('Preparing project briefing for "%s"', $projectName));

$totalSteps = max(1, count($milestones));

foreach ($milestones as $index => $milestone) {
$progress = ($index + 1) / $totalSteps;
$message = sprintf('Analyzing milestone "%s"', $milestone);

$client->progress(progress: $progress, total: 1, message: $message);

usleep(150_000); // Simulate work being done
}

$prompt = sprintf(
'Draft a concise stakeholder briefing for the project "%s". Highlight key milestones: %s. Focus on risks and next steps.',
$projectName,
implode(', ', $milestones)
);

$response = $client->sample(
prompt: $prompt,
maxTokens: 400,
timeout: 90,
options: ['temperature' => 0.4]
);

if ($response instanceof JsonRpcError) {
throw new RuntimeException(sprintf('Sampling request failed (%d): %s', $response->code, $response->message));
}

$result = $response->result;
$content = $result->content instanceof TextContent ? trim((string) $result->content->text) : '';

$client->log(LoggingLevel::Info, 'Briefing ready, returning to caller.');

return [
'project' => $projectName,
'milestones_reviewed' => $milestones,
'briefing' => $content,
'model' => $result->model,
'stop_reason' => $result->stopReason,
];
},
name: 'prepare_project_briefing',
description: 'Compile a stakeholder briefing with live logging, progress updates, and LLM sampling.'
)
->addTool(
function (string $serviceName, ClientGateway $client): array {
$client->log(LoggingLevel::Info, sprintf('Starting maintenance checks for "%s"', $serviceName));

$steps = [
'Verifying health metrics',
'Checking recent deployments',
'Reviewing alert stream',
'Summarizing findings',
];

foreach ($steps as $index => $step) {
$progress = ($index + 1) / count($steps);

$client->progress(progress: $progress, total: 1, message: $step);

usleep(120_000); // Simulate work being done
}

$client->log(LoggingLevel::Info, sprintf('Maintenance checks complete for "%s"', $serviceName));

return [
'service' => $serviceName,
'status' => 'operational',
'notes' => 'No critical issues detected during automated sweep.',
];
},
name: 'run_service_maintenance',
description: 'Simulate service maintenance with logging and progress notifications.'
)
->build();

$transport = new StreamableHttpTransport($request, $psr17Factory, $psr17Factory, logger());

$response = $server->run($transport);

(new SapiEmitter())->emit($response);
Loading