Implementation of SEP-986: Specify Format for Tool Names (#900)
Co-authored-by: Felix Weinberger <fweinberger@anthropic.com> Co-authored-by: Felix Weinberger <3823880+felixweinberger@users.noreply.github.com>
This commit is contained in:
parent
9f06b6b499
commit
29cb08071a
2 changed files with 303 additions and 0 deletions
|
|
@ -227,6 +227,10 @@ describe('ResourceTemplate', () => {
|
|||
});
|
||||
|
||||
describe('tool()', () => {
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
/***
|
||||
* Test: Zero-Argument Tool Registration
|
||||
*/
|
||||
|
|
@ -1692,6 +1696,56 @@ describe('tool()', () => {
|
|||
expect(result.tools[0].name).toBe('test-without-meta');
|
||||
expect(result.tools[0]._meta).toBeUndefined();
|
||||
});
|
||||
|
||||
test('should validate tool names according to SEP specification', () => {
|
||||
// Create a new server instance for this test
|
||||
const testServer = new McpServer({
|
||||
name: 'test server',
|
||||
version: '1.0'
|
||||
});
|
||||
|
||||
// Spy on console.warn to verify warnings are logged
|
||||
const warnSpy = jest.spyOn(console, 'warn').mockImplementation();
|
||||
|
||||
// Test valid tool names
|
||||
testServer.registerTool(
|
||||
'valid-tool-name',
|
||||
{
|
||||
description: 'A valid tool name'
|
||||
},
|
||||
async () => ({ content: [{ type: 'text' as const, text: 'Success' }] })
|
||||
);
|
||||
|
||||
// Test tool name with warnings (starts with dash)
|
||||
testServer.registerTool(
|
||||
'-warning-tool',
|
||||
{
|
||||
description: 'A tool name that generates warnings'
|
||||
},
|
||||
async () => ({ content: [{ type: 'text' as const, text: 'Success' }] })
|
||||
);
|
||||
|
||||
// Test invalid tool name (contains spaces)
|
||||
testServer.registerTool(
|
||||
'invalid tool name',
|
||||
{
|
||||
description: 'An invalid tool name'
|
||||
},
|
||||
async () => ({ content: [{ type: 'text' as const, text: 'Success' }] })
|
||||
);
|
||||
|
||||
// Verify that warnings were issued (both for warnings and validation failures)
|
||||
expect(warnSpy).toHaveBeenCalled();
|
||||
|
||||
// Verify specific warning content
|
||||
const warningCalls = warnSpy.mock.calls.map(call => call.join(' '));
|
||||
expect(warningCalls.some(call => call.includes('Tool name starts or ends with a dash'))).toBe(true);
|
||||
expect(warningCalls.some(call => call.includes('Tool name contains spaces'))).toBe(true);
|
||||
expect(warningCalls.some(call => call.includes('Tool name contains invalid characters'))).toBe(true);
|
||||
|
||||
// Clean up spies
|
||||
warnSpy.mockRestore();
|
||||
});
|
||||
});
|
||||
|
||||
describe('resource()', () => {
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ import { Completable, CompletableDef } from './completable.js';
|
|||
import { UriTemplate, Variables } from '../shared/uriTemplate.js';
|
||||
import { RequestHandlerExtra } from '../shared/protocol.js';
|
||||
import { Transport } from '../shared/transport.js';
|
||||
import { validateAndWarnToolName } from '../shared/toolNameValidation.js';
|
||||
|
||||
/**
|
||||
* High-level MCP server that provides a simpler API for working with resources, tools, and prompts.
|
||||
|
|
@ -664,6 +665,9 @@ export class McpServer {
|
|||
_meta: Record<string, unknown> | undefined,
|
||||
callback: ToolCallback<ZodRawShape | undefined>
|
||||
): RegisteredTool {
|
||||
// Validate tool name according to SEP specification
|
||||
validateAndWarnToolName(name);
|
||||
|
||||
const registeredTool: RegisteredTool = {
|
||||
title,
|
||||
description,
|
||||
|
|
@ -678,6 +682,9 @@ export class McpServer {
|
|||
remove: () => registeredTool.update({ name: null }),
|
||||
update: updates => {
|
||||
if (typeof updates.name !== 'undefined' && updates.name !== name) {
|
||||
if (typeof updates.name === 'string') {
|
||||
validateAndWarnToolName(updates.name);
|
||||
}
|
||||
delete this._registeredTools[name];
|
||||
if (updates.name) this._registeredTools[updates.name] = registeredTool;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue