While tinkering with Agentic MCP servers at work, I ran into JSON-RPC, the slick tech driving parts of the MCP system. Talk about a *remote*ly cool find! It fired up my curiosity, so I dug in.
What’s JSON-RPC?
JSON-RPC is like texting your server to run a function. It’s a lightweight protocol using JSON for messages:
- Request: Names a method, sends params, and tracks with an optional ID.
- Response: Returns a result or error, tied to the ID.
- Notification: A request without an ID—no reply needed.
It vibes over HTTP, WebSocket, or TCP, sticking to JSON-RPC 2.0 for rpc-king simplicity.
Why’s It Awesome?
JSON-RPC makes remote calls a breeze by:
- Playing Nice: JSON works everywhere.
- Staying Lean: Ditches the bloat of SOAP or complex REST.
- Feeling Local: Remote functions act like they’re next door.
- Flexing: Handles requests and one-way notifications.
Perfect for microservices, IoT, or any app needing a fast rpc-ket to the server.
HandsOn
Let’s build a JSON-RPC server and client for cleaner message handling. The server supports:
-
add: Sums two numbers. -
greet: Returns a greeting. -
log: A notification to log a message.
We’ll use nodejs with HTTP for easy implementation.
1. Setup the project
mkdir json-rpc-jam cd json-rpc-jam npm init -y npm install express body-parser jsonrpc-lite 2. Create client and server files.
client.js
const jsonrpc = require("jsonrpc-lite"); async function sendRpcRequest(body, skipResponse = false) { const res = await fetch("http://localhost:3000/rpc", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body), }); if (skipResponse) { return res.status; } return await res.json(); } async function test() { console.log("add([5, 3]):", await sendRpcRequest(jsonrpc.request(1, "add", [5, 3]))); console.log("greet({ name: 'Alice' }):", await sendRpcRequest(jsonrpc.request(2, "greet", { name: "Alice" }))); console.log("notification:", await sendRpcRequest(jsonrpc.notification("log", { message: "Hello" }), true)); console.log("bogus method:", await sendRpcRequest(jsonrpc.request(3, "not-a-method", [5, 3]))); } test(); server.js
const express = require("express"); const jsonrpc = require("jsonrpc-lite"); const bodyParser = require("body-parser"); const app = express(); app.use(bodyParser.json()); const methods = { add: (params) => { if (!Array.isArray(params) || params.length !== 2) throw jsonrpc.JsonRpcError.invalidParams("Bad params, no sum!"); return params[0] + params[1]; }, greet: (params) => { if (!params?.name) throw jsonrpc.JsonRpcError.invalidParams("No name, no fame!"); return `Yo, ${params.name}, what’s good?`; }, log: (params) => { if (!params?.message) throw jsonrpc.JsonRpcError.invalidParams("No message to log!"); console.log(`Notification: ${params.message}`); return null; }, }; app.post("/rpc", (req, res) => { // Parse incoming request with jsonrpc-lite const parsed = jsonrpc.parseObject(req.body); console.log(parsed); // Handle invalid JSON-RPC if (parsed.type === "invalid") { return res .status(400) .json(jsonrpc.error(null, jsonrpc.JsonRpcError.invalidRequest())); } const { type, payload } = parsed; // Handle notifications (no ID) if (type === "notification") { try { if (!methods[payload.method]) throw jsonrpc.JsonRpcError.methodNotFound(); methods[payload.method](payload.params); return res.status(204).send(); } catch (error) { return res.status(204).send(); // Silent for notifications } } // Handle requests if (type === "request") { try { if (!methods[payload.method]) throw jsonrpc.JsonRpcError.methodNotFound(); const result = methods[payload.method](payload.params); res.json(jsonrpc.success(payload.id, result)); } catch (error) { res.status(400).json(jsonrpc.error(payload.id, error)); } } }); app.listen(3000, () => console.log("Server’s JSON-RPC at http://localhost:3000") ); 3. Run both server and client
node server.js # In a different terminal node client.js We will be seeing the following logs in the client terminal
add([5, 3]): { jsonrpc: '2.0', id: 1, result: 8 } greet({ name: 'Alice' }): { jsonrpc: '2.0', id: 2, result: 'Yo, Alice, what’s good?' } notification: 204 bogus method: { jsonrpc: '2.0', id: 3, error: { message: 'Method not found', code: -32601 } } Wrap-Up
JSON-RPC, is a total game-changer for zipping remote calls and notifications with minimal fuss. It’s like sending a quick DM to your server and getting a reply before you can say rpc-ord time!
What’s next? Here are a couple of ideas to take this forward.
- Add Authentication: Secure your JSON-RPC endpoint with JWT or API keys for safe vibes.
- Go WebSocket: Swap HTTP for WebSocket to unlock real-time, two-way rpc-king action.
- Expand Methods: Toss in more complex methods to handle bigger tasks and flex JSON-RPC’s power.
Keep rpc-king it!
Top comments (1)
I've also written a separate blog post covering authentication in more detail. You can check it out here: nkumar37.vercel.app/posts/securing...