Skip to content

Commit d10ec99

Browse files
authored
Align server with MCP 2025-06-18 tool spec (#80)
* Align tools API with MCP 2025-06-18 * Fix styling --------- Co-authored-by: kargnas <[email protected]>
2 parents 8c4cc01 + 266770d commit d10ec99

File tree

17 files changed

+301
-18
lines changed

17 files changed

+301
-18
lines changed

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,10 @@ Version 1.4.0 introduces powerful automatic tool and resource generation from Sw
4747
- Smart naming for readable class names (handles hash-based operationIds)
4848
- Built-in API testing before generation
4949
- Complete Laravel HTTP client integration with retry logic
50+
- **MCP 2025-06-18 compatibility**:
51+
- Default protocol negotiation now targets the 2025-06-18 revision.
52+
- `tools/list` returns cursor-based pages and advertises the `listChanged` capability flag.
53+
- Tool responses automatically expose `structuredContent` alongside text blocks for structured payloads.
5054

5155
**Example Usage:**
5256
```bash

config/mcp-server.php

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,37 @@
272272
// App\MCP\Tools\External\PaymentProcessorTool::class,
273273
],
274274

275+
/*
276+
|--------------------------------------------------------------------------
277+
| Tools Capability Flags
278+
|--------------------------------------------------------------------------
279+
|
280+
| The MCP 2025-06-18 revision requires servers to declare whether they emit
281+
| `notifications/tools/list_changed` via the `listChanged` capability flag.
282+
| You can opt-in through the `MCP_TOOLS_LIST_CHANGED` environment variable.
283+
| @see https://modelcontextprotocol.io/specification/2025-06-18#capabilities
284+
|
285+
*/
286+
'tool_capabilities' => [
287+
'list_changed' => env('MCP_TOOLS_LIST_CHANGED', false),
288+
],
289+
290+
/*
291+
|--------------------------------------------------------------------------
292+
| Tools List Pagination
293+
|--------------------------------------------------------------------------
294+
|
295+
| `tools/list` now supports cursor-based pagination per the 2025-06-18 spec.
296+
| Configure the maximum number of tool definitions returned per page here.
297+
| Clients can request subsequent pages by passing the `nextCursor` value as
298+
| the `cursor` parameter. @see
299+
| https://modelcontextprotocol.io/specification/2025-06-18#listing-tools
300+
|
301+
*/
302+
'tools_list' => [
303+
'page_size' => env('MCP_TOOLS_PAGE_SIZE', 50),
304+
],
305+
275306
/*
276307
|--------------------------------------------------------------------------
277308
| MCP Resources Registry

scripts/test-setup.sh

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -164,9 +164,11 @@ curl -X POST "$HTTP_ENDPOINT" \
164164
"id": 1,
165165
"method": "initialize",
166166
"params": {
167-
"protocolVersion": "2024-11-05",
167+
"protocolVersion": "2025-06-18",
168168
"capabilities": {
169-
"tools": {},
169+
"tools": {
170+
"listChanged": false
171+
},
170172
"resources": {}
171173
},
172174
"clientInfo": {

src/Data/Requests/NotificationData.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
* JSON-RPC Request Notification Data
77
* Represents the data structure for a JSON-RPC notification according to the MCP specification.
88
*
9-
* @see https://modelcontextprotocol.io/specification/2024-11-05/basic/index#notifications
9+
* @see https://modelcontextprotocol.io/specification/2025-06-18/basic/index#notifications
1010
*/
1111
class NotificationData
1212
{

src/Data/Requests/RequestData.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
* Represents a JSON-RPC Request object according to the MCP specification.
77
* This class encapsulates the data structure for incoming requests.
88
*
9-
* @link https://modelcontextprotocol.io/specification/2024-11-05/basic/index#requests
9+
* @link https://modelcontextprotocol.io/specification/2025-06-18/basic/index#requests
1010
*
1111
* @property string $method The name of the method to be invoked.
1212
* @property string $jsonRpc The JSON-RPC version string (e.g., "2.0").

src/Data/Resources/InitializeResource.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,12 @@ class InitializeResource
3838
* @param array $capabilities The capabilities supported by the server.
3939
* @param string $protocolVersion The protocol version being used.
4040
*/
41-
public function __construct(string $name, string $version, array $capabilities, string $protocolVersion = '2024-11-05')
41+
/**
42+
* @param string $protocolVersion Defaults to MCP revision 2025-06-18 per the upstream spec.
43+
* This ensures initialize echoes the negotiated version documented at
44+
* https://modelcontextprotocol.io/specification/2025-06-18#initialization.
45+
*/
46+
public function __construct(string $name, string $version, array $capabilities, string $protocolVersion = '2025-06-18')
4247
{
4348
$this->serverInfo = [
4449
'name' => $name,

src/Protocol/MCPProtocol.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,9 @@
2424
*/
2525
final class MCPProtocol
2626
{
27-
// This was supposed to be 2025-03-26, but I set this to 2024-11-05 because Vercel ai-sdk doesn't support it
28-
public const PROTOCOL_VERSION = '2024-11-05';
27+
// MCP specification (2025-06-18) requires advertising this protocol version during initialize.
28+
// @see https://modelcontextprotocol.io/specification/2025-06-18 for the authoritative definition.
29+
public const PROTOCOL_VERSION = '2025-06-18';
2930

3031
private TransportInterface $transport;
3132

src/Providers/SseServiceProvider.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,11 @@ public function register(): void
6363
$capabilities = new ServerCapabilities;
6464

6565
$toolRepository = app(ToolRepository::class);
66-
$capabilities->withTools(['schemas' => $toolRepository->getToolSchemas()]);
66+
$capabilities->withTools([
67+
// Advertise pagination & change notifications support as required by MCP 2025-06-18.
68+
'listChanged' => (bool) Config::get('mcp-server.tool_capabilities.list_changed', false),
69+
'schemas' => $toolRepository->getToolSchemas(),
70+
]);
6771
$resourceRepository = app(ResourceRepository::class);
6872
$capabilities->withResources(['schemas' => [
6973
'resources' => $resourceRepository->getResourceSchemas(),

src/Providers/StreamableHttpServiceProvider.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,11 @@ public function register(): void
5858
$capabilities = new ServerCapabilities;
5959

6060
$toolRepository = app(ToolRepository::class);
61-
$capabilities->withTools(['schemas' => $toolRepository->getToolSchemas()]);
61+
$capabilities->withTools([
62+
// Advertise pagination & change notifications support as required by MCP 2025-06-18.
63+
'listChanged' => (bool) Config::get('mcp-server.tool_capabilities.list_changed', false),
64+
'schemas' => $toolRepository->getToolSchemas(),
65+
]);
6266
$resourceRepository = app(ResourceRepository::class);
6367
$capabilities->withResources(['schemas' => [
6468
'resources' => $resourceRepository->getResourceSchemas(),

src/Server/Request/ToolsCallHandler.php

Lines changed: 49 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace OPGG\LaravelMcpServer\Server\Request;
44

5+
use JsonException;
56
use OPGG\LaravelMcpServer\Enums\ProcessMessageType;
67
use OPGG\LaravelMcpServer\Exceptions\Enums\JsonRpcErrorCode;
78
use OPGG\LaravelMcpServer\Exceptions\JsonRpcErrorException;
@@ -73,15 +74,60 @@ public function execute(string $method, ?array $params = null): array
7374
return $preparedResult;
7475
}
7576

76-
if (is_array($preparedResult) && array_key_exists('content', $preparedResult)) {
77-
return $preparedResult;
77+
if (is_array($preparedResult)) {
78+
if (array_key_exists('content', $preparedResult)
79+
|| array_key_exists('structuredContent', $preparedResult)
80+
|| array_key_exists('isError', $preparedResult)) {
81+
return $preparedResult;
82+
}
83+
84+
try {
85+
$json = json_encode($preparedResult, JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE);
86+
} catch (JsonException $exception) {
87+
throw new JsonRpcErrorException(
88+
message: 'Failed to encode tool result as JSON: '.$exception->getMessage(),
89+
code: JsonRpcErrorCode::INTERNAL_ERROR
90+
);
91+
}
92+
93+
return [
94+
'content' => [
95+
[
96+
'type' => 'text',
97+
'text' => $json,
98+
],
99+
],
100+
// Provide structuredContent alongside text per MCP 2025-06-18 guidance.
101+
// @see https://modelcontextprotocol.io/specification/2025-06-18#structured-content
102+
'structuredContent' => $preparedResult,
103+
];
104+
}
105+
106+
if (is_string($preparedResult)) {
107+
return [
108+
'content' => [
109+
[
110+
'type' => 'text',
111+
'text' => $preparedResult,
112+
],
113+
],
114+
];
115+
}
116+
117+
try {
118+
$text = json_encode($preparedResult, JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE);
119+
} catch (JsonException $exception) {
120+
throw new JsonRpcErrorException(
121+
message: 'Failed to encode tool result as JSON: '.$exception->getMessage(),
122+
code: JsonRpcErrorCode::INTERNAL_ERROR
123+
);
78124
}
79125

80126
return [
81127
'content' => [
82128
[
83129
'type' => 'text',
84-
'text' => is_string($preparedResult) ? $preparedResult : json_encode($preparedResult, JSON_UNESCAPED_UNICODE),
130+
'text' => $text,
85131
],
86132
],
87133
];

0 commit comments

Comments
 (0)