No automatic completion support unless needed - Revisited yet again (#1237)

This commit is contained in:
Cliff Hall 2025-12-04 23:12:16 -05:00 • committed by GitHub
commit a57303c335
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 96 additions and 4 deletions

View file

@ -2669,6 +2669,41 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
).rejects.toThrow(/Resource test:\/\/nonexistent not found/);
});
/***
* Test: Registering a resource template without a complete callback should not update server capabilities to advertise support for completion
*/
test('should not advertise support for completion when a resource template without a complete callback is defined', async () => {
const mcpServer = new McpServer({
name: 'test server',
version: '1.0'
});
const client = new Client({
name: 'test client',
version: '1.0'
});
mcpServer.resource(
'test',
new ResourceTemplate('test://resource/{category}', {
list: undefined
}),
async () => ({
contents: [
{
uri: 'test://resource/test',
text: 'Test content'
}
]
})
);
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
await Promise.all([client.connect(clientTransport), mcpServer.server.connect(serverTransport)]);
expect(client.getServerCapabilities()).not.toHaveProperty('completions');
});
/***
* Test: Registering a resource template with a complete callback should update server capabilities to advertise support for completion
*/
@ -3548,6 +3583,46 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
).rejects.toThrow(/Prompt nonexistent-prompt not found/);
});
/***
* Test: Registering a prompt without a completable argument should not update server capabilities to advertise support for completion
*/
test('should not advertise support for completion when a prompt without a completable argument is defined', async () => {
const mcpServer = new McpServer({
name: 'test server',
version: '1.0'
});
const client = new Client({
name: 'test client',
version: '1.0'
});
mcpServer.prompt(
'test-prompt',
{
name: z.string()
},
async ({ name }) => ({
messages: [
{
role: 'assistant',
content: {
type: 'text',
text: `Hello ${name}`
}
}
]
})
);
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
await Promise.all([client.connect(clientTransport), mcpServer.server.connect(serverTransport)]);
const capabilities = client.getServerCapabilities() || {};
const keys = Object.keys(capabilities);
expect(keys).not.toContain('completions');
});
/***
* Test: Registering a prompt with a completable argument should update server capabilities to advertise support for completion
*/

View file

@ -62,6 +62,7 @@ import { Transport } from '../shared/transport.js';
import { validateAndWarnToolName } from '../shared/toolNameValidation.js';
import { ExperimentalMcpServerTasks } from '../experimental/tasks/mcp-server.js';
import type { ToolTaskHandler } from '../experimental/tasks/interfaces.js';
import { ZodOptional } from 'zod';
/**
* High-level MCP server that provides a simpler API for working with resources, tools, and prompts.
@ -557,8 +558,6 @@ export class McpServer {
throw new McpError(ErrorCode.InvalidParams, `Resource ${uri} not found`);
});
this.setCompletionRequestHandler();
this._resourceHandlersInitialized = true;
}
@ -623,8 +622,6 @@ export class McpServer {
}
});
this.setCompletionRequestHandler();
this._promptHandlersInitialized = true;
}
@ -815,6 +812,14 @@ export class McpServer {
}
};
this._registeredResourceTemplates[name] = registeredResourceTemplate;
// If the resource template has any completion callbacks, enable completions capability
const variableNames = template.uriTemplate.variableNames;
const hasCompleter = Array.isArray(variableNames) && variableNames.some(v => !!template.completeCallback(v));
if (hasCompleter) {
this.setCompletionRequestHandler();
}
return registeredResourceTemplate;
}
@ -848,6 +853,18 @@ export class McpServer {
}
};
this._registeredPrompts[name] = registeredPrompt;
// If any argument uses a Completable schema, enable completions capability
if (argsSchema) {
const hasCompletable = Object.values(argsSchema).some(field => {
const inner: unknown = field instanceof ZodOptional ? field._def?.innerType : field;
return isCompletable(inner);
});
if (hasCompletable) {
this.setCompletionRequestHandler();
}
}
return registeredPrompt;
}