@sema-agent/connector-github
v0.2.6
Published
GitHub connector: runs the pinned github-mcp-server release behind the sema-connector launcher, with its personal access token handed to it in its environment.
Readme
GitHub connector
@sema-agent/connector-github (CN-044) connects the engine to github.com with a personal access token. It runs
github-mcp-server, GitHub's own MCP server, unmodified behind the
sema-connector launcher, at the version and sha256 its binaries.lock.json pins. The launcher's stages (scope,
normalize, status, trace, the withholding of secret values) apply to it as to every connector; see
ARCHITECTURE.md. A hand-written connectors/github/tools.json narrows which of its tools a
caller may use (the standalone per-tool policy, CN-054, ARCHITECTURE.md, "Stages on the launcher hop"); absent, every
tool is granted. Its bin is sema-connector-github; its subcommands are the shared CLI kit's
(CN-039) around the shared install of a pinned upstream program (CN-044) and this package's auth, and read the
configuration directory from CONFIG_LOCAL_DIR like every connector's (INTEGRATION-SERVER.md).
This release talks to github.com by default, or to a GitHub Enterprise Server address auth is given (CN-045), with a
personal access token: ghe.com and a login without a token are not offered.
Setting it up
export CONFIG_LOCAL_DIR=/absolute/path/to/the/engine/configuration
sema-connector-github install
sema-connector-github auth < github-auth.json # one line: {"token": "..."} or {"host": "...", "token": "..."}
sema-connector-github mount
sema-connector-github statusThen restart the engine. Write github-auth.json with an editor as a file only you can read, and delete it after
auth: a token on a command line (an echo or printf argument) is visible to other processes.
The pinned build
binaries.lock.json is the one place that names the github-mcp-server build, in the shape "Pinned upstream programs" in
ARCHITECTURE.md describes: the key github-mcp-server, version 2.0.2, four archives, one for each
platform (github-mcp-server_Darwin_arm64.tar.gz, github-mcp-server_Darwin_x86_64.tar.gz,
github-mcp-server_Linux_arm64.tar.gz, github-mcp-server_Linux_x86_64.tar.gz), each from
https://github.com/github/github-mcp-server/releases/download/v{version}/{file}, pinned with the sha256 the release's
github-mcp-server_2.0.2_checksums.txt gives for it and unpacked as its one entry github-mcp-server
({"entry": "github-mcp-server"}), which is the program ({"binary": "github-mcp-server"}).
install
install is the shared install of a pinned upstream program (see "Pinned upstream programs" in
ARCHITECTURE.md): it fetches this platform's archive, compares its sha256 with the lock before
anything is written, and keeps the archive and its github-mcp-server entry in
<configuration directory>/connectors/github/bin/ (the directory 0700, github-mcp-server 0755). When that directory
already holds the pinned build, install changes nothing.
auth
auth reads exactly one line from standard input (never from an argument), a JSON object with exactly the key
token and optionally host (the shared CLI kit's readStdinJson, CN-045):
{"token": "<personal access token>"}or, for a GitHub Enterprise Server:
{"host": "<GitHub Enterprise Server URL>", "token": "<personal access token>"}tokenis printable ASCII without spaces (github-mcp-server sends it in an HTTP header).host, when given, is held to the host rule shared with the Gitea connector (see "Secrets", "host rule" inARCHITECTURE.md): anhttpsURL, or anhttpURL on127.0.0.1orlocalhost, without credentials, query or fragment. A path is kept; a trailing slash is dropped.- Any other key, a key given twice, or a value that is not a string is refused, and a refusal never repeats what was read.
The token is stored through the secrets backend as the handle GITHUB_TOKEN: a 0600 file in
<configuration directory>/connectors/github/ (0700), a secret (no metadata). A host is stored alongside it as the
handle GITHUB_HOST, the same file shape but not a secret (metadata {"secret": false}), as the Gitea connector's
host is; without a host the handle is removed (so a later auth without one clears a GitHub Enterprise Server
address set earlier) and github-mcp-server is left to its default, github.com. Inside the group auth removes the
stored token first, writes the host (or removes it, with none given), and only then writes the new token, the shape
the Gitea connector's auth writes its own pair in: an auth that fails or ends halfway leaves no token at all,
never a new host paired with the old token or a new token paired with the wrong host. The write holds the lock of the
connector's directory, connectors/github/.auth.lock, which every launch of the connector also holds while it copies
the handles, and puts each file in place only once it is written whole: a connector started while auth runs gets
the old handles or the new ones. While another process holds the lock, auth waits up to 5 seconds and then refuses,
changing nothing (see "Secrets" in ARCHITECTURE.md).
mount and status
mount writes the connector's standalone catalog entry through the shared mount function, and status prints the
status the launcher serves, as for every connector. Before auth there is no token, so the launcher does not start the
connector and answers itself (see "Parked session" in ARCHITECTURE.md): status prints
{"state":"unauthorized","tools":[]} and exits 0, nothing is contacted, and a tool call through the launcher is refused
with connector.auth_required. Once auth has stored the token, the status lists the tools github-mcp-server reports,
so it needs github-mcp-server running: before install the connector cannot start it, and status exits 1 with what
the connector said, which names the subcommand to run. Like every start, a status with a classic token then contacts
the GitHub API (see "Outbound connections").
unmount removes that entry again and list prints the connectors mounted in the configuration directory, through the
shared unmount and listMounted (@sema-agent/connectors 0.9.0, CN-065); an entry under this connector's name that
is not exactly what mount writes is refused and left alone, and never listed (KNOWN-LIMITS.md, KL-32).
What runs
With no arguments, sema-connector-github is the connector process the launcher starts. It starts the pinned
github-mcp-server with the single argument stdio as its child on the same standard input, output and error, so
every MCP message passes between the launcher and github-mcp-server untouched, and it ends as github-mcp-server ends:
with its exit code, or by the signal that ended it (the launcher then reports the connector's outcome as for any
connector, exit 5 when abnormal). The stop signals the connector receives are passed on to github-mcp-server.
The launcher names the connector's directory in CONNECTOR_DIR (see "Connector process environment" in
ARCHITECTURE.md). The connector refuses to start github-mcp-server (exit 1, with a line on
stderr naming what to do) on the shared start check ("Pinned upstream programs": without CONNECTOR_DIR, without the
GITHUB_TOKEN handle, when GITHUB_TOKEN_FILE is not a regular file in CONNECTOR_DIR, when bin/ in
CONNECTOR_DIR does not hold the pinned build), and when the stored token is empty or breaks the token rule. It never
starts github-mcp-server without a token: github-mcp-server would then begin a login of its own instead. Without the
handle the launcher does not start the connector in the first place (see "mount and status").
github-mcp-server gets the argument stdio and the environment the launcher gave the connector with CONNECTOR_DIR,
GITHUB_TOKEN_FILE and (when stored) GITHUB_HOST_FILE replaced by GITHUB_PERSONAL_ACCESS_TOKEN (the stored token)
and GITHUB_HOST (the stored host, read and held to the host rule again, CN-045); without a stored host
GITHUB_HOST is not set at all, and github-mcp-server picks github.com; HOME is its own home in the connector's
directory, <CONNECTOR_DIR>/home (CN-040; "Pinned upstream programs" in
ARCHITECTURE.md). No other github-mcp-server setting reaches
it (such as GITHUB_TOOLSETS, GITHUB_TOOLS, GITHUB_READ_ONLY, GITHUB_LOCKDOWN_MODE, GITHUB_INSIDERS,
GITHUB_OAUTH_CALLBACK_PORT, a GITHUB_MCP_* override or a proxy variable), since the launcher passes none of them
to the connector, and the connector passes no option (such as --toolsets, --read-only, --log-file or
--gh-host): which tools a caller sees is the launcher's scope stage, and github-mcp-server's log goes to its
standard error, which the launcher passes on with secret values replaced.
Where the token goes
The token is a 0600 file in the configuration directory. The launcher reads it before the connector starts and hands
the connector a per-launch copy. github-mcp-server takes a personal access token from its environment only (it has no
file input), so the connector reads that copy once, drops a line break at its end, holds it to the token rule and
starts github-mcp-server with it in GITHUB_PERSONAL_ACCESS_TOKEN; github-mcp-server sends it to the GitHub API in its
Authorization header. The token is never in an argument, in another variable, in the catalog entry or in the status;
the launcher withholds any reply of github-mcp-server that carries it and replaces it in github-mcp-server's stderr.
Unlike the Gitea connector's file, a variable of a running process can be read by other processes of the same user
(KL-11; see KNOWN-LIMITS.md, shipped with this package and, in the repository, docs/KNOWN-LIMITS.md).
Outbound connections
Given no stored host, github-mcp-server is given no address and no proxy setting either, so it talks to the GitHub
API at its default address, api.github.com. The standalone form has no egress proxy (that is M3), so whether it
connects anywhere else is github-mcp-server's own behaviour: the negative-control gate (run-github-negctl-test) runs
the pinned build under a sandbox that refuses every connection and reads back what was refused, and runs it once more
through a loopback proxy of its own that names every host it would connect to. Measured for 2.0.2: with a token that
looks like a classic personal access token (it starts with ghp_), github-mcp-server sends one HEAD
https://api.github.com/ with the token while it starts, before any tool is called, to read the token's scopes (it
then hides the tools the token cannot use; when the request fails it logs a warning and lists every tool); with a
token that starts like a fine-grained one (github_pat_), or with neither prefix, it connects nowhere until a tool is
called. A fine-grained token is therefore the one to prefer when a start that contacts GitHub is unwanted. Other kinds
of token (such as gho_) were not measured. With a stored host (CN-045, a GitHub Enterprise Server), github-mcp-server
sends the same startup HEAD <host>/api/v3/ with the token, and separately reaches raw.<host>, the upstream's own
raw-content subdomain probe, without the token; measured through the connector's own auth and serve against a fake
host on loopback, nothing else is reached. The gate fails on any other destination, also one github-mcp-server only
names on its standard error (KL-12; see KNOWN-LIMITS.md, shipped with this package and, in the repository,
docs/KNOWN-LIMITS.md).
Known limits
- The token is in github-mcp-server's environment while it runs (KL-11).
- Every start contacts the GitHub API with a classic token,
statusincluded (KL-12). ghe.comand a login without a token are not offered in this release; a GitHub Enterprise Server host is (CN-045).- Description overrides: github-mcp-server reads tool description overrides from a
github-mcp-server-config.jsonnext to its binary, in the bin directory;installnever writes one, and someone who can write the configuration directory can add one, as for "Check, then start". - Check, then start: as for every pinned upstream program (see "Pinned upstream programs" in
ARCHITECTURE.md).
