Security·· by Michael Wybraniec

Security Practices for MCP Using JSON-RPC

Critical security issues, best solutions, and practical tools for robust and secure MCP systems using JSON-RPC.

Back to Blog

Model Context Protocol (MCP) is a framework for structured communication between clients and servers using JSON-RPC 2.0. It enables fine-grained message exchange in distributed or modular systems, including those involving AI models, modular agents, or service orchestration. When using lightweight protocols like JSON-RPC 2.0 for MCP communication, developers face a number of security challenges. This article outlines the critical issues, best solutions, and practical developer tools to ensure robust and secure MCP systems.

This guide provides a visual and practical overview of how to secure MCP systems, including message signing, session management, and real-world architecture patterns. It is intended for developers and architects building modular, agent-based, or distributed AI systems.

Message Signing and Verification Flow

sequenceDiagram
  participant Client
  participant Server

  Client->>Client: Serialize method + params
  Client->>Client: Sign payload with private key
  Client->>Server: Send JSON-RPC + signature + public key

  Server->>Server: Canonicalize method + params
  Server->>Server: Verify signature with public key
  Server-->>Client: Process or reject

Secure Session Bootstrapping

sequenceDiagram
  participant Coordinator
  participant Agent A
  participant Agent B

  Agent A->>Coordinator: Request session start
  Coordinator->>Agent A: Send session UUID + token
  Agent A->>Agent B: Share context with token
  Agent B->>Coordinator: Verify session + join

  Note over Coordinator: Coordinator tracks roles, rounds, and state

MCP Context Routing

graph TD
  A[Client] -->|JSON-RPC| B[Coordinator]
  B -->|Session Routing| C1[Agent A]
  B -->|Session Routing| C2[Agent B]
  C1 -->|Context Message| C2
  C2 -->|Response| C1
  B -->|Observability| D[Signed Logstore]
AreaCritical IssueWhy It Matters in MCPSolution
🗭 CoordinationNo native multi-party routingMCP often involves orchestrating multiple services or agentsImplement a coordinator service to manage routing, roles, and message relays
🔐 IdentityNo identity/auth/session layerYou must authenticate and verify message originUse DIDs, mTLS, or signature-based identity layers with session tokens
⚠️ ErrorsPoor observability and error semanticsHard to trace failures in contextual model flowsAdd signed logs, structured error codes, and trace dashboards
📦 Data TypesNo binary/complex data supportModel parameters and responses may not fit JSON easilyUse base64 encoding, CBOR, or attach structured payloads outside core JSON-RPC fields
🧪 TestingNo simulation or step-wise toolingNeed for protocol replay and debuggingBuild deterministic test harnesses with protocol replay and agent mocking
🧱 PersistenceNo session/memory across callsContext continuity across model calls needs tracked state and rolesUse Redis or in-memory session store to persist role, round, and message state
💨 Malicious AgentsAny actor can inject harmful logic or contextRogue agents can derail or poison model executionUse sandboxing (e.g., VM2/WASM), behavior validation, and allowlists for context flow
🦮 Session DriftAgents can desync context/round dataLeads to race conditions, hallucinations, or context overwriteUse signed session snapshots, round counters, and coordinator replay tools
🔄 Replay AttacksReuse of valid messages across contextsUndetected, it can manipulate agent behavior or outputTimestamped tokens, nonce validation, or hash chains to ensure freshness
🛨️ Malicious Agent MitigationCompromised agents acting in a valid sessionThreatens system integrityEnforce behavior rules, use secure sandboxes, and validate input/output patterns
🌐 Decentralized Identity (DIDs)Lack of trusted global identity resolutionNeeded for secure trust delegation across agentsAdopt W3C DIDs and resolve keys via DID Documents or key registries
📡 Secure Session ManagementSessions may be hijacked, lost, or inconsistentCritical for model state, context, and replay protectionTrack sessions via coordinator, assign unique IDs, use per-session keys
🔄 Replay and Race ProtectionDuplicate or reordered requests can cause errorsAgents may misbehave or reprocess old dataUse nonces, timestamps, and round counters in every message
CapabilityControlled ByDirectionSide EffectsApproval NeededTypical Use Cases
ToolsModel (LLM)Client → ServerYes (potentially)YesActions, API calls, data manipulation
ResourcesApplicationClient → ServerNo (read-only)Typically noData retrieval, context gathering
PromptsUserServer → ClientNoNoGuided workflows, specialized templates
SamplingServerServer → Client → ServerIndirectlyYesMulti-step tasks, agentic behaviors
  • Frontend: Nuxt 3 (with Nuxt Content & Tailwind)
  • Backend: Node.js / Fastify / Express
  • MCP Coordinator: Custom routing + context logic (stateless + session-aware)
  • Agents: Independent microservices or WASM-based modules
  • Transport: JSON-RPC 2.0 over HTTPS (or mTLS)
  • Security: Signature validation, DIDs, and per-session encryption keys
  • Logs: Signed append-only logs + observability dashboard (Grafana / OpenTelemetry)
graph LR
  FE[Nuxt3 Client] -->|JSON-RPC| COORD[MCP Coordinator]
  COORD --> A1[Agent A]
  COORD --> A2[Agent B]
  COORD --> REDIS[(Session Store)]
  COORD --> LOG[Signed Logs]
  A1 --> RES1[(External API)]
  A2 --> RES2[(LLM or Tool)]
  • Signed JSON-RPC Messages: Create canonical JSON, sign using EdDSA/ECDSA, send with public key or DID.
  • Session Snapshots: Store JSON snapshots per round with signatures.
  • Replay Detection: Use nonce + timestamps + hash chaining.
  • Testing Harness: Build MCP replay tools with mocked agents.
  • Observability: Use OpenTelemetry or log aggregators with structured events.
  • Signature Validation: Client + agent libs should enforce validation before processing.
  • DID Resolver Service: Resolve public keys tied to agent DIDs.
import { sign, verify } from 'crypto';
const method = 'agent.perform';
const params = { input: 'Run test' };
const payload = JSON.stringify({ method, params });
const signature = sign('sha256', Buffer.from(payload), privateKey);

// Send: { method, params, signature, pubKey }

A secure MCP system built with JSON-RPC requires layers of protection: identity, message integrity, context safety, and clear protocol rules. Combining DIDs, signatures, session control, and malicious agent defenses ensures your modular system remains safe, observable, and extensible.

Michael Wybraniec

Michael Wybraniec

Freelance, MCP Servers, Full-Stack Development, Architecture