Build production-quality MCP (Model Context Protocol) servers that expose tools, resources, and prompts to AI clients. This skill covers the full development lifecycle: tool definition, resource management, prompt templates, transport configuration (stdio, SSE), error handling, security hardening, testing, and client integration.
typescript
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { z } from 'zod';
const server = new McpServer({
name: 'my-mcp-server',
version: '1.0.0',
});
server.tool(
'search-documents',
'Search documents by query. Returns matching documents with relevance scores.',
{
query: z.string().describe('Search query string'),
limit: z.number().min(1).max(100).default(10).describe('Maximum results to return'),
filter: z.object({
type: z.enum(['article', 'page', 'note']).optional(),
dateAfter: z.string().datetime().optional(),
}).optional().describe('Optional filters'),
},
async ({ query, limit, filter }) => {
const results = await searchEngine.search(query, { limit, ...filter });
return {
content: [{
type: 'text',
text: JSON.stringify(results, null, 2),
}],
};
}
);
typescript
// Text response
return { content: [{ type: 'text', text: 'Operation completed successfully' }] };
// Structured data response
return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
// Multi-part response
return {
content: [
{ type: 'text', text: `Found ${results.length} results:` },
{ type: 'text', text: results.map(r => `- ${r.title}: ${r.summary}`).join('\n') },
],
};
// Image response
return { content: [{ type: 'image', data: base64Data, mimeType: 'image/png' }] };
// Error response
return {
content: [{ type: 'text', text: `Error: ${error.message}` }],
isError: true,
};
typescript
// Static resource
server.resource(
'config',
'config://app/settings',
{ mimeType: 'application/json' },
async () => ({
contents: [{
uri: 'config://app/settings',
mimeType: 'application/json',
text: JSON.stringify(appConfig),
}],
})
);
// Dynamic resource with URI template
server.resource(
'document',
new ResourceTemplate('docs://{category}/{id}', { list: undefined }),
{ mimeType: 'text/markdown' },
async (uri, { category, id }) => ({
contents: [{
uri: uri.href,
mimeType: 'text/markdown',
text: await getDocument(category, id),
}],
})
);
typescript
server.prompt(
'code-review',
'Generate a code review for the given file',
{
filePath: z.string().describe('Path to the file to review'),
severity: z.enum(['strict', 'normal', 'lenient']).default('normal'),
},
async ({ filePath, severity }) => {
const code = await readFile(filePath, 'utf-8');
return {
messages: [{
role: 'user',
content: {
type: 'text',
text: `Review this code with ${severity} standards:\n\n\`\`\`\n${code}\n\`\`\``,
},
}],
};
}
);
typescript
import express from 'express';
import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
const app = express();
app.get('/sse', async (req, res) => {
const transport = new SSEServerTransport('/messages', res);
await server.connect(transport);
});
app.post('/messages', async (req, res) => {
// Handle incoming messages
});
app.listen(3001);
typescript
server.tool('risky-operation', 'Performs an operation that might fail', {
input: z.string(),
}, async ({ input }) => {
try {
const result = await performOperation(input);
return { content: [{ type: 'text', text: JSON.stringify(result) }] };
} catch (error) {
if (error instanceof ValidationError) {
return {
content: [{ type: 'text', text: `Invalid input: ${error.message}` }],
isError: true,
};
}
if (error instanceof NotFoundError) {
return {
content: [{ type: 'text', text: `Resource not found: ${error.message}` }],
isError: true,
};
}
console.error('Unexpected error:', error);
return {
content: [{ type: 'text', text: 'An unexpected error occurred. Please try again.' }],
isError: true,
};
}
});
typescript
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { InMemoryTransport } from '@modelcontextprotocol/sdk/inMemory.js';
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
describe('MCP Server', () => {
let server: McpServer;
let client: Client;
beforeEach(async () => {
server = createServer();
client = new Client({ name: 'test-client', version: '1.0.0' });
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
await Promise.all([
server.connect(serverTransport),
client.connect(clientTransport),
]);
});
test('search-documents returns results', async () => {
const result = await client.callTool({
name: 'search-documents',
arguments: { query: 'test', limit: 5 },
});
expect(result.content[0].type).toBe('text');
const data = JSON.parse(result.content[0].text);
expect(data.length).toBeLessThanOrEqual(5);
});
test('handles invalid input gracefully', async () => {
const result = await client.callTool({
name: 'search-documents',
arguments: { query: '', limit: -1 },
});
expect(result.isError).toBe(true);
});
});
Use
mcp__context7__resolve-library-id
then
mcp__context7__query-docs
for up-to-date docs. Returned docs override memorized knowledge.
FLEXIBLE — Adapt project structure, transport choice, and tooling to the use case. Tool validation with Zod and error handling with
are strongly recommended. Security review is recommended before production deployment.