{"id":29211052,"url":"https://github.com/williamw520/zigjr","last_synced_at":"2026-04-18T17:37:25.750Z","repository":{"id":301061953,"uuid":"1008027369","full_name":"williamw520/zigjr","owner":"williamw520","description":"A Lightweight Zig Library for JSON-RPC 2.0","archived":false,"fork":false,"pushed_at":"2025-12-19T00:09:27.000Z","size":667,"stargazers_count":45,"open_issues_count":0,"forks_count":3,"subscribers_count":0,"default_branch":"master","last_synced_at":"2025-12-21T23:25:37.423Z","etag":null,"topics":["json-rpc","mcp","protocol","zig"],"latest_commit_sha":null,"homepage":"","language":"Zig","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/williamw520.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-06-24T23:07:50.000Z","updated_at":"2025-12-19T00:09:34.000Z","dependencies_parsed_at":"2025-06-25T01:21:04.129Z","dependency_job_id":"a243b975-a878-4646-9c7d-3808f100771e","html_url":"https://github.com/williamw520/zigjr","commit_stats":null,"previous_names":["williamw520/zigjr"],"tags_count":14,"template":false,"template_full_name":null,"purl":"pkg:github/williamw520/zigjr","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/williamw520%2Fzigjr","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/williamw520%2Fzigjr/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/williamw520%2Fzigjr/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/williamw520%2Fzigjr/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/williamw520","download_url":"https://codeload.github.com/williamw520/zigjr/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/williamw520%2Fzigjr/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31978658,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-18T17:30:12.329Z","status":"ssl_error","status_checked_at":"2026-04-18T17:29:59.069Z","response_time":103,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["json-rpc","mcp","protocol","zig"],"created_at":"2025-07-02T22:01:01.072Z","updated_at":"2026-04-18T17:37:25.731Z","avatar_url":"https://github.com/williamw520.png","language":"Zig","readme":"# ZigJR - JSON-RPC 2.0 Library for Zig\n\n\u003cstrong\u003e(Note: ZigJR 2.0 is under development. It will introduce some breaking changes. \nThe main changes are for adding session based handling support and multi-thread support.)\u003c/strong\u003e\n\nZigJR is a lightweight Zig library providing a full implementation of the JSON-RPC 2.0 protocol,\nwith message streaming on top, and a smart function dispatcher that turns native Zig functions \ninto RPC handlers. It aims to make building JSON-RPC applications in Zig simple and straightforward.\n\nThis small library is packed with the following features:\n\n* Parsing and composing JSON-RPC 2.0 messages.\n* Support for Request, Response, Notification, and Error JSON-RPC 2.0 messages.\n* Support for batch requests and batch responses in JSON-RPC 2.0.\n* Message streaming via delimiter based streams (`\\n`, etc.).\n* Message streaming via `Content-Length` header-based streams.\n* RPC pipeline to process the full request-to-response lifecycle.\n* Native Zig functions as message handlers with automatic type mapping.\n* Flexible logging mechanism for inspecting the JSON-RPC messages.\n\n## Content\n\n* [Quick Usage](#quick-usage)\n* [Installation](#installation)\n* [Usage](#usage)\n* [Dispatcher](#dispatcher)\n* [RpcDispatcher](#rpcdispatcher)\n* [Custom Dispatcher](#custom-dispatcher)\n* [Invocation and Cleanup](#invocation-and-cleanup)\n* [Handler Function](#handler-function)\n* [Extended Handlers](#extended-handlers)\n* [Universal Message Handling](#universal-message-handling)\n* [Transport](#transport)\n* [Project Build](#project-build)\n* [Examples](#examples)\n* [License](#license)\n* [References](#references)\n\n\n## Quick Usage\n\nThe following example shows a JSON-RPC server registering native Zig functions \nas RPC handlers in a dispatcher's registry and using it to handle \nJSON-RPC messages in a stream from `stdin` to `stdout`.\n\nThe handler functions take in native Zig data types and return native result values \nor errors, which are automatically mapped to the JSON data types.\n\n```zig\n{\n    var rpc_dispatcher = try zigjr.RpcDispatcher.init(alloc);\n    defer rpc_dispatcher.deinit();\n\n    try rpc_dispatcher.add\"say\", say);\n    try rpc_dispatcher.add(\"hello\", hello);\n    try rpc_dispatcher.add(\"hello-name\", helloName);\n    try rpc_dispatcher.add(\"substr\", substr);\n    try rpc_dispatcher.add(\"weigh-cat\", weigh);\n\n    try zigjr.stream.runByDelimiter(alloc, stdin, stdout, \u0026rpc_dispatcher, .{});\n}\n\nfn say(msg: []const u8) void {\n    std.debug.print(\"Message to say: {s}\\n\", .{msg});\n}\n\nfn hello() []const u8 {\n    return \"Hello world\";\n}\n\nfn helloName(alloc: Allocator, name: [] const u8) ![]const u8 {\n    return try std.fmt.allocPrint(alloc, \"Hello {s}\", .{name});\n}\n\nfn substr(name: [] const u8, start: i64, len: i64) []const u8 {\n    return name[@intCast(start) .. @intCast(len)];\n}\n\nfn weigh(cat: CatInfo) f64 {\n    return cat.weight;\n}\n```\nCheck out [hello.zig](examples/hello.zig) for a complete example. \nSee the example on how to obtain the `std.Io.Reader` based `stdin` and `std.Io.Writer` based `stdout`.\n\nHere are some sample request and response JSON messages for testing.\n```\nRequest:  {\"jsonrpc\": \"2.0\", \"method\": \"hello\", \"id\": 1}\nResponse: {\"jsonrpc\": \"2.0\", \"result\": \"Hello world\", \"id\": 1}\n```\n```\nRequest:  {\"jsonrpc\": \"2.0\", \"method\": \"hello-name\", \"params\": [\"Spiderman\"], \"id\": 2}\nResponse: {\"jsonrpc\": \"2.0\", \"result\": \"Hello Spiderman\", \"id\": 2}\n```\n\n## Installation\n\nSelect a version of the library in the [Releases](https://github.com/williamw520/zigjr/releases) page,\nand copy its asset URL. E.g. https://github.com/williamw520/zigjr/archive/refs/tags/1.9.0.zip\n\nUse `zig fetch` to add the ZigJR package to your project's dependencies. Replace `\u003cVERSION\u003e` with the version you selected.\n```shell\nzig fetch --save https://github.com/williamw520/zigjr/archive/refs/tags/\u003cVERSION\u003e.tar.gz\n```\n\nThis command updates your `build.zig.zon` file, adding ZigJR to the `dependencies` section with its URL and content hash.\n\n ```diff\n .{\n    .name = \"my-project\",\n    ...\n    .dependencies = .{\n +       .zigjr = .{\n +           .url = \"zig fetch https://github.com/williamw520/zigjr/archive/refs/tags/\u003cVERSION\u003e.tar.gz\",\n +           .hash = \"zigjr-...\",\n +       },\n    },\n }\n ```\n\nNext, update your `build.zig` to add the ZigJR module to your executable.\n\n```diff\npub fn build(b: *std.Build) void {\n    ...\n+ const zigjr_pkg = b.dependency(\"zigjr\", .{ .target = target, .optimize = optimize });\n+ const zigjr_mod = zigjr_pkg.module(\"zigjr\");      // get the module defined in the pkg.\n    ...\n    const exe1 = b.addExecutable(.{\n        .name = \"my_project\",\n        .root_module = exe1_mod,\n    });\n    ...\n+  exe1.root_module.addImport(\"zigjr\", zigjr_module);\n+  exe2.root_module.addImport(\"zigjr\", zigjr_module);\n+  lib1.root_module.addImport(\"zigjr\", zigjr_module);\n```\n\nThe `.addImport(\"zigjr\")` call makes the library's module available to your executable, \nallowing you to import it in your source files:\n```zig\nconst zigjr = @import(\"zigjr\");\n```\n\n\n## Usage\n\nYou can build JSON-RPC 2.0 applications with ZigJR at different levels of abstraction:\n* **Streaming API:** Handle message frames for continuous communication (recommended).\n* **RPC Pipeline:** Process individual requests and responses.\n* **Parsers and Composers:** Manually build and parse JSON-RPC messages for maximum control.\n\nFor most use cases, the Streaming API is the simplest and most powerful approach.\n\n### Streaming API\nThe following example handles a stream of messages prefixed with a `Content-Length` header, \nreading requests from `stdin` and writing responses to `stdout`.\n```zig\n{\n    var rpc_dispatcher = try zigjr.RpcDispatcher.init(alloc);\n    defer rpc_dispatcher.deinit();\n    try rpc_dispatcher.add(\"add\", addTwoNums);\n\n    try zigjr.stream.runByContentLength(alloc, stdin, stdout, \u0026rpc_dispatcher, .{});\n}\n\nfn addTwoNums(a: i64, b: i64) i64 { return a + b; }\n```\nSee [hello.zig](examples/hello.zig) on how to obtain the `std.Io.Reader` based `stdin` and `std.Io.Writer` based `stdout`.\n\n\nThis example streams messages from one in-memory buffer to another, \nusing a newline character (`\\n`) as a delimiter.\n```zig\n{\n    var rpc_dispatcher = try zigjr.RpcDispatcher.init(alloc);\n    defer rpc_dispatcher.deinit();\n    try rpc_dispatcher.add(\"add\", addTwoNums);\n\n    const req_jsons =\n        \\\\{\"jsonrpc\": \"2.0\", \"method\": \"add\", \"params\": [1, 2], \"id\": 1}\n        \\\\{\"jsonrpc\": \"2.0\", \"method\": \"add\", \"params\": [3, 4], \"id\": 2}\n        \\\\{\"jsonrpc\": \"2.0\", \"method\": \"add\", \"params\": [5, 6], \"id\": 3}\n    ;\n    var reader = std.Io.Reader.fixed(req_jsons);\n\n    var out_buf = std.Io.Writer.Allocating.init(alloc);\n    defer out_buf.deinit();\n\n    try zigjr.stream.runByDelimiter(alloc, \u0026reader, \u0026out_buf.writer, \u0026rpc_dispatcher, .{});\n\n    std.debug.print(\"output_jsons: {s}\\n\", .{out_buf.written()});\n}\n```\n\n### RPC Pipeline\nTo handle individual requests, use the `RequestPipeline`. It abstracts away message parsing, \ndispatching, and response composition.\n\n```zig\n{\n    // Set up the rpc_dispatcher as the dispatcher.\n    var rpc_dispatcher = try zigjr.RpcDispatcher.init(alloc);\n    defer rpc_dispatcher.deinit();\n    try rpc_dispatcher.add(\"add\", addTwoNums);\n    const dispatcher = zigjr.RequestDispatcher.implBy(\u0026rpc_dispatcher);\n\n    // Set up the request pipeline with the dispatcher.\n    var pipeline = try zigjr.RequestPipeline.init(alloc, dispatcher, null);\n    defer pipeline.deinit();\n\n    // Run the individual requests to the pipeline.\n    const response_json1 = try pipeline.runRequestToJson(alloc, \n        \\\\{\"jsonrpc\": \"2.0\", \"method\": \"add\", \"params\": [1, 2], \"id\": 1}\n    );\n    defer alloc.free(response_json1);\n\n    const response_json2 = try pipeline.runRequestToJson(alloc, \n        \\\\{\"jsonrpc\": \"2.0\", \"method\": \"add\", \"params\": [3, 4], \"id\": 2}\n    );\n    defer alloc.free(response_json2);\n\n    const response_json3 = try pipeline.runRequestToJson(alloc, \n        \\\\{\"jsonrpc\": \"2.0\", \"method\": \"add\", \"params\": [5, 6], \"id\": 3}\n    );\n    defer alloc.free(response_json3);\n}\n```\n\n### Parse JSON-RPC Messages\nFor lower-level control, you can parse messages directly into `RpcRequest` objects,\nwhere the request's method, parameters, and request ID can be accessed.\n```zig\nconst zigjr = @import(\"zigjr\");\n{\n    var result = zigjr.parseRpcRequest(alloc,\n        \\\\{\"jsonrpc\": \"2.0\", \"method\": \"func42\", \"params\": [42], \"id\": 1}\n    );\n    defer result.deinit();\n    const req = try result.request();\n    try testing.expect(std.mem.eql(u8, req.method, \"func42\"));\n    try testing.expect(req.arrayParams().?.items.len == 1);\n    try testing.expect(req.arrayParams().?.items[0].integer == 42);\n    try testing.expect(req.id.num == 1);\n}\n```\n`parseRpcRequest()` can parse a single message or a batch of messages.  Use `result.batch()` to get the list of requests in the batch.\n\n### Compose JSON-RPC Messages\nThe `composer` API helps to build valid JSON-RPC messages.\n```zig\nconst zigjr = @import(\"zigjr\");\n{\n    const msg1 = try zigjr.composer.makeRequestJson(alloc, \"hello\", null, zigjr.RpcId { .num = 1 });\n    defer alloc.free(msg1);\n\n    const msg2 = try zigjr.composer.makeRequestJson(alloc, \"hello-name\", [\"Spiderman\"], zigjr.RpcId { .num = 1 });\n    defer alloc.free(msg2);\n}\n```\nSee [composer.zig](src/jsonrpc/composer.zig) for other API methods.  \n\n\n## Dispatcher\nThe dispatcher is the entry point for handling incoming RPC messages. \nAfter a message is parsed, the RPC pipeline feeds it to the dispatcher, \nwhich routes it to a handler function based on the message's `method`. \nThe `RequestDispatcher` and `ResponseDispatcher` interfaces define the required dispatching functions.\n\n## RpcDispatcher\nThe built-in `RpcDispatcher` is a dispatcher with a registry of RPC handlers that also implements the `RequestDispatcher` interface and \nserves as a powerful, ready-to-use dispatcher. Use `RpcDispatcher.add(method_name, function)` \nto register a handler function for a specific JSON-RPC method. When a request comes in, \nthe it looks up the handler from its registry, maps the request's parameters to the function's arguments, \ncalls the function, and captures the result or error to formulate a response.\n\n```zig\n{\n    var rpc_dispatcher = try zigjr.RpcDispatcher.init(alloc);\n    defer rpc_dispatcher.deinit();\n\n    try rpc_dispatcher.add(\"add\", addTwoNums);\n    try rpc_dispatcher.add(\"sub\", subTwoNums);\n    ...\n    const dispatcher = zigjr.RequestDispatcher.implBy(\u0026rpc_dispatcher);\n    ...\n}\n```\n\n## Custom Dispatcher\nYou can provide a custom dispatcher as long as it implements the `dispatch()` and `dispatchEnd()` \nfunctions of the `RequestDispatcher` interface. See the `dispatcher_hello.zig` example for details.\n\n## Invocation and Cleanup\nEach request is processed in two phases: `dispatch()`, which executes the handler, \nand `dispatchEnd()`, which performs per-invocation cleanup (such as freeing memory).\n\n## Handler Function\nMessage handler functions are native Zig functions.\n\n### Scopes\nHandler functions can be defined in the global scope, a struct scope, or a struct instance scope.\n\nFor instance-scoped methods, pass a pointer to the struct instance as the context \nwhen registering the handler. This context pointer will be passed as the first parameter \nto the handler function when it is invoked.\n\n```zig\n{\n    try rpc_dispatcher.add(\"global-fn\", global_fn);\n    try rpc_dispatcher.add(\"group-fn\", Group.group_fn);\n    ...\n    var counter = Counter{};\n    try rpc_dispatcher.addWithCtx(\"counter-inc\", \u0026counter, Counter.inc);\n    try rpc_dispatcher.addWithCtx(\"counter-get\", \u0026counter, Counter.get);\n    ...\n}\n\nfn global_fn() void { }\n\nconst Group = struct {\n    fn group_fn() void { }\n};\n\nconst Counter = struct {\n    count:  i64 = 0;\n\n    fn inc(self: *Counter) void { self.count += 1; }\n    fn get(self: *Counter) i64  { return self.count; }\n};\n```\n\n### Parameters\nHandler function parameters are native Zig types, with a few limitations related \nto JSON compatibility. Parameter types should generally map to JSON types:\n\n*   `bool`: JSON boolean\n*   `i64`: JSON number (compatible with JavaScript's 53-bit safe integer range)\n*   `f64`: JSON number (64-bit float)\n*   `[]const u8`: JSON string\n*   `struct`: JSON object\n\nThere're some light automatic type conversion when the function parameter's type\nand the JSON message's parameter type are closely related. (See `ValueAs()` in json_call.zig for details).\n\nStruct parameters must be deserializable from JSON. The corresponding handler \nparameter's struct must have fields that match the JSON object. ZigJR uses `std.json` \nfor deserialization. Nested objects are supported, and you can implement custom \nparsing by adding a `jsonParseFromValue` function to your struct. See the `std.json` \ndocumentation for details.\n\nHere's an example on using `struct` as parameter and return value of a RPC handler.\n```zig\n{\n    var rpc_dispatcher = try zigjr.RpcDispatcher.init(alloc);\n    try rpc_dispatcher.add(\"weigh-cat\", weighCat);\n    try rpc_dispatcher.add(\"make-cat\", makeCat);\n    try zigjr.stream.runByDelimiter(alloc, stdin, stdout, \u0026rpc_dispatcher, .{});\n}\n\nconst CatInfo = struct {\n    cat_name: []const u8,\n    weight: f64,\n    eye_color: []const u8,\n};\n\nfn weighCat(cat: CatInfo) []const u8 {\n    if (std.mem.eql(u8, cat.cat_name, \"Garfield\")) return \"Fat Cat!\";\n    if (std.mem.eql(u8, cat.cat_name, \"Odin\")) return \"Not a cat!\";\n    if (0 \u003c cat.weight and cat.weight \u003c= 2.0) return \"Tiny cat\";\n    if (2.0 \u003c cat.weight and cat.weight \u003c= 10.0) return \"Normal weight\";\n    if (10.0 \u003c cat.weight ) return \"Heavy cat\";\n    return \"Something wrong\";\n}\n\nfn makeCat(name: []const u8, eye_color: []const u8) CatInfo {\n    const seed: u64 = @truncate(name.len);\n    var prng = std.Random.DefaultPrng.init(seed);\n    return .{\n        .cat_name = name,\n        .weight = @floatFromInt(prng.random().uintAtMost(u32, 20)),\n        .eye_color = eye_color,\n    };\n}\n```\n\n### Special Parameters\n\n#### Context\n\nIf a context pointer is supplied to `RpcDispatcher.addWithCtx()`, it is passed as \nthe first parameter to the handler function, effectively serving as a `self` pointer.\n\nThe first parameter's type and the context type need to be the same.\n\n#### Allocator\nIf an `std.mem.Allocator` is the first parameter of a handler (or the second, \nif a context is used as the first), an arena allocator is passed in. The handler does \nnot need to free memory allocated with it; the arena is automatically reset after the request completes.\nThe arena memory is reset in dispatchEnd() when the dispatching of a request has completed.\n\n#### Value\nTo handle parameters manually, you can use `std.json.Value`:\n* As the **only** parameter: The entire `params` field from the request (`array` or `object`) is passed as a single `std.json.Value`.\n    ```zig\n    fn h1(params: std.json.Value) void { /* ... */ }\n    ```\n* As **one of several** parameters: The corresponding JSON-RPC parameter is passed as a `std.json.Value` without being converted to a native Zig type.\n    ```zig\n    fn h3(a: std.json.Value, b: i64, c: std.json.Value) void { /* ... */ }\n    ```\n\n### Return Value\nThe return value of a handler function is serialized to JSON and becomes the `result` \nof the JSON-RPC response. You can return any Zig type that can be serialized by `std.json`.\n\nIf your function returns a `void`, it is treated as a Notification, and no response message is generated.\n\n#### JSON Return Value\nIf the return value is already a JSON string, you can wrap it in `zigjr.JsonStr` to avoid double-serialization.\nDeclare `zigjr.JsonStr` as the return type of the handler function.\n\n#### `DispatchResult` Return Value\nFor lower-level control, you can return a `DispatchResult` from the handler function.\nYou can set a returning JSON result or set an error in `DispatchResult`.\nTypically the normal error handling just returns the error code to the client.\n`DispatchResult` let you set the error code, error message, and additional error data\nto send back to the client.\n\n#### End-Stream Return Value\nSometimes you want to end the streaming session of JSON-RPC requests and responses\nby a user's command coming from the request. E.g. the client sends a request as,\n```\nRequest:  {\"jsonrpc\": \"2.0\", \"method\": \"end-session\"}\n```\nYour handler handles the `end-session` method and can return a `DispatchResult.end_stream` value\nto tell the calling streaming service to end its session. E.g.\n```zig\n    ...\n    try rpc_dispatcher.add(\"end-session\", endSession);\n    ...\n\nfn endSession() zigjr.DispatchResult {\n    return zigjr.DispatchResult.asEndStream();\n}\n```\nSee [hello_net.zig](examples/hello_net.zig) in TCP model for example. \nSend a `{\"jsonrpc\": \"2.0\", \"method\": \"end-session\"}` message to it for test.\n\n#### Process Termination via JSON-RPC Message\n\nSee the `endServer()` handler in [hello_net.zig](examples/hello_net.zig) for example. \nSend a `{\"jsonrpc\": \"2.0\", \"method\": \"end-server\"}` message to it for test.\n\n### Error\nA handler function can have an error union with the return type. Any error returned will be \npackaged into a JSON-RPC error response with the `InternalError` code.\n\n### Memory Management\nWhen using `RpcDispatcher`, memory management is straightforward. Any memory \nobtained from the allocator passed to a handler is automatically freed \nafter the request completes. Handlers do not need to perform manual cleanup.\nMemory is freed in the `dispatcher.dispatchEnd()` phase.\n\nIf you implement a custom dispatcher, you are responsible for managing the memory's lifecycle.\n\n### Logging\n\nLogging is a great way to learn about a protocol by watching the messages exchanged between\nthe client and server. ZigJR has a built-in logging mechanism to help you inspect messages \nand debug handlers. You can use a pre-built logger or implement a custom one.\n\n#### DbgLogger\nThis example uses a `DbgLogger` in a request pipeline. This logger prints to `stderr`.\n```zig\n    var d_logger = zigjr.DbgLogger{};\n    const pipeline = zigjr.pipeline.RequestPipeline.init(alloc, \n        RequestDispatcher.implBy(\u0026rpc_dispatcher), d_logger.asLogger());\n    \n```\n#### FileLogger\nThis example uses a `FileLogger` in a request stream. This logger writes to a file.\nFile based logging is great in situations where the stdout is not available, e.g.\nwhen running as a sub-process in a MCP host.\n\n```zig\n    var f_logger = try zigjr.FileLogger.init(alloc, \"log.txt\");\n    defer f_logger.deinit();\n    try zigjr.stream.runByDelimiter(alloc, stdin, stdout, \u0026rpc_dispatcher, .{ .logger = f_logger.asLogger() });\n```\n#### Custom Logger\nThis example uses a custom logger in a request pipeline.\n```zig\n{\n    var my_logger = MyLogger{};\n    const pipeline = zigjr.pipeline.RequestPipeline.init(alloc, \n        RequestDispatcher.implBy(\u0026rpc_dispatcher), zigjr.Logger.implBy(\u0026my_logger));\n}\n\nconst MyLogger = struct {\n    count: usize = 0,\n\n    pub fn start(_: MyLogger, _: []const u8) void {}\n    pub fn log(self: *MyLogger, source:[] const u8, operation: []const u8, message: []const u8) void {\n        self.count += 1;\n        std.debug.print(\"LOG {}: {s} - {s} - {s}\\n\", .{self.count, source, operation, message});\n    }\n    pub fn stop(_: MyLogger, _: []const u8) void {}\n};\n```\n\n## Extended Handlers\n\n`RpcDispatcher` supports adding of pre-handler, post handler, error handler and fallback handler\nfor the requests. Before dispatching a request to its method's handler, the\n`OnBeforeFn` extended handler is called, allowing any pre-handling of the request.\nAfter the method's handler for a request has returned successfully, the `OnAfterFn` \nextended handler is called, allowing any post-handling on the request. \nWhen the method's handler returns error, the `OnErrorFn` extended handler is called,\nallowing any error handling on the request before sending it back to the client.\n\nIf a request's method has no registered handler, the `OnFallbackFn` extended handler is called,\nallowing handling of any unknown requests.\n\nThe extended handlers are set up via `setOnBefore()`, `setOnAfter()`, `setOnError()`, and `setOnFallback()` on `RpcDispatcher`.\nOnce set, these extended handlers are applied to all requests, regardless of the request methods.\n\nAn extended handler takes in a context and an Allocator parameters. The context is passed\nin as the first parameter of the setOnXX() functions. The Allocator is the same arena allocator\nfor request handling.\n\nFor example,\n\n```zig\n{\n    var rpc_dispatcher = try zigjr.RpcDispatcher.init(alloc);\n    defer rpc_dispatcher.deinit();\n    rpc_dispatcher.setOnBefore(null, onBefore);\n    rpc_dispatcher.setOnAfter(null, onAfter);\n    rpc_dispatcher.setOnError(null, onError);\n    rpc_dispatcher.setOnFallback(null, onFallback);\n}\n\nfn onBefore(_: *anyopaque, _: Allocator, req: RpcRequest) void {\n    std.debug.print(\"Before handling request, method: {s}, id: {any}\\n\", .{req.method, req.id});\n}\n\nfn onAfter(_: *anyopaque, _: Allocator, req: RpcRequest, res: DispatchResult) void {\n    std.debug.print(\"After handling request, method: {s}, id: {any}, result: {any}\\n\", .{req.method, req.id, res});\n}\n\nfn onError(_: *anyopaque, _: Allocator, req: RpcRequest, err: anyerror) void {\n    std.debug.print(\"After handling request, method: {s}, id: {any}, error: {any}\\n\", .{req.method, req.id, err});\n}\n\nfn onFallback(_: *anyopaque, _: Allocator, req: RpcRequest) anyerror!DispatchResult {\n    std.debug.print(\"Unknown request, method: {s}, id: {any}\\n\", .{req.method, req.id});\n    return DispatchResult.asNone(); // return a .none result to discard the request.\n}\n```\n\n## Universal Message Handling\n\nSome servers (e.g. LSP server) can send both responses to a client's requests and \nits own server-to-client requests in the same channel. A client needs to be able\nto handle both JSON RPC responses and requests from the server in the same channel. \nZigJR provides universal message handling functions to handle message that is either a request or a response.\n\n* `stream.messagesByContentLength()`: handles a stream of mixed requests and responses.\n* `rpc_pipeline.runMessage()`: handles one message of either a request or response.\n* `message.parseRpcMessage()`: parses one message of either a request or response.\n\nSee the [lsp_client.zig](examples/lsp_client.zig) example on how to handle a mix of requests and responses\nin a stream.\n\n\n## Transport\n\nA few words on message transport. ZigJR doesn't deal with transport at all. \nIt sits on top of any transport, network or others.\nIt's assumed the JSON-RPC messages are sent over some transport before arriving at ZigJR.\n\n## Project Build\n\nYou do not need to build this project if you are only using it as a library \nvia `zig fetch`. To run the examples, clone the repository and run `zig build` to build the project.\nThe example binaries will be located in `zig-out/bin/`.\n\n## Examples\n\nThe project has a number of examples showing how to build applications with ZigJR.\n\n* [hello.zig](examples/hello.zig): Showcase the basics of handler function registration and the streaming API.\n* [calc.zig](examples/calc.zig): Showcase different kinds of handler functions.\n* [dispatcher_hello.zig](examples/dispatcher_hello.zig): Custom dispatcher.\n* [mcp_hello.zig](examples/mcp_hello.zig): A basic MCP server written from the ground up.\n* [lsp_client.zig](examples/lsp_client.zig): A LSP client interacting with LSP server.\n* [hello_net.zig](examples/hello_net.zig): A JSON-RPC server over network (HTTP or TCP).\n\nCheck out [examples](examples) for other examples.\n\n### Run Examples Interactively\nRunning the programs interactively is a great way to experiment with the handlers.\nJust type in the JSON requests and see the result.\n```\nzig-out/bin/hello\n```\nThe program will wait for input. Type or paste the JSON-RPC request and press Enter.\n```\n{\"jsonrpc\": \"2.0\", \"method\": \"hello\", \"id\": 1}\n```\nIt will print the JSON result.\n```\n{\"jsonrpc\": \"2.0\", \"result\": \"Hello world\", \"id\": 1}\n```\n\nOther sample requests,\n```\n{\"jsonrpc\": \"2.0\", \"method\": \"hello-name\", \"params\": [\"Foobar\"], \"id\": 1}\n```\n```\n{\"jsonrpc\": \"2.0\", \"method\": \"hello-name\", \"params\": [\"Spiderman\"], \"id\": 1}\n```\n```\n{\"jsonrpc\": \"2.0\", \"method\": \"hello-xtimes\", \"params\": [\"Spiderman\", 3], \"id\": 1}\n```\n```\n{\"jsonrpc\": \"2.0\", \"method\": \"say\", \"params\": [\"Abc Xyz\"], \"id\": 1}\n```\n\n### Run Examples with Data Files\nYou can also run the examples by piping test data from a file, which is useful for creating repeatable tests.\n```\nzig-out/bin/hello \u003c data/hello.json\nzig-out/bin/hello \u003c data/hello_name.json\nzig-out\\bin/hello \u003c data/hello_xtimes.json\nzig-out/bin/hello \u003c data/hello_say.json\nzig-out/bin/hello \u003c data/hello_stream.json\n```\n\nSome more sample data files.  Examine the data files in the [Data](data) directory to \nsee how they exercise the message handlers.\n```\nzig-out/bin/calc.exe \u003c data/calc_add.json\nzig-out/bin/calc.exe \u003c data/calc_weight.json\nzig-out/bin/calc.exe \u003c data/calc_sub.json\nzig-out/bin/calc.exe \u003c data/calc_multiply.json\nzig-out/bin/calc.exe \u003c data/calc_divide.json\nzig-out/bin/calc.exe \u003c data/calc_divide_99.json\nzig-out/bin/calc.exe \u003c data/calc_divide_by_0.json\n```\n\n### Run the MCP Server Example\n\nThe `mcp_hello` executible can be run standalone on a console for testing its message handling,\nor run as an embedded subprocess in a MCP host.\n\n#### Standalone Run\n\nRun it standalone. Feed the MCP requests by hand.\n\n```\n{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"initialize\",\"params\":{\"protocolVersion\":\"2025-03-26\",\"clientInfo\":{\"name\":\"mcphost\",\"version\":\"1.0.0\"},\"capabilities\":{}}}\n```\n```\n{\"jsonrpc\":\"2.0\",\"method\":\"notifications/initialized\",\"params\":{}}\n```\n```\n{\"jsonrpc\":\"2.0\",\"id\":2,\"method\":\"tools/list\",\"params\":{}}\n```\n```\n{\"jsonrpc\":\"2.0\",\"id\":3,\"method\":\"tools/call\",\"params\":{\"name\":\"hello\",\"arguments\":{}}}\n```\n```\n{\"jsonrpc\":\"2.0\",\"id\":3,\"method\":\"tools/call\",\"params\":{\"name\":\"hello-name\",\"arguments\":{\"name\":\"Mate\"}}}\n```\n\n#### Embedded in a MCP Host\n\nThis uses [MCP Host](https://github.com/mark3labs/mcphost) as an example.  \n\nCreate a configuration file `config-mcp-hello.json` with `command` pointing to the mcp_hello executible.\n```json\n{\n  \"mcpServers\": {\n    \"mcp-hello\": {\n      \"command\": \"/zigjr/zig-out/bin/mcp_hello.exe\",\n      \"args\": []\n    }\n  }\n}\n```\n\nRun `mcphost` with one of the LLM providers.\n```\nmcphost --config config-mcp-hello.json --provider-api-key YOUR-API-KEY --model anthropic:claude-3-5-sonnet-latest\nmcphost --config config-mcp-hello.json --provider-api-key YOUR-API-KEY --model openai:gpt-4\nmcphost --config config-mcp-hello.json --provider-api-key YOUR-API-KEY --model google:gemini-2.0-flash\n```\n\nType `hello`, `hello Joe` or `hello Joe 10` in the prompt for testing. The `log.txt` file captures the interaction.\n\n### Run the LSP Client Example\n\nThe LSP client \n\n**(Note: This example is not working after migrating to Zig 0.15.1, pending update.)**\n\nThe LSP client example is a rudimentary LSP client illustrating how to build a JSON RPC client.\nIt spawns a LSP server as a sub-process, communicating to it via its stdin and stdout.\nIt creates a thread for `request_worker()` to send LSP requests to the server's stdin\nand another thread for `response_worker()` to read LSP responses and requests from the server's stdout.\n\nSince a LSP server can send both responses to client's requests and its own server-to-client requests,\nlsp_client uses `stream.messagesByContentLength()` to handle both incoming JSON RPC responses and requests.\n\nIt uses `RpcDispatcher`, `ExtHandlers` and `ResponseDispatcher` to handle the requests and responses in\na central place.\n\nThe `request_worker()` sends a number of LSP requests to the server to illustrate how the LSP protocol works.\n- `initialize` - tells the LSP server the client's capabilities and starts the session.\n- `initialized` - tells the server the client is ready; server will send requests to client after this.\n- `textDocument/didOpen` - tells the server a file has been loaded; send the file content over.\n- `textDocument/definition` - gets the definition at a position of the file.\n- `textDocument/hover` - gets hovering information at a position of the file.\n- `textDocument/signatureHelp` - gets function signature description.\n- `textDocument/completion` - gets completion information to complete typing by the user.\n- `shutdown`\n- `exit`\n\n#### Sample Runs of lsp_client\n\nThis runs the ZLS executible as the embedded LSP server. Runs the lsp_client in minimum mode.\n```shell\n    lsp_client  /opt/zls/zls.exe\n```\n\nThis dumps the LSP message's payload as JSON string.\n```shell\n    lsp_client --json /opt/zls/zls.exe\n```\n\nThis pretty-prints the LSP message's JSON payload.\n```shell\n    lsp_client --pp-json /opt/zls/zls.exe\n```\n\nThis dumps the LSP message in raw format, i.e. with the message frame headers.\n```shell\n    lsp_client --dump /opt/zls/zls.exe\n```\n\nThe `--stderr` option pipes the LSP server subprocess' stderr to stderr.\n```shell\n    lsp_client --dump --stderr /opt/zls/zls.exe\n```\n\n### Run the Network Server Example\n\n```shell\n    hello_net --tcp\n```\nRuns the `hello_net` server in TCP mode. Use clients like `telnet` or `netcat` to interact with the server.\nThe JSON-RPC messages are delimited with LF.  See `data/hello.json` for sample data.\n```shell\nnc64 localhost 35354 \u003c data\\hello.json\nnc64 localhost 35354 \u003c data\\hello_name.json\n```\n\n```shell\n    hello_net --http\n```\nRuns the `hello_net` server in HTTP mode. Use a client like `curl` for testing.\n```shell\ncurl localhost:35354 --request POST --json @data/hello.json\ncurl localhost:35354 --request POST --json @data/hello_name.json\n```\n\n\n## License\n\nZigJR is [MIT licensed](./LICENSE).\n\n## References\n\n- [JSON-RPC 2.0 Specification](https://www.jsonrpc.org/specification)\n- [MCP Schema](https://github.com/modelcontextprotocol/modelcontextprotocol/tree/main/schema)\n- [LSP Specification](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/)\n\n","funding_links":[],"categories":["Network \u0026 Web"],"sub_categories":["Network"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwilliamw520%2Fzigjr","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fwilliamw520%2Fzigjr","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwilliamw520%2Fzigjr/lists"}