"""A catalogue of external, locally-run MCP servers, and the way in. These are other people's programs. The in-process servers beside this module run inside the worker and are governed by it; everything listed here runs as a child process speaking MCP over stdio, does its own I/O, and is exactly as trustworthy as its publisher. So this module is a catalogue and a doorway, never an installer: nothing below downloads anything, nothing starts unless an operator names the server, and a server whose credentials are absent refuses at connect time with the variable names it wanted — the credential values themselves are never read into this process beyond handing the child its environment. **Authentication is per server and stays in the environment.** Each entry names the environment variables its server reads (an API key, or Google Application Default Credentials established with ``gcloud auth login``). This module checks presence, never value, and passes the environment through to the child. Nothing here stores, logs or transmits a credential. The Google entries come from https://github.com/google/mcp — the repository is a directory of Google's official servers, and the ones catalogued here are its "open-source, run locally" set. The Playwright and Postman entries are the official publishers' packages. Remote/hosted MCP endpoints from that list are deliberately not catalogued: a remote tool server is a different trust conversation from a local child process, and this catalogue refuses to blur it. """ from __future__ import annotations import os import shutil import subprocess from collections.abc import Mapping, Sequence from dataclasses import dataclass from distinct_tools.mcp import McpError, StdioMcpTransport __all__ = [ "ExternalMcpServer", "EXTERNAL_CATALOGUE", "connect_external", "describe_external", "external_by_name", ] @dataclass(frozen=True) class ExternalMcpServer: """One locally-runnable server: how to start it, what it needs, who made it.""" name: str publisher: str summary: str #: The command line, exactly. ``npx --yes`` and ``uvx`` fetch on first #: run; that is the publisher's distribution mechanism, stated rather #: than hidden. command: tuple[str, ...] #: Environment variable NAMES the server authenticates with. Presence is #: checked before spawn; values are never read by this module. auth_env: tuple[str, ...] = () #: Auth that is a local login state rather than a variable, e.g. Google #: Application Default Credentials. Recorded for the operator's console. auth_note: str = "" #: Hosts the operator consents to this server reaching. A record of #: consent, not a control: the child does its own I/O. hosts: tuple[str, ...] = () source: str = "" @property def runtime(self) -> str: return self.command[0] _GOOGLE_ADC = ( "Authenticate with Google Application Default Credentials: run " "`gcloud auth application-default login` once as the operator." ) EXTERNAL_CATALOGUE: tuple[ExternalMcpServer, ...] = ( ExternalMcpServer( name="playwright", publisher="Microsoft", summary=( "Drive a real browser: navigate, click, fill and read pages. Needs a " "Chromium install; set PLAYWRIGHT_BROWSERS_PATH to reuse one already " "on the machine instead of downloading another." ), command=("npx", "--yes", "@playwright/mcp@latest"), auth_env=(), auth_note="No account. The browser itself is the capability.", hosts=("*",), source="https://github.com/microsoft/playwright-mcp", ), ExternalMcpServer( name="postman", publisher="Postman", summary=( "Postman's official server: collections, environments and API " "definitions in your workspaces, the public API network included." ), command=("npx", "--yes", "@postman/postman-mcp-server"), auth_env=("POSTMAN_API_KEY",), auth_note="Create a key at https://postman.com/settings/me/api-keys.", hosts=("api.getpostman.com",), source="https://github.com/postmanlabs/postman-mcp-server", ), ExternalMcpServer( name="google-workspace", publisher="Google", summary="Docs, Sheets, Slides, Calendar and Gmail in the operator's Workspace.", command=("npx", "--yes", "@googleworkspace/mcp"), auth_env=(), auth_note=_GOOGLE_ADC, hosts=("*.googleapis.com",), source="https://github.com/gemini-cli-extensions/workspace", ), ExternalMcpServer( name="google-analytics", publisher="Google", summary="Report on Analytics properties: audiences, events, conversions.", command=("uvx", "analytics-mcp"), auth_env=(), auth_note=_GOOGLE_ADC, hosts=("analyticsdata.googleapis.com", "analyticsadmin.googleapis.com"), source="https://github.com/googleanalytics/google-analytics-mcp", ), ExternalMcpServer( name="google-cloud-storage", publisher="Google", summary="List, read and manage Cloud Storage buckets and objects.", command=("npx", "--yes", "@google-cloud/storage-mcp"), auth_env=(), auth_note=_GOOGLE_ADC, hosts=("storage.googleapis.com",), source="https://github.com/googleapis/gcloud-mcp", ), ExternalMcpServer( name="gcloud", publisher="Google", summary="The gcloud CLI as tools: inspect and manage Google Cloud projects.", command=("npx", "--yes", "@google-cloud/gcloud-mcp"), auth_env=(), auth_note=_GOOGLE_ADC + " Also requires the gcloud CLI on PATH.", hosts=("*.googleapis.com",), source="https://github.com/googleapis/gcloud-mcp", ), ExternalMcpServer( name="google-observability", publisher="Google", summary="Cloud Logging, Monitoring and Trace: read logs and metrics.", command=("npx", "--yes", "@google-cloud/observability-mcp"), auth_env=(), auth_note=_GOOGLE_ADC, hosts=("logging.googleapis.com", "monitoring.googleapis.com"), source="https://github.com/googleapis/gcloud-mcp", ), ExternalMcpServer( name="mcp-toolbox-databases", publisher="Google", summary=( "Databases behind one server: BigQuery, Cloud SQL, AlloyDB, Spanner, " "Postgres and more, from a tools.yaml the operator writes." ), command=("toolbox", "--stdio"), auth_env=(), auth_note=( "Install the MCP Toolbox binary and write its tools.yaml; database " "credentials live in that file or in " + _GOOGLE_ADC ), hosts=("*",), source="https://github.com/googleapis/genai-toolbox", ), ExternalMcpServer( name="gke", publisher="Google", summary="Kubernetes Engine: inspect clusters, workloads and events.", command=("npx", "--yes", "@google-cloud/gke-mcp"), auth_env=(), auth_note=_GOOGLE_ADC, hosts=("container.googleapis.com",), source="https://github.com/GoogleCloudPlatform/gke-mcp", ), ExternalMcpServer( name="chrome-devtools", publisher="Google (Chrome DevTools)", summary="Debug a running Chrome: inspect pages, console, network and traces.", command=("npx", "--yes", "chrome-devtools-mcp@latest"), auth_env=(), auth_note="No account. Attaches to a local Chrome the operator starts.", hosts=(), source="https://github.com/ChromeDevTools/chrome-devtools-mcp", ), ) def external_by_name(name: str) -> ExternalMcpServer: for entry in EXTERNAL_CATALOGUE: if entry.name == name: return entry raise McpError(f"no external MCP server named {name!r} is catalogued") def _missing_auth(entry: ExternalMcpServer, environ: Mapping[str, str]) -> tuple[str, ...]: return tuple(var for var in entry.auth_env if not environ.get(var, "").strip()) def describe_external(environ: Mapping[str, str] | None = None) -> str: """The operator's view: every entry, its runtime, and its auth state.""" values = os.environ if environ is None else environ lines = [ "External MCP servers this worker can connect on request. Nothing below", "is installed, running or approved until an operator names it.", ] for entry in EXTERNAL_CATALOGUE: missing = _missing_auth(entry, values) runtime_present = shutil.which(entry.runtime) is not None state = [] state.append(f"runtime {entry.runtime}: {'present' if runtime_present else 'absent'}") if entry.auth_env: state.append( "auth: ready" if not missing else "auth: missing " + ", ".join(missing) ) else: state.append("auth: " + (entry.auth_note or "none")) lines.append(f" {entry.name} ({entry.publisher})") lines.append(f" {entry.summary}") lines.append(f" {'; '.join(state)}") return "\n".join(lines) def connect_external( name: str, *, environ: Mapping[str, str] | None = None, timeout: float = 60.0, ) -> StdioMcpTransport: """Spawn one catalogued server and complete the MCP handshake. Fails closed, with the exact variable names, when its credentials are absent — a server started without them would answer the handshake and then fail every real call, which is a worse failure because it is a later one. The caller owns the transport and must ``close()`` it; the spawn uses no shell and passes the operator's environment through untouched, which is the entire credential path. """ entry = external_by_name(name) values = os.environ if environ is None else environ missing = _missing_auth(entry, values) if missing: raise McpError( f"{entry.name} needs {', '.join(missing)} set in the environment. " f"{entry.auth_note}".strip() ) if shutil.which(entry.runtime) is None: raise McpError( f"{entry.name} needs {entry.runtime!r} on PATH; install it and retry" ) def spawn(command: Sequence[str]) -> subprocess.Popen: return subprocess.Popen( list(command), stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, env=dict(values), text=True, ) transport = StdioMcpTransport( tuple(entry.command), spawn=spawn, handshake_timeout=timeout ) transport.start() return transport