@mokoconsulting/mcp-mokogitea-api 1.2.0 → 1.4.0
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/.mokogitea/workflows/npm-publish.yml +13 -10
- package/Dockerfile +18 -0
- package/README.md +4 -1
- package/dist/index.js +45 -17
- package/dist/server.d.ts +4 -0
- package/dist/server.js +12 -0
- package/dist/sse.d.ts +2 -0
- package/dist/sse.js +87 -0
- package/package.json +2 -2
- package/src/index.ts +63 -19
- package/src/server.ts +16 -0
- package/src/sse.ts +100 -0
- package/tsconfig.json +1 -1
- package/wiki/ARCHITECTURE.md +144 -0
- package/wiki/Home.md +35 -0
- package/wiki/INSTALLATION.md +170 -0
|
@@ -28,24 +28,27 @@ jobs:
|
|
|
28
28
|
- name: Build
|
|
29
29
|
run: npm run build
|
|
30
30
|
|
|
31
|
-
- name:
|
|
32
|
-
id: version
|
|
31
|
+
- name: Auto-bump patch version
|
|
33
32
|
run: |
|
|
34
33
|
PKG_NAME=$(node -p "require('./package.json').name")
|
|
35
|
-
|
|
36
|
-
PUBLISHED=$(npm view "${PKG_NAME}
|
|
37
|
-
if [ "$
|
|
38
|
-
|
|
39
|
-
|
|
34
|
+
CURRENT=$(node -p "require('./package.json').version")
|
|
35
|
+
PUBLISHED=$(npm view "${PKG_NAME}@latest" version 2>/dev/null || echo "0.0.0")
|
|
36
|
+
if [ "$CURRENT" = "$PUBLISHED" ]; then
|
|
37
|
+
npm version patch --no-git-tag-version
|
|
38
|
+
NEW_VER=$(node -p "require('./package.json').version")
|
|
39
|
+
echo "Bumped ${CURRENT} -> ${NEW_VER}"
|
|
40
|
+
git config user.name "github-actions[bot]"
|
|
41
|
+
git config user.email "github-actions[bot]@users.noreply.github.com"
|
|
42
|
+
git add package.json
|
|
43
|
+
git commit -m "chore: bump to ${NEW_VER} [skip ci]"
|
|
44
|
+
git push
|
|
40
45
|
else
|
|
41
|
-
echo "
|
|
42
|
-
echo "Publishing ${PKG_NAME}@${PKG_VERSION}"
|
|
46
|
+
echo "Version ${CURRENT} not yet published, using as-is."
|
|
43
47
|
fi
|
|
44
48
|
env:
|
|
45
49
|
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
46
50
|
|
|
47
51
|
- name: Publish
|
|
48
|
-
if: steps.version.outputs.skip != 'true'
|
|
49
52
|
run: npm publish --access public
|
|
50
53
|
env:
|
|
51
54
|
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
package/Dockerfile
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
FROM node:20-alpine
|
|
2
|
+
|
|
3
|
+
WORKDIR /app
|
|
4
|
+
|
|
5
|
+
COPY package.json package-lock.json ./
|
|
6
|
+
RUN npm ci --production=false
|
|
7
|
+
|
|
8
|
+
COPY tsconfig.json ./
|
|
9
|
+
COPY src/ ./src/
|
|
10
|
+
RUN npx tsc && npm prune --production
|
|
11
|
+
|
|
12
|
+
EXPOSE 3100
|
|
13
|
+
|
|
14
|
+
ENV PORT=3100
|
|
15
|
+
ENV NODE_ENV=production
|
|
16
|
+
|
|
17
|
+
# SSE mode by default for Docker deployments
|
|
18
|
+
CMD ["node", "dist/sse.js"]
|
package/README.md
CHANGED
|
@@ -250,12 +250,15 @@ If `connection` is omitted, the `defaultConnection` is used.
|
|
|
250
250
|
| `gitea_webhooks_list` | List webhooks for a repository |
|
|
251
251
|
| `gitea_webhook_create` | Create a webhook |
|
|
252
252
|
|
|
253
|
-
### Wiki (
|
|
253
|
+
### Wiki (5 tools)
|
|
254
254
|
|
|
255
255
|
| Tool | Description |
|
|
256
256
|
|------|-------------|
|
|
257
257
|
| `gitea_wiki_pages_list` | List wiki pages |
|
|
258
258
|
| `gitea_wiki_page_get` | Get a wiki page |
|
|
259
|
+
| `gitea_wiki_page_create` | Create a new wiki page |
|
|
260
|
+
| `gitea_wiki_page_edit` | Edit an existing wiki page |
|
|
261
|
+
| `gitea_wiki_page_delete` | Delete a wiki page |
|
|
259
262
|
|
|
260
263
|
### Notifications (2 tools)
|
|
261
264
|
|
package/dist/index.js
CHANGED
|
@@ -690,6 +690,43 @@ server.tool('gitea_webhook_create', 'Create a webhook', {
|
|
|
690
690
|
// ── Wiki ────────────────────────────────────────────────────────────────
|
|
691
691
|
server.tool('gitea_wiki_pages_list', 'List wiki pages', { ...OwnerRepo, ...PaginationParams, ...ConnectionParam }, async ({ owner, repo, page, limit, connection }) => formatResponse(await clientFor(connection).get(`/repos/${owner}/${repo}/wiki/pages`, pageQuery({ page, limit }))));
|
|
692
692
|
server.tool('gitea_wiki_page_get', 'Get a wiki page', { ...OwnerRepo, page_name: z.string().describe('Page name/slug'), ...ConnectionParam }, async ({ owner, repo, page_name, connection }) => formatResponse(await clientFor(connection).get(`/repos/${owner}/${repo}/wiki/page/${page_name}`)));
|
|
693
|
+
server.tool('gitea_wiki_page_create', 'Create a new wiki page', {
|
|
694
|
+
...OwnerRepo,
|
|
695
|
+
title: z.string().describe('Page title'),
|
|
696
|
+
content: z.string().describe('Page content (markdown)'),
|
|
697
|
+
message: z.string().optional().describe('Commit message for the wiki change'),
|
|
698
|
+
...ConnectionParam,
|
|
699
|
+
}, async ({ owner, repo, title, content, message, connection }) => {
|
|
700
|
+
const body = {
|
|
701
|
+
title,
|
|
702
|
+
content_base64: Buffer.from(content).toString('base64'),
|
|
703
|
+
};
|
|
704
|
+
if (message !== undefined)
|
|
705
|
+
body.message = message;
|
|
706
|
+
return formatResponse(await clientFor(connection).post(`/repos/${owner}/${repo}/wiki/pages`, body));
|
|
707
|
+
});
|
|
708
|
+
server.tool('gitea_wiki_page_edit', 'Edit an existing wiki page', {
|
|
709
|
+
...OwnerRepo,
|
|
710
|
+
page_name: z.string().describe('Current page name/slug'),
|
|
711
|
+
content: z.string().describe('New page content (markdown)'),
|
|
712
|
+
title: z.string().optional().describe('New page title (to rename)'),
|
|
713
|
+
message: z.string().optional().describe('Commit message for the wiki change'),
|
|
714
|
+
...ConnectionParam,
|
|
715
|
+
}, async ({ owner, repo, page_name, content, title, message, connection }) => {
|
|
716
|
+
const body = {
|
|
717
|
+
content_base64: Buffer.from(content).toString('base64'),
|
|
718
|
+
};
|
|
719
|
+
if (title !== undefined)
|
|
720
|
+
body.title = title;
|
|
721
|
+
if (message !== undefined)
|
|
722
|
+
body.message = message;
|
|
723
|
+
return formatResponse(await clientFor(connection).patch(`/repos/${owner}/${repo}/wiki/page/${page_name}`, body));
|
|
724
|
+
});
|
|
725
|
+
server.tool('gitea_wiki_page_delete', 'Delete a wiki page', {
|
|
726
|
+
...OwnerRepo,
|
|
727
|
+
page_name: z.string().describe('Page name/slug to delete'),
|
|
728
|
+
...ConnectionParam,
|
|
729
|
+
}, async ({ owner, repo, page_name, connection }) => formatResponse(await clientFor(connection).delete(`/repos/${owner}/${repo}/wiki/page/${page_name}`)));
|
|
693
730
|
// ── Notifications ───────────────────────────────────────────────────────
|
|
694
731
|
server.tool('gitea_notifications_list', 'List notifications for the authenticated user', {
|
|
695
732
|
status_types: z.string().optional().describe('Comma-separated: read, unread, pinned'),
|
|
@@ -1063,48 +1100,39 @@ server.tool('gitea_list_connections', 'List configured Gitea connections', {}, a
|
|
|
1063
1100
|
});
|
|
1064
1101
|
return { content: [{ type: 'text', text: `Configured connections:\n${lines.join('\n')}` }] };
|
|
1065
1102
|
});
|
|
1066
|
-
// ──
|
|
1067
|
-
server.tool('
|
|
1103
|
+
// ── Metadata ────────────────────────────────────────────────────────────
|
|
1104
|
+
server.tool('gitea_metadata_get', 'Get repo metadata (project identity, governance, distribution, build settings)', {
|
|
1068
1105
|
owner: z.string().describe('Repository owner'),
|
|
1069
1106
|
repo: z.string().describe('Repository name'),
|
|
1070
1107
|
...ConnectionParam,
|
|
1071
1108
|
}, async ({ owner, repo, connection }) => {
|
|
1072
1109
|
const c = clientFor(connection);
|
|
1073
|
-
return formatResponse(await c.get(`/repos/${owner}/${repo}/
|
|
1110
|
+
return formatResponse(await c.get(`/repos/${owner}/${repo}/metadata`));
|
|
1074
1111
|
});
|
|
1075
|
-
server.tool('
|
|
1112
|
+
server.tool('gitea_metadata_update', 'Update repo metadata settings (merges with existing — only provided fields are changed)', {
|
|
1076
1113
|
owner: z.string().describe('Repository owner'),
|
|
1077
1114
|
repo: z.string().describe('Repository name'),
|
|
1078
|
-
name: z.string().optional().describe('Project name
|
|
1115
|
+
name: z.string().optional().describe('Project name'),
|
|
1079
1116
|
org: z.string().optional().describe('Organization'),
|
|
1080
|
-
description: z.string().optional().describe('Project description'),
|
|
1081
1117
|
version: z.string().optional().describe('Version string (e.g. 06.00.00)'),
|
|
1082
1118
|
version_prefix: z.string().optional().describe('Tag prefix for version display (e.g. v1.26.1-moko.)'),
|
|
1083
|
-
element_name: z.string().optional().describe('Full element name override (e.g. pkg_mokowaas). Auto-constructed from type + name if empty.'),
|
|
1084
1119
|
license_spdx: z.string().optional().describe('SPDX license identifier'),
|
|
1085
|
-
license_name: z.string().optional().describe('Human-readable license name'),
|
|
1086
1120
|
platform: z.string().optional().describe('Platform (joomla, wordpress, dolibarr, go, mcp, platform, generic)'),
|
|
1087
|
-
standards_version: z.string().optional().describe('MokoPlatform standards version'),
|
|
1088
|
-
standards_source: z.string().optional().describe('URL to standards repo'),
|
|
1089
|
-
display_name: z.string().optional().describe('Human-readable name for update feeds (e.g. Package - MokoWaaS)'),
|
|
1090
|
-
maintainer: z.string().optional().describe('Maintainer/author name'),
|
|
1091
|
-
maintainer_url: z.string().optional().describe('Maintainer website URL'),
|
|
1092
1121
|
info_url: z.string().optional().describe('Extension info/product page URL'),
|
|
1093
1122
|
target_version: z.string().optional().describe('Target platform version regex (e.g. (5|6)\\.*)'),
|
|
1094
1123
|
php_minimum: z.string().optional().describe('Minimum PHP version (e.g. 8.1)'),
|
|
1095
|
-
|
|
1096
|
-
package_type: z.string().optional().describe('Package type (application, library, component, module, plugin, package, template, file)'),
|
|
1124
|
+
package_type: z.string().optional().describe('Extension type (component, module, plugin, package, template, library, file)'),
|
|
1097
1125
|
entry_point: z.string().optional().describe('Build entry point path'),
|
|
1098
1126
|
...ConnectionParam,
|
|
1099
1127
|
}, async ({ owner, repo, connection, ...fields }) => {
|
|
1100
1128
|
const c = clientFor(connection);
|
|
1101
|
-
const current = await c.get(`/repos/${owner}/${repo}/
|
|
1129
|
+
const current = await c.get(`/repos/${owner}/${repo}/metadata`);
|
|
1102
1130
|
const merged = { ...current.data };
|
|
1103
1131
|
for (const [k, v] of Object.entries(fields)) {
|
|
1104
1132
|
if (v !== undefined)
|
|
1105
1133
|
merged[k] = v;
|
|
1106
1134
|
}
|
|
1107
|
-
return formatResponse(await c.put(`/repos/${owner}/${repo}/
|
|
1135
|
+
return formatResponse(await c.put(`/repos/${owner}/${repo}/metadata`, merged));
|
|
1108
1136
|
});
|
|
1109
1137
|
// ── Start Server ────────────────────────────────────────────────────────
|
|
1110
1138
|
async function main() {
|
package/dist/server.d.ts
ADDED
package/dist/server.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
|
|
2
|
+
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
3
|
+
//
|
|
4
|
+
// Creates a configured MCP server instance for use by both stdio and SSE transports.
|
|
5
|
+
// Import index.ts to register all tools on its exported `server` singleton,
|
|
6
|
+
// then re-export a factory that initializes config and returns the server.
|
|
7
|
+
import { server, initConfig } from './index.js';
|
|
8
|
+
export function createMcpServer(cfg) {
|
|
9
|
+
initConfig(cfg);
|
|
10
|
+
return server;
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=server.js.map
|
package/dist/sse.d.ts
ADDED
package/dist/sse.js
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
|
|
2
|
+
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
3
|
+
//
|
|
4
|
+
// SSE transport entry point for MokoGitea MCP server.
|
|
5
|
+
// Run with: node dist/sse.js
|
|
6
|
+
// Or: GITEA_URL=https://gitea.example.com GITEA_TOKEN=xxx node dist/sse.js
|
|
7
|
+
//
|
|
8
|
+
// Listens on PORT (default 3100) and serves SSE at /sse with POST at /message.
|
|
9
|
+
import { createServer } from 'node:http';
|
|
10
|
+
import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
|
|
11
|
+
import { createMcpServer } from './server.js';
|
|
12
|
+
import { loadConfig } from './config.js';
|
|
13
|
+
const PORT = parseInt(process.env.PORT ?? '3100', 10);
|
|
14
|
+
async function main() {
|
|
15
|
+
const config = await loadConfig();
|
|
16
|
+
const transports = new Map();
|
|
17
|
+
const httpServer = createServer(async (req, res) => {
|
|
18
|
+
// CORS headers for browser clients
|
|
19
|
+
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
20
|
+
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
|
21
|
+
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
|
|
22
|
+
if (req.method === 'OPTIONS') {
|
|
23
|
+
res.writeHead(204);
|
|
24
|
+
res.end();
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
// Health check
|
|
28
|
+
if (req.url === '/health') {
|
|
29
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
30
|
+
res.end(JSON.stringify({ status: 'ok', tools: 120 }));
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
// SSE endpoint - client connects here
|
|
34
|
+
if (req.url === '/sse' && req.method === 'GET') {
|
|
35
|
+
const transport = new SSEServerTransport('/message', res);
|
|
36
|
+
const sessionId = transport.sessionId;
|
|
37
|
+
transports.set(sessionId, transport);
|
|
38
|
+
const server = createMcpServer(config);
|
|
39
|
+
await server.connect(transport);
|
|
40
|
+
req.on('close', () => {
|
|
41
|
+
transports.delete(sessionId);
|
|
42
|
+
});
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
// Message endpoint - client sends tool calls here
|
|
46
|
+
if (req.url?.startsWith('/message') && req.method === 'POST') {
|
|
47
|
+
const url = new URL(req.url, `http://${req.headers.host}`);
|
|
48
|
+
const sessionId = url.searchParams.get('sessionId');
|
|
49
|
+
if (!sessionId || !transports.has(sessionId)) {
|
|
50
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
51
|
+
res.end(JSON.stringify({ error: 'Invalid or missing sessionId' }));
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
const transport = transports.get(sessionId);
|
|
55
|
+
await transport.handlePostMessage(req, res);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
// Root - info page
|
|
59
|
+
if (req.url === '/' || req.url === '') {
|
|
60
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
61
|
+
res.end(JSON.stringify({
|
|
62
|
+
name: '@mokoconsulting/mokogitea-mcp',
|
|
63
|
+
version: '1.1.0',
|
|
64
|
+
description: 'MCP server for Gitea and MokoGitea - 120+ tools',
|
|
65
|
+
endpoints: {
|
|
66
|
+
sse: '/sse',
|
|
67
|
+
message: '/message',
|
|
68
|
+
health: '/health',
|
|
69
|
+
},
|
|
70
|
+
docs: 'https://git.mokoconsulting.tech/MokoConsulting/mcp_mokogitea_api',
|
|
71
|
+
}));
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
res.writeHead(404);
|
|
75
|
+
res.end('Not found');
|
|
76
|
+
});
|
|
77
|
+
httpServer.listen(PORT, () => {
|
|
78
|
+
process.stderr.write(`MokoGitea MCP SSE server listening on port ${PORT}\n`);
|
|
79
|
+
process.stderr.write(` SSE: http://localhost:${PORT}/sse\n`);
|
|
80
|
+
process.stderr.write(` Health: http://localhost:${PORT}/health\n`);
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
main().catch((err) => {
|
|
84
|
+
process.stderr.write(`Fatal: ${err}\n`);
|
|
85
|
+
process.exit(1);
|
|
86
|
+
});
|
|
87
|
+
//# sourceMappingURL=sse.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mokoconsulting/mcp-mokogitea-api",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"description": "MCP server for Gitea REST API v1 operations",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -29,6 +29,6 @@
|
|
|
29
29
|
"author": "Moko Consulting <hello@mokoconsulting.tech>",
|
|
30
30
|
"repository": {
|
|
31
31
|
"type": "git",
|
|
32
|
-
"url": "https://git.mokoconsulting.tech/MokoConsulting/
|
|
32
|
+
"url": "https://git.mokoconsulting.tech/MokoConsulting/mcp-mokogitea-api.git"
|
|
33
33
|
}
|
|
34
34
|
}
|
package/src/index.ts
CHANGED
|
@@ -1059,6 +1059,59 @@ server.tool(
|
|
|
1059
1059
|
async ({ owner, repo, page_name, connection }) => formatResponse(await clientFor(connection).get(`/repos/${owner}/${repo}/wiki/page/${page_name}`)),
|
|
1060
1060
|
);
|
|
1061
1061
|
|
|
1062
|
+
server.tool(
|
|
1063
|
+
'gitea_wiki_page_create',
|
|
1064
|
+
'Create a new wiki page',
|
|
1065
|
+
{
|
|
1066
|
+
...OwnerRepo,
|
|
1067
|
+
title: z.string().describe('Page title'),
|
|
1068
|
+
content: z.string().describe('Page content (markdown)'),
|
|
1069
|
+
message: z.string().optional().describe('Commit message for the wiki change'),
|
|
1070
|
+
...ConnectionParam,
|
|
1071
|
+
},
|
|
1072
|
+
async ({ owner, repo, title, content, message, connection }) => {
|
|
1073
|
+
const body: Record<string, unknown> = {
|
|
1074
|
+
title,
|
|
1075
|
+
content_base64: Buffer.from(content).toString('base64'),
|
|
1076
|
+
};
|
|
1077
|
+
if (message !== undefined) body.message = message;
|
|
1078
|
+
return formatResponse(await clientFor(connection).post(`/repos/${owner}/${repo}/wiki/pages`, body));
|
|
1079
|
+
},
|
|
1080
|
+
);
|
|
1081
|
+
|
|
1082
|
+
server.tool(
|
|
1083
|
+
'gitea_wiki_page_edit',
|
|
1084
|
+
'Edit an existing wiki page',
|
|
1085
|
+
{
|
|
1086
|
+
...OwnerRepo,
|
|
1087
|
+
page_name: z.string().describe('Current page name/slug'),
|
|
1088
|
+
content: z.string().describe('New page content (markdown)'),
|
|
1089
|
+
title: z.string().optional().describe('New page title (to rename)'),
|
|
1090
|
+
message: z.string().optional().describe('Commit message for the wiki change'),
|
|
1091
|
+
...ConnectionParam,
|
|
1092
|
+
},
|
|
1093
|
+
async ({ owner, repo, page_name, content, title, message, connection }) => {
|
|
1094
|
+
const body: Record<string, unknown> = {
|
|
1095
|
+
content_base64: Buffer.from(content).toString('base64'),
|
|
1096
|
+
};
|
|
1097
|
+
if (title !== undefined) body.title = title;
|
|
1098
|
+
if (message !== undefined) body.message = message;
|
|
1099
|
+
return formatResponse(await clientFor(connection).patch(`/repos/${owner}/${repo}/wiki/page/${page_name}`, body));
|
|
1100
|
+
},
|
|
1101
|
+
);
|
|
1102
|
+
|
|
1103
|
+
server.tool(
|
|
1104
|
+
'gitea_wiki_page_delete',
|
|
1105
|
+
'Delete a wiki page',
|
|
1106
|
+
{
|
|
1107
|
+
...OwnerRepo,
|
|
1108
|
+
page_name: z.string().describe('Page name/slug to delete'),
|
|
1109
|
+
...ConnectionParam,
|
|
1110
|
+
},
|
|
1111
|
+
async ({ owner, repo, page_name, connection }) =>
|
|
1112
|
+
formatResponse(await clientFor(connection).delete(`/repos/${owner}/${repo}/wiki/page/${page_name}`)),
|
|
1113
|
+
);
|
|
1114
|
+
|
|
1062
1115
|
// ── Notifications ───────────────────────────────────────────────────────
|
|
1063
1116
|
|
|
1064
1117
|
server.tool(
|
|
@@ -1643,11 +1696,11 @@ server.tool(
|
|
|
1643
1696
|
},
|
|
1644
1697
|
);
|
|
1645
1698
|
|
|
1646
|
-
// ──
|
|
1699
|
+
// ── Metadata ────────────────────────────────────────────────────────────
|
|
1647
1700
|
|
|
1648
1701
|
server.tool(
|
|
1649
|
-
'
|
|
1650
|
-
'Get
|
|
1702
|
+
'gitea_metadata_get',
|
|
1703
|
+
'Get repo metadata (project identity, governance, distribution, build settings)',
|
|
1651
1704
|
{
|
|
1652
1705
|
owner: z.string().describe('Repository owner'),
|
|
1653
1706
|
repo: z.string().describe('Repository name'),
|
|
@@ -1655,46 +1708,37 @@ server.tool(
|
|
|
1655
1708
|
},
|
|
1656
1709
|
async ({ owner, repo, connection }) => {
|
|
1657
1710
|
const c = clientFor(connection);
|
|
1658
|
-
return formatResponse(await c.get(`/repos/${owner}/${repo}/
|
|
1711
|
+
return formatResponse(await c.get(`/repos/${owner}/${repo}/metadata`));
|
|
1659
1712
|
},
|
|
1660
1713
|
);
|
|
1661
1714
|
|
|
1662
1715
|
server.tool(
|
|
1663
|
-
'
|
|
1664
|
-
'Update
|
|
1716
|
+
'gitea_metadata_update',
|
|
1717
|
+
'Update repo metadata settings (merges with existing — only provided fields are changed)',
|
|
1665
1718
|
{
|
|
1666
1719
|
owner: z.string().describe('Repository owner'),
|
|
1667
1720
|
repo: z.string().describe('Repository name'),
|
|
1668
|
-
name: z.string().optional().describe('Project name
|
|
1721
|
+
name: z.string().optional().describe('Project name'),
|
|
1669
1722
|
org: z.string().optional().describe('Organization'),
|
|
1670
|
-
description: z.string().optional().describe('Project description'),
|
|
1671
1723
|
version: z.string().optional().describe('Version string (e.g. 06.00.00)'),
|
|
1672
1724
|
version_prefix: z.string().optional().describe('Tag prefix for version display (e.g. v1.26.1-moko.)'),
|
|
1673
|
-
element_name: z.string().optional().describe('Full element name override (e.g. pkg_mokowaas). Auto-constructed from type + name if empty.'),
|
|
1674
1725
|
license_spdx: z.string().optional().describe('SPDX license identifier'),
|
|
1675
|
-
license_name: z.string().optional().describe('Human-readable license name'),
|
|
1676
1726
|
platform: z.string().optional().describe('Platform (joomla, wordpress, dolibarr, go, mcp, platform, generic)'),
|
|
1677
|
-
standards_version: z.string().optional().describe('MokoPlatform standards version'),
|
|
1678
|
-
standards_source: z.string().optional().describe('URL to standards repo'),
|
|
1679
|
-
display_name: z.string().optional().describe('Human-readable name for update feeds (e.g. Package - MokoWaaS)'),
|
|
1680
|
-
maintainer: z.string().optional().describe('Maintainer/author name'),
|
|
1681
|
-
maintainer_url: z.string().optional().describe('Maintainer website URL'),
|
|
1682
1727
|
info_url: z.string().optional().describe('Extension info/product page URL'),
|
|
1683
1728
|
target_version: z.string().optional().describe('Target platform version regex (e.g. (5|6)\\.*)'),
|
|
1684
1729
|
php_minimum: z.string().optional().describe('Minimum PHP version (e.g. 8.1)'),
|
|
1685
|
-
|
|
1686
|
-
package_type: z.string().optional().describe('Package type (application, library, component, module, plugin, package, template, file)'),
|
|
1730
|
+
package_type: z.string().optional().describe('Extension type (component, module, plugin, package, template, library, file)'),
|
|
1687
1731
|
entry_point: z.string().optional().describe('Build entry point path'),
|
|
1688
1732
|
...ConnectionParam,
|
|
1689
1733
|
},
|
|
1690
1734
|
async ({ owner, repo, connection, ...fields }) => {
|
|
1691
1735
|
const c = clientFor(connection);
|
|
1692
|
-
const current = await c.get(`/repos/${owner}/${repo}/
|
|
1736
|
+
const current = await c.get(`/repos/${owner}/${repo}/metadata`);
|
|
1693
1737
|
const merged = { ...(current.data as Record<string, unknown>) };
|
|
1694
1738
|
for (const [k, v] of Object.entries(fields)) {
|
|
1695
1739
|
if (v !== undefined) merged[k] = v;
|
|
1696
1740
|
}
|
|
1697
|
-
return formatResponse(await c.put(`/repos/${owner}/${repo}/
|
|
1741
|
+
return formatResponse(await c.put(`/repos/${owner}/${repo}/metadata`, merged));
|
|
1698
1742
|
},
|
|
1699
1743
|
);
|
|
1700
1744
|
|
package/src/server.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
|
|
2
|
+
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
3
|
+
//
|
|
4
|
+
// Creates a configured MCP server instance for use by both stdio and SSE transports.
|
|
5
|
+
|
|
6
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
7
|
+
import type { GiteaConfig } from './types.js';
|
|
8
|
+
|
|
9
|
+
// Import index.ts to register all tools on its exported `server` singleton,
|
|
10
|
+
// then re-export a factory that initializes config and returns the server.
|
|
11
|
+
import { server, initConfig } from './index.js';
|
|
12
|
+
|
|
13
|
+
export function createMcpServer(cfg: GiteaConfig): McpServer {
|
|
14
|
+
initConfig(cfg);
|
|
15
|
+
return server;
|
|
16
|
+
}
|
package/src/sse.ts
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
|
|
2
|
+
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
3
|
+
//
|
|
4
|
+
// SSE transport entry point for MokoGitea MCP server.
|
|
5
|
+
// Run with: node dist/sse.js
|
|
6
|
+
// Or: GITEA_URL=https://gitea.example.com GITEA_TOKEN=xxx node dist/sse.js
|
|
7
|
+
//
|
|
8
|
+
// Listens on PORT (default 3100) and serves SSE at /sse with POST at /message.
|
|
9
|
+
|
|
10
|
+
import { createServer } from 'node:http';
|
|
11
|
+
import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
|
|
12
|
+
import { createMcpServer } from './server.js';
|
|
13
|
+
import { loadConfig } from './config.js';
|
|
14
|
+
|
|
15
|
+
const PORT = parseInt(process.env.PORT ?? '3100', 10);
|
|
16
|
+
|
|
17
|
+
async function main(): Promise<void> {
|
|
18
|
+
const config = await loadConfig();
|
|
19
|
+
const transports = new Map<string, SSEServerTransport>();
|
|
20
|
+
|
|
21
|
+
const httpServer = createServer(async (req, res) => {
|
|
22
|
+
// CORS headers for browser clients
|
|
23
|
+
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
24
|
+
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
|
25
|
+
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
|
|
26
|
+
|
|
27
|
+
if (req.method === 'OPTIONS') {
|
|
28
|
+
res.writeHead(204);
|
|
29
|
+
res.end();
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Health check
|
|
34
|
+
if (req.url === '/health') {
|
|
35
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
36
|
+
res.end(JSON.stringify({ status: 'ok', tools: 120 }));
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// SSE endpoint - client connects here
|
|
41
|
+
if (req.url === '/sse' && req.method === 'GET') {
|
|
42
|
+
const transport = new SSEServerTransport('/message', res);
|
|
43
|
+
const sessionId = transport.sessionId;
|
|
44
|
+
transports.set(sessionId, transport);
|
|
45
|
+
|
|
46
|
+
const server = createMcpServer(config);
|
|
47
|
+
await server.connect(transport);
|
|
48
|
+
|
|
49
|
+
req.on('close', () => {
|
|
50
|
+
transports.delete(sessionId);
|
|
51
|
+
});
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Message endpoint - client sends tool calls here
|
|
56
|
+
if (req.url?.startsWith('/message') && req.method === 'POST') {
|
|
57
|
+
const url = new URL(req.url, `http://${req.headers.host}`);
|
|
58
|
+
const sessionId = url.searchParams.get('sessionId');
|
|
59
|
+
if (!sessionId || !transports.has(sessionId)) {
|
|
60
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
61
|
+
res.end(JSON.stringify({ error: 'Invalid or missing sessionId' }));
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
const transport = transports.get(sessionId)!;
|
|
65
|
+
await transport.handlePostMessage(req, res);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Root - info page
|
|
70
|
+
if (req.url === '/' || req.url === '') {
|
|
71
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
72
|
+
res.end(JSON.stringify({
|
|
73
|
+
name: '@mokoconsulting/mokogitea-mcp',
|
|
74
|
+
version: '1.1.0',
|
|
75
|
+
description: 'MCP server for Gitea and MokoGitea - 120+ tools',
|
|
76
|
+
endpoints: {
|
|
77
|
+
sse: '/sse',
|
|
78
|
+
message: '/message',
|
|
79
|
+
health: '/health',
|
|
80
|
+
},
|
|
81
|
+
docs: 'https://git.mokoconsulting.tech/MokoConsulting/mcp_mokogitea_api',
|
|
82
|
+
}));
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
res.writeHead(404);
|
|
87
|
+
res.end('Not found');
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
httpServer.listen(PORT, () => {
|
|
91
|
+
process.stderr.write(`MokoGitea MCP SSE server listening on port ${PORT}\n`);
|
|
92
|
+
process.stderr.write(` SSE: http://localhost:${PORT}/sse\n`);
|
|
93
|
+
process.stderr.write(` Health: http://localhost:${PORT}/health\n`);
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
main().catch((err) => {
|
|
98
|
+
process.stderr.write(`Fatal: ${err}\n`);
|
|
99
|
+
process.exit(1);
|
|
100
|
+
});
|
package/tsconfig.json
CHANGED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
← [Home](Home)
|
|
2
|
+
|
|
3
|
+
# Architecture
|
|
4
|
+
|
|
5
|
+
## Component Overview
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
src/
|
|
9
|
+
index.ts -- MCP server entry point, tool registrations (61 tools)
|
|
10
|
+
client.ts -- HTTP client for Gitea REST API v1
|
|
11
|
+
config.ts -- Configuration loader (multi-connection support)
|
|
12
|
+
types.ts -- TypeScript type definitions
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
### index.ts -- Server Entry Point
|
|
16
|
+
|
|
17
|
+
The main module that:
|
|
18
|
+
|
|
19
|
+
- Creates the `McpServer` instance with stdio transport
|
|
20
|
+
- Loads configuration on startup via `loadConfig()`
|
|
21
|
+
- Registers all 61 tools organized by category
|
|
22
|
+
- Provides shared parameter schemas (`OwnerRepo`, `PaginationParams`, `ConnectionParam`)
|
|
23
|
+
- Routes tool calls through `clientFor(connection)` to resolve the target Gitea instance
|
|
24
|
+
- Formats all API responses through `formatResponse()` for consistent MCP output
|
|
25
|
+
|
|
26
|
+
### client.ts -- HTTP Client
|
|
27
|
+
|
|
28
|
+
`GiteaClient` is a zero-dependency HTTP client built on `node:https` and `node:http`:
|
|
29
|
+
|
|
30
|
+
- Prepends `/api/v1` to all endpoint paths
|
|
31
|
+
- Sets `Authorization: token <token>` header on every request (Gitea's native auth format)
|
|
32
|
+
- Supports `GET`, `POST`, `PUT`, `PATCH`, `DELETE` methods
|
|
33
|
+
- Handles query parameter construction via `URL.searchParams`
|
|
34
|
+
- Parses JSON responses automatically
|
|
35
|
+
- 30-second request timeout
|
|
36
|
+
- Optional TLS certificate verification bypass (`insecure` flag)
|
|
37
|
+
|
|
38
|
+
### config.ts -- Configuration Loader
|
|
39
|
+
|
|
40
|
+
Loads the connection configuration from disk:
|
|
41
|
+
|
|
42
|
+
- Default path: `~/.gitea-api-mcp.json`
|
|
43
|
+
- Override via `GITEA_API_MCP_CONFIG` environment variable
|
|
44
|
+
- Validates that at least one connection exists
|
|
45
|
+
- Sets `defaultConnection` to the first connection if not explicitly specified
|
|
46
|
+
- `getConnection()` resolves a named connection or falls back to the default
|
|
47
|
+
|
|
48
|
+
### types.ts -- Type Definitions
|
|
49
|
+
|
|
50
|
+
Three core interfaces:
|
|
51
|
+
|
|
52
|
+
- `GiteaConnection` -- Single connection config (`baseUrl`, `token`, `insecure?`)
|
|
53
|
+
- `GiteaConfig` -- Full config with named connections map and default
|
|
54
|
+
- `ApiResponse` -- Standardized response wrapper (`status`, `data`)
|
|
55
|
+
|
|
56
|
+
## Design Decisions
|
|
57
|
+
|
|
58
|
+
### Token Authentication
|
|
59
|
+
|
|
60
|
+
Gitea uses `Authorization: token <access-token>` as its native authentication header format, unlike GitHub's `Bearer` token format. This server uses the native Gitea format directly, ensuring compatibility with all Gitea versions without requiring OAuth2 setup.
|
|
61
|
+
|
|
62
|
+
### Multi-Connection Architecture
|
|
63
|
+
|
|
64
|
+
Every tool accepts an optional `connection` parameter. This enables:
|
|
65
|
+
|
|
66
|
+
- Managing multiple Gitea instances from a single MCP server
|
|
67
|
+
- Switching between production, staging, and development instances
|
|
68
|
+
- Cross-instance operations within a single Claude Code session
|
|
69
|
+
|
|
70
|
+
The connection is resolved at call time, not at startup, so different tool calls can target different instances.
|
|
71
|
+
|
|
72
|
+
### node:https (Zero HTTP Dependencies)
|
|
73
|
+
|
|
74
|
+
The HTTP client uses Node.js built-in `node:https` and `node:http` modules instead of third-party libraries (e.g., axios, node-fetch). This decision:
|
|
75
|
+
|
|
76
|
+
- Eliminates supply-chain risk from HTTP client dependencies
|
|
77
|
+
- Reduces `node_modules` size
|
|
78
|
+
- Avoids compatibility issues across Node.js versions
|
|
79
|
+
- Provides full control over TLS configuration (needed for `insecure` flag)
|
|
80
|
+
|
|
81
|
+
### Stdio Transport
|
|
82
|
+
|
|
83
|
+
The MCP server uses stdio transport (stdin/stdout) rather than HTTP/SSE. This means:
|
|
84
|
+
|
|
85
|
+
- No network ports are opened by the server
|
|
86
|
+
- Tokens are never exposed through HTTP endpoints
|
|
87
|
+
- The server runs as a child process of the MCP client
|
|
88
|
+
- Communication is process-local, reducing attack surface
|
|
89
|
+
|
|
90
|
+
### Shared Parameter Objects
|
|
91
|
+
|
|
92
|
+
Common parameter patterns are defined as reusable objects:
|
|
93
|
+
|
|
94
|
+
- `OwnerRepo` -- `{ owner, repo }` for all repository-scoped operations
|
|
95
|
+
- `PaginationParams` -- `{ page, limit }` for all list endpoints
|
|
96
|
+
- `ConnectionParam` -- `{ connection }` for multi-connection routing
|
|
97
|
+
|
|
98
|
+
This ensures consistency across all 61 tools and simplifies adding new tools.
|
|
99
|
+
|
|
100
|
+
## Data Flow
|
|
101
|
+
|
|
102
|
+
```
|
|
103
|
+
+-----------------+
|
|
104
|
+
| Claude Code / |
|
|
105
|
+
| MCP Client |
|
|
106
|
+
+--------+--------+
|
|
107
|
+
|
|
|
108
|
+
stdio (JSON-RPC)
|
|
109
|
+
|
|
|
110
|
+
+--------v--------+
|
|
111
|
+
| index.ts |
|
|
112
|
+
| McpServer |
|
|
113
|
+
| (tool router) |
|
|
114
|
+
+--------+--------+
|
|
115
|
+
|
|
|
116
|
+
+-----------+-----------+
|
|
117
|
+
| |
|
|
118
|
+
+------v------+ +-------v-------+
|
|
119
|
+
| config.ts | | client.ts |
|
|
120
|
+
| loadConfig | | GiteaClient |
|
|
121
|
+
| getConn | | (HTTP calls) |
|
|
122
|
+
+------+------+ +-------+-------+
|
|
123
|
+
| |
|
|
124
|
+
+------v------+ HTTPS / HTTP
|
|
125
|
+
| ~/.gitea- | |
|
|
126
|
+
| api-mcp.json| +--------v--------+
|
|
127
|
+
+-------------+ | Gitea Instance |
|
|
128
|
+
| /api/v1/* |
|
|
129
|
+
+-----------------+
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
1. Claude Code sends a JSON-RPC tool call over stdio
|
|
133
|
+
2. `McpServer` routes the call to the registered tool handler
|
|
134
|
+
3. The handler calls `clientFor(connection)` which resolves the connection from config
|
|
135
|
+
4. `GiteaClient` constructs the HTTP request and sends it to the Gitea API
|
|
136
|
+
5. The JSON response is wrapped in `formatResponse()` and returned to the MCP client
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
*Repo: [gitea-api-mcp](https://git.mokoconsulting.tech/MokoConsulting/gitea-api-mcp) . [MokoStandards](https://git.mokoconsulting.tech/MokoConsulting/mokoplatform/wiki/Home)*
|
|
141
|
+
|
|
142
|
+
| Revision | Date | Author | Description |
|
|
143
|
+
|---|---|---|---|
|
|
144
|
+
| 1.0 | 2026-05-09 | Moko Consulting | Initial version |
|
package/wiki/Home.md
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# gitea-api-mcp
|
|
2
|
+
|
|
3
|
+
MCP server for Gitea REST API v1 operations — 61 tools for repos, issues, PRs, releases, branches, actions, orgs, wiki, webhooks, and more
|
|
4
|
+
|
|
5
|
+
| Field | Value |
|
|
6
|
+
|---|---|
|
|
7
|
+
| **Language** | Markdown |
|
|
8
|
+
| **License** | GPL-3.0-or-later |
|
|
9
|
+
| **Platform** | [Gitea](https://git.mokoconsulting.tech/MokoConsulting/gitea-api-mcp) |
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Guides
|
|
14
|
+
|
|
15
|
+
| Page | Description |
|
|
16
|
+
|---|---|
|
|
17
|
+
| [INSTALLATION](INSTALLATION) | - **Node.js** >= 20.0.0 ([download](https://nodejs.org)) |
|
|
18
|
+
|
|
19
|
+
## Reference
|
|
20
|
+
|
|
21
|
+
| Page | Description |
|
|
22
|
+
|---|---|
|
|
23
|
+
| [ARCHITECTURE](ARCHITECTURE) | index.ts -- MCP server entry point, tool registrations (61 tools) |
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
> [MokoStandards](https://git.mokoconsulting.tech/MokoConsulting/mokoplatform/wiki/Home)
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
*Repo: [gitea-api-mcp](https://git.mokoconsulting.tech/MokoConsulting/gitea-api-mcp) . [MokoStandards](https://git.mokoconsulting.tech/MokoConsulting/mokoplatform/wiki/Home)*
|
|
32
|
+
|
|
33
|
+
| Revision | Date | Author | Description |
|
|
34
|
+
|---|---|---|---|
|
|
35
|
+
| 1.0 | 2026-05-09 | Moko Consulting | Initial version |
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
← [Home](Home)
|
|
2
|
+
|
|
3
|
+
# Installation Guide
|
|
4
|
+
|
|
5
|
+
## Prerequisites
|
|
6
|
+
|
|
7
|
+
- **Node.js** >= 20.0.0 ([download](https://nodejs.org))
|
|
8
|
+
- **npm** (included with Node.js)
|
|
9
|
+
- A **Gitea instance** with API access enabled
|
|
10
|
+
- A **Gitea access token** with appropriate scopes
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
### Clone and Build
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
git clone https://git.mokoconsulting.tech/MokoConsulting/gitea-api-mcp.git
|
|
18
|
+
cd gitea-api-mcp
|
|
19
|
+
npm install
|
|
20
|
+
npm run build
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
The compiled output is placed in `dist/index.js`.
|
|
24
|
+
|
|
25
|
+
### Verify the Build
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
node dist/index.js --help
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Gitea Token Generation
|
|
32
|
+
|
|
33
|
+
1. Log in to your Gitea instance (e.g., `https://git.mokoconsulting.tech`)
|
|
34
|
+
2. Navigate to **Settings** (click your avatar, top-right corner)
|
|
35
|
+
3. Select **Applications** from the sidebar
|
|
36
|
+
4. Under **Manage Access Tokens**, enter a descriptive token name (e.g., `mcp-server`)
|
|
37
|
+
5. Select the required permission scopes:
|
|
38
|
+
- `repo` -- for repository operations
|
|
39
|
+
- `admin:org` -- for organization management
|
|
40
|
+
- `notification` -- for notification tools
|
|
41
|
+
- `user` -- for user profile operations
|
|
42
|
+
- `issue` -- for issue and pull request tools
|
|
43
|
+
- `package` -- if using package-related endpoints
|
|
44
|
+
6. Click **Generate Token**
|
|
45
|
+
7. **Copy the token immediately** -- it will not be displayed again
|
|
46
|
+
|
|
47
|
+
## Configuration
|
|
48
|
+
|
|
49
|
+
### Config File Format
|
|
50
|
+
|
|
51
|
+
Create `~/.gitea-api-mcp.json`:
|
|
52
|
+
|
|
53
|
+
```json
|
|
54
|
+
{
|
|
55
|
+
"defaultConnection": "moko",
|
|
56
|
+
"connections": {
|
|
57
|
+
"moko": {
|
|
58
|
+
"baseUrl": "https://git.mokoconsulting.tech",
|
|
59
|
+
"token": "your-gitea-access-token",
|
|
60
|
+
"insecure": false
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Config Fields
|
|
67
|
+
|
|
68
|
+
| Field | Type | Required | Description |
|
|
69
|
+
|-------|------|----------|-------------|
|
|
70
|
+
| `defaultConnection` | string | No | Name of the default connection (defaults to the first one) |
|
|
71
|
+
| `connections` | object | Yes | Map of named connections |
|
|
72
|
+
| `connections.<name>.baseUrl` | string | Yes | Base URL of the Gitea instance (no trailing slash) |
|
|
73
|
+
| `connections.<name>.token` | string | Yes | Gitea API access token |
|
|
74
|
+
| `connections.<name>.insecure` | boolean | No | Skip TLS certificate verification (default: `false`) |
|
|
75
|
+
|
|
76
|
+
### Custom Config Path
|
|
77
|
+
|
|
78
|
+
Override the default config location with an environment variable:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
export GITEA_API_MCP_CONFIG=/path/to/custom-config.json
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### File Permissions
|
|
85
|
+
|
|
86
|
+
Secure your config file since it contains API tokens:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
chmod 600 ~/.gitea-api-mcp.json
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Claude Code Registration
|
|
93
|
+
|
|
94
|
+
### Project-Level (`.mcp.json`)
|
|
95
|
+
|
|
96
|
+
Create or edit `.mcp.json` in your project root:
|
|
97
|
+
|
|
98
|
+
```json
|
|
99
|
+
{
|
|
100
|
+
"mcpServers": {
|
|
101
|
+
"gitea-moko": {
|
|
102
|
+
"command": "node",
|
|
103
|
+
"args": ["/absolute/path/to/gitea-api-mcp/dist/index.js"]
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Global-Level (`~/.claude/claude_desktop_config.json`)
|
|
110
|
+
|
|
111
|
+
```json
|
|
112
|
+
{
|
|
113
|
+
"mcpServers": {
|
|
114
|
+
"gitea-moko": {
|
|
115
|
+
"command": "node",
|
|
116
|
+
"args": ["/absolute/path/to/gitea-api-mcp/dist/index.js"]
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
After registering, restart Claude Code or run `/mcp` to verify the server is connected.
|
|
123
|
+
|
|
124
|
+
## Troubleshooting
|
|
125
|
+
|
|
126
|
+
### "Failed to load config" Error
|
|
127
|
+
|
|
128
|
+
- Verify `~/.gitea-api-mcp.json` exists and is valid JSON
|
|
129
|
+
- Check that at least one connection is defined in `connections`
|
|
130
|
+
- If using `GITEA_API_MCP_CONFIG`, verify the path is correct and the file is readable
|
|
131
|
+
|
|
132
|
+
### "Connection not found" Error
|
|
133
|
+
|
|
134
|
+
- The `connection` parameter you passed does not match any key in `connections`
|
|
135
|
+
- Run `gitea_list_connections` to see available connections
|
|
136
|
+
- Check for typos in the connection name
|
|
137
|
+
|
|
138
|
+
### Authentication Failures (HTTP 401)
|
|
139
|
+
|
|
140
|
+
- Verify your token is correct and has not expired
|
|
141
|
+
- Ensure the token has the required scopes for the operation
|
|
142
|
+
- Check that the `baseUrl` points to the correct Gitea instance
|
|
143
|
+
- Confirm the Gitea instance has API access enabled (admin setting)
|
|
144
|
+
|
|
145
|
+
### TLS / Certificate Errors
|
|
146
|
+
|
|
147
|
+
- For self-signed certificates, set `"insecure": true` in the connection config
|
|
148
|
+
- Ensure the Gitea instance URL uses the correct protocol (`https://` vs `http://`)
|
|
149
|
+
|
|
150
|
+
### Timeout Errors
|
|
151
|
+
|
|
152
|
+
- The default request timeout is 30 seconds
|
|
153
|
+
- Check network connectivity to the Gitea instance
|
|
154
|
+
- Verify the Gitea instance is running and responsive
|
|
155
|
+
- For large responses (e.g., recursive tree listings), consider pagination
|
|
156
|
+
|
|
157
|
+
### MCP Server Not Appearing in Claude Code
|
|
158
|
+
|
|
159
|
+
- Ensure the path in your MCP config is an absolute path to `dist/index.js`
|
|
160
|
+
- Verify the build completed successfully (`npm run build`)
|
|
161
|
+
- Check that Node.js >= 20.0.0 is in your PATH
|
|
162
|
+
- Restart Claude Code after modifying MCP configuration
|
|
163
|
+
|
|
164
|
+
---
|
|
165
|
+
|
|
166
|
+
*Repo: [gitea-api-mcp](https://git.mokoconsulting.tech/MokoConsulting/gitea-api-mcp) . [MokoStandards](https://git.mokoconsulting.tech/MokoConsulting/mokoplatform/wiki/Home)*
|
|
167
|
+
|
|
168
|
+
| Revision | Date | Author | Description |
|
|
169
|
+
|---|---|---|---|
|
|
170
|
+
| 1.0 | 2026-05-09 | Moko Consulting | Initial version |
|