@mimik/mcp-kit
v1.1.1
Published
A lightweight, flexible library for building Model Context Protocol (MCP) servers with comprehensive schema validation and Zod-like syntax support in mimik serverless environment.
Downloads
226
Keywords
Readme
mcp-kit: MCP Server Library
A lightweight, flexible library for building Model Context Protocol (MCP) servers with comprehensive schema validation and Zod-like syntax support in mimik serverless environment.
Table of Contents
- Installation
- Quick Start
- Core Concepts
- Schema Definition
- API Reference
- Examples
- Error Handling
- Best Practices
Installation
npm install @mimik/mcp-kitconst { McpServer, z } = require('@mimik/mcp-kit');Quick Start
const { McpServer, z } = require('@mimik/mcp-kit');
// Create a server instance
const server = new McpServer({
name: 'my-mcp-server',
version: '1.0.0'
});
// Register a tool
server.tool(
'greet',
'Greets a user with optional formality',
{
name: z.string().describe('The name to greet'),
formal: z.boolean().default(false).describe('Use formal greeting')
},
async (args) => {
const { name, formal } = args;
const greeting = formal ? `Good day, ${name}` : `Hi ${name}!`;
return greeting;
}
);
// Handle MCP requests
const response = await server.handleMcpRequest(request);Core Concepts
Server Instance
The McpServer class is the main entry point for creating MCP servers. It handles JSON-RPC 2.0 protocol communication and manages tools, resources, and prompts.
Tools
Tools are functions that can be called by MCP clients. Each tool has:
- Name: Unique identifier
- Description: Human-readable description
- Input Schema: Defines expected parameters
- Handler: Async function that processes the request
Resources
Resources represent readable content (files, data, etc.) that clients can access.
Prompts
Prompts are templates for generating messages with dynamic content.
Schema Definition
This library supports multiple schema definition formats for maximum flexibility.
Zod-like Syntax (Recommended)
{
name: z.string().describe('User name'),
age: z.number().default(25).describe('User age'),
role: z.enum(['admin', 'user', 'guest']).default('user').describe('User role'),
active: z.boolean().describe('Is user active'),
tags: z.array(z.string()).describe('User tags'),
profile: z.object({
bio: z.string().describe('Biography'),
website: z.optional(z.string()).describe('Website URL')
}),
nickname: z.optional(z.string()).describe('Optional nickname')
}Custom Format
{
name: { type: 'string', description: 'User name' },
age: { type: 'number', optional: true, default: 25, description: 'User age' },
role: { type: 'string', enum: ['admin', 'user', 'guest'], default: 'user' }
}Pure JSON Schema
{
type: 'object',
properties: {
name: { type: 'string', description: 'User name' },
age: { type: 'number', default: 25, description: 'User age' }
},
required: ['name']
}API Reference
McpServer
Constructor
new McpServer(serverInfo?)Parameters:
serverInfo(optional): Object withnameandversionproperties
Methods
tool(name, description, inputSchema, handler)
Registers a new tool.
Parameters:
name(string): Unique tool identifierdescription(string): Tool descriptioninputSchema(object): Parameter schema definitionhandler(async function): Tool implementation
Returns: this (for chaining)
resource(uri, name, description, mimeType, handler)
Registers a new resource.
Parameters:
uri(string): Unique resource URIname(string): Resource namedescription(string): Resource descriptionmimeType(string): MIME type of the resourcehandler(async function): Resource content provider
Returns: this (for chaining)
prompt(name, description, argumentsSchema, handler)
Registers a new prompt.
Parameters:
name(string): Unique prompt identifierdescription(string): Prompt descriptionargumentsSchema(object): Arguments schemahandler(async function): Prompt generator
Returns: this (for chaining)
handleMcpRequest(requestBody)
Processes an MCP request.
Parameters:
requestBody(string | object): JSON-RPC 2.0 request
Returns: Promise - JSON-RPC 2.0 response
Schema Helpers (z)
Basic Types
z.string() // String type
z.number() // Number type
z.boolean() // Boolean typeComplex Types
z.enum(['option1', 'option2', 'option3']) // Enumeration
z.array(z.string()) // Array of strings
z.object({ key: z.string() }) // Object with properties
z.optional(z.string()) // Optional fieldModifiers
.describe('Field description') // Add description
.default(value) // Set default value (makes field optional)Chaining
All modifiers can be chained in any order:
z.enum(['small', 'medium', 'large'])
.default('medium')
.describe('Size selection')Examples
Basic Tool
server.tool(
'add',
'Adds two numbers',
{
a: z.number().describe('First number'),
b: z.number().describe('Second number')
},
async ({ a, b }) => {
return `${a} + ${b} = ${a + b}`;
}
);Tool with Optional Parameters
server.tool(
'search',
'Search for items',
{
query: z.string().describe('Search query'),
limit: z.number().default(10).describe('Maximum results'),
category: z.optional(z.string()).describe('Filter by category')
},
async ({ query, limit, category }) => {
// Implementation here
return `Searching for "${query}" (limit: ${limit}, category: ${category || 'all'})`;
}
);Tool with Enum and Complex Objects
server.tool(
'createOrder',
'Create a new order',
{
type: z.enum(['pickup', 'delivery', 'dine-in']).default('pickup'),
priority: z.enum(['low', 'normal', 'high']).default('normal'),
customer: z.object({
name: z.string(),
email: z.string(),
phone: z.optional(z.string())
}),
items: z.array(z.object({
id: z.string(),
quantity: z.number(),
notes: z.optional(z.string())
}))
},
async (args) => {
const { type, priority, customer, items } = args;
return {
content: [
{
type: 'text',
text: `Order created: ${items.length} items for ${customer.name} (${type}, ${priority} priority)`
}
]
};
}
);Resource Example
server.resource(
'file://logs/system.log',
'System Log',
'Current system log file',
'text/plain',
async () => {
// Return log content
return 'Log entries...';
}
);Prompt Example
server.prompt(
'codeReview',
'Generate code review prompt',
{
language: z.string().describe('Programming language'),
level: z.enum(['beginner', 'intermediate', 'advanced']).default('intermediate')
},
async ({ language, level }) => {
return [
{
role: 'user',
content: {
type: 'text',
text: `Please review this ${language} code focusing on ${level}-level best practices.`
}
}
];
}
);Error Handling
The library provides comprehensive error handling:
Validation Errors
// Missing required parameter
{
"jsonrpc": "2.0",
"id": 1,
"error": {
"code": -32602,
"message": "Invalid arguments: Missing required parameter: name"
}
}
// Wrong parameter type
{
"jsonrpc": "2.0",
"id": 1,
"error": {
"code": -32602,
"message": "Invalid arguments: Parameter 'age' should be a number, got string"
}
}Tool Execution Errors
{
"jsonrpc": "2.0",
"id": 1,
"error": {
"code": -32603,
"message": "Tool execution error",
"data": "Division by zero"
}
}Common Error Codes
-32700: Parse error (malformed JSON)-32601: Method not found-32602: Invalid parameters-32603: Internal error
Best Practices
1. Use Descriptive Names and Descriptions
// Good
server.tool(
'calculateTax',
'Calculate tax amount based on income and tax rate',
{
income: z.number().describe('Annual income in dollars'),
rate: z.number().describe('Tax rate as decimal (0.1 for 10%)')
},
handler
);
// Avoid
server.tool('calc', 'Does math', { a: z.number(), b: z.number() }, handler);2. Provide Sensible Defaults
server.tool(
'search',
'Search documents',
{
query: z.string().describe('Search query'),
maxResults: z.number().default(20).describe('Maximum results to return'),
sortBy: z.enum(['relevance', 'date', 'title']).default('relevance')
},
handler
);3. Use Enums for Limited Options
// Good - clear valid options
status: z.enum(['active', 'inactive', 'pending']).default('pending')
// Avoid - unclear what values are valid
status: z.string().describe('Status (active, inactive, or pending)')4. Validate Input Early
The library automatically validates parameters against the schema before calling your handler, so focus on business logic validation:
async ({ email, age }) => {
// Schema validation already ensures email is string, age is number
// Add business logic validation
if (age < 0 || age > 150) {
throw new Error('Age must be between 0 and 150');
}
if (!email.includes('@')) {
throw new Error('Invalid email format');
}
// Process the request...
}5. Return Structured Responses
// For simple text responses
return "Operation completed successfully";
// For complex responses
return {
content: [
{ type: 'text', text: 'Results:' },
{ type: 'text', text: JSON.stringify(data, null, 2) }
]
};6. Handle Async Operations Properly
server.tool(
'fetchData',
'Fetch data from external API',
{ url: z.string() },
async ({ url }) => {
try {
const response = await fetch(url);
const data = await response.json();
return JSON.stringify(data, null, 2);
} catch (error) {
throw new Error(`Failed to fetch data: ${error.message}`);
}
}
);Integration Examples
Express-like Middleware
The key to integrating with Express or any similar web framework is using server.handleMcpRequest() to process all MCP requests:
const { McpServer, z } = require('@mimik/mcp-kit');
const server = new McpServer({ name: 'my-api', version: '1.0.0' });
// Register your tools...
server.tool('ping', 'Simple ping', {}, async () => 'pong');
// IMPORTANT: Use server.handleMcpRequest() to handle all MCP communication
app.post('/mcp', async (req, res) => {
try {
const response = await server.handleMcpRequest(req.body);
if (response) {
res.json(response);
} else {
res.status(204).end(); // For notifications - no response needed
}
} catch (error) {
res.status(500).json({
jsonrpc: '2.0',
id: req.body?.id || null,
error: { code: -32603, message: 'Internal error', data: error.message }
});
}
});Alternative Promise-based Approach
// In your middleware app
app.post('/mcp', (req, res) => {
server.handleMcpRequest(req.body).then((response) => {
if (response) {
res.json(response);
} else {
// Notification - no response needed
res.status(204).end();
}
}).catch((error) => {
res.status(500).json({
jsonrpc: '2.0',
id: req.body?.id || null,
error: { code: -32603, message: 'Internal error', data: error.message }
});
});
});Key Points:
- Always use
server.handleMcpRequest(req.body)to process MCP requests - Handle both responses (requests) and notifications (no response)
- Use status 204 for notifications, not 200
- Properly format error responses as JSON-RPC 2.0
Testing Your Server
async function testServer() {
// Initialize
const initResponse = await server.handleMcpRequest({
jsonrpc: '2.0',
id: 1,
method: 'initialize',
params: {}
});
// List tools
const toolsResponse = await server.handleMcpRequest({
jsonrpc: '2.0',
id: 2,
method: 'tools/list'
});
// Call a tool
const callResponse = await server.handleMcpRequest({
jsonrpc: '2.0',
id: 3,
method: 'tools/call',
params: {
name: 'greet',
arguments: { name: 'World', formal: true }
}
});
console.log('Tool response:', callResponse);
}