Bosphorus

← Knowledge base

Your own GitHub key: private repositories through one tool server

Register GitHub's hosted MCP server once with no key of its own, and let every person reach their own private repositories with their own token.

ToolsIdentityGitHubChecked against the product on

Animated diagram: a request carrying one person's own variable through the gateway to a tool serverMehmetengineeringAyşeengineeringZeynepoperationsaimochat + agentsGateway/api/mcp/githubkeyteamgrantvarsno per-person valueread for this request onlyMCP servergithubGitHub APIsees one identityVAULTOpenBao or sealed · per person, serverno values storedAUDIT LOG
  1. Step 01

    Before: one key for everyone

    • The gateway added the single credential an administrator stored on the server to every call.
    • Ayşe, Mehmet and Zeynep all arrived at the far end as the same identity.
    • Rate limits, permissions and audit trails over there could not tell your people apart.
  2. Step 02

    An administrator declares what the server accepts

    • On the console's /mcp page, Variables tab: a name, whether it is secret, whether it is required, and how it travels.
    • Names only. An administrator never types anyone's value here.
    • A name that was not declared is never stored and never sent. The fields follow the environment-variable shape of the MCP Registry's server.json.
    On the wireGITHUB_API_KEY secret · required header Authorization: Bearer ${value}
  3. Step 03

    Ayşe asks. The gateway stops.

    • Key, team scope and her grant all pass. Then the required variable turns out to be missing.
    • No request leaves for the tool server. Nothing reaches GitHub.
    • The tool reply links to her personal page, and a client that supports MCP link prompts shows it as a prompt. The value is never asked for in chat: the MCP specification forbids collecting secrets that way.
    On the wireisError: true "This tool needs your own GITHUB_API_KEY … Set it once at /vault?server=github"
  4. Step 04

    Ayşe sets it once, on her own page

    • My variables (/vault), behind the same sign-in as the console. She sees only her own rows.
    • Where the installation runs OpenBao, it goes into her own folder, kv/users/<her email>/mcp/github, which she can open there herself. Otherwise it is stored as ciphertext bound to (person, server, name), so a row cannot be moved to another person.
    • Never shown here again. The page shows a short fingerprint instead; administrators and the model never see the value. Only Ayşe can read it back, and only in her own OpenBao folder.
    On the wirekv/users/ayse.demir@corp.example/mcp/github GITHUB_API_KEY = ••••••••
  5. Step 05

    Now the call is hers

    • The gateway reads her value for this one request only, from her OpenBao folder or by decrypting her sealed row, and renders the administrator's template into a header.
    • Never cached and never placed in the model's context.
    • The audit row records the variable's name. If the tool echoes the value, the stored copy of the response is masked.
    On the wirerequestContext.userVars = { injected: ["GITHUB_API_KEY"] }
  6. Step 06

    Everyone on their own key

    • Ayşe and Mehmet reach GitHub as themselves. Zeynep has set nothing, so she gets the link to her page instead.
    • An administrator sees how many people hold a value. Never which value.
    • Offboarding is one delete on the Variables tab. An administrator can delete a person's values but never read them.
    On the wiregithub · 2 of 3 people have set GITHUB_API_KEY
  7. Step 07

    Three ways in, one vault

    • Header template, live: for third-party servers you do not own. Nothing changes on their side.
    • Sealed bundle, live: for servers you run yourself. One encrypted header, bound to that server's name and valid for 60 seconds.
    • Environment bridge, live: for tools that only read environment variables. One child process per person, its environment filled from the vault.
  8. Step 08

    What it protects, and what it does not

    • What the vault protects today, and where the remaining trust sits.

    Never sees a value

    The chat, the model's context, the audit log, the log stream, metrics, a console administrator, another person, and any tool server the variable was not declared for.

    Could still read a value

    The platform itself: it reads values unattended, so a scheduled 03:00 agent run still works. With OpenBao it signs in as its own service account, holds no stored credential and may read only people's mcp/ folders, while OpenBao's own administrators can reach every folder. Without it, whoever holds both the vault key and the database. Backup archives carry the vault, so guard them like it.

Header delivery, sealed bundles and the environment bridge are live, and each person's values can live in their own OpenBao folder.

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

Before you start

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):

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:

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

The walkthrough at the top of this article, and the tool catalogue it comes from, show the same flow step by step.