This article registers GitHub’s hosted MCP server once, with no key of its own, and lets every person reach their own private repositories with their own GitHub token. The same pattern fits any service where each colleague has their own credential: a code host, a ticketing system, a SaaS API.
It was checked end to end on the running product on 25 September 2026. A person listed and read files in their own private repositories from the chat workspace, and the audit trail recorded the variable’s name on every call and the token nowhere.
What you get
- One entry in the tool registry, GitHub, available to everyone in its team scope.
- Each person’s calls carry that person’s token. Alice sees what Alice’s token can see; Bob sees what Bob’s can. Nobody borrows anybody’s access.
- The token is typed once, on the console’s personal My variables page, never into a chat. It is stored encrypted, sent only on that person’s calls to that one server, and never shown to the model, to administrators or in the audit trail, which records the variable’s name and nothing else.
Before you start
- You need administrator access to the console to register the server and declare the variable. Everyone else needs only their normal sign-in.
- Console sign-in must be enabled: My variables is a personal page, and it stays closed on a deployment where nobody signs in.
- People use the tool from the chat workspace (aimo), from the editor extension or from their own agents. All three reach it through the same governed relay.
Step 1: register the server (administrator)
On the console’s /mcp page, open Register a server and enter:
| Field | Value |
|---|---|
| Name | GitHub |
| URL (streamable HTTP endpoint) | https://api.githubcopilot.com/mcp/x/repos/readonly |
| Description | Your own GitHub repositories, read-only |
| Auth | none, with Secret left empty |
| Identity | require user (signed assertion) ticked |
Then press Register & sync.
The URL selects GitHub’s repositories tools in read-only mode. The same host offers other choices:
| URL ending | What the model gets |
|---|---|
…/mcp/x/repos/readonly |
repositories only, read-only (the lean choice used here) |
…/mcp/readonly |
GitHub’s default toolsets (profile, repositories, issues, pull requests, users), read-only |
…/mcp/x/all/readonly |
every toolset, read-only; large, and every enabled tool costs prompt tokens on every turn |
Leave off /readonly only if people should be able to write with their own tokens.
Expect this first sync to fail. GitHub refuses even the MCP handshake without a
token, and this server has none yet, so the row shows error with:
mcp http 401 on initialize — this server has no shared key: declare a per-person variable on the Variables tab, set your own value on /vault, then Sync tools
That is the expected state at this point.
Step 2: declare the variable (administrator)
On the Variables tab of /mcp, declare:
| Field | Value |
|---|---|
| Server | GitHub (github) |
| Variable | GITHUB_API_KEY |
| Delivery | header (third-party server) |
| Header | Authorization |
| Template | Bearer ${value} |
| Required | ticked |
| Secret | ticked |
| Description | Your GitHub personal access token (read-only is enough) |
In one line, this is what the gateway will do on each person’s calls:
GITHUB_API_KEY -> Authorization: Bearer ${value}
Required means that a person who has not set a token gets a clear message with a link to their page instead of a failed call, and nothing is sent to GitHub.
Step 3: create a GitHub token (everyone)
In GitHub, go to Settings → Developer settings → Personal access tokens → Fine-grained tokens → Generate new token (GitHub’s own instructions):
- Resource owner: yourself, or your organisation.
- Expiration: as short as is practical, for example 90 days.
- Repository access: all repositories, or only the ones you pick.
- Repository permissions: Contents: Read-only. Metadata: Read-only is added automatically.
A fine-grained token belongs to one resource owner. To reach both your own private
repositories and an organisation’s, create the token for the owner you need, or use a
classic token with the repo scope. A classic token can also write; the /readonly
server URL still blocks writes, but a fine-grained read-only token is the safer choice.
An organisation may require its administrators to approve fine-grained tokens before
they work.
Step 4: save it on My variables (everyone)
Sign in to the console and open My variables (/vault). Under GitHub, paste
the token into GITHUB_API_KEY and press Save. The page confirms with a short
fingerprint. The value is never shown again, not even to you.
If you ask the tool for something before you have saved a token, its answer contains a direct link to this page.
Step 5: sync the tool list (administrator)
Back on /mcp, press Sync tools on the GitHub row, once you have saved your own
token in step 4. Discovery now runs with your own token: the status turns ok and
the tool list is cached for the editor extension and for agents. The cached list is
what your token can see; every other person’s calls still use their own token. The
chat workspace does not depend on this step, because it lists tools live as each
person.
Step 6: use it
In the chat workspace, enable the GitHub tool in a chat and ask, for example:
- List the private repositories of GitHub user followed by your GitHub username.
- Show me the README of one of your private repositories.
- What changed in the last five commits on main in that repository?
In aimo for VS Code the model finds GitHub’s tools on
demand. Pin the github server with AIMO: Choose Tool Servers and they load
directly, named mcp__github__<tool>.
When something is missing
| Situation | What happens |
|---|---|
| You have not saved a token | The tool answers This tool needs your own GITHUB_API_KEY … Set it once at …/vault?server=github, and nothing reaches GitHub. A client that supports MCP link prompts shows the link as a prompt instead. |
| Your token expired or was revoked | GitHub answers Bad credentials. Create a new token and replace the value on My variables. |
| A repository “does not exist” | Your token was not granted that repository, or your organisation has not approved the token yet. |
| The editor extension shows no GitHub tools | The cached list was never discovered. An administrator saves their own token and presses Sync tools (step 5). |
| Someone leaves | They, or an administrator on the Variables tab, delete their values. Administrators can delete a person’s values but never read them. Revoke the token on GitHub as well. |
Why one shared server is safe here
- Per person, per server. A value belongs to one person and one server. Where the
installation runs OpenBao it sits in that person’s own folder,
kv/users/<email>/mcp/github; otherwise it is sealed, so a copy moved onto another person’s record does not decrypt, and that call is treated as not set. - Nothing in the chat. The token never passes through the model, so it cannot be
repeated, summarised or leaked into a transcript. If a tool result ever echoes it,
the stored audit copy shows
[uservar:GITHUB_API_KEY]instead. - GitHub decides what is visible. The platform adds no access of its own: a call reaches exactly what the person’s token can reach, and read-only is enforced twice, by the server URL and by the token’s permissions.
- Signed-in people only. With require user ticked, a shared service key with no person behind it is refused before anything is sent.
The walkthrough at the top of this article, and the tool catalogue it comes from, show the same flow step by step.