@sema-agent/connector-gitea
v0.1.10
Published
Gitea connector: runs the pinned gitea-mcp release behind the sema-connector launcher, with its personal access token delivered as a file.
Readme
Gitea connector
@sema-agent/connector-gitea (CN-037) connects the engine to a Gitea instance with a personal access token. It runs
gitea-mcp, the Gitea project'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/gitea/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-gitea; 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).
Setting it up
export CONFIG_LOCAL_DIR=/absolute/path/to/the/engine/configuration
sema-connector-gitea install
sema-connector-gitea auth < gitea-auth.json # one line: {"host": "...", "token": "..."}
sema-connector-gitea mount
sema-connector-gitea statusThen restart the engine. Write gitea-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 gitea-mcp build, in the shape "Pinned upstream programs" in
ARCHITECTURE.md describes: the key gitea-mcp, version 1.8.1, four archives, one for each
platform, each from https://gitea.com/gitea/gitea-mcp/releases/download/v{version}/{file}, pinned with the sha256 the
release's checksums.txt gives for it and unpacked as its one entry gitea-mcp ({"entry": "gitea-mcp"}), which is
the program ({"binary": "gitea-mcp"}).
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 gitea-mcp entry in <configuration directory>/connectors/gitea/bin/
(the directory 0700, gitea-mcp 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 two string
members:
{"host": "https://gitea.example.com", "token": "<personal access token>"}hostis anhttpsURL, or anhttpURL on127.0.0.1orlocalhost(the host rule; see "Secrets", "host rule" inARCHITECTURE.md— the shared layer's one rule for a connector's host, the rule the launcher also holds a key set URL to, and the one the GitHub connector's optional GitHub Enterprise Server host shares, CN-045), without credentials, query or fragment. A path is kept (a Gitea served under/gitea); a trailing slash is dropped.tokenis printable ASCII without spaces.- 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 shared CLI kit's
readStdinJson).
The two values are stored through the secrets backend as the handles GITEA_HOST and GITEA_TOKEN: 0600 files in
<configuration directory>/connectors/gitea/ (0700). The host is not part of the catalog entry (an entry never
carries environment references), so it is kept as a handle next to the token, and it is not a secret: it is stored
with the metadata {"secret": false}, so the launcher delivers it like the token but does not withhold the many
Gitea answers that carry addresses on the host, and the status counts only the token (see "Secrets" in
ARCHITECTURE.md).
auth writes the two handles as one group under the lock of the connector's directory, connectors/gitea/.auth.lock,
which every launch of the connector also holds while it copies the handles: a connector started while auth runs
gets the old pair or the new pair, never the new host with the old token. While another process holds the lock,
auth waits up to 5 seconds and then refuses, changing nothing. Only the holder removes the lock; one left by a
process that died is removed by hand (nothing guesses that a lock is stale). Inside the group auth removes the
stored token first and only then writes the host and the token, so an auth that fails or ends halfway leaves no
token at all, never one auth's token next to another's host, and never a new token paired with an unwritten host.
A handle always has a value (a secret value is never the empty string, see "Secrets" in
ARCHITECTURE.md); taking the token away is remove, not a put of an empty string. With no
token the status is unauthorized, the launcher never starts the connector (see "Parked session" in
ARCHITECTURE.md), and a tool call through the launcher is refused with
connector.auth_required: auth again stores a fresh pair.
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 (the host alone is not a secret), 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, and a tool
call through the launcher is refused with connector.auth_required. Once auth has stored the token, the status lists
the tools gitea-mcp reports, so it needs gitea-mcp running: before install the connector cannot start it, and status
exits 1 with what the connector said, which names the subcommand to run.
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-gitea is the connector process the launcher starts. It starts the pinned
gitea-mcp as its child on the same standard input, output and error, so every MCP message passes between the
launcher and gitea-mcp untouched, and it ends as gitea-mcp 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 gitea-mcp.
The launcher names the connector's directory in CONNECTOR_DIR (see "Connector process environment" in
ARCHITECTURE.md). The connector refuses to start gitea-mcp (exit 1, with a line on stderr
naming what to do) on the shared start check ("Pinned upstream programs": without CONNECTOR_DIR, without both
handles, when the handle paths are not regular files in CONNECTOR_DIR, when bin/ in CONNECTOR_DIR does not hold
the pinned build), and when the stored host is outside the host rule or the stored token is empty.
gitea-mcp gets no arguments and the environment the launcher gave the connector with CONNECTOR_DIR and the two
handle paths replaced by what gitea-mcp reads:
GITEA_HOST: the stored host;GITEA_ACCESS_TOKEN_FILE: the path of the launcher's per-launch copy of the token;
and with HOME set to its own home in the connector's directory, <CONNECTOR_DIR>/home (CN-040; "Pinned upstream
programs" in ARCHITECTURE.md).
No other gitea-mcp setting reaches it (such as GITEA_ACCESS_TOKEN, MCP_MODE, GITEA_INSECURE, GITEA_TOOLS or a
proxy variable), since the launcher passes none of them to the connector.
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; the connector passes on only that copy's path, and gitea-mcp reads the file itself
and sends the token to the stored host in its Authorization header. The connector process never reads the token.
The token is never in an argument, in the value of an environment variable, in the catalog entry or in the status;
the launcher withholds any reply of gitea-mcp that carries it and replaces it in gitea-mcp's stderr.
Outbound connections
gitea-mcp is given one address, the stored host, and no proxy setting. The standalone form has no egress proxy (that
is M3), so whether it connects anywhere else is gitea-mcp's own behaviour: the negative-control gate
(run-gitea-negctl-test) runs the pinned build under a sandbox that refuses every other connection, and fails on any
attempt it reports; when the host stops delivering the reports of an executable the gate cannot see those attempts
(KL-28; see KNOWN-LIMITS.md, shipped with this package and, in the repository, docs/KNOWN-LIMITS.md).
Known limits
- gitea-mcp keeps its own log in its home,
<CONNECTOR_DIR>/home/.gitea-mcp/gitea-mcp.log(CN-040: in the configuration directory, never in the user's home), outside the launcher's stderr and its replacement of secret values; it holds gitea-mcp's tool errors, which can name URLs and user names of the Gitea server, and nothing removes it (KL-25). - Redirects: gitea-mcp follows up to ten redirects of a
GET, to wherever the Gitea server sends it. - Check, then start: as for every pinned upstream program (see "Pinned upstream programs" in
ARCHITECTURE.md).
