@amonstack/gitea-mcp 0.3.0 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +91 -24
- package/README.zh-CN.md +82 -21
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +38 -6
- package/dist/cli.js.map +1 -1
- package/dist/credentials.d.ts +145 -0
- package/dist/credentials.js +141 -0
- package/dist/credentials.js.map +1 -0
- package/dist/git-config.d.ts +43 -19
- package/dist/git-config.js +127 -27
- package/dist/git-config.js.map +1 -1
- package/dist/gitea-client.d.ts +54 -2
- package/dist/gitea-client.js +109 -6
- package/dist/gitea-client.js.map +1 -1
- package/dist/server.d.ts +3 -2
- package/dist/server.js +16 -5
- package/dist/server.js.map +1 -1
- package/dist/skills.d.ts +6 -1
- package/dist/skills.js +25 -11
- package/dist/skills.js.map +1 -1
- package/dist/tools.d.ts +1 -0
- package/dist/tools.js +1 -0
- package/dist/tools.js.map +1 -1
- package/package.json +6 -3
package/README.md
CHANGED
|
@@ -1,20 +1,50 @@
|
|
|
1
|
-
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img alt="gitea-mcp" src="https://raw.githubusercontent.com/amonstack/gitea_mcp/master/docs/assets/gitea-mcp-banner.png" />
|
|
3
|
+
<h3 align="center">gitea-mcp</h3>
|
|
4
|
+
<p align="center">MCP server that exposes Gitea issues, labels, milestones, and comments as tools for AI assistants</p>
|
|
5
|
+
</p>
|
|
2
6
|
|
|
3
|
-
|
|
7
|
+
---
|
|
4
8
|
|
|
5
|
-
|
|
6
|
-
|
|
9
|
+
[](https://www.npmjs.com/package/@amonstack/gitea-mcp)
|
|
10
|
+
[](https://codecov.io/gh/amonstack/gitea_mcp)
|
|
11
|
+
[](https://github.com/amonstack/gitea_mcp/blob/master/LICENSE)
|
|
12
|
+
[](https://www.npmjs.com/package/@amonstack/gitea-mcp)
|
|
13
|
+
[](https://www.typescriptlang.org)
|
|
14
|
+
[](https://modelcontextprotocol.io)
|
|
7
15
|
|
|
8
|
-
|
|
16
|
+
**English** | [中文文档](https://github.com/amonstack/gitea_mcp/blob/master/README.zh-CN.md)
|
|
9
17
|
|
|
10
|
-
`gitea-mcp` is a [Model Context Protocol (MCP)](https://modelcontextprotocol.io/)
|
|
11
|
-
server that exposes Gitea repository operations as tools. Once connected to an
|
|
12
|
-
MCP client (Claude Desktop, opencode, Cursor, etc.), an AI assistant can list,
|
|
13
|
-
create, update, and delete issues, labels, milestones, and comments on your
|
|
14
|
-
Gitea instance — all through natural language.
|
|
18
|
+
`gitea-mcp` is a [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) server that exposes Gitea repository operations as tools. Once connected to an MCP client (Claude Desktop, opencode, Cursor, etc.), an AI assistant can list, create, update, and delete issues, labels, milestones, and comments on your Gitea instance — all through natural language.
|
|
15
19
|
|
|
16
|
-
The server communicates over stdio and wraps the [Gitea REST API
|
|
17
|
-
|
|
20
|
+
The server communicates over stdio and wraps the [Gitea REST API (`/api/v1`)](https://docs.gitea.com/api/1.22/).
|
|
21
|
+
|
|
22
|
+
## Features
|
|
23
|
+
|
|
24
|
+
- **Full Gitea project management** — issues, labels, milestones, and comments via natural language
|
|
25
|
+
- **Zero-config auto-discovery** — reads `baseUrl`, `owner`, `repo`, and token from the project's git config; one global install serves many repos
|
|
26
|
+
- **Multi-source auth with failover** — tries `[gitea]` config tokens, `GITEA_TOKEN`, then the git credential store, advancing automatically on `401`/`403`
|
|
27
|
+
- **Action-scoped skills** — ships one skill per workflow (find, create, label, comment, plan milestones, …) for opencode, Claude Code, Cursor, and more
|
|
28
|
+
- **Client-agnostic** — works with any stdio-based MCP client; ships guidance prompts and on-demand reference resources too
|
|
29
|
+
|
|
30
|
+
## Table of Contents
|
|
31
|
+
|
|
32
|
+
- [Features](#features)
|
|
33
|
+
- [Requirements](#requirements)
|
|
34
|
+
- [Installation](#installation)
|
|
35
|
+
- [Configuration](#configuration)
|
|
36
|
+
- [MCP Client Setup](#mcp-client-setup)
|
|
37
|
+
- [Available Tools](#available-tools)
|
|
38
|
+
- [AI Guidance & Skills](#ai-guidance--skills)
|
|
39
|
+
- [Development](#development)
|
|
40
|
+
- [Contributing](#contributing)
|
|
41
|
+
- [License](#license)
|
|
42
|
+
|
|
43
|
+
## Requirements
|
|
44
|
+
|
|
45
|
+
- **Node.js ≥ 18** — uses the global `fetch`
|
|
46
|
+
- A **Gitea instance** (self-hosted or Gitea Cloud) reachable over HTTP
|
|
47
|
+
- A **Gitea API token** (or a git credential-store entry) for anything beyond reading public repositories
|
|
18
48
|
|
|
19
49
|
## Installation
|
|
20
50
|
|
|
@@ -59,7 +89,7 @@ projects. Set them only to override the discovery.
|
|
|
59
89
|
| Variable | Required | Description |
|
|
60
90
|
|----------|:--------:|-------------|
|
|
61
91
|
| `GITEA_BASE_URL` | No | Gitea instance URL (e.g. `https://gitea.example.com`). Auto-detected from the project's git remote when omitted. |
|
|
62
|
-
| `GITEA_TOKEN` | No | Gitea API access token.
|
|
92
|
+
| `GITEA_TOKEN` | No | Gitea API access token. One of several auth candidates; tried after a `.git/config [gitea]` token and before the git credential store (see [Token discovery](#token-discovery)). |
|
|
63
93
|
| `GITEA_DEFAULT_OWNER` | No | Default repository owner — skip passing `owner` on every call |
|
|
64
94
|
| `GITEA_DEFAULT_REPO` | No | Default repository name — skip passing `repo` on every call |
|
|
65
95
|
|
|
@@ -80,21 +110,48 @@ repository, or set `GITEA_BASE_URL` / `GITEA_TOKEN` explicitly.
|
|
|
80
110
|
|
|
81
111
|
### Token discovery
|
|
82
112
|
|
|
83
|
-
|
|
113
|
+
`gitea-mcp` collects authentication **candidates** from three sources, in this
|
|
114
|
+
priority order:
|
|
84
115
|
|
|
85
|
-
1. A `[gitea "<baseUrl>"]` section in `.git/config
|
|
116
|
+
1. A `[gitea "<baseUrl>"]` section in `.git/config` (a bare `[gitea]` section is
|
|
117
|
+
a host-wide fallback):
|
|
86
118
|
```ini
|
|
87
119
|
[gitea "https://gitea.example.com"]
|
|
88
120
|
token = <your-token>
|
|
89
121
|
```
|
|
90
|
-
|
|
91
|
-
2. The
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
122
|
+
Always sent as `Authorization: token <token>`.
|
|
123
|
+
2. The `GITEA_TOKEN` environment variable — also sent as `Authorization: token`.
|
|
124
|
+
3. The git credential store (`~/.git-credentials`, or
|
|
125
|
+
`$XDG_CONFIG_HOME/git/credentials`) — every line whose host matches the
|
|
126
|
+
instance, e.g. `https://alice:s3cret@gitea.example.com`. When several lines
|
|
127
|
+
match, the one whose path best matches `owner/repo` is tried first.
|
|
128
|
+
|
|
129
|
+
A credential-store entry's `password` field may hold a real PAT, an account
|
|
130
|
+
password, or an OAuth token — git stores whatever was typed at the prompt, and
|
|
131
|
+
the server cannot tell them apart statically. So each credential-store entry is
|
|
132
|
+
tried under **two authentication schemes**:
|
|
133
|
+
|
|
134
|
+
- `Authorization: Basic <base64(user:pass)>` — works for account passwords and
|
|
135
|
+
PATs alike (Gitea checks that the username matches the secret's owner).
|
|
136
|
+
- `Authorization: token <secret>` — works only for real PATs.
|
|
137
|
+
|
|
138
|
+
The order is chosen by a username heuristic: a convention username
|
|
139
|
+
(`oauth2`, `x-oauth-basic`, or empty) tries `token` first; a real-looking
|
|
140
|
+
username (e.g. `alice`) tries `basic` first.
|
|
141
|
+
|
|
142
|
+
**Fault tolerance.** On `401`/`403` the server advances to the next
|
|
143
|
+
scheme/candidate and retries the same request; once a combination succeeds it is
|
|
144
|
+
locked for the rest of the session (no re-probing). Non-auth errors (`404`,
|
|
145
|
+
`500`, network) propagate immediately and do **not** trigger a retry.
|
|
146
|
+
|
|
147
|
+
**Diagnostics.** The `gitea_status` tool (see [Repository Helpers](#repository-helpers))
|
|
148
|
+
returns a redacted view of the current state — which candidate is active, which
|
|
149
|
+
are exhausted, the last status seen — without ever exposing the secret. Use it
|
|
150
|
+
to troubleshoot a `401` instead of guessing.
|
|
151
|
+
|
|
152
|
+
If no source resolves a credential, the server still starts anonymously. Public
|
|
153
|
+
repositories may be read; private repos and write operations return `401` — use
|
|
154
|
+
the `gitea-configure` skill to guide setup, or set `GITEA_TOKEN`.
|
|
98
155
|
|
|
99
156
|
When `GITEA_DEFAULT_OWNER` and `GITEA_DEFAULT_REPO` are set, you can omit the
|
|
100
157
|
`owner` and `repo` parameters in tool calls. The `resolve_repo` tool can also
|
|
@@ -234,6 +291,7 @@ gitea-mcp
|
|
|
234
291
|
|------|-------------|
|
|
235
292
|
| `list_my_repos` | List repositories accessible to the authenticated user |
|
|
236
293
|
| `resolve_repo` | Detect `baseUrl`, `owner`, and `repo` from the project's git remotes (`upstream` preferred, then `origin`) |
|
|
294
|
+
| `gitea_status` | Inspect credential-handling state — active candidate, exhausted candidates, last error (redacted; secrets never exposed) |
|
|
237
295
|
|
|
238
296
|
## AI Guidance & Skills
|
|
239
297
|
|
|
@@ -290,11 +348,20 @@ npm ci
|
|
|
290
348
|
| `make test` | Run unit tests |
|
|
291
349
|
| `make test-watch` | Run tests in watch mode |
|
|
292
350
|
| `make test-integration` | Run integration tests (needs live Gitea instance) |
|
|
351
|
+
| `make scan` | Scan for leaked secrets with gitleaks (part of `make verify`) |
|
|
293
352
|
| `make dev` | Run directly with tsx |
|
|
294
353
|
|
|
295
354
|
For the full architecture — module layout, dependency graph, core patterns, and
|
|
296
355
|
the guide to adding a new tool — see [`docs/architecture.md`](docs/architecture.md).
|
|
297
356
|
|
|
357
|
+
## Contributing
|
|
358
|
+
|
|
359
|
+
Contributions are welcome!
|
|
360
|
+
|
|
361
|
+
- Found a bug or have a feature idea? Please [open an issue](https://github.com/amonstack/gitea_mcp/issues).
|
|
362
|
+
- Pull requests are gladly accepted. This repo follows [Conventional Commits](https://www.conventionalcommits.org/) and the workflow described in [`AGENTS.md`](AGENTS.md) — please skim it before your first PR.
|
|
363
|
+
- For the module layout, dependency graph, and the guide to adding a new tool, see [`docs/architecture.md`](docs/architecture.md).
|
|
364
|
+
|
|
298
365
|
## License
|
|
299
366
|
|
|
300
|
-
MIT
|
|
367
|
+
[MIT](LICENSE) — Copyright (c) 2026 [amonstack](https://github.com/amonstack).
|
package/README.zh-CN.md
CHANGED
|
@@ -1,19 +1,50 @@
|
|
|
1
|
-
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img alt="gitea-mcp" src="https://raw.githubusercontent.com/amonstack/gitea_mcp/master/docs/assets/gitea-mcp-banner.png" />
|
|
3
|
+
<h3 align="center">gitea-mcp</h3>
|
|
4
|
+
<p align="center">MCP 服务端,将 Gitea 的议题、标签、里程碑与评论封装为工具,供 AI 助手调用</p>
|
|
5
|
+
</p>
|
|
2
6
|
|
|
3
|
-
|
|
7
|
+
---
|
|
4
8
|
|
|
5
|
-
|
|
6
|
-
|
|
9
|
+
[](https://www.npmjs.com/package/@amonstack/gitea-mcp)
|
|
10
|
+
[](https://codecov.io/gh/amonstack/gitea_mcp)
|
|
11
|
+
[](https://github.com/amonstack/gitea_mcp/blob/master/LICENSE)
|
|
12
|
+
[](https://www.npmjs.com/package/@amonstack/gitea-mcp)
|
|
13
|
+
[](https://www.typescriptlang.org)
|
|
14
|
+
[](https://modelcontextprotocol.io)
|
|
7
15
|
|
|
8
|
-
|
|
16
|
+
[English](https://github.com/amonstack/gitea_mcp/blob/master/README.md) | **中文文档**
|
|
9
17
|
|
|
10
|
-
`gitea-mcp` 是一个 [Model Context Protocol (MCP)](https://modelcontextprotocol.io/)
|
|
11
|
-
服务端,将 Gitea 仓库操作暴露为 MCP 工具。连接到 MCP 客户端(Claude Desktop、
|
|
12
|
-
opencode、Cursor 等)后,AI 助手即可通过自然语言在 Gitea 实例上创建、查询、
|
|
13
|
-
更新和删除议题、标签、里程碑和评论。
|
|
18
|
+
`gitea-mcp` 是一个 [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) 服务端,将 Gitea 仓库操作暴露为 MCP 工具。连接到 MCP 客户端(Claude Desktop、opencode、Cursor 等)后,AI 助手即可通过自然语言在 Gitea 实例上创建、查询、更新和删除议题、标签、里程碑和评论。
|
|
14
19
|
|
|
15
|
-
服务端通过 stdio 通信,封装了 [Gitea REST API
|
|
16
|
-
|
|
20
|
+
服务端通过 stdio 通信,封装了 [Gitea REST API (`/api/v1`)](https://docs.gitea.com/api/1.22/)。
|
|
21
|
+
|
|
22
|
+
## 功能特性
|
|
23
|
+
|
|
24
|
+
- **完整的 Gitea 项目管理** —— 通过自然语言管理议题、标签、里程碑与评论
|
|
25
|
+
- **零配置自动发现** —— 从项目 git 配置读取 `baseUrl`、`owner`、`repo` 与令牌;一次全局安装即可服务多个仓库
|
|
26
|
+
- **多来源认证 + 自动容错** —— 依次尝试 `[gitea]` 配置令牌、`GITEA_TOKEN`、git 凭据存储,遇 `401`/`403` 自动切换
|
|
27
|
+
- **按动作划分的技能** —— 每个工作流一个技能(查找、创建、打标签、评论、规划里程碑……),适配 opencode、Claude Code、Cursor 等
|
|
28
|
+
- **客户端无关** —— 兼容任何基于 stdio 的 MCP 客户端;同时内置引导提示与按需参考资源
|
|
29
|
+
|
|
30
|
+
## 目录
|
|
31
|
+
|
|
32
|
+
- [功能特性](#功能特性)
|
|
33
|
+
- [环境要求](#环境要求)
|
|
34
|
+
- [安装](#安装)
|
|
35
|
+
- [配置](#配置)
|
|
36
|
+
- [MCP 客户端配置](#mcp-客户端配置)
|
|
37
|
+
- [可用工具](#可用工具)
|
|
38
|
+
- [AI 引导与技能](#ai-引导与技能)
|
|
39
|
+
- [二次开发](#二次开发)
|
|
40
|
+
- [参与贡献](#参与贡献)
|
|
41
|
+
- [许可证](#许可证)
|
|
42
|
+
|
|
43
|
+
## 环境要求
|
|
44
|
+
|
|
45
|
+
- **Node.js ≥ 18** —— 使用全局 `fetch`
|
|
46
|
+
- 一个可通过 HTTP 访问的 **Gitea 实例**(自托管或 Gitea Cloud)
|
|
47
|
+
- 一个 **Gitea API 令牌**(或 git 凭据存储条目),用于读取公开仓库以外的操作
|
|
17
48
|
|
|
18
49
|
## 安装
|
|
19
50
|
|
|
@@ -56,7 +87,7 @@ node dist/cli.js
|
|
|
56
87
|
| 变量 | 必填 | 说明 |
|
|
57
88
|
|------|:----:|------|
|
|
58
89
|
| `GITEA_BASE_URL` | 否 | Gitea 实例地址(如 `https://gitea.example.com`)。未设置时从项目 git 远程地址自动推导。 |
|
|
59
|
-
| `GITEA_TOKEN` | 否 | Gitea API
|
|
90
|
+
| `GITEA_TOKEN` | 否 | Gitea API 访问令牌。是多个认证候选之一;排在 `.git/config [gitea]` 令牌之后、git 凭据存储之前(见[令牌发现](#令牌发现))。 |
|
|
60
91
|
| `GITEA_DEFAULT_OWNER` | 否 | 默认仓库所有者,免去每次传入 `owner` 参数 |
|
|
61
92
|
| `GITEA_DEFAULT_REPO` | 否 | 默认仓库名称,免去每次传入 `repo` 参数 |
|
|
62
93
|
|
|
@@ -76,20 +107,40 @@ node dist/cli.js
|
|
|
76
107
|
|
|
77
108
|
### 令牌发现
|
|
78
109
|
|
|
79
|
-
|
|
110
|
+
`gitea-mcp` 从三个来源收集认证**候选**,按以下优先级排序:
|
|
80
111
|
|
|
81
|
-
1. `.git/config` 中的 `[gitea "<baseUrl>"]`
|
|
112
|
+
1. `.git/config` 中的 `[gitea "<baseUrl>"]` 段(不带地址的 `[gitea]` 段作为全局兜底):
|
|
82
113
|
```ini
|
|
83
114
|
[gitea "https://gitea.example.com"]
|
|
84
115
|
token = <your-token>
|
|
85
116
|
```
|
|
86
|
-
|
|
87
|
-
2.
|
|
88
|
-
|
|
89
|
-
|
|
117
|
+
始终以 `Authorization: token <token>` 发送。
|
|
118
|
+
2. `GITEA_TOKEN` 环境变量 —— 同样以 `Authorization: token` 发送。
|
|
119
|
+
3. git 凭据存储(`~/.git-credentials`,或 `$XDG_CONFIG_HOME/git/credentials`)—— host 匹配实例
|
|
120
|
+
的每一行,如 `https://alice:s3cret@gitea.example.com`。多行同时匹配时,path 与 `owner/repo`
|
|
121
|
+
最贴近的那一行优先尝试。
|
|
122
|
+
|
|
123
|
+
凭据存储条目的 `password` 字段可能是真正的 PAT、账户登录密码,或 OAuth token —— git 存的是
|
|
124
|
+
用户在密码提示里输入的任何内容,服务端无法静态区分。因此每个凭据存储条目会以**两种认证方案**
|
|
125
|
+
依次尝试:
|
|
90
126
|
|
|
91
|
-
|
|
92
|
-
|
|
127
|
+
- `Authorization: Basic <base64(user:pass)>` —— 账户密码和 PAT 都能通过(Gitea 会校验用户名与
|
|
128
|
+
密钥属主一致)。
|
|
129
|
+
- `Authorization: token <secret>` —— 仅对真正的 PAT 有效。
|
|
130
|
+
|
|
131
|
+
尝试顺序由用户名启发式决定:约定用户名(`oauth2`、`x-oauth-basic`、空)先试 `token`;真实用户名
|
|
132
|
+
(如 `alice`)先试 `basic`。
|
|
133
|
+
|
|
134
|
+
**容错。** 遇到 `401`/`403`,服务端会切换到下一个方案/候选并重试同一请求;一旦某组凭据成功,
|
|
135
|
+
它会被锁定到本会话结束(不再重复探测)。非认证类错误(`404`、`500`、网络错误)立即向上抛出,
|
|
136
|
+
**不会**触发重试。
|
|
137
|
+
|
|
138
|
+
**诊断。** `gitea_status` 工具(见[仓库辅助](#仓库辅助-repository-helpers))返回当前状态的脱敏
|
|
139
|
+
视图 —— 哪个候选处于 active、哪些已耗尽、最近一次的状态码 —— 永不暴露密钥本身。排查 `401` 时
|
|
140
|
+
用它代替盲猜。
|
|
141
|
+
|
|
142
|
+
若所有来源都未解析到凭据,服务器仍会以匿名方式启动。公开仓库可读;私有仓库和写操作返回
|
|
143
|
+
`401` —— 此时使用 `gitea-configure` 技能引导配置,或设置 `GITEA_TOKEN`。
|
|
93
144
|
|
|
94
145
|
设置 `GITEA_DEFAULT_OWNER` 和 `GITEA_DEFAULT_REPO` 后,调用工具时可以省略
|
|
95
146
|
`owner` 和 `repo` 参数。也可以使用 `resolve_repo` 工具自动从本地 git 仓库
|
|
@@ -227,6 +278,7 @@ gitea-mcp
|
|
|
227
278
|
|------|------|
|
|
228
279
|
| `list_my_repos` | 列出当前用户可访问的仓库 |
|
|
229
280
|
| `resolve_repo` | 从项目 git 远程地址检测 `baseUrl`、`owner`、`repo`(优先 `upstream`,回退 `origin`) |
|
|
281
|
+
| `gitea_status` | 查看认证处理状态 —— active 候选、已耗尽候选、最近一次错误(脱敏;永不暴露密钥) |
|
|
230
282
|
|
|
231
283
|
## AI 引导与技能
|
|
232
284
|
|
|
@@ -279,11 +331,20 @@ npm ci
|
|
|
279
331
|
| `make test` | 运行单元测试 |
|
|
280
332
|
| `make test-watch` | 监听模式运行测试 |
|
|
281
333
|
| `make test-integration` | 运行集成测试(需要可用的 Gitea 实例) |
|
|
334
|
+
| `make scan` | 用 gitleaks 扫描泄露的密钥(属于 `make verify`) |
|
|
282
335
|
| `make dev` | 通过 tsx 直接运行 |
|
|
283
336
|
|
|
284
337
|
完整的架构说明(模块布局、依赖关系、核心模式,以及新增工具的指引)请参阅
|
|
285
338
|
[`docs/architecture.md`](docs/architecture.md)。
|
|
286
339
|
|
|
340
|
+
## 参与贡献
|
|
341
|
+
|
|
342
|
+
欢迎贡献!
|
|
343
|
+
|
|
344
|
+
- 发现 bug 或有功能建议?请[提交一个 issue](https://github.com/amonstack/gitea_mcp/issues)。
|
|
345
|
+
- 欢迎提交 Pull Request。本仓库遵循 [Conventional Commits](https://www.conventionalcommits.org/),工作流见 [`AGENTS.md`](AGENTS.md) —— 首次 PR 前请先浏览一遍。
|
|
346
|
+
- 模块布局、依赖关系、核心模式以及新增工具的指引,请参阅 [`docs/architecture.md`](docs/architecture.md)。
|
|
347
|
+
|
|
287
348
|
## 许可证
|
|
288
349
|
|
|
289
|
-
MIT
|
|
350
|
+
[MIT](LICENSE) —— 版权所有 (c) 2026 [amonstack](https://github.com/amonstack)。
|
package/dist/cli.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
export
|
|
2
|
+
export declare const TOP_LEVEL_USAGE = "Usage: gitea-mcp [command] [options]\n\nGitea MCP server \u2014 exposes Gitea issues, labels, milestones, and comments as\ntools for AI assistants. With no command, it starts the Model Context Protocol\nserver over stdio and auto-discovers baseUrl, owner, repo, and credentials from\nthe environment and the local git remote (.git/config).\n\nCommands:\n init Install bundled action skills into an AI tool's skills directory.\n\nOptions:\n -h, --help Show this help and exit.\n -V, --version Show version and exit.\n\nWith no command, gitea-mcp starts the MCP server. Run `gitea-mcp init --help`\nfor init-specific options.\n";
|
package/dist/cli.js
CHANGED
|
@@ -1,8 +1,36 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { createRequire } from "node:module";
|
|
2
3
|
import { runServer } from "./server.js";
|
|
3
4
|
import { discoverConfig } from "./git-config.js";
|
|
5
|
+
const pkgVersion = createRequire(import.meta.url)("../package.json").version;
|
|
6
|
+
export const TOP_LEVEL_USAGE = `Usage: gitea-mcp [command] [options]
|
|
7
|
+
|
|
8
|
+
Gitea MCP server — exposes Gitea issues, labels, milestones, and comments as
|
|
9
|
+
tools for AI assistants. With no command, it starts the Model Context Protocol
|
|
10
|
+
server over stdio and auto-discovers baseUrl, owner, repo, and credentials from
|
|
11
|
+
the environment and the local git remote (.git/config).
|
|
12
|
+
|
|
13
|
+
Commands:
|
|
14
|
+
init Install bundled action skills into an AI tool's skills directory.
|
|
15
|
+
|
|
16
|
+
Options:
|
|
17
|
+
-h, --help Show this help and exit.
|
|
18
|
+
-V, --version Show version and exit.
|
|
19
|
+
|
|
20
|
+
With no command, gitea-mcp starts the MCP server. Run \`gitea-mcp init --help\`
|
|
21
|
+
for init-specific options.
|
|
22
|
+
`;
|
|
4
23
|
const argv = process.argv.slice(2);
|
|
5
|
-
|
|
24
|
+
const head = argv[0];
|
|
25
|
+
if (head === "-h" || head === "--help" || head === "help") {
|
|
26
|
+
process.stdout.write(TOP_LEVEL_USAGE);
|
|
27
|
+
process.exit(0);
|
|
28
|
+
}
|
|
29
|
+
if (head === "-V" || head === "--version") {
|
|
30
|
+
process.stdout.write(`gitea-mcp ${pkgVersion}\n`);
|
|
31
|
+
process.exit(0);
|
|
32
|
+
}
|
|
33
|
+
if (head === "init") {
|
|
6
34
|
// `gitea-mcp init [--tool <name>]` installs the bundled skills into a target
|
|
7
35
|
// AI tool's skills directory. It needs no Gitea credentials, so it is
|
|
8
36
|
// dispatched before the config-discovery logic below.
|
|
@@ -13,10 +41,14 @@ if (argv[0] === "init") {
|
|
|
13
41
|
});
|
|
14
42
|
}
|
|
15
43
|
else {
|
|
16
|
-
// Resolve baseUrl/owner/repo/
|
|
17
|
-
// (`.git/config` remotes + credential store).
|
|
18
|
-
//
|
|
19
|
-
//
|
|
44
|
+
// Resolve baseUrl/owner/repo/credentials from env first, then the local git
|
|
45
|
+
// context (`.git/config` remotes + credential store). Discovery collects ALL
|
|
46
|
+
// credential candidates (config token, env token, credential-store entries)
|
|
47
|
+
// rather than picking one, so the client can fall back across them when one
|
|
48
|
+
// scheme is rejected (e.g. an account password that is not a PAT). When
|
|
49
|
+
// neither env nor any git remote provides a baseUrl, the server is
|
|
50
|
+
// intentionally skipped: a single global install should stay dormant outside
|
|
51
|
+
// of git projects.
|
|
20
52
|
const discovered = await discoverConfig().catch((err) => {
|
|
21
53
|
console.error("Fatal error:", err);
|
|
22
54
|
process.exit(1);
|
|
@@ -25,7 +57,7 @@ else {
|
|
|
25
57
|
console.error(`gitea-mcp: no git remote found in ${process.cwd()} and GITEA_BASE_URL is not set; skipping server start.`);
|
|
26
58
|
process.exit(0);
|
|
27
59
|
}
|
|
28
|
-
runServer(discovered.baseUrl, discovered.
|
|
60
|
+
runServer(discovered.baseUrl, discovered.candidates, discovered.defaultOwner, discovered.defaultRepo).catch((err) => {
|
|
29
61
|
console.error("Fatal error:", err);
|
|
30
62
|
process.exit(1);
|
|
31
63
|
});
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAEjD,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAEjD,MAAM,UAAU,GAAI,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,iBAAiB,CAAyB,CAAC,OAAO,CAAC;AAEtG,MAAM,CAAC,MAAM,eAAe,GAAG;;;;;;;;;;;;;;;;CAgB9B,CAAC;AAEF,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AACnC,MAAM,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;AAErB,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,MAAM,EAAE,CAAC;IAC1D,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,eAAe,CAAC,CAAC;IACtC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AAED,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,KAAK,WAAW,EAAE,CAAC;IAC1C,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,aAAa,UAAU,IAAI,CAAC,CAAC;IAClD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AAED,IAAI,IAAI,KAAK,MAAM,EAAE,CAAC;IACpB,6EAA6E;IAC7E,sEAAsE;IACtE,sDAAsD;IACtD,MAAM,EAAE,cAAc,EAAE,GAAG,MAAM,MAAM,CAAC,aAAa,CAAC,CAAC;IACvD,cAAc,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,GAAY,EAAE,EAAE;QACnD,OAAO,CAAC,KAAK,CAAC,cAAc,EAAE,GAAG,CAAC,CAAC;QACnC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC,CAAC;AACL,CAAC;KAAM,CAAC;IACN,4EAA4E;IAC5E,6EAA6E;IAC7E,4EAA4E;IAC5E,4EAA4E;IAC5E,wEAAwE;IACxE,mEAAmE;IACnE,6EAA6E;IAC7E,mBAAmB;IACnB,MAAM,UAAU,GAAG,MAAM,cAAc,EAAE,CAAC,KAAK,CAAC,CAAC,GAAY,EAAE,EAAE;QAC/D,OAAO,CAAC,KAAK,CAAC,cAAc,EAAE,GAAG,CAAC,CAAC;QACnC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,UAAU,EAAE,CAAC;QAChB,OAAO,CAAC,KAAK,CACX,qCAAqC,OAAO,CAAC,GAAG,EAAE,wDAAwD,CAC3G,CAAC;QACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,SAAS,CACP,UAAU,CAAC,OAAO,EAClB,UAAU,CAAC,UAAU,EACrB,UAAU,CAAC,YAAY,EACvB,UAAU,CAAC,WAAW,CACvB,CAAC,KAAK,CAAC,CAAC,GAAY,EAAE,EAAE;QACvB,OAAO,CAAC,KAAK,CAAC,cAAc,EAAE,GAAG,CAAC,CAAC;QACnC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Credential candidate model and state machine for fault-tolerant Gitea auth.
|
|
3
|
+
*
|
|
4
|
+
* A discovered credential is treated as an opaque candidate: the `secret`
|
|
5
|
+
* field might be a Gitea Personal Access Token, an account login password,
|
|
6
|
+
* or an OAuth token — the type cannot be determined statically from any
|
|
7
|
+
* source (`~/.git-credentials` stores whatever the user typed into git's
|
|
8
|
+
* password prompt, and `[gitea] token` / `GITEA_TOKEN` are by convention but
|
|
9
|
+
* not guarantee a PAT). The runtime therefore tries each candidate under
|
|
10
|
+
* one or more HTTP auth schemes, advancing on 401/403 until something works
|
|
11
|
+
* or every candidate × scheme is exhausted.
|
|
12
|
+
*
|
|
13
|
+
* This module is pure (no I/O, no fetch) so the state machine is unit-tested
|
|
14
|
+
* directly. Mutation is contained to per-candidate state fields; callers
|
|
15
|
+
* never replace the candidates array.
|
|
16
|
+
*
|
|
17
|
+
* SECURITY: `secret` is never logged, never interpolated into error
|
|
18
|
+
* messages, and never surfaced by `summarizeCandidates`. Diagnostic output
|
|
19
|
+
* only ever carries `secretPresent: boolean` and a masked `username`.
|
|
20
|
+
*/
|
|
21
|
+
/** Where a candidate came from. Determines scheme ordering and priority. */
|
|
22
|
+
export type CredentialSource = "gitea-config" | "env" | "credential-store";
|
|
23
|
+
/**
|
|
24
|
+
* HTTP auth scheme. Both are sent via the `Authorization` header.
|
|
25
|
+
* - `token` → `Authorization: token <PAT>` (Gitea's PAT/OAuth-token scheme)
|
|
26
|
+
* - `basic` → `Authorization: Basic base64(<username>:<secret>)` (works for
|
|
27
|
+
* both account passwords and PATs when the username matches the owner)
|
|
28
|
+
*/
|
|
29
|
+
export type AuthScheme = "token" | "basic";
|
|
30
|
+
/**
|
|
31
|
+
* A single candidate credential with its runtime state. The state fields
|
|
32
|
+
* (`status`, `nextSchemeIndex`, `lastTriedScheme`, `lastError`,
|
|
33
|
+
* `activeScheme`) are mutated by the GiteaClient as requests succeed or fail.
|
|
34
|
+
*/
|
|
35
|
+
export interface CandidateCredential {
|
|
36
|
+
source: CredentialSource;
|
|
37
|
+
/** Username from a credential-store URL. Undefined for config/env sources. */
|
|
38
|
+
username?: string;
|
|
39
|
+
/** The secret value (PAT, password, or OAuth token). Never logged. */
|
|
40
|
+
secret: string;
|
|
41
|
+
/**
|
|
42
|
+
* Ordered list of auth schemes to try for this candidate. The first scheme
|
|
43
|
+
* that succeeds is locked in via `activeScheme` and reused for subsequent
|
|
44
|
+
* requests without re-iterating.
|
|
45
|
+
*/
|
|
46
|
+
schemes: AuthScheme[];
|
|
47
|
+
/** Runtime state: pending (untried), active (verified working), exhausted. */
|
|
48
|
+
status: "pending" | "active" | "exhausted";
|
|
49
|
+
/** Index into `schemes` for the next untried scheme. */
|
|
50
|
+
nextSchemeIndex: number;
|
|
51
|
+
/** The scheme that last produced an HTTP error (for diagnostics). */
|
|
52
|
+
lastTriedScheme?: AuthScheme;
|
|
53
|
+
/**
|
|
54
|
+
* The scheme that succeeded and is in active use. Set when status becomes
|
|
55
|
+
* "active"; subsequent requests reuse it without re-iterating.
|
|
56
|
+
*/
|
|
57
|
+
activeScheme?: AuthScheme;
|
|
58
|
+
/**
|
|
59
|
+
* Short error reason from the last failed attempt (e.g. "401"). Never
|
|
60
|
+
* contains the secret or the response body.
|
|
61
|
+
*/
|
|
62
|
+
lastError?: string;
|
|
63
|
+
}
|
|
64
|
+
/** Result of credential discovery — feeds straight into GiteaClient. */
|
|
65
|
+
export interface CredentialDiscoveryResult {
|
|
66
|
+
baseUrl: string;
|
|
67
|
+
defaultOwner?: string;
|
|
68
|
+
defaultRepo?: string;
|
|
69
|
+
/** Name of the remote the values were derived from. */
|
|
70
|
+
remote?: string;
|
|
71
|
+
/** Candidates in priority order (highest priority first). */
|
|
72
|
+
candidates: CandidateCredential[];
|
|
73
|
+
}
|
|
74
|
+
/** A picked next attempt — the candidate index plus the scheme to apply. */
|
|
75
|
+
export interface Attempt {
|
|
76
|
+
candidateIndex: number;
|
|
77
|
+
scheme: AuthScheme;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Decide scheme ordering for a credential-store entry based on username
|
|
81
|
+
* heuristic. Config/env candidates always use `["token"]` (per project
|
|
82
|
+
* decision to preserve their "simple token" semantics).
|
|
83
|
+
*
|
|
84
|
+
* - Username `oauth2`, `x-oauth-basic`, or empty → `["token", "basic"]`:
|
|
85
|
+
* these are git OAuth conventions; basic auth with such a username fails
|
|
86
|
+
* for real passwords, so try `token` first.
|
|
87
|
+
* - Any other (real-looking) username → `["basic", "token"]`: basic auth
|
|
88
|
+
* with the correct username works for both PATs and passwords, so it has
|
|
89
|
+
* the widest coverage and goes first.
|
|
90
|
+
*/
|
|
91
|
+
export declare function orderSchemesForCredentialStore(username?: string): AuthScheme[];
|
|
92
|
+
/**
|
|
93
|
+
* Build the `Authorization` header value for one attempt. Mutates nothing.
|
|
94
|
+
*
|
|
95
|
+
* For `basic`, the username falls back to `oauth2` when absent (a missing
|
|
96
|
+
* username only happens for malformed credential-store entries; Gitea
|
|
97
|
+
* rejects basic auth without a username, so the attempt will fail and the
|
|
98
|
+
* state machine will advance).
|
|
99
|
+
*/
|
|
100
|
+
export declare function buildAuthHeader(candidate: CandidateCredential, scheme: AuthScheme): string;
|
|
101
|
+
/**
|
|
102
|
+
* Pick the next (candidate, scheme) to try. Skips exhausted candidates and
|
|
103
|
+
* candidates whose scheme list is fully tried. Does NOT mutate state — the
|
|
104
|
+
* caller records the attempt via `markAttemptFailed` / `markAttemptSucceeded`.
|
|
105
|
+
*
|
|
106
|
+
* Returns null when every candidate × scheme has been tried.
|
|
107
|
+
*/
|
|
108
|
+
export declare function pickNextAttempt(candidates: CandidateCredential[]): Attempt | null;
|
|
109
|
+
/**
|
|
110
|
+
* Record that an attempt failed with `error` (a short reason like "401").
|
|
111
|
+
* Advances the candidate's scheme index; when no schemes remain, marks the
|
|
112
|
+
* candidate as exhausted.
|
|
113
|
+
*/
|
|
114
|
+
export declare function markAttemptFailed(candidates: CandidateCredential[], candidateIndex: number, error: string): void;
|
|
115
|
+
/**
|
|
116
|
+
* Record that an attempt succeeded. Marks the candidate as active, locks in
|
|
117
|
+
* `activeScheme`, and marks all PRIOR candidates as exhausted (they were
|
|
118
|
+
* tried and failed before this one succeeded).
|
|
119
|
+
*/
|
|
120
|
+
export declare function markAttemptSucceeded(candidates: CandidateCredential[], candidateIndex: number, scheme: AuthScheme): void;
|
|
121
|
+
/**
|
|
122
|
+
* Mask a username for diagnostic output: first character plus `***`. Returns
|
|
123
|
+
* null when the candidate has no username (config/env sources, or malformed
|
|
124
|
+
* credential-store entries).
|
|
125
|
+
*/
|
|
126
|
+
export declare function maskUsername(username?: string): string | null;
|
|
127
|
+
/**
|
|
128
|
+
* Build a redacted summary of all candidates for the `gitea_status` tool.
|
|
129
|
+
* The `secret` value is never included — only `secretPresent: true`. The
|
|
130
|
+
* caller passes the active candidate index (or null) so the summary can flag
|
|
131
|
+
* which candidate is currently in use.
|
|
132
|
+
*/
|
|
133
|
+
export interface CandidateSummary {
|
|
134
|
+
source: CredentialSource;
|
|
135
|
+
schemes: AuthScheme[];
|
|
136
|
+
username: string | null;
|
|
137
|
+
secretPresent: boolean;
|
|
138
|
+
status: "pending" | "active" | "exhausted";
|
|
139
|
+
lastTriedScheme: AuthScheme | null;
|
|
140
|
+
activeScheme: AuthScheme | null;
|
|
141
|
+
lastError: string | null;
|
|
142
|
+
}
|
|
143
|
+
export declare function summarizeCandidates(candidates: CandidateCredential[]): CandidateSummary[];
|
|
144
|
+
/** Find the index of the active candidate, or null when none is active. */
|
|
145
|
+
export declare function findActiveCandidateIndex(candidates: CandidateCredential[]): number | null;
|