Skip to content

MCP Protocol Implementation

overthelex edited this page May 17, 2026 · 3 revisions

MCP Protocol Implementation

SecondLayer implements the Model Context Protocol (MCP) to expose legal research tools to LLM clients. The implementation strictly follows the JSON-RPC 2.0 specification and supports multiple MCP protocol versions for broad client compatibility.

Triple Transport Architecture

The server exposes the same tool registry through three independent transport layers, allowing integration with desktop apps, web applications, and remote LLM services.

Transport Endpoint Protocol Primary Clients
stdio Process stdin/stdout MCP stdio Claude Desktop, local CLI
HTTP REST POST /api/tools/:toolName JSON over HTTP Web frontend, mobile apps
Remote MCP SSE GET /v1/sse, POST /sse JSON-RPC 2.0 over SSE / Streamable HTTP ChatGPT, Claude.ai, remote MCP clients

Transport Selection

  • stdio: Used when the server is spawned as a child process by a local MCP client (e.g., Claude Desktop via mcp.json configuration).
  • HTTP REST: Traditional request-response pattern. Supports optional SSE streaming via POST /api/tools/:toolName/stream or Accept: text/event-stream header.
  • Remote MCP SSE: Full MCP protocol over the network. Two sub-modes:
    • SSE transport (GET /v1/sse): Persistent event-stream connection with message routing via POST /v1/sse?sessionId=...
    • Streamable HTTP (POST /sse): Single request-response per JSON-RPC message, compatible with OpenAI's MCP integration.

Data Flow

graph TD
    subgraph Clients
        C1[Claude Desktop]
        C2[Web App / Mobile]
        C3[ChatGPT / Claude.ai]
    end

    subgraph Transport
        T1[stdio]
        T2[HTTP REST]
        T3[Remote MCP SSE]
    end

    subgraph Core
        TR[ToolRegistry]
        H[BaseToolHandler instances]
        RC[RemoteServiceClient]
    end

    subgraph External
        RADA[RADA Server :3001]
        OR[OpenReyestr Server :3005]
    end

    C1 --> T1
    C2 --> T2
    C3 --> T3

    T1 --> TR
    T2 --> TR
    T3 --> TR

    TR --> H
    TR --> RC
    RC --> RADA
    RC --> OR
Loading

MCP Protocol Compliance

Supported Protocol Versions

The server negotiates the protocol version during the initialize handshake:

  • 2024-11-05 (MCP baseline)
  • 2025-03-26 (OpenAI ChatGPT)
  • 2025-11-05
  • 2025-11-25

If the client requests an unrecognized version, the server defaults to 2025-11-05.

JSON-RPC 2.0 Methods

Method Direction Description
initialize Client -> Server Protocol handshake; server returns capabilities and version
tools/list Client -> Server Returns available tool definitions with JSON Schema inputs
tools/call Client -> Server Execute a tool by name with arguments
prompts/list Client -> Server List available prompts (currently empty)
resources/list Client -> Server List available resources (currently empty)
ping Client -> Server Health check; server returns { pong: true }
notifications/progress Server -> Client Progress updates during tool execution
notifications/* Client -> Server Fire-and-forget notifications (acknowledged with 202)

Server Capabilities

Returned during initialize:

{
  "protocolVersion": "2025-11-05",
  "capabilities": {
    "tools": {
      "listChanged": false
    }
  },
  "serverInfo": {
    "name": "SecondLayer Legal MCP Server",
    "version": "1.0.0"
  }
}

Error Codes

Standard JSON-RPC 2.0 error codes are used:

Code Meaning
-32601 Method not found
-32602 Invalid params (e.g., missing tool name)
-32603 Internal error
-32000 Application error (tool execution failure, insufficient credits, service unavailable)

SSE Transport Details

Connection Lifecycle (Streamable HTTP mode)

ChatGPT and similar clients use the Streamable HTTP pattern where each JSON-RPC message is a separate POST /sse request:

sequenceDiagram
    participant Client
    participant Server

    Client->>Server: POST /sse {method: "initialize"}
    Server-->>Client: JSON-RPC result (capabilities)
    Note over Server: Mcp-Session-Id header set

    Client->>Server: POST /sse {method: "tools/list"}
    Server-->>Client: JSON-RPC result (tool definitions)

    Client->>Server: POST /sse {method: "tools/call", params: {name, arguments}}
    Server-->>Client: JSON-RPC result (tool output)
Loading

In Streamable HTTP mode (POST), the response Content-Type is application/json. Notifications are suppressed -- only the final result (message with id) is returned.

Connection Lifecycle (SSE stream mode)

Standard MCP SSE clients use GET /v1/sse to open a persistent stream, then send messages via POST /v1/sse?sessionId=<id>:

sequenceDiagram
    participant Client
    participant Server

    Client->>Server: GET /v1/sse (Bearer auth)
    Server-->>Client: event: endpoint (session URL)
    Note over Server: SSEServerTransport created

    Client->>Server: POST /v1/sse?sessionId=abc {method: "tools/list"}
    Server-->>Client: event: message (tool definitions)

    Client->>Server: POST /v1/sse?sessionId=abc {method: "tools/call"}
    Server-->>Client: event: message (notifications/progress)
    Server-->>Client: event: message (tool result)
Loading

SSE messages use the format:

event: message
data: {"jsonrpc":"2.0","id":1,"result":{...}}

Keepalive pings (: ping\n\n) are sent every 30 seconds to prevent connection timeout.

HTTP REST Streaming

The HTTP API also supports SSE streaming for long-running tools:

POST /api/tools/:toolName/stream
Content-Type: application/json
Accept: text/event-stream

-> event: connected    (tool name, timestamp)
-> event: progress     (intermediate updates)
-> event: complete     (final result)
-> event: end          (stream closed)

Tool Registry and Discovery

Architecture

The ToolRegistry class is the central dispatcher. It maintains:

  1. Local handlers -- BaseToolHandler subclasses registered at startup (26 handler classes)
  2. Remote tool routes -- Proxied to RADA and OpenReyestr services via HTTP

Tool definitions follow the MCP standard schema:

interface ToolDefinition {
  name: string;
  description: string;
  inputSchema: {
    type: "object";
    properties: Record<string, JSONSchema>;
    required?: string[];
  };
  annotations?: {
    title?: string;
    readOnlyHint?: boolean;
    destructiveHint?: boolean;
    idempotentHint?: boolean;
    openWorldHint?: boolean;
  };
}

Tool Categories

Category Prefix Examples Source
Court Decisions -- search_court_decisions, edrsr_get_decision_dispositive Local
Legislation -- search_legislation, get_legislation_article Local
Legal Analysis -- build_legal_decision, analyze_data Local
Document Vault -- store_document, semantic_search, list_documents Local
Open Data -- search_edrnpa, search_public_spending Local
OSINT osint_ osint_search_sanctions, osint_check_domain_reputation Local
Parliament rada_ rada_search_parliament_bills, rada_get_deputy_info Remote (RADA)
Business Registry openreyestr_ openreyestr_search_entities, openreyestr_get_beneficiaries Remote (OpenReyestr)

Unified Gateway

When ENABLE_UNIFIED_GATEWAY=true, the tool registry aggregates tools from all three services behind a single endpoint. Remote tools are prefixed (rada_*, openreyestr_*) and proxied transparently.

Discovery Endpoint

GET /mcp returns a public discovery document (rate-limited):

{
  "protocolVersion": "2024-11-05",
  "serverInfo": {
    "name": "SecondLayer Legal MCP Server",
    "version": "1.0.0",
    "description": "Ukrainian legal research and document analysis platform"
  },
  "capabilities": {
    "tools": { "count": 45, "listChanged": false },
    "prompts": {},
    "resources": {}
  },
  "endpoints": {
    "sse": "/sse",
    "sse-standard": "/v1/sse",
    "http": "/api/tools"
  },
  "tools": [{ "name": "...", "description": "..." }]
}

Timeout Configuration

Each tool has a configurable execution timeout. Overrides exist for expensive operations:

Tool Timeout
search_court_decisions 120s
build_legal_decision 120s
search_public_spending 120s
edrsr_court_decisions_by_court 90s
analyze_data 45s
OSINT tools 35s
Default (all others) 60s

Authentication

All transports require authentication. The server supports three credential types:

Method Format Use Case
JWT Authorization: Bearer <jwt> (contains .) Web sessions, authenticated users
OAuth 2.0 Authorization: Bearer mcp_token_* ChatGPT Custom Connectors, Claude.ai
API Key Authorization: Bearer <key> Programmatic access, CI/CD

OAuth 2.0 Discovery (RFC 8414 / RFC 9728)

The server implements full OAuth discovery for MCP clients that support dynamic registration:

Endpoint Purpose
/.well-known/oauth-authorization-server RFC 8414 Authorization Server Metadata
/.well-known/oauth-protected-resource RFC 9728 Protected Resource Metadata
/.well-known/openid-configuration OpenID Connect Discovery
/sse/.well-known/oauth-protected-resource Resource metadata scoped to SSE endpoint
OPTIONS /sse Returns MCP-Auth-* headers for OAuth configuration

OAuth flow:

  1. Client discovers authorization server via /.well-known/oauth-protected-resource
  2. Dynamic client registration at POST /oauth/register
  3. Authorization code flow with PKCE (S256 or plain)
  4. Token exchange at POST /oauth/token
  5. Access token used as Bearer credential for MCP calls

When authentication fails, the server returns HTTP 401 with a WWW-Authenticate header containing the resource_metadata URL per RFC 9728.

Batch Execution

The HTTP transport supports batch tool calls in a single request:

POST /api/tools/batch
Content-Type: application/json

{
  "calls": [
    { "name": "search_court_decisions", "arguments": { "query": "..." } },
    { "name": "get_legislation_article", "arguments": { "law": "...", "article": "..." } }
  ]
}

All calls execute in parallel. Each call is independently tracked and results are returned as an array.

Usage Tracking

Every tool execution (across all transports) creates a tracking record that captures:

  • Tool name and input parameters
  • Execution duration (ms)
  • Completion status (completed / failed)
  • Associated user or API key

This enables per-user usage metering and rate limiting enforcement.

Implementation Files

File Responsibility
mcp_backend/src/api/mcp-sse-server.ts MCPSSEServer class -- Streamable HTTP handler, session management
mcp_backend/src/routes/mcp-sse-routes.ts Express router -- all /sse, /v1/sse, /mcp, OAuth discovery routes
mcp_backend/src/routes/tool-execution-routes.ts HTTP REST tool execution (/api/tools/*)
mcp_backend/src/api/tool-registry.ts ToolRegistry -- central tool dispatch, remote aggregation
mcp_backend/src/api/base-tool-handler.ts BaseToolHandler abstract class -- tool definition and execution interface
mcp_backend/src/factories/tool-services.ts Handler registration (26 handlers wired at startup)
mcp_backend/src/index.ts stdio transport entry point

Clone this wiki locally