The curated @submilli/github Package covers what most agents need from
GitHub (repositories, issues, pull requests, releases, and search). Each
operation reports the repository it acts on, so a rule can allow
github.com/issues.create for one repository and no other. When an
agent needs something the Package doesn’t have, such as sub-issues,
issue types, or a Copilot review, GitHub’s hosted MCP server has it.
Declared in a Blueprint, that server becomes a Package the agent imports.
The trade is in the rules, because a rule over an MCP server sees which
tool is called but not its arguments. GitHub is also the common case of
a service that won’t take a login from an application it hasn’t heard
of, so this is where you meet an OAuth provider.
In this tutorial we will give an agent GitHub through its hosted MCP server, on your machine and then on a server. You need the CLI and a GitHub account.
Declare it
Section titled “Declare it”submilli blueprint init trackersubmilli blueprint add-mcp github https://api.githubcopilot.com/mcp/✓ created blueprint.yaml (name: tracker)✓ added mcp server 'github' (oauth) to blueprint.yaml Gated by `mcp.github` (deny by default) — set its action to `allow` (optionally `filter: tool == "..."`) to use it. Authenticate locally: submilli mcp authenticate github --blueprint blueprint.yaml After applying to a server: submilli server mcp authenticate tracker githubmcp: github: url: https://api.githubcopilot.com/mcp/ auth: type: oauth2Notice (oauth). add-mcp asked the server how it authenticates and
wrote the answer. Until someone logs in, the Blueprint is PENDING:
submilli mcp auth-status --blueprint blueprint.yamltracker: PENDING github oauth — NOT AUTHENTICATEDRegister an application with GitHub
Section titled “Register an application with GitHub”An OAuth login names the application that is asking. Linear’s server lets Submilli register one on the spot. GitHub’s doesn’t, so you register a GitHub OAuth App yourself and give Submilli its id and secret as a provider. It has to be an OAuth App, not a GitHub App.
On GitHub, open Settings, Developer settings, OAuth Apps, and New OAuth App, or go straight to github.com/settings/applications/new. To own the app as an organization, start from the organization’s settings. Fill in:
- Application name: what you will recognize on the approval page,
such as
Submilli tracker. - Homepage URL: any page of yours. GitHub only displays it.
- Authorization callback URL:
http://127.0.0.1:8765/callback, the address the login comes back to on your machine. It must match character for character.
Register it. GitHub shows the app’s Client ID. Click Generate a new client secret and copy the secret, which GitHub shows only once.
Put the secret in the local store, and configure the provider with a reference to it, so the secret stays out of the provider file:
submilli secret put github_client_secretsubmilli mcp provider add --match github.com --client-id Ov23li… \ --client-secret '${secrets.github_client_secret}' --scope repoValue for 'github_client_secret': [hidden]Stored secret 'github_client_secret'✓ configured OAuth provider for github.comproviders:- match: github.com client_id: Ov23li… client_secret: ${secrets.github_client_secret} scopes: - repoThe provider is matched on the host of the service’s login, github.com,
which is not the host of the MCP server. repo is the scope the issue
tools need.
Log in
Section titled “Log in”submilli mcp authenticate github --blueprint blueprint.yamlOpen this URL in your browser to authorize:
https://github.com/login/oauth/authorize?response_type=code&client_id=Ov23li…&redirect_uri=http%3A%2F%2F127.0.0.1%3A8765%2Fcallback&state=…&code_challenge=…&code_challenge_method=S256&scope=repo
Waiting for the redirect on http://127.0.0.1:8765/callback …✓ authenticated 'github' on blueprint 'tracker' — blueprint 'tracker' is ACTIVEOpen the address, approve the application, and the command finishes with
the Blueprint ACTIVE. The login lands in the local secret store, and one
login serves each program run under the Blueprint. If GitHub answers
“The redirect_uri is not associated with this application”, the app’s
callback URL isn’t the one above. Fix it in the app’s settings and open
the address again.
Allow a tool and call it
Section titled “Allow a tool and call it”The server’s 46 tools are now a Package, @mcp/github, and submilli docs @mcp/github --blueprint blueprint.yaml lists them as declarations. One
capability, mcp.github, covers them all, and a rule picks tools by
name. Replace the deny rule add-mcp wrote with one that allows listing
issues:
submilli blueprint capability remove mcp.githubsubmilli blueprint capability add mcp.github --filter 'tool == "list_issues"'✓ removed 1 rule(s) for 'mcp.github' from caller 'main' in blueprint.yaml✓ added allow mcp.github (filter: tool == "list_issues") to caller 'main' in blueprint.yamlA program imports the server like any Package:
import github from "@mcp/github";
function main(): string { const page = github.list_issues({ owner: "rust-lang", repo: "rust", state: "OPEN", perPage: 5 }); const lines: string[] = []; for (const issue of page.issues) { lines.push(`#${issue.number} ${issue.title}`); } return lines.join("\n");}submilli run --blueprint blueprint.yaml issues.tswarning: @mcp/github: 29 tool(s) return unknown: result schemas are unavailable or unrepresentable; consult package docs for signatures#163678 Tracking Issue for MCP 1016: -Zasync-panic#163677 Rust 1.99 regression: fat LTO leaves short constant string comparisons as bcmp calls#163676 `dec2flt::decimal_seq` overflows an `i32` subtraction on a large valid decimal when overflow checks are enabled#163675 std::io::Read incorrectly has no marked required methods#163674 `str::parse::()` returns 1.0 for an overflowing decimal with a long fractional partThe warning is about types. GitHub’s server publishes no result schemas,
so most tools return unknown and a program casts the result to a type
it declares. For 17 of them, list_issues among them, Submilli carries
the result type itself, so issue.number needs no cast. Any tool the
rule doesn’t name is refused before a request leaves. Add an
MCP server covers choosing
tools and what a rule over them can and can’t see.
The same Blueprint on a server
Section titled “The same Blueprint on a server”Registered on a server, the Blueprint gives each session the same
Package, but the login and the provider are now the server’s. The
provider goes under mcp_oauth in the server’s config file, with the
client secret in the server’s store:
submilli server secret put github_client_secretmcp_oauth: providers: - match: github.com client_id: Ov23li… client_secret: ${secrets.github_client_secret} scopes: - repoRegister the Blueprint and look at its logins:
submilli server blueprint apply blueprint.yamlsubmilli server mcp auth-status trackerAdded blueprint 'tracker'tracker: PENDING github oauth — NOT AUTHENTICATEDThe local login doesn’t carry over. Until someone logs in on the server,
a program that imports the Package doesn’t compile, and run-code says
which login is missing:
submilli server run-code issues.ts --blueprint trackerwarning: @mcp/github: server unavailable: not authenticated — run `submilli server mcp authenticate`error: MCP server `github` is unavailable — `@mcp/github` is absent from the discovered catalog; check the blueprint's `mcp:` block and discovery warningsLog in on the server. The command runs on your machine, so the browser it needs is yours, and the login lands in the server’s secret store:
submilli server mcp authenticate tracker githubOpen this URL in your browser to authorize:
https://github.com/login/oauth/authorize?response_type=code&client_id=Ov23li…&redirect_uri=http%3A%2F%2F127.0.0.1%3A8765%2Fcallback&state=…&code_challenge=…&code_challenge_method=S256&scope=repo
Waiting for the redirect on http://127.0.0.1:8765/callback …✓ authenticated 'github' on blueprint 'tracker' — blueprint 'tracker' is ACTIVEsubmilli server mcp auth-status trackersubmilli server run-code issues.ts --blueprint trackertracker: ACTIVE github oauth — authenticatedwarning: @mcp/github: 29 tool(s) return unknown: result schemas are unavailable or unrepresentable; consult package docs for signatures#163678 Tracking Issue for MCP 1016: -Zasync-panic#163677 Rust 1.99 regression: fat LTO leaves short constant string comparisons as bcmp calls#163676 `dec2flt::decimal_seq` overflows an `i32` subtraction on a large valid decimal when overflow checks are enabled#163675 std::io::Read incorrectly has no marked required methods#163674 `str::parse::()` returns 1.0 for an overflowing decimal with a long fractional partNotice what the login is. It is one GitHub account, used by every session of the Blueprint, for every user of your application. Log in as an account that may do what you are willing to let any user’s agent do, and keep the rule as narrow as the task needs.
Where a per-user token belongs
Section titled “Where a per-user token belongs”One login means every user’s agent acts as that account. When users must act on GitHub as themselves, there is no login to make. Your application holds each user’s own token, and the Blueprint declares it as a secret the harness supplies when it opens the session, written straight into the server’s header:
submilli blueprint secret add GITHUB_TOKEN --harness --requiredsubmilli blueprint add-mcp github https://api.githubcopilot.com/mcp/ \ --header 'Authorization: Bearer ${secrets.GITHUB_TOKEN}'✓ declared secret 'GITHUB_TOKEN' (harness, required: true) in blueprint.yaml✓ added mcp server 'github' (static header auth) to blueprint.yaml Gated by `mcp.github` (deny by default) — set its action to `allow` (optionally `filter: tool == "..."`) to use it.secrets: GITHUB_TOKEN: harness: required: truemcp: github: url: https://api.githubcopilot.com/mcp/ headers: Authorization: Bearer ${secrets.GITHUB_TOKEN}There is no provider and no authenticate step. The server sends whatever
token the session was opened with, and a session opened without one is refused.
Connect a harness
shows how each harness supplies it. The rules are the same either way.
What changes is whose account the call is made as.
You have given an agent GitHub through its hosted MCP server, declared in a Blueprint and logged in once through an application you registered. Its read tools are allowed and its write tools refused until you say otherwise, on your machine and on a server. From here, go to the part on Blueprints for everything else a Blueprint can grant.