@boringstudio_org/gitea-mcp 1.4.1 → 1.7.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.
@@ -1,4 +1,4 @@
1
- name: Release
1
+ name: Release and Publish
2
2
 
3
3
  on:
4
4
  push:
@@ -25,6 +25,7 @@ jobs:
25
25
  uses: actions/setup-node@v4
26
26
  with:
27
27
  node-version: '24'
28
+ registry-url: 'https://registry.npmjs.org'
28
29
 
29
30
  - name: Configure Git
30
31
  run: |
@@ -41,3 +42,8 @@ jobs:
41
42
  run: git push --follow-tags origin main
42
43
  env:
43
44
  GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}
45
+
46
+ - name: Publish to NPM
47
+ run: npm publish
48
+ env:
49
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
package/.versionrc ADDED
@@ -0,0 +1,3 @@
1
+ {
2
+ "releaseCommitMessageFormat": "chore(release): {{currentTag}} [skip ci]"
3
+ }
package/CHANGELOG.md CHANGED
@@ -2,6 +2,24 @@
2
2
 
3
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
4
 
5
+ ### [1.7.1](https://git.boringstudio.by/BoringStudio/mcp-gitea/compare/v1.7.0...v1.7.1) (2026-01-15)
6
+
7
+ ## [1.7.0](https://git.boringstudio.by/BoringStudio/mcp-gitea/compare/v1.6.1...v1.7.0) (2026-01-15)
8
+
9
+
10
+ ### Features
11
+
12
+ * update readme with security details ([#7](https://git.boringstudio.by/BoringStudio/mcp-gitea/issues/7)) ([9c719bb](https://git.boringstudio.by/BoringStudio/mcp-gitea/commit/9c719bbf6f4e46bcba98f3656f899c9e11fd1e5e))
13
+
14
+ ### [1.6.1](https://git.boringstudio.by/BoringStudio/mcp-gitea/compare/v1.6.0...v1.6.1) (2026-01-15)
15
+
16
+ ## [1.6.0](https://git.boringstudio.by/BoringStudio/mcp-gitea/compare/v1.4.1...v1.6.0) (2026-01-15)
17
+
18
+
19
+ ### Features
20
+
21
+ * enhance security and optimization ([#5](https://git.boringstudio.by/BoringStudio/mcp-gitea/issues/5)) ([f9f7055](https://git.boringstudio.by/BoringStudio/mcp-gitea/commit/f9f70556240482149ac245357f23f1160e873e5c))
22
+
5
23
  ### 1.4.1 (2026-01-14)
6
24
 
7
25
  ### [1.3.6](https://git.boringstudio.by/BoringStudio/mcp-gitea-proxy/compare/v1.3.5...v1.3.6) (2026-01-14)
package/README.md CHANGED
@@ -12,6 +12,11 @@ This package is designed to be used as a standalone server or imported by a gate
12
12
  * **Analysis:** Analyze issues with AI prompts.
13
13
  * **Shell:** Execute safe git commands in WSL (`run_safe_shell`).
14
14
 
15
+ ## 🔒 Security
16
+
17
+ * **Allowed Paths:** Restricted via `MCP_ALLOWED_PATHS` environment variable.
18
+ * **Git Safety:** Only whitelisted git subcommands are allowed. Global flags are blocked.
19
+
15
20
  ## 📦 Installation
16
21
 
17
22
  ```bash
package/index.js CHANGED
@@ -14,7 +14,7 @@ dotenv.config({ path: path.join(process.cwd(), ".env"), quiet: true });
14
14
  export async function runGiteaServer() {
15
15
  const server = new McpServer({
16
16
  name: "gitea-proxy-agent",
17
- version: "1.6.0",
17
+ version: "1.5.0",
18
18
  });
19
19
 
20
20
  const GITEA_TOKEN = process.env.GITEA_TOKEN;
@@ -57,6 +57,24 @@ export async function runGiteaServer() {
57
57
  }
58
58
  }
59
59
 
60
+ // Cache for labels: key="owner/repo", value={ timestamp, data }
61
+ const labelCache = new Map();
62
+ const CACHE_TTL = 5 * 60 * 1000; // 5 minutes
63
+
64
+ async function getRepoLabels(owner, repo) {
65
+ const key = `${owner}/${repo}`;
66
+ const now = Date.now();
67
+ const cached = labelCache.get(key);
68
+
69
+ if (cached && (now - cached.timestamp < CACHE_TTL)) {
70
+ return cached.data;
71
+ }
72
+
73
+ const labels = await giteaApi("GET", `/repos/${owner}/${repo}/labels`);
74
+ labelCache.set(key, { timestamp: now, data: labels });
75
+ return labels;
76
+ }
77
+
60
78
  // --- RESOURCES ---
61
79
  server.resource(
62
80
  "issues-list",
@@ -116,6 +134,23 @@ export async function runGiteaServer() {
116
134
  );
117
135
 
118
136
  // --- TOOLS ---
137
+
138
+ // Security: Allowed paths for run_safe_shell
139
+ const ALLOWED_PATHS = (process.env.MCP_ALLOWED_PATHS || process.cwd()).split(',').map(p => path.resolve(p.trim()));
140
+
141
+ function isPathAllowed(targetPath) {
142
+ const resolved = path.resolve(targetPath);
143
+ return ALLOWED_PATHS.some(allowed => resolved.startsWith(allowed));
144
+ }
145
+
146
+ // Security: Allowed git subcommands
147
+ const ALLOWED_GIT_COMMANDS = [
148
+ 'status', 'add', 'commit', 'push', 'pull', 'diff', 'log', 'show',
149
+ 'checkout', 'branch', 'merge', 'remote', 'fetch', 'clone', 'init',
150
+ 'ls-files', 'ls-tree', 'rev-parse', 'clean', 'restore', 'rm', 'mv',
151
+ 'reset', 'stash', 'grep'
152
+ ];
153
+
119
154
  server.tool(
120
155
  "run_safe_shell",
121
156
  "Execute a safe shell command (git only) in a specific directory",
@@ -129,6 +164,23 @@ export async function runGiteaServer() {
129
164
  return { content: [{ type: "text", text: "Error: Only 'git' commands are allowed." }], isError: true };
130
165
  }
131
166
 
167
+ if (!isPathAllowed(cwd)) {
168
+ return { content: [{ type: "text", text: `Error: Access denied. Path '${cwd}' is not within allowed directories.` }], isError: true };
169
+ }
170
+
171
+ if (!args || args.length === 0) {
172
+ return { content: [{ type: "text", text: "Error: No git subcommand provided." }], isError: true };
173
+ }
174
+
175
+ const subcommand = args[0];
176
+ if (subcommand.startsWith('-')) {
177
+ return { content: [{ type: "text", text: "Error: Global git flags are not allowed. Start with a subcommand." }], isError: true };
178
+ }
179
+
180
+ if (!ALLOWED_GIT_COMMANDS.includes(subcommand)) {
181
+ return { content: [{ type: "text", text: `Error: Git subcommand '${subcommand}' is not allowed.` }], isError: true };
182
+ }
183
+
132
184
  return new Promise((resolve) => {
133
185
  const child = spawn(command, args, {
134
186
  cwd: cwd,
@@ -267,7 +319,7 @@ export async function runGiteaServer() {
267
319
  repo: z.string()
268
320
  },
269
321
  async ({ owner, repo }) => {
270
- const labels = await giteaApi("GET", `/repos/${owner}/${repo}/labels`);
322
+ const labels = await getRepoLabels(owner, repo);
271
323
  const formatted = labels.map(l => `${l.name} (ID: ${l.id})`).join("\n");
272
324
  return { content: [{ type: "text", text: formatted || "No labels found." }] };
273
325
  }
@@ -323,7 +375,7 @@ body: z.string().optional()
323
375
  if (body) payload.body = body;
324
376
 
325
377
  if (labels) {
326
- const repoLabels = await giteaApi("GET", `/repos/${owner}/${repo}/labels`);
378
+ const repoLabels = await getRepoLabels(owner, repo);
327
379
  const labelIds = labels.map(name => {
328
380
  const found = repoLabels.find(l => l.name === name);
329
381
  if (!found) throw new Error(`Label '${name}' not found in repository.`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@boringstudio_org/gitea-mcp",
3
- "version": "1.4.1",
3
+ "version": "1.7.1",
4
4
  "description": "A Gitea MCP Server for interacting with repositories, issues, and more.",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -1,27 +0,0 @@
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 }}