MCP Protocol Architecture

Tier 2 | Deep technical documentation for MCP server development Hub: README.md | Full Architecture: ARCHITECTURE.md


Overview

Model Context Protocol (MCP) integration enables Claude Desktop to orchestrate nexus-agents. The system implements the MCP 2025-11-25 specification.

Key Capabilities

  • Tool Definitions - Zod-validated tool schemas
  • Structured Output - JSON and text content blocks
  • Resource Management - Dynamic context exposure
  • Error Handling - Tool errors vs protocol errors

MCP Server Architecture

┌─────────────────────────────────────────────────────────────┐
│                    Claude Desktop                            │
│                         │                                    │
│                    MCP Protocol                              │
│                         │                                    │
└─────────────────────────┼───────────────────────────────────┘

┌─────────────────────────▼───────────────────────────────────┐
│                  MCP Server (nexus-agents)                   │
│  ┌────────────────────────────────────────────────────────┐ │
│  │                    Tool Registry                        │ │
│  │  orchestrate │ delegate │ expert │ workflow │ ...      │ │
│  └──────────────────────────┬─────────────────────────────┘ │
│                             │                                │
│  ┌──────────────────────────▼─────────────────────────────┐ │
│  │              Validation Layer (Zod)                     │ │
│  └──────────────────────────┬─────────────────────────────┘ │
│                             │                                │
│  ┌──────────────────────────▼─────────────────────────────┐ │
│  │              Agent Orchestration                        │ │
│  │  Orchestrator │ Expert Pool │ Consensus │ Workflows    │ │
│  └────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘

Tool Design Pattern

server.tool(
  'tool_name',
  {
    // Zod schema with descriptions (Claude uses these)
    param: z.string().describe('What this parameter does'),
    optional: z.number().optional().describe('Optional parameter'),
  },
  async (args) => {
    // 1. Validate input (already done by SDK, but double-check business rules)
    const validated = Schema.safeParse(args);
    if (!validated.success) {
      return {
        isError: true,
        content: [{ type: 'text', text: validated.error.message }],
      };
    }

    // 2. Execute with proper error handling
    try {
      const result = await doWork(validated.data);
      return {
        content: [{ type: 'text', text: JSON.stringify(result) }],
      };
    } catch (error) {
      return {
        isError: true,
        content: [{ type: 'text', text: error.message }],
      };
    }
  }
);

Tool Design Rules

  1. Clear names - Verb-noun format: create_expert, run_workflow
  2. Detailed descriptions - Claude uses these to decide when to call
  3. Zod validation - All inputs validated at tool boundary
  4. Tool errors vs protocol errors - Use isError: true for tool failures
  5. Structured output - Support both structuredContent and TextContent

Tool Interface

interface ITool {
  readonly name: string;
  readonly description: string;
  readonly inputSchema: z.ZodSchema;

  execute(input: unknown): Promise<ToolResult>;
}

interface IToolRegistry {
  register(tool: ITool): void;
  get(name: string): ITool | undefined;
  list(): ToolInfo[];
  unregister(name: string): boolean;
}

interface ToolResult {
  isError?: boolean;
  content: ToolContentBlock[];
}

type ToolContentBlock =
  | { type: 'text'; text: string }
  | { type: 'image'; data: string; mimeType: string }
  | { type: 'resource'; uri: string; mimeType?: string };

Registered Tools

ToolDescriptionKey Parameters
orchestrateTask orchestration with expert coordinationtask, context, maxIterations
create_expertCreate a specialized expert agentrole, modelPreference
execute_expertExecute a task using a created expertexpertId, task, context
run_workflowExecute a workflow templatetemplate, inputs, dryRun
delegate_to_modelRoute task to optimal modeltask, preferredCapability
consensus_voteMulti-model consensus votingproposal, strategy, quickMode
list_expertsList available expert typesformat
list_workflowsList available workflow templatescategory, format
research_queryQuery research registryaction, techniqueId, query
research_addAdd paper to registry by arXiv IDarxivId, topic, priority
research_discoverDiscover papers/repos from external sourcestopic, source, maxResults
research_analyzeAnalyze registry for gaps, trends, coveragefocus, topic
research_catalog_reviewReview auto-cataloged research referencesaction, identifier, topic
research_synthesizeSynthesize registry into topic clusterstopicFilter
memory_queryQuery across all memory backendsquery, limit, source
memory_statsMemory system statistics dashboardincludeDecay, includePromotion
memory_writeWrite entry to a specific memory backendbackend, key, value, metadata
weather_reportMulti-CLI performance weather reportcli, category, includeAdaptive
issue_triageTriage GitHub issues with trust classificationissueUrl, dryRun
run_graph_workflowExecute graph-based workflows with checkpointingworkflow, inputs
execute_specExecute AI software factory spec pipelinespec, dryRun
registry_importGenerate draft model registry entryprovider, modelId, dryRun
repo_analyzeAnalyze a GitHub repository structurerepoUrl, includeSecurityGaps
repo_security_planGenerate security scanning pipeline for a reporepoUrl, analysis
query_traceQuery execution trace JSONL files from diskrunId, eventType, limit

Tool Examples

orchestrate

// Input
{ task: "Review this code for security issues", context: { file: "auth.ts" } }

// Output
{ summary: "...", experts_consulted: ["security", "code"], recommendations: [...] }

workflow

// Input
{ template: "code-review", inputs: { url: "https://github.com/..." }, dryRun: false }

// Output
{ status: "completed", steps: [...], output: "..." }

Resource Exposure

Dynamic context exposure to Claude:

server.resource('project_context', 'text/markdown', async () => {
  const content = await readFile('CLAUDE.md', 'utf-8');
  return { content };
});

// Claude can read with:
// resource://nexus-agents/project_context

Resource Types

ResourceMIME TypePurpose
project_contexttext/markdownCLAUDE.md instructions
architecturetext/markdownARCHITECTURE.md summary
configapplication/jsonCurrent configuration
routing_statsapplication/jsonRouting performance data

Testing MCP Tools

import { InMemoryTransport } from '@modelcontextprotocol/sdk/inMemory.js';

describe('MCP Tools', () => {
  let client: Client;
  let server: Server;

  beforeEach(async () => {
    // Create linked transport pair
    const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();

    server = createServer();
    client = new Client({ name: 'test-client' });

    await server.connect(serverTransport);
    await client.connect(clientTransport);
  });

  it('should orchestrate task', async () => {
    const result = await client.callTool({
      name: 'orchestrate',
      arguments: { task: 'Test task' },
    });

    expect(result.isError).toBe(false);
    expect(result.content).toHaveLength(1);
  });
});

Error Handling

Tool Errors (Recoverable)

Use for expected failures:

return {
  isError: true,
  content: [
    {
      type: 'text',
      text: 'Validation failed: task cannot be empty',
    },
  ],
};

Protocol Errors (Unexpected)

Throw for unexpected failures:

throw new McpError(ErrorCode.InternalError, 'Database connection failed');

Error Categories

CategoryisErrorThrowExample
ValidationtrueNoInvalid input format
Not FoundtrueNoExpert type doesn’t exist
Rate LimitedtrueNoToo many requests
InternalNoYesDatabase failure
ProtocolNoYesMCP spec violation

Extension: Adding a New Tool

1. Define Tool Interface

// src/mcp/tools/my-tool.ts
import { z } from 'zod';
import { ITool, ToolResult } from '../types.js';

const InputSchema = z.object({
  param: z.string().describe('What this does'),
  option: z.number().optional().describe('Optional setting'),
});

export const myTool: ITool = {
  name: 'my_tool',
  description: 'Does something useful',
  inputSchema: InputSchema,

  async execute(input: unknown): Promise<ToolResult> {
    const args = InputSchema.parse(input);
    // ... implementation
    return {
      content: [{ type: 'text', text: JSON.stringify(result) }],
    };
  },
};

2. Register with Server

// src/mcp/index.ts
import { myTool } from './tools/my-tool.js';

export function registerTools(server: McpServer, registry: IToolRegistry): void {
  registry.register(myTool);
  // ... other tools
}

3. Add Tests

// src/mcp/tools/my-tool.test.ts
describe('my_tool', () => {
  it('should handle valid input', async () => {
    const result = await myTool.execute({ param: 'test' });
    expect(result.isError).toBeFalsy();
  });

  it('should reject invalid input', async () => {
    await expect(myTool.execute({})).rejects.toThrow();
  });
});

Configuration

mcp:
  server:
    name: nexus-agents
    version: 2.2.0
    capabilities:
      tools: true
      resources: true
      prompts: true # 4 prompts registered

  transport: stdio # stdio | tcp
  # tcp:
  #   port: 3000
  #   host: localhost

  tools:
    orchestrate:
      maxTurns: 10
      timeout: 300000 # 5 minutes
    workflow:
      dryRunDefault: false

Claude Desktop Integration

Setup

Add to .mcp.json in your project root (or use claude mcp add-json):

{
  "mcpServers": {
    "nexus-agents": {
      "command": "nexus-agents",
      "args": ["--mode=server"]
    }
  }
}

Verification

# Test MCP server starts correctly
nexus-agents --mode=server --verbose

# Check tool registration
# Look for: "Registered tool: orchestrate"

Source Files

FilePurpose
src/mcp/index.tsServer creation and setup
src/mcp/tools/Tool implementations
src/mcp/resources/Resource handlers
src/mcp/types.tsType definitions
src/cli/cli-server.tsServer mode entry point

Protocol Reference

  • Specification: modelcontextprotocol.io
  • SDK Version: @modelcontextprotocol/sdk@1.27.1
  • Protocol Version: 2025-11-25