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

@crow-agent/mcp

v0.2.0

Published

Model Context Protocol client: transports, OAuth, and tool bridging.

Readme

@crow-agent/mcp

A self-contained Model Context Protocol client: no dependency on the official MCP SDK and no dependency on any other Crow package.

It covers the client side of MCP only — there is no server implementation here. The transport layer (how bytes move) is separated from the client core (what the bytes mean), so stdio, Streamable HTTP, and in-memory transports all plug into the same McpClient.

Talking to a server

import { McpClient, StdioTransport } from "@crow-agent/mcp";

const client = new McpClient({ name: "my-client", version: "1.0.0" });
await client.connect(
	new StdioTransport({ command: "my-mcp-server", args: ["--stdio"] }),
);

for (const tool of await client.listTools()) {
	console.log(tool.name, "-", tool.description);
}

const result = await client.callTool("read_file", { path: "/etc/hosts" });
await client.close();

Point at a remote server with StreamableHttpTransport instead:

import { StreamableHttpTransport } from "@crow-agent/mcp";

const transport = new StreamableHttpTransport({
	url: "https://mcp.example.com/mcp",
	headers: { Authorization: `Bearer ${token}` },
	fetch: customFetch, // optional: proxying, custom agents, test doubles
});

What the client handles for you

Once connected, McpClient owns the protocol bookkeeping so callers deal with plain requests and results:

  • the initialize handshake and notifications/initialized, negotiating the newest mutually supported protocol version (2025-11-25 preferred, back to 2024-11-05 accepted);
  • correlating requests with responses, timeouts, and cancellation — including cancelling a long tool call via notifications/cancelled;
  • renewing timeouts while progress notifications arrive;
  • paginating tools/list and surfacing structured tool output;
  • answering incoming ping and roots/list requests from the server;
  • delivering logging and tool-list-changed notifications through a generic notification API;
  • Streamable HTTP session management: the server-to-client GET stream, reconnection, and resuming dropped response streams with Last-Event-ID.

For stdio servers it also owns shutdown: stdin is closed first, then SIGTERM, then SIGKILL, across the server's whole process group — so a server that ignores termination still gets reaped.

Out of scope by design: batch JSON-RPC, the legacy HTTP+SSE transport, sampling, tasks, and anything server-side.

Handing tools to a model

toLlmContent() turns a CallToolResult into the content blocks a model accepts (TextContent / ImageContent in @crow-agent/ai's shape). Text and images pass through, embedded resources are unwrapped, and audio, resource links, and binary blobs become short text placeholders. A result with no content blocks but a structuredContent payload becomes its JSON.

import { toLlmContent } from "@crow-agent/mcp";

const result = await client.callTool("search", { query: "MCP" });
const blocks = toLlmContent(result); // TextContent[] | ImageContent[]
// MCP reports tool failures inside the result, not as protocol errors:
const failed = result.isError === true;

Signing in with OAuth

@crow-agent/mcp/oauth implements the MCP OAuth client subset — protected resource and authorization server discovery, PKCE authorization code flow, dynamic client registration, token refresh (one refresh shared across concurrent 401s), and step-up authorization on insufficient_scope — without the official SDK:

import { McpClient, StreamableHttpTransport } from "@crow-agent/mcp";
import {
	adaptOAuthProvider,
	authorizeMcp,
	McpOAuthAuthorizationRequiredError,
	McpOAuthProvider,
	OAuthCallbackServer,
} from "@crow-agent/mcp/oauth";

const serverUrl = "https://mcp.example.com/mcp";
const callback = await OAuthCallbackServer.listen();

const oauth = new McpOAuthProvider({
	serverUrl,
	redirectUrl: callback.redirectUrl,
	clientMetadata: { client_name: "My MCP client" },
	onRedirect: (url) => openInBrowser(url), // your UI, your browser call
});

async function connectOnce() {
	const client = new McpClient({ name: "my-client", version: "1.0.0" });
	await client.connect(
		new StreamableHttpTransport({ url: serverUrl, authProvider: adaptOAuthProvider(oauth) }),
	);
	return client;
}

try {
	return await connectOnce();
} catch (error) {
	if (!(error instanceof McpOAuthAuthorizationRequiredError)) throw error;
	const { code } = await callback.waitForCallback(await oauth.state());
	await authorizeMcp(oauth, { serverUrl, authorizationCode: code });
	return connectOnce();
}

The package never opens a browser itself and never decides where credentials live: pass an McpOAuthStateStore (or the in-memory MemoryOAuthStateStore) to McpOAuthProvider for durable tokens.

The OAuth implementation is adapted from the MIT-licensed Model Context Protocol TypeScript SDK; its license text ships under LICENSES/.

Writing tests

@crow-agent/mcp/testing exports createInMemoryTransportPair(), a matched pair of in-memory transports — drive one end as the server in your test and connect the client to the other, no subprocesses involved.