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

@lucidbrain/sdk

v0.0.10

Published

LucidBrain SDK — MCP tool server with OAuth 2.1 + PKCE, the WorkSpec v1.2 pattern packaged.

Readme

@lucidbrain/sdk

MCP tool server with OAuth 2.1 + PKCE, packaged. One call yields a fully spec-compliant server with DCR (RFC 7591/7592), Resource Indicators (RFC 8707), Protected Resource Metadata (RFC 9728), opaque tokens, refresh rotation with replay detection, revocation, introspection, and Streamable HTTP transport.

See the Spec v0.1 if present, or the in-code comments in src/ for full detail.

Install

pnpm add @lucidbrain/sdk
pnpm add -D @types/pg

Peer: express ^4.19 || ^5. Requires Node ≥ 22.

Migrate the database

The SDK owns two Postgres tables, shipped as SQL migrations.

pnpm --filter @lucidbrain/sdk run migrate
# or
DATABASE_URL=... npx lucidbrain migrate

Re-runnable; tracks applied migrations in lucidbrain_migrations.

Hello world

import express from "express";
import { createToolServer } from "@lucidbrain/sdk";

const app = express();

const mcp = createToolServer({
  name: "hello",
  version: "1.0.0",
  issuer: "https://hello.example.com",
  db: { url: process.env.DATABASE_URL! },
  cookieSecrets: [process.env.COOKIE_1!, process.env.COOKIE_2!],
  resource: {
    pattern: "/mcp",
    resolve: () => "https://hello.example.com/mcp",
  },
  consent: { url: (uid) => `/consent/${uid}` },
  scopes: { "hello:read": ["greet"] },
  findUser: async (id) => ({ id, name: "Hello user" }),
});

mcp.tool(async function greet({ name }: { name: string }, ctx) {
  return { message: `Hello, ${name}! Authenticated as ${ctx.user.id}.` };
});

mcp.mount(app);
app.listen(3000);

Per-resource (multi-tenant) pattern

createToolServer({
  // ...
  resource: {
    pattern: "/mcp/:workspaceSlug",
    resolve: (req) => `${env.ISSUER}/mcp/${req.params.workspaceSlug}`,
    validate: async ({ workspaceSlug }) => {
      const ws = await db.workspaces.findBySlug(workspaceSlug);
      if (!ws) throw new Error("unknown_workspace");
      return { workspace: ws };
    },
  },
});

validate runs on every MCP request. Whatever it returns is merged into ctx.resourceParams.

Tool registration (three forms)

// 1. Function form — name derived from fn.name
mcp.tool(async function list_items({}, ctx) { return db.items.forUser(ctx.user.id); });

// 2. Explicit form — preferred for production
mcp.tool({
  name: "post_message",
  description: "Post a message",
  input: z.object({ channelId: z.string(), body: z.string() }),
  scopes: ["synapse:write"],
  handler: ({ channelId, body }, ctx) =>
    postMessage({ channelId, body, userId: ctx.user.id }),
});

// 3. Batch form — for groups of simple tools
mcp.tools({
  list_workspaces: (_, ctx) => db.workspaces.forUser(ctx.user.id),
  get_workspace: ({ id }, ctx) => db.workspaces.get(id, ctx.user.id),
});

Scope enforcement: explicit scopes: on the tool def + the scopes map at config level are unioned. Missing scope becomes an MCP error.

Flow B — external OAuth clients (Claude Code, Cursor, etc.)

Standard OAuth 2.1 + PKCE + Resource Indicators. The SDK mounts every endpoint. The consumer renders consent.

// Consumer's Express setup — one call mounts all three consent routes
app.get("/oauth/consent/:uid", (req, res) => res.sendFile("consent-spa.html"));
mcp.mountConsent(app);
// equivalent to:
//   app.get ("/api/oauth/consent/:uid/context", mcp.handlers.consentContext);
//   app.post("/api/oauth/consent/:uid/confirm", express.json(), mcp.handlers.consentConfirm);
//   app.post("/api/oauth/consent/:uid/abort",   express.json(), mcp.handlers.consentAbort);

mountConsent(app, { basePath?, jsonParser? }) — override either if your app already owns those paths.

The consent SPA calls:

  1. GET /api/oauth/consent/:uid/context → renders UI with client + scopes + user info
  2. POST /api/oauth/consent/:uid/confirm with { scopes: [...] } → receives { returnTo } to navigate to

Scope descriptions

Register scope copy once at config time so every consent SPA across your orgs renders the same labels:

createToolServer({
  // ...
  scopes: { "workspec:write": ["claim_slice", "release_slice"] },
  scopeDescriptions: {
    "workspec:write": {
      human: "Act on your behalf in WorkSpec",
      permits: ["claim slices", "release slices", "update slice status"],
    },
  },
});

consentContext returns the matching entries for each requested scope under scopes.descriptions[scope] — the SPA renders them verbatim.

Consent context shape

interface ConsentContextResponse {
  interactionId: string;
  prompt: "login" | "consent" | string;
  client:   { id, name, redirectUris, trusted };
  scopes:   {
    requested:    string[];
    descriptions: Record<string, { human: string; permits: string[] }>;
  };
  resource:       string;
  resourceParams: Record<string, unknown>;  // pathParams + resource.validate() result
  user:           { id, ... } | null;       // from findUser(session.accountId)
}

resourceParams runs config.resource.validate() (same function tool handlers get in ctx.resourceParams), so the SPA can render "Connect to Fieldstate workspace" without a second round-trip.

Pre-seeded trusted clients

await mcp.registerClient({
  clientId: "lucidbrain",
  clientName: "LucidBrain Agents",
  redirectUris: ["https://lucidbrain.nz/oauth/callback/mycompany"],
  trusted: true,
});

Idempotent. Use for known integrations. Skips DCR. Still subject to consent.

Flow A — consumer-launched agents (no consent)

When the user clicks "Launch agent" inside your app, mint tokens directly:

const tokens = await mcp.mintDelegatedTokens({
  userId: session.userId,
  resource: `${env.ISSUER}/mcp/fieldstate`,
  scopes: ["workspec:read", "workspec:write"],
  ttl: 3600,
  actor: { type: "agent", name: "Story Planner" },
});

await lucidbrainClient.launchAgent({
  agent: { persona, model },
  toolServers: [{
    url: `${env.ISSUER}/mcp/fieldstate`,
    accessToken: tokens.access_token,
    refreshToken: tokens.refresh_token,
    refreshEndpoint: `${env.ISSUER}/oauth/token`,
  }],
});

No consent screen. No OAuth redirect. The user never sees LucidBrain.

Delegated grants use a reserved clientId of @delegated — filtered out of listGrants() by default. Pass { includeDelegated: true } to include them in an admin UI.

Connected-apps portal

app.get("/api/connected-apps", requireUser, async (req, res) => {
  res.json(await mcp.listGrants({ userId: req.user.id }));
});

app.delete("/api/connected-apps/:grantId", requireUser, async (req, res) => {
  await mcp.revokeGrant({ userId: req.user.id, grantId: req.params.grantId });
  res.status(204).end();
});

Revocation is immediate — all tokens under the grant are destroyed in DB.

body-parser ordering

node-oidc-provider parses its own bodies. Do NOT mount express.json() globally before mcp.mount(app):

// WRONG
app.use(express.json());
mcp.mount(app);

// RIGHT
mcp.mount(app);                      // SDK scopes JSON parsing to the MCP path
app.use("/api", express.json());     // mount json for your routes AFTER

LucidBrain platform registration

Opt-in. Fires once on mount() if enabled. Failure is warned, not fatal.

createToolServer({
  // ...
  lucidbrain: {
    register: true,
    url: "https://lucidbrain.nz",
    apiKey: env.LUCIDBRAIN_TENANT_KEY,
  },
});

Set register: false (or omit the block) and the SDK has zero runtime coupling to LucidBrain.

Cleanup

Expired payloads > 1 day old are deleted by:

await mcp.cleanup();

Call from your cron. The SDK doesn't schedule it.

Scopes convention

<product>:<capability> — conventional capabilities: :read, :propose, :write, :admin.

Architecture principles

  • Consumer owns the user relationship. The SDK renders no UI. Consent screens, login pages, connected-apps lists — all consumer.
  • Opaque tokens, hashed at rest. DB dump yields no usable tokens.
  • Fail closed. Every rejection path in the middleware returns WWW-Authenticate with resource metadata URL.
  • Audience binding. Every token is audience-locked via RFC 8707. Cross-resource replay is impossible at the OAuth layer.
  • DCR open with policy. Public clients, PKCE S256 required, @delegated client-id reserved.