Complete technical reference for the local-dev MCP server.
- Server Configuration
- Tool Definitions
- Request/Response Formats
- Type Definitions
- Error Codes
- Environment Variables
const server = new Server(
serverInfo: ServerInfo,
serverOptions: ServerOptions
)| Property | Type | Description |
|---|---|---|
name |
string | Server identifier (e.g., "local-dev-mcp-server") |
version |
string | Semantic version (e.g., "1.0.0") |
| Property | Type | Description |
|---|---|---|
capabilities |
Capabilities | Declares server capabilities |
{
tools?: {} // Supports tool execution
resources?: {} // Supports resource access
prompts?: {} // Supports prompt templates
}Example:
const server = new Server({
name: 'my-server',
version: '1.0.0',
}, {
capabilities: {
tools: {},
},
});Every tool must follow this structure:
interface Tool {
name: string;
description: string;
inputSchema: JSONSchema;
}{
type: 'object',
properties: {
stringProp: {
type: 'string',
description: 'A string parameter',
minLength?: number,
maxLength?: number,
pattern?: string, // Regex pattern
format?: string, // 'email', 'uri', 'date-time', etc.
},
numberProp: {
type: 'number',
description: 'A number parameter',
minimum?: number,
maximum?: number,
multipleOf?: number,
},
integerProp: {
type: 'integer',
description: 'An integer parameter',
minimum?: number,
maximum?: number,
},
booleanProp: {
type: 'boolean',
description: 'A boolean parameter',
default?: boolean,
},
arrayProp: {
type: 'array',
description: 'An array parameter',
items: JSONSchema, // Schema for array items
minItems?: number,
maxItems?: number,
uniqueItems?: boolean,
},
objectProp: {
type: 'object',
description: 'An object parameter',
properties: {...},
required?: string[],
additionalProperties?: boolean,
}
},
required: string[], // Required property names
additionalProperties?: boolean
}{
type: 'string',
enum: ['option1', 'option2', 'option3'],
description: 'Choose one option',
default?: 'option1'
}{
type: 'object',
properties: {
user: {
type: 'object',
properties: {
name: { type: 'string' },
email: { type: 'string', format: 'email' },
age: { type: 'integer', minimum: 0 }
},
required: ['name', 'email']
}
}
}Request:
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/list"
}Response:
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"tools": [
{
"name": "tool-name",
"description": "Tool description",
"inputSchema": {
"type": "object",
"properties": {...},
"required": [...]
}
}
]
}
}Request:
{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "tool-name",
"arguments": {
"param1": "value1",
"param2": "value2"
}
}
}Success Response:
{
"jsonrpc": "2.0",
"id": 2,
"result": {
"content": [
{
"type": "text",
"text": "Tool execution result"
}
]
}
}Error Response:
{
"jsonrpc": "2.0",
"id": 2,
"result": {
"content": [
{
"type": "text",
"text": "Error message"
}
],
"isError": true
}
}{
type: 'text',
text: string
}{
type: 'image',
data: string, // Base64-encoded image
mimeType: string // e.g., 'image/png'
}{
type: 'resource',
resource: {
uri: string,
mimeType?: string,
text?: string,
blob?: string // Base64-encoded binary
}
}// Request handler types
type ListToolsHandler = () => Promise<{ tools: Tool[] }>;
type CallToolHandler = (request: {
params: {
name: string;
arguments: Record<string, any>;
}
}) => Promise<ToolResult>;
// Response types
interface ToolResult {
content: Content[];
isError?: boolean;
}
type Content = TextContent | ImageContent | ResourceContent;
interface TextContent {
type: 'text';
text: string;
}
interface ImageContent {
type: 'image';
data: string;
mimeType: string;
}
interface ResourceContent {
type: 'resource';
resource: {
uri: string;
mimeType?: string;
text?: string;
blob?: string;
};
}interface Task {
id: string;
title: string;
description: string;
status: 'todo' | 'in-progress' | 'done';
priority: 'low' | 'medium' | 'high';
createdAt: string; // ISO 8601 timestamp
updatedAt: string; // ISO 8601 timestamp
}interface FileStats {
size: number;
createdAt: Date;
modifiedAt: Date;
isDirectory: boolean;
isFile: boolean;
permissions: string;
}
interface DirectoryEntry {
name: string;
isDirectory: boolean;
isFile: boolean;
}| Code | Type | Description |
|---|---|---|
TOOL_NOT_FOUND |
Error | Requested tool doesn't exist |
INVALID_PARAMS |
Error | Tool parameters don't match schema |
EXECUTION_ERROR |
Error | Error during tool execution |
TIMEOUT |
Error | Tool execution exceeded time limit |
| Code | Type | Description |
|---|---|---|
ENOENT |
Error | File or directory not found |
EACCES |
Error | Permission denied |
EISDIR |
Error | Expected file but got directory |
ENOTDIR |
Error | Expected directory but got file |
EEXIST |
Error | File already exists |
| Code | Type | Description |
|---|---|---|
VALIDATION_ERROR |
ZodError | Input validation failed |
SCHEMA_ERROR |
Error | Invalid JSON schema |
TYPE_ERROR |
TypeError | Type mismatch |
| Variable | Type | Default | Description |
|---|---|---|---|
MCP_SERVER_NAME |
string | "local-dev-mcp-server" | Server identifier |
MCP_SERVER_VERSION |
string | "1.0.0" | Server version |
LOG_LEVEL |
string | "info" | Logging level (debug, info, warn, error) |
| Variable | Type | Default | Description |
|---|---|---|---|
ENABLE_FILE_OPS |
boolean | true | Enable file operation tools |
ENABLE_TASKS |
boolean | true | Enable task management tools |
ENABLE_DOCS |
boolean | true | Enable document generation tools |
| Variable | Type | Default | Description |
|---|---|---|---|
MAX_FILE_SIZE |
number | 10485760 | Maximum file size in bytes (10MB) |
REQUEST_TIMEOUT |
number | 30000 | Request timeout in milliseconds |
MAX_CONCURRENT_REQUESTS |
number | 10 | Maximum concurrent tool executions |
| Variable | Type | Default | Description |
|---|---|---|---|
DATA_DIR |
string | "./data" | Directory for persistent data |
TEMP_DIR |
string | "./tmp" | Directory for temporary files |
TASK_FILE |
string | "./tasks.json" | Task storage file path |
Register a handler for a specific request type.
server.setRequestHandler(
schema: RequestSchema,
handler: HandlerFunction
): voidParameters:
schema: Request schema (e.g.,ListToolsRequestSchema,CallToolRequestSchema)handler: Async function to handle the request
Example:
server.setRequestHandler(ListToolsRequestSchema, async () => {
return { tools: TOOLS };
});
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
// Handle tool call
return { content: [...] };
});Handles standard input/output communication.
const transport = new StdioServerTransport();Methods:
| Method | Returns | Description |
|---|---|---|
start() |
Promise | Start listening for messages |
close() |
Promise | Stop transport and clean up |
Usage:
const transport = new StdioServerTransport();
await server.connect(transport);
console.error('Server running on stdio');Note: Use console.error() for logging because console.log() interferes with stdio transport.
import { z } from 'zod';
// Define schema
const MySchema = z.object({
name: z.string().min(1).max(100),
email: z.string().email(),
age: z.number().int().positive().optional(),
role: z.enum(['user', 'admin', 'guest']),
});
// Infer TypeScript type
type MyType = z.infer<typeof MySchema>;
// Validate data
try {
const validated = MySchema.parse(data);
// Use validated data (fully typed)
} catch (error) {
if (error instanceof z.ZodError) {
// Handle validation error
const messages = error.errors.map(e => e.message);
}
}
// Safe parse (doesn't throw)
const result = MySchema.safeParse(data);
if (result.success) {
// Use result.data
} else {
// Handle result.error
}// Strings
z.string()
.min(5)
.max(100)
.email()
.url()
.uuid()
.regex(/^[A-Z]+$/)
.startsWith('prefix')
.endsWith('suffix')
.toLowerCase()
.trim()
// Numbers
z.number()
.int()
.positive()
.negative()
.min(0)
.max(100)
.multipleOf(5)
// Arrays
z.array(z.string())
.min(1)
.max(10)
.nonempty()
// Objects
z.object({
name: z.string(),
nested: z.object({
value: z.number()
})
})
// Unions
z.union([z.string(), z.number()])
// Enums
z.enum(['option1', 'option2'])
// Optionals
z.string().optional()
z.string().nullable()
z.string().nullish() // null or undefined
// Defaults
z.string().default('default value')
// Transformations
z.string().transform(s => s.toUpperCase())Always use TypeScript types for tool arguments:
// ✓ Good
const { path, content } = args as { path: string; content: string };
// ✗ Bad
const path = args.path;
const content = args.content;Always wrap tool logic in try-catch:
// ✓ Good
try {
const result = await riskyOperation();
return { content: [{ type: 'text', text: result }] };
} catch (error) {
return {
content: [{ type: 'text', text: `Error: ${error.message}` }],
isError: true
};
}Always use async/await for I/O operations:
// ✓ Good
const content = await fs.readFile(path, 'utf-8');
// ✗ Bad
fs.readFileSync(path, 'utf-8'); // Blocks event loopValidate all inputs, even if schema is defined:
// ✓ Good
const schema = z.object({ email: z.string().email() });
const { email } = schema.parse(args);
// ✗ Bad (trusts client-side validation)
const { email } = args as { email: string };Use stderr for logs (stdout is for transport):
// ✓ Good
console.error('Processing request:', name);
// ✗ Bad
console.log('Processing request:', name); // Breaks stdio transportHere's a fully-typed, validated, error-handled tool:
import { z } from 'zod';
import { promises as fs } from 'fs';
// Schema
const CopyFileSchema = z.object({
source: z.string().min(1, 'Source path required'),
destination: z.string().min(1, 'Destination path required'),
overwrite: z.boolean().default(false)
});
type CopyFileInput = z.infer<typeof CopyFileSchema>;
// Tool definition
{
name: 'copy-file',
description: 'Copy a file from source to destination',
inputSchema: {
type: 'object',
properties: {
source: {
type: 'string',
description: 'Source file path'
},
destination: {
type: 'string',
description: 'Destination file path'
},
overwrite: {
type: 'boolean',
description: 'Overwrite if destination exists',
default: false
}
},
required: ['source', 'destination']
}
}
// Implementation
if (name === 'copy-file') {
try {
// Validate input
const input: CopyFileInput = CopyFileSchema.parse(args);
const { source, destination, overwrite } = input;
console.error(`Copying ${source} to ${destination}`);
// Check source exists
try {
await fs.access(source);
} catch {
return {
content: [{
type: 'text',
text: `Source file not found: ${source}`
}],
isError: true
};
}
// Check destination
if (!overwrite) {
try {
await fs.access(destination);
return {
content: [{
type: 'text',
text: `Destination already exists: ${destination}. Use overwrite: true to replace.`
}],
isError: true
};
} catch {
// File doesn't exist, OK to proceed
}
}
// Copy file
await fs.copyFile(source, destination);
// Get file info
const stats = await fs.stat(destination);
return {
content: [{
type: 'text',
text: `File copied successfully!\n\n` +
`Source: ${source}\n` +
`Destination: ${destination}\n` +
`Size: ${stats.size} bytes`
}]
};
} catch (error) {
if (error instanceof z.ZodError) {
return {
content: [{
type: 'text',
text: `Validation error: ${error.errors.map(e => e.message).join(', ')}`
}],
isError: true
};
}
return {
content: [{
type: 'text',
text: `Error copying file: ${error instanceof Error ? error.message : String(error)}`
}],
isError: true
};
}
}- Initial release
- File operations tools
- Task management tools
- Document generation tools
- TypeScript implementation
- Zod validation support
- MCP Specification: https://modelcontextprotocol.io/specification
- TypeScript Handbook: https://www.typescriptlang.org/docs/handbook/
- Zod Documentation: https://zod.dev
- Node.js API Reference: https://nodejs.org/api/
End of API Reference