@mokoconsulting/mcp-mokogitea-api 1.3.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.
@@ -28,24 +28,27 @@ jobs:
28
28
  - name: Build
29
29
  run: npm run build
30
30
 
31
- - name: Check if version changed
32
- id: version
31
+ - name: Auto-bump patch version
33
32
  run: |
34
33
  PKG_NAME=$(node -p "require('./package.json').name")
35
- PKG_VERSION=$(node -p "require('./package.json').version")
36
- PUBLISHED=$(npm view "${PKG_NAME}@${PKG_VERSION}" version 2>/dev/null || echo "")
37
- if [ "$PUBLISHED" = "$PKG_VERSION" ]; then
38
- echo "skip=true" >> "$GITHUB_OUTPUT"
39
- echo "Version ${PKG_VERSION} already published, skipping."
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 "skip=false" >> "$GITHUB_OUTPUT"
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 (2 tools)
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'),
@@ -0,0 +1,4 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import type { GiteaConfig } from './types.js';
3
+ export declare function createMcpServer(cfg: GiteaConfig): McpServer;
4
+ //# sourceMappingURL=server.d.ts.map
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
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=sse.d.ts.map
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.0",
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/gitea-api-mcp.git"
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(
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
@@ -15,5 +15,5 @@
15
15
  "sourceMap": true
16
16
  },
17
17
  "include": ["src/**/*"],
18
- "exclude": ["node_modules", "dist"]
18
+ "exclude": ["node_modules", "dist", "src/server.ts", "src/sse.ts"]
19
19
  }
@@ -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 |