add readme and license headers

This commit is contained in:
Tobias Nyholm
2025-05-29 13:40:32 +02:00
parent 1741c859b7
commit da16b7cd4d
72 changed files with 881 additions and 136 deletions

96
.doctor-rst.yaml Normal file
View File

@@ -0,0 +1,96 @@
rules:
american_english: ~
argument_variable_must_match_type: ~
avoid_repetetive_words: ~
# be_kind_to_newcomers: ~
blank_line_after_anchor: ~
# blank_line_after_colon: ~
blank_line_after_directive: ~
# blank_line_after_filepath_in_code_block: ~
# blank_line_after_filepath_in_php_code_block: ~
# blank_line_after_filepath_in_twig_code_block: ~
# blank_line_after_filepath_in_xml_code_block: ~
# blank_line_after_filepath_in_yaml_code_block: ~
blank_line_before_directive: ~
# composer_dev_option_at_the_end: ~
composer_dev_option_not_at_the_end: ~
correct_code_block_directive_based_on_the_content: ~
deprecated_directive_major_version:
major_version: 2
deprecated_directive_min_version:
min_version: '2.0'
deprecated_directive_should_have_version: ~
ensure_bash_prompt_before_composer_command: ~
ensure_exactly_one_space_before_directive_type: ~
ensure_exactly_one_space_between_link_definition_and_link: ~
ensure_link_definition_contains_valid_url: ~
ensure_order_of_code_blocks_in_configuration_block: ~
extend_abstract_admin: ~
extend_abstract_controller: ~
# extend_controller: ~
extension_xlf_instead_of_xliff: ~
# filename_uses_dashes_only: ~
# filename_uses_underscores_only: ~
final_admin_classes: ~
final_admin_extension_classes: ~
forbidden_directives:
directives:
- '.. index::'
indention: ~
kernel_instead_of_app_kernel: ~
# line_length: ~
lowercase_as_in_use_statements: ~
max_blank_lines:
max: 2
max_colons: ~
no_admin_yaml: ~
no_app_bundle: ~
no_app_console: ~
# no_bash_prompt: ~
no_blank_line_after_filepath_in_code_block: ~
no_blank_line_after_filepath_in_php_code_block: ~
no_blank_line_after_filepath_in_twig_code_block: ~
no_blank_line_after_filepath_in_xml_code_block: ~
no_blank_line_after_filepath_in_yaml_code_block: ~
no_brackets_in_method_directive: ~
no_composer_phar: ~
no_composer_req: ~
no_config_yaml: ~
# no_contraction: ~
no_directive_after_shorthand: ~
no_explicit_use_of_code_block_php: ~
no_inheritdoc_in_code_examples: ~
no_merge_conflict: ~
no_namespace_after_use_statements: ~
no_php_open_tag_in_code_block_php_directive: ~
# no_php_prefix_before_bin_console: ~
# no_php_prefix_before_composer: ~
# no_space_before_self_xml_closing_tag: ~
only_backslashes_in_namespace_in_php_code_block: ~
only_backslashes_in_use_statements_in_php_code_block: ~
ordered_use_statements: ~
# php_open_tag_in_code_block_php_directive: ~
php_prefix_before_bin_console: ~
replace_code_block_types: ~
replacement: ~
short_array_syntax: ~
# space_before_self_xml_closing_tag: ~
space_between_label_and_link_in_doc: ~
space_between_label_and_link_in_ref: ~
string_replacement: ~
title_underline_length_must_match_title_length: ~
typo: ~
unused_links: ~
use_deprecated_directive_instead_of_versionadded: ~
use_https_xsd_urls: ~
# use_named_constructor_without_new_keyword_rule: ~
valid_inline_highlighted_namespaces: ~
valid_use_statements: ~
versionadded_directive_major_version:
major_version: 2
versionadded_directive_min_version:
min_version: '2.0'
versionadded_directive_should_have_version: ~
yaml_instead_of_yml_suffix: ~
yarn_dev_option_at_the_end: ~
# yarn_dev_option_not_at_the_end: ~

View File

@@ -1,13 +1,54 @@
<?php
$finder = (new PhpCsFixer\Finder())
->in('src')
->exclude(['var', 'vendor'])
;
declare(strict_types=1);
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
if (!file_exists(__DIR__.'/src')) {
exit(0);
}
$fileHeaderParts = [
<<<'EOF'
This file is part of the Symfony package.
(c) Fabien Potencier <fabien@symfony.com>
EOF,
<<<'EOF'
For the full copyright and license information, please view the LICENSE
file that was distributed with this source code.
EOF,
];
return (new PhpCsFixer\Config())
// @see https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/pull/7777
->setParallelConfig(PhpCsFixer\Runner\Parallel\ParallelConfigFactory::detect())
->setRules([
'@PHP71Migration' => true,
'@PHPUnit75Migration:risky' => true,
'@Symfony' => true,
'@Symfony:risky' => true,
'protected_to_private' => false,
'declare_strict_types' => true,
'header_comment' => [
'header' => implode('', $fileHeaderParts),
],
])
->setFinder($finder)
->setRiskyAllowed(true)
->setFinder(
(new PhpCsFixer\Finder())
->in(__DIR__.'/src')
->append([__FILE__])
->notPath('#/Fixtures/#')
)
->setCacheFile('.php-cs-fixer.cache')
;

26
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,26 @@
Contributing
------------
Symfony is an open source, community-driven project.
If you'd like to contribute, please read the following documents:
* [Reviewing issues/pull requests][0]
* [Reporting a Bug][1]
* [Submitting a Patch][2]
* [Symfony Core Team][3]
* [Security Issues][4]
* [Running Symfony Tests][5]
* [Our Backwards Compatibility Promise][6]
* [Coding Standards][7]
* [Conventions][8]
[0]: https://symfony.com/doc/current/contributing/community/reviews.html
[1]: https://symfony.com/doc/current/contributing/code/bugs.html
[2]: https://symfony.com/doc/current/contributing/code/patches.html
[3]: https://symfony.com/doc/current/contributing/code/core_team.html
[4]: https://symfony.com/doc/current/contributing/code/security.html
[5]: https://symfony.com/doc/current/contributing/code/tests.html
[6]: https://symfony.com/doc/current/contributing/code/bc.html
[7]: https://symfony.com/doc/current/contributing/code/standards.html
[8]: https://symfony.com/doc/current/contributing/code/conventions.html

20
README.md Normal file
View File

@@ -0,0 +1,20 @@
<p align="center"><a href="https://symfony.com" target="_blank">
<img src="https://symfony.com/logos/symfony_dynamic_01.svg" alt="Symfony Logo">
</a></p>
<h3 align="center">
Symfony AI
</h3>
Symfony AI is a set of packages that integrate AI capabilities into PHP applications.
## Sponsor
Help Symfony by [sponsoring][2] its development!
## Contributing
Thank you for considering contributing to Symfony AI! You can find the [contribution guide here](CONTRIBUTING.md).
[1]: https://symfony.com/backers
[2]: https://symfony.com/sponsor

View File

@@ -1,119 +1,26 @@
# Model Context Protocol PHP SDK [WIP]
# Model Context Protocol PHP SDK
Model Context Protocol SDK for Client and Server applications in PHP.
**Currently only support Tool Calls as Server via Server-Sent Events (SSE) and STDIO.**
See [Demo App](https://github.com/php-llm/mcp-demo) for a working example and [MCP Bundle](https://github.com/php-llm/mcp-bundle) for Symfony integration.
## Installation
```bash
composer require php-llm/mcp-sdk
composer require symfony/mcp-sdk
```
## Usage with Symfony
This is a low level SDK that implements the [Model Context Protocol](https://modelcontextprotocol.io/)
(MCP). The protocol is used by LLM model to build "plugins" and give them extra
context. Example the logged in users' latest order.
Server integration points for are tailored to Symfony Console and HttpFoundation (Laravel compatible).
**This repository is a READ-ONLY sub-tree split**. See
https://github.com/symfony/ai to create issues or submit pull requests.
### Console Command for STDIO Server
## Resources
```php
namespace App\Command;
- [Documentation](doc/index.rst)
- [Report issues](https://github.com/symfony/ai/issues) and
[send Pull Requests](https://github.com/symfony/ai/pulls)
in the [main Symfony AI repository](https://github.com/symfony/ai)
use PhpLlm\McpSdk\Server;
use PhpLlm\McpSdk\Server\Transport\Stdio\SymfonyConsoleTransport;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
#[AsCommand('mcp', 'Starts an MCP server')]
final class McpCommand extends Command
{
public function __construct(
private readonly Server $server,
) {
parent::__construct();
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$this->server->connect(
new SymfonyConsoleTransport($input, $output)
);
return Command::SUCCESS;
}
}
```
### Controller for Server-Sent Events Server
```php
namespace App\Controller;
use PhpLlm\McpSdk\Server;
use PhpLlm\McpSdk\Server\Transport\Sse\Store\CachePoolStore;
use PhpLlm\McpSdk\Server\Transport\Sse\StreamTransport;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\StreamedResponse;
use Symfony\Component\HttpKernel\Attribute\AsController;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Uid\Uuid;
#[AsController]
#[Route('/mcp', name: 'mcp_')]
final readonly class McpController
{
public function __construct(
private Server $server,
private CachePoolStore $store,
private UrlGeneratorInterface $urlGenerator,
) {
}
#[Route('/sse', name: 'sse', methods: ['GET'])]
public function sse(): StreamedResponse
{
$id = Uuid::v4();
$endpoint = $this->urlGenerator->generate('mcp_messages', ['id' => $id], UrlGeneratorInterface::ABSOLUTE_URL);
$transport = new StreamTransport($endpoint, $this->store, $id);
return new StreamedResponse(fn() => $this->server->connect($transport), headers: [
'Content-Type' => 'text/event-stream',
'Cache-Control' => 'no-cache',
'X-Accel-Buffering' => 'no',
]);
}
#[Route('/messages/{id}', name: 'messages', methods: ['POST'])]
public function messages(Request $request, Uuid $id): Response
{
$this->store->push($id, $request->getContent());
return new Response();
}
}
```
### Exposing Tools
Under the hood the SDK uses [LLM Chain](https://github.com/php-llm/llm-chain)'s `ToolBox` to register, analyze and
execute tools. In combination with its [Symfony Bundle](https://github.com/php-llm/llm-chain-bundle) you can expose
tools with `#[AsTool]` attribute.
```php
use PhpLlm\LlmChain\ToolBox\Attribute\AsTool;
#[AsTool('company_name', 'Provides the name of your company')]
final class CompanyName
{
public function __invoke(): string
{
return 'ACME Corp.'
}
}
```
See [LLM Chain Documentation](https://github.com/php-llm/llm-chain?tab=readme-ov-file#tools) for more information.
[1]: https://symfony.com/backers
[3]: https://symfony.com/sponsor

View File

@@ -1,5 +1,5 @@
{
"name": "php-llm/mcp-sdk",
"name": "symfony/mcp-sdk",
"type": "library",
"description": "Model Context Protocol SDK for Client and Server applications in PHP",
"license": "MIT",

32
src/mcp-sdk/doc/index.rst Normal file
View File

@@ -0,0 +1,32 @@
Model Context Protocol SDK
==========================
Symfony AI MCP SDK is the low level library that enables communication between
a PHP application and an LLM model.
Installation
------------
Install the bundle using Composer:
.. code-block:: terminal
$ composer require symfony/mcp-sdk
Usage
-----
The `Model Context Protocol`_ is built on top of JSON-RPC. There two types of
messages. A Notification and Request. The Notification is just a status update
that something has happened. There is never a response to a Notification. A Request
is a message that expects a response. There are 3 concepts that you may request.
These are::
1. **Resources**: File-like data that can be read by clients (like API responses or file contents)
1. **Tools**: Functions that can be called by the LLM (with user approval)
1. **Prompts**: Pre-written templates that help users accomplish specific tasks
The SDK comes with NotificationHandlers and RequestHandlers which are expected
to be wired up in your application.
.. _`Model Context Protocol`: https://modelcontextprotocol.io/

View File

@@ -1,5 +1,16 @@
<?php
declare(strict_types=1);
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
require __DIR__.'/vendor/autoload.php';
use Symfony\Component\Console as SymfonyConsole;

View File

@@ -1,5 +1,16 @@
<?php
declare(strict_types=1);
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace App;
use PhpLlm\McpSdk\Capability\PromptChain;

View File

@@ -1,5 +1,16 @@
<?php
declare(strict_types=1);
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace App;
use PhpLlm\McpSdk\Capability\Prompt\MetadataInterface;
@@ -18,7 +29,7 @@ class ExamplePrompt implements MetadataInterface, PromptGetterInterface
$this->getDescription(),
[new PromptGetResultMessages(
'user',
sprintf('Hello %s', $firstName ?? 'World')
\sprintf('Hello %s', $firstName ?? 'World')
)]
);
}

View File

@@ -1,5 +1,16 @@
<?php
declare(strict_types=1);
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace App;
use PhpLlm\McpSdk\Capability\Resource\MetadataInterface;

View File

@@ -1,5 +1,16 @@
<?php
declare(strict_types=1);
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace App;
use PhpLlm\McpSdk\Capability\Tool\MetadataInterface;

View File

@@ -1,5 +1,16 @@
<?php
declare(strict_types=1);
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PhpLlm\McpSdk\Capability\Prompt;
interface CollectionInterface

View File

@@ -1,5 +1,16 @@
<?php
declare(strict_types=1);
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PhpLlm\McpSdk\Capability\Prompt;
interface IdentifierInterface

View File

@@ -2,6 +2,15 @@
declare(strict_types=1);
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PhpLlm\McpSdk\Capability\Prompt;
interface MetadataInterface extends IdentifierInterface

View File

@@ -2,6 +2,15 @@
declare(strict_types=1);
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PhpLlm\McpSdk\Capability\Prompt;
final readonly class PromptGet

View File

@@ -2,6 +2,15 @@
declare(strict_types=1);
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PhpLlm\McpSdk\Capability\Prompt;
final readonly class PromptGetResult

View File

@@ -2,6 +2,15 @@
declare(strict_types=1);
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PhpLlm\McpSdk\Capability\Prompt;
final readonly class PromptGetResultMessages

View File

@@ -1,5 +1,16 @@
<?php
declare(strict_types=1);
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PhpLlm\McpSdk\Capability\Prompt;
use PhpLlm\McpSdk\Exception\PromptGetException;

View File

@@ -1,5 +1,16 @@
<?php
declare(strict_types=1);
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PhpLlm\McpSdk\Capability;
use PhpLlm\McpSdk\Capability\Prompt\CollectionInterface;

View File

@@ -1,5 +1,16 @@
<?php
declare(strict_types=1);
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PhpLlm\McpSdk\Capability\Resource;
interface CollectionInterface

View File

@@ -1,5 +1,16 @@
<?php
declare(strict_types=1);
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PhpLlm\McpSdk\Capability\Resource;
interface IdentifierInterface

View File

@@ -2,6 +2,15 @@
declare(strict_types=1);
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PhpLlm\McpSdk\Capability\Resource;
interface MetadataInterface extends IdentifierInterface

View File

@@ -2,6 +2,15 @@
declare(strict_types=1);
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PhpLlm\McpSdk\Capability\Resource;
final readonly class ResourceRead

View File

@@ -2,6 +2,15 @@
declare(strict_types=1);
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PhpLlm\McpSdk\Capability\Resource;
final readonly class ResourceReadResult

View File

@@ -1,5 +1,16 @@
<?php
declare(strict_types=1);
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PhpLlm\McpSdk\Capability\Resource;
use PhpLlm\McpSdk\Exception\ResourceNotFoundException;

View File

@@ -1,5 +1,16 @@
<?php
declare(strict_types=1);
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PhpLlm\McpSdk\Capability;
use PhpLlm\McpSdk\Capability\Resource\CollectionInterface;

View File

@@ -1,5 +1,16 @@
<?php
declare(strict_types=1);
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PhpLlm\McpSdk\Capability\Tool;
interface CollectionInterface

View File

@@ -1,5 +1,16 @@
<?php
declare(strict_types=1);
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PhpLlm\McpSdk\Capability\Tool;
interface IdentifierInterface

View File

@@ -2,6 +2,15 @@
declare(strict_types=1);
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PhpLlm\McpSdk\Capability\Tool;
interface MetadataInterface extends IdentifierInterface

View File

@@ -2,6 +2,15 @@
declare(strict_types=1);
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PhpLlm\McpSdk\Capability\Tool;
final readonly class ToolCall

View File

@@ -2,6 +2,15 @@
declare(strict_types=1);
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PhpLlm\McpSdk\Capability\Tool;
final readonly class ToolCallResult

View File

@@ -1,5 +1,16 @@
<?php
declare(strict_types=1);
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PhpLlm\McpSdk\Capability\Tool;
interface ToolCollectionInterface

View File

@@ -1,5 +1,16 @@
<?php
declare(strict_types=1);
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PhpLlm\McpSdk\Capability\Tool;
use PhpLlm\McpSdk\Exception\ToolExecutionException;

View File

@@ -1,5 +1,16 @@
<?php
declare(strict_types=1);
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PhpLlm\McpSdk\Capability;
use PhpLlm\McpSdk\Capability\Tool\CollectionInterface;

View File

@@ -2,6 +2,15 @@
declare(strict_types=1);
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PhpLlm\McpSdk\Exception;
interface ExceptionInterface

View File

@@ -2,6 +2,15 @@
declare(strict_types=1);
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PhpLlm\McpSdk\Exception;
interface NotFoundExceptionInterface extends ExceptionInterface

View File

@@ -2,6 +2,15 @@
declare(strict_types=1);
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PhpLlm\McpSdk\Exception;
use PhpLlm\McpSdk\Capability\Prompt\PromptGet;
@@ -12,6 +21,6 @@ final class PromptGetException extends \RuntimeException implements ExceptionInt
public readonly PromptGet $promptGet,
?\Throwable $previous = null,
) {
parent::__construct(sprintf('Handling prompt "%s" failed with error: %s', $promptGet->name, $previous->getMessage()), previous: $previous);
parent::__construct(\sprintf('Handling prompt "%s" failed with error: %s', $promptGet->name, $previous->getMessage()), previous: $previous);
}
}

View File

@@ -2,6 +2,15 @@
declare(strict_types=1);
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PhpLlm\McpSdk\Exception;
use PhpLlm\McpSdk\Capability\Prompt\PromptGet;
@@ -11,6 +20,6 @@ final class PromptNotFoundException extends \RuntimeException implements NotFoun
public function __construct(
public readonly PromptGet $promptGet,
) {
parent::__construct(sprintf('Resource not found for uri: "%s"', $promptGet->name));
parent::__construct(\sprintf('Resource not found for uri: "%s"', $promptGet->name));
}
}

View File

@@ -2,6 +2,15 @@
declare(strict_types=1);
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PhpLlm\McpSdk\Exception;
use PhpLlm\McpSdk\Capability\Resource\ResourceRead;
@@ -11,6 +20,6 @@ final class ResourceNotFoundException extends \RuntimeException implements NotFo
public function __construct(
public readonly ResourceRead $readRequest,
) {
parent::__construct(sprintf('Resource not found for uri: "%s"', $readRequest->uri));
parent::__construct(\sprintf('Resource not found for uri: "%s"', $readRequest->uri));
}
}

View File

@@ -2,6 +2,15 @@
declare(strict_types=1);
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PhpLlm\McpSdk\Exception;
use PhpLlm\McpSdk\Capability\Resource\ResourceRead;
@@ -12,6 +21,6 @@ final class ResourceReadException extends \RuntimeException implements Exception
public readonly ResourceRead $readRequest,
?\Throwable $previous = null,
) {
parent::__construct(sprintf('Reading resource "%s" failed with error: %s', $readRequest->uri, $previous?->getMessage() ?? ''), previous: $previous);
parent::__construct(\sprintf('Reading resource "%s" failed with error: %s', $readRequest->uri, $previous?->getMessage() ?? ''), previous: $previous);
}
}

View File

@@ -2,6 +2,15 @@
declare(strict_types=1);
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PhpLlm\McpSdk\Exception;
use PhpLlm\McpSdk\Capability\Tool\ToolCall;
@@ -12,6 +21,6 @@ final class ToolExecutionException extends \RuntimeException implements Exceptio
public readonly ToolCall $toolCall,
?\Throwable $previous = null,
) {
parent::__construct(sprintf('Execution of tool "%s" failed with error: %s', $toolCall->name, $previous?->getMessage() ?? ''), previous: $previous);
parent::__construct(\sprintf('Execution of tool "%s" failed with error: %s', $toolCall->name, $previous?->getMessage() ?? ''), previous: $previous);
}
}

View File

@@ -2,6 +2,15 @@
declare(strict_types=1);
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PhpLlm\McpSdk\Exception;
use PhpLlm\McpSdk\Capability\Tool\ToolCall;
@@ -11,6 +20,6 @@ final class ToolNotFoundException extends \RuntimeException implements NotFoundE
public function __construct(
public readonly ToolCall $toolCall,
) {
parent::__construct(sprintf('Tool not found for call: "%s"', $toolCall->name));
parent::__construct(\sprintf('Tool not found for call: "%s"', $toolCall->name));
}
}

View File

@@ -2,6 +2,15 @@
declare(strict_types=1);
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PhpLlm\McpSdk\Message;
final readonly class Error implements \JsonSerializable

View File

@@ -2,13 +2,22 @@
declare(strict_types=1);
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PhpLlm\McpSdk\Message;
final class Factory
{
public function create(string $json): Request|Notification
{
$data = json_decode($json, true, flags: JSON_THROW_ON_ERROR);
$data = json_decode($json, true, flags: \JSON_THROW_ON_ERROR);
if (!isset($data['method'])) {
throw new \InvalidArgumentException('Invalid JSON-RPC request, missing method');

View File

@@ -2,6 +2,15 @@
declare(strict_types=1);
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PhpLlm\McpSdk\Message;
final readonly class Notification implements \JsonSerializable, \Stringable
@@ -40,6 +49,6 @@ final readonly class Notification implements \JsonSerializable, \Stringable
public function __toString(): string
{
return sprintf('%s', $this->method);
return \sprintf('%s', $this->method);
}
}

View File

@@ -2,6 +2,15 @@
declare(strict_types=1);
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PhpLlm\McpSdk\Message;
final class Request implements \JsonSerializable, \Stringable
@@ -43,6 +52,6 @@ final class Request implements \JsonSerializable, \Stringable
public function __toString(): string
{
return sprintf('%s: %s', $this->id, $this->method);
return \sprintf('%s: %s', $this->id, $this->method);
}
}

View File

@@ -2,6 +2,15 @@
declare(strict_types=1);
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PhpLlm\McpSdk\Message;
final readonly class Response implements \JsonSerializable

View File

@@ -2,6 +2,15 @@
declare(strict_types=1);
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PhpLlm\McpSdk;
use PhpLlm\McpSdk\Server\JsonRpcHandler;

View File

@@ -2,6 +2,15 @@
declare(strict_types=1);
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PhpLlm\McpSdk\Server;
use PhpLlm\McpSdk\Message\Error;
@@ -68,7 +77,7 @@ readonly class JsonRpcHandler
} catch (\DomainException) {
return null;
} catch (\InvalidArgumentException $e) {
$this->logger->warning(sprintf('Failed to create response: %s', $e->getMessage()), ['exception' => $e]);
$this->logger->warning(\sprintf('Failed to create response: %s', $e->getMessage()), ['exception' => $e]);
return $this->encodeResponse(Error::methodNotFound($message->id ?? 0, $e->getMessage()));
}
@@ -85,10 +94,10 @@ readonly class JsonRpcHandler
$this->logger->info('Encoding response', ['response' => $response]);
if ($response instanceof Response && [] === $response->result) {
return json_encode($response, JSON_THROW_ON_ERROR | JSON_FORCE_OBJECT);
return json_encode($response, \JSON_THROW_ON_ERROR | \JSON_FORCE_OBJECT);
}
return json_encode($response, JSON_THROW_ON_ERROR);
return json_encode($response, \JSON_THROW_ON_ERROR);
}
private function handleNotification(Notification $notification): null
@@ -99,7 +108,7 @@ readonly class JsonRpcHandler
}
}
$this->logger->warning(sprintf('No handler found for "%s".', $notification->method), ['notification' => $notification]);
$this->logger->warning(\sprintf('No handler found for "%s".', $notification->method), ['notification' => $notification]);
return null;
}
@@ -112,6 +121,6 @@ readonly class JsonRpcHandler
}
}
throw new \InvalidArgumentException(sprintf('No handler found for method "%s".', $request->method));
throw new \InvalidArgumentException(\sprintf('No handler found for method "%s".', $request->method));
}
}

View File

@@ -2,6 +2,15 @@
declare(strict_types=1);
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PhpLlm\McpSdk\Server\NotificationHandler;
use PhpLlm\McpSdk\Message\Notification;
@@ -11,7 +20,7 @@ abstract class BaseNotificationHandler implements NotificationHandlerInterface
{
public function supports(Notification $message): bool
{
return $message->method === sprintf('notifications/%s', $this->supportedNotification());
return $message->method === \sprintf('notifications/%s', $this->supportedNotification());
}
abstract protected function supportedNotification(): string;

View File

@@ -2,6 +2,15 @@
declare(strict_types=1);
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PhpLlm\McpSdk\Server\NotificationHandler;
use PhpLlm\McpSdk\Message\Notification;

View File

@@ -2,6 +2,15 @@
declare(strict_types=1);
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PhpLlm\McpSdk\Server;
use PhpLlm\McpSdk\Message\Notification;

View File

@@ -2,6 +2,15 @@
declare(strict_types=1);
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PhpLlm\McpSdk\Server\RequestHandler;
use PhpLlm\McpSdk\Message\Request;

View File

@@ -2,6 +2,15 @@
declare(strict_types=1);
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PhpLlm\McpSdk\Server\RequestHandler;
use PhpLlm\McpSdk\Message\Request;

View File

@@ -2,6 +2,15 @@
declare(strict_types=1);
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PhpLlm\McpSdk\Server\RequestHandler;
use PhpLlm\McpSdk\Message\Request;

View File

@@ -2,6 +2,15 @@
declare(strict_types=1);
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PhpLlm\McpSdk\Server\RequestHandler;
use PhpLlm\McpSdk\Capability\Prompt\PromptGet;

View File

@@ -2,6 +2,15 @@
declare(strict_types=1);
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PhpLlm\McpSdk\Server\RequestHandler;
use PhpLlm\McpSdk\Capability\Prompt\CollectionInterface;

View File

@@ -2,6 +2,15 @@
declare(strict_types=1);
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PhpLlm\McpSdk\Server\RequestHandler;
use PhpLlm\McpSdk\Capability\Resource\CollectionInterface;

View File

@@ -2,6 +2,15 @@
declare(strict_types=1);
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PhpLlm\McpSdk\Server\RequestHandler;
use PhpLlm\McpSdk\Capability\Resource\ResourceRead;

View File

@@ -2,6 +2,15 @@
declare(strict_types=1);
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PhpLlm\McpSdk\Server\RequestHandler;
use PhpLlm\McpSdk\Capability\Tool\ToolCall;

View File

@@ -2,6 +2,15 @@
declare(strict_types=1);
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PhpLlm\McpSdk\Server\RequestHandler;
use PhpLlm\McpSdk\Capability\Tool\CollectionInterface;

View File

@@ -2,6 +2,15 @@
declare(strict_types=1);
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PhpLlm\McpSdk\Server;
use PhpLlm\McpSdk\Message\Error;

View File

@@ -2,6 +2,15 @@
declare(strict_types=1);
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PhpLlm\McpSdk\Server\Transport\Sse\Store;
use PhpLlm\McpSdk\Server\Transport\Sse\StoreInterface;

View File

@@ -2,6 +2,15 @@
declare(strict_types=1);
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PhpLlm\McpSdk\Server\Transport\Sse;
use Symfony\Component\Uid\Uuid;

View File

@@ -2,6 +2,15 @@
declare(strict_types=1);
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PhpLlm\McpSdk\Server\Transport\Sse;
use PhpLlm\McpSdk\Server\TransportInterface;
@@ -44,9 +53,9 @@ final readonly class StreamTransport implements TransportInterface
private function flushEvent(string $event, string $data): void
{
echo sprintf('event: %s', $event).PHP_EOL;
echo sprintf('data: %s', $data).PHP_EOL;
echo PHP_EOL;
echo \sprintf('event: %s', $event).\PHP_EOL;
echo \sprintf('data: %s', $data).\PHP_EOL;
echo \PHP_EOL;
if (false !== ob_get_length()) {
ob_flush();
}

View File

@@ -2,6 +2,15 @@
declare(strict_types=1);
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PhpLlm\McpSdk\Server\Transport\Stdio;
use PhpLlm\McpSdk\Server\TransportInterface;
@@ -33,14 +42,14 @@ final class SymfonyConsoleTransport implements TransportInterface
public function receive(): \Generator
{
$stream = $this->input instanceof StreamableInputInterface ? $this->input->getStream() ?? STDIN : STDIN;
$stream = $this->input instanceof StreamableInputInterface ? $this->input->getStream() ?? \STDIN : \STDIN;
$line = fgets($stream);
if (false === $line) {
return;
}
$this->buffer .= STDIN === $stream ? rtrim($line).PHP_EOL : $line;
if (str_contains($this->buffer, PHP_EOL)) {
$lines = explode(PHP_EOL, $this->buffer);
$this->buffer .= \STDIN === $stream ? rtrim($line).\PHP_EOL : $line;
if (str_contains($this->buffer, \PHP_EOL)) {
$lines = explode(\PHP_EOL, $this->buffer);
$this->buffer = array_pop($lines);
yield from $lines;

View File

@@ -2,6 +2,15 @@
declare(strict_types=1);
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PhpLlm\McpSdk\Server;
interface TransportInterface

View File

@@ -2,6 +2,15 @@
declare(strict_types=1);
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PhpLlm\McpSdk\Tests\Message;
use PhpLlm\McpSdk\Message\Error;

View File

@@ -2,6 +2,15 @@
declare(strict_types=1);
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PhpLlm\McpSdk\Tests\Message;
use PhpLlm\McpSdk\Message\Factory;

View File

@@ -2,6 +2,15 @@
declare(strict_types=1);
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PhpLlm\McpSdk\Tests\Message;
use PhpLlm\McpSdk\Message\Response;

View File

@@ -1,5 +1,16 @@
<?php
declare(strict_types=1);
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PhpLlm\McpSdk\Tests;
use PhpLlm\McpSdk\Server;