Get the FREE Ultimate OpenClaw Setup Guide →

Json Rpc

npx machina-cli add skill martinholovsky/claude-skills-generator/json-rpc --openclaw
Files (1)
SKILL.md
15.5 KB

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

  1. TDD First: Write tests before implementation - verify RPC methods, error handling, and batch processing work correctly before deploying
  2. Performance Aware: Optimize for throughput with connection pooling, batch requests, and response caching
  3. Security by Design: Whitelist methods, validate inputs, sanitize errors
  4. Specification Compliance: Follow JSON-RPC 2.0 exactly

3. Core Responsibilities

Fundamental Duties

  1. Specification Compliance: Implement JSON-RPC 2.0 correctly
  2. Secure Method Dispatch: Validate methods before execution
  3. Proper Error Handling: Use standard error codes, hide internals
  4. 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

CodeMessageMeaning
-32700Parse errorInvalid JSON
-32600Invalid RequestNot valid JSON-RPC
-32601Method not foundMethod doesn't exist
-32602Invalid paramsInvalid method parameters
-32603Internal errorInternal JSON-RPC error
-32000 to -32099Server errorImplementation-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.md for 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.

Source

git clone https://github.com/martinholovsky/claude-skills-generator/blob/main/skills/json-rpc/SKILL.mdView on GitHub

Overview

This skill specializes in JSON-RPC 2.0 protocol implementation, focusing on secure message dispatching, standardized error handling, and efficient batch processing. It emphasizes building compliant RPC servers and clients with robust security measures to prevent leaks and abuse.

How This Skill Works

It enforces the JSON-RPC 2.0 message format for requests and responses, dispatches methods through registered handlers, and processes batch requests with proper validation. Security is built in via method whitelisting, input validation, rate limiting, and sanitized errors, while errors use standard codes to avoid leaking internals.

When to Use It

  • Building a microservice architecture with a JSON-RPC 2.0 gateway
  • Creating an RPC client library to consume multiple services
  • Optimizing batch operation throughput for remote calls
  • Standardizing error handling across internal services
  • Securing RPC endpoints against injection, DoS, and data leakage

Quick Start

  1. Step 1: Define your JSON-RPC 2.0 server skeleton and register allowed methods
  2. Step 2: Implement handlers with input validation and proper error codes, then add batch support
  3. Step 3: Write tests (TDD) for method dispatch, invalid params, and batch scenarios, then run and refine

Best Practices

  • Whitelist acceptable methods and validate all inputs before dispatch
  • Use JSON-RPC 2.0 error codes and avoid exposing internal details
  • Implement rate limiting and request batching with size controls
  • Adopt test-driven development (TDD) to verify method dispatch and errors
  • Treat batch requests atomically and handle partial failures gracefully

Example Use Cases

  • JSON-RPC gateway for a microservice ecosystem with centralized dispatch
  • Client SDKs that encapsulate secure method calls and error translation
  • Batch processing pipeline that reduces round-trips for analytics calls
  • Internal API layer with standardized error reporting across services
  • Secure endpoints with input validation and method whitelisting to prevent abuse

Frequently Asked Questions

Add this skill to your agents
Sponsor this space

Reach thousands of developers