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

@neon/tools

v2.0.1

Published

Agent tools for the Neon SDK ergonomic client, compatible with MCP, Eve, and Mastra.

Readme

@neon/tools

Agent tools for the @neon/sdk ergonomic client. Select the methods an agent needs, then use the descriptors directly or adapt them for MCP, Eve, and Mastra.

npm install @neon/tools

Create tools

Selectors are SDK paths. The returned record is keyed by those paths. Each tool's published id is the last path segment, then the resource, in snake_case (projects.list → list_projects, postgres.roles.resetPassword → reset_password_postgres_roles, postgres.connectionString → connection_string_postgres). publishedId derives that string. toolIds lists every published selector.

import { createNeonTools, publishedId } from "@neon/tools";

publishedId("projects.list"); // "list_projects"
publishedId("postgres.roles.resetPassword"); // "reset_password_postgres_roles"

const apiKey = process.env.NEON_API_KEY;
if (!apiKey) throw new Error("NEON_API_KEY is required");

const tools = createNeonTools({
	apiKey,
	tools: [
		"projects.list",
		"projects.createAndConnect",
		"branches.createAndConnect",
		"branches.resetFromParent",
		"branches.compareSchema",
	],
});

const listed = await tools["projects.list"].execute({ limit: 10 });
const created = await tools["projects.createAndConnect"].execute({
	name: "agent-project",
	region_id: "aws-us-east-1",
});
await tools["branches.resetFromParent"].execute({
	project_id: "project-id",
	branch_id: "br-feature",
	preserve_under_name: "feature-before-reset",
});
const compared = await tools["branches.compareSchema"].execute({
	project_id: "project-id",
	branch_id: "br-feature",
	database_name: "neondb",
});

Trigger create keeps the discriminator nested as body. type is "schedule" or "storage_object_created":

const triggerTools = createNeonTools({
	apiKey,
	tools: ["triggers.create"],
});
const createdTrigger = await triggerTools["triggers.create"].execute({
	project_id: "project-id",
	branch_id: "br-feature",
	body: {
		type: "schedule",
		function_slug: "worker",
		name: "daily-refresh",
		schedule: { cron: "0 9 * * *" },
	},
});
const createdStorageTrigger = await triggerTools["triggers.create"].execute({
	project_id: "project-id",
	branch_id: "br-feature",
	body: {
		type: "storage_object_created",
		function_slug: "ingest",
		name: "uploads",
		storage_object_created: { bucket_name: "uploads", prefix: "incoming/" },
	},
});

limit on a list tool caps how many items come back.

MCP and Mastra publish tool.id (list_projects), not the record key.

apiKey is a Bearer credential: a Neon API key or a Neon OAuth access token. A function is called on every request, which is how short-lived OAuth tokens get refreshed. A credential is required when a tool executes — at construction, on execute(), or from MCP authInfo — and an empty value is rejected rather than ignored.

const tools = createNeonTools({
	apiKey: () => oauth.getAccessToken(),
	tools: ["projects.list"],
});

await tools["projects.list"].execute(
	{ limit: 10 },
	{ apiKey: oauthAccessToken },
);

Each tool includes its Zod 4 inputSchema, published id, title, description, safety annotations, stability metadata, and execute(). Inputs are snake_case at the tool boundary. execute() strictly validates the input, rejects unknown fields, and returns typed, JSON-safe { data }. Neon SDK errors remain typed and are thrown to the caller.

Paginated lists call .all() and return the item array. Do not pass a cursor; those fields are omitted from the input schema.

const createBranch = createNeonTool("branches.createAndConnect", { apiKey });

Writes and waiting

Tools run with waitForReadiness: true. When a mutation response includes an operations array, the call waits until those operations finish. The default deadline is five minutes (wait.timeoutMs). Pass wait: { timeoutMs: 30_000 } on createNeonTools or createNeonTool to bound that. Set it below the host's own tool-call timeout, otherwise the host gives up first.

An abort signal on execute or a wait timeout stops the poll, not the create: the branch or project may already exist, and the error does not include its id. List before retrying.

functions.deploy can still return pending. Its response has no operations array, so the tool does not poll.

metadata.method and metadata.path name the first request; extra readiness GETs are not listed there.

These public client methods are not tools: operations.waitFor, postgres.roles.password, storage.objects.get, and credentials.reveal. Waiting is what the write tools already do. projects.create and branches.create return the created resource without a connection string; createAndConnect returns a URI. triggers.create and triggers.update take the OpenAPI discriminator as a nested body field. credentials.rotate requires approval and is not idempotent: a lost success already replaced the secret; create a replacement and revoke the rotated credential.

Optional host add-ons

Descriptions

Pass a map keyed by SDK path or the current published id, or a function that can append to the generated text. A key that matches neither is ignored.

Generated Management API tools use the first sentence of the OpenAPI description (title stays the OpenAPI summary). Composed tools keep their handwritten copy. Hosts that need more still pass descriptions.

const tools = createNeonTools({
	apiKey,
	tools: ["projects.list", "projects.delete"],
	descriptions: {
		"projects.list":
			"List Neon projects in your account. Do not use for projects shared with you.",
		delete_projects:
			"Delete a Neon project and all its data. NEVER run autonomously; always ask the user first.",
	},
});

Tracking

onExecute wraps the call. The host must call event.execute(). That inner call performs getter resolution, path injection, original schema validation, auth, and the API request, so tracking and spans see those failures:

const tools = createNeonTools({
	apiKey,
	tools: ["projects.list"],
	onExecute: async ({ id, execute }) => {
		return execute();
	},
});

This package does not send analytics. Mutating event.input does not change a grant-locked project or branch id.

Names

name rewrites every published tool id. names overrides one tool first, keyed by SDK path or the generated snake-case id:

const tools = createNeonTools({
	apiKey,
	tools: ["branches.createAndConnect", "projects.list"] as const,
	names: { "branches.createAndConnect": "create_branch" },
	name: (id) => `neon_${id}`,
});

Those tools publish as neon_create_branch and neon_list_projects. The record is still keyed by SDK path (tools["branches.createAndConnect"]). MCP and Mastra publish tool.id. Eve uses the filename as the model-facing name, so name the file after the published id. Duplicate or non-snake-case ids throw, and a names key that matches no selected tool throws.

Project and branch injection

Tools take path parameters as project_id and branch_id. A host that already knows those values can inject them, including on branches.createAndConnect. Without omitFromSchema, the published field becomes optional and a caller-supplied value wins. With omitFromSchema: true, the field is removed from the published schema and the injector is the only source:

const tools = createNeonTools({
	apiKey,
	tools: ["projects.get", "branches.delete"] as const,
	inject: {
		projectId: "project-id",
		omitFromSchema: true,
	},
});

await tools["projects.get"].execute({});
await tools["branches.delete"].execute({ branch_id: "br-id" });

Use a getter when the value is request-scoped. The getter can read the host's own AsyncLocalStorage (this package does not export one):

import { AsyncLocalStorage } from "node:async_hooks";

const grant = new AsyncLocalStorage<{ projectId: string }>();

const tools = createNeonTools({
	tools: ["projects.get"] as const,
	inject: {
		projectId: () => grant.getStore()?.projectId,
		omitFromSchema: true,
	},
});

await grant.run({ projectId: "project-id" }, () =>
	tools["projects.get"].execute({}),
);

Injectors only apply to tools that have that path key. projects.list is unchanged. Empty inject values fail closed. Invalid ids still fail the original path schema before fetch.

Injection reads the URL template, so it fills path project_id and branch_id only. Query and body fields with those names stay caller-supplied, including postgres.connectionString's branch_id. omitFromSchema: true does not hide those fields.

Request schemas

Generated request schemas are available independently:

import {
	zCreateProjectBody,
	zListProjectsQuery,
} from "@neon/tools/schemas";

const query = zListProjectsQuery.parse({ limit: 10 });
const body = zCreateProjectBody.parse({
	project: { name: "agent-project" },
});

These are the raw OpenAPI request shapes, not the tool input. zCreateProjectBody still wraps fields in project. projects.createAndConnect does not.

These schemas are strict. If a newly added API field is not recognized, upgrade @neon/tools; use @neon/sdk directly until a matching tools release is available.

MCP

Use @neon/tools/mcp with MCP 2.x:

import { McpServer } from "@modelcontextprotocol/server";
import { createNeonTools } from "@neon/tools";
import { registerNeonTools } from "@neon/tools/mcp";

const apiKey = process.env.NEON_API_KEY;
if (!apiKey) throw new Error("NEON_API_KEY is required");

const server = new McpServer({ name: "neon", version: "1.0.0" });
const tools = createNeonTools({
	apiKey,
	tools: ["projects.list", "projects.createAndConnect"] as const,
});

registerNeonTools(server, tools);

registerNeonTools publishes that catalog. MCP 2 inputSchema is JSON Schema without $schema. Generated fields have types, enums, required, and constraints, and no OpenAPI property docs. Fields this package described with .describe() (pooled, finalize, zip) keep that copy.

Hosts that convert Zod themselves:

import { compactJsonSchema } from "@neon/tools/mcp";
import * as z from "zod";
import { createNeonTool } from "@neon/tools";

const tool = createNeonTool("projects.update", { apiKey });
const inputSchema = compactJsonSchema(
	z.toJSONSchema(tool.inputSchema, { io: "input" }),
);

For a remote MCP server that already authenticated the client, omit apiKey at construction. registerNeonTools sends authInfo.token as the Bearer credential: MCP 2.x http.authInfo.token, MCP 1.x authInfo.token. The host must put a Neon API key or Neon OAuth access token there. A present authInfo with an empty token is an error, not a fall back to a constructor key.

const tools = createNeonTools({
	tools: ["projects.list", "projects.createAndConnect"] as const,
});
registerNeonTools(server, tools);

This package does not implement an OAuth authorization server. That is mcp-server-neon at mcp.neon.tech.

Existing MCP 1.x servers can use the version-specific entry point:

import { registerNeonTools } from "@neon/tools/mcp-v1";

MCP 1.x still receives Zod input schemas, including handwritten .describe() copy. Generated Zod has no OpenAPI field essays. Use compactJsonSchema if you convert those schemas yourself and need $schema removed.

The adapter returns both text content and object-valued structuredContent. Execution failures use isError: true with { error: { message, name, kind, status, code, source, timeoutMs, requestId, reason, operationId } } when those fields exist on the thrown error. A wait timeout has kind: "timeout" and source: "wait".

MCP annotations are advisory; the protocol does not enforce approval. Tools expose neon/requiresApproval in MCP _meta. Hosts must read that value and enforce their own approval policy before execution.

Eve

Eve requires Node.js 24 or later.

create_and_connect_projects.ts:

import { defineTool } from "eve/tools";
import { createNeonTool } from "@neon/tools";
import { toEveTool } from "@neon/tools/eve";

const apiKey = process.env.NEON_API_KEY;
if (!apiKey) throw new Error("NEON_API_KEY is required");

export default defineTool(
	toEveTool(
		createNeonTool("projects.createAndConnect", {
			apiKey,
		}),
	),
);

Eve uses the filename as the model-facing tool name, so name the file after the published id. The adapter maps approval requirements to Eve's approval hook and forwards its abort signal. abortSignal is optional. Credentials are the constructor apiKey (a string or a callback invoked on every request).

Mastra

Mastra requires Node.js 22.13 or later.

import { createTool } from "@mastra/core/tools";
import { createNeonTools } from "@neon/tools";
import { toMastraTools } from "@neon/tools/mastra";

const apiKey = process.env.NEON_API_KEY;
if (!apiKey) throw new Error("NEON_API_KEY is required");

const neonTools = createNeonTools({
	apiKey,
	tools: ["projects.list", "projects.createAndConnect"] as const,
});
const configs = toMastraTools(neonTools);

const listProjects = createTool(configs.list_projects);
const createProject = createTool(configs.create_and_connect_projects);

The adapter maps approval requirements to Mastra's requireApproval field and forwards its abort signal. Credentials are the constructor apiKey (a string or a callback invoked on every request).

Safety and binary data

Every non-read operation is conservatively marked as potentially destructive and requires approval. Reads that return connection credentials also require approval.

Binary request fields accept base64 strings:

import { createNeonTool } from "@neon/tools";

const apiKey = process.env.NEON_API_KEY;
if (!apiKey) throw new Error("NEON_API_KEY is required");

const deployFunction = createNeonTool("functions.deploy", { apiKey });

await deployFunction.execute({
	project_id: "project-id",
	branch_id: "branch-id",
	slug: "hello",
	zip: "UEsDBA==",
});

@neon/tools supports Node.js 20.19 or later. Framework integrations also require the Node.js version supported by that framework.