diff --git a/content/docs/ai/neon-mcp-server.md b/content/docs/ai/neon-mcp-server.md
index ecb5394af4..10fd1d6533 100644
--- a/content/docs/ai/neon-mcp-server.md
+++ b/content/docs/ai/neon-mcp-server.md
@@ -58,166 +58,15 @@ The Neon MCP Server grants powerful database management capabilities through nat
## Other setup options
-
+### MCP Server Config Generator
-
+Use this generator to build valid Neon hosted MCP config snippets with supported auth modes, transport, and headers:
-Connect to Neon's managed MCP server using OAuth. No API key configuration needed.
-
-```bash
-npx add-mcp https://mcp.neon.tech/mcp
-```
-
-Or add this to your MCP config file:
-
-```json
-{
- "mcpServers": {
- "neon": {
- "type": "http",
- "url": "https://mcp.neon.tech/mcp"
- }
- }
-}
-```
-
-
-
-
-
-
-
-
-
-After saving, restart your MCP client. When the OAuth window opens in your browser, review the requested permissions and click **Authorize** to complete the connection.
-
-
-
-
-
-Connect using API key authentication. Useful for remote agents where OAuth isn't available.
-
-**Requires:** [Neon API key](/docs/manage/api-keys)
-
-```bash
-npx add-mcp https://mcp.neon.tech/mcp --header "Authorization: Bearer "
-```
-
-### MCP-only setup (OAuth)
-
-If you only want the MCP server and prefer OAuth, run:
-
-```bash
-npx add-mcp https://mcp.neon.tech/mcp
-```
-
-The command adds the config to your editor; restart your editor (or enable the MCP server) for it to take effect. When you use the MCP connection, an OAuth window will open in your browser; follow the prompts to authorize. For the recommended quick setup (API key + agent skills), use `npx neonctl@latest init` instead.
-
-
-Click the button below to install the Neon MCP server in Cursor. When prompted, click **Install** within Cursor.
-
-```json
-{
- "mcpServers": {
- "neon": {
- "type": "http",
- "url": "https://mcp.neon.tech/mcp",
- "headers": {
- "Authorization": "Bearer <$NEON_API_KEY>"
- }
- }
- }
-}
-```
-
-
-
-
-Use an organization API key to limit access to organization projects only.
-
-
-### Manual setup
-
-1. Go to your MCP Client's settings where you configure MCP Servers (this varies by client)
-2. Register a new MCP Server. When prompted for the configuration, name the server "Neon" and add the following configuration:
-
- ```json
- {
- "mcpServers": {
- "Neon": {
- "type": "http",
- "url": "https://mcp.neon.tech/mcp"
- }
- }
- }
- ```
-
- > MCP supports two remote server transports: the deprecated Server-Sent Events (SSE) and the newer, recommended Streamable HTTP. If your LLM client doesn't support Streamable HTTP yet, you can switch the endpoint from `https://mcp.neon.tech/mcp` to `https://mcp.neon.tech/sse` to use SSE instead.
-
-
-
-
-
-Run the MCP server locally on your machine.
-
-**Requires:** Node.js >= v18, [Neon API key](/docs/manage/api-keys)
-
-```bash
-npx add-mcp "npx -y @neondatabase/mcp-server-neon start " --name neon
-```
-
-Or add this to your MCP config file:
-
-```json
-{
- "mcpServers": {
- "neon": {
- "command": "npx",
- "args": ["-y", "@neondatabase/mcp-server-neon", "start", ""]
- }
- }
-}
-```
-
-
-
-Use `cmd` or `wsl` if you encounter issues:
-
-
-
-```json
-{
- "mcpServers": {
- "neon": {
- "command": "cmd",
- "args": ["/c", "npx", "-y", "@neondatabase/mcp-server-neon", "start", ""]
- }
- }
-}
-```
-
-```json
-{
- "mcpServers": {
- "neon": {
- "command": "wsl",
- "args": ["npx", "-y", "@neondatabase/mcp-server-neon", "start", ""]
- }
- }
-}
-```
-
-
-
-
-
-
-
-
+
-## Troubleshooting
+### Troubleshooting
If your client does not use JSON for configuration of MCP servers (such as older versions of Cursor), use this command when prompted:
diff --git a/content/docs/shared-content/mcp-tools.md b/content/docs/shared-content/mcp-tools.md
index affa2db782..2a407b1bce 100644
--- a/content/docs/shared-content/mcp-tools.md
+++ b/content/docs/shared-content/mcp-tools.md
@@ -37,7 +37,7 @@ The Neon MCP Server provides the following actions, which are exposed as "tools"
- `prepare_database_migration`: Initiates a database migration process. Critically, it creates a temporary branch to apply and test the migration safely before affecting the main branch.
- `complete_database_migration`: Finalizes and applies a prepared database migration to the main branch. This action merges changes from the temporary migration branch and cleans up temporary resources.
-**Query performance optimization:**
+**SQL querying and optimization:**
- `list_slow_queries`: Identifies performance bottlenecks by finding the slowest queries in a database. Requires the pg_stat_statements extension.
- `explain_sql_statement`: Provides detailed execution plans for SQL queries to help identify performance bottlenecks.
@@ -57,6 +57,8 @@ The Neon MCP Server provides the following actions, which are exposed as "tools"
- `search`: Searches across organizations, projects, and branches matching a query. Returns IDs, titles, and direct links to the Neon Console.
- `fetch`: Fetches detailed information about a specific organization, project, or branch using an ID (typically from the search tool).
+In project-scoped mode, `search` and `fetch` are not available.
+
**Documentation and resources:**
- `list_docs_resources`: Lists all available Neon documentation pages by fetching the docs index. Returns page URLs and titles that can be fetched individually using the `get_doc_resource` tool.
diff --git a/src/components/pages/doc/mcp-setup-configurator/index.js b/src/components/pages/doc/mcp-setup-configurator/index.js
new file mode 100644
index 0000000000..b05d5ecd46
--- /dev/null
+++ b/src/components/pages/doc/mcp-setup-configurator/index.js
@@ -0,0 +1,3 @@
+import McpSetupConfigurator from './mcp-setup-configurator';
+
+export default McpSetupConfigurator;
diff --git a/src/components/pages/doc/mcp-setup-configurator/mcp-setup-configurator.jsx b/src/components/pages/doc/mcp-setup-configurator/mcp-setup-configurator.jsx
new file mode 100644
index 0000000000..da0ea71e1f
--- /dev/null
+++ b/src/components/pages/doc/mcp-setup-configurator/mcp-setup-configurator.jsx
@@ -0,0 +1,596 @@
+'use client';
+
+import clsx from 'clsx';
+import parse from 'html-react-parser';
+import PropTypes from 'prop-types';
+import { useEffect, useMemo, useState } from 'react';
+
+import CodeBlockWrapper from 'components/shared/code-block-wrapper';
+import highlight from 'lib/shiki';
+
+const SERVER_BASE = 'https://mcp.neon.tech';
+const MCP_PATH = '/mcp';
+const PROD_LIST_TOOLS_URL = `${SERVER_BASE}/api/list-tools`;
+
+const AUTH_MODES = [
+ {
+ id: 'oauth',
+ label: 'OAuth',
+ description: 'Browser-based authorization. Best for local IDEs.',
+ },
+ {
+ id: 'apiKey',
+ label: 'API Key',
+ description: 'Static bearer token. Best for headless and remote agents.',
+ },
+];
+
+const SCOPE_CATEGORIES = [
+ { id: 'projects', label: 'Projects', description: 'Create and manage projects' },
+ { id: 'branches', label: 'Branches', description: 'Create, reset, delete branches' },
+ { id: 'schema', label: 'Schema', description: 'Tables, columns, indexes' },
+ { id: 'querying', label: 'Querying', description: 'Run SQL and explain plans' },
+ { id: 'neon_auth', label: 'Neon Auth', description: 'Users and sessions' },
+ { id: 'data_api', label: 'Data API', description: 'RESTful data endpoints' },
+ { id: 'docs', label: 'Docs', description: 'Search and fetch docs' },
+];
+const SCOPE_IDS = SCOPE_CATEGORIES.map((scope) => scope.id);
+const SCOPE_ID_SET = new Set(SCOPE_IDS);
+
+const CARD_CLASS =
+ 'rounded-xl border border-gray-new-90 bg-white/80 p-5 backdrop-blur-sm dark:border-gray-new-20 dark:bg-gray-new-10/50';
+const SECTION_LABEL_CLASS =
+ 'mb-3 flex items-center gap-2 text-[11px] font-semibold uppercase tracking-[0.08em] text-gray-new-40 dark:text-gray-new-60';
+const FIELD_LABEL_CLASS =
+ 'mb-1.5 block text-xs font-semibold uppercase tracking-wide text-gray-new-30 dark:text-gray-new-70';
+const HELPER_TEXT_CLASS = 'text-[13px] leading-relaxed text-gray-new-40 dark:text-gray-new-60';
+
+function getListToolsBaseUrl() {
+ if (process.env.NEXT_PUBLIC_MCP_API_URL) {
+ return process.env.NEXT_PUBLIC_MCP_API_URL;
+ }
+ return PROD_LIST_TOOLS_URL;
+}
+
+function buildQueryParams({ readOnly, projectId, selectedScopes }) {
+ const params = new URLSearchParams();
+ if (readOnly) {
+ params.set('readonly', 'true');
+ }
+ const trimmedProjectId = projectId.trim();
+ if (trimmedProjectId) {
+ params.set('projectId', trimmedProjectId);
+ }
+ const validScopes = selectedScopes.filter((id) => SCOPE_ID_SET.has(id));
+ if (validScopes.length > 0 && validScopes.length < SCOPE_IDS.length) {
+ params.set('category', validScopes.join(','));
+ }
+ return params;
+}
+
+function appendParams(baseUrl, params) {
+ const qs = params.toString();
+ return qs ? `${baseUrl}?${qs}` : baseUrl;
+}
+
+async function fetchTools({ url, timeoutMs = 12000 }) {
+ const controller = new AbortController();
+ const timeout = setTimeout(() => controller.abort(), timeoutMs);
+ const response = await fetch(url, { signal: controller.signal }).finally(() =>
+ clearTimeout(timeout)
+ );
+ if (!response.ok) {
+ throw new Error(`Failed to fetch tools preview: ${response.status}`);
+ }
+ return response.json();
+}
+
+const SegmentedControl = ({ name, options, value, onChange }) => (
+
+ {options.map((option) => {
+ const selected = value === option.id;
+ return (
+
+ );
+ })}
+
+);
+
+SegmentedControl.propTypes = {
+ name: PropTypes.string.isRequired,
+ options: PropTypes.arrayOf(
+ PropTypes.shape({
+ id: PropTypes.string.isRequired,
+ label: PropTypes.string.isRequired,
+ description: PropTypes.string,
+ })
+ ).isRequired,
+ value: PropTypes.string.isRequired,
+ onChange: PropTypes.func.isRequired,
+};
+
+const Toggle = ({ checked, onChange, label, description }) => (
+
+);
+
+Toggle.propTypes = {
+ checked: PropTypes.bool.isRequired,
+ onChange: PropTypes.func.isRequired,
+ label: PropTypes.string.isRequired,
+ description: PropTypes.string,
+};
+
+const HighlightedCode = ({ code, language }) => {
+ const [html, setHtml] = useState('');
+
+ useEffect(() => {
+ let cancelled = false;
+ highlight(code, language).then((result) => {
+ if (!cancelled) setHtml(result);
+ });
+ return () => {
+ cancelled = true;
+ };
+ }, [code, language]);
+
+ if (!html) {
+ return (
+
+ {code}
+
+ );
+ }
+
+ return <>{parse(html)}>;
+};
+
+HighlightedCode.propTypes = {
+ code: PropTypes.string.isRequired,
+ language: PropTypes.string.isRequired,
+};
+
+const McpSetupConfigurator = () => {
+ const [authMode, setAuthMode] = useState('oauth');
+ const [apiKey, setApiKey] = useState('');
+ const [readOnly, setReadOnly] = useState(false);
+ const [projectId, setProjectId] = useState('');
+ const [selectedScopes, setSelectedScopes] = useState(SCOPE_IDS);
+ const [toolsPreview, setToolsPreview] = useState(null);
+ const [allTools, setAllTools] = useState(null);
+ const [toolsPreviewLoading, setToolsPreviewLoading] = useState(false);
+ const [toolsPreviewError, setToolsPreviewError] = useState(false);
+ const [toolsPreviewErrorMessage, setToolsPreviewErrorMessage] = useState('');
+ const [lastSuccessfulToolsPreview, setLastSuccessfulToolsPreview] = useState(null);
+ const [lastSuccessfulAllTools, setLastSuccessfulAllTools] = useState(null);
+ const [toolsReloadNonce, setToolsReloadNonce] = useState(0);
+
+ const queryParams = useMemo(
+ () => buildQueryParams({ readOnly, projectId, selectedScopes }),
+ [readOnly, projectId, selectedScopes]
+ );
+ const queryString = queryParams.toString();
+
+ const baseServerUrl = `${SERVER_BASE}${MCP_PATH}`;
+ const generatedServerUrl = useMemo(
+ () => appendParams(baseServerUrl, queryParams),
+ [baseServerUrl, queryParams]
+ );
+
+ const generatedHeaders = useMemo(() => {
+ const headers = {};
+ if (authMode === 'apiKey') {
+ headers.Authorization = `Bearer ${apiKey.trim() || ''}`;
+ }
+ return headers;
+ }, [apiKey, authMode]);
+
+ const generatedConfig = useMemo(() => {
+ const neonEntry = {
+ type: 'http',
+ url: generatedServerUrl,
+ };
+ if (Object.keys(generatedHeaders).length > 0) {
+ neonEntry.headers = generatedHeaders;
+ }
+ const config = {
+ mcpServers: {
+ Neon: neonEntry,
+ },
+ };
+ return JSON.stringify(config, null, 2);
+ }, [generatedHeaders, generatedServerUrl]);
+
+ const addMcpCommand = useMemo(() => {
+ const urlArg = queryString ? `"${generatedServerUrl}"` : generatedServerUrl;
+ const commandParts = [`npx add-mcp@latest ${urlArg}`, '--name Neon'];
+ if (authMode === 'apiKey') {
+ commandParts.push(
+ `--header "Authorization: ${generatedHeaders.Authorization || 'Bearer '}"`
+ );
+ }
+ return commandParts.join(' \\\n ');
+ }, [authMode, generatedHeaders.Authorization, generatedServerUrl, queryString]);
+
+ const effectiveToolsPreview = useMemo(() => {
+ if (toolsPreviewError && lastSuccessfulToolsPreview) return lastSuccessfulToolsPreview;
+ return toolsPreview;
+ }, [lastSuccessfulToolsPreview, toolsPreview, toolsPreviewError]);
+
+ const effectiveAllTools = useMemo(() => {
+ if (toolsPreviewError && lastSuccessfulAllTools) return lastSuccessfulAllTools;
+ return allTools;
+ }, [allTools, lastSuccessfulAllTools, toolsPreviewError]);
+
+ const selectedTools = useMemo(() => {
+ if (!Array.isArray(effectiveToolsPreview?.tools)) return [];
+ return effectiveToolsPreview.tools;
+ }, [effectiveToolsPreview]);
+
+ const notIncludedTools = useMemo(() => {
+ if (!Array.isArray(effectiveAllTools?.tools) || !Array.isArray(effectiveToolsPreview?.tools)) {
+ return [];
+ }
+ const selectedNames = new Set(effectiveToolsPreview.tools.map((tool) => tool.name));
+ return effectiveAllTools.tools.filter((tool) => !selectedNames.has(tool.name));
+ }, [effectiveAllTools, effectiveToolsPreview]);
+
+ useEffect(() => {
+ let cancelled = false;
+ const listToolsBase = getListToolsBaseUrl();
+ const filteredUrl = appendParams(listToolsBase, queryParams);
+
+ setToolsPreviewLoading(true);
+ setToolsPreviewError(false);
+ setToolsPreviewErrorMessage('');
+
+ Promise.all([fetchTools({ url: filteredUrl }), fetchTools({ url: listToolsBase })])
+ .then(([filteredPayload, allPayload]) => {
+ if (!cancelled) {
+ setToolsPreview(filteredPayload);
+ setAllTools(allPayload);
+ setLastSuccessfulToolsPreview(filteredPayload);
+ setLastSuccessfulAllTools(allPayload);
+ setToolsPreviewLoading(false);
+ }
+ })
+ .catch((error) => {
+ if (!cancelled) {
+ setToolsPreviewError(true);
+ if (error instanceof Error) {
+ const message =
+ error.name === 'AbortError'
+ ? 'Request timed out while loading tools.'
+ : error.message;
+ setToolsPreviewErrorMessage(message);
+ } else {
+ setToolsPreviewErrorMessage('Unable to load tools.');
+ }
+ setToolsPreviewLoading(false);
+ }
+ });
+
+ return () => {
+ cancelled = true;
+ };
+ }, [queryParams, toolsReloadNonce]);
+
+ const toggleScope = (scopeId) => {
+ setSelectedScopes((prev) =>
+ prev.includes(scopeId) ? prev.filter((item) => item !== scopeId) : [...prev, scopeId]
+ );
+ };
+
+ const allScopesSelected = selectedScopes.length === SCOPE_IDS.length;
+
+ return (
+
+
+
+ MCP Server Config Generator
+
+
+ Generate a ready-to-use config for Neon's hosted MCP server.
+
+
+
+
+
+
Configuration
+
+
+ Authentication
+
+ {authMode === 'apiKey' && (
+
+ )}
+
+
+
+
Access
+
+
+
+
+
+ Scopes the agent to a single project. Hides project-wide management tools.
+
+
+
+
+
+
+
+ Tool categories
+
+
+
+ Pick which capability groups the agent can access. Unselected categories are excluded
+ via the{' '}
+
+ category
+ {' '}
+ query param.
+
+
+ {SCOPE_CATEGORIES.map((scope) => {
+ const checked = selectedScopes.includes(scope.id);
+ return (
+
+ );
+ })}
+
+
+
+
+
+
+
+
+
+
+ Tools preview
+
+
+ {selectedTools.length} enabled ยท {notIncludedTools.length} hidden
+
+
+
+ {toolsPreviewLoading && (
+
+
+ {Array.from({ length: 12 }).map((_, idx) => (
+
+ ))}
+
+
+ )}
+
+ {toolsPreviewError && (
+
+
+ Could not refresh selected tools.
+ {toolsPreviewErrorMessage ? ` ${toolsPreviewErrorMessage}` : ''}
+
+
+
+ {lastSuccessfulToolsPreview && (
+
+ Showing last successful results.
+
+ )}
+
+
+ )}
+
+ {!toolsPreviewLoading && !toolsPreviewError && (
+ <>
+
+ {selectedTools.map((tool) => (
+
+ {tool.title || tool.name}
+
+ ))}
+ {selectedTools.length === 0 && (
+
+ No tools match the current configuration.
+
+ )}
+
+ {notIncludedTools.length > 0 && (
+ <>
+
+
+
+ Hidden
+
+
+
+
+ {notIncludedTools.map((tool) => (
+
+ {tool.title || tool.name}
+
+ ))}
+
+ >
+ )}
+ >
+ )}
+
+
+
+ add-mcp command
+
+
+
+
+
+
+ MCP JSON config
+
+
+
+
+
+
+
+ );
+};
+
+export default McpSetupConfigurator;
diff --git a/src/components/shared/code-block-wrapper/code-block-wrapper.jsx b/src/components/shared/code-block-wrapper/code-block-wrapper.jsx
index 3048925093..9b32da88f9 100644
--- a/src/components/shared/code-block-wrapper/code-block-wrapper.jsx
+++ b/src/components/shared/code-block-wrapper/code-block-wrapper.jsx
@@ -46,7 +46,6 @@ const CodeBlockWrapper = ({
}) => {
const { isCopied, handleCopy } = useCopyToClipboard(3000);
- // copyCode bypasses extractTextFromNode, which can't traverse RSC lazy chunks in children
const code =
copyCode ?? extractTextFromNode(children).replace(/(\n)?__line_removed_in_code__(\n)?/g, '');
const isSingleLineCode = code.trimEnd().split('\n').length === 1;
diff --git a/src/components/shared/content/content.jsx b/src/components/shared/content/content.jsx
index 9098a6710d..50ec498c39 100644
--- a/src/components/shared/content/content.jsx
+++ b/src/components/shared/content/content.jsx
@@ -16,6 +16,7 @@ import DocsList from 'components/pages/doc/docs-list';
import IncludeBlock from 'components/pages/doc/include-block';
import InfoBlock from 'components/pages/doc/info-block';
import LinkPreview from 'components/pages/doc/link-preview';
+import McpSetupConfigurator from 'components/pages/doc/mcp-setup-configurator';
import PromptCards from 'components/pages/doc/prompt-cards';
import Steps from 'components/pages/doc/steps';
import StickyTable from 'components/pages/doc/sticky-table';
@@ -201,6 +202,7 @@ const getComponents = (withoutAnchorHeading, isReleaseNote, isPostgres, isTempla
ExternalCode: (props) => ,
MegaLink,
CopyPrompt,
+ McpSetupConfigurator,
SqlToRestConverter,
...sharedComponents,
});