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

@flow-state-dev/mcp

v0.1.3

Published

MCP transport adapter for flow-state-dev.

Readme

@flow-state-dev/mcp

MCP (Model Context Protocol) transport adapter for the Flow State Dev runtime. Exposes any flow with mcp.enabled: true as its own MCP server over Streamable HTTP at POST /api/flows/:kind/mcp. Flow actions become MCP tools.

Install

pnpm add @flow-state-dev/mcp

Usage

import { createFlowApiRouter } from "@flow-state-dev/engine";
import { createMcpTransportAdapter } from "@flow-state-dev/mcp";
import { defineFlow, handler } from "@flow-state-dev/core";
import { z } from "zod";

const billing = defineFlow({
  kind: "billing",
  mcp: { enabled: true },
  authentication: {
    resolvePrincipal: async (ctx) => {
      // Verify a bearer token, return the bound user.
      const token = ctx.request?.headers.get("authorization")?.slice("Bearer ".length);
      if (!token) return null;
      return { userId: await lookupUserIdForToken(token) };
    }
  },
  actions: {
    recordPayment: {
      inputSchema: z.object({ amount: z.number(), invoiceId: z.string() }),
      block: handler({ name: "record-payment", execute: ({ amount, invoiceId }) => {
        // ...
        return { ok: true };
      }}),
      description:
        "Record a customer payment against an open invoice. Use when a payment " +
        "has cleared and you need to update the ledger. `amount` is in USD cents."
    }
  }
});

const router = createFlowApiRouter({
  registry: myRegistry,
  stores: myStores,
  adapters: [createMcpTransportAdapter()]
});

Connect Claude Desktop, Cursor, or any MCP client to:

http://localhost:3000/api/flows/billing/mcp

The flow's actions appear as tools (record_payment); calling a tool runs the action via the same runAction path as a normal HTTP invocation. Every MCP-originated request is stamped with RequestRecord.source = "mcp".

Dedicated endpoint prefix

The default shared layout remains /api/flows/:kind/mcp. To give MCP clients a shorter, MCP-exclusive URL without moving ordinary HTTP action routes, enable the dedicated layout:

adapters: [createMcpTransportAdapter({ dedicatedBasePath: true })]

The adapter then mounts the billing flow at:

http://localhost:3000/mcp/billing

Dedicated mode defaults basePath to /mcp. Set an explicit prefix such as basePath: "/integrations/mcp" when needed. One adapter instance mounts one layout, so the canonical URL is not retained as an alias after opting in. A root-only dedicated base is rejected because /:kind could claim unrelated single-segment routes.

Your HTTP host must forward the selected prefix to the Flow State router. Hosts that only mount the router beneath /api/flows need a corresponding /mcp/* mount (or an equivalent rewrite) before the dedicated URL is reachable.

v1 Limitations

  • Stateless by default. No Mcp-Session-Id is ever issued; every tools/call runs in a fresh flow session unless the action opts in with mcp.session (see Deriving a session id per action).
  • Single JSON tool result. No notifications/progress streaming, no outputSchema / structuredContent. Tool results are text content only.
  • GET and DELETE on the endpoint return 405 Method Not Allowed.
  • resources/list is empty in v1 because the framework has no flow-bound resource scope yet. The hook is wired through and will return entries once that lands.
  • No bundled OAuth. Authentication is whatever your flow's authentication.resolvePrincipal implements (bearer tokens, HMAC, host session cookies — the framework provides extractBearerToken and createHs256JwtVerifier helpers).

Tool name derivation

Action keys are converted to MCP tool names with decamelize:

| Action key | Tool name | | --- | --- | | recordPayment | record_payment | | URLParser | url_parser | | getHTTPSProxy | get_https_proxy | | event-queue | event-queue |

Two actions resolving to the same tool name throw at flow registration.

Override the auto-derived name on a single action via mcp.name:

recordPayment: {
  inputSchema,
  block,
  description: "...",
  mcp: { name: "log-payment" }
}

Per-action opt-out

actions: {
  publicAction: { /* exposed */ description: "..." },
  internalOnly: {
    /* not exposed to MCP, even though the flow opts in */
    mcp: { enabled: false }
  }
}

Installation-level values via query params

Sometimes a value should be fixed per client installation rather than supplied by the model on each call — a provenance tag being the common case. Pass forwardQueryParams an allowlist, and any of those params present on the endpoint URL are merged into the tools/call input:

adapters: [createMcpTransportAdapter({ forwardQueryParams: ["source"] })]
# Each installation points at its own tagged URL:
http://localhost:3000/api/flows/billing/mcp?source=claude-desktop

The forwarded value is authoritative — it overrides a same-named tool argument, since the point is a value the model should not be able to override. Listing a param is your explicit opt-in that it becomes endpoint-controlled. A forwarded param only lands if the action's input schema accepts it (otherwise the normal zod boundary strips or rejects it). Only tools/call is affected; initialize / tools/list / resources/* and auth are untouched. Defaults to forwarding nothing.

Deriving a session id per action

MCP is sessionless, so by default every tools/call mints a fresh flow session. The per-action mcp.session directive lets one action derive its dispatch session id, so a client can group related calls:

actions: {
  // Template string → a freshly minted id (`*` is replaced by a random token,
  // or appended when absent). The handler returns the id to the caller.
  createConversation: { block: createConversation, mcp: { session: "conv_*" } },
  // { fromInput } → the session id is the string at input.conversationId, so
  // calls sharing a conversationId land in one session.
  logActivity: { block: logActivity, mcp: { session: { fromInput: "conversationId" } } },
}

This is a flow session id, not the protocol Mcp-Session-Id. It only selects which flow session state and history a call sees; the principal still comes from resolvePrincipal, so a fromInput id is a grouping key, never an auth decision. A missing/blank fromInput field falls back to a fresh ephemeral session. Bound the length of any fromInput field that becomes a storage key in the action's input schema.

Status

See the spec for design rationale and explicit non-goals.