Skip to content

Commit ccf6751

Browse files
authored
Merge pull request #3236 from nielskaspers/add-everything-server-tests
Add Vitest tests for Everything Server
2 parents 10d3827 + cd20aee commit ccf6751

8 files changed

Lines changed: 1541 additions & 3 deletions

File tree

package-lock.json

Lines changed: 3 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
import { describe, it, expect, vi } from 'vitest';
2+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3+
import { registerSimplePrompt } from '../prompts/simple.js';
4+
import { registerArgumentsPrompt } from '../prompts/args.js';
5+
import { registerPromptWithCompletions } from '../prompts/completions.js';
6+
import { registerEmbeddedResourcePrompt } from '../prompts/resource.js';
7+
8+
// Helper to capture registered prompt handlers
9+
function createMockServer() {
10+
const handlers: Map<string, Function> = new Map();
11+
const configs: Map<string, any> = new Map();
12+
13+
const mockServer = {
14+
registerPrompt: vi.fn((name: string, config: any, handler: Function) => {
15+
handlers.set(name, handler);
16+
configs.set(name, config);
17+
}),
18+
} as unknown as McpServer;
19+
20+
return { mockServer, handlers, configs };
21+
}
22+
23+
describe('Prompts', () => {
24+
describe('simple-prompt', () => {
25+
it('should return fixed message with no arguments', () => {
26+
const { mockServer, handlers } = createMockServer();
27+
registerSimplePrompt(mockServer);
28+
29+
const handler = handlers.get('simple-prompt')!;
30+
const result = handler();
31+
32+
expect(result).toEqual({
33+
messages: [
34+
{
35+
role: 'user',
36+
content: {
37+
type: 'text',
38+
text: 'This is a simple prompt without arguments.',
39+
},
40+
},
41+
],
42+
});
43+
});
44+
});
45+
46+
describe('args-prompt', () => {
47+
it('should include city in message', () => {
48+
const { mockServer, handlers } = createMockServer();
49+
registerArgumentsPrompt(mockServer);
50+
51+
const handler = handlers.get('args-prompt')!;
52+
const result = handler({ city: 'San Francisco' });
53+
54+
expect(result.messages[0].content.text).toBe("What's weather in San Francisco?");
55+
});
56+
57+
it('should include city and state in message', () => {
58+
const { mockServer, handlers } = createMockServer();
59+
registerArgumentsPrompt(mockServer);
60+
61+
const handler = handlers.get('args-prompt')!;
62+
const result = handler({ city: 'San Francisco', state: 'California' });
63+
64+
expect(result.messages[0].content.text).toBe(
65+
"What's weather in San Francisco, California?"
66+
);
67+
});
68+
69+
it('should handle city only (optional state omitted)', () => {
70+
const { mockServer, handlers } = createMockServer();
71+
registerArgumentsPrompt(mockServer);
72+
73+
const handler = handlers.get('args-prompt')!;
74+
const result = handler({ city: 'New York' });
75+
76+
expect(result.messages[0].content.text).toBe("What's weather in New York?");
77+
expect(result.messages[0].content.text).not.toContain(',');
78+
expect(result.messages[0].role).toBe('user');
79+
expect(result.messages[0].content.type).toBe('text');
80+
});
81+
});
82+
83+
describe('completable-prompt', () => {
84+
it('should generate promotion message with department and name', () => {
85+
const { mockServer, handlers } = createMockServer();
86+
registerPromptWithCompletions(mockServer);
87+
88+
const handler = handlers.get('completable-prompt')!;
89+
const result = handler({ department: 'Engineering', name: 'Alice' });
90+
91+
expect(result.messages[0].content.text).toBe(
92+
'Please promote Alice to the head of the Engineering team.'
93+
);
94+
});
95+
96+
it('should work with different departments', () => {
97+
const { mockServer, handlers } = createMockServer();
98+
registerPromptWithCompletions(mockServer);
99+
100+
const handler = handlers.get('completable-prompt')!;
101+
102+
const salesResult = handler({ department: 'Sales', name: 'David' });
103+
expect(salesResult.messages[0].content.text).toContain('Sales');
104+
expect(salesResult.messages[0].content.text).toContain('David');
105+
expect(salesResult.messages[0].role).toBe('user');
106+
107+
const marketingResult = handler({ department: 'Marketing', name: 'Grace' });
108+
expect(marketingResult.messages[0].content.text).toContain('Marketing');
109+
expect(marketingResult.messages[0].content.text).toContain('Grace');
110+
});
111+
});
112+
113+
describe('resource-prompt', () => {
114+
it('should return text resource reference', () => {
115+
const { mockServer, handlers } = createMockServer();
116+
registerEmbeddedResourcePrompt(mockServer);
117+
118+
const handler = handlers.get('resource-prompt')!;
119+
const result = handler({ resourceType: 'Text', resourceId: '1' });
120+
121+
expect(result.messages).toHaveLength(2);
122+
expect(result.messages[0].content.text).toContain('Text');
123+
expect(result.messages[0].content.text).toContain('1');
124+
expect(result.messages[1].content.type).toBe('resource');
125+
expect(result.messages[1].content.resource.uri).toContain('text/1');
126+
});
127+
128+
it('should return blob resource reference', () => {
129+
const { mockServer, handlers } = createMockServer();
130+
registerEmbeddedResourcePrompt(mockServer);
131+
132+
const handler = handlers.get('resource-prompt')!;
133+
const result = handler({ resourceType: 'Blob', resourceId: '5' });
134+
135+
expect(result.messages[0].content.text).toContain('Blob');
136+
expect(result.messages[1].content.resource.uri).toContain('blob/5');
137+
});
138+
139+
it('should reject invalid resource type', () => {
140+
const { mockServer, handlers } = createMockServer();
141+
registerEmbeddedResourcePrompt(mockServer);
142+
143+
const handler = handlers.get('resource-prompt')!;
144+
expect(() => handler({ resourceType: 'Invalid', resourceId: '1' })).toThrow(
145+
'Invalid resourceType'
146+
);
147+
});
148+
149+
it('should reject invalid resource ID', () => {
150+
const { mockServer, handlers } = createMockServer();
151+
registerEmbeddedResourcePrompt(mockServer);
152+
153+
const handler = handlers.get('resource-prompt')!;
154+
expect(() => handler({ resourceType: 'Text', resourceId: '-1' })).toThrow(
155+
'Invalid resourceId'
156+
);
157+
expect(() => handler({ resourceType: 'Text', resourceId: '0' })).toThrow(
158+
'Invalid resourceId'
159+
);
160+
expect(() => handler({ resourceType: 'Text', resourceId: 'abc' })).toThrow(
161+
'Invalid resourceId'
162+
);
163+
});
164+
165+
it('should include both intro text and resource messages', () => {
166+
const { mockServer, handlers } = createMockServer();
167+
registerEmbeddedResourcePrompt(mockServer);
168+
169+
const handler = handlers.get('resource-prompt')!;
170+
const result = handler({ resourceType: 'Text', resourceId: '3' });
171+
172+
expect(result.messages).toHaveLength(2);
173+
expect(result.messages[0].role).toBe('user');
174+
expect(result.messages[0].content.type).toBe('text');
175+
expect(result.messages[1].role).toBe('user');
176+
expect(result.messages[1].content.type).toBe('resource');
177+
});
178+
});
179+
});
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
import { describe, it, expect, vi } from 'vitest';
2+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3+
4+
// Create mock server
5+
function createMockServer() {
6+
return {
7+
registerTool: vi.fn(),
8+
registerPrompt: vi.fn(),
9+
registerResource: vi.fn(),
10+
server: {
11+
getClientCapabilities: vi.fn(() => ({})),
12+
setRequestHandler: vi.fn(),
13+
},
14+
sendLoggingMessage: vi.fn(),
15+
sendResourceUpdated: vi.fn(),
16+
} as unknown as McpServer;
17+
}
18+
19+
describe('Registration Index Files', () => {
20+
describe('tools/index.ts', () => {
21+
it('should register all standard tools', async () => {
22+
const { registerTools } = await import('../tools/index.js');
23+
const mockServer = createMockServer();
24+
25+
registerTools(mockServer);
26+
27+
// Should register 12 standard tools (non-conditional)
28+
expect(mockServer.registerTool).toHaveBeenCalledTimes(12);
29+
30+
// Verify specific tools are registered
31+
const registeredTools = (mockServer.registerTool as any).mock.calls.map(
32+
(call: any[]) => call[0]
33+
);
34+
expect(registeredTools).toContain('echo');
35+
expect(registeredTools).toContain('get-sum');
36+
expect(registeredTools).toContain('get-env');
37+
expect(registeredTools).toContain('get-tiny-image');
38+
expect(registeredTools).toContain('get-structured-content');
39+
expect(registeredTools).toContain('get-annotated-message');
40+
expect(registeredTools).toContain('trigger-long-running-operation');
41+
expect(registeredTools).toContain('get-resource-links');
42+
expect(registeredTools).toContain('get-resource-reference');
43+
expect(registeredTools).toContain('gzip-file-as-resource');
44+
expect(registeredTools).toContain('toggle-simulated-logging');
45+
expect(registeredTools).toContain('toggle-subscriber-updates');
46+
});
47+
48+
it('should register conditional tools based on capabilities', async () => {
49+
const { registerConditionalTools } = await import('../tools/index.js');
50+
51+
// Server with all capabilities including experimental tasks API
52+
const mockServerWithCapabilities = {
53+
registerTool: vi.fn(),
54+
server: {
55+
getClientCapabilities: vi.fn(() => ({
56+
roots: {},
57+
elicitation: {},
58+
sampling: {},
59+
})),
60+
},
61+
experimental: {
62+
tasks: {
63+
registerToolTask: vi.fn(),
64+
},
65+
},
66+
} as unknown as McpServer;
67+
68+
registerConditionalTools(mockServerWithCapabilities);
69+
70+
// Should register 3 conditional tools + 3 task-based tools when all capabilities present
71+
expect(mockServerWithCapabilities.registerTool).toHaveBeenCalledTimes(3);
72+
73+
const registeredTools = (
74+
mockServerWithCapabilities.registerTool as any
75+
).mock.calls.map((call: any[]) => call[0]);
76+
expect(registeredTools).toContain('get-roots-list');
77+
expect(registeredTools).toContain('trigger-elicitation-request');
78+
expect(registeredTools).toContain('trigger-sampling-request');
79+
80+
// Task-based tools are registered via experimental.tasks.registerToolTask
81+
expect(mockServerWithCapabilities.experimental.tasks.registerToolTask).toHaveBeenCalled();
82+
});
83+
84+
it('should not register conditional tools when capabilities missing', async () => {
85+
const { registerConditionalTools } = await import('../tools/index.js');
86+
87+
const mockServerNoCapabilities = {
88+
registerTool: vi.fn(),
89+
server: {
90+
getClientCapabilities: vi.fn(() => ({})),
91+
},
92+
experimental: {
93+
tasks: {
94+
registerToolTask: vi.fn(),
95+
},
96+
},
97+
} as unknown as McpServer;
98+
99+
registerConditionalTools(mockServerNoCapabilities);
100+
101+
// Should not register any capability-gated tools when capabilities are missing
102+
expect(mockServerNoCapabilities.registerTool).not.toHaveBeenCalled();
103+
});
104+
});
105+
106+
describe('prompts/index.ts', () => {
107+
it('should register all prompts', async () => {
108+
const { registerPrompts } = await import('../prompts/index.js');
109+
const mockServer = createMockServer();
110+
111+
registerPrompts(mockServer);
112+
113+
// Should register 4 prompts
114+
expect(mockServer.registerPrompt).toHaveBeenCalledTimes(4);
115+
116+
const registeredPrompts = (mockServer.registerPrompt as any).mock.calls.map(
117+
(call: any[]) => call[0]
118+
);
119+
expect(registeredPrompts).toContain('simple-prompt');
120+
expect(registeredPrompts).toContain('args-prompt');
121+
expect(registeredPrompts).toContain('completable-prompt');
122+
expect(registeredPrompts).toContain('resource-prompt');
123+
});
124+
});
125+
126+
describe('resources/index.ts', () => {
127+
it('should register resource templates', async () => {
128+
const { registerResources } = await import('../resources/index.js');
129+
const mockServer = createMockServer();
130+
131+
registerResources(mockServer);
132+
133+
// Should register at least the 2 resource templates (text and blob) plus file resources
134+
expect(mockServer.registerResource).toHaveBeenCalled();
135+
const registeredResources = (mockServer.registerResource as any).mock.calls.map(
136+
(call: any[]) => call[0]
137+
);
138+
expect(registeredResources).toContain('Dynamic Text Resource');
139+
expect(registeredResources).toContain('Dynamic Blob Resource');
140+
});
141+
142+
it('should read instructions from file', async () => {
143+
const { readInstructions } = await import('../resources/index.js');
144+
145+
const instructions = readInstructions();
146+
147+
// Should return a string (either content or error message)
148+
expect(typeof instructions).toBe('string');
149+
expect(instructions.length).toBeGreaterThan(0);
150+
});
151+
});
152+
});

0 commit comments

Comments
 (0)