# apollo-mcp

Standalone **Apollo MCP Server** for The PDEGO platform. It exposes a curated slice of the federated GraphQL graph to AI agents as MCP tools, and
forwards tool calls to the MCP router (`qa-graphql-router-mcp`).

Part of the **MCP Router Pilot** epic ([GQL-343](https://theorchard.atlassian.net/browse/GQL-343)) /
this repo is [GQL-349](https://theorchard.atlassian.net/browse/GQL-349). Design + auth decisions
live in the [PDEGO MCP Rollout doc](https://app.notion.com/p/37697177520f81e2b622d7465b5a8b76).

## Topology

```
AI client ──/mcp──▶ qa-apollo-mcp (this repo) ──GraphQL──▶ qa-graphql-router-mcp ──▶ subgraphs
                    (Apollo MCP Server,                     (router, qa-mcp
                     standalone, Fargate)                    contract variant)
```

The server is the **prebuilt** `ghcr.io/apollographql/apollo-mcp-server` image — we
ship config + operations, not server code.

## Layout

```
.
├── Dockerfile              # standalone apollo-mcp-server + our config/operations
├── config/
│   ├── mcp.qa.yaml         # QA: endpoint=qa-graphql-router-mcp, tools from GraphOS collection
│   └── mcp.local.yaml      # local: endpoint=localhost router, tools from ./operations
├── operations/             # pre-defined GraphQL operations -> MCP tools (local source)
│   └── GetCampaigns.graphql
├── inspector.config.json   # MCP Inspector connection (transport+URL) for `make inspect`
├── Makefile                # build / run / inspect
├── Jenkinsfile             # CI -> ECR -> qa-apollo-mcp Fargate (starter; see TODO)
├── .env.shadow
├── .gitignore
└── .dockerignore
```

## Tools

Tools are **pre-defined GraphQL operations**, not live introspection (tighter
surface, smaller agent context). Two sources:

- **QA** (`config/mcp.qa.yaml`): `operations.source: collection` — reads the
  "Default MCP Tools" collection on the `graphql-theorchard@qa-mcp` variant.
  Needs `APOLLO_KEY` + `APOLLO_GRAPH_REF` (see `.env.shadow`).
- **Local** (`config/mcp.local.yaml`): `operations.source: local` — one `.graphql`
  file per tool under `operations/`, hot-reloaded.

Only fields tagged into the qa-mcp contract are reachable. See GQL-345 / GQL-350.

## Run it locally

```bash
cp .env.shadow .env          # fill APOLLO_KEY for the collection source (QA build only)
make build               # uses config/mcp.local.yaml
make run-local                 # serves Streamable HTTP on :8080
make inspect                   # MCP Inspector, connection pre-filled from inspector.config.json
```

Local needs a router on `localhost:8080` (e.g. `make dev` in `graphql-router`).

### MCP Inspector

`make inspect` runs `@modelcontextprotocol/inspector --config inspector.config.json
--server apollo-mcp`, so the transport (`streamable-http`) and URL
(`http://localhost:8080/mcp`) are pre-filled — no retyping the endpoint each session.

**Headers can't be baked into the config file or a launch flag** — the launcher
forwards only transport + URL to the UI (a `headers` block in the config, and the
`--header` flag, are ignored in UI mode). Instead the UI persists request headers in
browser `localStorage` (`lastCustomHeaders`), so headers you enter **stay pre-filled
across restarts** in that browser:

- Open **Authentication → Custom Headers**, add your static headers once (pre-filled
  on every later launch), and set the per-session `Authorization` bearer there too
  (passed through to the router — GQL-353).
- To predefine them without clicking (e.g. shared team setup), paste this once in the
  Inspector tab's devtools console, then reload:

  ```js
  localStorage.setItem("lastCustomHeaders", JSON.stringify([
    { name: "x-example-static", value: "value", enabled: true }
    // add the headers you want forwarded; leave Authorization to set per session
  ]));
  ```

  For those headers to reach the router, list them under `forward_headers` in
  `config/mcp.local.yaml`. (Values that are static MCP→router belong under `headers:`
  there instead — no need to send them from the Inspector at all.)

For a fully scripted, non-interactive run, CLI mode *does* honor `--header`:

```bash
npx @modelcontextprotocol/inspector --cli \
  http://localhost:8080/mcp --transport http --method tools/list \
  --header "Authorization: Bearer $TOKEN" \
  --header "x-example-static: value"
```

or if you are using it interactively, from Claude:

```bash
claude mcp add --transport http pdego-mcp-local http://localhost:8080/mcp \
    --client-id axdhcX5MLHiK2qgQPKwRiw44fwXTTYpa \
    --callback-port 8511
```

## Auth (in progress — GQL-353)

The MCP server is the OAuth 2.1 **protected resource**; **Auth0** is the
authorization server. The `transport.auth` block in `config/mcp.qa.yaml` is
stubbed pending the Auth0 client/audience decisions. The server forwards the
caller's bearer token to the router (token passthrough) and sets
`apollographql-client-name`, both of which the router enforces. Open decisions
(dedicated audience, passthrough on/off, cohort gate) are tracked in the rollout doc.

## Deploy

CI builds the image and deploys to the `qa-apollo-mcp` Fargate service. The
Fargate/ECR infra is provisioned in `terraform-infra` (GQL-349). The
`Jenkinsfile` here is a starter — wire the deploy stage once the Fargate service
and ECR repo exist.
