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

mcp-stdio-oauth

v0.1.0

Published

Experimental MCP extension for proactive third-party OAuth readiness discovery on local stdio servers.

Readme

mcp-stdio-oauth

mcp-stdio-oauth is an experimental TypeScript extension for local stdio MCP servers that need authorization to a third-party provider such as Google.

This is not MCP transport authorization. MCP transport authorization controls whether an MCP client may connect to an MCP server. This package covers a different relationship: the local MCP server acts as an OAuth client to a downstream provider and keeps all downstream tokens itself.

The extension provides a small control plane:

  • Discover support through negotiated extension capabilities.
  • Read authorization readiness without invoking a tool.
  • Start authorization only after an explicit user action.
  • Deliver the authorization URL through MCP URL elicitation.
  • Keep authorization URLs out of tool content and downstream tokens out of MCP entirely.

Status

The wire contract is experimental at version 0.1. The package targets Node.js 22 or newer, TypeScript, ESM, MCP initialize-era protocols, and MCP 2026-07-28 over stdio.

MCP 2026-07-28 normally limits multi-round-trip results to selected core methods. This negotiated extension adds co.com.flujo/mcp-stdio-oauth/start as an extension-defined MRTR-capable method. The supplied stdio transport adapters implement that opt-in behavior without exposing authorization as an MCP tool.

This does not reserve a generic start name or alter unrelated extension methods. The behavior belongs only to the fully qualified method above, and only while both peers advertise extension version 0.1.

Install

npm install mcp-stdio-oauth zod
npm install @modelcontextprotocol/client @modelcontextprotocol/server

Client

Create the helper before connecting so its capabilities are advertised. When the SDK uses automatic stdio era negotiation, keep its exact StdioClientTransport through connect and attach the deferred MRTR controller immediately afterward. The callback owns trusted presentation, explicit consent, and browser navigation; the package never opens a browser.

import { Client } from "@modelcontextprotocol/client";
import { StdioClientTransport } from "@modelcontextprotocol/client/stdio";
import {
  createStdioOAuthClient,
  StdioOAuthDeferredMrtrController,
} from "mcp-stdio-oauth/client";

const transport = new StdioClientTransport({ command: "my-mcp-server" });
const mrtrController = new StdioOAuthDeferredMrtrController();
const mcpClient = new Client(
  { name: "my-host", version: "1.0.0" },
  { versionNegotiation: { mode: "auto" } },
);
const oauth = createStdioOAuthClient({
  mcpClient,
  mrtrController,
  // Required for initialize-era fallback only when this helper owns the
  // client's global elicitation/create handler.
  installElicitationHandler: true,
  onUrlElicitation: async (request) => {
    // Show request.target.href and highlight request.target.origin.
    // Return accept only after a separate user consent action and navigation.
    return host.requestUrlConsent(request);
  },
});

await mcpClient.connect(transport);
// Attach only after connect so automatic stdio negotiation can recognize the
// SDK's exact base transport and use a disposable sibling for legacy probes.
mrtrController.attach(transport);

if (oauth.isSupported()) {
  const status = await oauth.getStatus();
  // Call start only from an explicit user action.
  await oauth.start(status.authorizations[0]!.id);
}

The client helper rejects IDs that were not present in its latest successful status response, cancels unsolicited URL elicitations, rejects unsafe URLs, and allows only one active start per helper.

Do not wrap an auto-negotiated SDK stdio transport before connect: the SDK needs its exact base transport to probe legacy servers on a disposable sibling process. StdioOAuthClientTransport remains available for callers that already pin/know the modern protocol era. attachStdioOAuthMrtrAdapter is the lower-level post-connect API used by StdioOAuthDeferredMrtrController.

The helper reports the extension as unsupported when the supplied MCP client cannot advertise the extension and URL-elicitation capabilities. Create it before connecting so the SDK can include those capabilities in negotiation.

createStdioOAuthClient does not install a global elicitation/create handler by default. Set installElicitationHandler: true only for a dedicated client where this helper owns that handler, as in the standalone example above. A host with an existing elicitation dispatcher should leave the option unset and route only the URL request associated with the active OAuth start to oauth.handleUrlElicitation; this prevents the package from replacing handlers for unrelated elicitation features. The modern transport decorator uses the same callback directly and does not require global-handler installation.

Server

Register the extension on the MCP server and wrap its stdio transport with the same implementation. The wrapper is needed only for the negotiated 2026-07-28 custom-method MRTR path; initialize-era URL elicitation uses the SDK request channel.

import { randomBytes } from "node:crypto";
import { McpServer } from "@modelcontextprotocol/server";
import {
  serveStdio,
  StdioServerTransport,
} from "@modelcontextprotocol/server/stdio";
import {
  createUrlAuthorizationRequest,
  readUrlElicitationResult,
  registerStdioOAuthExtension,
  StdioOAuthServerTransport,
} from "mcp-stdio-oauth/server";

let ready = false;
const implementation = {
  getStatus() {
    return {
      authorizations: [
        {
          id: "provider-account",
          label: "Provider Account",
          state: ready ? "ready" : "authorization_required",
        },
      ],
    };
  },
  start({ authorizationId }, context) {
    const response = readUrlElicitationResult(context.inputResponses);
    if (response?.action === "accept" && ready) {
      return { authorizationId, state: "ready" };
    }
    if (response) {
      return { authorizationId, state: "authorization_pending" };
    }
    return createUrlAuthorizationRequest({
      url: createProviderAuthorizationUrl(),
      message: "Authorize Provider Account.",
      requestState: randomBytes(32).toString("base64url"),
    });
  },
};

const serverInfo = { name: "provider-server", version: "1.0.0" };
const transport = new StdioOAuthServerTransport(
  new StdioServerTransport(),
  implementation,
  { serverInfo },
);

serveStdio(
  () => {
    const server = new McpServer(serverInfo, { capabilities: {} });
    registerStdioOAuthExtension(server, implementation);
    return server;
  },
  { transport },
);

The server owns provider OAuth state, callback listeners, authorization codes, client secrets, access tokens, refresh tokens, and persistence. None belong in extension messages.

For the modern transport path, the decorator replaces an implementation's internal requestState with a cryptographically random, one-use wire handle bound to the authorization ID and expected response key. It restores the internal state only after a valid retry and rejects missing, malformed, expired, or replayed handles. Wire handles expire after ten minutes by default; set requestStateTtlMs on StdioOAuthServerTransport when the provider attempt uses a different lifetime.

Entry points

  • mcp-stdio-oauth/protocol: SDK-independent constants, schemas, types, capabilities, and error parsing.
  • mcp-stdio-oauth/client: client helper plus deferred, in-place, and wrapping stdio MRTR adapters.
  • mcp-stdio-oauth/server: server registration, URL request helpers, and stdio transport decorator.
  • mcp-stdio-oauth/conformance: reusable conformance assertions.

See SPEC.md for the wire contract and SECURITY.md before implementing a host or provider flow.

Gmail reference executable

The published package includes the reference Gmail server as an executable. An MCP client configuration can launch it without treating authorization as a setup tool:

{
  "command": "npx",
  "args": ["-y", "--package", "mcp-stdio-oauth", "mcp-stdio-oauth-gmail"],
  "env": {
    "GOOGLE_CLIENT_ID": "your-client-id.apps.googleusercontent.com"
  }
}

Create a Google Desktop app OAuth client first. The executable uses the non-sensitive gmail.labels scope by default and in-memory token storage by design; see the Gmail example guide before testing or replacing the store for production.

Release

From a normal interactive terminal, run:

npm run release

The command starts npm login when necessary, runs the complete verification suite, publishes the public package, and checks that npm serves version 0.1.0. Passwords, tokens, and one-time codes are handled only by npm's own interactive process. Tagged GitHub releases publish with provenance through the separate workflow.