---
title: "MCP Gateway"
slug: mcp-gateway
section: Developer Onboarding
status: stable
description: "One MCP endpoint for every Alliance Abroad tool."
last-updated: 2026-10-07
---

# MCP Gateway

One MCP endpoint for every Alliance Abroad tool. Configure it once with your personal token and the gateway routes each tool call to the correct backend (Teams, GCP, GitHub, Globus, Nexus, GoDaddy, WordPress, and more) — injecting the right credential and enforcing your role.

> **This replaces the old MCP Shop**
>
> You no longer add each MCP server individually. Add the single gateway below; it aggregates all backends behind role-based access.

## Overview

The gateway is a single Streamable-HTTP MCP endpoint. It authenticates you (Google SSO for humans via the admin portal, or an `aag-pat-` Personal Access Token for agents/robots), resolves your role-based toolpack, and proxies each call to the right upstream MCP — without ever forwarding your token to backends.

- Gateway MCP endpoint: https://mcp.allianceabroad.com/mcp
- Admin / setup portal: https://mcp-admin.allianceabroad.com (Google SSO, superadmin-only — humans never call `/mcp` directly; agent PATs are issued from here)

## Why a single gateway

- Configure one server instead of two dozen — no per-backend URLs or keys to manage.
- Role-gated: you only see the tools your role permits.
- Credentials stay server-side; your JWT/PAT is never passed to backends.
- Every call is audited.

## Step 1 — Get a token

Agents and robots authenticate with a Personal Access Token (PAT). Create one in the admin portal:

- Open the admin portal and sign in with your Alliance Google account.
- Go to Users & Roles → Robot PATs → New Robot PAT.
- Pick a label and roles, create it, and copy the token — it is shown only once (starts with `aag-pat-`).
- An owner/superadmin caller can instead mint a robot PAT from an MCP session with `gateway_admin_create_robot_pat`. Any role name that is not yet registered is registered automatically in the same transaction (so it can immediately be granted permissions), and `gateway_admin_create_role` registers a role directly without minting a robot.

> **Handle tokens safely**
>
> Never commit a PAT. Store it in 1Password / Secret Manager. It carries whatever roles you granted it.

## Step 2 — Add one MCP server

Add a single HTTP MCP server that passes your PAT as an `Authorization: Bearer` header. This works in Claude, Cursor, Manus (Custom MCP → Import by JSON), n8n, and any MCP client that speaks Streamable HTTP.

```json
{
  "mcpServers": {
    "alliance-gateway": {
      "url": "https://mcp.allianceabroad.com/mcp",
      "headers": { "Authorization": "Bearer aag-pat-..." }
    }
  }
}
```

## Step 3 — First calls

Once connected, the gateway tells the agent what to do via its MCP `initialize` instructions. The mandatory startup sequence:

- If unsure a tool exists, call `gateway_search_tools` with a keyword first — cheaper than scanning the full `tools/list`.
- Call `gateway_getting_started` and follow it exactly.
- Call `gateway_list_skills`, then `gateway_get_skill` for the skills relevant to your task.
- Only then use domain tools, in the documented order — for example, read the Nexus documentation via the `ecosystem_*`/`nexusdocs_*` docs tools before taking any `ecosystem_*` write action.

## Auth model

Every backend has an auth mode:

- **Shared** — one org-wide credential, no per-user concept: `hubspot`, `ecosystem` (Nexus Hub), `nexusdocs`, `platformhub`, `wordpress`, `itrequest` (IT Request Portal) and `replitssh` (Replit SSH, owner role only).
- **Personal** — the default. The gateway forwards YOUR per-user token so you act as yourself: `teams`, `gcp`, `gcplogs`, `auth0`, `azure`, `gworkspace`, `posthog`, `asana`, `manus`, `godaddy`, `1password`, `openstatus`, `outlook`, `replit`, `globus`, `github`.
- **Hybrid fallback** — for most personal backends, if you haven't connected your own account the gateway falls back to the shared service credential so the tool still works now, then upgrades to your personal token once you connect.
- **PersonalOnly (no fallback)** — `outlook`, `replit`, `teams` and `asana` have no shared fallback for human callers and hard-fail with "requires authorisation" until you connect your own account. (`teams_send_bot_channel_message` is the one Teams tool that works without a connected account, because it posts as the bot.) Asana was made PersonalOnly so that tasks created through the gateway are attributed to the person who made them rather than to whoever owned the shared token.
- **Robot exception for Asana** — a robot (PAT) caller has no human account to connect, so for `asana` it skips the must-connect gate and is served on asana-mcp's shared credential, which is intended to be the dedicated platform bot Asana identity, never a person's token. The gateway does not send a per-user Asana token on that path, marks the request as a robot fallback for asana-mcp, and asana-mcp only honours the marker together with the gateway's shared API key. A connected account always wins; humans and the other PersonalOnly backends are unchanged.

### Connecting your own account

Call `gateway_connect_url` to get a one-click browser link for connecting a personal backend — it walks you through the provider's OAuth consent screen, or (for API-key backends) a paste-token form: `globus` takes your `glb_` key, `1password` your service-account token, `github` your `ghp_`/`github_pat_` token. Pasted tokens are validated live before storage. All tokens are AES-256-GCM encrypted at rest, scoped strictly per user, and never visible to another caller. OAuth consent is wired for `teams`, `outlook`, `gworkspace`, `gcp`, `gcplogs`, `asana` and `replit`. `gcplogs` reuses the same Google web client and `cloud-platform` scope as `gcp` (callback `/auth/callback/gcplogs`) but is a separate connection, so a caller can connect one without the other, and an unconnected caller keeps the shared GCP fallback for that backend.

## What you get

Backends are exposed as prefixed tools — `gcp_*`, `gcplogs_*`, `teams_*`, `github_*`, `globus_*`, `godaddy_*`, `ecosystem_*`, `nexusdocs_*`, `platformhub_*`, `wordpress_*`, `gworkspace_*`, `posthog_*`, `hubspot_*`, `outlook_*`, `auth0_*`, `azure_*`, `1password_*`, `asana_*`, `itrequest_*`, `replitssh_*`. Plus gateway-native tools, advertised to every authenticated caller regardless of role (the admin-style ones listed last are only usable by an owner/superadmin):

- `gateway_getting_started`, `gateway_list_skills`, `gateway_get_skill` — onboarding and the skill library.
- `gateway_search_tools` — keyword search across every tool your role grants; use this before assuming a tool doesn't exist.
- `gateway_connect_url` — self-serve personal-account connect links (see above).
- `gateway_rotate_pat` — rotate your own PAT when the gateway warns it's expiring.
- `gateway_submit_ticket` / `gateway_get_tickets` / `gateway_submit_test_plan` — file and read tickets and QA test plans against the Development ticket board. These are always advertised, and degrade gracefully (a "board offline" message, no retry) if the upstream board isn't live.
- `gateway_estimate_feature` — a read-only, deterministic planning calculator, available to every caller. You decompose the work into PR-sized tasks classified by shape (XS/S/M/L/XL) and it returns a planning object that keeps three clocks separate: agent wall-clock, attended orchestration hours (the capacity constraint) and calendar-to-hardened days (`committed_days` is the date to quote), plus confidence, risk, `prioritization.spikes_first` and a `model_version` stamp. It refuses to certify a single task for a date commitment (`usable_for_commitment=false`): estimate batches, not tasks. The method and constants are in the `estimation-model` skill, which must be updated together with the tool.
- `gateway_tool_usage` — owner/superadmin only. Ranks tools by call volume from the audit log (30-day retention): calls, denied calls, distinct callers, average latency and owning backend per tool, aggregates only. Arguments: `days` (default 7, max 30), `limit` (default 15, max 100) and an optional `server_slug`.
- `gateway_admin_list_permissions`, `gateway_admin_update_permissions`, `gateway_admin_create_robot_pat`, `gateway_admin_create_role` — MCP equivalents of the admin REST API for permissions, robot PATs and roles. They are listed in `tools/list` for every caller but checked inside the handler: a non-owner gets a permission error on `tools/call`. Updating permissions replaces the allowed-tools list for that role and backend rather than merging it.

`nexusdocs_*` also carries the video library's two read tools, open to every caller like `searchDocs`: `nexusdocs_searchVideos` (`q`, optional `audience`, `program`, `maxDuration`, `updatedSince`, `limit`) finds clips by the question they answer, their chapter titles or transcript, and `nexusdocs_getVideo` (`videoId`) returns one clip's metadata, chapters with deep links and transcript. They are **link-only**: a result is a Nexus page link such as `/docs/video-approve-passport-scan?t=70` plus metadata, never a signed URL, bucket name or object path, because playback is authorised per signed-in viewer. There is no role grant to manage: the gateway proxies whatever the Nexus backend lists. The `help-video-routing` skill tells an assistant when to use them.

Two backends added after launch are deliberately narrow:

- `itrequest_*` (IT Request Portal, the Tech/IT request ticketing API): `create_ticket`, `list_tickets`, `get_ticket`, `list_freeze_windows`, `create_freeze_window`, `delete_freeze_window`. owner and admin get all six; member gets only the first four. Creating or deleting a deploy freeze window is admin/owner-only because a freeze window blocks deploys platform-wide. viewer and the service (`svc_*`) roles get nothing by default.
- `replitssh_*` (Replit SSH): `exec`, `read_file`, `write_file`, `list_dir` over SSH into a Repl. This is arbitrary remote execution, so only the owner role has a grant; for every other role the absence of a grant is a hard deny.

Call `tools/list` after connecting to see exactly what your role grants.

## Skills library and update notices

The skills behind `gateway_list_skills` and `gateway_get_skill` are synced from GitHub into the gateway's skills table by the admin sync endpoint. Sources at `main`: the `skills` directory of `alliance-agent-config` (category platform), `deploy/skills/backoffice` in `alliance-mcp-gateway` (category backoffice, authored in the gateway repo and propagated to BackOfficePrototype by a separate publish step) and `deploy/skills/platform` in `alliance-mcp-gateway` (category platform, for example `estimation-model`). A skill's `updated_at` only advances when its content actually changes.

- **Platform Pulse notice.** When a sync finds skills whose content changed, the gateway posts a short HTML notice ("MCP Gateway - Skills Updated", listing the changed skills) to the Platform Pulse channel through the bot identity, never a user token. The sync response also returns a `changed` list alongside `synced`, `failed` and `errors`.
- **Signed skill-update directives.** An agent that already loaded a skill that has since changed is told through a signed directive on `result._meta.gateway_skill_update`, a sibling of the tool result `content` and never inside it, so a genuine gateway notice cannot be confused with instruction-like text a backend plants in its own output. The directive is an RS256 JWT of type `skill_update` (with `jti`, issuer, `aud` set to the caller identity, `iat`/`exp` and the changed skill name and version). The public keys are served at `/.well-known/skill-directive-jwks.json` so an MCP client adapter can verify a directive independently. Any `_meta.gateway_skill_update` a backend returns is stripped before the gateway's own value, if any, is applied. The feature is optional: with `SKILL_DIRECTIVE_SIGNING_KEY` unset no directive is attached and the JWKS route is not registered (there is no fallback to the old free-text banner). The wire schema and the validation algorithm for adapter authors are in `docs/skill-update-directive.md` in the repo.

## Operator Configuration

Environment variables read by the gateway's admin UI, the two backends that fall back to a shared credential, and one gateway server setting (repo `alliance-mcp-gateway`). Read from `admin-ui/src/` and `backends/github-mcp` / `backends/globus-mcp`.

| Variable | Component | Behaviour |
|---|---|---|
| `VITE_GOOGLE_CLIENT_ID` | admin UI | Enables the "Sign in with Google" fallback used when there is no IAP session (local development). Empty, or a value starting with `REPLACE_WITH`, counts as not configured. Sign-in is limited to the `allianceabroad.com` domain; there is no username/password path. |
| `VITE_GATEWAY_URL` | admin UI | Base URL for the UI's API calls. Defaults to the same origin. |
| `GITHUB_API_URL` | github-mcp | GitHub API base. Defaults to `https://api.github.com`; trailing slashes are stripped. |
| `GITHUB_SERVICE_TOKEN` | github-mcp | Shared token used only when the caller supplies none. A caller's own token always wins. |
| `GITHUB_DEFAULT_OWNER` | github-mcp | Owner assumed when a tool call omits one. Defaults to `Alliance-Strategies`. |
| `MCP_APP_KEY` | github-mcp | Optional app-level gate. When set, requests must send a matching `x-api-key` header; when empty, no gate applies. |
| `GLOBUS_SERVICE_API_KEY` | globus-mcp | Shared key used only when the caller supplies none. A caller's own key always wins. |
| `SKILL_DIRECTIVE_SIGNING_KEY` | gateway | Signing key for skill-update directives. Optional: when unset, no directive is attached to tool results and `/.well-known/skill-directive-jwks.json` is not registered. |

## Notes & troubleshooting

- 401 unauthorized: the `Authorization: Bearer` header is missing or the PAT is wrong.
- "requires authorisation": that backend is personal and needs you to connect it first — call `gateway_connect_url` rather than hunting for a manual OAuth link.
- A tool you expect is missing: your role does not grant it, or the backend is disabled — check the admin portal's Servers page.
- Some older `ecosystem_get_*` tools tied to the pre-2026-07-16 build-health/n8n-status/agent-observatory dashboards were retired when those moved to Platform Hub and now return 410 Gone — look for a `platformhub_*` equivalent instead of retrying the old name.
