diff --git a/composer.json b/composer.json index ef52894..47d8ecd 100644 --- a/composer.json +++ b/composer.json @@ -28,7 +28,7 @@ "require": { "php": "^8.1", "laravel/framework": "^9.46 || ^10.34 || ^11.29 || ^12.0", - "php-mcp/server": "^3.2" + "php-mcp/server": "^3.3" }, "require-dev": { "laravel/pint": "^1.13", diff --git a/config/mcp.php b/config/mcp.php index 3fa876c..5822ddf 100644 --- a/config/mcp.php +++ b/config/mcp.php @@ -87,6 +87,7 @@ 'host' => env('MCP_HTTP_DEDICATED_HOST', '127.0.0.1'), 'port' => (int) env('MCP_HTTP_DEDICATED_PORT', 8090), 'path_prefix' => env('MCP_HTTP_DEDICATED_PATH_PREFIX', 'mcp'), + 'stateless' => (bool) env('MCP_HTTP_DEDICATED_STATELESS', false), 'ssl_context_options' => [], 'enable_json_response' => (bool) env('MCP_HTTP_DEDICATED_JSON_RESPONSE', true), 'event_store' => null, // FQCN or null @@ -96,6 +97,7 @@ 'enabled' => (bool) env('MCP_HTTP_INTEGRATED_ENABLED', true), 'legacy' => (bool) env('MCP_HTTP_INTEGRATED_LEGACY', false), 'route_prefix' => env('MCP_HTTP_INTEGRATED_ROUTE_PREFIX', 'mcp'), + 'stateless' => (bool) env('MCP_HTTP_INTEGRATED_STATELESS', false), 'middleware' => ['api'], 'domain' => env('MCP_HTTP_INTEGRATED_DOMAIN'), 'sse_poll_interval' => (int) env('MCP_HTTP_INTEGRATED_SSE_POLL_SECONDS', 1), diff --git a/samples/basic/composer.lock b/samples/basic/composer.lock index 094bb16..ab604f2 100644 --- a/samples/basic/composer.lock +++ b/samples/basic/composer.lock @@ -1207,16 +1207,16 @@ }, { "name": "laravel/framework", - "version": "v12.19.3", + "version": "v12.20.0", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "4e6ec689ef704bb4bd282f29d9dd658dfb4fb262" + "reference": "1b9a00f8caf5503c92aa436279172beae1a484ff" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/4e6ec689ef704bb4bd282f29d9dd658dfb4fb262", - "reference": "4e6ec689ef704bb4bd282f29d9dd658dfb4fb262", + "url": "https://api.github.com/repos/laravel/framework/zipball/1b9a00f8caf5503c92aa436279172beae1a484ff", + "reference": "1b9a00f8caf5503c92aa436279172beae1a484ff", "shasum": "" }, "require": { @@ -1418,20 +1418,20 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2025-06-18T12:56:23+00:00" + "time": "2025-07-08T15:02:21+00:00" }, { "name": "laravel/prompts", - "version": "v0.3.5", + "version": "v0.3.6", "source": { "type": "git", "url": "https://github.com/laravel/prompts.git", - "reference": "57b8f7efe40333cdb925700891c7d7465325d3b1" + "reference": "86a8b692e8661d0fb308cec64f3d176821323077" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/prompts/zipball/57b8f7efe40333cdb925700891c7d7465325d3b1", - "reference": "57b8f7efe40333cdb925700891c7d7465325d3b1", + "url": "https://api.github.com/repos/laravel/prompts/zipball/86a8b692e8661d0fb308cec64f3d176821323077", + "reference": "86a8b692e8661d0fb308cec64f3d176821323077", "shasum": "" }, "require": { @@ -1475,9 +1475,9 @@ "description": "Add beautiful and user-friendly forms to your command-line applications.", "support": { "issues": "https://github.com/laravel/prompts/issues", - "source": "https://github.com/laravel/prompts/tree/v0.3.5" + "source": "https://github.com/laravel/prompts/tree/v0.3.6" }, - "time": "2025-02-11T13:34:40+00:00" + "time": "2025-07-07T14:17:42+00:00" }, { "name": "laravel/serializable-closure", @@ -2854,12 +2854,12 @@ "dist": { "type": "path", "url": "../..", - "reference": "571d03d87225587b1799d6f58880b4d805cdbaef" + "reference": "6cec2a728f6aed155a5bb4518e9241fbe3fa4d17" }, "require": { "laravel/framework": "^9.46 || ^10.34 || ^11.29 || ^12.0", "php": "^8.1", - "php-mcp/server": "^3.1" + "php-mcp/server": "^3.3" }, "require-dev": { "laravel/pint": "^1.13", @@ -2868,7 +2868,7 @@ "orchestra/testbench": "^8.0 || ^9.0", "pestphp/pest": "^2.0", "pestphp/pest-plugin-laravel": "^2.0", - "phpunit/phpunit": "^10.0 || ^11.0" + "phpunit/phpunit": "^10.0 || ^11.0 || ^12.0" }, "type": "library", "extra": { @@ -2909,11 +2909,18 @@ "role": "Developer" } ], - "description": "The official Laravel integration for the PHP MCP Server package.", + "description": "Laravel SDK for building Model Context Protocol (MCP) servers - Seamlessly integrate MCP tools, resources, and prompts into Laravel applications", "homepage": "https://github.com/php-mcp/laravel", "keywords": [ "ai", "laravel", + "laravel mcp", + "laravel mcp prompts", + "laravel mcp resources", + "laravel mcp sdk", + "laravel mcp server", + "laravel mcp tools", + "laravel model context protocol", "llm", "mcp", "model-context-protocol", @@ -2965,16 +2972,16 @@ }, { "name": "php-mcp/server", - "version": "3.1.0", + "version": "3.3.0", "source": { "type": "git", "url": "https://github.com/php-mcp/server.git", - "reference": "caa5686076a4707239a0af902f97722bc9689a89" + "reference": "37b40d5e91f0600442677ddd226e5a22d5661ee1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-mcp/server/zipball/caa5686076a4707239a0af902f97722bc9689a89", - "reference": "caa5686076a4707239a0af902f97722bc9689a89", + "url": "https://api.github.com/repos/php-mcp/server/zipball/37b40d5e91f0600442677ddd226e5a22d5661ee1", + "reference": "37b40d5e91f0600442677ddd226e5a22d5661ee1", "shasum": "" }, "require": { @@ -3001,7 +3008,7 @@ "symfony/var-dumper": "^6.4.11|^7.1.5" }, "suggest": { - "react/http": "Required for using the ReactPHP HTTP transport handler (^1.11 recommended)." + "ext-pcntl": "For signal handling support when using StdioServerTransport with StreamSelectLoop" }, "type": "library", "autoload": { @@ -3019,20 +3026,25 @@ "email": "koshnawaza@gmail.com" } ], - "description": "Core PHP implementation for the Model Context Protocol (MCP) server", + "description": "PHP SDK for building Model Context Protocol (MCP) servers - Create MCP tools, resources, and prompts", "keywords": [ "Model Context Protocol", - "ai", - "llm", "mcp", "php", + "php mcp", + "php mcp prompts", + "php mcp resources", + "php mcp sdk", + "php mcp server", + "php mcp tools", + "php model context protocol", "server" ], "support": { "issues": "https://github.com/php-mcp/server/issues", - "source": "https://github.com/php-mcp/server/tree/3.1.0" + "source": "https://github.com/php-mcp/server/tree/3.3.0" }, - "time": "2025-06-25T22:55:35+00:00" + "time": "2025-07-12T22:19:39+00:00" }, { "name": "phpdocumentor/reflection-common", @@ -3286,16 +3298,16 @@ }, { "name": "phpstan/phpdoc-parser", - "version": "2.1.0", + "version": "2.2.0", "source": { "type": "git", "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "9b30d6fd026b2c132b3985ce6b23bec09ab3aa68" + "reference": "b9e61a61e39e02dd90944e9115241c7f7e76bfd8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/9b30d6fd026b2c132b3985ce6b23bec09ab3aa68", - "reference": "9b30d6fd026b2c132b3985ce6b23bec09ab3aa68", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/b9e61a61e39e02dd90944e9115241c7f7e76bfd8", + "reference": "b9e61a61e39e02dd90944e9115241c7f7e76bfd8", "shasum": "" }, "require": { @@ -3327,9 +3339,9 @@ "description": "PHPDoc parser with support for nullable, intersection and generic types", "support": { "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/2.1.0" + "source": "https://github.com/phpstan/phpdoc-parser/tree/2.2.0" }, - "time": "2025-02-19T13:28:12+00:00" + "time": "2025-07-13T07:04:09+00:00" }, { "name": "psr/clock", @@ -3944,21 +3956,20 @@ }, { "name": "ramsey/uuid", - "version": "4.8.1", + "version": "4.9.0", "source": { "type": "git", "url": "https://github.com/ramsey/uuid.git", - "reference": "fdf4dd4e2ff1813111bd0ad58d7a1ddbb5b56c28" + "reference": "4e0e23cc785f0724a0e838279a9eb03f28b092a0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ramsey/uuid/zipball/fdf4dd4e2ff1813111bd0ad58d7a1ddbb5b56c28", - "reference": "fdf4dd4e2ff1813111bd0ad58d7a1ddbb5b56c28", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/4e0e23cc785f0724a0e838279a9eb03f28b092a0", + "reference": "4e0e23cc785f0724a0e838279a9eb03f28b092a0", "shasum": "" }, "require": { "brick/math": "^0.8.8 || ^0.9 || ^0.10 || ^0.11 || ^0.12 || ^0.13", - "ext-json": "*", "php": "^8.0", "ramsey/collection": "^1.2 || ^2.0" }, @@ -4017,9 +4028,9 @@ ], "support": { "issues": "https://github.com/ramsey/uuid/issues", - "source": "https://github.com/ramsey/uuid/tree/4.8.1" + "source": "https://github.com/ramsey/uuid/tree/4.9.0" }, - "time": "2025-06-01T06:28:46+00:00" + "time": "2025-06-25T14:20:11+00:00" }, { "name": "react/cache", @@ -4639,16 +4650,16 @@ }, { "name": "symfony/console", - "version": "v7.3.0", + "version": "v7.3.1", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "66c1440edf6f339fd82ed6c7caa76cb006211b44" + "reference": "9e27aecde8f506ba0fd1d9989620c04a87697101" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/66c1440edf6f339fd82ed6c7caa76cb006211b44", - "reference": "66c1440edf6f339fd82ed6c7caa76cb006211b44", + "url": "https://api.github.com/repos/symfony/console/zipball/9e27aecde8f506ba0fd1d9989620c04a87697101", + "reference": "9e27aecde8f506ba0fd1d9989620c04a87697101", "shasum": "" }, "require": { @@ -4713,7 +4724,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.3.0" + "source": "https://github.com/symfony/console/tree/v7.3.1" }, "funding": [ { @@ -4729,7 +4740,7 @@ "type": "tidelift" } ], - "time": "2025-05-24T10:34:04+00:00" + "time": "2025-06-27T19:55:54+00:00" }, { "name": "symfony/css-selector", @@ -4865,16 +4876,16 @@ }, { "name": "symfony/error-handler", - "version": "v7.3.0", + "version": "v7.3.1", "source": { "type": "git", "url": "https://github.com/symfony/error-handler.git", - "reference": "cf68d225bc43629de4ff54778029aee6dc191b83" + "reference": "35b55b166f6752d6aaf21aa042fc5ed280fce235" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/error-handler/zipball/cf68d225bc43629de4ff54778029aee6dc191b83", - "reference": "cf68d225bc43629de4ff54778029aee6dc191b83", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/35b55b166f6752d6aaf21aa042fc5ed280fce235", + "reference": "35b55b166f6752d6aaf21aa042fc5ed280fce235", "shasum": "" }, "require": { @@ -4922,7 +4933,7 @@ "description": "Provides tools to manage errors and ease debugging PHP code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/error-handler/tree/v7.3.0" + "source": "https://github.com/symfony/error-handler/tree/v7.3.1" }, "funding": [ { @@ -4938,7 +4949,7 @@ "type": "tidelift" } ], - "time": "2025-05-29T07:19:49+00:00" + "time": "2025-06-13T07:48:40+00:00" }, { "name": "symfony/event-dispatcher", @@ -5162,16 +5173,16 @@ }, { "name": "symfony/http-foundation", - "version": "v7.3.0", + "version": "v7.3.1", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "4236baf01609667d53b20371486228231eb135fd" + "reference": "23dd60256610c86a3414575b70c596e5deff6ed9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/4236baf01609667d53b20371486228231eb135fd", - "reference": "4236baf01609667d53b20371486228231eb135fd", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/23dd60256610c86a3414575b70c596e5deff6ed9", + "reference": "23dd60256610c86a3414575b70c596e5deff6ed9", "shasum": "" }, "require": { @@ -5221,7 +5232,7 @@ "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v7.3.0" + "source": "https://github.com/symfony/http-foundation/tree/v7.3.1" }, "funding": [ { @@ -5237,20 +5248,20 @@ "type": "tidelift" } ], - "time": "2025-05-12T14:48:23+00:00" + "time": "2025-06-23T15:07:14+00:00" }, { "name": "symfony/http-kernel", - "version": "v7.3.0", + "version": "v7.3.1", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "ac7b8e163e8c83dce3abcc055a502d4486051a9f" + "reference": "1644879a66e4aa29c36fe33dfa6c54b450ce1831" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/ac7b8e163e8c83dce3abcc055a502d4486051a9f", - "reference": "ac7b8e163e8c83dce3abcc055a502d4486051a9f", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/1644879a66e4aa29c36fe33dfa6c54b450ce1831", + "reference": "1644879a66e4aa29c36fe33dfa6c54b450ce1831", "shasum": "" }, "require": { @@ -5335,7 +5346,7 @@ "description": "Provides a structured process for converting a Request into a Response", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-kernel/tree/v7.3.0" + "source": "https://github.com/symfony/http-kernel/tree/v7.3.1" }, "funding": [ { @@ -5351,20 +5362,20 @@ "type": "tidelift" } ], - "time": "2025-05-29T07:47:32+00:00" + "time": "2025-06-28T08:24:55+00:00" }, { "name": "symfony/mailer", - "version": "v7.3.0", + "version": "v7.3.1", "source": { "type": "git", "url": "https://github.com/symfony/mailer.git", - "reference": "0f375bbbde96ae8c78e4aa3e63aabd486e33364c" + "reference": "b5db5105b290bdbea5ab27b89c69effcf1cb3368" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mailer/zipball/0f375bbbde96ae8c78e4aa3e63aabd486e33364c", - "reference": "0f375bbbde96ae8c78e4aa3e63aabd486e33364c", + "url": "https://api.github.com/repos/symfony/mailer/zipball/b5db5105b290bdbea5ab27b89c69effcf1cb3368", + "reference": "b5db5105b290bdbea5ab27b89c69effcf1cb3368", "shasum": "" }, "require": { @@ -5415,7 +5426,7 @@ "description": "Helps sending emails", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/mailer/tree/v7.3.0" + "source": "https://github.com/symfony/mailer/tree/v7.3.1" }, "funding": [ { @@ -5431,7 +5442,7 @@ "type": "tidelift" } ], - "time": "2025-04-04T09:51:09+00:00" + "time": "2025-06-27T19:55:54+00:00" }, { "name": "symfony/mime", @@ -6468,16 +6479,16 @@ }, { "name": "symfony/translation", - "version": "v7.3.0", + "version": "v7.3.1", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "4aba29076a29a3aa667e09b791e5f868973a8667" + "reference": "241d5ac4910d256660238a7ecf250deba4c73063" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/4aba29076a29a3aa667e09b791e5f868973a8667", - "reference": "4aba29076a29a3aa667e09b791e5f868973a8667", + "url": "https://api.github.com/repos/symfony/translation/zipball/241d5ac4910d256660238a7ecf250deba4c73063", + "reference": "241d5ac4910d256660238a7ecf250deba4c73063", "shasum": "" }, "require": { @@ -6544,7 +6555,7 @@ "description": "Provides tools to internationalize your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/translation/tree/v7.3.0" + "source": "https://github.com/symfony/translation/tree/v7.3.1" }, "funding": [ { @@ -6560,7 +6571,7 @@ "type": "tidelift" } ], - "time": "2025-05-29T07:19:49+00:00" + "time": "2025-06-27T19:55:54+00:00" }, { "name": "symfony/translation-contracts", @@ -6642,16 +6653,16 @@ }, { "name": "symfony/uid", - "version": "v7.3.0", + "version": "v7.3.1", "source": { "type": "git", "url": "https://github.com/symfony/uid.git", - "reference": "7beeb2b885cd584cd01e126c5777206ae4c3c6a3" + "reference": "a69f69f3159b852651a6bf45a9fdd149520525bb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/uid/zipball/7beeb2b885cd584cd01e126c5777206ae4c3c6a3", - "reference": "7beeb2b885cd584cd01e126c5777206ae4c3c6a3", + "url": "https://api.github.com/repos/symfony/uid/zipball/a69f69f3159b852651a6bf45a9fdd149520525bb", + "reference": "a69f69f3159b852651a6bf45a9fdd149520525bb", "shasum": "" }, "require": { @@ -6696,7 +6707,7 @@ "uuid" ], "support": { - "source": "https://github.com/symfony/uid/tree/v7.3.0" + "source": "https://github.com/symfony/uid/tree/v7.3.1" }, "funding": [ { @@ -6712,20 +6723,20 @@ "type": "tidelift" } ], - "time": "2025-05-24T14:28:13+00:00" + "time": "2025-06-27T19:55:54+00:00" }, { "name": "symfony/var-dumper", - "version": "v7.3.0", + "version": "v7.3.1", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "548f6760c54197b1084e1e5c71f6d9d523f2f78e" + "reference": "6e209fbe5f5a7b6043baba46fe5735a4b85d0d42" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/548f6760c54197b1084e1e5c71f6d9d523f2f78e", - "reference": "548f6760c54197b1084e1e5c71f6d9d523f2f78e", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/6e209fbe5f5a7b6043baba46fe5735a4b85d0d42", + "reference": "6e209fbe5f5a7b6043baba46fe5735a4b85d0d42", "shasum": "" }, "require": { @@ -6780,7 +6791,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v7.3.0" + "source": "https://github.com/symfony/var-dumper/tree/v7.3.1" }, "funding": [ { @@ -6796,7 +6807,7 @@ "type": "tidelift" } ], - "time": "2025-04-27T18:39:23+00:00" + "time": "2025-06-27T19:55:54+00:00" }, { "name": "tijsverkoyen/css-to-inline-styles", @@ -7551,16 +7562,16 @@ }, { "name": "laravel/pint", - "version": "v1.22.1", + "version": "v1.24.0", "source": { "type": "git", "url": "https://github.com/laravel/pint.git", - "reference": "941d1927c5ca420c22710e98420287169c7bcaf7" + "reference": "0345f3b05f136801af8c339f9d16ef29e6b4df8a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/pint/zipball/941d1927c5ca420c22710e98420287169c7bcaf7", - "reference": "941d1927c5ca420c22710e98420287169c7bcaf7", + "url": "https://api.github.com/repos/laravel/pint/zipball/0345f3b05f136801af8c339f9d16ef29e6b4df8a", + "reference": "0345f3b05f136801af8c339f9d16ef29e6b4df8a", "shasum": "" }, "require": { @@ -7571,10 +7582,10 @@ "php": "^8.2.0" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^3.75.0", - "illuminate/view": "^11.44.7", - "larastan/larastan": "^3.4.0", - "laravel-zero/framework": "^11.36.1", + "friendsofphp/php-cs-fixer": "^3.82.2", + "illuminate/view": "^11.45.1", + "larastan/larastan": "^3.5.0", + "laravel-zero/framework": "^11.45.0", "mockery/mockery": "^1.6.12", "nunomaduro/termwind": "^2.3.1", "pestphp/pest": "^2.36.0" @@ -7584,6 +7595,9 @@ ], "type": "project", "autoload": { + "files": [ + "overrides/Runner/Parallel/ProcessFactory.php" + ], "psr-4": { "App\\": "app/", "Database\\Seeders\\": "database/seeders/", @@ -7613,7 +7627,7 @@ "issues": "https://github.com/laravel/pint/issues", "source": "https://github.com/laravel/pint" }, - "time": "2025-05-08T08:38:12+00:00" + "time": "2025-07-10T18:09:32+00:00" }, { "name": "laravel/sail", @@ -7763,16 +7777,16 @@ }, { "name": "myclabs/deep-copy", - "version": "1.13.1", + "version": "1.13.3", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "1720ddd719e16cf0db4eb1c6eca108031636d46c" + "reference": "faed855a7b5f4d4637717c2b3863e277116beb36" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/1720ddd719e16cf0db4eb1c6eca108031636d46c", - "reference": "1720ddd719e16cf0db4eb1c6eca108031636d46c", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/faed855a7b5f4d4637717c2b3863e277116beb36", + "reference": "faed855a7b5f4d4637717c2b3863e277116beb36", "shasum": "" }, "require": { @@ -7811,7 +7825,7 @@ ], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.13.1" + "source": "https://github.com/myclabs/DeepCopy/tree/1.13.3" }, "funding": [ { @@ -7819,7 +7833,7 @@ "type": "tidelift" } ], - "time": "2025-04-29T12:36:36+00:00" + "time": "2025-07-05T12:25:42+00:00" }, { "name": "nunomaduro/collision", @@ -9864,16 +9878,16 @@ }, { "name": "symfony/yaml", - "version": "v7.3.0", + "version": "v7.3.1", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "cea40a48279d58dc3efee8112634cb90141156c2" + "reference": "0c3555045a46ab3cd4cc5a69d161225195230edb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/cea40a48279d58dc3efee8112634cb90141156c2", - "reference": "cea40a48279d58dc3efee8112634cb90141156c2", + "url": "https://api.github.com/repos/symfony/yaml/zipball/0c3555045a46ab3cd4cc5a69d161225195230edb", + "reference": "0c3555045a46ab3cd4cc5a69d161225195230edb", "shasum": "" }, "require": { @@ -9916,7 +9930,7 @@ "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/yaml/tree/v7.3.0" + "source": "https://github.com/symfony/yaml/tree/v7.3.1" }, "funding": [ { @@ -9932,7 +9946,7 @@ "type": "tidelift" } ], - "time": "2025-04-04T10:10:33+00:00" + "time": "2025-06-03T06:57:57+00:00" }, { "name": "ta-tikoma/phpunit-architecture-test", diff --git a/samples/basic/config/mcp.php b/samples/basic/config/mcp.php index 24ffc6f..e950e70 100644 --- a/samples/basic/config/mcp.php +++ b/samples/basic/config/mcp.php @@ -84,6 +84,7 @@ 'host' => env('MCP_HTTP_DEDICATED_HOST', '127.0.0.1'), 'port' => (int) env('MCP_HTTP_DEDICATED_PORT', 8090), 'path_prefix' => env('MCP_HTTP_DEDICATED_PATH_PREFIX', 'mcp'), + 'stateless' => (bool) env('MCP_HTTP_DEDICATED_STATELESS', false), 'ssl_context_options' => [], 'enable_json_response' => (bool) env('MCP_HTTP_DEDICATED_JSON_RESPONSE', true), 'event_store' => null, // FQCN or null @@ -93,6 +94,7 @@ 'enabled' => (bool) env('MCP_HTTP_INTEGRATED_ENABLED', true), 'legacy' => (bool) env('MCP_HTTP_INTEGRATED_LEGACY', false), 'route_prefix' => env('MCP_HTTP_INTEGRATED_ROUTE_PREFIX', 'mcp'), + 'stateless' => (bool) env('MCP_HTTP_INTEGRATED_STATELESS', true), 'middleware' => explode(',', env('MCP_HTTP_INTEGRATED_MIDDLEWARE', 'api')), 'domain' => env('MCP_HTTP_INTEGRATED_DOMAIN'), 'sse_poll_interval' => (int) env('MCP_HTTP_INTEGRATED_SSE_POLL_SECONDS', 1), diff --git a/src/Commands/ServeCommand.php b/src/Commands/ServeCommand.php index da1dbab..b84bc28 100644 --- a/src/Commands/ServeCommand.php +++ b/src/Commands/ServeCommand.php @@ -151,6 +151,7 @@ private function handleStreamableHttpTransport(Server $server, string $host, int { $enableJsonResponse = config('mcp.transports.http_dedicated.enable_json_response', true); $eventStore = $this->createEventStore(); + $stateless = config('mcp.transports.http_dedicated.stateless', false); $this->info("Starting MCP server on http://{$host}:{$port}"); $this->line(" - Transport: Streamable HTTP"); @@ -163,6 +164,7 @@ private function handleStreamableHttpTransport(Server $server, string $host, int mcpPath: $pathPrefix, sslContext: $sslContextOptions, enableJsonResponse: $enableJsonResponse, + stateless: $stateless, eventStore: $eventStore ); diff --git a/src/Http/Controllers/StreamableTransportController.php b/src/Http/Controllers/StreamableTransportController.php index 000f70f..becc601 100644 --- a/src/Http/Controllers/StreamableTransportController.php +++ b/src/Http/Controllers/StreamableTransportController.php @@ -19,8 +19,9 @@ public function __construct(Server $server) { $eventStore = $this->createEventStore(); $sessionManager = $server->getSessionManager(); + $stateless = config('mcp.transports.http_integrated.stateless', false); - $this->transport = new StreamableHttpServerTransport($sessionManager, $eventStore); + $this->transport = new StreamableHttpServerTransport($sessionManager, $eventStore, $stateless); $server->listen($this->transport, false); } diff --git a/src/Transports/StreamableHttpServerTransport.php b/src/Transports/StreamableHttpServerTransport.php index c583453..f07a32d 100644 --- a/src/Transports/StreamableHttpServerTransport.php +++ b/src/Transports/StreamableHttpServerTransport.php @@ -29,7 +29,8 @@ class StreamableHttpServerTransport implements ServerTransportInterface public function __construct( protected SessionManager $sessionManager, - protected ?EventStoreInterface $eventStore = null + protected ?EventStoreInterface $eventStore = null, + protected bool $stateless = false ) {} protected function generateId(): string @@ -104,27 +105,33 @@ public function handlePostRequest(Request $request): Response $isInitializeRequest = ($message instanceof JsonRpcRequest && $message->method === 'initialize'); $sessionId = null; - if ($isInitializeRequest) { - if ($request->hasHeader('Mcp-Session-Id')) { - Log::warning('Client sent Mcp-Session-Id with InitializeRequest. Ignoring.', ['clientSentId' => $request->header('Mcp-Session-Id')]); - $error = Error::forInvalidRequest('Invalid request: Session already initialized. Mcp-Session-Id header not allowed with InitializeRequest.', $message->getId()); - return response()->json($error, 400, $this->getCorsHeaders()); - } - + if ($this->stateless) { $sessionId = $this->generateId(); $this->emit('client_connected', [$sessionId]); } else { - $sessionId = $request->header('Mcp-Session-Id'); + if ($isInitializeRequest) { + if ($request->hasHeader('Mcp-Session-Id')) { + Log::warning('Client sent Mcp-Session-Id with InitializeRequest. Ignoring.', ['clientSentId' => $request->header('Mcp-Session-Id')]); + $error = Error::forInvalidRequest('Invalid request: Session already initialized. Mcp-Session-Id header not allowed with InitializeRequest.', $message->getId()); + return response()->json($error, 400, $this->getCorsHeaders()); + } + + $sessionId = $this->generateId(); + $this->emit('client_connected', [$sessionId]); + } else { + $sessionId = $request->header('Mcp-Session-Id'); - if (empty($sessionId)) { - Log::warning('POST request without Mcp-Session-Id'); - $error = Error::forInvalidRequest('Mcp-Session-Id header required for POST requests', $message->getId()); - return response()->json($error, 400, $this->getCorsHeaders()); + if (empty($sessionId)) { + Log::warning('POST request without Mcp-Session-Id'); + $error = Error::forInvalidRequest('Mcp-Session-Id header required for POST requests', $message->getId()); + return response()->json($error, 400, $this->getCorsHeaders()); + } } } $context = [ 'is_initialize_request' => $isInitializeRequest, + 'stateless' => $this->stateless, ]; $nRequests = match (true) { @@ -171,10 +178,14 @@ protected function handleJsonResponse(Message $message, string $sessionId, array ...$this->getCorsHeaders() ]; - if ($context['is_initialize_request'] ?? false) { + if ($context['is_initialize_request'] ?? false && !$this->stateless) { $headers['Mcp-Session-Id'] = $sessionId; } + if ($this->stateless) { + $this->emit('client_disconnected', [$sessionId, 'Stateless request completed']); + } + return response()->make($data, 200, $headers); } catch (Throwable $e) { Log::error('JSON response mode error', ['exception' => $e]); @@ -195,7 +206,7 @@ protected function handleSseResponse(Message $message, string $sessionId, int $n 'X-Accel-Buffering' => 'no', ], $this->getCorsHeaders()); - if ($context['is_initialize_request'] ?? false) { + if ($context['is_initialize_request'] ?? false && !$this->stateless) { $headers['Mcp-Session-Id'] = $sessionId; } @@ -210,6 +221,10 @@ protected function handleSseResponse(Message $message, string $sessionId, int $n $messages = $this->dequeueMessagesForContext($sessionId, 'post_sse', $streamId); $this->sendSseEvent($messages[0]['data'], $messages[0]['id']); + + if ($this->stateless) { + $this->emit('client_disconnected', [$sessionId, 'Stateless request completed']); + } }, headers: $headers); } @@ -218,6 +233,12 @@ protected function handleSseResponse(Message $message, string $sessionId, int $n */ public function handleGetRequest(Request $request): StreamedResponse|Response { + if ($this->stateless) { + return new Response('SSE not available in stateless mode', 405, [ + 'Allow' => 'POST, DELETE, OPTIONS' + ]); + } + $acceptHeader = $request->header('Accept'); if (!str_contains($acceptHeader, 'text/event-stream')) { $error = Error::forInvalidRequest("Not Acceptable: Client must accept text/event-stream for GET requests."); @@ -274,6 +295,10 @@ public function handleGetRequest(Request $request): StreamedResponse|Response */ public function handleDeleteRequest(Request $request): Response { + if ($this->stateless) { + return response()->noContent(headers: $this->getCorsHeaders()); + } + $sessionId = $request->header('Mcp-Session-Id'); if (empty($sessionId)) { Log::warning("DELETE request without Mcp-Session-Id."); @@ -285,7 +310,7 @@ public function handleDeleteRequest(Request $request): Response $this->emit('client_disconnected', [$sessionId, 'Session terminated by DELETE request']); - return response()->noContent(204, $this->getCorsHeaders()); + return response()->noContent(headers: $this->getCorsHeaders()); } /**