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

@klex/mcp-proxy-sdk

v1.0.0

Published

SDK for hosting MCP proxies and connecting remote MCP environments

Readme

MCP Proxy SDK

One SDK for hosting MCP proxies, embedding proxy endpoints in Node backends, and connecting remote MCP environments through an outbound daemon.

The package has no root export. Import an explicit subpath so runtime boundaries remain visible.

Public entry points

  • @klex/mcp-proxy-sdk/core: runtime-neutral environment IDs, exchange protocol, connections, and routing.
  • @klex/mcp-proxy-sdk/http: Web-standard request/response relay with fetch(Request): Promise<Response>.
  • @klex/mcp-proxy-sdk/server: Node HTTP handlers, WebSocket upgrades, and the batteries-included proxy server.
  • @klex/mcp-proxy-sdk/daemon/node: Node environment daemon backed by ws.

/core and /http do not import Node built-ins. /server and /daemon/node are Node-only.

Proxy

import { createEnvironmentId } from '@klex/mcp-proxy-sdk/core';
import { createProxyServer } from '@klex/mcp-proxy-sdk/server';

const server = createProxyServer({
  authenticateEnvironment: async ({ request }) =>
    authenticateEnvironment(request),
  parseEnvironmentId: (value) => createEnvironmentId(value),
  host: '127.0.0.1',
  port: 3000,
});

await server.start();

Agent traffic uses /environments/:environmentId/mcp. Daemons connect to /environment by default. The relay tunnels each HTTP request and streamed response as an independent exchange. It does not interpret MCP, own MCP session state, or authenticate agents.

The proxy authenticates environment connections only. Agent credentials, including Authorization, pass through unchanged to the environment handler. Each environment handler is responsible for authenticating and authorizing agents and may return 401 or 403; the relay passes those responses back unchanged. Proxy-Authorization and other hop-by-hop transport headers are not forwarded.

For an existing Node server, use createProxyNodeHandlers(options) from /server. Frameworks using Web-standard requests can call createProxyHttp(options).fetch(request) from /http.

Distributed request routing

Hosts with multiple proxy processes can provide routeRequest. The router receives the parsed environment ID, the original untouched Request, and a single-use forwardLocal() callback. It can choose the local in-memory connection or return a streamed Response obtained from the process that owns the connection. Without this option, all requests use the local proxy exactly as before.

const server = createProxyServer({
  authenticateEnvironment,
  parseEnvironmentId,
  routeRequest: async ({ environmentId, request, forwardLocal }) => {
    const owner = await ownership.find(environmentId);
    return owner.isLocal
      ? forwardLocal()
      : fetch(owner.url, request);
  },
});

forwardLocal() can be called once. Router failures are reported through hooks.onError and become 503 responses. The hosting application remains responsible for preserving cancellation and response streaming in its remote transport.

Environment lifecycle

onConnected and onDisconnected are awaitable lifecycle hooks. Both receive the same environment ID and connection object, allowing a host to associate an external ownership generation with that exact socket. A rejected connection hook closes and unregisters the socket. Shutdown waits for disconnection hooks, so ownership records can be removed before the process exits.

Environment daemon

The daemon accepts any Web-standard handler. MCP environments should pass the official stateless handler boundary rather than connecting an McpServer to a synthetic transport.

import { createMcpHandler, McpServer } from '@modelcontextprotocol/server';
import { createProxyDaemon } from '@klex/mcp-proxy-sdk/daemon/node';

const mcp = createMcpHandler(
  () => new McpServer({ name: 'computer', version: '1.0.0' }),
  { legacy: 'stateless' },
);

const daemon = createProxyDaemon({
  connection: () => ({
    url: 'wss://proxy.example.com/environment',
    headers: {
      authorization: `Bearer ${process.env.PROXY_TOKEN ?? ''}`,
    },
  }),
  handler: {
    fetch: mcp.fetch,
    close: () => mcp.close(),
  },
});

await daemon.start();

The daemon reconstructs requests, invokes handler.fetch, streams response bytes with bounded relay frames, propagates cancellation, and closes the handler exactly once. It calls connection before every initial connection and reconnect, so the host can provide fresh URLs or headers for each attempt.

For a hosted deployment, the provider can obtain a short-lived connection URL without coupling the SDK to a ticket format:

connection: async () => ({
  url: await platform.obtainEnvironmentConnectionUrl(),
})

The hosting platform owns ticket minting, expiry, replay prevention, and validation in authenticateEnvironment; the SDK only opens the returned WebSocket URL.

Long-lived MCP responses, including subscription streams and server notifications, remain ordinary response bodies. Concurrent exchanges are isolated and multiplexed over one environment WebSocket.

Future browser daemon

Browser daemon support is intentionally not exported. A future /daemon/browser entry point requires a runtime-neutral socket boundary and browser-compatible authentication such as short-lived connection tickets or first-frame authentication.