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

@venturekit/mcp

v0.0.0-dev.20260609102541

Published

Transport-agnostic Model Context Protocol (MCP) server primitives for VentureKit: JSON-RPC dispatch, a typed tool registry, and a Zod→JSON-Schema serializer.

Readme

@venturekit/mcp

Transport-agnostic Model Context Protocol (MCP) server primitives for VentureKit apps.

The package owns the protocol core — a stateless JSON-RPC dispatcher, a typed tool registry, and a Zod → JSON-Schema serializer for tools/list — plus an optional, batteries-included bearer-token layer: the vk_<scope>_<secret> token format, a Postgres-backed token store, and the mcp_tokens migration that ships with the package. App-specific concerns — authentication, rate limiting, audit logging, per-call context — are injected through hooks, so any app can expose an MCP endpoint (and issue/verify tokens for it) without re-implementing the generic parts.

It stays dependency-light: zod is a type-only peer dependency, token hashing uses node:crypto scrypt (no third-party crypto dep), and the store talks to your database through a structural Querier (pass @venturekit/data's query — no DB driver dependency). The dispatcher core itself remains entirely storage-free.

Why not the official SDK?

@modelcontextprotocol/sdk is built around long-lived transports/sessions (stdio, SSE, streamable HTTP). VentureKit APIs run as stateless request → response handlers (Lambda / API Gateway), where one HTTP POST carries one JSON-RPC message. This package implements exactly that slice — initialize, notifications/initialized, ping, tools/list, tools/call — with no session state to manage.

Usage

import { handleMcpRequest, McpError, type McpServerConfig } from '@venturekit/mcp';
import { z } from 'zod';

// 1. Define tools. `run` receives validated args + your per-call context.
const tools = {
  echo: {
    name: 'echo',
    description: 'Echo a message back.',
    inputSchema: z.object({ message: z.string() }),
    run: async (args: { message: string }, ctx: { tenantId: string }) => ({
      echoed: args.message,
      tenant: ctx.tenantId,
    }),
  },
};

// 2. Wire the host concerns as hooks.
const config: McpServerConfig<{ tenantId: string }, { tenantId: string }> = {
  serverInfo: { name: 'my-app-mcp', version: '1.0.0' },
  tools,
  async authenticate(authHeader) {
    const tenantId = await verifyBearer(authHeader); // throw McpError(...) to reject
    return { allowedTools: null, principal: { tenantId } };
  },
  createContext: (principal) => principal,
  // optional:
  async rateLimit(principal) {
    /* throw new McpError('rate limited', { statusCode: 429, data: { retryAfterSec: 30 } }) */
  },
  wrapToolCall: (info, run) => withAudit(info, run),
};

// 3. Dispatch a request from your HTTP route.
const { statusCode, response } = await handleMcpRequest(config, {
  authHeader: req.headers.authorization ?? null,
  body: req.body,
});

Hook contract

| Hook | Required | Purpose | | --------------- | -------- | ----------------------------------------------------------------------- | | authenticate | yes | Resolve + authorize the caller; returns { allowedTools, principal }. | | createContext | yes | Build the value passed to each tool's run (may be async). | | rateLimit | no | Pre-dispatch throttle. Throw to reject. | | wrapToolCall | no | Wrap each invocation (audit-log bracket, tracing span, etc.). |

Errors thrown from authenticate / rateLimit are mapped to the JSON-RPC error response. They may be an McpError (carrying statusCode / rpcCode / data) or any error exposing those fields — a host's existing error classes work unchanged. A retryAfterSec field is lifted into error.data automatically.

A tool's own thrown error is not a protocol error: per the MCP spec it is returned as a successful response with isError: true, so the agent can read the message and recover.

Allow-lists

authenticate returns allowedTools: null exposes every registered tool; a populated ReadonlySet<string> restricts both tools/list and tools/call to those names (handy for scoping a per-token capability set).

Bearer tokens & storage

MCP servers authenticate agents with long-lived bearer tokens. This package ships the whole generic stack so you don't re-build it per app:

  • Format & primitives (no deps, node:crypto): generateBearerToken, parseScopedToken, parseBearer, bearerTokenPrefix. Tokens are vk_<scope>_<secret>, where scope is an opaque owner key the verifier can read before hashing to narrow a lookup.
  • Postgres store (Querier-first, scrypt-hashed): createMcpToken, listMcpTokens, revokeMcpToken, verifyMcpToken. Only a scrypt hash + the lookup prefix are persisted; the plaintext is returned once from createMcpToken and is unrecoverable after.
  • Migration: mcp_tokens ships as migrations/vk_mcp_0001_tokens.sql and is auto-discovered by vk migrate for any app that depends on this package (via the vk.migrations field) — no wiring required. getMcpMigrationsDir() is the explicit escape hatch for vk.config.ts:extraMigrationsDirs.

The store is keyed on a generic scope (text, no foreign key), so it is not coupled to any host's tenancy model. A host maps scope to whatever it owns — a tenant slug, a user id, a project id — and layers its own checks on top:

import { query } from '@venturekit/data';
import { createMcpToken, verifyMcpToken } from '@venturekit/mcp';

// Issue (scope = the host's owner key — here, a tenant slug):
const { row, token } = await createMcpToken(query, {
  scope: tenant.slug,
  label: 'Cascade workflow',
  allowedTools: ['add_blog_post'], // omit/empty → every tool
  createdBy: user.id,
});
// `token` is the plaintext — surface it exactly once.

// Verify inside your `authenticate` hook:
const tok = await verifyMcpToken(query, plaintext); // active row, or null
if (!tok) throw new McpError('Invalid token', { statusCode: 401 });
// …then resolve `tok.scope` to your domain object + enforce its status.

Pass any @venturekit/data-compatible query; the store never imports a DB driver itself.