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

@graphmind-ai/mcp

v0.5.1

Published

In-process MCP server adapter for the GraphMind live agent debugger: wrap your McpServer to stream tool / resource / prompt / sampling events and honour pause / step / inject / retry / abort gates.

Readme

@graphmind-ai/mcp

In-process instrumentation for MCP servers written in TypeScript, for the GraphMind live agent debugger.

Wrap your McpServer; every incoming tools/call, resources/read and prompts/get — and every sampling/createMessage your handlers issue — becomes a node on a live graph that the debugger can hold: pause before the handler body runs, step, pause on error, and resume with continue / retry / inject / abort.

inject is the one to reach for first. Hold a request, type a different result, and your client receives it — a tool that returns nothing, a resource that 404s, a model that hallucinates — without editing the server or redeploying anything.

Everything fails open: with no debugger attached the adapter is a near-zero-cost no-op, and a debugger that disconnects mid-hold auto-continues every held gate. It writes nothing to stdout, so a stdio server stays a valid stdio server.

Built by wrapping, not patching: wrapServer returns a Proxy and your server object is never mutated, never re-created, and never has a property of ours written onto it.

Install

npm i -D @graphmind-ai/mcp graphmind-ai

The MCP SDK is a peer dependency, and both generations are supported:

| SDK | peer range | what the suite runs | |---|---|---| | @modelcontextprotocol/sdk (1.x) | >=1.26.0 <2 | 1.26.0 and 1.30.0 | | @modelcontextprotocol/server (2.x, with @modelcontextprotocol/core) | >=2 <3 | 2.0.0, on the 2025-11-25 era and on 2026-07-28 |

Both peers are optional — install the one your server uses. Run the debugger with npx graphmind-ai and open the viewer at http://127.0.0.1:4747.

Usage

import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { graphmind } from '@graphmind-ai/mcp';
import { z } from 'zod';

const gm = graphmind({ app: 'my-mcp-server' });

// Register on the value wrapServer returns — that is the instrumented view.
const server = gm.wrapServer(new McpServer({ name: 'my-server', version: '1.0.0' }));

server.registerTool(
  'search',
  { description: 'Search the index', inputSchema: { q: z.string() } },
  async ({ q }) => ({ content: [{ type: 'text', text: await search(q) }] }),
);

await server.connect(new StdioServerTransport());

That is the whole integration. Nothing else in your server changes: the same registration calls, the same handler signatures, the same results.

A runnable version is in example/stdio-server.mjs.

On the v2 SDK (@modelcontextprotocol/server 2.x)

Same call, same behaviour — only your imports differ:

import { McpServer } from '@modelcontextprotocol/server';
import { StdioServerTransport, serveStdio } from '@modelcontextprotocol/server/stdio';
import { graphmind } from '@graphmind-ai/mcp';

const gm = graphmind({ app: 'my-mcp-server' });
const server = gm.wrapServer(new McpServer({ name: 'my-server', version: '1.0.0' }));
server.registerTool('search', { inputSchema: { q: z.string() } }, async ({ q }, ctx) => ({
  content: [{ type: 'text', text: await search(q) }],
}));

await server.connect(new StdioServerTransport());      // 2025-11-25 era only
// or, to serve the 2026-07-28 era too — wrap INSIDE the factory:
serveStdio(() => gm.wrapServer(buildServer()));

What the adapter does with v2 specifically, all verified by the suite:

  • the trailing context (ctx.mcpReq.{id, method, signal, …}) that replaced the 1.x RequestHandlerExtra is recognised, so a zero-argument tool reports an empty input and a templated resource reports its variables — never the context object; instanceId is still ctx.mcpReq.id namespaced by the connection;
  • ctx.mcpReq.signal is chained with the debugger's abort signal while attached (never replaced); ctx.mcpReq.requestSampling(...) and ctx.mcpReq.send({ method: 'sampling/createMessage' }) become the gated llm:sampling node under the request, exactly like 1.x extra.sendRequest;
  • the low-level Server.setRequestHandler is instrumented in both v2 spellings — (method, handler) and (method, { params }, handler);
  • on the 2026-07-28 era the SDK's own encode seam runs after our gate, so a bare injected value ("a string", { price: 42 }) is stamped with resultType / ttlMs / cacheScope by the SDK and accepted by a 2026 client. (Through graphmind mcp-proxy that stamping does not happen — see the proxy docs.)
  • the server node's input carries sdk: "@modelcontextprotocol/server" (or /sdk), read structurally off the object you wrapped, so a project with both generations installed still tells its servers apart; the session-level badge reports whichever package is installed (2.x preferred when both are), and graphmind({ sdk }) overrides it.

Options

graphmind({
  app: 'my-mcp-server',   // shown in the viewer (default: "mcp-server")
  server: { name, version }, // override the `server` node label
  waitForAttach: true,    // connect() waits (<=2000ms) for the debugger, so
                          // breakpoints are armed for request #1
  url: 'ws://127.0.0.1:4747/ingest',   // or GRAPHMIND_URL
  enabled: true,          // GRAPHMIND_DISABLED=1 always wins
});

gm.ready(), gm.session (stats, custom events) and gm.dispose() behave exactly as in the other GraphMind adapters.

Kill switches

| | | |---|---| | GRAPHMIND_DISABLED=1 | Always off. wrapServer becomes the identity function and nothing touches the network. | | NODE_ENV=production | Off unless GRAPHMIND=1. | | enabled: false | Off. | | No debugger running | Detached: events are buffered for replay, gates take the fast path, your server behaves exactly as it would without the adapter. |

Capability matrix

| MCP surface | Node | nodeId | Gates | inject substitutes | |---|---|---|---|---| | tools/call | tool | tool:<name> | before / after / error | the CallToolResult | | resources/read | resource | resource:<registration name> | before / after / error | the ReadResourceResult | | prompts/get | prompt | prompt:<name> | before / after / error | the GetPromptResult | | sampling/createMessage (server → client) | llm | llm:sampling | before / after / error | the CreateMessageResult | | the server session | server | server:<server name> | — (structural parent) | — | | tools/list, resources/list, prompts/list, completion/complete, initialize, ping, logging, elicitation, progress notifications, task-augmented tools | not instrumented — forwarded untouched | | | |

Node identity follows the repo convention: nodeId is stable per logical node (one node per registration, so a templated resource stays a single node however many URIs it serves) and instanceId is per execution — the JSON-RPC request id, namespaced by the connection, set on node.started and on node.finished / node.error.

One incoming request = one run, named tools/call:<name> (etc.). A connecting transport gets a small mcp:connect <server> run that carries a graph.hint of the whole registered surface, so the viewer can draw your server grey before any traffic arrives.

What inject accepts

MCP results are typed and the SDK validates them on the way out, so a value that already looks like the right result shape is passed through untouched, and anything else is lifted into the smallest valid result that carries it:

| You type | The client receives | |---|---| | {"content": [...]} | exactly that | | "a string" | { content: [{ type: "text", text: "a string" }] } | | {"price": 42} | { content: [text], structuredContent: { price: 42 } } — so it also satisfies a tool that declares an outputSchema | | (nothing) | { content: [] } |

Resources and prompts get the same treatment against ReadResourceResult and GetPromptResult.

Gates and errors

  • before — awaited before your handler is invoked. A held gate has nothing in flight: no side effect has happened and abort costs nothing.
  • after — post-handler, pre-return. Fires on an explicit after breakpoint (decisions.md #2).
  • error — when your handler throws, before the error escapes into the SDK. retry re-invokes it, inject recovers the request, continue rethrows and the SDK produces its usual isError tool result or JSON-RPC error. Pause-on-error is armed by default by the CLI.
  • abort — aborts the run's AbortController (which is chained into your handler's extra.signal) and makes the request terminal.

@graphmind-ai/mcp vs graphmind mcp-proxy

Two deliberately different shapes. The proxy (graphmind mcp-proxy -- <cmd>) is a stdio man-in-the-middle that debugs a server in any language with zero code changes; this package instruments a server you own and can edit, in TypeScript, from the inside.

| | @graphmind-ai/mcp | graphmind mcp-proxy | |---|---|---| | Code changes to the server | 3 lines | none | | Server language | TypeScript / JavaScript | any | | Transport | any — it wraps the server, not the transport (verified on stdio, Streamable HTTP and in-memory) | stdio only | | Extra process | none | one (the proxy) | | Where a gate sits | inside the process, after routing/validation, before your handler body runs | at the protocol boundary, before the frame reaches the server | | What it sees of a request | the parsed, validated arguments the handler receives | the raw JSON-RPC frame | | Sampling issued by a handler | its own gated llm node, nested under the request that issued it | visible as frames, not attributable to a handler | | Resource identity | one node per registration (templates stay one node) | one node per concrete URI | | A server you did not write | ✗ | ✓ | | Works with no debugger running | ✓ (fast-path no-op) | ✓ (transparent relay) |

Use the proxy when you did not write the server, it is not TypeScript, you cannot or will not edit it, or you want to debug the exact bytes on the wire. It is the higher-value tool for most people and the right first thing to try.

Use this package when you own the server and want handler-level detail: gating on the validated arguments, sampling nested under the request that issued it, resource templates as single nodes, and gates on a server that does not speak stdio at all.

They are not exclusive — a server instrumented with this package still works behind the proxy; you just get two views of the same traffic.

Honest limitations

  • Only what you register on the wrapped object is instrumented. Register on the value wrapServer returns. Anything registered directly on the original object (or before wrapping) runs uninstrumented.
  • Task-augmented tools (registerToolTask, handlers with a createTask method) are passed through untouched.
  • elicitInput and completion/complete are not instrumented. Sampling is the only server→client request this adapter models.
  • A hold can outlive the client's patience. MCP clients cancel requests that exceed their timeout (the SDK default is 60s). The server resumes correctly and the run completes in the viewer, but the client has already given up on that request. No in-process debugger can change that; the adapter warns once the first time a gate holds for more than a second.
  • The low-level Server path names resources by URI. With setRequestHandler(ReadResourceRequestSchema, ...) there is no registration to name the node, so resources/read produces one node per concrete URI. McpServer's registerResource does not have this problem.
  • The server node's label comes from the { name, version } you gave the SDK's server, read defensively off the SDK object; if that ever stops working it falls back to your app name (or pass server: { name }).
  • Verified peers: 1.26.0 and 1.30.0 on the 1.x line; 2.0.0 on the 2.x line. The ranges in between are declared, not tested; the adapter never imports either SDK and only duck-types the registration methods, connect, .server, setRequestHandler, createMessage and the shape of the trailing handler argument.
  • v2 elicitInput and the input_required flow are not instrumented. On 2026-07-28 a handler that needs client input answers inputRequired(...) instead of issuing a request; the adapter records that answer as the node's output and gates the retried call as a new execution. Sampling on the modern era has no server→client channel by design (SEP-2577), so requestSampling there fails inside the SDK and surfaces as the node's error, exactly as it would without the adapter.
  • The floor is a security floor. The adapter works on older SDKs, but every release below 1.26.0 carries at least one high advisory — cross-client data leak through shared transport reuse (≤1.25.3), DNS-rebinding protection off by default (<1.24.0), ReDoS (<1.25.2) — so supporting them would mean telling you a vulnerable SDK is a supported configuration.

Testing

pnpm --filter @graphmind-ai/mcp test

The suite builds real MCP servers with the SDK and drives them with a real MCP client over the SDK's in-memory transport pair — tools, resources, templated resources, prompts, zero-argument tools, sampling, throwing handlers, concurrent requests with independent gates, disconnect auto-continue, detached overhead, stdout purity, a hand-rolled low-level Server, a Streamable HTTP server over a real node:http listener, and the same gate-and-inject story again on SDK 1.26.0, the declared floor.

There is also an end-to-end smoke that wires up the real pieces — the graphmind server, a real stdio MCP server (example/stdio-server.mjs) and a real MCP client — and arms breakpoints over the viewer's own WebSocket:

pnpm --filter graphmind-ai build && pnpm --filter @graphmind-ai/mcp build
pnpm --filter @graphmind-ai/mcp e2e

License

MIT