import { Server } from '../../src/server/index.js'; import { Client } from '../../src/client/index.js'; import { InMemoryTransport } from '../../src/inMemory.js'; import { McpServer, ResourceTemplate } from '../../src/server/mcp.js'; import { zodTestMatrix, type ZodMatrixEntry } from '../../src/__fixtures__/zodTestMatrix.js'; describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => { const { z } = entry; describe('Title field backwards compatibility', () => { it('should work with tools that have title', async () => { const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair(); const server = new McpServer({ name: 'test-server', version: '1.0.0' }, { capabilities: {} }); // Register tool with title server.registerTool( 'test-tool', { title: 'Test Tool Display Name', description: 'A test tool', inputSchema: { value: z.string() } }, async () => ({ content: [{ type: 'text', text: 'result' }] }) ); const client = new Client({ name: 'test-client', version: '1.0.0' }); await server.server.connect(serverTransport); await client.connect(clientTransport); const tools = await client.listTools(); expect(tools.tools).toHaveLength(1); expect(tools.tools[0].name).toBe('test-tool'); expect(tools.tools[0].title).toBe('Test Tool Display Name'); expect(tools.tools[0].description).toBe('A test tool'); }); it('should work with tools without title', async () => { const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair(); const server = new McpServer({ name: 'test-server', version: '1.0.0' }, { capabilities: {} }); // Register tool without title server.tool('test-tool', 'A test tool', { value: z.string() }, async () => ({ content: [{ type: 'text', text: 'result' }] })); const client = new Client({ name: 'test-client', version: '1.0.0' }); await server.server.connect(serverTransport); await client.connect(clientTransport); const tools = await client.listTools(); expect(tools.tools).toHaveLength(1); expect(tools.tools[0].name).toBe('test-tool'); expect(tools.tools[0].title).toBeUndefined(); expect(tools.tools[0].description).toBe('A test tool'); }); it('should work with prompts that have title using update', async () => { const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair(); const server = new McpServer({ name: 'test-server', version: '1.0.0' }, { capabilities: {} }); // Register prompt with title by updating after creation const prompt = server.prompt('test-prompt', 'A test prompt', async () => ({ messages: [{ role: 'user', content: { type: 'text', text: 'test' } }] })); prompt.update({ title: 'Test Prompt Display Name' }); const client = new Client({ name: 'test-client', version: '1.0.0' }); await server.server.connect(serverTransport); await client.connect(clientTransport); const prompts = await client.listPrompts(); expect(prompts.prompts).toHaveLength(1); expect(prompts.prompts[0].name).toBe('test-prompt'); expect(prompts.prompts[0].title).toBe('Test Prompt Display Name'); expect(prompts.prompts[0].description).toBe('A test prompt'); }); it('should work with prompts using registerPrompt', async () => { const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair(); const server = new McpServer({ name: 'test-server', version: '1.0.0' }, { capabilities: {} }); // Register prompt with title using registerPrompt server.registerPrompt( 'test-prompt', { title: 'Test Prompt Display Name', description: 'A test prompt', argsSchema: { input: z.string() } }, async ({ input }) => ({ messages: [ { role: 'user', content: { type: 'text', text: `test: ${input}` } } ] }) ); const client = new Client({ name: 'test-client', version: '1.0.0' }); await server.server.connect(serverTransport); await client.connect(clientTransport); const prompts = await client.listPrompts(); expect(prompts.prompts).toHaveLength(1); expect(prompts.prompts[0].name).toBe('test-prompt'); expect(prompts.prompts[0].title).toBe('Test Prompt Display Name'); expect(prompts.prompts[0].description).toBe('A test prompt'); expect(prompts.prompts[0].arguments).toHaveLength(1); }); it('should work with resources using registerResource', async () => { const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair(); const server = new McpServer({ name: 'test-server', version: '1.0.0' }, { capabilities: {} }); // Register resource with title using registerResource server.registerResource( 'test-resource', 'https://example.com/test', { title: 'Test Resource Display Name', description: 'A test resource', mimeType: 'text/plain' }, async () => ({ contents: [ { uri: 'https://example.com/test', text: 'test content' } ] }) ); const client = new Client({ name: 'test-client', version: '1.0.0' }); await server.server.connect(serverTransport); await client.connect(clientTransport); const resources = await client.listResources(); expect(resources.resources).toHaveLength(1); expect(resources.resources[0].name).toBe('test-resource'); expect(resources.resources[0].title).toBe('Test Resource Display Name'); expect(resources.resources[0].description).toBe('A test resource'); expect(resources.resources[0].mimeType).toBe('text/plain'); }); it('should work with dynamic resources using registerResource', async () => { const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair(); const server = new McpServer({ name: 'test-server', version: '1.0.0' }, { capabilities: {} }); // Register dynamic resource with title using registerResource server.registerResource( 'user-profile', new ResourceTemplate('users://{userId}/profile', { list: undefined }), { title: 'User Profile', description: 'User profile information' }, async (uri, { userId }, _extra) => ({ contents: [ { uri: uri.href, text: `Profile data for user ${userId}` } ] }) ); const client = new Client({ name: 'test-client', version: '1.0.0' }); await server.server.connect(serverTransport); await client.connect(clientTransport); const resourceTemplates = await client.listResourceTemplates(); expect(resourceTemplates.resourceTemplates).toHaveLength(1); expect(resourceTemplates.resourceTemplates[0].name).toBe('user-profile'); expect(resourceTemplates.resourceTemplates[0].title).toBe('User Profile'); expect(resourceTemplates.resourceTemplates[0].description).toBe('User profile information'); expect(resourceTemplates.resourceTemplates[0].uriTemplate).toBe('users://{userId}/profile'); // Test reading the resource const readResult = await client.readResource({ uri: 'users://123/profile' }); expect(readResult.contents).toHaveLength(1); 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 () => { const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair(); const server = new Server( { name: 'test-server', version: '1.0.0', title: 'Test Server Display Name' }, { capabilities: {} } ); const client = new Client({ name: 'test-client', version: '1.0.0' }); await server.connect(serverTransport); await client.connect(clientTransport); const serverInfo = client.getServerVersion(); expect(serverInfo?.name).toBe('test-server'); expect(serverInfo?.version).toBe('1.0.0'); expect(serverInfo?.title).toBe('Test Server Display Name'); }); }); });