SEP-1319: Decouple Request Payloads, Remove passthrough iteration, Typecheck fixes (#1086)
This commit is contained in:
parent
806d7cf9c0
commit
f59996ae66
6 changed files with 2840 additions and 951 deletions
|
|
@ -176,6 +176,7 @@ function testElicitationFlow(validatorProvider: typeof ajvProvider | typeof cfWo
|
|||
age: { type: 'integer', minimum: 0, maximum: 150 },
|
||||
street: { type: 'string' },
|
||||
city: { type: 'string' },
|
||||
// @ts-expect-error - pattern is not a valid property by MCP spec, however it is making use of the Ajv validator
|
||||
zipCode: { type: 'string', pattern: '^[0-9]{5}$' },
|
||||
newsletter: { type: 'boolean' },
|
||||
notifications: { type: 'boolean' }
|
||||
|
|
@ -355,6 +356,7 @@ function testElicitationFlow(validatorProvider: typeof ajvProvider | typeof cfWo
|
|||
requestedSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
// @ts-expect-error - pattern is not a valid property by MCP spec, however it is making use of the Ajv validator
|
||||
zipCode: { type: 'string', pattern: '^[0-9]{5}$' }
|
||||
},
|
||||
required: ['zipCode']
|
||||
|
|
|
|||
|
|
@ -702,9 +702,7 @@ test('should handle server cancelling a request', async () => {
|
|||
version: '1.0'
|
||||
},
|
||||
{
|
||||
capabilities: {
|
||||
sampling: {}
|
||||
}
|
||||
capabilities: {}
|
||||
}
|
||||
);
|
||||
|
||||
|
|
@ -763,9 +761,7 @@ test('should handle request timeout', async () => {
|
|||
version: '1.0'
|
||||
},
|
||||
{
|
||||
capabilities: {
|
||||
sampling: {}
|
||||
}
|
||||
capabilities: {}
|
||||
}
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -237,9 +237,9 @@ export class Server<
|
|||
|
||||
protected assertRequestHandlerCapability(method: string): void {
|
||||
switch (method) {
|
||||
case 'sampling/createMessage':
|
||||
if (!this._capabilities.sampling) {
|
||||
throw new Error(`Server does not support sampling (required for ${method})`);
|
||||
case 'completion/complete':
|
||||
if (!this._capabilities.completions) {
|
||||
throw new Error(`Server does not support completions (required for ${method})`);
|
||||
}
|
||||
break;
|
||||
|
||||
|
|
|
|||
|
|
@ -1407,7 +1407,14 @@ describe('tool()', () => {
|
|||
|
||||
expect(receivedRequestId).toBeDefined();
|
||||
expect(typeof receivedRequestId === 'string' || typeof receivedRequestId === 'number').toBe(true);
|
||||
expect(result.content?.[0].text).toContain('Received request ID:');
|
||||
expect(result.content).toEqual(
|
||||
expect.arrayContaining([
|
||||
{
|
||||
type: 'text',
|
||||
text: expect.stringContaining('Received request ID:')
|
||||
}
|
||||
])
|
||||
);
|
||||
});
|
||||
|
||||
/***
|
||||
|
|
@ -1781,7 +1788,14 @@ describe('resource()', () => {
|
|||
);
|
||||
|
||||
expect(result.contents).toHaveLength(1);
|
||||
expect(result.contents[0].text).toBe('Updated content');
|
||||
expect(result.contents).toEqual(
|
||||
expect.arrayContaining([
|
||||
{
|
||||
text: expect.stringContaining('Updated content'),
|
||||
uri: 'test://resource'
|
||||
}
|
||||
])
|
||||
);
|
||||
|
||||
// Update happened before transport was connected, so no notifications should be expected
|
||||
expect(notifications).toHaveLength(0);
|
||||
|
|
@ -1846,7 +1860,14 @@ describe('resource()', () => {
|
|||
);
|
||||
|
||||
expect(result.contents).toHaveLength(1);
|
||||
expect(result.contents[0].text).toBe('Updated content');
|
||||
expect(result.contents).toEqual(
|
||||
expect.arrayContaining([
|
||||
{
|
||||
text: expect.stringContaining('Updated content'),
|
||||
uri: 'test://resource/123'
|
||||
}
|
||||
])
|
||||
);
|
||||
|
||||
// Update happened before transport was connected, so no notifications should be expected
|
||||
expect(notifications).toHaveLength(0);
|
||||
|
|
@ -2195,7 +2216,14 @@ describe('resource()', () => {
|
|||
ReadResourceResultSchema
|
||||
);
|
||||
|
||||
expect(result.contents[0].text).toBe('Category: books, ID: 123');
|
||||
expect(result.contents).toEqual(
|
||||
expect.arrayContaining([
|
||||
{
|
||||
text: expect.stringContaining('Category: books, ID: 123'),
|
||||
uri: 'test://resource/books/123'
|
||||
}
|
||||
])
|
||||
);
|
||||
});
|
||||
|
||||
/***
|
||||
|
|
@ -2556,7 +2584,14 @@ describe('resource()', () => {
|
|||
|
||||
expect(receivedRequestId).toBeDefined();
|
||||
expect(typeof receivedRequestId === 'string' || typeof receivedRequestId === 'number').toBe(true);
|
||||
expect(result.contents[0].text).toContain('Received request ID:');
|
||||
expect(result.contents).toEqual(
|
||||
expect.arrayContaining([
|
||||
{
|
||||
text: expect.stringContaining(`Received request ID:`),
|
||||
uri: 'test://resource'
|
||||
}
|
||||
])
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -2662,7 +2697,17 @@ describe('prompt()', () => {
|
|||
);
|
||||
|
||||
expect(result.messages).toHaveLength(1);
|
||||
expect(result.messages[0].content.text).toBe('Updated response');
|
||||
expect(result.messages).toEqual(
|
||||
expect.arrayContaining([
|
||||
{
|
||||
role: 'assistant',
|
||||
content: {
|
||||
type: 'text',
|
||||
text: 'Updated response'
|
||||
}
|
||||
}
|
||||
])
|
||||
);
|
||||
|
||||
// Update happened before transport was connected, so no notifications should be expected
|
||||
expect(notifications).toHaveLength(0);
|
||||
|
|
@ -2754,7 +2799,17 @@ describe('prompt()', () => {
|
|||
);
|
||||
|
||||
expect(getResult.messages).toHaveLength(1);
|
||||
expect(getResult.messages[0].content.text).toBe('Updated: test, value');
|
||||
expect(getResult.messages).toEqual(
|
||||
expect.arrayContaining([
|
||||
{
|
||||
role: 'assistant',
|
||||
content: {
|
||||
type: 'text',
|
||||
text: 'Updated: test, value'
|
||||
}
|
||||
}
|
||||
])
|
||||
);
|
||||
|
||||
// Update happened before transport was connected, so no notifications should be expected
|
||||
expect(notifications).toHaveLength(0);
|
||||
|
|
@ -3411,7 +3466,17 @@ describe('prompt()', () => {
|
|||
|
||||
expect(receivedRequestId).toBeDefined();
|
||||
expect(typeof receivedRequestId === 'string' || typeof receivedRequestId === 'number').toBe(true);
|
||||
expect(result.messages[0].content.text).toContain('Received request ID:');
|
||||
expect(result.messages).toEqual(
|
||||
expect.arrayContaining([
|
||||
{
|
||||
role: 'assistant',
|
||||
content: {
|
||||
type: 'text',
|
||||
text: expect.stringContaining(`Received request ID:`)
|
||||
}
|
||||
}
|
||||
])
|
||||
);
|
||||
});
|
||||
|
||||
/***
|
||||
|
|
@ -3513,7 +3578,7 @@ describe('prompt()', () => {
|
|||
})
|
||||
}),
|
||||
{
|
||||
name: 'Template Name',
|
||||
title: 'Template Name',
|
||||
description: 'Template description',
|
||||
mimeType: 'application/json'
|
||||
},
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import {
|
|||
CallToolResult,
|
||||
McpError,
|
||||
ErrorCode,
|
||||
CompleteRequest,
|
||||
CompleteResult,
|
||||
PromptReference,
|
||||
ResourceTemplateReference,
|
||||
|
|
@ -31,7 +30,11 @@ import {
|
|||
ServerRequest,
|
||||
ServerNotification,
|
||||
ToolAnnotations,
|
||||
LoggingMessageNotification
|
||||
LoggingMessageNotification,
|
||||
CompleteRequestPrompt,
|
||||
CompleteRequestResourceTemplate,
|
||||
assertCompleteRequestPrompt,
|
||||
assertCompleteRequestResourceTemplate
|
||||
} from '../types.js';
|
||||
import { Completable, CompletableDef } from './completable.js';
|
||||
import { UriTemplate, Variables } from '../shared/uriTemplate.js';
|
||||
|
|
@ -217,9 +220,11 @@ export class McpServer {
|
|||
this.server.setRequestHandler(CompleteRequestSchema, async (request): Promise<CompleteResult> => {
|
||||
switch (request.params.ref.type) {
|
||||
case 'ref/prompt':
|
||||
assertCompleteRequestPrompt(request);
|
||||
return this.handlePromptCompletion(request, request.params.ref);
|
||||
|
||||
case 'ref/resource':
|
||||
assertCompleteRequestResourceTemplate(request);
|
||||
return this.handleResourceCompletion(request, request.params.ref);
|
||||
|
||||
default:
|
||||
|
|
@ -230,7 +235,7 @@ export class McpServer {
|
|||
this._completionHandlerInitialized = true;
|
||||
}
|
||||
|
||||
private async handlePromptCompletion(request: CompleteRequest, ref: PromptReference): Promise<CompleteResult> {
|
||||
private async handlePromptCompletion(request: CompleteRequestPrompt, ref: PromptReference): Promise<CompleteResult> {
|
||||
const prompt = this._registeredPrompts[ref.name];
|
||||
if (!prompt) {
|
||||
throw new McpError(ErrorCode.InvalidParams, `Prompt ${ref.name} not found`);
|
||||
|
|
@ -254,7 +259,10 @@ export class McpServer {
|
|||
return createCompletionResult(suggestions);
|
||||
}
|
||||
|
||||
private async handleResourceCompletion(request: CompleteRequest, ref: ResourceTemplateReference): Promise<CompleteResult> {
|
||||
private async handleResourceCompletion(
|
||||
request: CompleteRequestResourceTemplate,
|
||||
ref: ResourceTemplateReference
|
||||
): Promise<CompleteResult> {
|
||||
const template = Object.values(this._registeredResourceTemplates).find(t => t.resourceTemplate.uriTemplate.toString() === ref.uri);
|
||||
|
||||
if (!template) {
|
||||
|
|
|
|||
|
|
@ -189,7 +189,14 @@ describe('Title field backwards compatibility', () => {
|
|||
// Test reading the resource
|
||||
const readResult = await client.readResource({ uri: 'users://123/profile' });
|
||||
expect(readResult.contents).toHaveLength(1);
|
||||
expect(readResult.contents[0].text).toBe('Profile data for user 123');
|
||||
expect(readResult.contents).toEqual(
|
||||
expect.arrayContaining([
|
||||
{
|
||||
text: expect.stringContaining('Profile data for user 123'),
|
||||
uri: 'users://123/profile'
|
||||
}
|
||||
])
|
||||
);
|
||||
});
|
||||
|
||||
it('should support serverInfo with title', async () => {
|
||||
|
|
|
|||
Loading…
Reference in a new issue