neon-guard-mcp

neon-guard-mcp

A security proxy for Neon's MCP server that allows AI agents to create and manage isolated test branches with a default-deny whitelist, provenance gating, and short-lived credentials, while air-gapping production data and master keys.

Category
访问服务器

README

neon-guard-mcp 🛡️

Neon's official MCP server is amazing, but giving an LLM full admin access to your infrastructure is terrifying. neon-guard-mcp acts as a local security proxy, giving your AI agent the exact tools it needs to branch and test code, while completely air-gapping your production data and master keys.


Features

  • Default-deny whitelist — only a small set of branch-scoped tools are exposed; there is no tool for deleting projects, managing users, modifying production, or accessing connection strings for branches the proxy did not create.
  • Provenance gating — authorization to retrieve a connection string, delete, or reset a branch requires that the proxy itself created it (tracked in .neon-guard-state.json). A matching name prefix alone is not sufficient.
  • Schema-only branches by default — new branches are created with init_source: schema-only, so no production rows are ever copied into an agent-accessible branch.
  • Short-lived branches — configurable expiry (default 24 h); Neon deletes the branch automatically, invalidating the embedded credential.
  • Project allowlist — the proxy refuses to act on any project not explicitly listed in your config, even if the API key has broader access.
  • NEON_API_KEY never leaves the proxy process — the LLM receives only tool results, never the key itself.
  • Configurable tool disabling — remove individual tools from the schema if your threat model requires it.

Architecture

Claude Code (or any MCP client)
        |
        |  stdio (JSON-RPC)
        v
+---------------------------+
|   neon-guard-mcp proxy    |
|  - holds NEON_API_KEY     |
|  - enforces policy        |
|  - tracks provenance      |
|  - rewrites / filters     |
+---------------------------+
        |
        |  HTTPS (Neon REST API v2)
        v
   console.neon.tech

The LLM calls tools. The proxy validates every call against config and provenance state, then translates allowed calls into Neon API requests. The master key is only ever present in the proxy process environment.


Why / threat model

The official Neon MCP server

Neon's official server maps MCP tools 1:1 to Neon REST API operations. This is intentional and useful — for a human developer who understands what they are authorizing. For an autonomous AI agent, it means the LLM can:

  • Delete any branch, including main / production.
  • Read connection strings for any branch, including production.
  • Create branches that copy full production data.
  • Manage users, roles, and project settings.

One prompt-injection attack, one confused-deputy mistake, or one overly-helpful model decision is enough to destroy production data or exfiltrate it.

neon-guard-mcp

This proxy takes the opposite position: default-deny, least privilege, explicit allowlist. The agent gets only what it needs to create isolated test branches and run migrations/tests against them. Every other API surface simply does not exist as far as the LLM is concerned.


How the trust boundary works

Name prefix is NOT a security boundary.

It would be trivial for an attacker (or a confused model) to supply a branch name that happens to match the configured prefix. Authorization is instead gated on provenance: the proxy records every branch it creates in .neon-guard-state.json. A branch must appear in that record AND pass live server-side checks (not default, not protected, not the enforced parent branch) before the proxy will issue a connection string, delete it, or reset it.

The provenance store uses atomic file writes (write-then-os.replace) with mode 0o600 to prevent partial reads and limit filesystem exposure.


Installation

Recommended: uvx with a pinned version

uvx neon-guard-mcp==0.1.0

Pinning the version is important for security: it prevents a supply-chain update from silently changing proxy behavior.

pip

pip install neon-guard-mcp==0.1.0

From source (before the package is published)

Until neon-guard-mcp is published to PyPI, install it from a local checkout:

git clone https://github.com/your-org/neon-guard-mcp.git
cd neon-guard-mcp
uv sync                      # or: pip install -e .
uv run neon-guard-mcp        # runs the server over stdio

In your MCP client config, point command at uv with args: ["run", "neon-guard-mcp"] (or uvx --from /abs/path/to/neon-guard-mcp neon-guard-mcp) instead of the published-package form below.


Configuration

Copy the example config and edit it:

cp neon-guard.json.example neon-guard.json

Note: neon-guard.json and .neon-guard-state.json are in .gitignore and must never be committed. The example file (neon-guard.json.example) is safe to commit — it contains no secrets.

Configuration schema

Field Type Default Description
allowed_projects list[string] required Neon project IDs the proxy is permitted to act on. Any other project ID is rejected.
branch_naming_prefix string required Prefix applied to all branches the proxy creates (e.g. "ai-dev-"). Must match ^[a-z0-9][a-z0-9-]*$.
enforce_parent_branch string required Name of the branch all agent branches must be parented from (e.g. "main"). Prevents branching off an already-modified branch.
branch_data_mode "schema-only" | "full" "schema-only" Controls Neon's init_source. "schema-only" copies only DDL — no rows. Change to "full" only if you have reviewed the security implications.
branch_expiry_hours integer | null 24 Hours after creation before Neon auto-deletes the branch. null disables expiry. Expiry also invalidates the embedded credential in any connection string issued for the branch.
allow_branch_deletion bool false If false, the delete_test_branch and reset_test_branch tools are not registered at all.
max_ai_branches integer 10 Maximum number of live agent-created branches per project. Prevents runaway branch creation.
connection.default_database string required Database name used when generating connection strings.
connection.default_role string required Postgres role used when generating connection strings. Should be a dedicated least-privilege role (see Recommended Neon Setup).
connection.pooled bool true Whether to request a PgBouncer pooled connection URI.
disabled_tools list[string] [] Tool names to omit from the MCP schema entirely. Useful for further restricting the agent's surface.

Available tools

All tools are read-only or scoped to branches the proxy created. No tool can touch production or untracked branches.

Tool What it does Guard
list_safe_projects Lists Neon projects filtered to allowed_projects. Intersection with server response; unlisted projects are invisible.
list_safe_branches Lists branches for an allowed project, annotated with created_by_guard. Project must be in allowed_projects.
create_test_branch Creates a new branch from enforce_parent_branch using the naming prefix. Project allowed; parent verified server-side; max_ai_branches enforced; name uniqueness checked; branch_data_mode and expires_at applied.
get_branch_connection_string Returns the connection URI for a guard-created branch. Project allowed; branch must be in provenance store; branch must not be default, protected, or the enforced parent.
get_branch_status Returns branch state metadata. Project allowed; branch must exist in the project.
delete_test_branch Deletes a guard-created branch. Only registered if allow_branch_deletion: true; provenance + server-side safety checks; branch must have no children.
reset_test_branch Restores a guard-created branch to its parent's current state. Only registered if allow_branch_deletion: true; provenance + server-side safety checks.

Claude Code .mcp.json integration

Add this to your project's .mcp.json (or ~/.claude/mcp.json for global use):

{
  "mcpServers": {
    "neon-guard": {
      "command": "uvx",
      "args": ["neon-guard-mcp==0.1.0"],
      "env": {
        "NEON_API_KEY": "${NEON_API_KEY}",
        "NEON_GUARD_CONFIG": "/absolute/path/to/neon-guard.json"
      }
    }
  }
}

Important: The env block is passed only to the neon-guard-mcp subprocess. Claude Code does not forward it to the LLM context. NEON_API_KEY never appears in any tool call, tool result, or conversation message.

Note the "${NEON_API_KEY}" reference: Claude Code expands ${VAR} (and ${VAR:-default}) from your host environment at server-launch time, so the literal secret stays out of the config file. Use an absolute path for NEON_GUARD_CONFIG to avoid ambiguity about the working directory at startup.


Securing the master key

Claude Code passes NEON_API_KEY to the proxy as a process environment variable when it launches the server — that is the only way the key reaches the proxy, and it never reaches the model. But Claude Code does not encrypt this value anywhere. How you supply it is up to you; here are the options, most secure first.

⚠️ Myth: "claude mcp add --env stores the key encrypted in ~/.claude.json." False. It is stored as plaintext JSON. Treat anything you put in a config file or ~/.claude.json as readable by anyone (or anything) with access to that file.

1. Secrets manager (recommended). The literal key never lands in any file — it lives in your vault and is injected into the environment only at launch. With the 1Password CLI, reference the key with an op:// secret reference and let op run resolve it:

{
  "mcpServers": {
    "neon-guard": {
      "command": "op",
      "args": ["run", "--", "uvx", "neon-guard-mcp==0.1.0"],
      "env": {
        "NEON_API_KEY": "op://Private/Neon/api-key",
        "NEON_GUARD_CONFIG": "/absolute/path/to/neon-guard.json"
      }
    }
  }
}

The same pattern works with aws-vault exec, vault, doppler run, etc. — wrap the launch command, keep the secret in the manager.

2. Reference a host env var via ${...} expansion. Keep the real value in your shell environment (sourced from an uncommitted file or your secrets manager) and reference it — the config stays committable because it holds no secret:

export NEON_API_KEY="napi_…"   # from ~/.zshrc sourcing an uncommitted ~/.secrets, a vault, etc.
"env": { "NEON_API_KEY": "${NEON_API_KEY}" }

3. Local scope via the CLI (fine for a personal machine — but plaintext at rest). --scope local (the default) writes to ~/.claude.json, which is not committed to git, so it's safe from your repo — but the value is plaintext on disk:

claude mcp add --scope local --env NEON_API_KEY="napi_…" \
  neon-guard -- uvx neon-guard-mcp==0.1.0

Never hardcode the literal key in a project-scoped .mcp.json — that file is meant to be committed, so the secret would land in your git history in plaintext. Use ${NEON_API_KEY} expansion or a secrets-manager wrapper there instead. (neon-guard.json and .neon-guard-state.json are already git-ignored, but the master key is never written to either of them — it only ever comes from the environment.)


Security model and honest boundaries

What this protects

  • Master API key — never in LLM context; lives only in the proxy process environment.
  • Production branch — no tool can retrieve its connection string, delete it, or reset it (enforced server-side by checking the default and protected flags and matching against enforce_parent_branch).
  • Non-allowed projects — refused at the policy layer before any API call.
  • Untracked branches — connection strings and destructive operations are refused for any branch not recorded in .neon-guard-state.json.

What this does NOT fully protect

Connection strings are live credentials. When you call get_branch_connection_string, the returned URI contains a Postgres password. That URI enters the LLM context (it is the tool result). This is a fundamental constraint of how database connections work — the agent needs a credential to connect.

Mitigations applied by default:

  • Schema-only mode — even if the credential is exfiltrated, the branch contains no production rows.
  • Short expiry — the default 24 h expiry causes Neon to delete the branch, which invalidates the embedded password.
  • Dedicated non-owner role — the connection role (neon_guard_ai in the example) should have only the privileges needed for migrations and tests, not superuser or neondb_owner.

Operational posture: treat any branch that an agent has touched as potentially compromised. Deleting the branch (or allowing it to expire) rotates the credential. Do not reuse agent branches for sensitive data.

Log redaction is a best-effort backstop, not the primary control. The proxy never passes the master key or a connection URI to a log, an error message, or stdout, and it scrubs known secret shapes (Neon keys, Bearer tokens, postgresql:// credentials, password= keyword forms) from every log record as defense-in-depth. Redaction is pattern-based, so the real guarantee is the "never log a secret" discipline above — do not add log lines that interpolate credentials and rely on the scrubber to catch them.

Prompt injection risk

If your agent processes untrusted input (web pages, user-provided files, database content from a previous branch), an adversary could embed instructions that attempt to exfiltrate the connection string or manipulate the agent into calling reset_test_branch on the wrong branch. The schema-only default and provenance gating significantly reduce the blast radius, but they do not eliminate the attack surface entirely. Consider whether your agent's task requires access to get_branch_connection_string at all; if not, add it to disabled_tools.


Recommended Neon setup

Create a dedicated database role with limited privileges rather than using neondb_owner:

-- Run as neondb_owner on your Neon project
CREATE ROLE neon_guard_ai WITH LOGIN PASSWORD 'choose-a-strong-password';

-- Grant only what your migrations/tests need
GRANT CONNECT ON DATABASE neondb TO neon_guard_ai;
GRANT USAGE ON SCHEMA public TO neon_guard_ai;
GRANT CREATE ON SCHEMA public TO neon_guard_ai;  -- needed for migrations
-- Do NOT grant SUPERUSER, CREATEROLE, or CREATEDB

Set connection.default_role to "neon_guard_ai" in your config. This ensures that even if a connection string is leaked, the role cannot escalate privileges, create new roles, or access other databases.


License

MIT — see LICENSE.

推荐服务器

Baidu Map

Baidu Map

百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。

官方
精选
JavaScript
Playwright MCP Server

Playwright MCP Server

一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。

官方
精选
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。

官方
精选
本地
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。

官方
精选
本地
TypeScript
VeyraX

VeyraX

一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。

官方
精选
本地
graphlit-mcp-server

graphlit-mcp-server

模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。

官方
精选
TypeScript
Kagi MCP Server

Kagi MCP Server

一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。

官方
精选
Python
e2b-mcp-server

e2b-mcp-server

使用 MCP 通过 e2b 运行代码。

官方
精选
Neon MCP Server

Neon MCP Server

用于与 Neon 管理 API 和数据库交互的 MCP 服务器

官方
精选
Exa MCP Server

Exa MCP Server

模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。

官方
精选