Agent skill
json-rpc
Install this agent skill to your Project
npx add-skill https://github.com/martinholovsky/claude-skills-generator/tree/main/skills/json-rpc
SKILL.md
JSON-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
references/security-examples.mdfor complete CVE details.
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.
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
prompt-engineering
Expert skill for prompt engineering and task routing/orchestration. Covers secure prompt construction, injection prevention, multi-step task orchestration, and LLM output validation for JARVIS AI assistant.
windows-ui-automation
Expert in Windows UI Automation (UIA) and Win32 APIs for desktop automation. Specializes in accessible, secure automation of Windows applications including element discovery, input simulation, and process interaction. HIGH-RISK skill requiring strict security controls for system access.
accessibility-wcag
devsecops-expert
Expert DevSecOps engineer specializing in secure CI/CD pipelines, shift-left security, security automation, and compliance as code. Use when implementing security gates, container security, infrastructure scanning, secrets management, or building secure supply chains.
kanidm-expert
Expert in Kanidm modern identity management system specializing in user/group management, OAuth2/OIDC, LDAP, RADIUS, SSH key management, WebAuthn, and MFA. Deep expertise in secure authentication flows, credential policies, access control, and platform integrations. Use when implementing identity management, SSO, authentication systems, or securing access to infrastructure.
motion-design
Didn't find tool you were looking for?