{"slug":"mcp-server-proxying","title":"MCP server proxying","tags":["tailscale"],"agent_summary":"Last validated: Mar 30, 2026","trigger_phrases":[],"runnable":false,"markdown":"\r\n# MCP server proxying\r\n\r\nLast validated: Mar 30, 2026\r\n\r\nAperture MCP server proxyingis currently [in alpha](https://tailscale.com/docs/reference/tailscale-release-stages#alpha).\r\n\r\nAperture can aggregate tools and resources from multiple remote [Model Context Protocol](https://modelcontextprotocol.io/specification) (MCP) servers and expose them to AI agents through a single `/v1/mcp` endpoint. This lets you centralize MCP tool management behind your Aperture proxy with the same identity-based access control used for LLM providers.\r\n\r\nAperture acts as an MCP server when communicating with AI agent clients and as an MCP client when communicating with remote MCP servers. Clients connect to Aperture's `/v1/mcp` endpoint and access an aggregated view of all tools and resources from every configured remote server.\r\n\r\n## [Use cases](https://tailscale.com/docs/aperture/mcp-server\\#use-cases)\r\n\r\nMCP server proxying addresses the following use cases:\r\n\r\n- **Centralized tool access**: Aggregate tools from multiple MCP servers behind a single endpoint. AI agents connect to one URL instead of managing connections to each server individually.\r\n- **Identity-based access control**: Control which users can access which MCP tools and resources using the same Tailscale identity and grants system used for LLM providers.\r\n- **Dynamic tool discovery**: Register and unregister MCP servers at runtime. When a server comes online, its tools become available automatically. When it goes offline, Aperture removes its tools.\r\n- **Protocol compatibility**: Connect MCP servers that use different protocol versions. Aperture auto-detects whether each server supports Streamable HTTP or legacy SSE and handles the translation.\r\n\r\n## [Key MCP concepts](https://tailscale.com/docs/aperture/mcp-server\\#key-mcp-concepts)\r\n\r\nThe [Model Context Protocol](https://modelcontextprotocol.io/specification) defines a standard way for AI agents to discover and use external capabilities. The following concepts are relevant to configuring MCP in Aperture:\r\n\r\n- **Tools**: Functions that LLMs can discover and call, such as searching a database or running a command.\r\n- **Resources**: Contextual data identified by URI that LLMs can read, such as file contents or API responses.\r\n- **Streamable HTTP**: The current MCP transport protocol that uses a single HTTP endpoint for bidirectional communication.\r\n- **Legacy SSE**: The deprecated MCP transport that uses Server-Sent Events. Aperture supports both transports for backward compatibility.\r\n\r\n## [Prerequisites](https://tailscale.com/docs/aperture/mcp-server\\#prerequisites)\r\n\r\nBefore you can use MCP server proxying, you need the following:\r\n\r\n- Aperture enabled on your [tailnet](https://tailscale.com/docs/concepts/tailnet). Refer to [get started with Aperture](https://tailscale.com/docs/aperture#get-started) if you have not set this up.\r\n- At least one remote MCP server accessible from the Aperture host (in the tailnet or on localhost).\r\n- The URL of each MCP server's endpoint (for example, `http://localhost:8185/v1/mcp` or `http://mcp-server.example.ts.net:8080/v1/mcp`).\r\n\r\n## [Get started](https://tailscale.com/docs/aperture/mcp-server\\#get-started)\r\n\r\nTo configure MCP server proxying in Aperture, add remote servers, grant users access to MCP tools, and connect an MCP client.\r\n\r\n### [Step 1: Configure MCP servers](https://tailscale.com/docs/aperture/mcp-server\\#step-1-configure-mcp-servers)\r\n\r\nOpen the **Settings** page of the Aperture dashboard and add an `mcp` section to your configuration with one or more remote servers.\r\n\r\n```json\r\n{\r\n  \"mcp\": {\r\n    \"servers\": {\r\n      \"local\": {\r\n        \"url\": \"http://localhost:8185/v1/mcp\"\r\n      },\r\n      \"remote\": {\r\n        \"url\": \"http://mcp-server.example.ts.net:8080/v1/mcp\"\r\n      }\r\n    }\r\n  }\r\n}\r\n```\r\n\r\nEach key in the `servers` map is a server ID that Aperture uses as a name prefix. For the configuration above:\r\n\r\n- Tools from the `local` server are prefixed with `local_` (for example, a tool named `search` becomes `local_search`).\r\n- Tools from the `remote` server are prefixed with `remote_` (for example, `get_user` becomes `remote_get_user`).\r\n- Resources use a hyphen instead of an underscore (for example, `local-files://readme.md`).\r\n\r\nName prefixing prevents collisions when multiple servers expose tools with the same name. Clients receive the prefixed names and Aperture automatically strips the prefix when forwarding calls to the remote server.\r\n\r\nThe Aperture host uses its tailnet HTTP client for connections to remote MCP servers, so you can use tailnet hostnames (for example, `http://mcp-server.example.ts.net:8080/v1/mcp`) without additional network configuration.\r\n\r\n### [Step 2: Grant access to MCP tools](https://tailscale.com/docs/aperture/mcp-server\\#step-2-grant-access-to-mcp-tools)\r\n\r\nAperture is deny-by-default. Without MCP grants, users cannot access any MCP capabilities. Add MCP grants in the `grants` section of your configuration.\r\n\r\nMCP grants use the same `\"server/item\"` pattern as model grants, where the first segment is the server ID and the second is the item name. The following example grants all users access to all tools from the `local` server:\r\n\r\n```json\r\n{\r\n  \"grants\": [\\\r\n    {\\\r\n      \"src\": [\"*\"],\\\r\n      \"app\": {\\\r\n        \"tailscale.com/cap/aperture\": [\\\r\n          {\\\r\n            \"mcp_tools\": \"local/*\"\\\r\n          }\\\r\n        ]\\\r\n      }\\\r\n    }\\\r\n  ]\r\n}\r\n```\r\n\r\nThe following table describes the available MCP grant fields:\r\n\r\n| Field | Description | Example |\r\n| --- | --- | --- |\r\n| `mcp_tools` | Grant access to tools matching the pattern | `\"local/*\"` (all tools from `local` server) |\r\n| `mcp_resources` | Grant access to resources matching the pattern | `\"**\"` (all resources from all servers) |\r\n\r\nYou can use `*` to match any characters within a segment and `**` to match across segments. For example:\r\n\r\n- `\"local/search\"` matches the `search` tool from the `local` server.\r\n- `\"local/*\"` matches all tools from the `local` server.\r\n- `\"*/search\"` matches the `search` tool from any server.\r\n- `\"**\"` matches all items from all servers.\r\n\r\nAperture checks grants when clients list available tools and resources. Users only access the items they have permission for.\r\n\r\nRefer to the [grants configuration reference](https://tailscale.com/docs/aperture/configuration#grants) for the full grants syntax.\r\n\r\n### [Step 3: Connect an MCP client](https://tailscale.com/docs/aperture/mcp-server\\#step-3-connect-an-mcp-client)\r\n\r\nConfigure your MCP client (the AI agent host) to use the Aperture URL as the MCP server endpoint. For example, in an MCP client configuration file:\r\n\r\n```json\r\n{\r\n  \"mcpServers\": {\r\n    \"aperture\": {\r\n      \"url\": \"http://ai/v1/mcp\"\r\n    }\r\n  }\r\n}\r\n```\r\n\r\nReplace `http://ai` with your Aperture hostname if you use a different [MagicDNS](https://tailscale.com/docs/features/magicdns) name.\r\n\r\nAperture automatically detects whether the client uses Streamable HTTP (the current MCP protocol) or legacy SSE and responds with the appropriate transport.\r\n\r\n### [Step 4: Verify the connection](https://tailscale.com/docs/aperture/mcp-server\\#step-4-verify-the-connection)\r\n\r\nConnect your MCP client to `http://ai/v1/mcp` and list available tools. The tool list should include prefixed names from all configured servers (for example, `local_search`, `remote_get_user`).\r\n\r\n## [Built-in tools](https://tailscale.com/docs/aperture/mcp-server\\#built-in-tools)\r\n\r\nAperture registers a built-in `internal_current_time` tool that returns the current date and time. This tool appears in `tools/list` responses alongside tools from configured remote servers. To grant access to it, use a grant pattern that matches the `internal` server, such as `\"internal/*\"`.\r\n\r\n## [Common scenarios](https://tailscale.com/docs/aperture/mcp-server\\#common-scenarios)\r\n\r\nThe following sections describe common tasks related to MCP server proxying.\r\n\r\n### [Enable dynamic registration](https://tailscale.com/docs/aperture/mcp-server\\#enable-dynamic-registration)\r\n\r\nDynamic registration lets MCP servers register themselves with Aperture at runtime instead of being configured statically. Set `accept_registrations` to `true` in the `mcp` section.\r\n\r\n```json\r\n{\r\n  \"mcp\": {\r\n    \"accept_registrations\": true,\r\n    \"servers\": {}\r\n  }\r\n}\r\n```\r\n\r\nWhen dynamic registration is enabled, remote servers register by sending a POST request to `/v1/mcp/register` with a JSON body containing their URL:\r\n\r\n```shell\r\ncurl -X POST http://ai/v1/mcp/register \\\r\n  -H \"Content-Type: application/json\" \\\r\n  -d '{\"url\": \"http://my-mcp-server:8080/v1/mcp\"}'\r\n```\r\n\r\nAperture performs an initial capability fetch from the registering server, then responds with HTTP 200 and begins polling the server for capability changes. Each dynamically registered server receives a sequential ID (`auto1`, `auto2`, and so on) and its tools are prefixed accordingly (for example, `auto1_search`).\r\n\r\nThe registration endpoint requires Tailscale authentication, the same as all other Aperture endpoints. The registering server must be accessible through the tailnet.\r\n\r\nThe registering server must keep the HTTP connection to `/v1/mcp/register` open. Aperture sends keepalive messages on this connection every second. When the server closes the connection, Aperture automatically unregisters all of its tools and resources.\r\n\r\nYou can combine static servers and dynamic registration in the same configuration. Static servers are always available, while dynamically registered servers come and go as they connect and disconnect.\r\n\r\n## [MCP configuration reference](https://tailscale.com/docs/aperture/mcp-server\\#mcp-configuration-reference)\r\n\r\nRefer to the [Aperture configuration reference](https://tailscale.com/docs/aperture/configuration#mcp) for the full `mcp` configuration syntax. The following sections describe how Aperture handles transport detection and server availability.\r\n\r\n### [Transport auto-detection](https://tailscale.com/docs/aperture/mcp-server\\#transport-auto-detection)\r\n\r\nAperture automatically detects whether each remote MCP server supports Streamable HTTP (the current protocol) or legacy SSE. When connecting to a remote server, Aperture tries Streamable HTTP first and falls back to SSE if the server does not support it. Remote servers can be upgraded to a later protocol version without restarting Aperture.\r\n\r\nThe same auto-detection applies to clients connecting to Aperture's `/v1/mcp` endpoint. Aperture serves both Streamable HTTP and legacy SSE clients.\r\n\r\n### [Capability polling](https://tailscale.com/docs/aperture/mcp-server\\#capability-polling)\r\n\r\nAperture polls each configured remote MCP server every 5 seconds to detect capability changes. When a remote server adds or removes tools or resources, Aperture automatically updates the registrations, making the changes visible to connected clients.\r\n\r\nIf a remote server becomes unavailable, Aperture unregisters all of its tools and resources until the server recovers. Polling continues in the background, and Aperture re-registers capabilities when the server comes back online.\r\n\r\n## [Limitations](https://tailscale.com/docs/aperture/mcp-server\\#limitations)\r\n\r\nMCP server proxying has the following limitations:\r\n\r\n- **Experimental feature**: The MCP configuration syntax and grants format might change in future releases.\r\n- **Polling-based discovery**: Aperture detects remote server capability changes through polling (every 5 seconds), not push notifications. There is a brief delay between a remote server adding a tool and that tool becoming available to clients.\r\n- **No per-tool authorization on calls**: Grants control which tools appear in the tool list. There is no separate authorization check at tool call time. A user who knows a tool's prefixed name can call it even without a matching grant, because grant enforcement applies to listing, not calling.\r\n\r\n## [Troubleshooting](https://tailscale.com/docs/aperture/mcp-server\\#troubleshooting)\r\n\r\nUse the following sections to diagnose and resolve common issues with MCP server configuration.\r\n\r\n### [MCP tools do not appear](https://tailscale.com/docs/aperture/mcp-server\\#mcp-tools-do-not-appear)\r\n\r\nIf tools from a configured MCP server are not visible:\r\n\r\n1. Verify the URL in your configuration is correct and the MCP server is running.\r\n2. Check that the MCP server is reachable from the Aperture host. Test with `curl -v <your-mcp-server-url>` from the Aperture host.\r\n3. Verify your grants include `mcp_tools` patterns that match the server and tool names. Without grants, users cannot access any MCP tools.\r\n\r\n### [Connection refused or host not found](https://tailscale.com/docs/aperture/mcp-server\\#connection-refused-or-host-not-found)\r\n\r\nThese errors indicate the MCP server URL is unreachable.\r\n\r\n- **Connection refused**: The server is not running or is not listening on the configured port.\r\n- **Host not found**: The hostname cannot be resolved. For tailnet hostnames, verify the MCP server device is connected to the tailnet and check with `tailscale status`.\r\n\r\n### [Tools appear and then disappear](https://tailscale.com/docs/aperture/mcp-server\\#tools-appear-and-then-disappear)\r\n\r\nIf tools are briefly visible and then become unavailable, the remote MCP server is likely crashing or restarting. Aperture automatically unregisters tools when a remote server becomes unreachable and re-registers them when the server recovers.\r\n\r\n### [Dynamic registration fails](https://tailscale.com/docs/aperture/mcp-server\\#dynamic-registration-fails)\r\n\r\nIf remote servers cannot register dynamically:\r\n\r\n1. Verify `accept_registrations` is set to `true` in your `mcp` configuration.\r\n2. Ensure the remote server sends a valid POST request to `/v1/mcp/register` with a JSON body containing `{\"url\": \"<MCP_SERVER_URL>\"}`.\r\n3. The remote server must keep the HTTP connection open after registration. If the connection closes, Aperture unregisters the server's tools immediately.\r\n\r\n### [Tool calls time out](https://tailscale.com/docs/aperture/mcp-server\\#tool-calls-time-out)\r\n\r\nIf tool calls fail with timeout errors, the remote MCP server is taking too long to respond. Aperture retries tool calls once on connection errors. Check the remote server's performance and ensure it can respond within a reasonable time.\r\n\r\n![Project Logo](https://cdn.brandfetch.io/tailscale.com/fallback/lettermark/theme/dark/h/256/w/256/icon?c=1bfwsmEH20zzEfSNTed)\r\n\r\nAsk AI\r\n\r\nreCAPTCHA\r\n\r\nRecaptcha requires verification.\r\n\r\nprotected by **reCAPTCHA**\r\n","html":"<h1>MCP server proxying</h1>\n<p>Last validated: Mar 30, 2026</p>\n<p>Aperture MCP server proxyingis currently <a href=\"https://tailscale.com/docs/reference/tailscale-release-stages#alpha\">in alpha</a>.</p>\n<p>Aperture can aggregate tools and resources from multiple remote <a href=\"https://modelcontextprotocol.io/specification\">Model Context Protocol</a> (MCP) servers and expose them to AI agents through a single <code>/v1/mcp</code> endpoint. This lets you centralize MCP tool management behind your Aperture proxy with the same identity-based access control used for LLM providers.</p>\n<p>Aperture acts as an MCP server when communicating with AI agent clients and as an MCP client when communicating with remote MCP servers. Clients connect to Aperture's <code>/v1/mcp</code> endpoint and access an aggregated view of all tools and resources from every configured remote server.</p>\n<h2><a href=\"https://tailscale.com/docs/aperture/mcp-server#use-cases\">Use cases</a></h2>\n<p>MCP server proxying addresses the following use cases:</p>\n<ul>\n<li><strong>Centralized tool access</strong>: Aggregate tools from multiple MCP servers behind a single endpoint. AI agents connect to one URL instead of managing connections to each server individually.</li>\n<li><strong>Identity-based access control</strong>: Control which users can access which MCP tools and resources using the same Tailscale identity and grants system used for LLM providers.</li>\n<li><strong>Dynamic tool discovery</strong>: Register and unregister MCP servers at runtime. When a server comes online, its tools become available automatically. When it goes offline, Aperture removes its tools.</li>\n<li><strong>Protocol compatibility</strong>: Connect MCP servers that use different protocol versions. Aperture auto-detects whether each server supports Streamable HTTP or legacy SSE and handles the translation.</li>\n</ul>\n<h2><a href=\"https://tailscale.com/docs/aperture/mcp-server#key-mcp-concepts\">Key MCP concepts</a></h2>\n<p>The <a href=\"https://modelcontextprotocol.io/specification\">Model Context Protocol</a> defines a standard way for AI agents to discover and use external capabilities. The following concepts are relevant to configuring MCP in Aperture:</p>\n<ul>\n<li><strong>Tools</strong>: Functions that LLMs can discover and call, such as searching a database or running a command.</li>\n<li><strong>Resources</strong>: Contextual data identified by URI that LLMs can read, such as file contents or API responses.</li>\n<li><strong>Streamable HTTP</strong>: The current MCP transport protocol that uses a single HTTP endpoint for bidirectional communication.</li>\n<li><strong>Legacy SSE</strong>: The deprecated MCP transport that uses Server-Sent Events. Aperture supports both transports for backward compatibility.</li>\n</ul>\n<h2><a href=\"https://tailscale.com/docs/aperture/mcp-server#prerequisites\">Prerequisites</a></h2>\n<p>Before you can use MCP server proxying, you need the following:</p>\n<ul>\n<li>Aperture enabled on your <a href=\"https://tailscale.com/docs/concepts/tailnet\">tailnet</a>. Refer to <a href=\"https://tailscale.com/docs/aperture#get-started\">get started with Aperture</a> if you have not set this up.</li>\n<li>At least one remote MCP server accessible from the Aperture host (in the tailnet or on localhost).</li>\n<li>The URL of each MCP server's endpoint (for example, <code>http://localhost:8185/v1/mcp</code> or <code>http://mcp-server.example.ts.net:8080/v1/mcp</code>).</li>\n</ul>\n<h2><a href=\"https://tailscale.com/docs/aperture/mcp-server#get-started\">Get started</a></h2>\n<p>To configure MCP server proxying in Aperture, add remote servers, grant users access to MCP tools, and connect an MCP client.</p>\n<h3><a href=\"https://tailscale.com/docs/aperture/mcp-server#step-1-configure-mcp-servers\">Step 1: Configure MCP servers</a></h3>\n<p>Open the <strong>Settings</strong> page of the Aperture dashboard and add an <code>mcp</code> section to your configuration with one or more remote servers.</p>\n<pre><code class=\"language-json\">{\r\n  \"mcp\": {\r\n    \"servers\": {\r\n      \"local\": {\r\n        \"url\": \"http://localhost:8185/v1/mcp\"\r\n      },\r\n      \"remote\": {\r\n        \"url\": \"http://mcp-server.example.ts.net:8080/v1/mcp\"\r\n      }\r\n    }\r\n  }\r\n}\n</code></pre>\n<p>Each key in the <code>servers</code> map is a server ID that Aperture uses as a name prefix. For the configuration above:</p>\n<ul>\n<li>Tools from the <code>local</code> server are prefixed with <code>local_</code> (for example, a tool named <code>search</code> becomes <code>local_search</code>).</li>\n<li>Tools from the <code>remote</code> server are prefixed with <code>remote_</code> (for example, <code>get_user</code> becomes <code>remote_get_user</code>).</li>\n<li>Resources use a hyphen instead of an underscore (for example, <code>local-files://readme.md</code>).</li>\n</ul>\n<p>Name prefixing prevents collisions when multiple servers expose tools with the same name. Clients receive the prefixed names and Aperture automatically strips the prefix when forwarding calls to the remote server.</p>\n<p>The Aperture host uses its tailnet HTTP client for connections to remote MCP servers, so you can use tailnet hostnames (for example, <code>http://mcp-server.example.ts.net:8080/v1/mcp</code>) without additional network configuration.</p>\n<h3><a href=\"https://tailscale.com/docs/aperture/mcp-server#step-2-grant-access-to-mcp-tools\">Step 2: Grant access to MCP tools</a></h3>\n<p>Aperture is deny-by-default. Without MCP grants, users cannot access any MCP capabilities. Add MCP grants in the <code>grants</code> section of your configuration.</p>\n<p>MCP grants use the same <code>\"server/item\"</code> pattern as model grants, where the first segment is the server ID and the second is the item name. The following example grants all users access to all tools from the <code>local</code> server:</p>\n<pre><code class=\"language-json\">{\r\n  \"grants\": [\\\r\n    {\\\r\n      \"src\": [\"*\"],\\\r\n      \"app\": {\\\r\n        \"tailscale.com/cap/aperture\": [\\\r\n          {\\\r\n            \"mcp_tools\": \"local/*\"\\\r\n          }\\\r\n        ]\\\r\n      }\\\r\n    }\\\r\n  ]\r\n}\n</code></pre>\n<p>The following table describes the available MCP grant fields:</p>\n<p>| Field | Description | Example |\r\n| --- | --- | --- |\r\n| <code>mcp_tools</code> | Grant access to tools matching the pattern | <code>\"local/*\"</code> (all tools from <code>local</code> server) |\r\n| <code>mcp_resources</code> | Grant access to resources matching the pattern | <code>\"**\"</code> (all resources from all servers) |</p>\n<p>You can use <code>*</code> to match any characters within a segment and <code>**</code> to match across segments. For example:</p>\n<ul>\n<li><code>\"local/search\"</code> matches the <code>search</code> tool from the <code>local</code> server.</li>\n<li><code>\"local/*\"</code> matches all tools from the <code>local</code> server.</li>\n<li><code>\"*/search\"</code> matches the <code>search</code> tool from any server.</li>\n<li><code>\"**\"</code> matches all items from all servers.</li>\n</ul>\n<p>Aperture checks grants when clients list available tools and resources. Users only access the items they have permission for.</p>\n<p>Refer to the <a href=\"https://tailscale.com/docs/aperture/configuration#grants\">grants configuration reference</a> for the full grants syntax.</p>\n<h3><a href=\"https://tailscale.com/docs/aperture/mcp-server#step-3-connect-an-mcp-client\">Step 3: Connect an MCP client</a></h3>\n<p>Configure your MCP client (the AI agent host) to use the Aperture URL as the MCP server endpoint. For example, in an MCP client configuration file:</p>\n<pre><code class=\"language-json\">{\r\n  \"mcpServers\": {\r\n    \"aperture\": {\r\n      \"url\": \"http://ai/v1/mcp\"\r\n    }\r\n  }\r\n}\n</code></pre>\n<p>Replace <code>http://ai</code> with your Aperture hostname if you use a different <a href=\"https://tailscale.com/docs/features/magicdns\">MagicDNS</a> name.</p>\n<p>Aperture automatically detects whether the client uses Streamable HTTP (the current MCP protocol) or legacy SSE and responds with the appropriate transport.</p>\n<h3><a href=\"https://tailscale.com/docs/aperture/mcp-server#step-4-verify-the-connection\">Step 4: Verify the connection</a></h3>\n<p>Connect your MCP client to <code>http://ai/v1/mcp</code> and list available tools. The tool list should include prefixed names from all configured servers (for example, <code>local_search</code>, <code>remote_get_user</code>).</p>\n<h2><a href=\"https://tailscale.com/docs/aperture/mcp-server#built-in-tools\">Built-in tools</a></h2>\n<p>Aperture registers a built-in <code>internal_current_time</code> tool that returns the current date and time. This tool appears in <code>tools/list</code> responses alongside tools from configured remote servers. To grant access to it, use a grant pattern that matches the <code>internal</code> server, such as <code>\"internal/*\"</code>.</p>\n<h2><a href=\"https://tailscale.com/docs/aperture/mcp-server#common-scenarios\">Common scenarios</a></h2>\n<p>The following sections describe common tasks related to MCP server proxying.</p>\n<h3><a href=\"https://tailscale.com/docs/aperture/mcp-server#enable-dynamic-registration\">Enable dynamic registration</a></h3>\n<p>Dynamic registration lets MCP servers register themselves with Aperture at runtime instead of being configured statically. Set <code>accept_registrations</code> to <code>true</code> in the <code>mcp</code> section.</p>\n<pre><code class=\"language-json\">{\r\n  \"mcp\": {\r\n    \"accept_registrations\": true,\r\n    \"servers\": {}\r\n  }\r\n}\n</code></pre>\n<p>When dynamic registration is enabled, remote servers register by sending a POST request to <code>/v1/mcp/register</code> with a JSON body containing their URL:</p>\n<pre><code class=\"language-shell\">curl -X POST http://ai/v1/mcp/register \\\r\n  -H \"Content-Type: application/json\" \\\r\n  -d '{\"url\": \"http://my-mcp-server:8080/v1/mcp\"}'\n</code></pre>\n<p>Aperture performs an initial capability fetch from the registering server, then responds with HTTP 200 and begins polling the server for capability changes. Each dynamically registered server receives a sequential ID (<code>auto1</code>, <code>auto2</code>, and so on) and its tools are prefixed accordingly (for example, <code>auto1_search</code>).</p>\n<p>The registration endpoint requires Tailscale authentication, the same as all other Aperture endpoints. The registering server must be accessible through the tailnet.</p>\n<p>The registering server must keep the HTTP connection to <code>/v1/mcp/register</code> open. Aperture sends keepalive messages on this connection every second. When the server closes the connection, Aperture automatically unregisters all of its tools and resources.</p>\n<p>You can combine static servers and dynamic registration in the same configuration. Static servers are always available, while dynamically registered servers come and go as they connect and disconnect.</p>\n<h2><a href=\"https://tailscale.com/docs/aperture/mcp-server#mcp-configuration-reference\">MCP configuration reference</a></h2>\n<p>Refer to the <a href=\"https://tailscale.com/docs/aperture/configuration#mcp\">Aperture configuration reference</a> for the full <code>mcp</code> configuration syntax. The following sections describe how Aperture handles transport detection and server availability.</p>\n<h3><a href=\"https://tailscale.com/docs/aperture/mcp-server#transport-auto-detection\">Transport auto-detection</a></h3>\n<p>Aperture automatically detects whether each remote MCP server supports Streamable HTTP (the current protocol) or legacy SSE. When connecting to a remote server, Aperture tries Streamable HTTP first and falls back to SSE if the server does not support it. Remote servers can be upgraded to a later protocol version without restarting Aperture.</p>\n<p>The same auto-detection applies to clients connecting to Aperture's <code>/v1/mcp</code> endpoint. Aperture serves both Streamable HTTP and legacy SSE clients.</p>\n<h3><a href=\"https://tailscale.com/docs/aperture/mcp-server#capability-polling\">Capability polling</a></h3>\n<p>Aperture polls each configured remote MCP server every 5 seconds to detect capability changes. When a remote server adds or removes tools or resources, Aperture automatically updates the registrations, making the changes visible to connected clients.</p>\n<p>If a remote server becomes unavailable, Aperture unregisters all of its tools and resources until the server recovers. Polling continues in the background, and Aperture re-registers capabilities when the server comes back online.</p>\n<h2><a href=\"https://tailscale.com/docs/aperture/mcp-server#limitations\">Limitations</a></h2>\n<p>MCP server proxying has the following limitations:</p>\n<ul>\n<li><strong>Experimental feature</strong>: The MCP configuration syntax and grants format might change in future releases.</li>\n<li><strong>Polling-based discovery</strong>: Aperture detects remote server capability changes through polling (every 5 seconds), not push notifications. There is a brief delay between a remote server adding a tool and that tool becoming available to clients.</li>\n<li><strong>No per-tool authorization on calls</strong>: Grants control which tools appear in the tool list. There is no separate authorization check at tool call time. A user who knows a tool's prefixed name can call it even without a matching grant, because grant enforcement applies to listing, not calling.</li>\n</ul>\n<h2><a href=\"https://tailscale.com/docs/aperture/mcp-server#troubleshooting\">Troubleshooting</a></h2>\n<p>Use the following sections to diagnose and resolve common issues with MCP server configuration.</p>\n<h3><a href=\"https://tailscale.com/docs/aperture/mcp-server#mcp-tools-do-not-appear\">MCP tools do not appear</a></h3>\n<p>If tools from a configured MCP server are not visible:</p>\n<ol>\n<li>Verify the URL in your configuration is correct and the MCP server is running.</li>\n<li>Check that the MCP server is reachable from the Aperture host. Test with <code>curl -v &#x3C;your-mcp-server-url></code> from the Aperture host.</li>\n<li>Verify your grants include <code>mcp_tools</code> patterns that match the server and tool names. Without grants, users cannot access any MCP tools.</li>\n</ol>\n<h3><a href=\"https://tailscale.com/docs/aperture/mcp-server#connection-refused-or-host-not-found\">Connection refused or host not found</a></h3>\n<p>These errors indicate the MCP server URL is unreachable.</p>\n<ul>\n<li><strong>Connection refused</strong>: The server is not running or is not listening on the configured port.</li>\n<li><strong>Host not found</strong>: The hostname cannot be resolved. For tailnet hostnames, verify the MCP server device is connected to the tailnet and check with <code>tailscale status</code>.</li>\n</ul>\n<h3><a href=\"https://tailscale.com/docs/aperture/mcp-server#tools-appear-and-then-disappear\">Tools appear and then disappear</a></h3>\n<p>If tools are briefly visible and then become unavailable, the remote MCP server is likely crashing or restarting. Aperture automatically unregisters tools when a remote server becomes unreachable and re-registers them when the server recovers.</p>\n<h3><a href=\"https://tailscale.com/docs/aperture/mcp-server#dynamic-registration-fails\">Dynamic registration fails</a></h3>\n<p>If remote servers cannot register dynamically:</p>\n<ol>\n<li>Verify <code>accept_registrations</code> is set to <code>true</code> in your <code>mcp</code> configuration.</li>\n<li>Ensure the remote server sends a valid POST request to <code>/v1/mcp/register</code> with a JSON body containing <code>{\"url\": \"&#x3C;MCP_SERVER_URL>\"}</code>.</li>\n<li>The remote server must keep the HTTP connection open after registration. If the connection closes, Aperture unregisters the server's tools immediately.</li>\n</ol>\n<h3><a href=\"https://tailscale.com/docs/aperture/mcp-server#tool-calls-time-out\">Tool calls time out</a></h3>\n<p>If tool calls fail with timeout errors, the remote MCP server is taking too long to respond. Aperture retries tool calls once on connection errors. Check the remote server's performance and ensure it can respond within a reasonable time.</p>\n<p><img src=\"https://cdn.brandfetch.io/tailscale.com/fallback/lettermark/theme/dark/h/256/w/256/icon?c=1bfwsmEH20zzEfSNTed\" alt=\"Project Logo\"></p>\n<p>Ask AI</p>\n<p>reCAPTCHA</p>\n<p>Recaptcha requires verification.</p>\n<p>protected by <strong>reCAPTCHA</strong></p>\n"}