v2 refactor!: remove Zod schema parameter from public-facing APIs (#1606)
This commit is contained in:
parent
1a78b0138f
commit
f88c376c52
30 changed files with 1483 additions and 1708 deletions
|
|
@ -16,7 +16,6 @@ The examples below use these imports. Adjust based on which features and transpo
|
|||
import type { Prompt, Resource, Tool } from '@modelcontextprotocol/client';
|
||||
import {
|
||||
applyMiddlewares,
|
||||
CallToolResultSchema,
|
||||
Client,
|
||||
ClientCredentialsProvider,
|
||||
createMiddleware,
|
||||
|
|
@ -198,13 +197,16 @@ if (result.structuredContent) {
|
|||
Pass `onprogress` to receive incremental progress notifications from long-running tools. Use `resetTimeoutOnProgress` to keep the request alive while the server is actively reporting, and `maxTotalTimeout` as an absolute cap:
|
||||
|
||||
```ts source="../examples/client/src/clientGuide.examples.ts#callTool_progress"
|
||||
const result = await client.callTool({ name: 'long-operation', arguments: {} }, undefined, {
|
||||
onprogress: ({ progress, total }) => {
|
||||
console.log(`Progress: ${progress}/${total ?? '?'}`);
|
||||
},
|
||||
resetTimeoutOnProgress: true,
|
||||
maxTotalTimeout: 600_000
|
||||
});
|
||||
const result = await client.callTool(
|
||||
{ name: 'long-operation', arguments: {} },
|
||||
{
|
||||
onprogress: ({ progress, total }: { progress: number; total?: number }) => {
|
||||
console.log(`Progress: ${progress}/${total ?? '?'}`);
|
||||
},
|
||||
resetTimeoutOnProgress: true,
|
||||
maxTotalTimeout: 600_000
|
||||
}
|
||||
);
|
||||
console.log(result.content);
|
||||
```
|
||||
|
||||
|
|
@ -484,7 +486,6 @@ All requests have a 60-second default timeout. Pass a custom `timeout` in the op
|
|||
try {
|
||||
const result = await client.callTool(
|
||||
{ name: 'slow-task', arguments: {} },
|
||||
undefined,
|
||||
{ timeout: 120_000 } // 2 minutes instead of the default 60 seconds
|
||||
);
|
||||
console.log(result.content);
|
||||
|
|
@ -523,7 +524,6 @@ const result = await client.request(
|
|||
method: 'tools/call',
|
||||
params: { name: 'long-running-task', arguments: {} }
|
||||
},
|
||||
CallToolResultSchema,
|
||||
{
|
||||
resumptionToken: lastToken,
|
||||
onresumptiontoken: (token: string) => {
|
||||
|
|
|
|||
|
|
@ -203,7 +203,7 @@ import { OAuthError, OAuthErrorCode } from '@modelcontextprotocol/core';
|
|||
if (error instanceof OAuthError && error.code === OAuthErrorCode.InvalidClient) { ... }
|
||||
```
|
||||
|
||||
**Unchanged APIs** (only import paths changed): `Client` constructor and methods, `McpServer` constructor, `server.connect()`, `server.close()`, all client transports (`StreamableHTTPClientTransport`, `SSEClientTransport`, `StdioClientTransport`), `StdioServerTransport`, all Zod schemas, all callback return types.
|
||||
**Unchanged APIs** (only import paths changed): `Client` constructor and most methods, `McpServer` constructor, `server.connect()`, `server.close()`, all client transports (`StreamableHTTPClientTransport`, `SSEClientTransport`, `StdioClientTransport`), `StdioServerTransport`, all Zod schemas, all callback return types. Note: `callTool()` and `request()` signatures changed (schema parameter removed, see section 11).
|
||||
|
||||
## 6. McpServer API Changes
|
||||
|
||||
|
|
@ -396,11 +396,39 @@ Request/notification params remain fully typed. Remove unused schema imports aft
|
|||
| `ctx.mcpReq.elicitInput(params, options?)` | Elicit user input (form or URL) | `server.elicitInput(...)` from within handler |
|
||||
| `ctx.mcpReq.requestSampling(params, options?)` | Request LLM sampling from client | `server.createMessage(...)` from within handler |
|
||||
|
||||
## 11. Client Behavioral Changes
|
||||
## 11. Schema parameter removed from `request()`, `send()`, and `callTool()`
|
||||
|
||||
`Protocol.request()`, `BaseContext.mcpReq.send()`, and `Client.callTool()` no longer take a Zod result schema argument. The SDK resolves the schema internally from the method name.
|
||||
|
||||
```typescript
|
||||
// v1: schema required
|
||||
import { CallToolResultSchema, ElicitResultSchema } from '@modelcontextprotocol/sdk/types.js';
|
||||
const result = await client.request({ method: 'tools/call', params: { ... } }, CallToolResultSchema);
|
||||
const elicit = await ctx.mcpReq.send({ method: 'elicitation/create', params: { ... } }, ElicitResultSchema);
|
||||
const tool = await client.callTool({ name: 'my-tool', arguments: {} }, CompatibilityCallToolResultSchema);
|
||||
|
||||
// v2: no schema argument
|
||||
const result = await client.request({ method: 'tools/call', params: { ... } });
|
||||
const elicit = await ctx.mcpReq.send({ method: 'elicitation/create', params: { ... } });
|
||||
const tool = await client.callTool({ name: 'my-tool', arguments: {} });
|
||||
```
|
||||
|
||||
| v1 call | v2 call |
|
||||
|---------|---------|
|
||||
| `client.request(req, ResultSchema)` | `client.request(req)` |
|
||||
| `client.request(req, ResultSchema, options)` | `client.request(req, options)` |
|
||||
| `ctx.mcpReq.send(req, ResultSchema)` | `ctx.mcpReq.send(req)` |
|
||||
| `ctx.mcpReq.send(req, ResultSchema, options)` | `ctx.mcpReq.send(req, options)` |
|
||||
| `client.callTool(params, CompatibilityCallToolResultSchema)` | `client.callTool(params)` |
|
||||
| `client.callTool(params, schema, options)` | `client.callTool(params, options)` |
|
||||
|
||||
Remove unused schema imports: `CallToolResultSchema`, `CompatibilityCallToolResultSchema`, `ElicitResultSchema`, `CreateMessageResultSchema`, etc., when they were only used in `request()`/`send()`/`callTool()` calls.
|
||||
|
||||
## 12. Client Behavioral Changes
|
||||
|
||||
`Client.listPrompts()`, `listResources()`, `listResourceTemplates()`, `listTools()` now return empty results when the server lacks the corresponding capability (instead of sending the request). Set `enforceStrictCapabilities: true` in `ClientOptions` to throw an error instead.
|
||||
|
||||
## 12. Runtime-Specific JSON Schema Validators (Enhancement)
|
||||
## 13. Runtime-Specific JSON Schema Validators (Enhancement)
|
||||
|
||||
The SDK now auto-selects the appropriate JSON Schema validator based on runtime:
|
||||
- Node.js → `AjvJsonSchemaValidator` (no change from v1)
|
||||
|
|
@ -420,7 +448,7 @@ new McpServer({ name: 'server', version: '1.0.0' }, {});
|
|||
|
||||
Access validators via `_shims` export: `import { DefaultJsonSchemaValidator } from '@modelcontextprotocol/server/_shims';`
|
||||
|
||||
## 13. Migration Steps (apply in this order)
|
||||
## 14. Migration Steps (apply in this order)
|
||||
|
||||
1. Update `package.json`: `npm uninstall @modelcontextprotocol/sdk`, install the appropriate v2 packages
|
||||
2. Replace all imports from `@modelcontextprotocol/sdk/...` using the import mapping tables (sections 3-4), including `StreamableHTTPServerTransport` → `NodeStreamableHTTPServerTransport`
|
||||
|
|
|
|||
|
|
@ -337,6 +337,73 @@ Common method string replacements:
|
|||
| `ResourceListChangedNotificationSchema` | `'notifications/resources/list_changed'` |
|
||||
| `PromptListChangedNotificationSchema` | `'notifications/prompts/list_changed'` |
|
||||
|
||||
### `Protocol.request()`, `ctx.mcpReq.send()`, and `Client.callTool()` no longer take a schema parameter
|
||||
|
||||
The public `Protocol.request()`, `BaseContext.mcpReq.send()`, and `Client.callTool()` methods no longer accept a Zod result schema argument. The SDK now resolves the correct result schema internally based on the method name. This means you no longer need to import result schemas like `CallToolResultSchema` or `ElicitResultSchema` when making requests.
|
||||
|
||||
**`client.request()` — Before (v1):**
|
||||
|
||||
```typescript
|
||||
import { CallToolResultSchema } from '@modelcontextprotocol/sdk/types.js';
|
||||
|
||||
const result = await client.request(
|
||||
{ method: 'tools/call', params: { name: 'my-tool', arguments: {} } },
|
||||
CallToolResultSchema
|
||||
);
|
||||
```
|
||||
|
||||
**After (v2):**
|
||||
|
||||
```typescript
|
||||
const result = await client.request(
|
||||
{ method: 'tools/call', params: { name: 'my-tool', arguments: {} } }
|
||||
);
|
||||
```
|
||||
|
||||
**`ctx.mcpReq.send()` — Before (v1):**
|
||||
|
||||
```typescript
|
||||
import { CreateMessageResultSchema } from '@modelcontextprotocol/sdk/types.js';
|
||||
|
||||
server.setRequestHandler('tools/call', async (request, ctx) => {
|
||||
const samplingResult = await ctx.mcpReq.send(
|
||||
{ method: 'sampling/createMessage', params: { messages: [...], maxTokens: 100 } },
|
||||
CreateMessageResultSchema
|
||||
);
|
||||
return { content: [{ type: 'text', text: 'done' }] };
|
||||
});
|
||||
```
|
||||
|
||||
**After (v2):**
|
||||
|
||||
```typescript
|
||||
server.setRequestHandler('tools/call', async (request, ctx) => {
|
||||
const samplingResult = await ctx.mcpReq.send(
|
||||
{ method: 'sampling/createMessage', params: { messages: [...], maxTokens: 100 } }
|
||||
);
|
||||
return { content: [{ type: 'text', text: 'done' }] };
|
||||
});
|
||||
```
|
||||
|
||||
**`client.callTool()` — Before (v1):**
|
||||
|
||||
```typescript
|
||||
import { CompatibilityCallToolResultSchema } from '@modelcontextprotocol/sdk/types.js';
|
||||
|
||||
const result = await client.callTool(
|
||||
{ name: 'my-tool', arguments: {} },
|
||||
CompatibilityCallToolResultSchema
|
||||
);
|
||||
```
|
||||
|
||||
**After (v2):**
|
||||
|
||||
```typescript
|
||||
const result = await client.callTool({ name: 'my-tool', arguments: {} });
|
||||
```
|
||||
|
||||
The return type is now inferred from the method name via `ResultTypeMap`. For example, `client.request({ method: 'tools/call', ... })` returns `Promise<CallToolResult | CreateTaskResult>`.
|
||||
|
||||
### Client list methods return empty results for missing capabilities
|
||||
|
||||
`Client.listPrompts()`, `listResources()`, `listResourceTemplates()`, and `listTools()` now return empty results when the server didn't advertise the corresponding capability, instead of sending the request. This respects the MCP spec's capability negotiation.
|
||||
|
|
@ -708,7 +775,7 @@ import { AjvJsonSchemaValidator, CfWorkerJsonSchemaValidator } from '@modelconte
|
|||
|
||||
The following APIs are unchanged between v1 and v2 (only the import paths changed):
|
||||
|
||||
- `Client` constructor and all client methods (`connect`, `callTool`, `listTools`, `listPrompts`, `listResources`, `readResource`, etc.)
|
||||
- `Client` constructor and most client methods (`connect`, `listTools`, `listPrompts`, `listResources`, `readResource`, etc.) — note: `callTool()` signature changed (schema parameter removed)
|
||||
- `McpServer` constructor, `server.connect(transport)`, `server.close()`
|
||||
- `Server` (low-level) constructor and all methods
|
||||
- `StreamableHTTPClientTransport`, `SSEClientTransport`, `StdioClientTransport` constructors and options
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@
|
|||
import type { Prompt, Resource, Tool } from '@modelcontextprotocol/client';
|
||||
import {
|
||||
applyMiddlewares,
|
||||
CallToolResultSchema,
|
||||
Client,
|
||||
ClientCredentialsProvider,
|
||||
createMiddleware,
|
||||
|
|
@ -181,13 +180,16 @@ async function callTool_structuredOutput(client: Client) {
|
|||
/** Example: Track progress of a long-running tool call. */
|
||||
async function callTool_progress(client: Client) {
|
||||
//#region callTool_progress
|
||||
const result = await client.callTool({ name: 'long-operation', arguments: {} }, undefined, {
|
||||
onprogress: ({ progress, total }) => {
|
||||
console.log(`Progress: ${progress}/${total ?? '?'}`);
|
||||
},
|
||||
resetTimeoutOnProgress: true,
|
||||
maxTotalTimeout: 600_000
|
||||
});
|
||||
const result = await client.callTool(
|
||||
{ name: 'long-operation', arguments: {} },
|
||||
{
|
||||
onprogress: ({ progress, total }: { progress: number; total?: number }) => {
|
||||
console.log(`Progress: ${progress}/${total ?? '?'}`);
|
||||
},
|
||||
resetTimeoutOnProgress: true,
|
||||
maxTotalTimeout: 600_000
|
||||
}
|
||||
);
|
||||
console.log(result.content);
|
||||
//#endregion callTool_progress
|
||||
}
|
||||
|
|
@ -450,7 +452,6 @@ async function errorHandling_timeout(client: Client) {
|
|||
try {
|
||||
const result = await client.callTool(
|
||||
{ name: 'slow-task', arguments: {} },
|
||||
undefined,
|
||||
{ timeout: 120_000 } // 2 minutes instead of the default 60 seconds
|
||||
);
|
||||
console.log(result.content);
|
||||
|
|
@ -492,7 +493,6 @@ async function resumptionToken_basic(client: Client) {
|
|||
method: 'tools/call',
|
||||
params: { name: 'long-running-task', arguments: {} }
|
||||
},
|
||||
CallToolResultSchema,
|
||||
{
|
||||
resumptionToken: lastToken,
|
||||
onresumptiontoken: (token: string) => {
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import { createInterface } from 'node:readline';
|
|||
|
||||
import type {
|
||||
CallToolRequest,
|
||||
CallToolResult,
|
||||
ElicitRequest,
|
||||
ElicitRequestURLParams,
|
||||
ElicitResult,
|
||||
|
|
@ -18,10 +19,8 @@ import type {
|
|||
ResourceLink
|
||||
} from '@modelcontextprotocol/client';
|
||||
import {
|
||||
CallToolResultSchema,
|
||||
Client,
|
||||
getDisplayName,
|
||||
ListToolsResultSchema,
|
||||
ProtocolError,
|
||||
ProtocolErrorCode,
|
||||
StreamableHTTPClientTransport,
|
||||
|
|
@ -666,7 +665,7 @@ async function listTools(): Promise<void> {
|
|||
method: 'tools/list',
|
||||
params: {}
|
||||
};
|
||||
const toolsResult = await client.request(toolsRequest, ListToolsResultSchema);
|
||||
const toolsResult = await client.request(toolsRequest);
|
||||
|
||||
console.log('Available tools:');
|
||||
if (toolsResult.tools.length === 0) {
|
||||
|
|
@ -697,7 +696,7 @@ async function callTool(name: string, args: Record<string, unknown>): Promise<vo
|
|||
};
|
||||
|
||||
console.log(`Calling tool '${name}' with args:`, args);
|
||||
const result = await client.request(request, CallToolResultSchema);
|
||||
const result = (await client.request(request)) as CallToolResult;
|
||||
|
||||
console.log('Tool result:');
|
||||
const resourceLinks: ResourceLink[] = [];
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type { CallToolRequest, CallToolResult } from '@modelcontextprotocol/client';
|
||||
import { CallToolResultSchema, Client, StreamableHTTPClientTransport } from '@modelcontextprotocol/client';
|
||||
import { Client, StreamableHTTPClientTransport } from '@modelcontextprotocol/client';
|
||||
|
||||
/**
|
||||
* Multiple Clients MCP Example
|
||||
|
|
@ -60,7 +60,7 @@ async function createAndRunClient(config: ClientConfig): Promise<{ id: string; r
|
|||
}
|
||||
};
|
||||
|
||||
const result = await client.request(toolRequest, CallToolResultSchema);
|
||||
const result = (await client.request(toolRequest)) as CallToolResult;
|
||||
console.log(`[${config.id}] Tool call completed`);
|
||||
|
||||
// Keep the connection open for a bit to receive notifications
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type { CallToolResult, ListToolsRequest } from '@modelcontextprotocol/client';
|
||||
import { CallToolResultSchema, Client, ListToolsResultSchema, StreamableHTTPClientTransport } from '@modelcontextprotocol/client';
|
||||
import type { CallToolRequest, CallToolResult, ListToolsRequest } from '@modelcontextprotocol/client';
|
||||
import { Client, StreamableHTTPClientTransport } from '@modelcontextprotocol/client';
|
||||
|
||||
/**
|
||||
* Parallel Tool Calls MCP Client
|
||||
|
|
@ -86,7 +86,7 @@ async function listTools(client: Client): Promise<void> {
|
|||
method: 'tools/list',
|
||||
params: {}
|
||||
};
|
||||
const toolsResult = await client.request(toolsRequest, ListToolsResultSchema);
|
||||
const toolsResult = await client.request(toolsRequest);
|
||||
|
||||
console.log('Available tools:');
|
||||
if (toolsResult.tools.length === 0) {
|
||||
|
|
@ -108,7 +108,7 @@ async function listTools(client: Client): Promise<void> {
|
|||
async function startParallelNotificationTools(client: Client): Promise<Record<string, CallToolResult>> {
|
||||
try {
|
||||
// Define multiple tool calls with different configurations
|
||||
const toolCalls = [
|
||||
const toolCalls: Array<{ caller: string; request: CallToolRequest }> = [
|
||||
{
|
||||
caller: 'fast-notifier',
|
||||
request: {
|
||||
|
|
@ -159,7 +159,7 @@ async function startParallelNotificationTools(client: Client): Promise<Record<st
|
|||
const toolPromises = toolCalls.map(({ caller, request }) => {
|
||||
console.log(`Starting tool call for ${caller}...`);
|
||||
return client
|
||||
.request(request, CallToolResultSchema)
|
||||
.request(request)
|
||||
.then(result => ({ caller, result }))
|
||||
.catch(error => {
|
||||
console.error(`Error in tool call for ${caller}:`, error);
|
||||
|
|
@ -173,7 +173,7 @@ async function startParallelNotificationTools(client: Client): Promise<Record<st
|
|||
// Organize results by caller
|
||||
const resultsByTool: Record<string, CallToolResult> = {};
|
||||
for (const { caller, result } of results) {
|
||||
resultsByTool[caller] = result;
|
||||
resultsByTool[caller] = result as CallToolResult;
|
||||
}
|
||||
|
||||
return resultsByTool;
|
||||
|
|
|
|||
|
|
@ -4,14 +4,8 @@ import { createServer } from 'node:http';
|
|||
import { createInterface } from 'node:readline';
|
||||
import { URL } from 'node:url';
|
||||
|
||||
import type { CallToolRequest, ListToolsRequest, OAuthClientMetadata } from '@modelcontextprotocol/client';
|
||||
import {
|
||||
CallToolResultSchema,
|
||||
Client,
|
||||
ListToolsResultSchema,
|
||||
StreamableHTTPClientTransport,
|
||||
UnauthorizedError
|
||||
} from '@modelcontextprotocol/client';
|
||||
import type { CallToolRequest, CallToolResult, ListToolsRequest, OAuthClientMetadata } from '@modelcontextprotocol/client';
|
||||
import { Client, StreamableHTTPClientTransport, UnauthorizedError } from '@modelcontextprotocol/client';
|
||||
import open from 'open';
|
||||
|
||||
import { InMemoryOAuthClientProvider } from './simpleOAuthClientProvider.js';
|
||||
|
|
@ -262,7 +256,7 @@ class InteractiveOAuthClient {
|
|||
params: {}
|
||||
};
|
||||
|
||||
const result = await this.client.request(request, ListToolsResultSchema);
|
||||
const result = await this.client.request(request);
|
||||
|
||||
if (result.tools && result.tools.length > 0) {
|
||||
console.log('\n📋 Available tools:');
|
||||
|
|
@ -320,7 +314,7 @@ class InteractiveOAuthClient {
|
|||
}
|
||||
};
|
||||
|
||||
const result = await this.client.request(request, CallToolResultSchema);
|
||||
const result = (await this.client.request(request)) as CallToolResult;
|
||||
|
||||
console.log(`\n🔧 Tool '${toolName}' result:`);
|
||||
if (result.content) {
|
||||
|
|
@ -404,7 +398,8 @@ class InteractiveOAuthClient {
|
|||
|
||||
case 'result': {
|
||||
console.log('✓ Completed!');
|
||||
for (const content of message.result.content) {
|
||||
const toolResult = message.result as CallToolResult;
|
||||
for (const content of toolResult.content) {
|
||||
if (content.type === 'text') {
|
||||
console.log(content.text);
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { createInterface } from 'node:readline';
|
|||
|
||||
import type {
|
||||
CallToolRequest,
|
||||
CallToolResult,
|
||||
GetPromptRequest,
|
||||
ListPromptsRequest,
|
||||
ListResourcesRequest,
|
||||
|
|
@ -10,17 +11,11 @@ import type {
|
|||
ResourceLink
|
||||
} from '@modelcontextprotocol/client';
|
||||
import {
|
||||
CallToolResultSchema,
|
||||
Client,
|
||||
getDisplayName,
|
||||
GetPromptResultSchema,
|
||||
InMemoryTaskStore,
|
||||
ListPromptsResultSchema,
|
||||
ListResourcesResultSchema,
|
||||
ListToolsResultSchema,
|
||||
ProtocolError,
|
||||
ProtocolErrorCode,
|
||||
ReadResourceResultSchema,
|
||||
RELATED_TASK_META_KEY,
|
||||
StreamableHTTPClientTransport
|
||||
} from '@modelcontextprotocol/client';
|
||||
|
|
@ -541,13 +536,10 @@ async function connect(url?: string): Promise<void> {
|
|||
console.log('Client disconnected, cannot fetch resources');
|
||||
return;
|
||||
}
|
||||
const resourcesResult = await client.request(
|
||||
{
|
||||
method: 'resources/list',
|
||||
params: {}
|
||||
},
|
||||
ListResourcesResultSchema
|
||||
);
|
||||
const resourcesResult = await client.request({
|
||||
method: 'resources/list',
|
||||
params: {}
|
||||
});
|
||||
console.log('Available resources count:', resourcesResult.resources.length);
|
||||
} catch {
|
||||
console.log('Failed to list resources after change notification');
|
||||
|
|
@ -632,7 +624,7 @@ async function listTools(): Promise<void> {
|
|||
method: 'tools/list',
|
||||
params: {}
|
||||
};
|
||||
const toolsResult = await client.request(toolsRequest, ListToolsResultSchema);
|
||||
const toolsResult = await client.request(toolsRequest);
|
||||
|
||||
console.log('Available tools:');
|
||||
if (toolsResult.tools.length === 0) {
|
||||
|
|
@ -663,7 +655,7 @@ async function callTool(name: string, args: Record<string, unknown>): Promise<vo
|
|||
};
|
||||
|
||||
console.log(`Calling tool '${name}' with args:`, args);
|
||||
const result = await client.request(request, CallToolResultSchema);
|
||||
const result = (await client.request(request)) as CallToolResult;
|
||||
|
||||
console.log('Tool result:');
|
||||
const resourceLinks: ResourceLink[] = [];
|
||||
|
|
@ -767,10 +759,10 @@ async function runNotificationsToolWithResumability(interval: number, count: num
|
|||
console.log(`Updated resumption token: ${event}`);
|
||||
};
|
||||
|
||||
const result = await client.request(request, CallToolResultSchema, {
|
||||
const result = (await client.request(request, {
|
||||
resumptionToken: notificationsToolLastEventId,
|
||||
onresumptiontoken: onLastEventIdUpdate
|
||||
});
|
||||
})) as CallToolResult;
|
||||
|
||||
console.log('Tool result:');
|
||||
for (const item of result.content) {
|
||||
|
|
@ -796,7 +788,7 @@ async function listPrompts(): Promise<void> {
|
|||
method: 'prompts/list',
|
||||
params: {}
|
||||
};
|
||||
const promptsResult = await client.request(promptsRequest, ListPromptsResultSchema);
|
||||
const promptsResult = await client.request(promptsRequest);
|
||||
console.log('Available prompts:');
|
||||
if (promptsResult.prompts.length === 0) {
|
||||
console.log(' No prompts available');
|
||||
|
|
@ -825,7 +817,7 @@ async function getPrompt(name: string, args: Record<string, unknown>): Promise<v
|
|||
}
|
||||
};
|
||||
|
||||
const promptResult = await client.request(promptRequest, GetPromptResultSchema);
|
||||
const promptResult = await client.request(promptRequest);
|
||||
console.log('Prompt template:');
|
||||
for (const [index, msg] of promptResult.messages.entries()) {
|
||||
console.log(` [${index + 1}] ${msg.role}: ${msg.content.type === 'text' ? msg.content.text : JSON.stringify(msg.content)}`);
|
||||
|
|
@ -846,7 +838,7 @@ async function listResources(): Promise<void> {
|
|||
method: 'resources/list',
|
||||
params: {}
|
||||
};
|
||||
const resourcesResult = await client.request(resourcesRequest, ListResourcesResultSchema);
|
||||
const resourcesResult = await client.request(resourcesRequest);
|
||||
|
||||
console.log('Available resources:');
|
||||
if (resourcesResult.resources.length === 0) {
|
||||
|
|
@ -874,7 +866,7 @@ async function readResource(uri: string): Promise<void> {
|
|||
};
|
||||
|
||||
console.log(`Reading resource: ${uri}`);
|
||||
const result = await client.request(request, ReadResourceResultSchema);
|
||||
const result = await client.request(request);
|
||||
|
||||
console.log('Resource contents:');
|
||||
for (const content of result.contents) {
|
||||
|
|
@ -948,7 +940,8 @@ async function callToolTask(name: string, args: Record<string, unknown>): Promis
|
|||
case 'result': {
|
||||
console.log('Task completed!');
|
||||
console.log('Tool result:');
|
||||
for (const item of message.result.content) {
|
||||
const toolResult = message.result as CallToolResult;
|
||||
for (const item of toolResult.content) {
|
||||
if (item.type === 'text') {
|
||||
console.log(` ${item.text}`);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
|
||||
import { createInterface } from 'node:readline';
|
||||
|
||||
import type { CreateMessageRequest, CreateMessageResult, TextContent } from '@modelcontextprotocol/client';
|
||||
import type { CallToolResult, CreateMessageRequest, CreateMessageResult, TextContent } from '@modelcontextprotocol/client';
|
||||
import { Client, ProtocolError, ProtocolErrorCode, StreamableHTTPClientTransport } from '@modelcontextprotocol/client';
|
||||
|
||||
// Create readline interface for user input
|
||||
|
|
@ -135,7 +135,8 @@ async function run(url: string): Promise<void> {
|
|||
break;
|
||||
}
|
||||
case 'result': {
|
||||
console.log(`Result: ${getTextContent(message.result)}`);
|
||||
const toolResult = message.result as CallToolResult;
|
||||
console.log(`Result: ${getTextContent(toolResult)}`);
|
||||
break;
|
||||
}
|
||||
case 'error': {
|
||||
|
|
@ -165,7 +166,8 @@ async function run(url: string): Promise<void> {
|
|||
break;
|
||||
}
|
||||
case 'result': {
|
||||
console.log(`Result:\n${getTextContent(message.result)}`);
|
||||
const toolResult = message.result as CallToolResult;
|
||||
console.log(`Result:\n${getTextContent(toolResult)}`);
|
||||
break;
|
||||
}
|
||||
case 'error': {
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
* Run with: pnpm tsx src/ssePollingClient.ts
|
||||
* Requires: ssePollingExample.ts server running on port 3001
|
||||
*/
|
||||
import { CallToolResultSchema, Client, StreamableHTTPClientTransport } from '@modelcontextprotocol/client';
|
||||
import { Client, StreamableHTTPClientTransport } from '@modelcontextprotocol/client';
|
||||
|
||||
const SERVER_URL = 'http://localhost:3001/mcp';
|
||||
|
||||
|
|
@ -78,7 +78,6 @@ async function main(): Promise<void> {
|
|||
arguments: {}
|
||||
}
|
||||
},
|
||||
CallToolResultSchema,
|
||||
{
|
||||
// Track resumption tokens for debugging
|
||||
onresumptiontoken: token => {
|
||||
|
|
|
|||
|
|
@ -1,11 +1,5 @@
|
|||
import type { CallToolRequest, ListToolsRequest } from '@modelcontextprotocol/client';
|
||||
import {
|
||||
CallToolResultSchema,
|
||||
Client,
|
||||
ListToolsResultSchema,
|
||||
SSEClientTransport,
|
||||
StreamableHTTPClientTransport
|
||||
} from '@modelcontextprotocol/client';
|
||||
import type { CallToolRequest, CallToolResult, ListToolsRequest } from '@modelcontextprotocol/client';
|
||||
import { Client, SSEClientTransport, StreamableHTTPClientTransport } from '@modelcontextprotocol/client';
|
||||
|
||||
/**
|
||||
* Simplified Backwards Compatible MCP Client
|
||||
|
|
@ -135,7 +129,7 @@ async function listTools(client: Client): Promise<void> {
|
|||
method: 'tools/list',
|
||||
params: {}
|
||||
};
|
||||
const toolsResult = await client.request(toolsRequest, ListToolsResultSchema);
|
||||
const toolsResult = await client.request(toolsRequest);
|
||||
|
||||
console.log('Available tools:');
|
||||
if (toolsResult.tools.length === 0) {
|
||||
|
|
@ -168,7 +162,7 @@ async function startNotificationTool(client: Client): Promise<void> {
|
|||
};
|
||||
|
||||
console.log('Calling notification tool...');
|
||||
const result = await client.request(request, CallToolResultSchema);
|
||||
const result = (await client.request(request)) as CallToolResult;
|
||||
|
||||
console.log('Tool result:');
|
||||
for (const item of result.content) {
|
||||
|
|
|
|||
|
|
@ -16,13 +16,7 @@ import type {
|
|||
ReadResourceResult,
|
||||
ResourceLink
|
||||
} from '@modelcontextprotocol/server';
|
||||
import {
|
||||
ElicitResultSchema,
|
||||
InMemoryTaskMessageQueue,
|
||||
InMemoryTaskStore,
|
||||
isInitializeRequest,
|
||||
McpServer
|
||||
} from '@modelcontextprotocol/server';
|
||||
import { InMemoryTaskMessageQueue, InMemoryTaskStore, isInitializeRequest, McpServer } from '@modelcontextprotocol/server';
|
||||
import cors from 'cors';
|
||||
import type { Request, Response } from 'express';
|
||||
import * as z from 'zod/v4';
|
||||
|
|
@ -222,17 +216,14 @@ const getServer = () => {
|
|||
|
||||
try {
|
||||
// Use sendRequest through the ctx parameter to elicit input
|
||||
const result = await ctx.mcpReq.send(
|
||||
{
|
||||
method: 'elicitation/create',
|
||||
params: {
|
||||
mode: 'form',
|
||||
message,
|
||||
requestedSchema
|
||||
}
|
||||
},
|
||||
ElicitResultSchema
|
||||
);
|
||||
const result = await ctx.mcpReq.send({
|
||||
method: 'elicitation/create',
|
||||
params: {
|
||||
mode: 'form',
|
||||
message,
|
||||
requestedSchema
|
||||
}
|
||||
});
|
||||
|
||||
if (result.action === 'accept') {
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ import type {
|
|||
ClientNotification,
|
||||
ClientRequest,
|
||||
ClientResult,
|
||||
CompatibilityCallToolResultSchema,
|
||||
CompleteRequest,
|
||||
GetPromptRequest,
|
||||
Implementation,
|
||||
|
|
@ -476,7 +475,7 @@ export class Client extends Protocol<ClientContext> {
|
|||
return;
|
||||
}
|
||||
try {
|
||||
const result = await this.request(
|
||||
const result = await this._requestWithSchema(
|
||||
{
|
||||
method: 'initialize',
|
||||
params: {
|
||||
|
|
@ -709,22 +708,22 @@ export class Client extends Protocol<ClientContext> {
|
|||
|
||||
/** Sends a ping to the server to check connectivity. */
|
||||
async ping(options?: RequestOptions) {
|
||||
return this.request({ method: 'ping' }, EmptyResultSchema, options);
|
||||
return this._requestWithSchema({ method: 'ping' }, EmptyResultSchema, options);
|
||||
}
|
||||
|
||||
/** Requests argument autocompletion suggestions from the server for a prompt or resource. */
|
||||
async complete(params: CompleteRequest['params'], options?: RequestOptions) {
|
||||
return this.request({ method: 'completion/complete', params }, CompleteResultSchema, options);
|
||||
return this._requestWithSchema({ method: 'completion/complete', params }, CompleteResultSchema, options);
|
||||
}
|
||||
|
||||
/** Sets the minimum severity level for log messages sent by the server. */
|
||||
async setLoggingLevel(level: LoggingLevel, options?: RequestOptions) {
|
||||
return this.request({ method: 'logging/setLevel', params: { level } }, EmptyResultSchema, options);
|
||||
return this._requestWithSchema({ method: 'logging/setLevel', params: { level } }, EmptyResultSchema, options);
|
||||
}
|
||||
|
||||
/** Retrieves a prompt by name from the server, passing the given arguments for template substitution. */
|
||||
async getPrompt(params: GetPromptRequest['params'], options?: RequestOptions) {
|
||||
return this.request({ method: 'prompts/get', params }, GetPromptResultSchema, options);
|
||||
return this._requestWithSchema({ method: 'prompts/get', params }, GetPromptResultSchema, options);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -754,7 +753,7 @@ export class Client extends Protocol<ClientContext> {
|
|||
console.debug('Client.listPrompts() called but server does not advertise prompts capability - returning empty list');
|
||||
return { prompts: [] };
|
||||
}
|
||||
return this.request({ method: 'prompts/list', params }, ListPromptsResultSchema, options);
|
||||
return this._requestWithSchema({ method: 'prompts/list', params }, ListPromptsResultSchema, options);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -784,7 +783,7 @@ export class Client extends Protocol<ClientContext> {
|
|||
console.debug('Client.listResources() called but server does not advertise resources capability - returning empty list');
|
||||
return { resources: [] };
|
||||
}
|
||||
return this.request({ method: 'resources/list', params }, ListResourcesResultSchema, options);
|
||||
return this._requestWithSchema({ method: 'resources/list', params }, ListResourcesResultSchema, options);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -801,22 +800,22 @@ export class Client extends Protocol<ClientContext> {
|
|||
);
|
||||
return { resourceTemplates: [] };
|
||||
}
|
||||
return this.request({ method: 'resources/templates/list', params }, ListResourceTemplatesResultSchema, options);
|
||||
return this._requestWithSchema({ method: 'resources/templates/list', params }, ListResourceTemplatesResultSchema, options);
|
||||
}
|
||||
|
||||
/** Reads the contents of a resource by URI. */
|
||||
async readResource(params: ReadResourceRequest['params'], options?: RequestOptions) {
|
||||
return this.request({ method: 'resources/read', params }, ReadResourceResultSchema, options);
|
||||
return this._requestWithSchema({ method: 'resources/read', params }, ReadResourceResultSchema, options);
|
||||
}
|
||||
|
||||
/** Subscribes to change notifications for a resource. The server must support resource subscriptions. */
|
||||
async subscribeResource(params: SubscribeRequest['params'], options?: RequestOptions) {
|
||||
return this.request({ method: 'resources/subscribe', params }, EmptyResultSchema, options);
|
||||
return this._requestWithSchema({ method: 'resources/subscribe', params }, EmptyResultSchema, options);
|
||||
}
|
||||
|
||||
/** Unsubscribes from change notifications for a resource. */
|
||||
async unsubscribeResource(params: UnsubscribeRequest['params'], options?: RequestOptions) {
|
||||
return this.request({ method: 'resources/unsubscribe', params }, EmptyResultSchema, options);
|
||||
return this._requestWithSchema({ method: 'resources/unsubscribe', params }, EmptyResultSchema, options);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -858,11 +857,7 @@ export class Client extends Protocol<ClientContext> {
|
|||
* }
|
||||
* ```
|
||||
*/
|
||||
async callTool(
|
||||
params: CallToolRequest['params'],
|
||||
resultSchema: typeof CallToolResultSchema | typeof CompatibilityCallToolResultSchema = CallToolResultSchema,
|
||||
options?: RequestOptions
|
||||
) {
|
||||
async callTool(params: CallToolRequest['params'], options?: RequestOptions) {
|
||||
// Guard: required-task tools need experimental API
|
||||
if (this.isToolTaskRequired(params.name)) {
|
||||
throw new ProtocolError(
|
||||
|
|
@ -871,7 +866,7 @@ export class Client extends Protocol<ClientContext> {
|
|||
);
|
||||
}
|
||||
|
||||
const result = await this.request({ method: 'tools/call', params }, resultSchema, options);
|
||||
const result = await this._requestWithSchema({ method: 'tools/call', params }, CallToolResultSchema, options);
|
||||
|
||||
// Check if the tool has an outputSchema
|
||||
const validator = this.getToolOutputValidator(params.name);
|
||||
|
|
@ -988,7 +983,7 @@ export class Client extends Protocol<ClientContext> {
|
|||
console.debug('Client.listTools() called but server does not advertise tools capability - returning empty list');
|
||||
return { tools: [] };
|
||||
}
|
||||
const result = await this.request({ method: 'tools/list', params }, ListToolsResultSchema, options);
|
||||
const result = await this._requestWithSchema({ method: 'tools/list', params }, ListToolsResultSchema, options);
|
||||
|
||||
// Cache the tools and their output schemas for future validation
|
||||
this.cacheToolMetadata(result.tools);
|
||||
|
|
|
|||
|
|
@ -7,8 +7,7 @@
|
|||
* @module
|
||||
*/
|
||||
|
||||
import type { Request, RequestOptions } from '@modelcontextprotocol/core';
|
||||
import { CallToolResultSchema } from '@modelcontextprotocol/core';
|
||||
import type { RequestOptions } from '@modelcontextprotocol/core';
|
||||
|
||||
import type { Client } from '../../client/client.js';
|
||||
|
||||
|
|
@ -44,9 +43,9 @@ async function ExperimentalClientTasks_callToolStream(client: Client) {
|
|||
/**
|
||||
* Example: Using requestStream to consume task lifecycle events for any request type.
|
||||
*/
|
||||
async function ExperimentalClientTasks_requestStream(client: Client, request: Request, options: RequestOptions) {
|
||||
async function ExperimentalClientTasks_requestStream(client: Client, options: RequestOptions) {
|
||||
//#region ExperimentalClientTasks_requestStream
|
||||
const stream = client.experimental.tasks.requestStream(request, CallToolResultSchema, options);
|
||||
const stream = client.experimental.tasks.requestStream({ method: 'tools/call', params: { name: 'my-tool', arguments: {} } }, options);
|
||||
for await (const message of stream) {
|
||||
switch (message.type) {
|
||||
case 'taskCreated': {
|
||||
|
|
|
|||
|
|
@ -8,15 +8,18 @@
|
|||
import type {
|
||||
AnyObjectSchema,
|
||||
CallToolRequest,
|
||||
CallToolResult,
|
||||
CancelTaskResult,
|
||||
CreateTaskResult,
|
||||
GetTaskResult,
|
||||
ListTasksResult,
|
||||
Request,
|
||||
RequestMethod,
|
||||
RequestOptions,
|
||||
ResponseMessage,
|
||||
ResultTypeMap,
|
||||
SchemaOutput
|
||||
} from '@modelcontextprotocol/core';
|
||||
import { CallToolResultSchema, ProtocolError, ProtocolErrorCode } from '@modelcontextprotocol/core';
|
||||
import { ProtocolError, ProtocolErrorCode } from '@modelcontextprotocol/core';
|
||||
|
||||
import type { Client } from '../../client/client.js';
|
||||
|
||||
|
|
@ -25,11 +28,10 @@ import type { Client } from '../../client/client.js';
|
|||
* @internal
|
||||
*/
|
||||
interface ClientInternal {
|
||||
requestStream<T extends AnyObjectSchema>(
|
||||
request: Request,
|
||||
resultSchema: T,
|
||||
requestStream<M extends RequestMethod>(
|
||||
request: { method: M; params?: Record<string, unknown> },
|
||||
options?: RequestOptions
|
||||
): AsyncGenerator<ResponseMessage<SchemaOutput<T>>, void, void>;
|
||||
): AsyncGenerator<ResponseMessage<ResultTypeMap[M]>, void, void>;
|
||||
isToolTask(toolName: string): boolean;
|
||||
getToolOutputValidator(toolName: string): ((data: unknown) => { valid: boolean; errorMessage?: string }) | undefined;
|
||||
}
|
||||
|
|
@ -90,7 +92,7 @@ export class ExperimentalClientTasks {
|
|||
async *callToolStream(
|
||||
params: CallToolRequest['params'],
|
||||
options?: RequestOptions
|
||||
): AsyncGenerator<ResponseMessage<SchemaOutput<typeof CallToolResultSchema>>, void, void> {
|
||||
): AsyncGenerator<ResponseMessage<CallToolResult | CreateTaskResult>, void, void> {
|
||||
// Access Client's internal methods
|
||||
const clientInternal = this._client as unknown as ClientInternal;
|
||||
|
||||
|
|
@ -102,7 +104,7 @@ export class ExperimentalClientTasks {
|
|||
task: options?.task ?? (clientInternal.isToolTask(params.name) ? {} : undefined)
|
||||
};
|
||||
|
||||
const stream = clientInternal.requestStream({ method: 'tools/call', params }, CallToolResultSchema, optionsWithTask);
|
||||
const stream = clientInternal.requestStream({ method: 'tools/call', params }, optionsWithTask);
|
||||
|
||||
// Get the validator for this tool (if it has an output schema)
|
||||
const validator = clientInternal.getToolOutputValidator(params.name);
|
||||
|
|
@ -110,8 +112,9 @@ export class ExperimentalClientTasks {
|
|||
// Iterate through the stream and validate the final result if needed
|
||||
for await (const message of stream) {
|
||||
// If this is a result message and the tool has an output schema, validate it
|
||||
if (message.type === 'result' && validator) {
|
||||
const result = message.result;
|
||||
// Only validate CallToolResult (has 'content'), not CreateTaskResult (has 'task')
|
||||
if (message.type === 'result' && validator && 'content' in message.result) {
|
||||
const result = message.result as CallToolResult;
|
||||
|
||||
// If tool has outputSchema, it MUST return structuredContent (unless it's an error)
|
||||
if (!result.structuredContent && !result.isError) {
|
||||
|
|
@ -245,7 +248,7 @@ export class ExperimentalClientTasks {
|
|||
*
|
||||
* @example
|
||||
* ```ts source="./client.examples.ts#ExperimentalClientTasks_requestStream"
|
||||
* const stream = client.experimental.tasks.requestStream(request, CallToolResultSchema, options);
|
||||
* const stream = client.experimental.tasks.requestStream({ method: 'tools/call', params: { name: 'my-tool', arguments: {} } }, options);
|
||||
* for await (const message of stream) {
|
||||
* switch (message.type) {
|
||||
* case 'taskCreated': {
|
||||
|
|
@ -269,25 +272,16 @@ export class ExperimentalClientTasks {
|
|||
* ```
|
||||
*
|
||||
* @param request - The request to send
|
||||
* @param resultSchema - Zod schema for validating the result
|
||||
* @param options - Optional request options (timeout, signal, task creation params, etc.)
|
||||
* @returns AsyncGenerator that yields {@linkcode ResponseMessage} objects
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
requestStream<T extends AnyObjectSchema>(
|
||||
request: Request,
|
||||
resultSchema: T,
|
||||
requestStream<M extends RequestMethod>(
|
||||
request: { method: M; params?: Record<string, unknown> },
|
||||
options?: RequestOptions
|
||||
): AsyncGenerator<ResponseMessage<SchemaOutput<T>>, void, void> {
|
||||
): AsyncGenerator<ResponseMessage<ResultTypeMap[M]>, void, void> {
|
||||
// Delegate to the client's underlying Protocol method
|
||||
type ClientWithRequestStream = {
|
||||
requestStream<U extends AnyObjectSchema>(
|
||||
request: Request,
|
||||
resultSchema: U,
|
||||
options?: RequestOptions
|
||||
): AsyncGenerator<ResponseMessage<SchemaOutput<U>>, void, void>;
|
||||
};
|
||||
return (this._client as unknown as ClientWithRequestStream).requestStream(request, resultSchema, options);
|
||||
return (this._client as unknown as ClientInternal).requestStream(request, options);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ import type {
|
|||
RequestMethod,
|
||||
RequestTypeMap,
|
||||
Result,
|
||||
ResultTypeMap,
|
||||
ServerCapabilities,
|
||||
Task,
|
||||
TaskCreationParams,
|
||||
|
|
@ -44,6 +45,7 @@ import {
|
|||
CreateTaskResultSchema,
|
||||
getNotificationSchema,
|
||||
getRequestSchema,
|
||||
getResultSchema,
|
||||
GetTaskResultSchema,
|
||||
isJSONRPCErrorResponse,
|
||||
isJSONRPCNotification,
|
||||
|
|
@ -297,7 +299,10 @@ export type BaseContext = {
|
|||
*
|
||||
* This is used by certain transports to correctly associate related messages.
|
||||
*/
|
||||
send: <U extends AnySchema>(request: Request, resultSchema: U, options?: TaskRequestOptions) => Promise<SchemaOutput<U>>;
|
||||
send: <M extends RequestMethod>(
|
||||
request: { method: M; params?: Record<string, unknown> },
|
||||
options?: TaskRequestOptions
|
||||
) => Promise<ResultTypeMap[M]>;
|
||||
|
||||
/**
|
||||
* Sends a notification that relates to the current request being handled.
|
||||
|
|
@ -808,7 +813,7 @@ export abstract class Protocol<ContextT extends BaseContext> {
|
|||
method: request.method,
|
||||
_meta: request.params?._meta,
|
||||
signal: abortController.signal,
|
||||
send: async (r, resultSchema, options?) => {
|
||||
send: async (r, options?) => {
|
||||
const requestOptions: RequestOptions = { ...options, relatedRequestId: request.id };
|
||||
if (relatedTaskId && !requestOptions.relatedTask) {
|
||||
requestOptions.relatedTask = { taskId: relatedTaskId };
|
||||
|
|
@ -817,7 +822,7 @@ export abstract class Protocol<ContextT extends BaseContext> {
|
|||
if (effectiveTaskId && taskStore) {
|
||||
await taskStore.updateTaskStatus(effectiveTaskId, 'input_required');
|
||||
}
|
||||
return await this.request(r, resultSchema, requestOptions);
|
||||
return await this.request(r, requestOptions);
|
||||
},
|
||||
notify: async notification => {
|
||||
const notificationOptions: NotificationOptions = { relatedRequestId: request.id };
|
||||
|
|
@ -1030,12 +1035,32 @@ export abstract class Protocol<ContextT extends BaseContext> {
|
|||
protected abstract assertTaskHandlerCapability(method: string): void;
|
||||
|
||||
/**
|
||||
* Sends a request and returns an AsyncGenerator that yields response messages.
|
||||
* Sends a request and returns an AsyncGenerator that yields response messages,
|
||||
* resolving the result schema automatically from the method name.
|
||||
* The generator is guaranteed to end with either a `'result'` or `'error'` message.
|
||||
*
|
||||
* @experimental Use `client.experimental.tasks.requestStream()` to access this method.
|
||||
*/
|
||||
protected async *requestStream<T extends AnyObjectSchema>(
|
||||
protected async *requestStream<M extends RequestMethod>(
|
||||
request: { method: M; params?: Record<string, unknown> },
|
||||
options?: RequestOptions
|
||||
): AsyncGenerator<ResponseMessage<ResultTypeMap[M]>, void, void> {
|
||||
const resultSchema = getResultSchema(request.method) as unknown as AnyObjectSchema;
|
||||
yield* this._requestStreamWithSchema(request as Request, resultSchema, options) as AsyncGenerator<
|
||||
ResponseMessage<ResultTypeMap[M]>,
|
||||
void,
|
||||
void
|
||||
>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a request and returns an AsyncGenerator that yields response messages,
|
||||
* using the provided schema for validation.
|
||||
*
|
||||
* This is the internal implementation used by SDK methods that need to specify
|
||||
* a particular result schema.
|
||||
*/
|
||||
protected async *_requestStreamWithSchema<T extends AnyObjectSchema>(
|
||||
request: Request,
|
||||
resultSchema: T,
|
||||
options?: RequestOptions
|
||||
|
|
@ -1045,7 +1070,7 @@ export abstract class Protocol<ContextT extends BaseContext> {
|
|||
// For non-task requests, just yield the result
|
||||
if (!task) {
|
||||
try {
|
||||
const result = await this.request(request, resultSchema, options);
|
||||
const result = await this._requestWithSchema(request, resultSchema, options);
|
||||
yield { type: 'result', result };
|
||||
} catch (error) {
|
||||
yield {
|
||||
|
|
@ -1061,7 +1086,7 @@ export abstract class Protocol<ContextT extends BaseContext> {
|
|||
let taskId: string | undefined;
|
||||
try {
|
||||
// Send the request and get the CreateTaskResult
|
||||
const createResult = await this.request(request, CreateTaskResultSchema, options);
|
||||
const createResult = await this._requestWithSchema(request, CreateTaskResultSchema, options);
|
||||
|
||||
// Extract taskId from the result
|
||||
if (createResult.task) {
|
||||
|
|
@ -1132,11 +1157,30 @@ export abstract class Protocol<ContextT extends BaseContext> {
|
|||
}
|
||||
|
||||
/**
|
||||
* Sends a request and waits for a response.
|
||||
* Sends a request and waits for a response, resolving the result schema
|
||||
* automatically from the method name.
|
||||
*
|
||||
* Do not use this method to emit notifications! Use {@linkcode Protocol.notification | notification()} instead.
|
||||
*/
|
||||
request<T extends AnySchema>(request: Request, resultSchema: T, options?: RequestOptions): Promise<SchemaOutput<T>> {
|
||||
request<M extends RequestMethod>(
|
||||
request: { method: M; params?: Record<string, unknown> },
|
||||
options?: RequestOptions
|
||||
): Promise<ResultTypeMap[M]> {
|
||||
const resultSchema = getResultSchema(request.method);
|
||||
return this._requestWithSchema(request as Request, resultSchema, options) as Promise<ResultTypeMap[M]>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a request and waits for a response, using the provided schema for validation.
|
||||
*
|
||||
* This is the internal implementation used by SDK methods that need to specify
|
||||
* a particular result schema (e.g., for compatibility or task-specific schemas).
|
||||
*/
|
||||
protected _requestWithSchema<T extends AnySchema>(
|
||||
request: Request,
|
||||
resultSchema: T,
|
||||
options?: RequestOptions
|
||||
): Promise<SchemaOutput<T>> {
|
||||
const { relatedRequestId, resumptionToken, onresumptiontoken, task, relatedTask } = options ?? {};
|
||||
|
||||
// Send the request
|
||||
|
|
@ -1299,7 +1343,7 @@ export abstract class Protocol<ContextT extends BaseContext> {
|
|||
* @experimental Use `client.experimental.tasks.getTask()` to access this method.
|
||||
*/
|
||||
protected async getTask(params: GetTaskRequest['params'], options?: RequestOptions): Promise<GetTaskResult> {
|
||||
return this.request({ method: 'tasks/get', params }, GetTaskResultSchema, options);
|
||||
return this._requestWithSchema({ method: 'tasks/get', params }, GetTaskResultSchema, options);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -1312,7 +1356,7 @@ export abstract class Protocol<ContextT extends BaseContext> {
|
|||
resultSchema: T,
|
||||
options?: RequestOptions
|
||||
): Promise<SchemaOutput<T>> {
|
||||
return this.request({ method: 'tasks/result', params }, resultSchema, options);
|
||||
return this._requestWithSchema({ method: 'tasks/result', params }, resultSchema, options);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -1321,7 +1365,7 @@ export abstract class Protocol<ContextT extends BaseContext> {
|
|||
* @experimental Use `client.experimental.tasks.listTasks()` to access this method.
|
||||
*/
|
||||
protected async listTasks(params?: { cursor?: string }, options?: RequestOptions): Promise<SchemaOutput<typeof ListTasksResultSchema>> {
|
||||
return this.request({ method: 'tasks/list', params }, ListTasksResultSchema, options);
|
||||
return this._requestWithSchema({ method: 'tasks/list', params }, ListTasksResultSchema, options);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -1330,7 +1374,7 @@ export abstract class Protocol<ContextT extends BaseContext> {
|
|||
* @experimental Use `client.experimental.tasks.cancelTask()` to access this method.
|
||||
*/
|
||||
protected async cancelTask(params: { taskId: string }, options?: RequestOptions): Promise<SchemaOutput<typeof CancelTaskResultSchema>> {
|
||||
return this.request({ method: 'tasks/cancel', params }, CancelTaskResultSchema, options);
|
||||
return this._requestWithSchema({ method: 'tasks/cancel', params }, CancelTaskResultSchema, options);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -2638,7 +2638,39 @@ export type ResultTypeMap = {
|
|||
'tasks/cancel': CancelTaskResult;
|
||||
};
|
||||
|
||||
/* Runtime schema lookup */
|
||||
/* Runtime schema lookup — result schemas by method */
|
||||
const resultSchemas: Record<string, z.core.$ZodType> = {
|
||||
ping: EmptyResultSchema,
|
||||
initialize: InitializeResultSchema,
|
||||
'completion/complete': CompleteResultSchema,
|
||||
'logging/setLevel': EmptyResultSchema,
|
||||
'prompts/get': GetPromptResultSchema,
|
||||
'prompts/list': ListPromptsResultSchema,
|
||||
'resources/list': ListResourcesResultSchema,
|
||||
'resources/templates/list': ListResourceTemplatesResultSchema,
|
||||
'resources/read': ReadResourceResultSchema,
|
||||
'resources/subscribe': EmptyResultSchema,
|
||||
'resources/unsubscribe': EmptyResultSchema,
|
||||
'tools/call': z.union([CallToolResultSchema, CreateTaskResultSchema]),
|
||||
'tools/list': ListToolsResultSchema,
|
||||
'sampling/createMessage': z.union([CreateMessageResultWithToolsSchema, CreateTaskResultSchema]),
|
||||
'elicitation/create': z.union([ElicitResultSchema, CreateTaskResultSchema]),
|
||||
'roots/list': ListRootsResultSchema,
|
||||
'tasks/get': GetTaskResultSchema,
|
||||
'tasks/result': ResultSchema,
|
||||
'tasks/list': ListTasksResultSchema,
|
||||
'tasks/cancel': CancelTaskResultSchema
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the Zod schema for validating results of a given request method.
|
||||
* @see getRequestSchema for explanation of the internal type assertion.
|
||||
*/
|
||||
export function getResultSchema<M extends RequestMethod>(method: M): z.ZodType<ResultTypeMap[M]> {
|
||||
return resultSchemas[method] as unknown as z.ZodType<ResultTypeMap[M]>;
|
||||
}
|
||||
|
||||
/* Runtime schema lookup — request schemas by method */
|
||||
type RequestSchemaType = (typeof ClientRequestSchema.options)[number] | (typeof ServerRequestSchema.options)[number];
|
||||
type NotificationSchemaType = (typeof ClientNotificationSchema.options)[number] | (typeof ServerNotificationSchema.options)[number];
|
||||
|
||||
|
|
|
|||
|
|
@ -41,10 +41,15 @@ interface TestProtocol {
|
|||
_taskProgressTokens: Map<string, number>;
|
||||
_clearTaskQueue: (taskId: string, sessionId?: string) => Promise<void>;
|
||||
requestTaskStore: (request: Request, authInfo: unknown) => TaskStore;
|
||||
// Protected task methods (exposed for testing)
|
||||
// Protected methods (exposed for testing)
|
||||
_requestWithSchema: <T extends ZodType>(request: Request, resultSchema: T, options?: unknown) => Promise<z.output<T>>;
|
||||
listTasks: (params?: { cursor?: string }) => Promise<{ tasks: Task[]; nextCursor?: string }>;
|
||||
cancelTask: (params: { taskId: string }) => Promise<Result>;
|
||||
requestStream: <T extends Result>(request: Request, schema: ZodType<T>, options?: unknown) => AsyncGenerator<ResponseMessage<T>>;
|
||||
_requestStreamWithSchema: <T extends Result>(
|
||||
request: Request,
|
||||
schema: ZodType<T>,
|
||||
options?: unknown
|
||||
) => AsyncGenerator<ResponseMessage<T>>;
|
||||
}
|
||||
|
||||
// Mock Transport class
|
||||
|
|
@ -149,6 +154,15 @@ function assertQueuedRequest(o?: QueuedMessage): asserts o is QueuedRequest {
|
|||
expect(o?.type).toBe('request');
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to call the protected _requestWithSchema method from tests that
|
||||
* use custom method names not present in RequestMethod.
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
function testRequest(proto: Protocol<BaseContext>, request: Request, resultSchema: ZodType, options?: any) {
|
||||
return (proto as unknown as TestProtocol)._requestWithSchema(request, resultSchema, options);
|
||||
}
|
||||
|
||||
describe('protocol tests', () => {
|
||||
let protocol: Protocol<BaseContext>;
|
||||
let transport: MockTransport;
|
||||
|
|
@ -176,7 +190,7 @@ describe('protocol tests', () => {
|
|||
const mockSchema: ZodType<{ result: string }> = z.object({
|
||||
result: z.string()
|
||||
});
|
||||
await protocol.request(request, mockSchema, {
|
||||
await testRequest(protocol, request, mockSchema, {
|
||||
timeout: 0
|
||||
});
|
||||
} catch (error) {
|
||||
|
|
@ -230,13 +244,11 @@ describe('protocol tests', () => {
|
|||
const onProgressMock = vi.fn();
|
||||
|
||||
// Start request but don't await - we're testing the sent message
|
||||
void protocol
|
||||
.request(request, mockSchema, {
|
||||
onprogress: onProgressMock
|
||||
})
|
||||
.catch(() => {
|
||||
// May not complete, ignore error
|
||||
});
|
||||
void testRequest(protocol, request, mockSchema, {
|
||||
onprogress: onProgressMock
|
||||
}).catch(() => {
|
||||
// May not complete, ignore error
|
||||
});
|
||||
|
||||
expect(sendSpy).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
|
|
@ -270,13 +282,11 @@ describe('protocol tests', () => {
|
|||
const onProgressMock = vi.fn();
|
||||
|
||||
// Start request but don't await - we're testing the sent message
|
||||
void protocol
|
||||
.request(request, mockSchema, {
|
||||
onprogress: onProgressMock
|
||||
})
|
||||
.catch(() => {
|
||||
// May not complete, ignore error
|
||||
});
|
||||
void testRequest(protocol, request, mockSchema, {
|
||||
onprogress: onProgressMock
|
||||
}).catch(() => {
|
||||
// May not complete, ignore error
|
||||
});
|
||||
|
||||
expect(sendSpy).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
|
|
@ -310,7 +320,7 @@ describe('protocol tests', () => {
|
|||
});
|
||||
|
||||
// Start request but don't await - we're testing the sent message
|
||||
void protocol.request(request, mockSchema).catch(() => {
|
||||
void testRequest(protocol, request, mockSchema).catch(() => {
|
||||
// May not complete, ignore error
|
||||
});
|
||||
|
||||
|
|
@ -341,13 +351,11 @@ describe('protocol tests', () => {
|
|||
const onProgressMock = vi.fn();
|
||||
|
||||
// Start request but don't await - we're testing the sent message
|
||||
void protocol
|
||||
.request(request, mockSchema, {
|
||||
onprogress: onProgressMock
|
||||
})
|
||||
.catch(() => {
|
||||
// May not complete, ignore error
|
||||
});
|
||||
void testRequest(protocol, request, mockSchema, {
|
||||
onprogress: onProgressMock
|
||||
}).catch(() => {
|
||||
// May not complete, ignore error
|
||||
});
|
||||
|
||||
expect(sendSpy).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
|
|
@ -380,7 +388,7 @@ describe('protocol tests', () => {
|
|||
result: z.string()
|
||||
});
|
||||
const onProgressMock = vi.fn();
|
||||
const requestPromise = protocol.request(request, mockSchema, {
|
||||
const requestPromise = testRequest(protocol, request, mockSchema, {
|
||||
timeout: 1000,
|
||||
resetTimeoutOnProgress: false,
|
||||
onprogress: onProgressMock
|
||||
|
|
@ -418,7 +426,7 @@ describe('protocol tests', () => {
|
|||
result: z.string()
|
||||
});
|
||||
const onProgressMock = vi.fn();
|
||||
const requestPromise = protocol.request(request, mockSchema, {
|
||||
const requestPromise = testRequest(protocol, request, mockSchema, {
|
||||
timeout: 1000,
|
||||
resetTimeoutOnProgress: true,
|
||||
onprogress: onProgressMock
|
||||
|
|
@ -459,7 +467,7 @@ describe('protocol tests', () => {
|
|||
result: z.string()
|
||||
});
|
||||
const onProgressMock = vi.fn();
|
||||
const requestPromise = protocol.request(request, mockSchema, {
|
||||
const requestPromise = testRequest(protocol, request, mockSchema, {
|
||||
timeout: 1000,
|
||||
maxTotalTimeout: 150,
|
||||
resetTimeoutOnProgress: true,
|
||||
|
|
@ -506,7 +514,7 @@ describe('protocol tests', () => {
|
|||
const mockSchema: ZodType<{ result: string }> = z.object({
|
||||
result: z.string()
|
||||
});
|
||||
const requestPromise = protocol.request(request, mockSchema, {
|
||||
const requestPromise = testRequest(protocol, request, mockSchema, {
|
||||
timeout: 100,
|
||||
resetTimeoutOnProgress: true
|
||||
});
|
||||
|
|
@ -521,7 +529,7 @@ describe('protocol tests', () => {
|
|||
result: z.string()
|
||||
});
|
||||
const onProgressMock = vi.fn();
|
||||
const requestPromise = protocol.request(request, mockSchema, {
|
||||
const requestPromise = testRequest(protocol, request, mockSchema, {
|
||||
timeout: 1000,
|
||||
resetTimeoutOnProgress: true,
|
||||
onprogress: onProgressMock
|
||||
|
|
@ -566,7 +574,7 @@ describe('protocol tests', () => {
|
|||
});
|
||||
const onProgressMock = vi.fn();
|
||||
|
||||
const requestPromise = protocol.request(request, mockSchema, {
|
||||
const requestPromise = testRequest(protocol, request, mockSchema, {
|
||||
timeout: 1000,
|
||||
onprogress: onProgressMock
|
||||
});
|
||||
|
|
@ -1060,16 +1068,14 @@ describe('Task-based execution', () => {
|
|||
content: z.array(z.object({ type: z.literal('text'), text: z.string() }))
|
||||
});
|
||||
|
||||
void protocol
|
||||
.request(request, resultSchema, {
|
||||
task: {
|
||||
ttl: 30000,
|
||||
pollInterval: 1000
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
// May not complete, ignore error
|
||||
});
|
||||
void testRequest(protocol, request, resultSchema, {
|
||||
task: {
|
||||
ttl: 30000,
|
||||
pollInterval: 1000
|
||||
}
|
||||
}).catch(() => {
|
||||
// May not complete, ignore error
|
||||
});
|
||||
|
||||
expect(sendSpy).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
|
|
@ -1103,15 +1109,13 @@ describe('Task-based execution', () => {
|
|||
content: z.array(z.object({ type: z.literal('text'), text: z.string() }))
|
||||
});
|
||||
|
||||
void protocol
|
||||
.request(request, resultSchema, {
|
||||
task: {
|
||||
ttl: 60000
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
// May not complete, ignore error
|
||||
});
|
||||
void testRequest(protocol, request, resultSchema, {
|
||||
task: {
|
||||
ttl: 60000
|
||||
}
|
||||
}).catch(() => {
|
||||
// May not complete, ignore error
|
||||
});
|
||||
|
||||
expect(sendSpy).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
|
|
@ -1141,7 +1145,7 @@ describe('Task-based execution', () => {
|
|||
content: z.array(z.object({ type: z.literal('text'), text: z.string() }))
|
||||
});
|
||||
|
||||
const resultPromise = protocol.request(request, resultSchema, {
|
||||
const resultPromise = testRequest(protocol, request, resultSchema, {
|
||||
task: {
|
||||
ttl: 30000
|
||||
}
|
||||
|
|
@ -1164,15 +1168,13 @@ describe('Task-based execution', () => {
|
|||
const resultSchema = z.object({});
|
||||
|
||||
// Start the request (don't await completion, just let it send)
|
||||
void protocol
|
||||
.request(request, resultSchema, {
|
||||
relatedTask: {
|
||||
taskId: 'parent-task-123'
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
// May not complete, ignore error
|
||||
});
|
||||
void testRequest(protocol, request, resultSchema, {
|
||||
relatedTask: {
|
||||
taskId: 'parent-task-123'
|
||||
}
|
||||
}).catch(() => {
|
||||
// May not complete, ignore error
|
||||
});
|
||||
|
||||
// Wait a bit for the request to be queued
|
||||
await new Promise(resolve => setTimeout(resolve, 10));
|
||||
|
|
@ -1230,20 +1232,18 @@ describe('Task-based execution', () => {
|
|||
});
|
||||
|
||||
// Start the request (don't await completion, just let it send)
|
||||
void protocol
|
||||
.request(request, resultSchema, {
|
||||
task: {
|
||||
ttl: 60000,
|
||||
pollInterval: 1000
|
||||
},
|
||||
relatedTask: {
|
||||
taskId: 'parent-task'
|
||||
},
|
||||
onprogress: vi.fn()
|
||||
})
|
||||
.catch(() => {
|
||||
// May not complete, ignore error
|
||||
});
|
||||
void testRequest(protocol, request, resultSchema, {
|
||||
task: {
|
||||
ttl: 60000,
|
||||
pollInterval: 1000
|
||||
},
|
||||
relatedTask: {
|
||||
taskId: 'parent-task'
|
||||
},
|
||||
onprogress: vi.fn()
|
||||
}).catch(() => {
|
||||
// May not complete, ignore error
|
||||
});
|
||||
|
||||
// Wait a bit for the request to be queued
|
||||
await new Promise(resolve => setTimeout(resolve, 10));
|
||||
|
|
@ -2482,14 +2482,12 @@ describe('Progress notification support for tasks', () => {
|
|||
});
|
||||
|
||||
// Start a task-augmented request with progress callback
|
||||
void protocol
|
||||
.request(request, resultSchema, {
|
||||
task: { ttl: 60000 },
|
||||
onprogress: progressCallback
|
||||
})
|
||||
.catch(() => {
|
||||
// May not complete, ignore error
|
||||
});
|
||||
void testRequest(protocol, request, resultSchema, {
|
||||
task: { ttl: 60000 },
|
||||
onprogress: progressCallback
|
||||
}).catch(() => {
|
||||
// May not complete, ignore error
|
||||
});
|
||||
|
||||
// Wait a bit for the request to be sent
|
||||
await new Promise(resolve => setTimeout(resolve, 10));
|
||||
|
|
@ -2596,14 +2594,12 @@ describe('Progress notification support for tasks', () => {
|
|||
});
|
||||
|
||||
// Start a task-augmented request with progress callback
|
||||
void protocol
|
||||
.request(request, resultSchema, {
|
||||
task: { ttl: 60000 },
|
||||
onprogress: progressCallback
|
||||
})
|
||||
.catch(() => {
|
||||
// May not complete, ignore error
|
||||
});
|
||||
void testRequest(protocol, request, resultSchema, {
|
||||
task: { ttl: 60000 },
|
||||
onprogress: progressCallback
|
||||
}).catch(() => {
|
||||
// May not complete, ignore error
|
||||
});
|
||||
|
||||
// Wait a bit for the request to be sent
|
||||
await new Promise(resolve => setTimeout(resolve, 10));
|
||||
|
|
@ -2716,7 +2712,7 @@ describe('Progress notification support for tasks', () => {
|
|||
})
|
||||
});
|
||||
|
||||
void protocol.request(request, resultSchema, {
|
||||
void testRequest(protocol, request, resultSchema, {
|
||||
task: { ttl: 60000 },
|
||||
onprogress: progressCallback
|
||||
});
|
||||
|
|
@ -2817,7 +2813,7 @@ describe('Progress notification support for tasks', () => {
|
|||
})
|
||||
});
|
||||
|
||||
void protocol.request(request, resultSchema, {
|
||||
void testRequest(protocol, request, resultSchema, {
|
||||
task: { ttl: 60000 },
|
||||
onprogress: progressCallback
|
||||
});
|
||||
|
|
@ -2915,7 +2911,7 @@ describe('Progress notification support for tasks', () => {
|
|||
})
|
||||
});
|
||||
|
||||
void protocol.request(request, resultSchema, {
|
||||
void testRequest(protocol, request, resultSchema, {
|
||||
task: { ttl: 60000 },
|
||||
onprogress: progressCallback
|
||||
});
|
||||
|
|
@ -2986,7 +2982,7 @@ describe('Progress notification support for tasks', () => {
|
|||
|
||||
const onProgressMock = vi.fn();
|
||||
|
||||
void protocol.request(request, resultSchema, {
|
||||
void testRequest(protocol, request, resultSchema, {
|
||||
task: {
|
||||
ttl: 60000
|
||||
},
|
||||
|
|
@ -3011,7 +3007,7 @@ describe('Progress notification support for tasks', () => {
|
|||
|
||||
const onProgressMock = vi.fn();
|
||||
|
||||
void protocol.request(request, resultSchema, {
|
||||
void testRequest(protocol, request, resultSchema, {
|
||||
task: {
|
||||
ttl: 30000
|
||||
},
|
||||
|
|
@ -3061,7 +3057,7 @@ describe('Progress notification support for tasks', () => {
|
|||
|
||||
const onProgressMock = vi.fn();
|
||||
|
||||
void protocol.request(request, resultSchema, {
|
||||
void testRequest(protocol, request, resultSchema, {
|
||||
task: {
|
||||
ttl: 30000
|
||||
},
|
||||
|
|
@ -3375,7 +3371,8 @@ describe('Message interception for task-related requests', () => {
|
|||
const task = await taskStore.createTask({ ttl: 60000 }, 'test-request-1', { method: 'tools/call', params: {} });
|
||||
|
||||
// Send a request with related task metadata (don't await - we're testing queuing)
|
||||
const requestPromise = server.request(
|
||||
const requestPromise = testRequest(
|
||||
server,
|
||||
{
|
||||
method: 'ping',
|
||||
params: {}
|
||||
|
|
@ -3427,7 +3424,8 @@ describe('Message interception for task-related requests', () => {
|
|||
await server.connect(transport);
|
||||
|
||||
// Send a request without related task metadata
|
||||
const requestPromise = server.request(
|
||||
const requestPromise = testRequest(
|
||||
server,
|
||||
{
|
||||
method: 'ping',
|
||||
params: {}
|
||||
|
|
@ -3472,7 +3470,8 @@ describe('Message interception for task-related requests', () => {
|
|||
const task = await taskStore.createTask({ ttl: 60000 }, 'test-request-1', { method: 'tools/call', params: {} });
|
||||
|
||||
// Send a request with related task metadata
|
||||
const requestPromise = server.request(
|
||||
const requestPromise = testRequest(
|
||||
server,
|
||||
{
|
||||
method: 'ping',
|
||||
params: {}
|
||||
|
|
@ -3528,7 +3527,8 @@ describe('Message interception for task-related requests', () => {
|
|||
const task = await taskStore.createTask({ ttl: 60000 }, 'test-request-1', { method: 'tools/call', params: {} });
|
||||
|
||||
// Send a request with related task metadata
|
||||
const requestPromise = server.request(
|
||||
const requestPromise = testRequest(
|
||||
server,
|
||||
{
|
||||
method: 'ping',
|
||||
params: {}
|
||||
|
|
@ -3601,7 +3601,8 @@ describe('Message interception for task-related requests', () => {
|
|||
const task = await taskStore.createTask({ ttl: 60000 }, 'test-request-1', { method: 'tools/call', params: {} });
|
||||
|
||||
// Send a request with related task metadata
|
||||
void server.request(
|
||||
void testRequest(
|
||||
server,
|
||||
{
|
||||
method: 'ping',
|
||||
params: {}
|
||||
|
|
@ -3677,26 +3678,26 @@ describe('Message interception for task-related requests', () => {
|
|||
// Fill the queue to max capacity (100 messages)
|
||||
const promises: Promise<unknown>[] = [];
|
||||
for (let i = 0; i < 100; i++) {
|
||||
const promise = server
|
||||
.request(
|
||||
{
|
||||
method: 'ping',
|
||||
params: {}
|
||||
},
|
||||
z.object({}),
|
||||
{
|
||||
relatedTask: { taskId: task.taskId }
|
||||
}
|
||||
)
|
||||
.catch(() => {
|
||||
// Requests will remain pending until task completes or fails
|
||||
});
|
||||
const promise = testRequest(
|
||||
server,
|
||||
{
|
||||
method: 'ping',
|
||||
params: {}
|
||||
},
|
||||
z.object({}),
|
||||
{
|
||||
relatedTask: { taskId: task.taskId }
|
||||
}
|
||||
).catch(() => {
|
||||
// Requests will remain pending until task completes or fails
|
||||
});
|
||||
promises.push(promise);
|
||||
}
|
||||
|
||||
// Try to add one more request - should throw an error
|
||||
await expect(
|
||||
server.request(
|
||||
testRequest(
|
||||
server,
|
||||
{
|
||||
method: 'ping',
|
||||
params: {}
|
||||
|
|
@ -3766,7 +3767,8 @@ describe('Message Interception', () => {
|
|||
const mockSchema = z.object({ result: z.string() });
|
||||
|
||||
// Send a request with relatedTask metadata
|
||||
const requestPromise = protocol.request(
|
||||
const requestPromise = testRequest(
|
||||
protocol,
|
||||
{
|
||||
method: 'test/request',
|
||||
params: { data: 'test' }
|
||||
|
|
@ -3969,7 +3971,8 @@ describe('Message Interception', () => {
|
|||
const sendSpy = vi.spyOn(transport, 'send');
|
||||
|
||||
// Send a request without relatedTask metadata
|
||||
const requestPromise = protocol.request(
|
||||
const requestPromise = testRequest(
|
||||
protocol,
|
||||
{
|
||||
method: 'test/request',
|
||||
params: { data: 'test' }
|
||||
|
|
@ -4030,7 +4033,8 @@ describe('Message Interception', () => {
|
|||
const mockSchema = z.object({ result: z.string() });
|
||||
|
||||
// Send a request with relatedTask metadata
|
||||
const requestPromise = protocol.request(
|
||||
const requestPromise = testRequest(
|
||||
protocol,
|
||||
{
|
||||
method: 'test/request',
|
||||
params: { data: 'test' }
|
||||
|
|
@ -4174,7 +4178,8 @@ describe('Message Interception', () => {
|
|||
const relatedTask = { taskId: 'task-meta-456' };
|
||||
const mockSchema = z.object({ result: z.string() });
|
||||
|
||||
const requestPromise = protocol.request(
|
||||
const requestPromise = testRequest(
|
||||
protocol,
|
||||
{
|
||||
method: 'test/request',
|
||||
params: { data: 'test' }
|
||||
|
|
@ -4366,11 +4371,14 @@ describe('Queue lifecycle management', () => {
|
|||
const taskId = task.taskId;
|
||||
|
||||
// Queue a request (catch rejection to avoid unhandled promise rejection)
|
||||
const requestPromise = protocol
|
||||
.request({ method: 'test/request', params: { data: 'test' } }, z.object({ result: z.string() }), {
|
||||
const requestPromise = testRequest(
|
||||
protocol,
|
||||
{ method: 'test/request', params: { data: 'test' } },
|
||||
z.object({ result: z.string() }),
|
||||
{
|
||||
relatedTask: { taskId }
|
||||
})
|
||||
.catch(err => err);
|
||||
}
|
||||
).catch(err => err);
|
||||
|
||||
// Verify request is queued
|
||||
const queue = (protocol as unknown as TestProtocol)._taskMessageQueue;
|
||||
|
|
@ -4391,7 +4399,7 @@ describe('Queue lifecycle management', () => {
|
|||
await new Promise(resolve => setTimeout(resolve, 50));
|
||||
|
||||
// Verify the request promise is rejected
|
||||
const result = await requestPromise;
|
||||
const result = (await requestPromise) as Error;
|
||||
expect(result).toBeInstanceOf(ProtocolError);
|
||||
expect(result.message).toContain('Task cancelled or completed');
|
||||
|
||||
|
|
@ -4439,11 +4447,14 @@ describe('Queue lifecycle management', () => {
|
|||
const taskId = task.taskId;
|
||||
|
||||
// Queue a request (catch the rejection to avoid unhandled promise rejection)
|
||||
const requestPromise = protocol
|
||||
.request({ method: 'test/request', params: { data: 'test' } }, z.object({ result: z.string() }), {
|
||||
const requestPromise = testRequest(
|
||||
protocol,
|
||||
{ method: 'test/request', params: { data: 'test' } },
|
||||
z.object({ result: z.string() }),
|
||||
{
|
||||
relatedTask: { taskId }
|
||||
})
|
||||
.catch(err => err);
|
||||
}
|
||||
).catch(err => err);
|
||||
|
||||
// Verify request is queued
|
||||
const queue = (protocol as unknown as TestProtocol)._taskMessageQueue;
|
||||
|
|
@ -4453,7 +4464,7 @@ describe('Queue lifecycle management', () => {
|
|||
(protocol as unknown as TestProtocol)._clearTaskQueue(taskId);
|
||||
|
||||
// Verify the request promise is rejected
|
||||
const result = await requestPromise;
|
||||
const result = (await requestPromise) as Error;
|
||||
expect(result).toBeInstanceOf(ProtocolError);
|
||||
expect(result.message).toContain('Task cancelled or completed');
|
||||
|
||||
|
|
@ -4472,23 +4483,32 @@ describe('Queue lifecycle management', () => {
|
|||
const taskId = task.taskId;
|
||||
|
||||
// Queue multiple requests (catch rejections to avoid unhandled promise rejections)
|
||||
const request1Promise = protocol
|
||||
.request({ method: 'test/request1', params: { data: 'test1' } }, z.object({ result: z.string() }), {
|
||||
const request1Promise = testRequest(
|
||||
protocol,
|
||||
{ method: 'test/request1', params: { data: 'test1' } },
|
||||
z.object({ result: z.string() }),
|
||||
{
|
||||
relatedTask: { taskId }
|
||||
})
|
||||
.catch(err => err);
|
||||
}
|
||||
).catch(err => err);
|
||||
|
||||
const request2Promise = protocol
|
||||
.request({ method: 'test/request2', params: { data: 'test2' } }, z.object({ result: z.string() }), {
|
||||
const request2Promise = testRequest(
|
||||
protocol,
|
||||
{ method: 'test/request2', params: { data: 'test2' } },
|
||||
z.object({ result: z.string() }),
|
||||
{
|
||||
relatedTask: { taskId }
|
||||
})
|
||||
.catch(err => err);
|
||||
}
|
||||
).catch(err => err);
|
||||
|
||||
const request3Promise = protocol
|
||||
.request({ method: 'test/request3', params: { data: 'test3' } }, z.object({ result: z.string() }), {
|
||||
const request3Promise = testRequest(
|
||||
protocol,
|
||||
{ method: 'test/request3', params: { data: 'test3' } },
|
||||
z.object({ result: z.string() }),
|
||||
{
|
||||
relatedTask: { taskId }
|
||||
})
|
||||
.catch(err => err);
|
||||
}
|
||||
).catch(err => err);
|
||||
|
||||
// Verify requests are queued
|
||||
const queue = (protocol as unknown as TestProtocol)._taskMessageQueue;
|
||||
|
|
@ -4498,9 +4518,9 @@ describe('Queue lifecycle management', () => {
|
|||
(protocol as unknown as TestProtocol)._clearTaskQueue(taskId);
|
||||
|
||||
// Verify all request promises are rejected
|
||||
const result1 = await request1Promise;
|
||||
const result2 = await request2Promise;
|
||||
const result3 = await request3Promise;
|
||||
const result1 = (await request1Promise) as Error;
|
||||
const result2 = (await request2Promise) as Error;
|
||||
const result3 = (await request3Promise) as Error;
|
||||
|
||||
expect(result1).toBeInstanceOf(ProtocolError);
|
||||
expect(result1.message).toContain('Task cancelled or completed');
|
||||
|
|
@ -4522,11 +4542,14 @@ describe('Queue lifecycle management', () => {
|
|||
const taskId = task.taskId;
|
||||
|
||||
// Queue a request (catch rejection to avoid unhandled promise rejection)
|
||||
const requestPromise = protocol
|
||||
.request({ method: 'test/request', params: { data: 'test' } }, z.object({ result: z.string() }), {
|
||||
const requestPromise = testRequest(
|
||||
protocol,
|
||||
{ method: 'test/request', params: { data: 'test' } },
|
||||
z.object({ result: z.string() }),
|
||||
{
|
||||
relatedTask: { taskId }
|
||||
})
|
||||
.catch(err => err);
|
||||
}
|
||||
).catch(err => err);
|
||||
|
||||
// Get the request ID that was sent
|
||||
const requestResolvers = (protocol as unknown as TestProtocol)._requestResolvers;
|
||||
|
|
@ -4541,7 +4564,7 @@ describe('Queue lifecycle management', () => {
|
|||
(protocol as unknown as TestProtocol)._clearTaskQueue(taskId);
|
||||
|
||||
// Verify request promise is rejected
|
||||
const result = await requestPromise;
|
||||
const result = (await requestPromise) as Error;
|
||||
expect(result).toBeInstanceOf(ProtocolError);
|
||||
expect(result.message).toContain('Task cancelled or completed');
|
||||
|
||||
|
|
@ -4575,7 +4598,7 @@ describe('requestStream() method', () => {
|
|||
// Start the request stream
|
||||
const streamPromise = (async () => {
|
||||
const messages = [];
|
||||
const stream = (protocol as unknown as TestProtocol).requestStream(
|
||||
const stream = (protocol as unknown as TestProtocol)._requestStreamWithSchema(
|
||||
{ method: 'tools/call', params: { name: 'test', arguments: {} } },
|
||||
CallToolResultSchema
|
||||
);
|
||||
|
|
@ -4621,7 +4644,7 @@ describe('requestStream() method', () => {
|
|||
// Start the request stream
|
||||
const streamPromise = (async () => {
|
||||
const messages = [];
|
||||
const stream = (protocol as unknown as TestProtocol).requestStream(
|
||||
const stream = (protocol as unknown as TestProtocol)._requestStreamWithSchema(
|
||||
{ method: 'tools/call', params: { name: 'test', arguments: {} } },
|
||||
CallToolResultSchema
|
||||
);
|
||||
|
|
@ -4674,7 +4697,7 @@ describe('requestStream() method', () => {
|
|||
|
||||
// Start the request stream with already-aborted signal
|
||||
const messages = [];
|
||||
const stream = (protocol as unknown as TestProtocol).requestStream(
|
||||
const stream = (protocol as unknown as TestProtocol)._requestStreamWithSchema(
|
||||
{ method: 'tools/call', params: { name: 'test', arguments: {} } },
|
||||
CallToolResultSchema,
|
||||
{
|
||||
|
|
@ -4709,7 +4732,7 @@ describe('requestStream() method', () => {
|
|||
await protocol.connect(transport);
|
||||
|
||||
const messagesPromise = toArrayAsync(
|
||||
(protocol as unknown as TestProtocol).requestStream(
|
||||
(protocol as unknown as TestProtocol)._requestStreamWithSchema(
|
||||
{ method: 'tools/call', params: { name: 'test', arguments: {} } },
|
||||
CallToolResultSchema
|
||||
)
|
||||
|
|
@ -4754,7 +4777,7 @@ describe('requestStream() method', () => {
|
|||
await protocol.connect(transport);
|
||||
|
||||
const messagesPromise = toArrayAsync(
|
||||
(protocol as unknown as TestProtocol).requestStream(
|
||||
(protocol as unknown as TestProtocol)._requestStreamWithSchema(
|
||||
{ method: 'tools/call', params: { name: 'test', arguments: {} } },
|
||||
CallToolResultSchema,
|
||||
{
|
||||
|
|
@ -4800,7 +4823,7 @@ describe('requestStream() method', () => {
|
|||
|
||||
// Collect messages
|
||||
const messages = await toArrayAsync(
|
||||
(protocol as unknown as TestProtocol).requestStream(
|
||||
(protocol as unknown as TestProtocol)._requestStreamWithSchema(
|
||||
{ method: 'tools/call', params: { name: 'test', arguments: {} } },
|
||||
CallToolResultSchema,
|
||||
{
|
||||
|
|
@ -4832,7 +4855,7 @@ describe('requestStream() method', () => {
|
|||
await protocol.connect(transport);
|
||||
|
||||
const messagesPromise = toArrayAsync(
|
||||
(protocol as unknown as TestProtocol).requestStream(
|
||||
(protocol as unknown as TestProtocol)._requestStreamWithSchema(
|
||||
{ method: 'tools/call', params: { name: 'test', arguments: {} } },
|
||||
CallToolResultSchema
|
||||
)
|
||||
|
|
@ -4887,7 +4910,7 @@ describe('requestStream() method', () => {
|
|||
await protocol.connect(transport);
|
||||
|
||||
const messagesPromise = toArrayAsync(
|
||||
(protocol as unknown as TestProtocol).requestStream(
|
||||
(protocol as unknown as TestProtocol)._requestStreamWithSchema(
|
||||
{ method: 'tools/call', params: { name: 'test', arguments: {} } },
|
||||
CallToolResultSchema
|
||||
)
|
||||
|
|
@ -4953,7 +4976,7 @@ describe('requestStream() method', () => {
|
|||
transport.send = vi.fn().mockRejectedValue(new Error('Network error'));
|
||||
|
||||
const messages = await toArrayAsync(
|
||||
(protocol as unknown as TestProtocol).requestStream(
|
||||
(protocol as unknown as TestProtocol)._requestStreamWithSchema(
|
||||
{ method: 'tools/call', params: { name: 'test', arguments: {} } },
|
||||
CallToolResultSchema
|
||||
)
|
||||
|
|
@ -4981,7 +5004,7 @@ describe('requestStream() method', () => {
|
|||
await protocol.connect(transport);
|
||||
|
||||
const messagesPromise = toArrayAsync(
|
||||
(protocol as unknown as TestProtocol).requestStream(
|
||||
(protocol as unknown as TestProtocol)._requestStreamWithSchema(
|
||||
{ method: 'tools/call', params: { name: 'test', arguments: {} } },
|
||||
CallToolResultSchema
|
||||
)
|
||||
|
|
|
|||
|
|
@ -15,13 +15,12 @@ import type {
|
|||
ElicitResult,
|
||||
GetTaskResult,
|
||||
ListTasksResult,
|
||||
Request,
|
||||
RequestMethod,
|
||||
RequestOptions,
|
||||
ResponseMessage,
|
||||
Result,
|
||||
ResultTypeMap,
|
||||
SchemaOutput
|
||||
} from '@modelcontextprotocol/core';
|
||||
import { CreateMessageResultSchema, ElicitResultSchema } from '@modelcontextprotocol/core';
|
||||
|
||||
import type { Server } from '../../server/server.js';
|
||||
|
||||
|
|
@ -30,7 +29,7 @@ import type { Server } from '../../server/server.js';
|
|||
*
|
||||
* Access via `server.experimental.tasks`:
|
||||
* ```typescript
|
||||
* const stream = server.experimental.tasks.requestStream(request, schema, options);
|
||||
* const stream = server.experimental.tasks.requestStream(request, options);
|
||||
* ```
|
||||
*
|
||||
* For high-level server usage with task-based tools, use {@linkcode index.McpServer | McpServer}.experimental.tasks instead.
|
||||
|
|
@ -47,27 +46,24 @@ export class ExperimentalServerTasks {
|
|||
* This method provides streaming access to request processing, allowing you to
|
||||
* observe intermediate task status updates for task-augmented requests.
|
||||
*
|
||||
* @param request - The request to send
|
||||
* @param resultSchema - Zod schema for validating the result
|
||||
* @param request - The request to send (method name determines the result schema)
|
||||
* @param options - Optional request options (timeout, signal, task creation params, etc.)
|
||||
* @returns AsyncGenerator that yields {@linkcode ResponseMessage} objects
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
requestStream<T extends AnySchema>(
|
||||
request: Request,
|
||||
resultSchema: T,
|
||||
requestStream<M extends RequestMethod>(
|
||||
request: { method: M; params?: Record<string, unknown> },
|
||||
options?: RequestOptions
|
||||
): AsyncGenerator<ResponseMessage<SchemaOutput<T> & Result>, void, void> {
|
||||
): AsyncGenerator<ResponseMessage<ResultTypeMap[M]>, void, void> {
|
||||
// Delegate to the server's underlying Protocol method
|
||||
type ServerWithRequestStream = {
|
||||
requestStream<U extends AnySchema>(
|
||||
request: Request,
|
||||
resultSchema: U,
|
||||
requestStream<N extends RequestMethod>(
|
||||
request: { method: N; params?: Record<string, unknown> },
|
||||
options?: RequestOptions
|
||||
): AsyncGenerator<ResponseMessage<SchemaOutput<U> & Result>, void, void>;
|
||||
): AsyncGenerator<ResponseMessage<ResultTypeMap[N]>, void, void>;
|
||||
};
|
||||
return (this._server as unknown as ServerWithRequestStream).requestStream(request, resultSchema, options);
|
||||
return (this._server as unknown as ServerWithRequestStream).requestStream(request, options);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -163,9 +159,8 @@ export class ExperimentalServerTasks {
|
|||
method: 'sampling/createMessage',
|
||||
params
|
||||
},
|
||||
CreateMessageResultSchema,
|
||||
options
|
||||
);
|
||||
) as AsyncGenerator<ResponseMessage<CreateMessageResult>, void, void>;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -241,9 +236,8 @@ export class ExperimentalServerTasks {
|
|||
method: 'elicitation/create',
|
||||
params: normalizedParams
|
||||
},
|
||||
ElicitResultSchema,
|
||||
options
|
||||
);
|
||||
) as AsyncGenerator<ResponseMessage<ElicitResult>, void, void>;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue