@boringstudio_org/gitea-mcp 1.4.1
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/.gitea/workflows/publish.yaml +27 -0
- package/.gitea/workflows/release.yaml +43 -0
- package/CHANGELOG.md +60 -0
- package/README.md +55 -0
- package/index.js +354 -0
- package/package.json +38 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
name: Publish to NPM
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- 'v*'
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
publish:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
steps:
|
|
12
|
+
- name: Checkout
|
|
13
|
+
uses: actions/checkout@v4
|
|
14
|
+
|
|
15
|
+
- name: Setup Node.js
|
|
16
|
+
uses: actions/setup-node@v4
|
|
17
|
+
with:
|
|
18
|
+
node-version: '24'
|
|
19
|
+
registry-url: 'https://registry.npmjs.org'
|
|
20
|
+
|
|
21
|
+
- name: Install dependencies
|
|
22
|
+
run: npm ci
|
|
23
|
+
|
|
24
|
+
- name: Publish
|
|
25
|
+
run: npm publish
|
|
26
|
+
env:
|
|
27
|
+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
- main
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
release:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
if: "!contains(github.event.head_commit.message, 'chore(release)')"
|
|
12
|
+
steps:
|
|
13
|
+
- name: Checkout
|
|
14
|
+
uses: actions/checkout@v4
|
|
15
|
+
with:
|
|
16
|
+
fetch-depth: 0
|
|
17
|
+
token: ${{ secrets.RELEASE_TOKEN }}
|
|
18
|
+
|
|
19
|
+
- name: Checkout branch
|
|
20
|
+
run: |
|
|
21
|
+
git checkout main
|
|
22
|
+
git pull origin main
|
|
23
|
+
|
|
24
|
+
- name: Setup Node.js
|
|
25
|
+
uses: actions/setup-node@v4
|
|
26
|
+
with:
|
|
27
|
+
node-version: '24'
|
|
28
|
+
|
|
29
|
+
- name: Configure Git
|
|
30
|
+
run: |
|
|
31
|
+
git config user.name "Gitea Actions"
|
|
32
|
+
git config user.email "sgmakgg@gmail.com"
|
|
33
|
+
|
|
34
|
+
- name: Install dependencies
|
|
35
|
+
run: npm ci
|
|
36
|
+
|
|
37
|
+
- name: Run standard-version
|
|
38
|
+
run: npm run release
|
|
39
|
+
|
|
40
|
+
- name: Push changes
|
|
41
|
+
run: git push --follow-tags origin main
|
|
42
|
+
env:
|
|
43
|
+
GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
|
4
|
+
|
|
5
|
+
### 1.4.1 (2026-01-14)
|
|
6
|
+
|
|
7
|
+
### [1.3.6](https://git.boringstudio.by/BoringStudio/mcp-gitea-proxy/compare/v1.3.5...v1.3.6) (2026-01-14)
|
|
8
|
+
|
|
9
|
+
### [1.3.5](https://git.boringstudio.by/BoringStudio/mcp-gitea-proxy/compare/v1.3.4...v1.3.5) (2026-01-14)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
### Bug Fixes
|
|
13
|
+
|
|
14
|
+
* Update start script to use global package and load env ([#20](https://git.boringstudio.by/BoringStudio/mcp-gitea-proxy/issues/20)) ([c4598fc](https://git.boringstudio.by/BoringStudio/mcp-gitea-proxy/commit/c4598fce2bbfcd9acc48599fe0eddde013ecedb5))
|
|
15
|
+
|
|
16
|
+
### [1.3.4](https://git.boringstudio.by/BoringStudio/mcp-gitea-proxy/compare/v1.3.3...v1.3.4) (2026-01-14)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
### Features
|
|
20
|
+
|
|
21
|
+
* Native Node.js Google Search implementation ([#19](https://git.boringstudio.by/BoringStudio/mcp-gitea-proxy/issues/19)) ([555f44a](https://git.boringstudio.by/BoringStudio/mcp-gitea-proxy/commit/555f44a9bd9017d2cd01351efe19b5b2c766b442))
|
|
22
|
+
|
|
23
|
+
### [1.3.3](https://git.boringstudio.by/BoringStudio/mcp-gitea-proxy/compare/v1.3.2...v1.3.3) (2026-01-14)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
### Bug Fixes
|
|
27
|
+
|
|
28
|
+
* Silence npx output, map Google ID, add start script and update docs ([#18](https://git.boringstudio.by/BoringStudio/mcp-gitea-proxy/issues/18)) ([7c36bdc](https://git.boringstudio.by/BoringStudio/mcp-gitea-proxy/commit/7c36bdcedd5c29281a1562a7a801c3d0ac6d3256))
|
|
29
|
+
|
|
30
|
+
### [1.3.2](https://git.boringstudio.by/BoringStudio/mcp-gitea-proxy/compare/v1.3.1...v1.3.2) (2026-01-14)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
### Bug Fixes
|
|
34
|
+
|
|
35
|
+
* Add ~/.local/bin to PATH for uvx discovery ([#16](https://git.boringstudio.by/BoringStudio/mcp-gitea-proxy/issues/16)) ([b2a25d5](https://git.boringstudio.by/BoringStudio/mcp-gitea-proxy/commit/b2a25d5fa5ec6d5d9fface87d48eb431ff9fd175))
|
|
36
|
+
|
|
37
|
+
### [1.3.1](https://git.boringstudio.by/BoringStudio/mcp-gitea-proxy/compare/v1.3.0...v1.3.1) (2026-01-14)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
### Bug Fixes
|
|
41
|
+
|
|
42
|
+
* Remove hardcoded env from npm config and update docs ([9842c48](https://git.boringstudio.by/BoringStudio/mcp-gitea-proxy/commit/9842c48e07e91a0d5b79d547782aa0aa9b6d9390))
|
|
43
|
+
|
|
44
|
+
## [1.3.0](https://git.boringstudio.by/BoringStudio/mcp-gitea-proxy/compare/v1.2.0...v1.3.0) (2026-01-13)
|
|
45
|
+
|
|
46
|
+
## 1.2.0 (2026-01-13)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
### Features
|
|
50
|
+
|
|
51
|
+
* add create_pull_request and list_branches tools (Closes [#3](https://git.boringstudio.by/BoringStudio/mcp-gitea-proxy/issues/3)) ([465d4ed](https://git.boringstudio.by/BoringStudio/mcp-gitea-proxy/commit/465d4ed1738cf7e54d2b67b7a8c5f2df70194cfe))
|
|
52
|
+
* add list_labels and update_issue with labels support (Closes [#2](https://git.boringstudio.by/BoringStudio/mcp-gitea-proxy/issues/2)) ([9006385](https://git.boringstudio.by/BoringStudio/mcp-gitea-proxy/commit/9006385ce82cd5adc5f7af9bdbf5e28c89789816))
|
|
53
|
+
* add memory schema docs and update project structure ([841b0d6](https://git.boringstudio.by/BoringStudio/mcp-gitea-proxy/commit/841b0d6075c9e9b501a7d76a7cfa10a1f228a2bb))
|
|
54
|
+
* add secure shell execution and full Gitea integration ([d22d48c](https://git.boringstudio.by/BoringStudio/mcp-gitea-proxy/commit/d22d48cb99de046b560313995b08d1c67955fb41))
|
|
55
|
+
* upgrade to v1.4.0 with Resources and Prompts ([1510e9e](https://git.boringstudio.by/BoringStudio/mcp-gitea-proxy/commit/1510e9e7e1b3d682c011a7fdc56ae2b26435c086))
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
### Bug Fixes
|
|
59
|
+
|
|
60
|
+
* Add fallback for uvx path ([dba4e21](https://git.boringstudio.by/BoringStudio/mcp-gitea-proxy/commit/dba4e213790c91a8d031c37cac9a0eaf8724664a))
|
package/README.md
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Gitea MCP Server
|
|
2
|
+
|
|
3
|
+
A Model Context Protocol (MCP) server for interacting with Gitea.
|
|
4
|
+
This package is designed to be used as a standalone server or imported by a gateway.
|
|
5
|
+
|
|
6
|
+
## 🛠 Capabilities
|
|
7
|
+
|
|
8
|
+
* **Issues:** List, search, create, update, comment.
|
|
9
|
+
* **Repositories:** List repos, branches, labels.
|
|
10
|
+
* **Files:** Read file content.
|
|
11
|
+
* **Pull Requests:** Create PRs.
|
|
12
|
+
* **Analysis:** Analyze issues with AI prompts.
|
|
13
|
+
* **Shell:** Execute safe git commands in WSL (`run_safe_shell`).
|
|
14
|
+
|
|
15
|
+
## 📦 Installation
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install @boringstudio_org/gitea-mcp
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## 🚀 Usage
|
|
22
|
+
|
|
23
|
+
### Via NPX (Recommended)
|
|
24
|
+
```bash
|
|
25
|
+
# Configure environment variables
|
|
26
|
+
export GITEA_TOKEN=your_token
|
|
27
|
+
# Optional: Defaults to https://git.boringstudio.by/api/v1
|
|
28
|
+
export GITEA_API_URL=https://git.your-instance.com/api/v1
|
|
29
|
+
|
|
30
|
+
# Run
|
|
31
|
+
npx @boringstudio_org/gitea-mcp
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### Standalone (From Source)
|
|
35
|
+
```bash
|
|
36
|
+
# Install dependencies
|
|
37
|
+
npm install
|
|
38
|
+
|
|
39
|
+
# Configure .env
|
|
40
|
+
echo "Gitea_TOKEN=..." > .env
|
|
41
|
+
|
|
42
|
+
# Run
|
|
43
|
+
node index.js
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### As a Library
|
|
47
|
+
```javascript
|
|
48
|
+
import { runGiteaServer } from "@boringstudio_org/gitea-mcp";
|
|
49
|
+
// Ensure environment variables are set
|
|
50
|
+
runGiteaServer().catch(console.error);
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## 📝 License
|
|
54
|
+
|
|
55
|
+
MIT
|
package/index.js
ADDED
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import axios from "axios";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import dotenv from "dotenv";
|
|
7
|
+
import path from "path";
|
|
8
|
+
import { fileURLToPath } from 'url';
|
|
9
|
+
import { spawn } from "child_process";
|
|
10
|
+
|
|
11
|
+
// Load .env from the current working directory
|
|
12
|
+
dotenv.config({ path: path.join(process.cwd(), ".env"), quiet: true });
|
|
13
|
+
|
|
14
|
+
export async function runGiteaServer() {
|
|
15
|
+
const server = new McpServer({
|
|
16
|
+
name: "gitea-proxy-agent",
|
|
17
|
+
version: "1.6.0",
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
const GITEA_TOKEN = process.env.GITEA_TOKEN;
|
|
21
|
+
const BASE_URL = process.env.GITEA_API_URL || "https://git.boringstudio.by/api/v1";
|
|
22
|
+
|
|
23
|
+
if (!GITEA_TOKEN) {
|
|
24
|
+
console.error("❌ Error: GITEA_TOKEN not found.");
|
|
25
|
+
console.error("Please provide a Gitea Token in a .env file (GITEA_TOKEN=your_token).");
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const headers = {
|
|
30
|
+
"Authorization": `token ${GITEA_TOKEN}`,
|
|
31
|
+
"Content-Type": "application/json"
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
// For Cloudflare Access, set CF_ID and CF_SECRET in your .env file
|
|
35
|
+
if (process.env.CF_ID && process.env.CF_SECRET) {
|
|
36
|
+
headers["CF-Access-Client-Id"] = process.env.CF_ID;
|
|
37
|
+
headers["CF-Access-Client-Secret"] = process.env.CF_SECRET;
|
|
38
|
+
console.error("INFO: Running in Cloudflare Proxy mode");
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const api = axios.create({
|
|
42
|
+
baseURL: BASE_URL,
|
|
43
|
+
headers: headers
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
async function giteaApi(method, endpoint, data = null, params = null) {
|
|
47
|
+
try {
|
|
48
|
+
const config = { method, url: endpoint };
|
|
49
|
+
if (data) config.data = data;
|
|
50
|
+
if (params) config.params = params;
|
|
51
|
+
|
|
52
|
+
const response = await api(config);
|
|
53
|
+
return response.data;
|
|
54
|
+
} catch (error) {
|
|
55
|
+
const msg = error.response?.data?.message || error.message;
|
|
56
|
+
throw new Error(`Gitea API Error: ${msg}`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// --- RESOURCES ---
|
|
61
|
+
server.resource(
|
|
62
|
+
"issues-list",
|
|
63
|
+
new ResourceTemplate("gitea://{owner}/{repo}/issues", { list: undefined }),
|
|
64
|
+
async (uri, { owner, repo }) => {
|
|
65
|
+
const issues = await giteaApi("GET", `/repos/${owner}/${repo}/issues`, null, { limit: 50 });
|
|
66
|
+
return {
|
|
67
|
+
contents: [{
|
|
68
|
+
uri: uri.href,
|
|
69
|
+
text: JSON.stringify(issues, null, 2),
|
|
70
|
+
mimeType: "application/json"
|
|
71
|
+
}]
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
server.resource(
|
|
77
|
+
"file-content",
|
|
78
|
+
new ResourceTemplate("gitea://{owner}/{repo}/files/{branch}/{path}", { list: undefined }),
|
|
79
|
+
async (uri, { owner, repo, branch, path: filePath }) => {
|
|
80
|
+
const content = await giteaApi("GET", `/repos/${owner}/${repo}/raw/${branch}/${filePath}`);
|
|
81
|
+
return {
|
|
82
|
+
contents: [{
|
|
83
|
+
uri: uri.href,
|
|
84
|
+
text: typeof content === 'string' ? content : JSON.stringify(content),
|
|
85
|
+
mimeType: "text/plain"
|
|
86
|
+
}]
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
// --- PROMPTS ---
|
|
92
|
+
server.prompt(
|
|
93
|
+
"analyze-issue",
|
|
94
|
+
{
|
|
95
|
+
owner: z.string(),
|
|
96
|
+
repo: z.string(),
|
|
97
|
+
issue_number: z.string().describe("Issue number (as string)")
|
|
98
|
+
},
|
|
99
|
+
async ({ owner, repo, issue_number }) => {
|
|
100
|
+
const issue = await giteaApi("GET", `/repos/${owner}/${repo}/issues/${issue_number}`);
|
|
101
|
+
const comments = await giteaApi("GET", `/repos/${owner}/${repo}/issues/${issue_number}/comments`);
|
|
102
|
+
return {
|
|
103
|
+
messages: [{
|
|
104
|
+
role: "user",
|
|
105
|
+
content: {
|
|
106
|
+
type: "text",
|
|
107
|
+
text: `Please analyze the following Gitea issue and suggest a solution or next steps.\n\n` +
|
|
108
|
+
`Title: ${issue.title}\n` +
|
|
109
|
+
`State: ${issue.state}\n` +
|
|
110
|
+
`Description:\n${issue.body}\n\n` +
|
|
111
|
+
`Comments:\n${comments.map(c => `- ${c.user.username}: ${c.body}`).join("\n")}`
|
|
112
|
+
}
|
|
113
|
+
}]
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
// --- TOOLS ---
|
|
119
|
+
server.tool(
|
|
120
|
+
"run_safe_shell",
|
|
121
|
+
"Execute a safe shell command (git only) in a specific directory",
|
|
122
|
+
{
|
|
123
|
+
cwd: z.string().describe("The working directory for the command"),
|
|
124
|
+
command: z.string().describe("The command to execute (must start with 'git')"),
|
|
125
|
+
args: z.array(z.string()).describe("The arguments for the command")
|
|
126
|
+
},
|
|
127
|
+
async ({ cwd, command, args }) => {
|
|
128
|
+
if (command !== 'git') {
|
|
129
|
+
return { content: [{ type: "text", text: "Error: Only 'git' commands are allowed." }], isError: true };
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return new Promise((resolve) => {
|
|
133
|
+
const child = spawn(command, args, {
|
|
134
|
+
cwd: cwd,
|
|
135
|
+
env: process.env,
|
|
136
|
+
shell: false
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
let stdout = "";
|
|
140
|
+
let stderr = "";
|
|
141
|
+
|
|
142
|
+
child.stdout.on("data", (data) => {
|
|
143
|
+
stdout += data.toString();
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
child.stderr.on("data", (data) => {
|
|
147
|
+
stderr += data.toString();
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
child.on("close", (code) => {
|
|
151
|
+
if (code === 0) {
|
|
152
|
+
resolve({
|
|
153
|
+
content: [{ type: "text", text: stdout || "Success (no output)" }]
|
|
154
|
+
});
|
|
155
|
+
} else {
|
|
156
|
+
resolve({
|
|
157
|
+
content: [{ type: "text", text: `Exit code: ${code}\nStdout: ${stdout}\nStderr: ${stderr}` }],
|
|
158
|
+
isError: true
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
child.on("error", (err) => {
|
|
164
|
+
resolve({
|
|
165
|
+
content: [{ type: "text", text: `Failed to start command: ${err.message}` }],
|
|
166
|
+
isError: true
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
server.tool(
|
|
174
|
+
"list_repos",
|
|
175
|
+
"Get a list of all user/organization repositories",
|
|
176
|
+
{ org: z.string().optional().describe("Organization name") },
|
|
177
|
+
async ({ org }) => {
|
|
178
|
+
const endpoint = org ? `/orgs/${org}/repos` : "/user/repos";
|
|
179
|
+
const repos = await giteaApi("GET", endpoint, null, { limit: 50 });
|
|
180
|
+
const formatted = repos.map(r => `${r.full_name} (ID: ${r.id}) - ${r.private ? 'Private' : 'Public'}`).join("\n");
|
|
181
|
+
return { content: [{ type: "text", text: formatted || "No repositories found." }] };
|
|
182
|
+
}
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
server.tool(
|
|
186
|
+
"list_issues",
|
|
187
|
+
"Get a list of all open issues in the repository",
|
|
188
|
+
{
|
|
189
|
+
owner: z.string(),
|
|
190
|
+
repo: z.string(),
|
|
191
|
+
page: z.number().optional(),
|
|
192
|
+
limit: z.number().optional()
|
|
193
|
+
},
|
|
194
|
+
async ({ owner, repo, page = 1, limit = 20 }) => {
|
|
195
|
+
const issues = await giteaApi("GET", `/repos/${owner}/${repo}/issues`, null, { page, limit });
|
|
196
|
+
const formatted = issues.map(i => `#${i.number}: ${i.title}`).join("\n");
|
|
197
|
+
return { content: [{ type: "text", text: formatted || "No issues yet." }] };
|
|
198
|
+
}
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
server.tool(
|
|
202
|
+
"search_issues",
|
|
203
|
+
"Search for issues in a repository using keywords",
|
|
204
|
+
{
|
|
205
|
+
owner: z.string(),
|
|
206
|
+
repo: z.string(),
|
|
207
|
+
query: z.string(),
|
|
208
|
+
state: z.enum(["open", "closed", "all"]).optional()
|
|
209
|
+
},
|
|
210
|
+
async ({ owner, repo, query, state = "open" }) => {
|
|
211
|
+
const issues = await giteaApi("GET", `/repos/${owner}/${repo}/issues`, null, { q: query, state, limit: 20 });
|
|
212
|
+
const formatted = issues.map(i => `#${i.number} (${i.state}): ${i.title}`).join("\n");
|
|
213
|
+
return { content: [{ type: "text", text: formatted || "No matching issues found." }] };
|
|
214
|
+
}
|
|
215
|
+
);
|
|
216
|
+
|
|
217
|
+
server.tool(
|
|
218
|
+
"get_issue_details",
|
|
219
|
+
"Read the full issue description and its status",
|
|
220
|
+
{
|
|
221
|
+
owner: z.string(),
|
|
222
|
+
repo: z.string(),
|
|
223
|
+
issue_number: z.number()
|
|
224
|
+
},
|
|
225
|
+
async ({ owner, repo, issue_number }) => {
|
|
226
|
+
const i = await giteaApi("GET", `/repos/${owner}/${repo}/issues/${issue_number}`);
|
|
227
|
+
const result = `ISSUE #${i.number}: ${i.title}\nStatus: ${i.state}\nDescription:\n${i.body || "No description"}`;
|
|
228
|
+
return { content: [{ type: "text", text: result }] };
|
|
229
|
+
}
|
|
230
|
+
);
|
|
231
|
+
|
|
232
|
+
server.tool(
|
|
233
|
+
"add_comment",
|
|
234
|
+
"Leave a comment on a Gitea issue",
|
|
235
|
+
{
|
|
236
|
+
owner: z.string(),
|
|
237
|
+
repo: z.string(),
|
|
238
|
+
issue_number: z.number(),
|
|
239
|
+
body: z.string()
|
|
240
|
+
},
|
|
241
|
+
async ({ owner, repo, issue_number, body }) => {
|
|
242
|
+
await giteaApi("POST", `/repos/${owner}/${repo}/issues/${issue_number}/comments`, { body });
|
|
243
|
+
return { content: [{ type: "text", text: "Success: Comment added." }] };
|
|
244
|
+
}
|
|
245
|
+
);
|
|
246
|
+
|
|
247
|
+
server.tool(
|
|
248
|
+
"create_issue",
|
|
249
|
+
"Create a new issue in the repository",
|
|
250
|
+
{
|
|
251
|
+
owner: z.string(),
|
|
252
|
+
repo: z.string(),
|
|
253
|
+
title: z.string(),
|
|
254
|
+
body: z.string().optional()
|
|
255
|
+
},
|
|
256
|
+
async ({ owner, repo, title, body }) => {
|
|
257
|
+
const response = await giteaApi("POST", `/repos/${owner}/${repo}/issues`, { title, body });
|
|
258
|
+
return { content: [{ type: "text", text: `Success: Issue #${response.number} created.` }] };
|
|
259
|
+
}
|
|
260
|
+
);
|
|
261
|
+
|
|
262
|
+
server.tool(
|
|
263
|
+
"list_labels",
|
|
264
|
+
"Get a list of all labels in the repository",
|
|
265
|
+
{
|
|
266
|
+
owner: z.string(),
|
|
267
|
+
repo: z.string()
|
|
268
|
+
},
|
|
269
|
+
async ({ owner, repo }) => {
|
|
270
|
+
const labels = await giteaApi("GET", `/repos/${owner}/${repo}/labels`);
|
|
271
|
+
const formatted = labels.map(l => `${l.name} (ID: ${l.id})`).join("\n");
|
|
272
|
+
return { content: [{ type: "text", text: formatted || "No labels found." }] };
|
|
273
|
+
}
|
|
274
|
+
);
|
|
275
|
+
|
|
276
|
+
server.tool(
|
|
277
|
+
"list_branches",
|
|
278
|
+
"List branches in the repository",
|
|
279
|
+
{
|
|
280
|
+
owner: z.string(),
|
|
281
|
+
repo: z.string()
|
|
282
|
+
},
|
|
283
|
+
async ({ owner, repo }) => {
|
|
284
|
+
const branches = await giteaApi("GET", `/repos/${owner}/${repo}/branches`);
|
|
285
|
+
const formatted = branches.map(b => b.name).join("\n");
|
|
286
|
+
return { content: [{ type: "text", text: formatted || "No branches found." }] };
|
|
287
|
+
}
|
|
288
|
+
);
|
|
289
|
+
|
|
290
|
+
server.tool(
|
|
291
|
+
"create_pull_request",
|
|
292
|
+
"Create a Pull Request",
|
|
293
|
+
{
|
|
294
|
+
owner: z.string(),
|
|
295
|
+
repo: z.string(),
|
|
296
|
+
head: z.string(),
|
|
297
|
+
base: z.string().optional(),
|
|
298
|
+
title: z.string(),
|
|
299
|
+
body: z.string().optional()
|
|
300
|
+
},
|
|
301
|
+
async ({ owner, repo, head, base = "main", title, body }) => {
|
|
302
|
+
const response = await giteaApi("POST", `/repos/${owner}/${repo}/pulls`, { head, base, title, body });
|
|
303
|
+
return { content: [{ type: "text", text: `Success: PR #${response.number} created: ${response.html_url}` }] };
|
|
304
|
+
}
|
|
305
|
+
);
|
|
306
|
+
|
|
307
|
+
server.tool(
|
|
308
|
+
"update_issue",
|
|
309
|
+
"Update an issue (change state, title, body, or labels)",
|
|
310
|
+
{
|
|
311
|
+
owner: z.string(),
|
|
312
|
+
repo: z.string(),
|
|
313
|
+
issue_number: z.number(),
|
|
314
|
+
state: z.enum(["open", "closed"]).optional(),
|
|
315
|
+
title: z.string().optional(),
|
|
316
|
+
body: z.string().optional(),
|
|
317
|
+
labels: z.array(z.string()).optional()
|
|
318
|
+
},
|
|
319
|
+
async ({ owner, repo, issue_number, state, title, body, labels }) => {
|
|
320
|
+
const payload = {};
|
|
321
|
+
if (state) payload.state = state;
|
|
322
|
+
if (title) payload.title = title;
|
|
323
|
+
if (body) payload.body = body;
|
|
324
|
+
|
|
325
|
+
if (labels) {
|
|
326
|
+
const repoLabels = await giteaApi("GET", `/repos/${owner}/${repo}/labels`);
|
|
327
|
+
const labelIds = labels.map(name => {
|
|
328
|
+
const found = repoLabels.find(l => l.name === name);
|
|
329
|
+
if (!found) throw new Error(`Label '${name}' not found in repository.`);
|
|
330
|
+
return found.id;
|
|
331
|
+
});
|
|
332
|
+
payload.labels = labelIds;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
if (Object.keys(payload).length === 0) {
|
|
336
|
+
return { content: [{ type: "text", text: "No changes requested." }] };
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
await giteaApi("PATCH", `/repos/${owner}/${repo}/issues/${issue_number}`, payload);
|
|
340
|
+
return { content: [{ type: "text", text: `Success: Issue #${issue_number} updated.` }] };
|
|
341
|
+
}
|
|
342
|
+
);
|
|
343
|
+
|
|
344
|
+
const transport = new StdioServerTransport();
|
|
345
|
+
await server.connect(transport);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// Only run if executed directly
|
|
349
|
+
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
|
350
|
+
runGiteaServer().catch(error => {
|
|
351
|
+
console.error("FATAL ERROR:", error.message);
|
|
352
|
+
process.exit(1);
|
|
353
|
+
});
|
|
354
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@boringstudio_org/gitea-mcp",
|
|
3
|
+
"version": "1.4.1",
|
|
4
|
+
"description": "A Gitea MCP Server for interacting with repositories, issues, and more.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"gitea-mcp": "./index.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"start": "node index.js",
|
|
12
|
+
"release": "standard-version"
|
|
13
|
+
},
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"@modelcontextprotocol/sdk": "^1.25.2",
|
|
16
|
+
"axios": "^1.7.9",
|
|
17
|
+
"dotenv": "^17.2.3",
|
|
18
|
+
"zod": "^3.24.1"
|
|
19
|
+
},
|
|
20
|
+
"license": "MIT",
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "https://git.boringstudio.by/BoringStudio/mcp-gitea.git"
|
|
24
|
+
},
|
|
25
|
+
"keywords": [
|
|
26
|
+
"mcp",
|
|
27
|
+
"gitea",
|
|
28
|
+
"ai",
|
|
29
|
+
"llm",
|
|
30
|
+
"server"
|
|
31
|
+
],
|
|
32
|
+
"publishConfig": {
|
|
33
|
+
"access": "public"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"standard-version": "^9.5.0"
|
|
37
|
+
}
|
|
38
|
+
}
|