How MCP Servers Actually Work: JSON-RPC 2.0, SSE, and the 3 Primitives
Connecting language models to production databases, internal APIs, or developer environments requires a standardized interface. The Model Context Protocol (MCP) establishes this link by defining a clean boundary between semantic reasoning and stateful execution. Instead of building custom integrations for every tool, clients interface with a single protocol definition that translates LLM intent into structured execution.
To run MCP in production, engineers must look past high-level abstractions. Implementing secure agent systems requires a clear understanding of the JSON-RPC 2.0 wire format, transport layer negotiation, capabilities handshake, and the security boundaries of the core primitives.
The Communication Protocol: JSON-RPC 2.0
MCP implements the JSON-RPC 2.0 specification as its communication protocol. This stateless, transport-agnostic standard utilizes structured requests, responses, and notifications serialized in JSON. It provides deterministic message exchange, standard error codes, and batch processing, separating semantic logic from the transport implementation.
JSON-RPC 2.0 establishes a strict contract for all client-server exchanges. Every payload exchanged is a JSON object that adheres to the protocol specification, ensuring that client libraries and server runtimes can parse messages regardless of the underlying runtime language.
The Wire Format
The protocol separates traffic into requests, responses, and notifications:
- Requests are dispatched by the client when an execution result is required. They must contain a unique transaction identifier.
- Responses are returned by the server to answer a specific request, matching the transaction identifier.
- Notifications are one-way messages that do not contain an identifier and expect no response from the receiver.
Here is the exact request schema sent by a client to invoke a tool:
{
"jsonrpc": "2.0",
"id": "tx-098",
"method": "tools/call",
"params": {
"name": "fetch_database_schema",
"arguments": {
"table_name": "users"
}
}
}
The server processes the parameters and returns a structured response matching the request identifier:
{
"jsonrpc": "2.0",
"id": "tx-098",
"result": {
"content": [
{
"type": "text",
"text": "{\"columns\": [\"id\", \"email\", \"created_at\"], \"primary_key\": \"id\"}"
}
]
}
}
When a server needs to announce a state change without blocking for a response, it dispatches a notification frame:
{
"jsonrpc": "2.0",
"method": "notifications/resources/list-changed"
}
Error Handling Specification
If a transaction fails, the server must bypass the standard result object and return an error block containing a numerical code and message:
{
"jsonrpc": "2.0",
"id": "tx-098",
"error": {
"code": -32601,
"message": "Method not found: 'fetch_database_schema' is not registered on this server",
"data": {
"available_methods": ["get_metrics", "post_alert"]
}
}
}
The specification reserves standard error codes to indicate transport or parser-level failures:
| Code | Label | Cause |
|---|---|---|
-32700 | Parse Error | The server received invalid JSON data. |
-32600 | Invalid Request | The payload lacks a required JSON-RPC field. |
-32601 | Method Not Found | The requested procedure does not exist. |
-32602 | Invalid Params | The arguments fail schema validation. |
-32603 | Internal Error | An unhandled exception occurred during execution. |
Transport Layers: Streamable HTTP, SSE, and stdio
MCP decouples message payloads from physical transmission using three transport layers: Streamable HTTP for stateless edge routing, Server-Sent Events (SSE) for persistent server-to-client streaming, and stdio (standard input/output) for local process communication. Streamable HTTP represents the production standard for secure, horizontal scaling.
While the JSON-RPC wire format remains identical, the choice of transport dictates the scalability, latency, and security characteristics of your deployment.
+-------------------------------------------------------------+
| JSON-RPC 2.0 Payload |
+-------------------------------------------------------------+
|
+---------------------+---------------------+
| | |
v v v
+--------------+ +--------------+ +--------------+
| Streamable | | Server-Sent | | stdio |
| HTTP | | Events (SSE)| | Subprocess |
+--------------+ +--------------+ +--------------+
| | |
v v v
[Edge/Production] [Legacy/Push] [Local Dev/IDE]
1. Streamable HTTP
Streamable HTTP is the modern standard for production networks. Under this model, the client transmits JSON-RPC requests via standard HTTP POST requests. If the request triggers a long-running background process, the server initiates an SSE stream to push intermediate execution statuses back to the client.
- Stateless Operations: Individual request frames do not require persistent TCP sockets, making load balancing simple.
- Edge Compatibility: Runs directly inside edge containers like Vinkius Edge, Cloudflare Workers, and serverless runtimes.
- Network Simplicity: Uses standard HTTPS on port 443, eliminating firewall routing complexity.
2. Server-Sent Events (SSE)
In legacy server deployments, the client opens a persistent, uni-directional SSE connection to the server’s /sse route to receive events. The client then posts execution requests to a separate /messages route, referencing its session identifier.
Client Server
| |
|--- GET /sse (Establish Event Source Stream) --------->|
|<-- 200 OK (Connection Open, Keep-Alive) --------------|
| |
|--- POST /messages (payload: tools/call, id: 1) ------>|
|<-- 202 Accepted --------------------------------------|
| |
|<-- Event (payload: JSON-RPC result, id: 1) -----------| (Via SSE Stream)
| |
This model requires session sticky routing (session affinity) at the load balancer, which limits horizontal scale-out and introduces memory overhead on the host node.
3. Standard I/O (stdio)
The stdio transport acts as a local loopback. The client spawns the MCP server as an isolated child process and uses stdin/stdout pipes for communication.
import { spawn } from 'child_process';
const mcpServer = spawn('node', ['server.js']);
// Send JSON-RPC payload to standard input
mcpServer.stdin.write(JSON.stringify(requestPayload) + '\n');
// Read JSON-RPC responses from standard output
mcpServer.stdout.on('data', (data) => {
const response = JSON.parse(data.toString());
});
This model provides zero network security and is restricted to local development environments, command-line interfaces, and desktop client integrations.
The Initialization Handshake
The MCP initialization handshake is a three-step negotiation that establishes protocol versions and active capability sets between the client and server. The client requests connection capabilities, the server replies with its supported tools, resources, or prompts, and the client sends an initialized notification to begin operation.
Before any request to read context or execute a tool can proceed, the handshake contract must be verified.
Client Server
| |
|--- initialize (Protocol Version, Client Capabilities) ->|
|<-- Response (Server Capabilities, Version Contract) ---|
| |
|--- notifications/initialized (Handshake Confirmed) ---->|
| |
|================= Active Session Open ==================|
Handshake Sequence Trace
Step 1: The client requests initialization, declaring its version and supported client features:
{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2025-03-26",
"capabilities": {
"roots": {
"listChanged": true
}
},
"clientInfo": {
"name": "Cursor-IDE",
"version": "0.45.0"
}
}
}
Step 2: The server evaluates the requested version and replies with its capabilities list:
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"protocolVersion": "2025-03-26",
"capabilities": {
"tools": {
"listChanged": true
},
"resources": {
"subscribe": true,
"listChanged": true
}
},
"serverInfo": {
"name": "postgres-mcp",
"version": "2.1.0"
}
}
}
Step 3: The client acknowledges the capabilities contract and unlocks the session:
{
"jsonrpc": "2.0",
"method": "notifications/initialized"
}
The Three Core Primitives: Tools, Resources, and Prompts
The Model Context Protocol defines three core primitives: Tools for model-controlled execution, Resources for application-controlled read-only context, and Prompts for user-controlled templated instructions. Each primitive enforces a distinct authority model to control how and when external data or actions are executed.
These primitives map actions to different security contexts.
+---------------------------+
| Model Context Protocol |
+---------------------------+
|
+-------------------------+-------------------------+
| | |
v v v
+--------------+ +--------------+ +--------------+
| Tools | | Resources | | Prompts |
| (Model-Decided) | | (App-Decided) | | (User-Decided) |
+--------------+ +--------------+ +--------------+
| | |
v v v
State Mutation Read-Only Context Static Templates
(High Risk) (Medium Risk) (Lowest Risk)
1. Tools
Tools are schema-defined functions exposed by the server. The client shares these schemas with the LLM, which parses them and constructs arguments.
{
"name": "update_lead_status",
"description": "Modifies lead status fields inside the CRM",
"inputSchema": {
"type": "object",
"properties": {
"lead_id": { "type": "string" },
"status": { "type": "string", "enum": ["CONTACTED", "QUALIFIED", "LOST"] }
},
"required": ["lead_id", "status"]
}
}
When the LLM decides to update a lead, the client issues a tools/call request. Because the LLM defines the input values dynamically, tools present a significant security risk for state mutation.
2. Resources
Resources provide read-only context to ground the LLM. They are identified by structured URIs (e.g., db://billing/invoices/latest) and loaded directly by the client application.
{
"uri": "api://crm/leads/schema",
"name": "CRM Lead Properties Schema",
"mimeType": "application/json",
"description": "Metadata listing all valid custom attributes for lead records"
}
Clients request the contents of a resource using resources/read. Since the client application determines when to load resources, the LLM cannot access them without application approval.
3. Prompts
Prompts are predefined instruction templates managed by the server. Users select a prompt from the client interface, providing arguments that the server uses to assemble a system prompt populated with live context.
{
"name": "analyze_system_logs",
"description": "Generates diagnostic context from infrastructure alerts",
"arguments": [
{
"name": "server_id",
"description": "Target server host identifier",
"required": true
}
]
}
Since prompts require explicit user selection, they are the most secure way to guide LLM interactions.
The Governance Gap: Production-Ready MCP Security
Raw MCP lacks native security, authentication, or access controls, creating severe vulnerabilities to prompt injection and context leaks. Securing MCP in production requires an intercepting gateway like Vinkius Edge to enforce role-based access control (RBAC), perform Data Loss Prevention (DLP) filtering, and log cryptographic audit trails.
Raw MCP servers act as unauthenticated proxies. If a server is connected to an LLM without security layers, a prompt injection attack could trick the model into executing dangerous commands.
LLM (Non-Deterministic) -> [ Prompt Injection Risk ]
|
v
+-------------------+
| Vinkius Edge | <-- Enforces Auth, RBAC, DLP,
| Gateway | and Cryptographic Auditing
+-------------------+
|
v
+-------------------+
| Raw MCP Server | <-- Executes commands in a
| (Unsecured) | protected environment
+-------------------+
To close this gap, Vinkius Edge introduces a secure gateway between the client and the MCP server:
- Role-Based Access Control (RBAC): Grants granular tokens (e.g., read-only tokens for resources, write-only tokens for tools).
- Data Loss Prevention (DLP): Scans tool outputs and redacts sensitive information (like API keys, passwords, or personal data) before it reaches the LLM.
- Context Bleeding Prevention: Vinkius Edge servers use strict output schemas. Unlike raw servers using
JSON.stringify(), which can leak entire database records, our gateway filters and serializes only declared fields. - Cryptographic Auditing: Signatures verify the origin and parameters of every tool call, creating a clear audit trail for compliance.
Configuring Vinkius Edge MCP Servers
Configuring remote MCP servers through Vinkius Edge involves declaring the gateway URL and authentication token in the host client’s server configuration file. This centralized setup allows local LLM clients like Cursor or Claude Desktop to execute managed remote tools without storing raw credentials locally.
To configure a secure server connection, update your client configuration file (e.g., mcp.json or mcpServers.json):
{
"mcpServers": {
"github-mcp": {
"url": "https://edge.vinkius.com/mcp/github?token=vnk_live_79a2c89f4b1e5c"
},
"postgresql-mcp": {
"url": "https://edge.vinkius.com/mcp/postgres?token=vnk_live_38d2f10b7a4e6c"
}
}
}
The gateway manages credentials on the backend. When a client calls a tool, the gateway validates the token, retrieves the required keys from secure vault storage, and forwards the authorized request to the target server.
Related Guides & Resources
Explore our technical guides to learn more about Model Context Protocol architecture, security patterns, and deployment options:
- How to Convert OpenAPI to MCP Server — Step-by-step guide to generating MCP schemas from REST APIs.
- How to Connect MCP Servers Guide — Quickstart integration walkthrough.
- MCP Server Security: Attack Vectors & Defense — Hardening systems against prompt injection and confused deputy attacks.
- Context Bleeding: How JSON.stringify() Leaks Databases — Mitigating security vulnerabilities in tool serialization.
- What is MCP? The Complete Guide — A high-level introduction to the protocol and its capabilities.
Start Building
Ready to deploy your first MCP integration? Browse our App Catalog to discover pre-built, managed servers — or build your own with Vurb.ts and deploy to our managed edge network.
The protocol provides a flexible standard for AI integrations. By combining JSON-RPC 2.0 with Vinkius Edge security layers, you can build secure, production-grade agent systems that scale.
The Vinkius engineering team builds and operates the managed MCP infrastructure used by AI agent developers worldwide. Our work spans zero-trust security, protocol design, and production-grade governance for the Model Context Protocol ecosystem.
Your agents need tools. We make them safe.
Pick an MCP server from the catalog. Subscribe. Copy the URL. Paste it into Claude, Cursor, or any client. One URL — DLP, audit trail, and kill switch included.
V8 sandbox isolation · Semantic DLP · Cryptographic audit trail · Emergency kill switch
