mcpField
N
neon-functions

neon-functions

github.com/neondatabaseクラウド・DevOps10729.2k 回インストール1 日前に更新
リポジトリ

neon-functions について

neon-functionsは、Neonのデータベースブランチと並べてHTTPハンドラーを構築する開発者向けスキルです。Node.js関数の定義、ローカル実行、デプロイ、管理を扱い、ブランチごとに状態を分離します。

主な機能

  1. 01Node.js HTTP関数の定義とデプロイ
  2. 02neon devによるローカルホットリロード
  3. 03データベース接続変数の自動注入
  4. 04データベースブランチごとの分離
  5. 05スケジュールやオブジェクト作成で起動

活用シーン

  1. 01Postgresを参照するAPIのデプロイ
  2. 02SSEまたはWebSocketサービスの実行
  3. 03Webhookやボットのリクエスト処理
  4. 04定期処理やオブジェクト作成への対応

MCP 対応

Neon FunctionsでMCPサーバーをホストできると説明されています。

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/neondatabase/agent-skills --skill neon-functions

インストール後、Claude Code で/neon-functionsを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

references/
ai-sdk.md
function-triggers.md
mastra-studio.md
mcp.md
native-binaries.md
production-hardening.md
sentry.md
sse.md

詳細

最初に:Neon の概要、Neon の利用開始方法、Neon 開発のベストプラクティスなどについては、親の neon スキルを使用してください。

neon スキルがインストールされていない場合は、https://neon.com/docs/ai/skills/neon/SKILL.md から取得するか、次のコマンドでインストールしてください。

neon skills -s neon -y

Neon Functions

現在、aws-us-east-2、aws-us-east-1、aws-eu-central-1、aws-ap-southeast-1 で利用できます。

Neon Functions は、Neon ブランチにデプロイされる、長時間実行可能な Node.js HTTP ハンドラーです。各関数にはパブリック HTTPS URL が割り当てられ、データベースと同じリージョンで実行されます。また、ブランチに Postgres がある場合は、DATABASE_URL が自動的に注入されます。普段お使いの Neon CLI、neon.ts、API を通じて、デプロイと管理を行えます。

このスキルを使って、ユーザーがデータベースの近くで関数を定義し、ローカルで実行し、デプロイして管理できるよう支援してください。デプロイ済み関数とその呼び出し URL、動作するローカルの neon dev 開発ループ、または Neon の公式ドキュメントに基づく正確な回答を提供してください。

使用する場面

処理がリクエスト/レスポンス型であり、起動状態を保ち、データの近くで実行するメリットがある場合は、Neon Functions の使用を検討してください。

  • Lambda 型の制限を超えて実行される、長時間のリクエスト/レスポンス処理。 1 回のリクエストで複数の LLM 呼び出しやツール呼び出しを行うエージェントや、画像/動画の生成では、従来のサーバーレス関数における約 10~60 秒の実行上限や短いストリーミング時間を超えることがよくあります。Neon Functions は長時間実行が可能です。ハンドラーは 15 分以内にレスポンスを開始すればよく、バイトが流れ続ける限り、オープン状態のストリームは維持されます。実際のエージェントの処理に十分な余裕があります。
  • Redis を追加せずに実現する、状態を保持したストリーミング。 関数はリクエストの間、起動状態を保つため、プロセス内で接続を維持する SSE エンドポイントや WebSocket サーバーをホストできます。ストリームの整合性を保つためだけに、外部の状態ストア(Redis など)を用意する必要はありません。モジュールスコープの状態(pg プールやインメモリカウンターなど)は、同じ分離実行環境でリクエストをまたいで保持されます。
  • Postgres の近くで実行する必要があるコンピューティング。 関数はブランチのデータベースと同じリージョンで実行されるため、クエリのたびにリージョンをまたぐラウンドトリップが発生しません。DATABASE_URL は自動的に注入されます。
  • データとともにブランチ分岐するバックエンド。 各ブランチでは、独自の URL で関数の独自バージョンが実行され、独立したデータベース(およびストレージ、ゲートウェイ)の状態が使われます。プレビュー環境、CI、開発環境にはそれぞれ自己完結型のバックエンドが用意され、子ブランチへのデプロイが親ブランチに影響することはありません。
  • 関数(または既存のフレームワークハンドラー)から Postgres にクエリを実行する場合。 Data API よりもこちらを優先してください。アプリケーションですでに PostgREST または Supabase-js によるデータベース呼び出しを使用している場合や、そのクライアントを移行する場合は、Data API を使用してください。
  • Webhook、ボット、レスポンス後の処理。 複数の DB 書き込みに処理を振り分ける Webhook ハンドラー、Discord/WebSocket ボット、waitUntil による非同期の後続処理(分析、監査ログ)などに適しています。
  • 定期的な HTTP 処理。 Function Trigger は、cron(type: "schedule")に従って、またはオブジェクトが Object Storage に作成されたとき(type: "storage_object_created")に、関数へ POST リクエストを送信します。同じ fetch ハンドラーを使い、最初のバイトが返るまでの制限も同じく 15 分です。Function Triggers を参照してください。

純粋な静的サイトを提供する場合や、現在サポートされているリージョン(aws-us-east-2、aws-us-east-1、aws-eu-central-1、aws-ap-southeast-1)以外で実行する必要がある場合、現時点では適切なツールではありません(Timeouts and Runtime Limits と Availability を参照)。

機能

  • 長時間実行とサーバーレス — WebSocket サーバー(WebSocket サーバーを参照)、SSE エンドポイント(Server-Sent Events(SSE)を参照)、長時間のエージェント HTTP ストリーム、API 向けに構築されています。アイドル状態では引き続きゼロまでスケールします。
  • Web 標準のハンドラー — fetch(request) メソッドを持ち、Response を返すデフォルトエクスポートであれば、どの関数でも構いません(Workers/WinterTC 互換)。Hono アプリはまさにこの形式をエクスポートするため、export default app だけで動作します。Node.js 24 で実行されるため、すべての Node API を利用できます。
  • データベースの近くで実行 — ブランチのリージョンで実行されます。ブランチに Postgres がある場合、DATABASE_URL が自動的に注入されます。
  • ブランチ対応 — 各ブランチは、それぞれ独立した状態に対して、固有の URL で独自の関数バージョンを実行します。
  • 同じ CLI/API — neon、neon.ts、または Neon API を使ってデプロイおよび管理できます。
  • 関数トリガー — Neon は、cron の実行時、またはオブジェクトストレージへのアップロード時に関数へ POST リクエストを送信します。関数トリガーを参照してください。

利用可能状況

セットアップを始める前に、この前提条件を確認してください。Neon Functions は現在、aws-us-east-2、aws-us-east-1、aws-eu-central-1、aws-ap-southeast-1 で利用できます。ユーザーの Neon プロジェクトがこれらのリージョンのいずれかにあることを確認してください。

アーキテクチャ:Functions の役割

Neon(Functions を含む)は、フルスタックアプリのホスティングではなく、バックエンドのプリミティブです。アプリは Vercel(または Netlify、その他のフロントエンド/アプリホスティングサービス)でホストし、Functions はデータの近くで動作する、長時間実行可能でステートフルなバックエンドの一部を担います。これらは、次の 2 つの方法でそのプラットフォームと連携します。

  • フルスタックアプリに Function を追加する。 Vercel(または Netlify)上の Next.js/TanStack Start アプリが、UI、認証(Managed Auth、Better Auth、Clerk、またはその他の IdP)を担い、Lakebase Postgres と Object Storage に直接アクセスします。Web アプリやその他のクライアント向けの Hono API レイヤーとして、またはデータの近くで動作する単一のジョブ(Object Storage へのアップロード、AI エージェント、Discord ボット、WebSocket サーバー、SSE サーバー)用に Function を追加します。(クライアントから直接アクセスするパターンについては、エージェントのバックエンドとしての Functionsを参照してください。)
  • バックエンドのコントロールプレーン全体を Functions で実行する。 特に、フロントエンドがクライアントのみ(Vercel または Netlify でホストする TanStack Router、クライアントモードの React Router、その他同様の SPA)の場合、クライアントは Functions を直接呼び出します。REST API とリクエスト/レスポンス型のエージェントを構築し、MCP サーバーをホストし、ステートフルなものや Postgres および Object Storage の近くで動作すべきものを実行します。

どちらの場合も、呼び出し元に応じて認証します。アプリおよびパブリック HTTP には JWT または API キー(エージェントのバックエンドとしての Functionsの WARNING を参照)、Function Trigger ルートには parseTriggerDelivery を使用し、本番環境のセキュリティ強化を行ってください。Function は単なるバックエンドなので、ホスティング先と Neon の間で機能を移動できます。実行時間を増やす必要があるときは、エージェントやステートフルな WebSocket サーバーを Function に移し、必要に応じて戻すこともできます。

Postgres にクエリを実行する Function、または既存のフレームワークハンドラーを優先してください。アプリケーションですでに PostgREST/Supabase-js によるデータベース呼び出しを使用している場合や、そのクライアントから移行する場合は、Data API を使用してください。

本番環境のセキュリティ強化

本番ルートを公開する前に、本番環境のセキュリティ強化をお読みください。

呼び出し元に応じて選択してください。信頼できるアプリサーバー、Function Trigger、またはパブリックな利用者です。検証済みのストリーミング対応プロキシが必要な場合を除き、ブラウザーの長時間ストリームには、クライアントから直接アクセスする JWT パスを使用してください。認証によってアプリケーションの処理は拒否されますが、ネイティブ URL へのリクエストは引き続き Function に到達します。

セットアップ

関数は neon.ts で宣言します(ブランチファーストのワークフローと neon.ts の基本については neon スキルを参照してください)。@neon/config を追加し、functions の下に関数を slug をキーとして宣言します。

// neon.ts
import { defineConfig } from "@neon/config/v1";

export default defineConfig({
  functions: {
    todos: {
      // slug: ^[a-z0-9]{1,20}$ — lowercase letters/digits, no hyphens
      name: "todo api", // display label only
      source: "src/index.ts", // entry file, relative to neon.ts
    },
  },
});

slug は関数の永続的な識別子であり(呼び出し URL と CLI コマンドに表示されます)、最初のデプロイ後は変更できません。人が読みやすいラベルには name を使用してください。

最小限の関数の例です。注入された DATABASE_URL を使ってブランチの Postgres にクエリを実行する Hono アプリです。

// src/index.ts
import { Hono } from "hono";
import { drizzle } from "drizzle-orm/node-postgres";
import { Pool } from "pg";
import { parseEnv } from "@neon/env";
import { attachDatabasePool } from "@neon/functions";
import config from "../neon";
import { todos } from "./db/schema";

const env = parseEnv(config);
const pool = new Pool({ connectionString: env.postgres.databaseUrl, max: 5 });
attachDatabasePool(pool);
const db = drizzle(pool);

const app = new Hono();
app.get("/", (c) => c.text("Neon + Hono + Drizzle"));
app.post("/todos", async (c) => {
  const { text } = await c.req.json<{ text: string }>();
  const [row] = await db.insert(todos).values({ text }).returning();
  return c.json(row, 201);
});
app.get("/todos", async (c) => c.json(await db.select().from(todos)));

export default app;

pg プールはモジュールスコープで作成し(同じ isolate 上のリクエスト間で再利用されます)、各 isolate が独自のプールを保持するため、max は小さく(例:5)設定してください。アイドル状態での切断が uncaughtException にならないように、attachDatabasePool(pool) を呼び出してください。詳しくはPostgres への接続を参照してください。

parseEnv(config) では、設定が必要とするすべての変数が必要です。プールされた URL を介して Postgres とだけ通信する関数では、対象をそのキーだけに限定できます。parseEnv は指定したものだけを検証して返します(キーは neon.ts から自動補完されます)。

const { postgres } = parseEnv(config, ["DATABASE_URL"]); // not the unpooled URL, auth, etc.
const pool = new Pool({ connectionString: postgres.databaseUrl, max: 5 });
attachDatabasePool(pool);

ローカルでの開発とデプロイ

neon dev      # serves every function in neon.ts with hot reload; injects DATABASE_URL & friends
neon deploy --env <file>   # preferred full deploy from neon.ts; --env is the file Function env is read from

.env または .env.local を、functions.*.env のすべてのキーに対応するよう最新の状態に保ってください。neon env pull が書き込むのは Neon 管理の変数のみです。Function のシークレットをそのファイルに追加し、--env に渡してください。neon deploy --env <file> は実行のたびにそのファイルを process.env に読み込んでから、値をアップロードします。値がない場合は undefined となり、defineConfig がエラーをスローします。書き込みたくない場合は、neon.ts からキーを省略してください。process.env の値がない場合に空文字列へ変換しないでください("" がアップロードされ、ライブのキーが削除されます)。空の代入(KEY=)も "" です。TypeScript でアサーションが必要な場合は process.env.X! を使用してください。

neon.ts を適用せずに単一の関数をデプロイするには、neon functions deploy <slug> --src src/index.ts を実行します(--src にはエントリーファイル、または index.ts、index.mjs、index.js を含むディレクトリを指定できます)。このコマンドの --env はファイルパスではなく KEY=VALUE(繰り返し指定可能)です。対象を絞った環境変数の更新に使用してください。公開 URL は neon functions get <slug> で取得できます(invocation_url フィールド。形式は https://<branch_id>-<slug>.compute.<cell>.us-east-2.aws.neon.tech です)。neon functions list|get|delete で管理できます。

neon checkout が新しいブランチを_作成_し、neon.ts が存在する場合は、ポリシーが自動的に適用されます。process.env を参照する Function の環境変数を解決するには、作成時に --env <file> を渡してください(neon checkout feat --create --env .env.local)。既存のプロセス環境変数はファイル内の値より優先されます。既存のブランチにチェックアウトしても設定は再調整されません。設定変更を適用するには neon deploy --env <file> を実行してください(変更内容を確認してから --update-existing を追加してください)。

ネイティブバイナリとカスタムビルド

デフォルトのデプロイでは source が単一の index.mjs にバンドルされますが、コンパイル済みバイナリは含められません。別個のアーカイブファイルとして出荷してください。

  • Node アドオンを含む npm パッケージ(sharp):externalPackages: ["sharp"]。デプロイ時に linux-arm64 glibc ビルドがインストールされ、ファイルがコピーされます。
  • 独自のビルド出力(.node、.so、.wasm、データファイル):事前ビルド済みディレクトリと bundler: "none" を使用するか、インラインの bundler: async (fn) => ({ "index.mjs": …, "bin/tool": … }) を使用します。
  • 起動するスタンドアロン実行ファイル:上記の方法で出荷し、起動時に /run へコピーして chmod 755 を実行してください。/opt/function 内のファイルは読み取り専用ファイルシステム上でモード 644 として配置され、/tmp は noexec です。

ランタイムのターゲット、長所と短所、動作するスニペット、サイズ制限については、references/native-binaries.md を参照してください。

Neon Infrastructure as Code(neon.ts)

セットアップのfunctionsブロックは、Neon のインフラストラクチャ・アズ・コード用ファイルであるneon.tsの一部です。1 つの TypeScript ファイルで、各関数のsource、表示用のname、envと、ほかのブランチサービスをすべて宣言し、バージョン管理します(詳細なリファレンスはneonスキルを参照してください)。ブランチ用の Terraform のように扱ってください。

neon config status   # print the branch's live config (deployed functions)
neon config plan     # dry-run diff of what apply would change
neon config apply --env <file>  # bundle + deploy the declared functions  (neon deploy is an alias; pass --env when Function env reads process.env)

関数はブランチ単位です。各ブランチは、それぞれ固有の URL で独自のデプロイを実行します。neon.tsが存在する場合、neon checkoutはブランチの_作成時_にポリシーを適用します。Function の環境変数がprocess.envを読み取る場合は、作成時に--env <file>を渡してください。_既存の_ブランチをチェックアウトしても再デプロイは行われません。変更を適用するには、neon deploy --env <file>を実行してください。

ブランチごとのデプロイ設定(例:runtime)は、slug をキーとするbranchクロージャー内に記述するため、存在する関数を変更せずにブランチごとに設定を変えられます。

export default defineConfig({
  functions: { todos: { name: "todo api", source: "src/index.ts" } },
  branch: (branch) => ({
    functions: { todos: { runtime: "nodejs24" } },
  }),
});

環境変数

Neon はブランチ単位の接続文字列とサービス URL を実行時に注入します。これらを宣言したり、デプロイ時に渡したりする必要はありません。

変数備考
NEON_BRANCHブランチの名前(例:main、preview/foo)。デフォルトを含むすべてのブランチに注入されます。
DATABASE_URLプールされた接続文字列です。ほとんどのクエリで使用します。ブランチに Postgres がある場合のみ設定されます。
DATABASE_URL_UNPOOLED直接接続です。マイグレーション、LISTEN/NOTIFY、複数回のラウンドトリップが必要なトランザクションに使用します。
NEON_AUTH_BASE_URLブランチで Neon Auth が有効な場合に設定されます。
NEON_AUTH_JWKS_URLブランチで Neon Auth が有効な場合に設定されます。Managed Auth JWT を検証するための JWKS です。
NEON_DATA_API_URLブランチで Data API が有効な場合に設定されます。

これらのサービスが宣言されている場合、オブジェクトストレージ(AWS_*)と AI Gateway(NEON_AI_GATEWAY_*)の変数も注入されます。neon-object-storageとneon-ai-gatewayのスキルを参照してください。

neon env pull/neon-env run/neon devは、ローカルの開発環境にもNEON_BRANCH(および接続文字列)を出力するため、ローカルでの実行環境をデプロイ済みの実行環境に合わせられます。

独自のシークレットはデプロイごとに設定します。推奨される方法は、neon.tsで宣言してneon deploy --env <file>を実行することです。<file>は、env pull が書き込む gitignore 対象のファイルです(そのファイルが存在する場合は.env、それ以外の場合は.env.local)。env pull が書き込むのは Neon 管理の変数のみです。Function のシークレットはそのファイルに追加してください。宣言したすべての Function env キーが存在している必要があります。書き込みたくないキーはneon.tsで省略してください。undefinedは、キーの書き込みを指定したものの値がないことを意味し(defineConfigがエラーをスローします)、process.envの値がない場合に空文字列へ変換してはいけません。これを行うと""がアップロードされ、ライブのキーが削除されます。ファイル内の空の代入(KEY=)も""になります。TypeScript で型アサーションが必要な場合は、process.env.X!を使用し、ファイルに値があることを確認してください。

functions: {
  todos: {
    name: "todo api",
    source: "src/index.ts",
    env: { RESEND_API_KEY: process.env.RESEND_API_KEY! },
  },
}

neon functions deploy --env KEY=VALUEは手動で設定する方法です(繰り返し指定できます。--env KEY=はキーを削除し、指定していないキーは引き継がれます)。特定の環境変数だけを更新する場合に使用し、neon.tsの設定をすべて適用する場合には使用しないでください。

Function のシークレットを、env pull が書き込んだ同じファイルに読み込ませてから、neon deploy --env <file>を実行してください。ローカル開発用にブランチの Neon 管理変数をディスクに取得するには、neon env pullを使用します(link/checkoutでは自動的に実行されます。スキップするには--no-env-pullを渡し、実行時の注入にはneon-env run -- <cmd>を使用してください)。上限は変数 1,000 個、合計 64 KiB で、NEON_プレフィックスは予約されています。

Postgres への接続

ブランチに Postgres がある場合、Neon は実行時に接続文字列を注入します。宣言したり、デプロイ時に渡したり、ハードコードしたりする必要はありません。使用するのは次の 2 つです。

  • DATABASE_URL — プール経由の接続文字列です(Neon の接続プーラーを経由します)。通常のリクエスト/レスポンスのクエリ処理に使用してください。すべての Postgres ORM(Drizzle、Prisma、Knex、…)がデフォルトで DATABASE_URL を読み込むため、接頭辞は付けません。
  • DATABASE_URL_UNPOOLED — 同じデータベースへの直接接続文字列です。マイグレーション、LISTEN/NOTIFY、複数のステートメントを含む長時間のトランザクションに使用してください。

クエリとスキーマ管理には、Neon のサーバーレスドライバーではなく、node-postgres(pg)の上に Drizzle(または別の ORM)を使用してください。関数は長時間実行され、多数のリクエストで同じアイソレートが再利用されるため、永続的な pg プールが適しています。サーバーレスドライバーの HTTP トランスポートは、完全に分離された Lambda スタイルのランタイム向けです。

接続プールはモジュールスコープで 1 回だけ作成し、リクエスト間で再利用してください。リクエストごとに接続を開かないでください。

import { attachDatabasePool } from "@neon/functions";
import { drizzle } from "drizzle-orm/node-postgres";
import { Pool } from "pg";

const pool = new Pool({ connectionString: process.env.DATABASE_URL, max: 5 });
attachDatabasePool(pool);
const db = drizzle(pool);

node-postgres は、アイドル状態のクライアントで発生したエラーをプールの error イベントとして出力します。リスナーがない場合、これは uncaughtException となり、Node はアイソレートを終了します。new Pool の後に attachDatabasePool(pool) を 1 回呼び出してください。@neon/functions ≥ 0.8.0 が必要です。想定されるアイドル時の切断(ECONNRESET、EPIPE、ETIMEDOUT、Postgres の 57P01、node-postgres の Connection terminated unexpectedly)は出力されません。それ以外は console.error に出力されます。最初の呼び出し時に渡した場合は onUnexpectedError が使用されます。最初の呼び出しが優先されるため、後から onUnexpectedError を渡して呼び出しても無視され、警告が出ます。この処理によってプールが閉じられることはありません。

アイソレートは多くのリクエストで再利用されるため、プーリングをおすすめします(同じアイソレートで複数のリクエストが同時に処理中となる場合もあります。タイムアウトとランタイム制限を参照してください)。モジュールスコープのプールはコールドスタート時に 1 回開かれ、その後、そのアイソレートが処理するすべてのリクエストで共有されます。そのため、リクエストごとに接続を確立するのではなく、接続確立のコストを分散でき、負荷時に Postgres の接続を使い果たすのを防げます。

max は小さな値(例:5)にしてください。各アイソレートは独自のプールを保持するため、Postgres への総接続数は稼働中のアイソレート数に応じて増加します。シャットダウン時にプールを閉じる必要はありません。ランタイムがアイソレートを破棄するときに SIGINT/SIGTERM が送られ、Neon のプーラーが接続を回収するため、明示的なドレインハンドラーは不要です。

process.env.DATABASE_URL を直接読み取る方法は、どこでも機能します。セットアップの関数では、代わりに @neon/env の parseEnv(config) を使い、同じ値を型付きかつ検証済みの方法で読み取っています。どちらでも問題ありません。

タイムアウトとランタイムの制限

関数は長時間実行できますが、依然としてサーバーレスです。バックグラウンドジョブランナーではなく、リクエスト/レスポンス型のランタイムです。厳格な制限は次のとおりです。

  • 最初のバイトまでの時間:15 分。 ハンドラーはリクエストを受け取ってから 15 分以内にレスポンスの返却を_開始_する必要があります。ほとんどのハンドラーは数秒で完了します。15 分という上限は、画像や動画の生成など、エージェントのワークロードに余裕を持たせるためのものです。
  • ハートビート:15 分。 WebSocket/SSE 接続は、データが流れている限り維持されます。タイムアウトは接続が無通信になったときにのみ発生します。静かなストリームを維持するには、15 分ごとに少なくとも 1 バイトを送信してください。
  • waitUntil:15 分。 waitUntil(@neon/functions から)に登録した処理は、レスポンス送信後も最大 15 分間、呼び出しを維持します。これは分析データの書き込みや監査ログなどのクリーンアップ用であり、バックグラウンドジョブランナーではありません。Neon ランタイムの外部(ローカルの neon dev、テスト)では何もしません。Promise は実行されますが、追跡はされません。
  • アイドル時の終了。 アクティブな接続がない場合、Neon は関数をシャットダウンします。また、運用上の理由(メンテナンスや、関数を別のコンピュートノードへ移動する場合など)で終了または再起動することがあります(アクティブな関数はその前に数時間動作する場合があります)。終了はプロセスの再起動と同様に扱ってください。WebSocket/SSE クライアントは再接続する必要があります。Neon は終了前に SIGINT を送信するため、process.on("SIGINT", ...) ハンドラーを使えば、関数が終了しようとしていることを検知し、直前のクリーンアップを実行できます。Postgres 接続を閉じるためだけにハンドラーを用意する必要はありません。Neon のプーラーが自動的に回収します。
  • ランタイム: Node.js 24、メモリは 2048 MiB に固定されています。スラッグは ^[a-z0-9]{1,20}$ に一致する必要があります。分離環境は複数のリクエスト間で再利用されます。同じ分離環境上で複数のリクエストが同時に処理されることがあり(Node のシングルスレッドのイベントループ上で交互に処理されます)、負荷が高い場合、ランタイムはそれぞれ独自のモジュール状態を持つ複数の分離環境を並行して実行します。そのため、モジュールスコープに保持される状態は分離環境ごと(その分離環境が処理するすべてのリクエストで共有)であり、メモリ内にのみ存在します。終了後も保持する必要があるデータは、Postgres に永続化してください。この再利用があるからこそ、リクエストごとではなくモジュールスコープで一度だけ接続プールを作成します(Postgres への接続を参照)。

エージェントのバックエンドとしての Functions(Next.js および類似フレームワーク)

Neon Function は、AI エージェントの実行場所として最適です。Lambda 形式のサーバーレスとは異なり、タイムアウトしないためです(15 分の制限についてはタイムアウトとランタイムの制限を参照)。そのストリームを、Vercel、Netlify などでホストされる Next.js のルートハンドラー、Remix/SvelteKit/Nuxt のアクションなどを介してプロキシすると、Function は処理を続けられる場合でも、そのホストに設定された実行時間や転送の制限を超えた時点でストリームが切断されます。以下のクライアントから直接 JWT を使う方法をデフォルトにしてください。ストリーミングに対応したプロキシ(ストリームを検証した後の、HTTP トリガー型 Cloudflare Worker)は、本番環境での堅牢化に記載されている一般公開向けの例外です。

エージェント自体の構築。 エージェントの構築には Vercel AI SDK と Mastra を推奨します。どちらも Neon AI Gateway(neon-ai-gateway スキルを参照)を指定すれば、追加のプロバイダーキーなしで、すべてのモデルに対して 1 つの認証情報を使えます。Function として実行する完全な AI SDK エージェント(toUIMessageStreamResponse によるストリーミング、Postgres と連携した複数ステップのツール呼び出し、生成画像の Object Storage への永続化)については、references/ai-sdk.mdを参照してください。トレース機能が組み込まれた Mastra の同等の例については、references/mastra-studio.mdを参照してください。

解決策:クライアントから関数を直接呼び出す。 長時間かかるリクエストをアプリサーバー経由にしないでください。

Browser ──(Authorization: Bearer <JWT>)──▶  Neon Function (agent)   ✅ no host timeout
Browser ──▶ your app backend ──▶ Neon Function                       ❌ host cuts the stream
  • アプリがすでに使用している認証情報から、有効期間の短いベアラートークンを取得します。Function を呼び出すために、Clerk、Better Auth、Auth.js、Supabase Auth、Managed Auth に切り替えないでください。
    • Managed Auth のデフォルトクライアント(createAuthClient/Next ラッパー):authClient.token() を呼び出し、次に data.token を取得します。注入された NEON_AUTH_JWKS_URL と、issuer new URL(process.env.NEON_AUTH_BASE_URL!).origin を使って検証します。
    • SupabaseAuthAdapter() を使う Managed Auth:このクライアントには .token() がありません。getSession() を呼び出し、次に data.session.access_token を取得します。JWKS と issuer は上記と同じです。
    • JWKS をすでに公開している既存の Better Auth/Auth.js/その他の署名者:その JWKS URL、issuer、audience を使用します。インストール済みの契約を確認してください。Cookie セッションやデータベースセッションは JWKS ではありません。
    • Cookie/データベースセッションのみの場合:既存のアプリバックエンドで短いトークンを発行し(この呼び出しは高速で、ホストの制限内に収まります)、その後、ブラウザーから Authorization: Bearer を使って Function を直接呼び出します。Function のストリームをアプリのホスト経由にしてはいけません。
  • トークンをクライアントに渡します。たとえば Vercel AI SDK の場合、new DefaultChatTransport({ api: NEON_FUNCTION_URL, fetch }) を使い、fetch で Authorization: Bearer <token> を付加します。長時間のストリームがアプリサーバーを経由することはありません。
  • ブラウザーからアクセスできるように CORS を追加します(OPTIONS を処理し、Access-Control-Allow-Origin/-Headers を設定します)。

警告

Neon Function には**パブリック HTTPS URL があり、誰でもアクセスできます。**クライアントから Function を直接呼び出す場合、アクセスを制限するアプリバックエンドが前段にないため、**Function 自体で必ず認証を行う必要があります。**呼び出し元の JWKS に対して JWT を検証する、共有シークレット/API キーを確認する、またはリクエストを拒否してください。認証なしのエージェントをデプロイしないでください。ブラウザーからの呼び出しには有効期間の短いユーザートークンを使用します。サーバーまたはプロキシのオリジンシークレット(X-Secret)はサーバー側に保持してください。詳しくは本番環境のセキュリティ強化を参照してください。

// src/index.ts — verify the caller before doing any work
import { createRemoteJWKSet, jwtVerify } from "jose";

const jwks = createRemoteJWKSet(new URL(process.env.NEON_AUTH_JWKS_URL!));
const issuer = new URL(process.env.NEON_AUTH_BASE_URL!).origin;

export default {
  async fetch(request: Request) {
    if (request.method === "OPTIONS")
      return new Response(null, { status: 204, headers: cors(request) });

    const auth = request.headers.get("authorization");
    if (!auth?.toLowerCase().startsWith("bearer ")) {
      return new Response("Unauthorized", {
        status: 401,
        headers: cors(request),
      });
    }
    let userId: string;
    try {
      const { payload } = await jwtVerify(auth.slice(7), jwks, { issuer });
      if (!payload.sub) {
        return new Response("Unauthorized", {
          status: 401,
          headers: cors(request),
        });
      }
      userId = payload.sub;
    } catch {
      return new Response("Unauthorized", {
        status: 401,
        headers: cors(request),
      });
    }
    // Authorize resource access by userId, then run the agent scoped to that user.
    // ... return result.toUIMessageStreamResponse({ headers: cors(request) })
  },
};

このスニペットは Managed Auth の検証を行います。デフォルトクライアントでは .token()(data.token)を使ってベアラートークンを発行し、SupabaseAuthAdapter() では getSession() を呼び出してから data.session.access_token を取得します。別の認証情報を使う場合は、そのアプリの JWKS URL と issuer を Function の env 経由で渡し(環境変数を参照)、そのトークンの契約で必要な場合にのみ audience を含めてください。https://neon.com/docs/compute/functions/authentication.md

有効なトークンがあっても、他のユーザーの行を読み取る権限があるとは限りません。2 人のユーザーで検証してください。それぞれが自分のデータにはアクセスでき、ユーザー間のアクセスは拒否されることを確認します。保存済みの行を対象に Function を再起動した後も、同じ検証を繰り返してください。リクエストで指定された所有者 ID によってアクセス権を付与してはいけません。

生成した画像や履歴など、保持する必要があるものはすべて Postgres に永続化してください。モジュールの状態は、エビクション後には保持されません。

WebSocket サーバー

WebSocket サーバーは、Functions ワークロードの標準的な形です。長時間実行されるハンドラーがプロセス内で接続を維持するため、ストリームの整合性を保つ外部の状態ストアは必要ありません。データが流れている限り接続は維持されます(15 分間のハートビートについては、タイムアウトを参照してください)。

fetch 内でアップグレードします。 @neon/functions の upgradeWebSocket(request) を呼び出し、返されたレスポンスをそのまま返します。Hono アプリでは、@neon/functions/hono を使って同じプリミティブを利用できます(下記の Hono を参照)。エントリーポイントは 1 つで、WebSocket の依存関係をインストールする必要はありません。

import { upgradeWebSocket } from "@neon/functions";

export default {
  async fetch(req: Request): Promise<Response> {
    if (req.headers.get("upgrade")?.toLowerCase() !== "websocket") {
      return new Response("expected a websocket upgrade", { status: 426 });
    }

    const { socket, response } = upgradeWebSocket(req);
    socket.addEventListener("message", (event) => socket.send(event.data));
    return response;
  },
};

socket は標準の WebSocket なので、addEventListener と onopen/onmessage/onclose/onerror プロパティの両方が使えます。取得時点ではまだ CONNECTING の状態です。ランタイムはハンドラーが response を返した後にのみ 101 を書き込み、その時点でソケットが開きます。

重要なルールは 3 つあります。

  • response は変更せずに返します。 101 は通常の Response として作成できません(fetch の仕様では、作成したレスポンスのステータスは 200〜599 に制限されています)。そのため、ランタイムは保留中のアップグレードを保持するオブジェクトを返します。clone() を呼び出したり、レスポンスを書き換えるミドルウェアのように new Response(res.body, res) で再構築したりすると、アップグレードが破棄され、リクエストは失敗します。
  • ハンドシェイクを拒否するには、通常の Response を返します。 ソケットへのアクセスを制限するには、アップグレードする前に fetch から 401、403、または 404 を返します。ブラウザーのクライアントはハンドシェイクが拒否された理由を読み取れず、ステータスや本文ではなく一般的な接続エラーのみを受け取ります。接続を拒否してクライアントを締め出し、クライアントに必要な詳細情報は別の認証済みリクエストで送信してください。
  • binaryType のデフォルトはブラウザーの "blob" ではなく "arraybuffer" です。 event.data はテキストフレームでは string、バイナリフレームでは ArrayBuffer になるため、typeof で分岐してください。

認証を行う場合。 ブラウザーから WebSocket にヘッダーを設定することはできないため、?token= クエリパラメーターを使って認証します。エージェントのバックエンドと同じ方法で、JWKS に対して jwtVerify を実行し、アップグレード前に拒否してください。

// src/index.ts
import { upgradeWebSocket } from "@neon/functions";

const clients = new Set<WebSocket>();

export default {
  async fetch(request: Request): Promise<Response> {
    if (request.headers.get("upgrade")?.toLowerCase() !== "websocket") {
      return new Response("WebSocket endpoint — connect with ?token=<jwt>");
    }

    const url = new URL(request.url);
    const identity = await verifyToken(url.searchParams.get("token"));
    if (!identity) return new Response("unauthorized", { status: 401 });

    const { socket, response } = upgradeWebSocket(request);
    clients.add(socket);
    socket.addEventListener("close", () => clients.delete(socket));
    socket.addEventListener("message", (event) => {
      if (typeof event.data !== "string") return;
      persist(identity.id, event.data); // fan out to every isolate — see below
    });
    return response;
  },
};

サブプロトコル。 { protocol } を渡すと、クライアントが提示したプロトコルから 1 つを選択できます。選択したプロトコルは Sec-WebSocket-Protocol でエコーされ、socket.protocol から参照できます。クライアントが提示していないプロトコルを選択すると、TypeError がスローされます。省略すると、プロトコルはネゴシエーションされません。拡張機能もネゴシエーションされません。socket.extensions は常に "" で、permessage-deflate は利用できません。

Hono。 @neon/functions/hono の upgradeWebSocket を使います。これは Hono 独自の WebSocket ヘルパーと同じプリミティブで、ws への依存関係はなく、非推奨の @hono/node-ws も使用しません。認証には通常のミドルウェアを使います。next() の前にアップグレードリクエストを制限してください。

// src/index.ts
import { Hono } from "hono";
import { upgradeWebSocket } from "@neon/functions/hono";

const clients = new Set<WebSocket>();

const app = new Hono<{ Variables: { userId: string } }>();

app.use("/ws", async (c, next) => {
  const identity = await verifyToken(c.req.query("token"));
  if (!identity) return c.text("Unauthorized", 401);
  c.set("userId", identity.id);
  await next();
});

app.get(
  "/ws",
  upgradeWebSocket((c) => ({
    onOpen(_event, ws) {
      clients.add(ws.raw);
      ws.send("welcome");
    },
    onClose(_event, ws) {
      clients.delete(ws.raw);
    },
    onMessage(event, ws) {
      ws.send(`echo: ${event.data}`);
    },
  })),
);

export default app;

関数の wss:// URL(neon functions get <slug> で取得)を使ってブラウザーから接続します。たとえば、new WebSocket("wss://<branch>-<slug>.compute.<region>.aws.neon.tech/ws?token=<jwt>") のようにします。接続が閉じたら再接続してください。アイソレートは退避される可能性があり、アイドル状態の接続は 15 分後に終了することがあります。

アップグレードルートには cors() を設定しないでください。また、await next() の前に c.res を読み取ったり、その後に c.header() を呼び出したりしないでください。どちらも 101 を再構築し、アップグレードを失敗させます。ミドルウェアの全一覧は @neon/functions の README を参照してください。

ハートビート(ソケットを維持する)

接続が開いたままになるのは、バイトが流れている間だけです。Neon は無通信のストリームを 15 分後に切断し(タイムアウトとランタイムの制限)、中継プロキシやロードバランサーは通常、はるかに短い時間(多くの場合、数十秒)で切断します。アプリが十分な頻度で通信することを当てにせず、ソケットが無通信にならないよう、サーバーから定期的にキープアライブを送信してください。

標準の WebSocket インターフェースには ping() がないため、クライアントが無視するアプリケーションレベルのメッセージを送信します。

const HEARTBEAT_MS = 25_000; // comfortably under proxy idle timeouts

const beat = setInterval(() => {
  for (const socket of clients) {
    if (socket.readyState === socket.OPEN) socket.send('{"type":"ping"}');
  }
}, HEARTBEAT_MS);
beat.unref?.();

クライアントはメッセージの処理時にこれらをスキップします。ここにプロトコルレベルの代替手段はありません。upgradeWebSocket の標準 WebSocket には ping() がなく、ブラウザーは JavaScript から ping フレームを送信できないため、アプリケーションレベルのメッセージがブラウザークライアントで使用できる唯一のキープアライブです。(Node の ws クライアントは ping フレームを送信でき、サーバーは自動的に pong で応答しますが、ブラウザーではできません。)

アイソレート間のファンアウト(省略しないでください)

高負荷時、ランタイムはそれぞれが独自のモジュール状態のコピーを持つ複数のアイソレートを並行して実行します。そのため、各アイソレートは独自の clients セットを持ちます。そのローカルセットにだけブロードキャストすると、アイソレート A のクライアントにはアイソレート B で発生したイベントが届かず、フィードが気づかれないまま分断されます。これは見落としやすい問題です。neon dev は単一プロセス(1 つのアイソレート)を実行するため、プロセス内ブロードキャストはローカルでは常に問題なく動いているように見えますが、本番環境では複数のアイソレートに同時接続が分散するため機能しなくなります。

データがデータベースの行(ライブダッシュボード、フィード、保存済みメッセージを使ったチャット)の場合は、独自にファンアウトを実装せず、Neon Realtime(neon-realtime スキル)を使ってください。Realtime を使っても、データベースがゼロまでスケールダウンする機能は妨げられません。以下の方法は、データベースの行に由来しないプッシュ(プレゼンス、タイピング、LLM ストリーム)や、Claimable プロジェクトがクレームされるまでのように Realtime を利用できない場合の代替策です。

モジュール状態は、いずれにせよエビクション後には維持されないため、Postgres が共有される信頼できる唯一の情報源です。ファンアウトの方法を選んでください。以下のすべてのスニペットで、pool はプールされた pg クライアントで、clients はこのアイソレートのライブ接続を保持する Set です。

1. Postgres をポーリングする — デフォルトの方法で、Scale to Zero も維持できます。 各アイソレートは短い間隔で共有状態(またはカーソルより後の行)を再読み込みし、変更を自身のクライアントにプッシュします。クエリはクライアントごとではなく、各アイソレートにつき各間隔で 1 回です。また、アイソレートにクライアントがいないときはクエリを実行しないため、アイドル状態のコンピューティングは引き続き一時停止します。

let lastId = "0"; // bigint id, so a string
let polling = false;

async function poll() {
  if (polling || clients.size === 0) return; // guard overlap; no clients → no query → compute can scale to zero
  polling = true;
  try {
    const { rows } = await pool.query(
      "SELECT id, payload FROM events WHERE id > $1 ORDER BY id",
      [lastId],
    );
    for (const { id, payload } of rows) {
      lastId = id;
      for (const socket of clients) {
        if (socket.readyState === socket.OPEN) socket.send(payload);
      }
    }
  } catch (err) {
    console.error("[poll]", err);
  } finally {
    polling = false;
  }
}

// Seed from the latest id so a fresh isolate sends only new rows, not the whole table, then poll.
pool
  .query("SELECT coalesce(max(id), 0)::text AS id FROM events")
  .then((seed) => {
    lastId = seed.rows[0].id;
  })
  .catch((err) => console.error("[seed]", err))
  .finally(() => setInterval(poll, 1000).unref?.());
  • レイテンシ: 間隔分まで(約 1 秒)です。
  • スケーリング: データベースの負荷はクライアント数ではなく、稼働中のアイソレート数に応じて増加します。カーソルにはインデックス付きの serial/bigserial PK を使い、間隔は適切な値にしてください。
  • Scale to Zero: ✅ 維持されます。クライアントが接続されていないとポーリングが停止するため、コンピューティングは通常のタイマーに従って一時停止します。
  • 順序: WHERE id > cursor は、コミット順が入れ替わると行を取りこぼす場合があります。低い ID を取得したトランザクションが、高い ID のトランザクションのコミット後にコミットすると、その行はカーソルより前になり、ポーリングで返されなくなります。ブロードキャストフィードでは、多少の取りこぼしは通常問題ありません。すべての行が必要な場合は、LISTEN/NOTIFY を使うか、小さな重複期間を設けた created_at によるポーリングを行い、ID で重複を排除してください。

2. LISTEN/NOTIFY — レイテンシは最小ですが、Scale to Zero を無効にする必要があります。 各アイソレートは、専用の非プール接続を通じてチャネルを LISTEN します。ブロードキャストは NOTIFY で行うため、送信元を含むすべてのアイソレートがソケットへ再プッシュします。ほぼ即時に届きますが、リスナーはアイドル状態の接続を保持し、それはアクティブとしてカウントされません。そのため、Scale to Zero によってコンピューティングが一時停止され、接続が切断され、フィードが気づかれないまま停止します。常時稼働のコンピューティング(Scale to Zero が無効になっている有料プランの設定)でのみ使用してください。

import { attachDatabasePool } from "@neon/functions";
import { Pool, Client } from "pg";

const pool = new Pool({ connectionString: process.env.DATABASE_URL, max: 5 });
attachDatabasePool(pool);
const CHANNEL = "chat_events";

// One dedicated DIRECT connection per isolate, just to receive events.
// Use DATABASE_URL_UNPOOLED — LISTEN needs a real session, not a pooled one.
// Don't call attachDatabasePool here: it would silence the idle drop that killed the feed.
// The error listener keeps the process alive; reconnect the client on error in production (omitted here).
const listener = new Client({
  connectionString: process.env.DATABASE_URL_UNPOOLED,
});
listener.on("error", (err) => {
  console.error(err);
});
listener.connect().then(() => listener.query(`LISTEN ${CHANNEL}`));
listener.on("notification", (msg) => {
  if (!msg.payload) return;
  for (const socket of clients) {
    if (socket.readyState === socket.OPEN) socket.send(msg.payload);
  }
});

// Broadcast by NOTIFYing through the pool — every isolate's listener fires.
function broadcast(event: unknown) {
  return pool.query("SELECT pg_notify($1, $2)", [
    CHANNEL,
    JSON.stringify(event),
  ]);
}

3. 外部 Pub/Sub(例:Upstash Redis)— 大規模利用に最適です。 多数へのファンアウト、大量の接続に対する 1 秒未満のレイテンシ、またはマルチリージョンが必要な場合は、専用ブローカーを介してパブリッシュ/サブスクライブします。スループットが最も高く、Postgres に触れず、Scale to Zero も妨げませんが、別のサービスを運用する必要があります。

目安: データベースの行にはNeon Realtimeを使ってください。それ以外はまずポーリングから始めます(Scale to Zero に対応し、追加インフラも不要です)。1 秒未満のレイテンシが必要な常時稼働のコンピューティングでのみ LISTEN/NOTIFY に切り替え、ファンアウトが Postgres の処理能力を超えたら Redis に移行してください。

クライアントは再接続する必要があります

アイドル状態の関数は退避され(運用上の理由で分離環境が再起動することもあるため)、クライアントのソケットは必ず切断されます。再接続は例外ではなく通常の動作として扱ってください。上限を設けた指数バックオフで再接続し、試行のたびに新しいトークンを発行してください(トークンの有効期間は短いため、古いトークンではupgrade認証チェックに失敗します)。

let closed = false,
  retry = 0,
  timer: ReturnType<typeof setTimeout>;

async function connect() {
  if (closed) return;
  const token = await getToken(); // re-mint each attempt; short-lived
  const ws = new WebSocket(`${WS_URL}?token=${encodeURIComponent(token)}`);
  ws.onopen = () => {
    retry = 0; // reset backoff on success
  };
  ws.onmessage = (e) => {
    /* apply the event */
  };
  ws.onclose = () => {
    if (!closed)
      timer = setTimeout(connect, Math.min(1000 * 2 ** retry++, 15000));
  };
  ws.onerror = () => ws.close(); // let onclose drive the retry
}
connect();

fetch内のupgradeWebSocket、?token=によるJWT認証、分離環境をまたぐファンアウト、クライアント側のバックオフを組み合わせることで、単一の関数で完全なWebSocketバックエンドを構築できます。これは、データベースの行に由来しないプッシュ(プレゼンス、入力中表示、LLMストリーム)や、Realtimeを利用できない場合に適しています。データベースの行を使う永続チャット、フィード、その他のライブUIには、Neon Realtime(neon-realtimeスキル)を使用してください。

Server-Sent Events(SSE)

サーバーからクライアントへのストリーミングだけ(ライブカウンター、通知、進捗状況、トークンストリーム)が必要な場合は、SSEのほうがWebSocketよりシンプルで、アップグレードも不要です。通常のfetchハンドラーが、Content-Type: text/event-streamを指定したReadableStreamを本文に持つResponseを返すだけで、バイトデータが流れている限りランタイムが接続を維持します。ブラウザーはEventSourceで受信し、自動で再接続するため、クライアント側のバックオフを実装する必要はありません。

// src/index.ts — minimal SSE endpoint
const encoder = new TextEncoder();
export default {
  fetch: () => {
    let t: ReturnType<typeof setInterval>;
    return new Response(
      new ReadableStream<Uint8Array>({
        start(controller) {
          controller.enqueue(encoder.encode("data: hello\n\n"));
          t = setInterval(
            () => controller.enqueue(encoder.encode(": ping\n\n")),
            25_000,
          );
        },
        cancel() {
          clearInterval(t); // fires when the client disconnects
        },
      }),
      {
        headers: {
          "Content-Type": "text/event-stream",
          "Cache-Control": "no-cache, no-transform",
        },
      },
    );
  },
};

WebSocketと同じルールが適用されます。**ハートビート:**ストリームはバイトデータが流れている間だけ接続を維持します。Neonの上限は15分(タイムアウトとランタイム制限)ですが、プロキシの制限は通常これよりずっと厳しいため、アイドル状態のストリームが切断されないよう、約25~30秒ごとに: ping\n\nコメントを送信してください(上記の例を参照)。状態はPostgresに保持し、ファンアウト戦略のいずれかを使って分離環境間でファンアウトします(ストリームコントローラーのSetを保持し、それぞれにenqueueします)。EventSourceはGETのみで、ヘッダーを設定できないため、WebSocketの場合と同様に、?token=クエリパラメーターまたはCookieで認証します。完全なパターン(Honoの実装、分離環境間のファンアウト、ワイヤーフォーマット、クライアント、注意事項)はreferences/sse.mdを参照してください。

Function Triggers

Function Triggerは、cron(schedule)またはObject Storageでオブジェクトが作成されたとき(storage_object_created)に、関数へJSONをPOSTします。neon.tsで宣言し、neon deployで適用して、parseTriggerDelivery(@neon/functions/triggers)で配信を認証します。parseTrigger(Hono)とparseTriggerInvocationは、引き続きschedule専用です。neon.tsを優先し、CLIとNeon MCPトリガーツール(list_triggers、create_trigger、…)は代替手段として使用してください。

トリガーのルートでは、ユーザーJWTやX-Secretを要求してはいけません。NeonはそれらなしでネイティブURLにPOSTします。本番環境での呼び出し元の形式については、references/production-hardening.mdを参照してください。フィールド一覧、CLI、MCP、ペイロード、継承、2種類のハンドラー形式については、references/function-triggers.mdを参照してください。

MCP サーバー

MCP サーバーは、Functions に自然に適したワークロードです。つまり、AI クライアント(Cursor、Claude、ChatGPT、エージェント)にツールを公開する、長時間実行される HTTP ハンドラーであり、ツールはコンピューティング環境のすぐそばにあるブランチの Postgres を読み書きします。MCP のストリーミング可能な HTTP トランスポートは、単一のエンドポイント(慣例的に /mcp)に対する通常の POST/GET なので、upgrade メソッドや追加のプロトコルなしで、関数の fetch ハンドラーに対応します。

最もシンプルなホストは、公式の @modelcontextprotocol/sdk と @hono/mcp を使った Hono アプリです。これはトランスポートをルートに接続します。サーバーを構築してツールを登録し、モジュールスコープでトランスポートを一度作成したら、すべての /mcp リクエストをそこに渡します。

const transport = new StreamableHTTPTransport();
app.all("/mcp", async (c) => {
  if (!mcpServer.isConnected()) await mcpServer.connect(transport);
  return transport.handleRequest(c);
});

関数の URL は公開されているため、トランスポートに接続する前に認証してください。Better Auth は、OAuth(MCP プラグインによってアプリが認可サーバーとなり、サードパーティークライアントが MCP 仕様に従って個別に認可を行います)と、よりシンプルな API キー/セッション JWT による独自の呼び出し元向けチェックの両方に対応します。公開コンシューマー向けのエッジ保護については、references/production-hardening.md を参照してください。references/mcp.md には、Drizzle を介して Postgres を利用するツールを備えたサーバー、Better Auth の 2 つの認証オプション、mcporter/add-mcp を使ったテストを含む、パターンの全容が記載されています。

インテグレーションと可観測性

組み込みのブランチログ
neon logs query --branch production --source function --since 1h

現在、ブランチログの対象となるソースは 2 つあり、Functions はその 1 つです。もう 1 つは Object Storage です。ログは単一のブランチにスコープされるため、デプロイ済みの関数が現在チェックアウトしているブランチにない場合は、--branch を指定してください。ログに関するその他の情報(必要な CLI バージョン、フィルター、SDK、Loki 互換の読み取り API)については、親の neon スキルの可観測性セクションを参照してください。

アプリケーションの計装

関数は、ウェブ標準のリクエスト/レスポンスハンドラーを実行する長時間稼働の Node.js プロセスなので、標準的な Node インテグレーション SDK をそのまま利用できます。これらはモジュールの読み込み時に一度初期化し、環境変数が設定されている場合にのみ有効にすることで、ローカル開発や未設定のブランチでは何もしないようにします。シークレットは --env または neon.ts の env を介して渡してください。

  • Sentry — HTTP フレームワーク、関数ランタイム、エージェント自身が捕捉したエラーやフォールバック時の失敗(Functions が対象とする長時間実行のケース)を監視します。references/sentry.md を参照してください。
  • Mastra Studio(Mastra Cloud) — 関数上で Mastra エージェントを実行し、そのトレースを Studio プロジェクトに送信して可観測性を確保します。references/mastra-studio.md を参照してください。

Neon のドキュメント

Neon のドキュメントが信頼できる情報源であり、Functions は急速に進化しているため、常に公式ドキュメントで確認してください。任意のドキュメントページは、URL に .md を付けるか、Accept: text/markdown を指定してリクエストすることで、Markdown として取得できます。ドキュメントの索引(https://neon.com/docs/llms.txt)と変更履歴のお知らせから、該当するページを探してください。

さらに読む

コメント