Aperture connectors reference

Last validated:

Aperture connectors are currently in alpha.

Aperture connectors are in public alpha. The connectors grant syntax and the connectors configuration section may change. The mcp_tools, mcp_resources, and mcp_templates grant fields are deprecated. Use the connectors field instead.

The connectors section of the Aperture configuration configures outbound integrations with external services. Connectors support two protocols:

  • MCP ("protocol": "mcp"): Aperture connects to remote Model Context Protocol servers, aggregates their tools and resources, and exposes them through the /v1/mcp endpoint.
  • HTTP ("protocol": "http"): Aperture acts as an authenticated reverse proxy, forwarding requests to upstream HTTP APIs with credentials injected automatically.

Connectors replace the deprecated mcp.servers syntax and add support for authenticated connections and multiple protocols.

Feature flag

The connectors section requires the connectors feature flag. Enable it by adding "flags": {"connectors": {"value": true}} to your configuration.

connectors fields

The connectors section accepts the following top-level field:

FieldTypeDefaultDescription
serversmap{}Map of connector ID to server configuration. The map key is the connector ID, which becomes the name prefix for tools (connectorID_toolname), resources (connectorID-uri), and resource templates (connectorID-uriTemplate).

servers fields

Each entry in the servers map accepts the following fields:

FieldTypeRequiredDefaultDescription
protocolstringYesN/AHow Aperture communicates with the upstream. One of "mcp" or "http".
urlstringNoN/AEndpoint URL of the upstream server. A missing url produces a load-time warning rather than a fatal error, so Aperture still loads the configuration. The dashboard's connector save path, however, rejects it as an error.
providerstringNo"Custom"Provider identifier. Set automatically when using a verified connector from the registry (for example, "Salesforce", "Atlassian", "Slack"). Custom connectors use the default value "Custom". Controls whether the connector appears in the "verified" or "custom" category in the UI. If set manually, provider must exactly match a verified connector ID from the registry or be "Custom". Any other value is a load-time error. Verified provider IDs are case-sensitive (for example, "Salesforce", not "salesforce").
descriptionstringNounset ("")Human-readable label surfaced to agents and the Aperture UI. When empty, the field is omitted from the stored configuration.
contextstring or JSONNoN/APrompt or context information to help models understand the connector. Accepts a plain string or arbitrary JSON object. Visible to all authenticated users through MCP tool discovery.
authobjectNoN/AAuthentication configuration. Dispatched by the type field.

Example with both protocols:

{
  "connectors": {
    "servers": {
      "tailnetMCP": {
        "protocol": "mcp",
        "url": "http://mcp-server.example.ts.net:8080/v1/mcp"
      },
      "github": {
        "protocol": "http",
        "url": "https://api.github.com",
        "description": "GitHub REST API (read-only)",
        "auth": {
          "type": "bearer_token",
          "secret": "github_pat_example"
        }
      }
    }
  }
}

In this example, github is a custom HTTP connector (its provider is unset, so it defaults to "Custom"). It is distinct from the verified GitHub MCP connector in the registry, which uses the case-sensitive provider ID "GitHub".

Connector ID rules

Connector IDs must match the pattern [a-zA-Z][a-zA-Z0-9]* (letters and digits only, starting with a letter). Connector IDs cannot contain underscores or hyphens because the MCP wire format reserves these as separators: underscores separate the connector ID from the tool name (snowflake_query), and hyphens separate the connector ID from resource URIs (snowflake-resource://...).

Valid IDs: github, snowflake, analyticsV2. Invalid IDs: my-mcp (hyphen), github_api (underscore), 2fast (starts with digit).

The IDs tailscale, internal, and aperture are reserved and cannot be used as connector IDs. The aperture ID is used by the built-in system connector and produces a distinct error message from tailscale and internal (refer to validation errors).

System connectors

Aperture includes built-in system connectors that are always available, independent of the connectors.servers configuration. System connectors appear in the GET /api/connectors response with category: "system".

IDProviderProtocolToolsDescription
apertureApertureMCPlist_connectorsLists all HTTP API connectors available through Aperture.

The aperture system connector helps AI models discover available HTTP connectors and construct requests to /v1/connectors/<id>/<path>. The aperture ID is reserved and cannot be used as a custom connector ID.

Verified connectors registry

Aperture maintains a registry of verified connectors with pre-configured defaults for known providers. The registry is accessible through GET /api/connectors-registry and is used by the dashboard's connector picker to pre-fill configuration fields.

Each registry entry carries the provider's url, auth_url, token_url, scopes, and (where applicable) auth_params. The following row illustrates the shape of a single entry:

ProviderProtocolAuth typeURLScopes
SlackMCPoauth2_authorization_codehttps://mcp.slack.com/mcpchannels:read, channels:history, users:read, search:read.public, chat:write

Aperture ships more than a dozen verified connectors. Field values (including auth_url, token_url, scopes, and auth_params) change as providers update their endpoints, so query GET /api/connectors-registry for the authoritative current list rather than copying values from this page.

Verified connector defaults are starting points. Once an admin saves a connector, the stored configuration values are the authoritative source of truth. The registry values are not enforced at runtime.

When a connector's provider is set to any value other than Custom, the connector appears in the "verified" category in the dashboard UI. Custom connectors (with provider: "Custom" or unset) appear in the "custom" category.

Connector categories

Each connector belongs to one of three categories based on its origin:

CategoryDescriptionExample
systemBuilt-in connectors provided by Aperture. Always present.aperture
customConnectors configured by an admin with provider: "Custom" or no provider field.github, weather
verifiedConnectors whose provider is set to any value other than Custom.salesforce (with provider: "Salesforce")

In the dashboard UI, verified providers that are not yet configured appear as placeholders with the not_configured status, prompting admins to set them up.

Connector statuses

The GET /api/connectors endpoint returns a status field for each connector indicating the user's authorization state:

StatusMeaningResolution
readyAccessible and authorized. No action needed.N/A
needs_authThe user has grant access but has not completed the OAuth authorization flow.Select Connect in the dashboard or call POST /api/connectors/<id>/connect.
misconfiguredAn OAuth token exists but lacks a refresh token. The OAuth application may not be configured for offline access.Disconnect and reconnect. Ensure auth_params includes the provider-specific parameter for refresh tokens (for example, "access_type": "offline" for Google).
no_accessThe user does not have a matching connectors grant.An admin must add a connectors grant for this user.
not_configuredA verified registry provider that has not been configured by an admin. UI-only status (not returned by the API for configured connectors).An admin must configure the connector in Settings.

The statuses above report authorization state, not upstream health. A connector can be authorized yet still fail at request time if its upstream is unreachable. Use the Connectors page to confirm the connector can reach its upstream and list capabilities.

Authentication

The auth object's type field selects the authentication scheme. The following table describes the available auth types:

TypeRequired fieldsDescription
bearer_tokensecretStatic Authorization: Bearer <secret> header.
api_keysecret, name, inCredential injected as a named header or query parameter. in must be "header" or "query".
basicusername, passwordHTTP Basic authentication.
oauth2_client_credentialsclient_id, client_secret, token_urlMachine-to-machine OAuth 2.0 token. Auto-refreshed. Optional scopes array.
oauth2_authorization_codeclient_id, auth_url, token_urlPer-user OAuth 2.0 consent flow. Optional client_secret (not required for PKCE-only), scopes, and auth_params.

API key

{
  "connectors": {
    "servers": {
      "weather": {
        "protocol": "http",
        "url": "https://api.weather.example.com",
        "auth": {
          "type": "api_key",
          "secret": "wk-example-key",
          "name": "X-API-Key",
          "in": "header"
        }
      }
    }
  }
}

The in field must be "header" or "query". When set to "header", Aperture injects the credential as a request header with the name specified in name. When set to "query", Aperture appends it as a URL query parameter.

Per-user OAuth 2.0 authorization code flow

When a connector uses oauth2_authorization_code, each user must complete an individual consent flow. Aperture always uses PKCE with the S256 challenge method. Each user gets an isolated MCP client session, so backends that tie session state to a bearer token cannot mix up cross-user state.

Initiating the flow

The connect and disconnect endpoints apply only to connectors that use oauth2_authorization_code authentication. Other connectors return a "not found" error.

To start authorization, send a POST request:

POST /api/connectors/<id>/connect

Aperture returns a JSON response containing the provider's authorization URL:

{"auth_url": "https://provider.example.com/oauth2/authorize?client_id=...&code_challenge=..."}

The user must open this URL in a browser to complete the consent flow. After granting consent, the provider redirects the user to Aperture's callback at /aperture/auth/<id>/callback, which renders an HTML page confirming success or failure. Register this callback URL with your OAuth provider when creating the application.

Pending authorization flows expire after 15 minutes. If the user does not complete the consent flow within this window, the flow is rejected.

Token lifecycle

Aperture stores tokens per-user and treats them as expired 60 seconds before their actual expiry, triggering a refresh on the next request. If a refresh fails (for example, because the provider revoked the refresh token), Aperture deletes the stored credential and the user must re-authorize by calling POST /api/connectors/<id>/connect again.

Lazy population

Connectors using oauth2_authorization_code populate their tool catalog lazily: Aperture fetches tools on the first authenticated request from each user rather than at startup. This means tools from a per-user connector do not appear in tools/list until at least one user has authorized.

auth_params

The auth_params field appends custom parameters to both the authorization URL and the token exchange request body. Some providers require specific parameters to issue refresh tokens:

  • Google: {"access_type": "offline", "prompt": "consent"}. Without access_type: offline, Google does not issue a refresh token.
  • Atlassian: {"audience": "api.atlassian.com", "prompt": "consent"}. Required for Jira and Confluence API access.
{
  "connectors": {
    "servers": {
      "google": {
        "protocol": "mcp",
        "url": "https://mcp.google.example.com/v1/mcp",
        "auth": {
          "type": "oauth2_authorization_code",
          "client_id": "example-client-id",
          "client_secret": "example-client-secret",
          "auth_url": "https://accounts.google.com/o/oauth2/v2/auth",
          "token_url": "https://oauth2.googleapis.com/token",
          "scopes": ["https://www.googleapis.com/auth/spreadsheets.readonly"],
          "auth_params": {"access_type": "offline", "prompt": "consent"}
        }
      }
    }
  }
}

The auth block rejects unknown fields. A typo in a field name (for example, "secert" instead of "secret") produces a validation error at load time.

Bearer token

{
  "connectors": {
    "servers": {
      "snowflake": {
        "protocol": "mcp",
        "url": "https://mcp.example.com/v1/mcp",
        "auth": {
          "type": "bearer_token",
          "secret": "sk-example-token"
        }
      }
    }
  }
}

OAuth 2.0 client credentials

{
  "connectors": {
    "servers": {
      "analytics": {
        "protocol": "mcp",
        "url": "https://analytics.example.com/v1/mcp",
        "auth": {
          "type": "oauth2_client_credentials",
          "client_id": "aperture-analytics",
          "client_secret": "secret-example",
          "token_url": "https://auth.example.com/oauth2/token",
          "scopes": ["read:data"]
        }
      }
    }
  }
}

MCP connectors

When protocol is "mcp", Aperture connects to remote MCP servers, aggregates their tools and resources, and exposes them through the /v1/mcp endpoint.

Name prefixing

Each key in the servers map is a connector ID that Aperture uses as a name prefix:

  • Aperture prefixes tools with the connector ID and an underscore. For example, a tool named search on the local connector becomes local_search.
  • Resources use a hyphen instead of an underscore. For example, local-files://readme.md.

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.

Transport auto-detection

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. You can upgrade remote servers to a later protocol version without restarting Aperture.

The same auto-detection applies to clients connecting to Aperture's /v1/mcp endpoint. Aperture serves both Streamable HTTP and legacy SSE clients.

Capability polling

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.

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.

Connectors using oauth2_authorization_code authentication are an exception. They have no 5-second background poller because capabilities are per-user. Instead, Aperture loads a user's capabilities lazily when that user starts a session, and refreshes them when the user calls the capability refresh endpoint (PUT /api/connectors/<id>/capabilities).

System connectors

Aperture includes a built-in aperture system connector that exposes the list_connectors tool. This tool lists all HTTP API connectors available through Aperture, helping AI models discover which APIs they can access and how to call them. To grant access, use a connectors grant matching "aperture/**" or "aperture/tools/*".

The aperture system connector always appears in GET /api/connectors, but its aperture_list_connectors MCP tool is registered only when at least one HTTP connector is configured.

On the /v1/mcp endpoint, this tool appears with the connector-ID prefix as aperture_list_connectors, consistent with the name-prefixing rule. Grants reference the unprefixed tool name (for example, "aperture/tools/list_connectors").

System connectors are not granted by default. The grant system governs their access the same way it governs custom and verified connectors. Being a system connector confers no special access. Without a matching connectors grant, users cannot access the aperture connector's tools.

The 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.

HTTP connectors

When protocol is "http", Aperture acts as an authenticated reverse proxy. Aperture forwards requests sent to /v1/connectors/<id>/<path> to the connector's upstream URL and injects credentials automatically. When at least one HTTP connector is configured, Aperture registers an aperture_list_connectors MCP tool so that AI models can discover available HTTP connectors.

The authentication credentials configured on an HTTP connector are shared by all users with a matching connectors grant. Users must have a grant matching "connectorID/proxy" or a broader pattern like "connectorID/**" to access the proxy endpoint. Use the narrowest possible credential scope (ideally read-only) and do not put sensitive information in the description or context fields, which are visible to all users.

Allowed HTTP methods

HTTP connectors accept the following request methods: GET, HEAD, POST, PUT, PATCH, DELETE, and OPTIONS. All other methods (including TRACE and CONNECT) are rejected with 405 Method Not Allowed. TRACE is blocked because it reflects request headers, which would expose injected authentication credentials.

Request and response handling

Aperture modifies requests and responses passing through the HTTP connector proxy:

  • Request headers: Aperture strips Authorization and Cookie headers from client requests before forwarding to the upstream. This prevents clients from injecting credentials that could leak to the upstream server. Aperture injects the correct credentials separately based on the connector's auth configuration.
  • Response headers: Aperture strips Set-Cookie headers from upstream responses, preventing the upstream from setting cookies on Aperture's origin.
  • Hop-by-hop headers: Standard hop-by-hop headers (Connection, Keep-Alive, Proxy-Authenticate, Proxy-Authorization, Te, Trailer, Transfer-Encoding, Upgrade) are stripped in both directions per HTTP specification.
  • Request body: Forwarded as-is with no size limit.
  • Response body: Limited to 50 MB. Responses exceeding this limit are truncated.

Security behavior

HTTP connectors enforce the following security restrictions:

  • URL restrictions: Connector URLs are validated against a safe dialer that blocks loopback addresses, link-local ranges, and certain special-purpose IP ranges. RFC 1918 (private), ULA, and Tailscale CGNAT addresses are permitted.
  • Redirect blocking: HTTP connectors block all redirects from upstream servers to prevent credential exfiltration through redirect chains.
  • Credential injection: Aperture injects credentials on every proxied request based on the connector's auth configuration. Client-supplied Authorization headers are always replaced.

Dynamic registration

Dynamic registration is configured only through the legacy mcp section and has no connectors equivalent. The behavior described here applies to the mcp section.

Dynamic registration lets MCP servers register themselves with Aperture at runtime instead of through static configuration. Set accept_registrations to true in the mcp section of your configuration. This field requires the mcp section and has no equivalent in connectors.

{
  "mcp": {
    "accept_registrations": true,
    "servers": {}
  }
}

Remote servers register by sending a POST request to /v1/mcp/register with a JSON body containing their URL:

curl -X POST http://<aperture-hostname>/v1/mcp/register \
  -H "Content-Type: application/json" \
  -d '{"url": "http://my-mcp-server:8080/v1/mcp"}'

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 (auto1, auto2, and so on), and Aperture prefixes its tools accordingly (for example, auto1_search).

The registration endpoint requires Tailscale authentication, the same as all other Aperture endpoints. The registering server must be accessible through the tailnet.

The 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.

You can combine static connectors and dynamic registration in the same configuration. Static connectors are always available, while dynamically registered servers come and go as they connect and disconnect.

mcp fields

FieldTypeDefaultDescription
accept_registrationsbooleanfalseAllow backends to register dynamically through POST /v1/mcp/register. Backends POST {"url": "http://..."} and keep the connection open. Tools are unregistered when the connection closes. This field has no connectors equivalent.
serversmap{}Map of server ID to server configuration. Deprecated: use connectors.servers instead. The map key is the server ID, which becomes the name prefix for tools (serverID_toolname), resources (serverID-uri), and resource templates (serverID-uriTemplate) from that backend.

Grants for connector resources

Aperture is deny-by-default. Without grants, users cannot access any connector capabilities. This applies to both MCP and HTTP connectors. Add grants in the grants section of the Aperture configuration.

Connector grants use the connectors field with "connectorID/category/resource" FQN glob patterns. The following example grants all users access to all tools from the local connector and proxy access to the github HTTP connector:

{
  "grants": [
    {
      "src": ["*"],
      "app": {
        "tailscale.com/cap/aperture": [
          {"connectors": ["aperture/**"]},
          {"connectors": ["local/tools/*"]},
          {"connectors": ["github/**"]}
        ]
      }
    }
  ]
}

The connectors field accepts an array of FQN glob strings. Each pattern has up to three segments: connectorID/category/resource.

Grant categories

CategoryApplies toDescriptionExample
toolsMCP connectorsMCP tools exposed by the connector"local/tools/search"
resourcesMCP connectorsMCP resources exposed by the connector"local/resources/*"
templatesMCP connectorsMCP resource templates exposed by the connector"local/templates/*"
proxyHTTP connectorsProxy access to the HTTP connector endpoint"github/proxy"

Grant patterns

You can use * to match any characters within a segment and ** to match across segments:

  • "local/tools/search": the search tool from the local connector.
  • "local/tools/*": all tools from the local connector.
  • "local/**": all capabilities from the local connector (tools, resources, templates).
  • "github/proxy": proxy access to the github HTTP connector.
  • "github/**": all access to github (proxy and any future categories).
  • "**": all capabilities from all connectors.

A top-level pattern like "connectorID/*" is automatically expanded to "connectorID/**", so it covers all categories and sub-resources.

Aperture checks grants when clients list available tools and resources. Users can access only the items their grants permit. Aperture also enforces grants at invocation time: when a client calls a tool, reads a resource, or sends a proxy request, Aperture checks the session's grants before dispatching the request. If no grant matches, Aperture rejects the call with a "forbidden" or "unknown tool" error.

The deprecated mcp_tools, mcp_resources, and mcp_templates grant fields continue to work for backward compatibility with the "server/item" pattern syntax. New configurations should use the connectors field, which supports all connector types including HTTP proxy access.

Resource grants are now matched against the resource's name, not its URI. Legacy mcp_resources patterns written against resource URIs no longer match. Rewrite them against the resource name using connectors with the resources category.

Refer to the grants configuration reference for the full grants syntax.

Migration from mcp.servers

The connectors section replaces the deprecated mcp.servers syntax. At load time, Aperture automatically folds legacy mcp.servers entries into connectors.servers with protocol: "mcp" and no authentication. Both sections can coexist during migration, but connector IDs must be unique across both. Aperture rejects duplicate IDs at load time.

Legacy mcp.servers entries that use connector IDs not conforming to the [a-zA-Z][a-zA-Z0-9]* pattern generate a warning but are still accepted for backward compatibility. New entries in the connectors section must conform to the ID rules.

Consider the following mcp.servers entry:

"mcp": { "servers": { "local": { "url": "http://localhost:8185/v1/mcp" } } }

The equivalent connectors entry is:

"connectors": { "servers": { "local": { "protocol": "mcp", "url": "http://localhost:8185/v1/mcp" } } }

The mcp section is retained for backward compatibility and for the accept_registrations field, which has no connectors equivalent.

Validation errors

Aperture validates connector configuration at load time. The following table describes connector-specific validation messages:

ConditionMessageSeverity
Connector missing protocolconnectors.servers.{id}: protocol is required (e.g. "mcp" or "http")Error
Unsupported connector protocolconnectors.servers.{id}: unsupported protocol "{value}" (supported: "mcp", "http")Error
Invalid connector IDconnectors.servers.{id}: invalid ID "{id}"; connector IDs must match [a-zA-Z][a-zA-Z0-9]* (letters and digits only, starting with a letter; no hyphens or underscores)Error
Reserved connector ID (tailscale, internal)connectors.servers.{id}: "{id}" is a reserved identifierError
Reserved system connector ID (aperture)connectors.servers.{id}: "{id}" is a reserved system connector identifierError
Connector sets an unknown providerconnectors.servers.{id}: unknown provider "{value}"; use a verified ID or omit for custom connectorsError
Connector missing URLconnectors.servers.{id}: URL is requiredWarning
Connector URL uses restricted addressconnectors.servers.{id}: URL {details}Warning
Connector auth validation failureconnectors.servers.{id}.auth: {details}Error
Duplicate connector ID across mcp and connectorsconfig: server ID "{id}" is defined in both 'mcp.servers' and 'connectors.servers'; IDs must be unique across both sectionsError

Refer to the Aperture configuration reference for the full validation table.

REST API endpoints

The following endpoints manage connectors at runtime. All endpoints require Tailscale authentication.

MethodPathDescriptionAccess
GET/api/connectorsList all connectors (system, custom, verified) with status for the current user.All authenticated users
GET/api/connectors/{id}/capabilitiesGet cached MCP capabilities (tools, resources, templates) for a connector. Requires a connectors grant.Grant required
PUT/api/connectors/{id}/capabilitiesForce-refresh capabilities from the upstream MCP server.Grant required
POST/api/connectors/{id}/connectStart an OAuth 2.0 authorization code flow. Returns {"auth_url": "..."}.Grant required
POST/api/connectors/{id}/disconnectForget the current user's OAuth token for a connector.Grant required
GET/api/connectors-registryList the verified connectors registry (pre-configured providers).All authenticated users

The HTTP connector proxy endpoint uses a separate path:

MethodPathDescriptionAccess
Any supported/v1/connectors/{id}/{path}Reverse proxy to an HTTP connector's upstream URL.Requires connectorID/proxy grant

Full annotated example

The following example shows a complete connector configuration with both MCP and HTTP protocols, authentication, grants, and dynamic registration:

{
  // Enable the connectors feature flag
  "flags": {
    "connectors": {"value": true}
  },

  // Outbound integrations (MCP servers and HTTP APIs)
  "connectors": {
    "servers": {
      // Unauthenticated MCP server on your tailnet
      "tailnetMCP": {
        "protocol": "mcp",
        "url": "http://mcp-server.example.ts.net:8080/v1/mcp"
      },
      // MCP server with bearer token auth
      "snowflake": {
        "protocol": "mcp",
        "url": "https://mcp.example.com/v1/mcp",
        "auth": {
          "type": "bearer_token",
          "secret": "sk-example-token"
        }
      },
      // HTTP API proxy with OAuth 2.0 client credentials
      "analytics": {
        "protocol": "http",
        "url": "https://analytics.example.com/api",
        "description": "Analytics API (read-only)",
        "auth": {
          "type": "oauth2_client_credentials",
          "client_id": "aperture-analytics",
          "client_secret": "secret-example",
          "token_url": "https://auth.example.com/oauth2/token",
          "scopes": ["read:data"]
        }
      }
    }
  },

  // Dynamic MCP registration (accept_registrations has no connectors equivalent)
  "mcp": {
    "accept_registrations": true
  },

  // Grant all users access to Aperture built-in tools and all connectors
  "grants": [
    {
      "src": ["*"],
      "app": {
        "tailscale.com/cap/aperture": [
          {"connectors": ["aperture/**"]},
          {"connectors": ["**"]}
        ]
      }
    }
  ]
}

Troubleshooting

Connector problems fall into two flows with different causes and fixes: an admin setting up and configuring a connector, and a user authorizing and using a connector's tools. Use the section that matches your role.

The Connectors page in the dashboard is the fastest first check for either role. It probes each connector's upstream URL and reports whether Aperture can reach the server and list its capabilities, so it surfaces most connection and configuration errors without requiring access to the Aperture host.

For admins setting up connectors

These issues affect connector configuration and reachability. Start with the Connectors page to confirm Aperture can reach the upstream.

MCP tools do not appear

If tools from a configured MCP connector are not visible:

  1. Open the Connectors page and check the connector's status and capability list. Because the page probes the upstream URL, it reports whether Aperture can reach the server and list its capabilities.
  2. Verify the URL in your configuration is correct and the MCP server is running.
  3. Verify your grants include connectors patterns that match the connector and tool names (for example, "local/tools/*"). Without grants, users cannot access any connector tools.

Connection refused or host not found

These errors indicate the connector URL is unreachable. The Connectors page shows the error when it probes the upstream.

  • Connection refused: The server is not running or is not listening on the configured port.
  • Host not found: DNS cannot resolve the hostname. For tailnet hostnames, verify the device is connected to the tailnet and check with tailscale status.

Tools appear and then disappear

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.

Auth configuration rejected with unknown field error

The auth block uses strict field validation. If a field name is misspelled (for example, "secert" instead of "secret"), the configuration fails to load with a JSON decoder error. Double-check field names against the auth types table above.

Dynamic registration fails

If remote servers cannot register dynamically:

  1. Verify accept_registrations is set to true in the mcp section of your configuration (this field is not available in the connectors section).
  2. Ensure the remote server sends a valid POST request to /v1/mcp/register with a JSON body containing {"url": "<mcp-server-url>"}.
  3. The remote server must keep the HTTP connection open after registration. If the connection closes, Aperture unregisters the server's tools immediately.

For users authorizing and using connectors

These issues affect authorizing a connector and calling its tools. You do not need access to the Aperture host. The Connectors page is enough to diagnose and recover.

A connector shows "Needs auth" or its tools are missing

Connectors that use oauth2_authorization_code require each user to authorize individually. Open the Connectors page, select the connector, and choose Connect to complete the consent flow. The connector's tools appear after you authorize.

Tools stop working after previously working

If tools from a connector using oauth2_authorization_code stop working after a period of inactivity, the refresh token has likely expired or been revoked by the provider. To recover, open the Connectors page, select the connector, and choose Disconnect and then Connect to re-authorize.

Disconnecting and reconnecting is safe and forces Aperture to generate fresh tokens. If you suspect your authorization is stuck (a token that is broken but still present), disconnect and reconnect to clear it.

Tool calls time out

If tool calls fail with timeout errors, the remote MCP server is not responding quickly enough. Aperture retries a tool call once on connection errors. On failure, it evicts the current session, reconnects to the remote server, and retries. If timeouts persist, ask your admin to check the remote server's performance and logs.