Skip to content

Commit b91987e

Browse files
committed
Kicking off the SDK based on Symfony's MCP SDK
1 parent 0044ffb commit b91987e

File tree

88 files changed

+3884
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

88 files changed

+3884
-0
lines changed

.gitattributes

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
/.git* export-ignore
2+
/examples export-ignore
3+
/tests export-ignore
4+
/.php-cs-fixer.dist.php export-ignore
5+
/phpstan.dist.neon export-ignore
6+
/phpunit.xml.dist export-ignore

.github/CODEOWNERS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
* @chr-hertel @Nyholm @CodeWithKyrian

.github/workflows/pipeline.yml

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
name: pipeline
2+
on: pull_request
3+
4+
permissions:
5+
contents: read
6+
pull-requests: write
7+
8+
jobs:
9+
tests:
10+
runs-on: ubuntu-latest
11+
strategy:
12+
matrix:
13+
php: ['8.2', '8.3', '8.4']
14+
dependencies: ['lowest', 'highest']
15+
steps:
16+
- name: Checkout
17+
uses: actions/checkout@v4
18+
19+
- name: Setup PHP
20+
uses: shivammathur/setup-php@v2
21+
with:
22+
php-version: ${{ matrix.php }}
23+
coverage: "none"
24+
25+
- name: Install Composer
26+
uses: "ramsey/composer-install@v3"
27+
with:
28+
dependency-versions: "${{ matrix.dependencies }}"
29+
30+
- name: Composer Validation
31+
run: composer validate --strict
32+
33+
- name: Install PHP Dependencies
34+
run: composer install --no-scripts
35+
36+
- name: Tests
37+
run: vendor/bin/phpunit
38+
39+
qa:
40+
runs-on: ubuntu-latest
41+
steps:
42+
- name: Checkout
43+
uses: actions/checkout@v4
44+
45+
- name: Setup PHP
46+
uses: shivammathur/setup-php@v2
47+
with:
48+
php-version: '8.2'
49+
coverage: "none"
50+
51+
- name: Install Composer
52+
uses: "ramsey/composer-install@v3"
53+
54+
- name: Composer Validation
55+
run: composer validate --strict
56+
57+
- name: Install PHP Dependencies
58+
run: composer install --no-scripts
59+
60+
- name: Code Style PHP
61+
run: vendor/bin/php-cs-fixer fix --dry-run
62+
63+
- name: PHPStan
64+
run: vendor/bin/phpstan analyse

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
.phpunit.cache
2+
.php-cs-fixer.cache
3+
composer.lock
4+
vendor

.php-cs-fixer.dist.php

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
if (!file_exists(__DIR__.'/src')) {
13+
exit(0);
14+
}
15+
16+
$fileHeaderParts = [
17+
<<<'EOF'
18+
This file is part of the official PHP MCP SDK.
19+
20+
A collaboration between Symfony and the PHP Foundation.
21+
22+
EOF,
23+
<<<'EOF'
24+
25+
For the full copyright and license information, please view the LICENSE
26+
file that was distributed with this source code.
27+
EOF,
28+
];
29+
30+
return (new PhpCsFixer\Config())
31+
// @see https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/pull/7777
32+
->setParallelConfig(PhpCsFixer\Runner\Parallel\ParallelConfigFactory::detect())
33+
->setRules([
34+
'@Symfony' => true,
35+
'@Symfony:risky' => true,
36+
'protected_to_private' => false,
37+
'declare_strict_types' => false,
38+
'header_comment' => [
39+
'header' => implode('', $fileHeaderParts),
40+
],
41+
'php_unit_test_case_static_method_calls' => ['call_type' => 'this'],
42+
])
43+
->setRiskyAllowed(true)
44+
->setFinder((new PhpCsFixer\Finder())->in(__DIR__))
45+
;

CHANGELOG.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
CHANGELOG
2+
=========
3+
4+
0.1
5+
---
6+
7+
* Add Model Context Protocol (MCP) implementation for LLM-application communication
8+
* Add JSON-RPC based protocol handling with `JsonRpcHandler`
9+
* Add three core MCP capabilities:
10+
- Resources: File-like data readable by clients (API responses, file contents)
11+
- Tools: Functions callable by LLMs (with user approval)
12+
- Prompts: Pre-written templates for specific tasks
13+
* Add multiple transport implementations:
14+
- Symfony Console Transport for testing and CLI applications
15+
- Stream Transport supporting Server-Sent Events (SSE) and HTTP streaming
16+
- STDIO transport for command-line interfaces
17+
* Add capability chains for organizing features:
18+
- `ToolChain` for tool management
19+
- `ResourceChain` for resource management
20+
- `PromptChain` for prompt template management
21+
* Add Server component managing transport connections
22+
* Add request/notification handlers for MCP operations
23+
* Add standardized interface enabling LLMs to interact with external systems
24+
* Add support for building LLM "plugins" with extra context capabilities

LICENSE

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
MIT License
2+
3+
Permission is hereby granted, free of charge, to any person obtaining a copy
4+
of this software and associated documentation files (the "Software"), to deal
5+
in the Software without restriction, including without limitation the rights
6+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7+
copies of the Software, and to permit persons to whom the Software is furnished
8+
to do so, subject to the following conditions:
9+
10+
The above copyright notice and this permission notice shall be included in all
11+
copies or substantial portions of the Software.
12+
13+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19+
THE SOFTWARE.

composer.json

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
{
2+
"name": "mcp/sdk",
3+
"type": "library",
4+
"description": "Model Context Protocol SDK for Client and Server applications in PHP",
5+
"license": "MIT",
6+
"authors": [
7+
{
8+
"name": "Christopher Hertel",
9+
"email": "[email protected]"
10+
},
11+
{
12+
"name": "Tobias Nyholm",
13+
"email": "[email protected]"
14+
},
15+
{
16+
"name": "Kyrian Obikwelu",
17+
"email": "[email protected]"
18+
}
19+
],
20+
"require": {
21+
"php": "^8.2",
22+
"psr/log": "^1.0 || ^2.0 || ^3.0",
23+
"symfony/uid": "^6.4 || ^7.0"
24+
},
25+
"require-dev": {
26+
"phpstan/phpstan": "^2.1",
27+
"phpunit/phpunit": "^11.5",
28+
"symfony/console": "^6.4 || ^7.0",
29+
"psr/cache": "^3.0",
30+
"php-cs-fixer/shim": "^3.84"
31+
},
32+
"suggest": {
33+
"symfony/console": "To use SymfonyConsoleTransport for STDIO",
34+
"psr/cache": "To use CachePoolStore with SSE Transport"
35+
},
36+
"autoload": {
37+
"psr-4": {
38+
"Mcp\\": "src/"
39+
}
40+
},
41+
"autoload-dev": {
42+
"psr-4": {
43+
"Mcp\\Tests\\": "tests/"
44+
}
45+
}
46+
}

doc/index.rst

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
Model Context Protocol SDK
2+
==========================
3+
4+
The PHP MCP SDK is the low level library that enables communication between a PHP application and an LLM model.
5+
6+
Installation
7+
------------
8+
9+
Install the SDK using Composer:
10+
11+
.. code-block:: terminal
12+
13+
$ composer require mcp/sdk
14+
15+
Usage
16+
-----
17+
18+
The `Model Context Protocol`_ is built on top of JSON-RPC. There two types of
19+
messages. A Notification and Request. The Notification is just a status update
20+
that something has happened. There is never a response to a Notification. A Request
21+
is a message that expects a response. There are 3 concepts/capabilities that you
22+
may use. These are::
23+
24+
1. **Resources**: File-like data that can be read by clients (like API responses or file contents)
25+
1. **Tools**: Functions that can be called by the LLM (with user approval)
26+
1. **Prompts**: Pre-written templates that help users accomplish specific tasks
27+
28+
The SDK comes with NotificationHandlers and RequestHandlers which are expected
29+
to be wired up in your application.
30+
31+
JsonRpcHandler
32+
..............
33+
34+
The ``Mcp\Server\JsonRpcHandler`` is the heart of the SDK. It is here
35+
you inject the NotificationHandlers and RequestHandlers. It is recommended to use
36+
the built-in handlers in ``Mcp\Server\NotificationHandlers\*`` and
37+
``Mcp\Server\RequestHandlers\*``.
38+
39+
The ``Mcp\Server\JsonRpcHandler`` is started and kept running by
40+
the ``Mcp\Server``
41+
42+
Transports
43+
..........
44+
45+
The SDK supports multiple transports for sending and receiving messages. The
46+
``Mcp\Server`` is using the transport to fetch a message, then
47+
give it to the ``Mcp\Server\JsonRpcHandler`` and finally send the
48+
response/error back to the transport. The SDK comes with a few transports::
49+
50+
1. **Symfony Console Transport**: Good for testing and for CLI applications
51+
1. **Stream Transport**: It uses Server Side Events (SSE) and HTTP streaming
52+
53+
Capabilities
54+
............
55+
56+
Any client would like to discover the capabilities of the server. Exactly what
57+
the server supports is defined in the ``Mcp\Server\RequestHandler\InitializeHandler``.
58+
When the client connects, it sees the capabilities and will ask the server to list
59+
the tools/resource/prompts etc. When you want to add a new capability, example a
60+
**Tool** that can tell the current time, you need to provide some metadata to the
61+
``Mcp\Server\RequestHandler\ToolListHandler``::
62+
63+
namespace App;
64+
65+
use Mcp\Capability\Tool\MetadataInterface;
66+
67+
class CurrentTimeToolMetadata implements MetadataInterface
68+
{
69+
public function getName(): string
70+
{
71+
return 'Current time';
72+
}
73+
74+
public function getDescription(): string
75+
{
76+
return 'Returns the current time in UTC';
77+
}
78+
79+
public function getInputSchema(): array
80+
{
81+
return [
82+
'type' => 'object',
83+
'properties' => [
84+
'format' => [
85+
'type' => 'string',
86+
'description' => 'The format of the time, e.g. "Y-m-d H:i:s"',
87+
'default' => 'Y-m-d H:i:s',
88+
],
89+
],
90+
'required' => [],
91+
];
92+
}
93+
}
94+
95+
We would also need a class to actually execute the tool::
96+
97+
namespace App;
98+
99+
use Mcp\Capability\Tool\IdentifierInterface;
100+
use Mcp\Capability\Tool\ToolCall;
101+
use Mcp\Capability\Tool\ToolCallResult;
102+
use Mcp\Capability\Tool\ToolExecutorInterface;
103+
104+
class CurrentTimeToolExecutor implements ToolExecutorInterface, IdentifierInterface
105+
{
106+
public function getName(): string
107+
{
108+
return 'Current time';
109+
}
110+
111+
public function call(ToolCall $input): ToolCallResult
112+
{
113+
$format = $input->arguments['format'] ?? 'Y-m-d H:i:s';
114+
115+
return new ToolCallResult(
116+
(new \DateTime('now', new \DateTimeZone('UTC')))->format($format)
117+
);
118+
}
119+
}
120+
121+
If you have multiple tools, you can put them in a ToolChain::
122+
123+
$tools = new ToolChain([
124+
new CurrentTimeToolMetadata(),
125+
new CurrentTimeToolExecutor(),
126+
]);
127+
128+
$jsonRpcHandler = new Mcp\Server\JsonRpcHandler(
129+
new Mcp\Message\Factory(),
130+
[
131+
new ToolCallHandler($tools),
132+
new ToolListHandler($tools),
133+
// Other RequestHandlers ...
134+
],
135+
[
136+
// Other NotificationHandlers ...
137+
],
138+
new NullLogger()
139+
);
140+
141+
With this metadata and executor, the client can now call the tool.
142+
143+
Extending the SDK
144+
-----------------
145+
146+
If you want to extend the SDK, you can create your own RequestHandlers and NotificationHandlers.
147+
The provided one are very good defaults for most applications but they are not
148+
a requirement.
149+
150+
If you do decide to use them, you get the benefit of having a well-defined interfaces
151+
and value objects to work with. They will assure that you follow the `Model Context Protocol`_.
152+
specification.
153+
154+
You also have the Transport abstraction that allows you to create your own transport
155+
if non of the standard ones fit your needs.
156+
157+
.. _`Model Context Protocol`: https://modelcontextprotocol.io/

0 commit comments

Comments
 (0)