gittr-mcp
# gittr-mcp
<!-- mcp-name: io.github.arbadacarbaYK/gittr-mcp -->
**Let your AI agent (or app) use [gittr](https://gittr.space) — Nostr git hosting — like a developer would** — create repos, push code, open and merge pull requests, manage issues, and work with Lightning bounties — using your **Nostr identity**, not a GitHub login.
Works with **Cursor**, **Claude Desktop**, **VS Code / Copilot MCP**, **Windsurf**, **OpenClaw**, or any host that speaks the [Model Context Protocol](https://modelcontextprotocol.io/) over stdio.
Needs **Node 22.23.4, 24.22.1, or 26.12.1** (or a newer build on those lines). Node 18 and 20 have no fix for the October 2026 certificate and Brotli bugs, and gittr-mcp exits instead of starting on those.
**Docs hub:** [gittr-docu](https://3un05jr4dnwh6njreq6uhzgv67st83jxi5lnpsbf8y3dkdq1qkgittr-docu.pages.gittr.space/) — product map and how this MCP sits next to the website.
**This repo’s Page:** root [`index.html`](./index.html). After **Push Manifest**, save site name `gittr-mcp`.
---
## Why use this?
| Without gittr-mcp | With gittr-mcp |
|-------------------|----------------|
| You copy-paste between chat and the gittr website | The agent calls tools: push files, publish repo metadata, open issues/PRs |
| Custom scripts for NIP-98 bridge auth and NIP-34 signing | Signing, challenge handling, and relay checks are built in |
| Unclear whether a push “really” landed on Nostr | Tools return pass/fail plus `verification` / `nextSteps` for automation |
**End result:** one MCP server connects your agent to **decentralized git on Nostr** — same account as on gittr.space (`nsec` / keys file), no separate vendor account for the agent.
**Hosting note:** The website Create/Import flow stays **browser-local** until announce/Push. MCP `createRepo` / `mirrorRepo` / `pushToBridge` write the git the same way the website does. The clone addresses are the servers saved under **Settings → Relays** (yours first), then the public copies. Soft-delete POSTs the tombstone to the bridge so disk is wiped.
## Where this sits (platform map)
gittr-mcp is the **agent door** into the same platform humans use in the browser. **You are here = gittr-mcp** (this repo, teal). Cyan-outlined host boxes = public hostnames (`git.` / `pages.` / `relay.gittr.space`) (teal = this repo; cyan outline = host URLs).
```mermaid
flowchart TB
Agent["AI host<br/>Cursor / Claude / …"]
MCP["★ YOU ARE HERE · gittr-mcp<br/>this repo · agent tools"]
UI["gittr Client<br/>gittr.space"]
Bridge["gitnostr Bridge<br/>git.gittr.space<br/>SSH / HTTPS git"]
RelayGittr["gittr Pyramid relay<br/>relay.gittr.space<br/>wss · open forge + GRASP"]
Relays["Other Nostr relays"]
Pages["Pages / nsite<br/>pages.gittr.space"]
Remote["git remote nostr<br/>optional"]
Agent -->|MCP tools| MCP
MCP -->|HTTPS + Nostr auth| Bridge
MCP -->|sign NIP-34 events| RelayGittr
MCP -->|sign NIP-34 events| Relays
UI --> RelayGittr
UI --> Relays
UI --> Bridge
Pages --> Relays
Remote -.-> Relays
classDef youAreHere fill:#0f766e,stroke:#5eead4,stroke-width:3px,color:#ecfdf5
classDef hostUrl fill:#164e63,stroke:#22d3ee,stroke-width:2px,color:#ecfeff
class MCP youAreHere
class Bridge,Pages,RelayGittr hostUrl
```
| Piece | Host / link | How MCP uses it |
| --- | --- | --- |
| **gittr Client** | [gittr on gittr.space](https://gittr.space/npub1n2ph08n4pqz4d3jk6n2p35p2f4ldhc5g5tu7dhftfpueajf4rpxqfjhzmc/gittr?branch=main) · `gittr.space` | Same product; MCP mirrors forge actions (repos, issues, PRs, bounties) |
| **gitnostr Bridge** | [gitnostr on gittr.space](https://gittr.space/npub1n2ph08n4pqz4d3jk6n2p35p2f4ldhc5g5tu7dhftfpueajf4rpxqfjhzmc/gitnostr?branch=main) · **`git.gittr.space`** | `pushToBridge`, file list, merge clones over HTTPS |
| **Pages / nsite** | [nsite-gateway](https://gittr.space/npub1n2ph08n4pqz4d3jk6n2p35p2f4ldhc5g5tu7dhftfpueajf4rpxqfjhzmc/nsite-gateway) · **`pages.gittr.space`** | Out of band for most MCP git tools |
| **Blossom** | [gittr-blossom](https://gittr.space/npub1n2ph08n4pqz4d3jk6n2p35p2f4ldhc5g5tu7dhftfpueajf4rpxqfjhzmc/gittr-blossom?branch=master) · **`blossom.gittr.space`** | Blob storage Pages uploads land on |
| **gittr Pyramid relay** | [pyramid](https://gittr.space/npub1n2ph08n4pqz4d3jk6n2p35p2f4ldhc5g5tu7dhftfpueajf4rpxqfjhzmc/pyramid) · **`relay.gittr.space`** | Prefer in relay lists when publishing NIP-34 |
| **★ gittr-mcp (this README)** | [gittr-mcp on gittr.space](https://gittr.space/npub1n2ph08n4pqz4d3jk6n2p35p2f4ldhc5g5tu7dhftfpueajf4rpxqfjhzmc/gittr-mcp) | **You are here** |
| **git remote nostr** | [ngit-cli](https://github.com/DanConwayDev/ngit-cli) | Not required for MCP; agents usually use bridge HTTPS + events |
**Addressing for agents:** `resolveRepoByNostrId(npub|hex, repo)` → `cloneUrl` + relays. Prefer announced **npub**-path HTTPS on **`git.gittr.space`** (NIP-34); hex path is a disk fallback if a symlink is missing. Include **`wss://relay.gittr.space`** when publishing.
---
## What you can do (workflows)
These are the **processes** people actually run; each maps to MCP tools the agent can call. Agents: follow [docs/AGENT-WORKFLOW.md](docs/AGENT-WORKFLOW.md) (which tool, and what not to call after it).
### Ship a new project
1. **`createRepo`** — push initial files to the bridge **and** publish Nostr kinds **30617** + **30618** in one step (best default for agents).
Pass **`publicRead: false`** to create a **private** repo (code/clone/API/SSH readable only by you and listed maintainers). The announcement name/description still appear on relays — only file access is gated.
2. Or step-by-step: **`pushToBridge`** → **`publishRepoAnnouncement`** → **`publishRepoState`**.
### Private repositories
- Set **`publicRead: false`** on **`createRepo`**, **`publishRepoAnnouncement`**, **`forkRepo`**, or **`mirrorRepo`**.
- Private repos are **hidden from Explore/home/profile listings** for strangers.
- **Direct URL** still shows the repo name with a **Private** badge; unauthorized viewers see a lock screen (no code).
- **SSH / CLI / API reads** use the same ACL as the web UI: your **npub** must be owner or maintainer (`addCollaborator` or Settings → Contributors on gittr.space).
- **SSH key registration** is unchanged — keys identify *you*; private repos only check whether *your pubkey* has read permission.
### Day-to-day development
- **`pushToBridge`** — update files on a branch (NIP-98 auth to gittr bridge); optional **`deletedPaths`** / **`allowTreeShrink`** for file or folder deletes (parity with Code-tab trash).
- **`getFile`**, **`bridgeListFiles`**, **`bridgeGetFileContent`**, **`getBranches`**, **`getCommitHistory`** — read without cloning. `getFile` is the **bridge, then a short GRASP list** — not the Code tab. On the website: latest live **30617**; forge **`source`** is the tree when present (stale bridge listing is replaced); otherwise first non-empty `clone[]` listing. See [MCP-GITTR-PARITY.md](docs/MCP-GITTR-PARITY.md) and gittr [FILE_FETCHING_INSIGHTS.md](https://gittr.space/npub1n2ph08n4pqz4d3jk6n2p35p2f4ldhc5g5tu7dhftfpueajf4rpxqfjhzmc/gittr?file=docs/FILE_FETCHING_INSIGHTS.md&branch=main).
- **`resolveRepoByNostrId`** — find clone URLs and relays from npub + repo name.
### Issues (bug reports, tasks)
- **`listIssues`**, **`getIssueById`** — same rows as the website Issues tab. Nostr issues, plus GitHub or Codeberg/Gitea issues when the repo source says so. They stay separate rows. `getIssueById` with `issue-N` or a number needs **`ownerPubkey`** and **`repoId`**. GitLab sources stay Nostr-only.
- **`createIssue`**
- **`listIssueComments`**, **`createIssueComment`** — Nostr comments on a 64-character id. Forge comments when the id is `issue-N` or a number (read-only; the website does not post them back to GitHub).
- **`closeIssue`**, **`reopenIssue`** — publish NIP-34 status events (1632 / 1630) for a **64-char Nostr event id**. GitHub/Gitea imported `issue-12` / `pr-12` (and a bare `12`) are refused — close those on the origin.
### Pull requests (code review flow)
| Step | Tool | Notes |
|------|------|--------|
| List / open PR | **`listPRs`**, **`createPR`** | List matches the website: Nostr kind **1618** plus GitHub or Codeberg/Gitea `pr-N` when the repo has that source. `createPR` publishes the Nostr event. |
| Comment on PR | **`listPRComments`**, **`createPRComment`** | Nostr comments on a 64-character id. `pr-N` reads the forge conversation (needs `ownerPubkey` and `repoId`). Not written back to GitHub. |
| Full PR with git branches | **`createPRViaGittrCLI`** | Recommended when the agent has **`git`** on PATH. |
| Update PR tip | **`updatePullRequest`** | New commit + clone URLs on the PR event. |
| Merge into `main` | **`mergePullRequest`** | **Real git merge** of a **Nostr** PR (kind **1618** event id): clone/fetch, merge, push bridge, publish **30618** + merged status **1631**. Forge `pr-N` is refused. Repo owner or listed maintainer; **`git` required**. |
| Mark merged (Nostr only) | **`markPullRequestMerged`** | Status only — no git merge. |
**Honest limits on PRs:** Creating and listing PRs via MCP is supported. **Merging** needs **`git`** installed and permission on the repo. Some relays are strict about **clone URL + relay** matching in repo announcements — if PR publish fails, fix metadata (see [Limitations](#limitations-prs--clone-urls)) or use **`createPRViaGittrCLI`**. Details: [docs/DEVELOPER.md#limitations](docs/DEVELOPER.md#limitations).
### Fork, mirror, import
- **`forkRepo`** — fork an existing gittr repo under your key.
- **`mirrorRepo`** — same result as the website Push of a GitHub/GitLab repo: forge commits on gittr (history kept, and a later call updates them), the same git-server list, and a state event whose `HEAD` is the forge default branch. A later call keeps the name, description, and web links already announced.
- **`importRemoteToBridge`** — copy onto the git server only. With the owner key, a later call updates the forge commits. It does not publish the Nostr card. Use **`mirrorRepo`** for the full website result.
- **Forge 403 / suspended account:** if the repo was already copied, keep using `https://git.gittr.space/<npub>/<repo>.git`. Do not point `mirrorRepo` at a new forge to change the source — that copies the new URL onto gittr and replaces the branches. Guide: [docs/AGENT-WORKFLOW.md](docs/AGENT-WORKFLOW.md).
### Discover & social
- **`listRepos`**, **`searchRepos`**, **`myRepos`**, **`exploreRepos`**, **`getTrendingRepos`** (trending = recent repos, not engagement rank)
- **`starRepo`**, **`unstarRepo`**, **`listStars`** — NIP-25 on the repo’s **30617** event (same as gittr Star button).
- **`watchRepo`**, **`unwatchRepo`**, **`listWatchedRepos`** — NIP-51 kind **10018** followed-repo list (same as gittr Watch).
- **`getRepoContributors`**
**Parity details:** [docs/MCP-GITTR-PARITY.md](docs/MCP-GITTR-PARITY.md) — what matches gittr.space vs caveats.
### Releases & tags
- **`listReleases`** — git **tags** from bridge (`refs/tags/*`), not the web UI Releases tab and not Zapstore.
- **`listForgeReleases`** — forge Releases tab listing (all assets; no NIP-82 MIME gate).
- **`createRelease`** — returns guidance only (UI release notes until next **30617** push).
- **`fetchForgeReleases`** — one forge Release + announceable binaries. Omit `tag` for latest; `hash:true` for sha256 (required before announce).
- **`announceSoftwareFromForgeRelease`** — Zapstore/NIP-82 (kinds **32267** / **30063** / **3063**) from a tagged forge Release. APK preferred; AppImage/DMG/linux `tar.gz`/MSI/EXE/IPA also. Extra binaries on the same tag are sibling assets. Copies `images:` from the forge `zapstore.yaml`. Optional `pinToBlossom` (public Blossom; `blossom.gittr.space` only for official `space.gittr.app`). Same as gittr **Nostr Apps** (latest) or Releases **Announce on Nostr** (`tag=`). Never a tagless app.
- **`deleteSoftwareAnnounce`** — NIP-09 kind **5** for those app/release/asset event ids.
### Pages & security
- **`publishNostrPages`** — NIP-5A kind **35128** + Blossom upload through gittr (`index.html` required).
- **`auditRepoDependencies`** — parse lockfiles on the bridge and query OSV via gittr `/api/security/audit`.
### Lightning bounties & pay-to-push
- Bounties: **`listBounties`**, **`createBountyInvoice`**, **`publishBountyToNostr`**, **`submitBounty`**, **`listBountiesForIssue`**, release/withdraw tools.
- Paywall: **`getPushPaywallStatus`**, **`createPushPaywallIntent`**, **`syncRepoPushPolicy`**.
- Optional LNbits: set **`GITTR_LNBITS_URL`** and **`GITTR_LNBITS_ADMIN_KEY`** in MCP env (see `.env.example`).
### Session / keys
- **`describeAgentAuth`** — run once: confirms keys load (never returns `nsec`); if unconfigured it tells the agent to ask you about a test keypair.
- **`setupTestKeypair`** — after your explicit OK, writes a disposable test identity to `.nostr-keys.json` (replace with your real `nsec` anytime).
- **`loadCredentials`**, **`getPublicKey`** — debugging helpers. MCP `loadCredentials` masks `nsec` (prefix only), `secretKey`, and `private_key`.
**Full tool list:** 50+ tools in `server.js` (search for `name:`). Library API: [docs/DEVELOPER.md](docs/DEVELOPER.md).
---
## Install (5 minutes)
### Requirements
- **Node.js 18+**
- A **Nostr private key** (`nsec` or hex) — same identity you use on gittr.space
### 1. Get the server
**Clone (developers / Cursor):**
```bash
git clone https://github.com/arbadacarbaYK/gittr-mcp.git
cd gittr-mcp
npm install
```
**Claude Desktop one-click (`.mcpb`):** this package is **not on npm**. Download the latest bundle from [GitHub Releases](https://github.com/arbadacarbaYK/gittr-mcp/releases) (`gittr-mcp-x.y.z.mcpb`) and install that. New releases are built automatically when we push a `v*` tag — see [docs/RELEASE.md](docs/RELEASE.md).
### 2. Add your key (local only, never commit)
```bash
cp .nostr-keys.json.example .nostr-keys.json
```
Edit `.nostr-keys.json` and set your **`nsec`** (or hex `secretKey`). The file is gitignored.
Lookup order: `./.nostr-keys.json` (the folder the MCP process was started in) → `.nostr-keys.json` next to `server.js` → `~/.nostr-identity.json` → `~/.config/gittr/keys.json`. On Windows, the home folder is used even when `HOME` is unset. A UTF-8 BOM (PowerShell’s default) is stripped before the file is read.
**No key yet? Test keypair flow.** If no credentials are found, `describeAgentAuth` and all key-missing errors tell the agent to ask you whether a disposable **test keypair** should be created. If you agree, the agent calls **`setupTestKeypair({ confirm: true })`** — it writes a fresh identity into `.nostr-keys.json` (flagged `"generated": true`, file mode 600, never committed) and everything auto-loads it from then on. Replace the `nsec` in that file with your real key whenever you're ready; `describeAgentAuth` keeps reminding the agent that a test key is active. It never runs without `confirm: true` and never overwrites existing credentials unless you explicitly ask for `force: true` — anything published under a keypair stays under that identity forever, so this is always your call, not the agent's.
### 3. Wire up your MCP host
**Important:** **Add** a new server entry — do **not** replace your entire MCP config.
#### Cursor
Edit `~/.cursor/mcp.json` (or project MCP settings). Use an **absolute** path:
```json
{
"mcpServers": {
"gittr": {
"command": "node",
"args": ["/ABSOLUTE/PATH/TO/gittr-mcp/server.js"],
"env": {
"BRIDGE_URL": "https://gittr.space"
}
}
}
}
```
Reload MCP or restart Cursor.
#### Claude Desktop
Quit Claude, edit `claude_desktop_config.json` (path depends on OS — see Anthropic docs), same `mcpServers` block as above, restart.
#### VS Code / Copilot, Windsurf, OpenClaw, custom apps
Same stdio contract: `command`: `node`, `args`: `["/path/to/server.js"]`, optional `env`.
OpenClaw / mcporter: [docs/MCP-HOSTS.md](docs/MCP-HOSTS.md).
#### Embed as a library (no MCP)
```javascript
const gittr = require('gittr-mcp');
await gittr.pushToBridge({ /* ... */ });
```
Entry point: `index.js`. MCP process: `server.js` (npm bin **`gittr-mcp`**).
### 4. Verify
In chat, ask the agent to call **`describeAgentAuth`**, or from the repo:
```bash
npm test # full package (includes clone-set + forge-match regressions)
npm run test:regressions # fast: grasp clone URLs + findReposBySource matchers
npm run test:mcp-stdio # optional live stdio CallTool matrix
```
UI / file-fetch tip fidelity regressions live in the gittr monorepo: `cd ../gittr/ui && npm run test:regressions` (see gittr `docs/FILE_FETCHING_INSIGHTS.md`).
---
## What to ask your agent
Examples that map to the workflows above:
- “Create a repo `my-demo` with a README and publish it on gittr.”
- “Push these file changes to `my-demo` on `main`.”
- “Open an issue: login button broken.”
- “List open PRs on npub…/my-demo and merge PR `<id>` if I’m the owner.”
- “Mirror `https://github.com/user/repo` to gittr as `repo-name`.”
Agents should read tool results as JSON; many responses include **`agentSummary`** and **`nextSteps`**.
---
## Limitations (PRs & clone URLs)
Short version — full detail in [docs/DEVELOPER.md](docs/DEVELOPER.md):
1. **Bridge push** and **Nostr publish** are separate steps unless you use **`createRepo`**. Pushing alone does not make the repo visible everywhere.
2. **`git clone`** only “works” for others if your published **`clone`** URL serves git HTTP. This MCP defaults toward **`https://git.gittr.space/<hex-pubkey>/<repo>.git`**. A failed clone means fix the URL in **30617**, not “ignore and continue.” Host-only values like `https://git.gittr.space` are rejected/expanded on publish.
3. **`mergePullRequest`** needs **`git`** on the machine running MCP and maintainer/owner rights.
4. Relays can rate-limit or lag; failed verification is a **failed** publish, not “maybe OK.”
5. **Same result as the website Push.** `mirrorRepo` copies the forge commits (and updates them if the repo is already there), then writes `clone` for gittr, ngit, gitnostr, shakespeare, and danconway, keeps the forge URL in `source`, and publishes the branch tips with `HEAD` on the forge default branch. A second call keeps the existing name, description, and web links. Dead hosts (`git-01.uid.ovh`, `ngit-relay.nostrver.se`) are not added.
6. **`pushToBridge` replaces the branch with one new snapshot** (author `gittr push`) and drops imported GitHub history. That matches editing files on the website, not syncing GitHub. Do not use it after `mirrorRepo`.
### Do MCP users get gittr’s filter / CORS server fixes?
**Mostly yes, without updating MCP.** Browser/`filter`/`uploadpack`/CORS fixes live on **`git.gittr.space`**. Anyone (including agents via MCP) cloning that host benefits as soon as the server is fixed.
**MCP updates are a new version, not an npm package.** This project is not installed from the npm registry. Cursor runs `node` on a local `server.js`. Claude can install the `.mcpb` file from [GitHub Releases](https://github.com/arbadacarbaYK/gittr-mcp/releases).
- **Cursor:** in the folder whose path ends with `gittr-mcp/server.js`, run `git pull`, then reload the MCP. `npm install` is only the first-time library download in that folder. A normal update does not need it.
- **Release file:** install the new `gittr-mcp-x.y.z.mcpb` from Releases. The old 1.0.9 file does not include later fixes. The version shown at startup is the way to tell them apart.
---
## For developers
```bash
npm ci
npm test
# Live tests (real relays; optional LNbits) — see .env.example
GITTR_TEST_NSEC=nsec1... npm run test:live:matrix
```
| Doc | Contents |
|-----|----------|
| [docs/MCP-HOSTS.md](docs/MCP-HOSTS.md) | Per-host MCP config |
| [docs/AGENT-WORKFLOW.md](docs/AGENT-WORKFLOW.md) | Which tool for which job |
| [docs/AGENT-QUICKSTART.md](docs/AGENT-QUICKSTART.md) | Copy-paste agent prompts |
| [docs/DEVELOPER.md](docs/DEVELOPER.md) | API, verification contract, GRASP |
| [docs/SIGNING-GUIDE.md](docs/SIGNING-GUIDE.md) | Keys and NIP-98 |
| [docs/NIP34-SCHEMAS.md](docs/NIP34-SCHEMAS.md) | Event kinds |
| [docs/MCP-GITTR-PARITY.md](docs/MCP-GITTR-PARITY.md) | MCP vs gittr.space feature map |
---
## Security
- Do **not** commit `.nostr-keys.json`, `.env`, or real `nsec` values.
- Bridge auth uses **NIP-98**; treat agent transcripts as sensitive.
- Only **`.nostr-keys.json.example`** belongs in git.
- The gittr **Dependencies** tab can list matches inside the MCP SDK. gittr-mcp talks stdio only (it is a server, not an OAuth client). `@modelcontextprotocol/sdk` stays on **^1.32.1** or newer so CVE-2026-104850 is not in the lockfile, and patched `fast-uri` / `hono` / `qs` stay pinned in `overrides`. Details: [docs/SECURITY-ADVISORIES.md](docs/SECURITY-ADVISORIES.md).
---
## Links
- **This repo:** https://github.com/arbadacarbaYK/gittr-mcp
- **gittr.space:** https://gittr.space
- **Glama:** https://glama.ai/mcp/servers/arbadacarbaYK/gittr-mcp
- **Awesome MCP Servers:** https://github.com/punkpeye/awesome-mcp-servers (Version Control)
- **NIP-34 (git on Nostr):** https://github.com/nostr-protocol/nips/blob/master/34.md
See [LICENSE](LICENSE) (MIT).
TDQS
Scored across 75 tools
Multiple tools overlap heavily: createPR vs createPRViaGittrCLI, listReleases vs listForgeReleases vs fetchForgeReleases, publishSoftwareAnnounce vs announceSoftwareFromForgeRelease, and pushToBridge vs importRemoteToBridge vs mirrorRepo. Several aliases (deleteRepo/findReposByGithub) and multiple bounty flows add to the confusion. An agent must read long warnings to choose correctly.
All tool names use lowerCamelCase consistently, so there is no casing chaos. However, the verb+noun pattern is not uniform: some tools are noun-first (bountyCreateWithdraw, bountyRelease), and verb choices vary widely (fetch, announce, explore, star, watch, merge, softDelete). Aliases and prefixed names (bridge*) are minor deviations.
75 tools is far beyond the typical 3-15 range and includes many alias, low-level, and niche tools. While the platform is broad, the count forces agents to sift through redundant options and raises the risk of misselection. It should be consolidated.
Core lifecycle coverage is strong: repos (create/get/list/delete/fork), issues (create/list/get/close/reopen/comments), PRs (create/list/get/merge/comments), bounties, releases, paywall, and bridge operations are all present. Minor gaps exist (e.g., no updateIssue, no removeCollaborator, createRelease is a stub), but agents can work around them.