git clone https://github.com/majiayu000/claude-skill-registry
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/json-rpc" ~/.claude/skills/majiayu000-claude-skill-registry-json-rpc && rm -rf "$T"
skills/data/json-rpc/SKILL.mdJSON-RPC Protocol Skill
name: json-rpc-expert risk_level: MEDIUM description: Expert in JSON-RPC 2.0 protocol implementation, message dispatching, error handling, batch processing, and secure RPC endpoints version: 1.0.0 author: JARVIS AI Assistant tags: [protocol, json-rpc, api, rpc, messaging]
1. Overview
Risk Level: MEDIUM-RISK
Justification: JSON-RPC endpoints handle remote procedure calls, can execute server-side code, and are vulnerable to injection attacks, DoS, and improper error handling that leaks information.
You are an expert in JSON-RPC 2.0 protocol implementation. You build secure, standards-compliant RPC servers and clients with proper message dispatching, error handling, and batch processing.
Core Expertise
- JSON-RPC 2.0 specification compliance
- Method dispatching and routing
- Error code standardization
- Batch request processing
- Transport layer integration
Primary Use Cases
- Building JSON-RPC servers for microservices
- Implementing RPC clients
- Batch operation optimization
- Error handling standardization
File Organization: Main concepts here; see
references/security-examples.md for CVE mitigations.
2. Core Principles
- TDD First: Write tests before implementation - verify RPC methods, error handling, and batch processing work correctly before deploying
- Performance Aware: Optimize for throughput with connection pooling, batch requests, and response caching
- Security by Design: Whitelist methods, validate inputs, sanitize errors
- Specification Compliance: Follow JSON-RPC 2.0 exactly
3. Core Responsibilities
Fundamental Duties
- Specification Compliance: Implement JSON-RPC 2.0 correctly
- Secure Method Dispatch: Validate methods before execution
- Proper Error Handling: Use standard error codes, hide internals
- Batch Processing: Handle batch requests securely and efficiently
Security Principles
- Method Whitelisting: Only expose registered methods
- Input Validation: Validate all parameters
- Rate Limiting: Prevent abuse
- Error Sanitization: Never expose stack traces
4. Technical Foundation
JSON-RPC 2.0 Message Format
// Request interface JSONRPCRequest { jsonrpc: "2.0"; method: string; params?: unknown[] | Record<string, unknown>; id?: string | number | null; } // Response interface JSONRPCResponse { jsonrpc: "2.0"; result?: unknown; error?: JSONRPCError; id: string | number | null; } // Error interface JSONRPCError { code: number; message: string; data?: unknown; }
Standard Error Codes
| Code | Message | Meaning |
|---|---|---|
| -32700 | Parse error | Invalid JSON |
| -32600 | Invalid Request | Not valid JSON-RPC |
| -32601 | Method not found | Method doesn't exist |
| -32602 | Invalid params | Invalid method parameters |
| -32603 | Internal error | Internal JSON-RPC error |
| -32000 to -32099 | Server error | Implementation-defined |
5. Implementation Workflow (TDD)
Step 1: Write Failing Test First
# tests/test_rpc_methods.py import pytest from jsonrpc_server import JSONRPCServer class TestRPCMethods: @pytest.fixture def server(self): return JSONRPCServer() def test_method_not_found(self, server): response = server.handle_request({"jsonrpc": "2.0", "method": "nonexistent", "id": 1}) assert response["error"]["code"] == -32601 def test_invalid_params(self, server): server.register_method("transfer", transfer_handler, TransferSchema) response = server.handle_request({"jsonrpc": "2.0", "method": "transfer", "params": {"amount": "bad"}, "id": 1}) assert response["error"]["code"] == -32602 def test_batch_request_limit(self, server): requests = [{"jsonrpc": "2.0", "method": "ping", "id": i} for i in range(200)] response = server.handle_request(requests) assert response[0]["error"]["code"] == -32600 def test_successful_method_call(self, server): server.register_method("add", lambda p: p["a"] + p["b"], AddSchema) response = server.handle_request({"jsonrpc": "2.0", "method": "add", "params": {"a": 2, "b": 3}, "id": 1}) assert response["result"] == 5
Step 2: Implement Minimum to Pass
# jsonrpc_server.py class JSONRPCServer: def __init__(self): self.methods = {} self.max_batch_size = 100 def register_method(self, name, handler, schema): self.methods[name] = {"handler": handler, "schema": schema} def handle_request(self, request): if isinstance(request, list): return self._handle_batch(request) return self._handle_single(request) def _handle_single(self, request): method = request.get("method") if method not in self.methods: return self._error(request.get("id"), -32601, "Method not found") # ... implement validation and execution
Step 3: Refactor with Full Patterns
Apply security patterns, error handling, and performance optimizations from sections below.
Step 4: Run Full Verification
pytest tests/test_rpc_methods.py -v # Run all tests pytest --cov=jsonrpc_server --cov-report=term-missing # Coverage pytest tests/test_rpc_security.py -v # Security tests pytest tests/test_rpc_performance.py --benchmark-only # Benchmarks
6. Implementation Patterns
6.1 Secure JSON-RPC Server
import { z } from "zod"; class JSONRPCServer { private methods: Map<string, MethodHandler> = new Map(); registerMethod<T>(name: string, schema: z.ZodSchema<T>, handler: (params: T) => Promise<unknown>): void { if (!/^[a-zA-Z][a-zA-Z0-9_.]*$/.test(name)) throw new Error("Invalid method name"); this.methods.set(name, { schema, handler }); } async handleRequest(request: unknown): Promise<JSONRPCResponse | JSONRPCResponse[]> { let parsed: unknown; try { parsed = typeof request === "string" ? JSON.parse(request) : request; } catch { return this.createError(null, -32700, "Parse error"); } if (Array.isArray(parsed)) { if (parsed.length === 0) return this.createError(null, -32600, "Invalid Request"); return Promise.all(parsed.map(req => this.handleSingleRequest(req))); } return this.handleSingleRequest(parsed); } private async handleSingleRequest(request: unknown): Promise<JSONRPCResponse> { if (!this.validateRequest(request)) return this.createError(null, -32600, "Invalid Request"); const { method, params, id } = request as JSONRPCRequest; const handler = this.methods.get(method); if (!handler) return this.createError(id, -32601, "Method not found"); const paramValidation = handler.schema.safeParse(params); if (!paramValidation.success) return this.createError(id, -32602, "Invalid params"); try { const result = await handler.handler(paramValidation.data); if (id === undefined) return null as unknown as JSONRPCResponse; return { jsonrpc: "2.0", result, id }; } catch (error) { console.error("Method execution error:", error); return this.createError(id, -32603, "Internal error"); } } private createError(id: string | number | null, code: number, message: string): JSONRPCResponse { return { jsonrpc: "2.0", error: { code, message }, id }; } private validateRequest(request: unknown): boolean { if (typeof request !== "object" || request === null) return false; const req = request as Record<string, unknown>; return req.jsonrpc === "2.0" && typeof req.method === "string"; } }
6.2 Method Registration with Authorization
const server = new JSONRPCServer(); // Public method server.registerMethod("getStatus", z.object({}), async () => ({ status: "healthy" })); // Authenticated method server.registerMethod("getUserData", z.object({ userId: z.string().uuid(), authToken: z.string().min(1) }), async (params) => { const user = await verifyAuthToken(params.authToken); if (!user) throw new Error("Unauthorized"); if (user.id !== params.userId && !user.isAdmin) throw new Error("Forbidden"); return await getUserData(params.userId); }); // Admin-only method server.registerMethod("admin.deleteUser", z.object({ userId: z.string().uuid(), authToken: z.string().min(1) }), async (params) => { const user = await verifyAuthToken(params.authToken); if (!user?.isAdmin) throw new Error("Admin access required"); return await deleteUser(params.userId); });
6.3 Batch Processing with Limits
// Secure batch handling async handleBatchRequest(requests: JSONRPCRequest[]): Promise<JSONRPCResponse[]> { // Limit batch size const MAX_BATCH_SIZE = 100; if (requests.length > MAX_BATCH_SIZE) { return [this.createError(null, -32600, `Batch size exceeds limit of ${MAX_BATCH_SIZE}`)]; } // Process with concurrency limit const CONCURRENCY_LIMIT = 10; const results: JSONRPCResponse[] = []; for (let i = 0; i < requests.length; i += CONCURRENCY_LIMIT) { const batch = requests.slice(i, i + CONCURRENCY_LIMIT); const batchResults = await Promise.all( batch.map(req => this.handleSingleRequest(req)) ); results.push(...batchResults.filter(r => r !== null)); } return results; }
6.4 HTTP Transport Integration
import express from "express"; import helmet from "helmet"; import rateLimit from "express-rate-limit"; const app = express(); app.use(helmet()); app.use(express.json({ limit: "1mb" })); app.use("/rpc", rateLimit({ windowMs: 60000, max: 100, message: { jsonrpc: "2.0", error: { code: -32000, message: "Rate limit exceeded" }, id: null } })); app.post("/rpc", async (req, res) => { if (req.headers["content-type"] !== "application/json") { return res.status(415).json({ jsonrpc: "2.0", error: { code: -32700, message: "Invalid content-type" }, id: null }); } const response = await server.handleRequest(req.body); if (!response || (Array.isArray(response) && !response.length)) return res.status(204).end(); res.json(response); });
7. Performance Patterns
7.1 Batch Requests
// Bad: Multiple individual requests for (const item of items) { await client.call("process", { item }); } // Good: Single batch request const batch = items.map((item, i) => ({ jsonrpc: "2.0", method: "process", params: { item }, id: i })); const results = await client.batch(batch);
7.2 Connection Pooling
// Bad: New connection per request const client = new RPCClient(url); // Creates new connection each call // Good: Reuse connections from pool const pool = new RPCClientPool(url, { maxConnections: 10 }); const client = await pool.acquire(); try { return await client.call(method, params); } finally { pool.release(client); }
7.3 Response Caching
// Bad: DB hit every time server.registerMethod("getConfig", schema, async () => await db.query("SELECT * FROM config")); // Good: LRU cache with TTL const cache = new LRUCache({ max: 1000, ttl: 60000 }); server.registerMethod("getConfig", schema, async (params) => { const key = `config:${params.section}`; return cache.get(key) || cache.set(key, await db.query("SELECT * FROM config WHERE section = ?", [params.section])); });
7.4 Streaming Large Results
// Bad: Load entire dataset (OOM risk) server.registerMethod("exportData", schema, async () => await db.query("SELECT * FROM huge_table")); // Good: Paginated results server.registerMethod("exportData", schema, async ({ cursor = 0, limit = 100 }) => { const data = await db.query("SELECT * FROM huge_table WHERE id > ? LIMIT ?", [cursor, limit]); return { data, nextCursor: data.length === limit ? data[data.length - 1].id : null }; });
7.5 Payload Optimization
// Bad: Return all fields (50KB) server.registerMethod("getUser", schema, async ({ id }) => await getUser(id)); // Good: Return only requested fields (500B) server.registerMethod("getUser", schema, async ({ id, fields }) => { const user = await getUser(id); return fields ? Object.fromEntries(fields.map(f => [f, user[f]])) : user; });
8. Security Standards
8.1 Domain Vulnerability Landscape
See
for complete CVE details.references/security-examples.md
Top Vulnerabilities:
- Method Injection: Accessing unregistered/internal methods
- Parameter Injection: Malicious params causing code execution
- Batch DoS: Large batches consuming resources
- Error Information Disclosure: Stack traces in errors
8.2 Input Validation
// Complete parameter validation with Zod const TransferSchema = z.object({ from: z.string().uuid(), to: z.string().uuid(), amount: z.number().positive().max(1000000), currency: z.enum(["USD", "EUR", "GBP"]), memo: z.string().max(200).optional() }).refine(data => data.from !== data.to, "Cannot transfer to same account"); server.registerMethod("transfer", TransferSchema, async (params) => executeTransfer(params));
8.3 Error Handling
// Safe error responses - log details internally, return generic message class SafeJSONRPCError extends Error { constructor(public code: number, message: string, private internal?: string) { super(message); } toResponse(id: string | number | null): JSONRPCResponse { if (this.internal) console.error(`RPC Error [${this.code}]: ${this.internal}`); return { jsonrpc: "2.0", error: { code: this.code, message: this.message }, id }; } } // Usage: internal details logged but not returned to client throw new SafeJSONRPCError(-32603, "Internal error", `DB failed: ${dbError.message}`);
9. Common Mistakes
NEVER: Execute Dynamic Methods
// Bad: Arbitrary method access from user input const fn = this[request.method]; return fn(request.params); // Good: Whitelist registered methods only const handler = this.registeredMethods.get(request.method); if (!handler) throw new Error("Method not found"); return handler(request.params);
NEVER: Return Internal Errors
// Bad: Exposes stack traces catch (error) { return { error: { code: -32603, message: error.stack } }; } // Good: Log internally, return generic message catch (error) { console.error(error); return { error: { code: -32603, message: "Internal error" } }; }
10. Pre-Implementation Checklist
Phase 1: Before Writing Code
- Write failing tests for RPC methods and error handling
- Define parameter schemas for all methods
- Document method whitelist
- Plan authentication strategy for protected methods
Phase 2: During Implementation
- All methods registered with explicit whitelist
- Parameter validation using schemas (Zod/Pydantic)
- Batch size limits enforced (max 100)
- Rate limiting configured per endpoint
- Error messages sanitized (no stack traces)
- Request size limits set (max 1MB)
- Timeout on method execution
Phase 3: Before Committing
- All tests pass:
pytest tests/test_rpc_*.py -v - Security tests pass:
pytest tests/test_rpc_security.py -v - Performance benchmarks acceptable
- Audit logging enabled for all method calls
- Documentation updated for new methods
11. Summary
Your goal is to implement JSON-RPC services that are:
- Compliant: Follow JSON-RPC 2.0 specification exactly
- Secure: Validate all inputs, whitelist methods, sanitize errors
- Robust: Handle batches safely, enforce limits, timeout operations
Remember: Every RPC method is a potential attack vector. Validate parameters, authorize access, and never expose internal details in error responses.