Repository navigation
Conversation
vedang
commented
Jun 12, 2026
Contributor
- The client support is inspired by the integration-test format used by clojure-lsp
- The specification changes are done keeping the java-sdk as the comparison target
Use these example servers for integration testing against an imaginary client that I am in the process of building.
…tion framework - Copy the entrypoint and client.clj files with minor changes - Modify lsp.clj to mcp.clj, with minor changes - Create an empty integration.helper file for compilation purposes Right now, there is no integration test running, but the plan is to run the servers and do basic integration testing from the point of view of the client.
Modify the integration.client framework to use our example servers when running integration tests against the client.
Implement previously stubbed requests: resources/templates/list,
resources/subscribe|unsubscribe, logging/setLevel, completion/complete,
and the cancelled/progress notification receivers. Add server->client
senders (list_changed x3, resources/updated, logging message with
RFC-5424 level filtering, progress) and request helpers (roots/list,
sampling/createMessage). Advertise full default capabilities. Fix ping
to return {} per schema (Result must be an object).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add notify-progress! (mirrors the server-side sender) and add-root!/remove-root! which update client state and automatically send notifications/roots/list_changed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Server (http_server.clj): Pedestal + Jetty on a single /mcp endpoint. POST routes JSON-RPC requests into a per-session jsonrpc4clj endpoint and answers with the matching response; notifications/responses get 202. GET opens an SSE stream for server-initiated messages; DELETE terminates the session. Sessions are keyed by the Mcp-Session-Id header assigned on initialize. Client (http_client.clj): clj-http sender loop POSTing outgoing messages, session id capture from the initialize response, and an SSE listener feeding server-initiated messages into the client input channel. run!/shutdown! mirror the stdio client API. io_chan.clj gains public message->json-str/json-str->message so HTTP and stdio share identical wire conventions. Pedestal pinned to 0.8.1 to match the logger's pedestal.log. JSON-RPC batching is intentionally unsupported (removed in the 2025-06-18 spec revision). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Some JSON providers (cheshire under Babashka, jsonista without an explicit provider) return nil for nil input instead of throwing. At stream EOF readLine returns nil, the nil leaked through json-str->message and crashed the reader thread with 'Can't put nil on channel' — leaving the input channel open, so stdio servers never shut down when their client closed stdin (integration harness then hung on process deref). Verified: bb integration suite against the calculator server now passes (4 tests, 11 assertions) and the server process exits cleanly on EOF. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
README gains Client Usage and Streamable HTTP Transport sections; CHANGELOG records the new protocol surface, transports, dependencies and the io-chan EOF fix; todo.org closes the client, roots, sampling, integration-test and HTTP transport items. Task plan/progress notes in .agents/plans/. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Version is maj.min.x where x is the git commit count at build time (unchanged). maj.min moves from a hardcoded string in build.clj to a VERSION file, bumped by 'make release-major' (maj+1, min reset to 0) and 'make release-minor' (min+1). Each release target requires a clean working copy and commits the bump on its own (jj, with git fallback). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
tools/call, prompts/get, resources/read and completion/complete now run inside a promesa thread (eventually macro), so a slow handler never blocks the jsonrpc message loop. jsonrpc4clj resolves future-returning handlers on completion, which gives concurrent request processing: pings, list requests and parallel tool calls are served while a slow tool runs. Matches mcp-java-sdk's async server semantics that clojure-mcp relies on. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add unregister-tool!/unregister-resource!/unregister-prompt!. The context now carries a :server* atom that start! fills; while a server is attached, register-*!/unregister-*! automatically send the matching list_changed notification (mcp-java-sdk add/remove parity). Registration during create-context! happens before start! and stays silent. The Streamable HTTP transport (one endpoint per session) intentionally does not set :server*; broadcast explicitly there. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Validate tool :annotations in the ::tool spec (title plus the four boolean hints). Allow overriding advertised capabilities through the :capabilities key of the server spec. Negotiate protocol version 2025-06-18, preferring the latest. Closes the remaining small parity gaps with mcp-java-sdk usage in clojure-mcp. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
bench/ holds two identical stdio MCP servers (mcp-java-sdk 1.1.3 using clojure-mcp's exact interop pattern, and this SDK) driven by one SDK-neutral raw-JSON client. Measures startup-to-initialize, serial RTT (ping / 24B echo / 64KB echo) and 100 pipelined 20ms tool calls. Run with 'make bench' (AOT-compiles the cljc server first). Headline results (full report in .agents/plans/...benchmark__done/): - java-sdk silently LOSES concurrent async tool responses (299/300 at depth 100): unicast Sinks.Many tryEmitNext is not concurrency-safe (StdioServerTransportProvider.java:158) and the error is swallowed. Ours: 0 lost, 34ms wall. - startup: 893ms java vs 977ms ours AOT-compiled (3012ms from source; core.async compile-at-load accounts for ~1.6s). - serial RTT and 64KB payloads at parity. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A handler return value that is a map with :content passes through coerce-tool-response untouched, so handlers can set :isError (tool errors the LLM should see, per the spec) and :structuredContent directly. Needed by the clojure-mcp port, whose tools signal isError through their continuation. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A resource read handler can return either a single contents map or a sequence of them, matching the schema (ReadResourceResult.contents is a list). Needed by the clojure-mcp port, whose resource-fns return a list of strings. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
vedang
added a commit
that referenced
this pull request
Jun 12, 2026
CI installs zprint 'latest' (now 1.3.0), which wraps two long forms in validate-spec! and the :tool/properties spec that older zprint left on one line — so 'make format' produced changes and the formatting gate failed (also blocking PR #5, whose CI runs on the merge into main). Format those forms, and port the explicit $(ZPRINT_CONFIG) pass-through to the Makefile so CI (no ~/.zprint.edn) applies the repo's :fn-map/:map config deterministically. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
CI installs zprint 'latest' (now 1.3.0), which wraps two long forms in validate-spec! and the :tool/properties spec that older zprint left on one line — so 'make format' produced changes and the formatting gate failed (also blocking PR #5, whose CI runs on the merge into main). Format those forms, and port the explicit $(ZPRINT_CONFIG) pass-through to the Makefile so CI (no ~/.zprint.edn) applies the repo's :fn-map/:map config deterministically. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
make check runs check-tagref first, which calls the tagref binary. The GitHub runner never installed tagref, so PR CI failed with 'tagref not found'. Download a pinned tagref release (v1.12.1, x86_64-unknown-linux-gnu) to /usr/local/bin before the check steps. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
make check runs check-cljkondo, which calls the clj-kondo binary. The GitHub runner never installed clj-kondo, so PR CI failed with 'clj-kondo: command not found'. Add the clj-kondo input to the setup-clojure step so the action provisions it. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
clojure.org's install guide recommends Temurin 25, so bump the JDK from 17 to 25 (and setup-java v4 -> v5.2.0). Pin every action to a full commit SHA instead of a mutable tag, a supply-chain hardening measure: a repointed tag can no longer pull unexpected code into CI. Each pin keeps the human-readable version in a trailing comment. Also bumps checkout v4 -> v6.0.3 and setup-clojure 12.5 -> 13.6.1. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.