npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@johnhenry/mcp-query

v0.0.0

Published

A reactive, cached, embeddable MCP client for non-agentic apps. TanStack Query keys + RTK Query tags + LSP-client lifecycle, on top of the official MCP SDK.

Downloads

178

Readme

mcp-query

A reactive, cached, embeddable MCP client for ordinary (non-agentic) applications.

MCP is almost always consumed by LLM agents. But an MCP server is just a typed, introspectable capability surface — tools, resources, prompts — and there's no reason a normal app can't use it as a universal data/capability layer. mcp-query gives such apps the developer experience that Apollo/React-Query gave GraphQL/REST apps: hooks, a cache, reactivity, optimistic updates, devtools — on top of the official v2 MCP SDK (@modelcontextprotocol/client).

MCP 2026-07-28 ready, v1 by default. Unconfigured connections speak the classic 2025-era protocol byte-for-byte (no probe, no behavioral changes). Opt into newer revisions per client or per connection with the additive versions list:

new MCPClient({ servers, versions: ["2026-07-28", "2025-11-25"] });
// → probe via server/discover; speak 2026-07-28 where offered, fall back
//   losslessly to v1 otherwise. A modern-only list (["2026-07-28"]) pins.

On a modern connection you get the full revision — multi-round-trip elicitation/sampling through the same human-in-the-loop broker, change notifications over subscriptions/listen, SEP-2549 ttlMs/cacheScope cache hints, per-request logLevel — while legacy connections keep the classic behavior (including session resumption). connection(name).era tells you what was actually negotiated; versionNegotiation remains the low-level escape hatch. Deprecated surfaces (sampling/roots/logging per SEP-2577, sessions per SEP-2567, the old tasks RPCs per SEP-2663) are annotated @deprecated and remain functional on legacy connections. Tasks are reimplemented on the io.modelcontextprotocol/tasks extension wire shapes (legacy-era-gated until the SDK ships its extension runtime).

Status: published, working reference implementation. main is pre-2026-07-28: this package is at 0.0.1, the latest tag on npm, and speaks MCP 2025-11-25 (v1) only, on top of the official @modelcontextprotocol/sdk@^1.29.0. tsc --noEmit is clean and the full vitest suite passes (npm test for the current count), including end-to-end coverage of the cache, multiplexing, protocol-driven invalidation, dynamic registration, reconnect, the human-in-the-loop broker, and Inspector-style tooling (message log, manual sampling, auth recorder, CLI) — all driven against a real SDK server over an in-memory transport, with the codegen CLI verified against the live @modelcontextprotocol/server-everything. A preview build with the 2026-07-28 migration (v2 SDK, dual-era support, versions negotiation) is in progress on PR #17, held until the spec finalizes on the 28th — try it via the rc dist-tag: npm install @johnhenry/mcp-query@rc. See Supported protocol versions below.

Install

npm install @johnhenry/mcp-query

This installs latest (0.0.1), which speaks MCP 2025-11-25 (v1) only. To preview the in-progress 2026-07-28 migration ahead of its release, install the rc tag instead: npm install @johnhenry/mcp-query@rc.

Supported protocol versions

| npm tag | Version | Protocol | SDK | |---|---|---|---| | latest (default, this branch) | 0.0.1 | MCP 2025-11-25 only | @modelcontextprotocol/sdk@^1.29.0 (v1) | | rc | 0.1.0-rc.1 | MCP 2025-11-25 + opt-in 2026-07-28 | v2 SDK, dual-era |

The rc preview (built from PR #17, not yet merged to main) adds opt-in support for the finalized 2026-07-28 revision via an additive versions list:

new MCPClient({ servers, versions: ["2026-07-28", "2025-11-25"] })

This probes each server with server/discover, speaks 2026-07-28 where the server offers it, and falls back losslessly to 2025-11-25 (v1) otherwise; a modern-only list pins (no fallback). Unconfigured connections — and everything installed from latest today — keep speaking 2025-11-25 byte-for-byte, no probe.

Develop

npm install
npm run typecheck     # tsc --noEmit (covers src + examples)
npm test              # vitest run
npm run build         # emit dist/ (ESM + .d.ts) — what `npm publish` ships
npm run example:node  # runnable: drives @modelcontextprotocol/server-everything
npm run codegen -- --command npx --args "-y @modelcontextprotocol/server-everything" --out src/mcp.gen.ts

Examples

A graded series from one-liner to full app lives in examples/ (see examples/README.md) — 0106 are runnable with no network (npm run example:01example:06):

  • 01 connect/list/call · 02 caching + invalidation · 03 live subscriptions · 04 multi-server routing · 05 human-in-the-loop · 06 running alongside a separate MCP client on shared state.
  • examples/node-everything.ts — guided tour against the real server-everything (npm run example:node).
  • examples/07-hybrid-agent-ui.tsx / react-app.tsx — illustrative React (agent + live UI + broker; every React surface).

What the tests cover

| File | Exercises | |---|---| | test/cache.test.ts | staleTime, subscriber ref-counting, tag + protocol invalidation, optimistic rollback, gc | | test/router.test.ts | tool/resource routing, namespacing, ambiguity errors | | test/connection.test.ts | connect/negotiate, cursor pagination, resources/updated, list_changed, reconnect with a changed capability set | | test/session-resume.test.ts | session capture/persist, resume-skips-initialize, validated fallback on forgotten sessions, resume across reconnect | | test/client.test.ts | multi-server routing, URI-tagged caching, subscribe ref-count, declared invalidation, isError rollback | | test/codegen.test.ts | JSON Schema → TS, generated output compiles under --strict, paginated generateFromClient | | test/react.dom.test.tsx | useResource loading→data, useTool invoke, useTools reactivity on list_changed (happy-dom) |

The in-memory mock server (src/testing/mockServer.ts, exported as mcp-query/testing) is reusable for testing your own integrations.

Design & background

  • docs/api.mdthe full API reference: every feature with an example.
  • docs/backend.md — using mcp-query server-side: multi-tenant context, interceptors, authorization + audit, resilience, metrics/health, the gateway re-server, per-principal sessions, and the multi-node L2 cache.

The conceptual analysis behind every choice lives in docs/:

  • docs/design.md — the Apollo reframe, the GraphQL↔MCP mapping, what's similar/different/new/harder/impossible, the MCP server conventions a client must respect, and how MCPClient relates to the SDK's Client (wraps, not replaces).
  • docs/prior-art.md — does this already exist? Lessons from TanStack Query, RTK Query, urql, Relay, gRPC, tRPC, Connect, and LSP.
  • docs/sampling-and-non-agentic.md — why "non-agentic" ≠ "no LLM," and how to plug Chrome's built-in AI into the sampling handler.
  • docs/human-in-the-loop.md — the InteractionBroker: one approval queue for sampling + elicitation, with prompt-edit, response-redaction, trust policy, and an audit log.
  • docs/inspector.md — Inspector-style debugging on mcp-query: raw JSON-RPC message log, manual (human-as-model) sampling, OAuth-debug recorder, and the mcp-query-inspect CLI + per-request timeouts.
  • docs/webmcp.mdexperimental WebMCP bridge: expose backend tools to an in-browser agent (bridgeToWebMCP), and consume page tools (webMcpToolServer).

The thesis

The right prior art for an MCP client is not Apollo — Apollo's defining feature (normalized entity caching) is impossible on MCP's opaque, identity-free results. The right models are:

| Borrowed from | What we took | |---|---| | TanStack Query | key→document cache, staleTime/gcTime, background refetch, cache-and-network | | RTK Query | tag-based invalidation (providesTags/invalidatesTags) | | urql | document cache by default, normalization strictly opt-in | | Language Server Protocol | per-server lifecycle state machine, dynamic registration (list_changedclient/registerCapability), N-server multiplexing, reconnection with capability re-negotiation, cancellation/progress | | Connect-Query | typed RPC feeding a key-cache; codegen from schema (JSON Schema here) |

The MCP bonus: a chunk of the invalidation you'd hand-declare in RTK Query is emitted by the protocol itself (notifications/resources/updated, notifications/.../list_changed), so well-behaved servers invalidate your cache for free.

Architecture (two layers)

                        ┌──────────────────────────── React bindings ───────────────────────────┐
                        │ useResource (useQuery)   useTool (useMutation)   useTools/usePrompt …   │
                        └───────────────▲───────────────────────▲──────────────────▲─────────────┘
                                        │ useSyncExternalStore    │                  │
  Layer 1: CACHE  ──────────────────────┴─────────────────────────┴──────────────────┴───────────
  MCPCache: key→entry, tag index, invalidateTags, onResourceUpdated/onListChanged,
            ref-counted subscribers (→ drives resources/subscribe), gc, optimistic patch
                                        ▲                         ▲
                       writes / invalidation                fires onResourceUpdated / onListChanged
                                        │                         │
  Layer 2: CONNECTIONS ─────────────────┴─────────────────────────┴──────────────────────────────
  MCPClient            multiplexer + router + host handlers (sampling/elicitation/roots)
   └─ ServerConnection (×N)  LSP-style state machine · dynamic registration · reconnect+reconcile
        └─ @modelcontextprotocol/sdk Client  ── stdio / Streamable HTTP / SSE

The seam that matters: the connection layer drives the cache layer.

  • notifications/resources/updatedcache.onResourceUpdated → invalidates that exact URI tag.
  • notifications/<kind>/list_changed → re-list → cache.onListChangeduseTools() re-renders.
  • cache subscriber count (>0) → connection issues resources/subscribe; (==0) → unsubscribe + gc.
  • reconnect → re-initialize (capabilities may differ) → reconcile → re-list → resubscribe observed entries.
  • with a sessionStore (opt-in), reload/reconnect resumes the server-side Streamable HTTP session instead: the persisted Mcp-Session-Id skips initialize, a ping validates it, and a forgotten session falls back to fresh init (docs/api.md).

Usage

import { MCPClient } from "@johnhenry/mcp-query";
import { MCPProvider, useResource, useTool, useTools } from "@johnhenry/mcp-query/react";
import { DevtoolsHub, MCPDevtools } from "@johnhenry/mcp-query/devtools";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";

const hub = new DevtoolsHub();

const client = new MCPClient({
  servers: {
    fs:     { transport: () => new StdioClientTransport({ command: "mcp-server-filesystem", args: ["/work"] }) },
    github: { transport: () => new StreamableHTTPClientTransport(new URL("https://mcp.example.com/github")) },
  },
  schemeMap: { file: "fs", github: "github" },
  handlers: {
    // Registering a handler is what advertises the capability to the server.
    elicitation: async (req) => showModal(req.message, req.requestedSchema), // → UI dialog
    roots:       () => [{ uri: "file:///work" }],
    // no `sampling` → not advertised → server never asks for an LLM. (non-agentic)
  },
  devtools: hub,
});
await client.connect();

function App() {
  return (
    <MCPProvider client={client}>
      <Issues />
      <MCPDevtools hub={hub} />
    </MCPProvider>
  );
}

function Issues() {
  // useQuery analog: read a resource, live-subscribe, auto-tagged by URI.
  const { data, isLoading } = useResource("github://repos/acme/app/issues", {
    fetchPolicy: "cache-and-network",
    subscribe: true,
  });

  // useMutation analog. Args validate against the tool's inputSchema (bind a form to it).
  // `invalidates` is the fallback for servers that DON'T emit resources/updated.
  const [createIssue, { isPending, isDestructive, inputSchema }] = useTool("github.create_issue", {
    invalidates: ["res:github:github://repos/acme/app/issues"],
    optimistic: (a) => [{
      key: { kind: "resource", server: "github", uri: "github://repos/acme/app/issues" },
      recipe: (prev: any) => ({ ...prev, contents: [...(prev?.contents ?? []), { title: a.title }] }),
    }],
  });

  const { tools } = useTools({ server: "github" }); // re-renders on tools/list_changed
  // ...render `inputSchema` as a form; gate a confirm dialog on `isDestructive`...
}

What's deliberately not here (and why)

  • Normalized caching. No global object identity in MCP results → impossible to do automatically. The opt-in entity layer is providesTags + entityTag() only.
  • Static end-to-end types (tRPC-style). MCP servers are polyglot/decoupled, so types come from codegen against tools/list JSON Schemas + createTypedHooks(), not TS inference. Everything else discussed during design — codegen-typed hooks, sampling (incl. Chrome built-in AI), polling, persistence, Suspense, dynamic topology, completion, ping — is now implemented. See docs/api.md for every feature with an example.

Feature coverage

Reads/queries (useResource, useToolResult, queryTool) · mutations (useTool with optimistic + invalidation + progress + cancel) · capability lists + templates + prompts · useServerState · in-flight dedup · structural sharing · polling · Suspense · persistence · entity tags · structured output + annotation helpers · human-in-the-loop broker (sampling + elicitation, trust policy, audit) · Chrome built-in AI sampling · codegen + typed hooks · ping · completion · dynamic add/remove server · read retry · devtools · raw JSON-RPC message log · manual (human-as-model) sampling · OAuth-debug recorder · mcp-query-inspect CLI + per-request timeouts. Full vitest suite green — run npm test for the current count.

File map

| Path | Role | |---|---| | core/cache.ts | the centerpiece — keys, tags, invalidation, subscribers, gc, optimistic | | core/connection.ts | LSP-style lifecycle, dynamic registration, reconnect + reconcile | | core/client.ts | multiplexer + imperative read/call/list API | | core/router.ts | resolve tool-name / resource-URI → server (namespacing, ambiguity) | | core/handlers.ts | sampling/elicitation/roots; registration ⇒ capability advertisement | | core/keys.ts, core/tags.ts | cache-key shapes + tag conventions | | react/* | useResource, useTool, useTools/useResourceList/usePrompt | | devtools/* | event protocol + three-pane panel |