Add MCP registry

Seto Elkahfi committed Jul 22, 2026 at 22:59 UTC 674b836af43db766b20c04deeec634dcace31825
4 files changed +181 -1
.agents/AGENTS.md
+6 -1
@@ -180,7 +180,12 @@ feeds results back. Neither the loop nor ACP/TUI surfaces depend on a concrete b
180 corresponding baked-in one. Extra servers live in `mcp.toml` (global
181 `$SIGIT_CONFIG_DIR/mcp.toml` and project-local `.sigit/mcp.toml`). The stdio path is covered by
182 `tests/mcp_stdio.rs`, driven by the test-only `src/bin/mcp_stdio_stub.rs` helper binary
183 - (excluded from the published crate via `exclude` in `Cargo.toml`).
183 + (excluded from the published crate via `exclude` in `Cargo.toml`). The baked-in official
184 + server is *also* listed in the public MCP Registry as `si.sigit/sigit` — a **remote**
185 + Streamable-HTTP listing (`server.json` at the repo root, published by
186 + `release-mcp-registry.yml`). Because it's a remote server, the registry's URL-match rule
187 + forces a domain namespace (`si.sigit` ↔ `sigit.si`) verified by a DNS TXT record, not the
188 + GitHub-OIDC scheme `smbcloud-cli` uses for its package listing.
189 - **`src/permissions.rs`** — tool permission policy. Every tool call passes through
190 `decision_for` before executing: read-only tools always run; mutating tools (and all
191 `mcp__*`/unknown tools) are governed by, in order: per-session plan mode (`/plan` — deny all
.github/workflows/release-mcp-registry.yml new
+79
@@ -0,0 +1,79 @@
1 +name: MCP Registry Release
2 +
3 +on:
4 + workflow_dispatch:
5 + inputs:
6 + version:
7 + description: "Override the version in server.json (e.g. 0.2.0); leave blank to publish as-is"
8 + required: false
9 +
10 +# A remote server proves namespace ownership through domain (DNS) verification,
11 +# not GitHub OIDC: the registry checks a v=MCPv1 TXT record on sigit.si against
12 +# a request signed with the matching Ed25519 private key. See the MCP Registry
13 +# doc in the sigit-si repo for the one-time key/DNS setup.
14 +permissions:
15 + contents: read
16 +
17 +jobs:
18 + publish:
19 + name: Publish server.json to the MCP Registry
20 + runs-on: ubuntu-latest
21 + steps:
22 + - name: Checkout
23 + uses: actions/checkout@v6
24 +
25 + - name: Optionally override the version in server.json
26 + shell: bash
27 + run: |
28 + version="${{ github.event.inputs.version }}"
29 + if [ -n "${version}" ]; then
30 + jq --arg v "${version}" '.version = $v' server.json > server.tmp
31 + mv server.tmp server.json
32 + fi
33 + cat server.json
34 +
35 + - name: Check the remote URL matches the namespace domain
36 + shell: bash
37 + run: |
38 + # The registry rejects a remote server whose URL isn't under the
39 + # namespace's domain. si.sigit -> sigit.si, so every listed remote
40 + # must live on sigit.si (or a subdomain).
41 + server_name="$(jq -r '.name' server.json)"
42 + domain="$(echo "${server_name%%/*}" | awk -F. '{ for (i=NF; i>=1; i--) printf "%s%s", $i, (i>1 ? "." : "") }')"
43 + echo "Server: ${server_name} Domain: ${domain}"
44 +
45 + bad="$(jq -r --arg d "${domain}" '
46 + .remotes // []
47 + | map(.url | sub("^[a-z]+://"; "") | sub("/.*$"; ""))
48 + | map(select(. != $d and (endswith("." + $d) | not)))
49 + | .[]' server.json)"
50 + if [ -n "${bad}" ]; then
51 + echo "Remote URL host(s) not under ${domain}: ${bad}" >&2
52 + exit 1
53 + fi
54 +
55 + - name: Install mcp-publisher
56 + shell: bash
57 + run: |
58 + curl -L "https://github.com/modelcontextprotocol/registry/releases/latest/download/mcp-publisher_$(uname -s | tr '[:upper:]' '[:lower:]')_$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/').tar.gz" | tar xz mcp-publisher
59 +
60 + - name: Authenticate to the MCP Registry
61 + shell: bash
62 + env:
63 + MCP_REGISTRY_DNS_PRIVATE_KEY: ${{ secrets.MCP_REGISTRY_DNS_PRIVATE_KEY }}
64 + run: |
65 + if [ -z "${MCP_REGISTRY_DNS_PRIVATE_KEY}" ]; then
66 + echo "MCP_REGISTRY_DNS_PRIVATE_KEY secret is not set. See the MCP Registry doc in the sigit-si repo." >&2
67 + exit 1
68 + fi
69 + ./mcp-publisher login dns --domain=sigit.si --private-key="${MCP_REGISTRY_DNS_PRIVATE_KEY}"
70 +
71 + - name: Publish
72 + shell: bash
73 + run: ./mcp-publisher publish
74 +
75 + - name: Verify the server is listed
76 + shell: bash
77 + run: |
78 + server_name="$(jq -r '.name' server.json)"
79 + curl -fsSL "https://registry.modelcontextprotocol.io/v0.1/servers?search=${server_name}" | jq .
docs/mcp.md new
+68
@@ -0,0 +1,68 @@
1 +# The official siGit MCP server
2 +
3 +siGit Code Cloud runs a hosted [Model Context Protocol](https://modelcontextprotocol.io)
4 +server at **`https://sigit.si/api/v1/mcp`**. It's a stateless **Streamable-HTTP**
5 +server (JSON-RPC 2.0 over a single POST endpoint) that exposes your
6 +siGit-hosted git repositories — plus web search — as MCP tools, so any MCP
7 +client can browse repos, read files, search code, and open or comment on pull
8 +requests and issues on your behalf.
9 +
10 +This page documents the server from a **client's** perspective: the siGit Code
11 +CLI is its reference consumer, but the endpoint is a standard remote MCP server
12 +that works with any compliant client. The server implementation itself is
13 +closed-source (it's part of the hosted service); only this consumer-facing
14 +description is public.
15 +
16 +## Tools
17 +
18 +| Tool | What it does |
19 +| --- | --- |
20 +| `list_repositories` | List the git repositories the authenticated user can access. Use this first to discover the `owner/name` to pass to other tools. |
21 +| `get_file_contents` | Read a file's contents at a given ref (branch, tag, or commit SHA). |
22 +| `search_code` | Search file contents across a repository; returns matching files with line snippets. |
23 +| `list_issues` | List issues in a repository, optionally filtered by state or a title/body search. |
24 +| `get_issue` | Fetch one issue by number, including its body and comments. |
25 +| `create_issue` | Open a new issue in a repository. |
26 +| `list_pull_requests` | List pull requests in a repository, optionally filtered by state. |
27 +| `get_pull_request` | Fetch one pull request by number. |
28 +| `create_pull_request` | Open a pull request from a head branch into a base branch. |
29 +| `add_issue_comment` | Post a comment on an issue or pull request by its number. |
30 +| `web_search` | Search the public web; returns matching pages as title, URL, and snippet. |
31 +
32 +All repository tools take a repo in `owner/name` form and only ever see
33 +repositories the authenticated user may access.
34 +
35 +## Authentication
36 +
37 +The server is a per-user protected resource. Every `tools/call` requires an
38 +`Authorization: Bearer <token>` header carrying a siGit Code Cloud session
39 +token. Unauthenticated requests get a `401` with a `WWW-Authenticate` header
40 +advertising the OAuth protected-resource metadata, so MCP clients that support
41 +OAuth can discover the sign-in flow automatically.
42 +
43 +Being stateless, the server issues no `Mcp-Session-Id` and offers no
44 +server→client SSE stream (a `GET` to the endpoint returns `405`) — a valid,
45 +tool-only MCP transport mode.
46 +
47 +## How the siGit Code CLI uses it
48 +
49 +The CLI bakes this endpoint in as its **official** MCP server (default
50 +`https://sigit.si/api/v1/mcp`, overridable via `SIGIT_CLOUD_URL`). When you're
51 +signed in with `sigit login`, the CLI sends your cloud session token as the
52 +bearer credential; when you're not, the server simply contributes no tools.
53 +Its tools are namespaced `mcp__sigit__<tool>` inside the agent (see
54 +[`src/mcp.rs`](../src/mcp.rs)). `web_search` is additionally re-exposed as a
55 +first-class read-only tool rather than a raw `mcp__*` call.
56 +
57 +A user-defined `mcp.toml` entry named `sigit` overrides the baked-in default,
58 +so you can repoint the CLI at a different deployment without a code change.
59 +
60 +## MCP Registry listing
61 +
62 +The server is published to the
63 +[official MCP Registry](https://registry.modelcontextprotocol.io) as
64 +**`si.sigit/sigit`**, a remote Streamable-HTTP listing, so registry-aware
65 +clients can add it in one click. The registry only stores metadata; the
66 +`server.json` and publish workflow live in this repo, and namespace ownership is
67 +proven by a DNS TXT record on `sigit.si`. The internal setup and release runbook
68 +live with the server (private `sigit-si` repo).
server.json new
+28
@@ -0,0 +1,28 @@
1 +{
2 + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
3 + "name": "si.sigit/sigit",
4 + "title": "siGit Si",
5 + "description": "siGit tools: browse repos, read files, search code, manage pull requests and issues, and web search.",
6 + "version": "0.1.0",
7 + "websiteUrl": "https://sigit.si",
8 + "remotes": [
9 + {
10 + "type": "streamable-http",
11 + "url": "https://sigit.si/api/v1/mcp"
12 + }
13 + ],
14 + "_meta": {
15 + "io.modelcontextprotocol.registry/publisher-provided": {
16 + "categories": ["developer-tools", "version-control"],
17 + "keywords": [
18 + "git",
19 + "code search",
20 + "pull requests",
21 + "issues",
22 + "repositories",
23 + "web search",
24 + "coding agent"
25 + ]
26 + }
27 + }
28 +}