All skills
neondatabase avatar

/neon-functions

@b870d74 official

Long-running, serverless Node.js HTTP functions deployed onto your Neon branch, with DATABASE_URL injected automatically and compute that runs next to your data. Use when a user wants to host an API, an AI agent with long streaming responses, a WebSocket or server-sent-events (SSE) server, a webhook handler, a Discord bot, an MCP server, or any request/response workload that risks timing out on short, lambda-style serverless functions — and wants it to branch with their database. Also use for Function Triggers: a cron or an object-storage event that POSTs to a function. Triggers include "serverless function", "deploy an API", "long-running function", "streaming agent", "SSE server", "WebSocket server", "webhook handler", "MCP server", "cron", "function trigger", "scheduled function", "cron job", "object storage trigger", "on upload", "run code next to my database", "function that won't time out", "function logs", "Neon Functions", "Neon Compute", "DDoS protection", "rate limiting", and "production hardening".

Use this Skill: https://skilld.dev/gh/neondatabase/agent-skills/neon-functions

This session only. Nothing lands on disk.

referencesmastra-studio.md

≈1.6k tokens on demand. Your agent reads this file only when SKILL.md points to it.

Mastra agents with Mastra Studio observability

A Neon Function is a long-lived Node.js 24 process, which makes it a natural host for a Mastra agent: the agent keeps running for the life of the request, and you point its model at the Neon AI Gateway so there are no extra provider keys. You can keep running the agent on Neon Functions while shipping its traces to a Mastra Studio (Mastra Cloud) project for observability — the agent runs on Neon, the traces are viewable in Mastra.

The shape mirrors any other Node integration (see sentry.md): instantiate at module load, gate on env vars so local dev and unconfigured branches stay a no-op, and pass secrets at deploy time via neon.ts. @mastra/core and @mastra/observability bundle cleanly through neon deploy's esbuild with no extra config.

1. Define the agent against the Neon AI Gateway

With @mastra/core 1.47+, use a neon/<model> magic string — Mastra reads NEON_AI_GATEWAY_BASE_URL and NEON_AI_GATEWAY_TOKEN from the environment (injected by neon deploy / neon env pull when aiGateway is enabled in neon.ts). No manual url/apiKey or MLflow dialect swap is needed; Mastra routes each model to the correct gateway endpoint.

// src/mastra/agents/pricing.ts
import { Agent } from "@mastra/core/agent";

export const pricingAgent = new Agent({
  id: "pricing-analyst",
  name: "pricing-analyst",
  instructions: "You are a meticulous pricing analyst. …",
  model: "neon/gpt-5-mini",
});

2. Wire observability to Mastra Studio

The MastraPlatformExporter (from @mastra/observability) sends traces to a Mastra Studio project. It reads MASTRA_PLATFORM_ACCESS_TOKEN and MASTRA_PROJECT_ID from the environment.

Gotcha: Observability requires at least one exporter — passing an empty exporters array throws OBSERVABILITY_INVALID_INSTANCE_CONFIG. So omit the observability option entirely until the platform creds are present, keeping the app runnable before the Mastra project exists (and in local dev).

// src/mastra/index.ts
import { Mastra } from "@mastra/core/mastra";
import { Observability, MastraPlatformExporter } from "@mastra/observability";
import { pricingAgent } from "./agents/pricing";

const platformReady = Boolean(
  process.env.MASTRA_PLATFORM_ACCESS_TOKEN && process.env.MASTRA_PROJECT_ID,
);

const observability = platformReady
  ? new Observability({
      configs: {
        default: { serviceName: "my-app", exporters: [new MastraPlatformExporter()] },
      },
    })
  : undefined;

export const mastra = new Mastra({
  agents: { pricingAgent },
  ...(observability ? { observability } : {}),
});

Agents must be registered on the Mastra instance (the agents map) for their .generate() / .stream() calls to be traced. Call them via mastra.getAgent("pricingAgent").

3. Structured output through the gateway

The gateway does not enforce native structured output, so a bare structuredOutput: { schema } can come back missing fields (e.g. a nested meta object), failing Zod validation. Set jsonPromptInjection: true so Mastra injects the schema into the prompt and the model returns the full shape:

const agent = mastra.getAgent("pricingAgent");
const result = await agent.generate(prompt, {
  structuredOutput: { schema: myZodSchema, jsonPromptInjection: true },
  abortSignal: AbortSignal.timeout(70_000), // bound each attempt; the gateway has an upstream timeout
});
const data = result.object; // validated against myZodSchema

For resilience, register a second agent on a different model (e.g. neon/claude-haiku-4-5) and fall back to it if the primary attempt throws — both models are reachable on the gateway via the same env vars.

4. Create the Mastra project + token with the CLI

Install the Mastra CLI (npm i -g mastra) and authenticate. Project/token creation needs a live login session:

mastra auth login        # opens a browser; required before the steps below
mastra auth whoami       # shows your user + org id (org_…)
  • Access token (non-interactive): mastra auth tokens create <name> prints a one-time secret (sk_…). This is your MASTRA_PLATFORM_ACCESS_TOKEN.
  • Project: the interactive mastra studio projects create TUI is hard to script. Instead, register the project as part of a Studio deploy, which is non-interactive with -y and writes the project id to .mastra-project.json:
mastra studio deploy --org org_xxx --project my-app -y
# → .mastra-project.json: { "projectId": "…", "projectName": "my-app", "organizationId": "org_…" }

Use that projectId as MASTRA_PROJECT_ID.

Two gotchas:

  • Don't set MASTRA_API_TOKEN in the env for project/deploy commands — it makes the CLI report No organizations found. Rely on the interactive login session instead.
  • If you keep multiple env files (e.g. .env.deploy and .env.local), studio deploy errors with Multiple env files found; pass --env-file <file> to disambiguate.

5. Pass the creds via neon.ts (third-party env)

Neon-injected vars (DATABASE_URL, AI Gateway NEON_AI_GATEWAY_*) are automatic. Declare only third-party vars under the function's env, resolved from process.env at deploy time:

// neon.ts
functions: {
  myapp: {
    name: "my app",
    source: "src/index.ts",
    env: {
      MASTRA_PROJECT_ID: process.env.MASTRA_PROJECT_ID!,
      MASTRA_PLATFORM_ACCESS_TOKEN: process.env.MASTRA_PLATFORM_ACCESS_TOKEN!,
    },
  },
}

Load the values from a git-ignored file at deploy time:

neon deploy --env .env.deploy

6. Verify

Send a request that exercises the agent, then open the Mastra Studio project's Observability / Traces view — you'll see the agent run (model calls, latency, token usage) under the serviceName you configured. Only SPAN_ENDED events are exported, buffered and flushed periodically, so a trace appears a few seconds after the agent run completes.

Further reading

Source: SKILL.md on GitHub

1 warning8d3 checks · Risk SAFE
  • Gen Agent Trust Hub8d

    The neon-functions skill is a secure and highly technical guide for developing serverless Node.js functions on the Neon platform. It adheres to security best practices, including explicit warnings and code examples for JWT-based authentication, CORS configuration, and the use of environment variables for secret management. All external packages and service integrations involve well-known, reputable providers.

  • Socket8d

    No alerts

  • Snyk8d

    Risk: MEDIUM · 1 issue

Signed by skilld at b870d74. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub last week.

Activeupdated last week
Other metadata
metadata
{
  "parent": "neon",
  "source": "https://github.com/neondatabase/agent-skills/tree/main/skills/neon-functions"
}
  • API
  • nodejs
  • serverless
  • websocket
  • streaming
  • sse
  • neon
  • postgres
  • agent
  • long-running

README badge

README badge for neondatabase/agent-skills/neon-functions

Deploys long-running Node.js HTTP functions to a Neon branch with automatic DATABASE_URL injection and zero cold starts. Use this for APIs, WebSocket/SSE servers, agent backends with streaming responses, webhook handlers, and any workload that exceeds lambda-style timeouts — all co-located with your Postgres database.

Generated from the current SKILL.md.

Does this work outside us-east-2?
No. Neon Functions are currently only available in us-east-2.
What happens to my function when I create a new branch?
If neon.ts is present, the function is automatically deployed to the new branch at its own URL against the branch's isolated database and storage.
Can I use this for long-running agent workloads?
Yes. Functions are designed for agents that make multiple LLM calls and tool invocations per request; the handler can stay open as long as bytes keep flowing, with no hard execution timeout like lambda-style serverless.
Do I need to manage DATABASE_URL myself?
No. DATABASE_URL is injected automatically at runtime when the branch has Postgres. You retrieve it via parseEnv(config).
Can I host a WebSocket or SSE server on this?
Yes. Functions stay alive across requests, so you can hold WebSocket and SSE connections open in-process without needing an external state store like Redis.

Generated from the current SKILL.md. These answers refresh after source changes.