mcpField
M
mcp-server-development

mcp-server-development

github.com/akiojinDevelopment1037 installsUpdated 2d ago
Repository

About mcp-server-development

mcp-server-development is a skill for building reliable MCP servers, with guidance centered on unity-mcp-server architecture and patterns. It is intended for MCP server developers and covers handlers, input validation, transport, errors, and testing.

Key features

  1. 01Structure one handler per tool
  2. 02Validate inputs with Zod
  3. 03Handle Content-Length and NDJSON input
  4. 04Use JSON-RPC error formats
  5. 05Test handlers and transport flows
  6. 06Apply Unity commands and workspace paths

Use cases

  1. 01Design MCP tool handler structure
  2. 02Implement standard input/output transport
  3. 03Troubleshoot protocol and error handling
  4. 04Write tests for Unity integrations

MCP support

The skill guides MCP server construction, including tool definitions, JSON-RPC transport, and protocol compliance. Its architecture examples focus on unity-mcp-server.

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/akiojin/llmlb --skill mcp-server-development

After installing, type/mcp-server-developmentin Claude Code to confirm it loaded.

Details

From the project README

This skill provides comprehensive guidance for building robust MCP servers, with specific focus on the unity-mcp-server architecture and patterns.

Core Philosophy

MCP servers bridge AI assistants to external systems. They must be:

  • Reliable: Handle errors gracefully, never crash unexpectedly
  • Discoverable: Tools should have clear, self-documenting schemas
  • Performant: Minimize latency, especially for stdio transport
  • Protocol-compliant: Follow JSON-RPC 2.0 and MCP spec exactly

Architecture Patterns

Handler-Based Design

ALWAYS use a handler class per tool. Each handler encapsulates:

  • Input validation (Zod schema)
  • Business logic execution
  • Error handling and response formatting
// Pattern: One handler per tool
export class SystemPingToolHandler extends BaseToolHandler {
  constructor(unityConnection) {
    super({
      name: 'system_ping',
      description: 'Check Unity Editor connectivity',
      inputSchema: { type: 'object', properties: {} }
    });
    this.unityConnection = unityConnection;
  }

  async execute(params) {
    const result = await this.unityConnection.send({ command: 'ping' });
    return { status: 'ok', ...result };
  }
}
BaseToolHandler Contract

All handlers MUST:

  1. Call super() with tool metadata
  2. Implement execute(params) method
  3. Return plain objects (framework handles JSON-RPC wrapping)
  4. Throw errors with descriptive messages
Input Validation with Zod

ALWAYS validate inputs before processing:

import { z } from 'zod';

const inputSchema = z.object({
  name: z.string().min(1).describe('GameObject name'),
  primitiveType: z.enum(['cube', 'sphere', 'cylinder']).optional()
});

validate(input) {
  return inputSchema.parse(input);
}

Transport Layer

Content-Length Framing (Standard)

MCP uses LSP-style Content-Length framing for stdio:

Content-Length: 123\r\n
\r\n
{"jsonrpc":"2.0","id":1,"method":"tools/call"...}

ALWAYS use Content-Length for output. NEVER mix framing formats in a session.

Hybrid Input (Compatibility)

Accept both Content-Length and NDJSON input for client compatibility:

  • Claude Desktop: Content-Length
  • Some CLI tools: NDJSON (newline-delimited)

Error Handling

Structured Errors

Use MCP error codes from the spec:

const McpError = {
  ParseError: -32700,
  InvalidRequest: -32600,
  MethodNotFound: -32601,
  InvalidParams: -32602,
  InternalError: -32603
};

throw new Error(JSON.stringify({
  code: McpError.InvalidParams,
  message: 'primitiveType must be one of: cube, sphere, cylinder'
}));
Error Response Format
{
  jsonrpc: '2.0',
  id: requestId,
  error: {
    code: -32602,
    message: 'Invalid params',
    data: { field: 'name', issue: 'required' }
  }
}

Unity-Specific Patterns

Command Protocol

Unity communication uses a simple request/response pattern:

const result = await this.unityConnection.send({
  command: 'gameobject_create',
  params: { name: 'Cube', primitiveType: 'cube' }
});
Workspace Root Resolution

ALWAYS include workspaceRoot in commands requiring file paths:

execute(params) {
  return this.unityConnection.send({
    command: 'screenshot_capture',
    workspaceRoot: config.workspaceRoot,
    ...params
  });
}

Testing Patterns

TDD for Handlers
  1. Contract test first: Define expected input/output schema
  2. Mock Unity connection: Isolate handler logic
  3. Test error paths: Invalid input, connection failures
  4. Test happy path last: After contracts are verified
describe('CreateGameObjectHandler', () => {
  it('validates primitiveType enum', async () => {
    const handler = new CreateGameObjectHandler(mockConnection);
    await assert.rejects(
      () => handler.handle({ name: 'Cube', primitiveType: 'invalid' }),
      /primitiveType must be one of/
    );
  });
});
Integration Testing

Test full request/response cycle including JSON-RPC framing:

const stdin = new PassThrough();
const stdout = new PassThrough();
const transport = new HybridStdioServerTransport(stdin, stdout);

stdin.write('Content-Length: 50\r\n\r\n{"jsonrpc":"2.0","id":1,"method":"ping"}');
// Assert stdout contains Content-Length response

Common Mistakes

Mixing framing formats:

  • NEVER: Output NDJSON after receiving Content-Length input
  • ALWAYS: Output Content-Length regardless of input format

Swallowing errors:

  • NEVER: catch (e) { return null; }
  • ALWAYS: Propagate errors with context

Missing validation:

  • NEVER: Trust raw input from clients
  • ALWAYS: Validate with Zod before processing

Blocking stdio:

  • NEVER: Synchronous operations on transport
  • ALWAYS: Use async/await throughout

Handler Registration

Register all handlers in a central index:

// src/handlers/index.js
export function createHandlers(unityConnection) {
  return [
    new SystemPingToolHandler(unityConnection),
    new CreateGameObjectHandler(unityConnection),
    new ScreenshotHandler(unityConnection),
    // ...
  ];
}

Remember

  • Content-Length always: Output framing must be consistent
  • Validate everything: Never trust client input
  • One handler, one tool: Keep handlers focused
  • Test error paths: Most bugs hide in error handling
  • Protocol compliance: Follow JSON-RPC 2.0 exactly

Comments

Markdown supported