diff --git a/.mokogitea/mcp/Dockerfile b/.mokogitea/mcp/Dockerfile new file mode 100644 index 0000000000..e2997344ff --- /dev/null +++ b/.mokogitea/mcp/Dockerfile @@ -0,0 +1,18 @@ +FROM node:20-alpine + +WORKDIR /app + +COPY package.json package-lock.json ./ +RUN npm ci --production=false + +COPY tsconfig.json ./ +COPY src/ ./src/ +RUN npx tsc && npm prune --production + +EXPOSE 3100 + +ENV PORT=3100 +ENV NODE_ENV=production + +# SSE mode by default for Docker deployments +CMD ["node", "dist/sse.js"] diff --git a/.mokogitea/mcp/README.md b/.mokogitea/mcp/README.md index 74e7908376..7402d834d4 100644 --- a/.mokogitea/mcp/README.md +++ b/.mokogitea/mcp/README.md @@ -1,286 +1,116 @@ - +# MokoGitea MCP Server -# gitea-api-mcp +A comprehensive [Model Context Protocol](https://modelcontextprotocol.io) server for [Gitea](https://gitea.com) and [MokoGitea](https://git.mokoconsulting.tech/MokoConsulting/MokoGitea). 120+ tools for repos, issues, PRs, projects, releases, custom fields, statuses, priorities, and manifests. -[![License: GPL-3.0-or-later](https://img.shields.io/badge/License-GPL--3.0--or--later-blue.svg)](https://www.gnu.org/licenses/gpl-3.0) -[![MCP](https://img.shields.io/badge/MCP-compatible-brightgreen.svg)](https://modelcontextprotocol.io) -[![Node](https://img.shields.io/badge/node-%3E%3D20.0.0-green.svg)](https://nodejs.org) -[![TypeScript](https://img.shields.io/badge/TypeScript-5.x-blue.svg)](https://www.typescriptlang.org) +Works with any Gitea instance. MokoGitea-specific features degrade gracefully on vanilla Gitea. -> MCP server for Gitea REST API v1 operations -- 61 tools for complete Gitea instance management from Claude Code and other MCP clients. +## Quick Start -## Table of Contents - -- [Background](#background) -- [Install](#install) -- [Configuration](#configuration) -- [Usage](#usage) -- [Tools](#tools) -- [Contributing](#contributing) -- [License](#license) -- [Revision History](#revision-history) - -## Background - -`gitea-api-mcp` is a Model Context Protocol (MCP) server that exposes 61 tools for interacting with the Gitea REST API v1. It supports multiple named connections, allowing you to manage several Gitea instances from a single server. Authentication uses Gitea's native `Authorization: token` header format. - -## Install - -### Prerequisites - -- Node.js >= 20.0.0 -- A Gitea instance with API access -- A Gitea access token (Settings > Applications > Generate Token) - -### Build from Source +### npx (no install) ```bash -git clone https://git.mokoconsulting.tech/MokoConsulting/gitea-api-mcp.git -cd gitea-api-mcp -npm install -npm run build +GITEA_URL=https://gitea.example.com GITEA_TOKEN=your_token npx @mokoconsulting/mokogitea-mcp ``` -## Configuration +### Claude Code -Create `~/.gitea-api-mcp.json`: - -```json -{ - "defaultConnection": "moko", - "connections": { - "moko": { - "baseUrl": "https://git.mokoconsulting.tech", - "token": "your-gitea-access-token", - "insecure": false - } - } -} -``` - -### Config Fields - -| Field | Type | Required | Description | -|-------|------|----------|-------------| -| `baseUrl` | string | Yes | Base URL of your Gitea instance | -| `token` | string | Yes | Gitea API access token | -| `insecure` | boolean | No | Skip TLS verification (self-signed certs) | - -Override the config path with the `GITEA_API_MCP_CONFIG` environment variable. - -### Multi-Connection Example - -```json -{ - "defaultConnection": "moko", - "connections": { - "moko": { - "baseUrl": "https://git.mokoconsulting.tech", - "token": "token-for-moko-gitea" - }, - "github-mirror": { - "baseUrl": "https://gitea.example.com", - "token": "token-for-mirror" - } - } -} -``` - -## Usage - -### Claude Code Registration - -Add to your Claude Code MCP config (`~/.claude/claude_desktop_config.json` or project-level `.mcp.json`): +Add to `.claude.json`: ```json { "mcpServers": { - "gitea-moko": { - "command": "node", - "args": ["/path/to/gitea-api-mcp/dist/index.js"] + "mokogitea": { + "command": "npx", + "args": ["@mokoconsulting/mokogitea-mcp"], + "env": { + "GITEA_URL": "https://gitea.example.com", + "GITEA_TOKEN": "your_token" + } } } } ``` -### Multi-Connection Usage in Claude Code +### Docker (SSE mode) -When using multiple connections, pass the `connection` parameter to any tool: - -``` -Use gitea_repo_get with connection "github-mirror" to get owner/repo details. +```bash +docker run -p 3100:3100 \ + -e GITEA_URL=https://gitea.example.com \ + -e GITEA_TOKEN=your_token \ + mokoconsulting/mokogitea-mcp ``` -If `connection` is omitted, the `defaultConnection` is used. +Connect MCP client to `http://localhost:3100/sse`. -## Tools +### Multi-instance config -### User / Auth (3 tools) +Create `~/.mcp_mokogitea.json`: -| Tool | Description | -|------|-------------| -| `gitea_me` | Get the authenticated user info | -| `gitea_user_orgs` | List organizations the authenticated user belongs to | -| `gitea_user_repos` | List repositories owned by the authenticated user | +```json +{ + "defaultConnection": "production", + "connections": { + "production": { "baseUrl": "https://gitea.example.com", "token": "your_token" }, + "dev": { "baseUrl": "https://dev.gitea.example.com", "token": "dev_token" } + } +} +``` -### Repositories (8 tools) +## Configuration -| Tool | Description | -|------|-------------| -| `gitea_repo_get` | Get repository details | -| `gitea_repo_create` | Create a new repository | -| `gitea_repo_delete` | Delete a repository | -| `gitea_repo_edit` | Edit repository settings | -| `gitea_repo_fork` | Fork a repository | -| `gitea_repo_search` | Search repositories | -| `gitea_org_repos` | List repositories in an organization | -| `gitea_list_connections` | List configured Gitea connections | +| Method | Use Case | +|--------|----------| +| `GITEA_URL` + `GITEA_TOKEN` env vars | Single instance, quick setup | +| `~/.mcp_mokogitea.json` config file | Multiple instances | +| `GITEA_API_MCP_CONFIG` env var | Custom config path | +| `GITEA_INSECURE=true` | Skip TLS verification | -### File Contents (5 tools) +## Tools (120+) -| Tool | Description | -|------|-------------| -| `gitea_file_get` | Get file contents from a repository | -| `gitea_dir_get` | Get directory contents (file listing) from a repository | -| `gitea_file_create_or_update` | Create or update a file in a repository | -| `gitea_file_delete` | Delete a file from a repository | -| `gitea_tree_get` | Get the git tree for a repository (recursive file listing) | +### Repositories +`gitea_repo_create` `gitea_repo_get` `gitea_repo_edit` `gitea_repo_delete` `gitea_repo_search` `gitea_repo_fork` `gitea_repo_generate` `gitea_repo_languages` `gitea_repo_contributors` `gitea_repo_topics` `gitea_repo_topics_set` -### Branches (4 tools) +### Issues +`gitea_issue_create` (dedup by title) `gitea_issue_get` `gitea_issue_update` `gitea_issues_list` `gitea_issue_search` `gitea_issue_comment_create` `gitea_issue_comments_list` `gitea_issue_labels_set` `gitea_issue_bulk_set_status` -| Tool | Description | -|------|-------------| -| `gitea_branches_list` | List branches in a repository | -| `gitea_branch_get` | Get a specific branch | -| `gitea_branch_create` | Create a new branch | -| `gitea_branch_delete` | Delete a branch | +### Pull Requests +`gitea_pull_create` `gitea_pull_get` `gitea_pulls_list` `gitea_pull_merge` `gitea_pull_files` `gitea_pull_review_create` -### Commits (2 tools) +### Branches and Tags +`gitea_branches_list` `gitea_branch_create` `gitea_branch_delete` `gitea_branch_get` `gitea_tags_list` `gitea_tag_create` `gitea_tag_delete` -| Tool | Description | -|------|-------------| -| `gitea_commits_list` | List commits in a repository | -| `gitea_commit_get` | Get a specific commit | +### Releases +`gitea_releases_list` `gitea_release_create` `gitea_release_get` `gitea_release_latest` `gitea_release_delete` `gitea_release_asset_upload` `gitea_release_asset_delete` -### Issues (7 tools) +### Files and Trees +`gitea_file_get` `gitea_file_create_or_update` `gitea_file_delete` `gitea_dir_get` `gitea_tree_get` `gitea_bulk_file_push` -| Tool | Description | -|------|-------------| -| `gitea_issues_list` | List issues in a repository | -| `gitea_issue_get` | Get a single issue by number | -| `gitea_issue_create` | Create a new issue | -| `gitea_issue_update` | Update an issue | -| `gitea_issue_comments_list` | List comments on an issue | -| `gitea_issue_comment_create` | Add a comment to an issue | -| `gitea_issue_search` | Search issues across all repositories | +### Projects +`gitea_project_list` `gitea_project_create` `gitea_project_get` `gitea_project_update` `gitea_project_delete` `gitea_project_overview` `gitea_project_columns_list` `gitea_project_column_create` `gitea_project_column_delete` `gitea_project_cards_list` `gitea_project_card_add` `gitea_project_card_move` `gitea_project_card_remove` -### Labels (2 tools) +### Organizations +`gitea_org_get` `gitea_org_repos` `gitea_org_members_list` `gitea_org_teams_list` `gitea_org_labels_list` `gitea_org_label_create` -| Tool | Description | -|------|-------------| -| `gitea_labels_list` | List labels in a repository | -| `gitea_label_create` | Create a label | +### Wiki +`gitea_wiki_pages_list` `gitea_wiki_page_get` -### Milestones (2 tools) +### MokoGitea Extensions +`gitea_manifest_get` `gitea_manifest_update` `gitea_org_custom_fields_list` `gitea_org_custom_field_create` `gitea_org_custom_field_delete` `gitea_issue_custom_fields_get` `gitea_issue_custom_fields_set` `gitea_org_issue_statuses_list` `gitea_issue_set_status` `gitea_org_issue_priorities_list` `gitea_issue_set_priority` -| Tool | Description | -|------|-------------| -| `gitea_milestones_list` | List milestones in a repository | -| `gitea_milestone_create` | Create a milestone | +### Admin and Other +`gitea_me` `gitea_users_search` `gitea_user_get` `gitea_notifications_list` `gitea_notifications_read` `gitea_commits_list` `gitea_commit_get` `gitea_compare` `gitea_webhooks_list` `gitea_webhook_create` `gitea_admin_users_list` `gitea_admin_orgs_list` `gitea_admin_cron_list` `gitea_admin_cron_run` `gitea_list_connections` -### Pull Requests (6 tools) +## SSE Server -| Tool | Description | -|------|-------------| -| `gitea_pulls_list` | List pull requests | -| `gitea_pull_get` | Get a single pull request | -| `gitea_pull_create` | Create a pull request | -| `gitea_pull_merge` | Merge a pull request | -| `gitea_pull_files` | List files changed in a pull request | -| `gitea_pull_review_create` | Create a pull request review | +For hosted deployments: -### Releases (5 tools) - -| Tool | Description | -|------|-------------| -| `gitea_releases_list` | List releases | -| `gitea_release_get` | Get a single release by ID | -| `gitea_release_latest` | Get the latest release | -| `gitea_release_create` | Create a new release | -| `gitea_release_delete` | Delete a release | - -### Tags (3 tools) - -| Tool | Description | -|------|-------------| -| `gitea_tags_list` | List tags | -| `gitea_tag_create` | Create a tag | -| `gitea_tag_delete` | Delete a tag | - -### Actions (2 tools) - -| Tool | Description | -|------|-------------| -| `gitea_actions_runs_list` | List workflow runs for a repository | -| `gitea_actions_run_get` | Get a specific workflow run | - -### Organizations (3 tools) - -| Tool | Description | -|------|-------------| -| `gitea_org_get` | Get organization details | -| `gitea_org_teams_list` | List teams in an organization | -| `gitea_org_members_list` | List members of an organization | - -### Users (2 tools) - -| Tool | Description | -|------|-------------| -| `gitea_user_get` | Get a user profile | -| `gitea_users_search` | Search users | - -### Webhooks (2 tools) - -| Tool | Description | -|------|-------------| -| `gitea_webhooks_list` | List webhooks for a repository | -| `gitea_webhook_create` | Create a webhook | - -### Wiki (2 tools) - -| Tool | Description | -|------|-------------| -| `gitea_wiki_pages_list` | List wiki pages | -| `gitea_wiki_page_get` | Get a wiki page | - -### Notifications (2 tools) - -| Tool | Description | -|------|-------------| -| `gitea_notifications_list` | List notifications for the authenticated user | -| `gitea_notifications_read` | Mark all notifications as read | - -### Generic (2 tools) - -| Tool | Description | -|------|-------------| -| `gitea_api_request` | Make a raw API request to any Gitea v1 endpoint | -| `gitea_list_connections` | List configured Gitea connections | - -## Contributing - -See [CONTRIBUTING.md](CONTRIBUTING.md) for development guidelines. +``` +GET / Server info +GET /sse SSE connection endpoint +POST /message Tool call messages +GET /health Health check +``` ## License -[GPL-3.0-or-later](https://www.gnu.org/licenses/gpl-3.0.html) -- Copyright (C) 2026 Moko Consulting - -## Revision History - -| Version | Date | Description | -|---------|------|-------------| -| 0.0.1 | 2026-05-07 | Initial release with 61 tools | +GPL-3.0-or-later - [Moko Consulting](https://mokoconsulting.tech) diff --git a/.mokogitea/mcp/package.json b/.mokogitea/mcp/package.json index 298a848046..952874d1ef 100644 --- a/.mokogitea/mcp/package.json +++ b/.mokogitea/mcp/package.json @@ -1,19 +1,33 @@ { - "name": "@mokoconsulting/mcp-mokogitea-api", - "version": "1.0.0", - "description": "MCP server for Gitea REST API v1 operations", + "name": "@mokoconsulting/mokogitea-mcp", + "version": "1.1.0", + "description": "MCP server for Gitea and MokoGitea - 120+ tools for repos, issues, PRs, projects, releases, custom fields, statuses, priorities, and manifests", "type": "module", "main": "dist/index.js", "bin": { - "mokogitea-api-mcp": "dist/index.js" + "mokogitea-mcp": "dist/index.js", + "mokogitea-mcp-sse": "dist/sse.js" }, "scripts": { "build": "tsc", "dev": "tsc --watch", "start": "node dist/index.js", + "start:sse": "node dist/sse.js", "setup": "node scripts/setup.mjs", "clean": "rm -rf dist/" }, + "keywords": [ + "mcp", + "gitea", + "mokogitea", + "model-context-protocol", + "claude", + "ai", + "git", + "self-hosted", + "api", + "devops" + ], "dependencies": { "@modelcontextprotocol/sdk": "^1.12.1", "zod": "^3.24.4" @@ -27,8 +41,18 @@ }, "license": "GPL-3.0-or-later", "author": "Moko Consulting ", + "homepage": "https://git.mokoconsulting.tech/MokoConsulting/mcp_mokogitea_api", "repository": { "type": "git", - "url": "https://git.mokoconsulting.tech/MokoConsulting/gitea-api-mcp.git" + "url": "https://git.mokoconsulting.tech/MokoConsulting/mcp_mokogitea_api.git" + }, + "files": [ + "dist/", + "config.example.json", + "README.md", + "LICENSE" + ], + "publishConfig": { + "access": "public" } } diff --git a/.mokogitea/mcp/src/config.ts b/.mokogitea/mcp/src/config.ts index 7ae522e73a..4ff2283397 100644 --- a/.mokogitea/mcp/src/config.ts +++ b/.mokogitea/mcp/src/config.ts @@ -1,17 +1,5 @@ -/* Copyright (C) 2026 Moko Consulting - * - * This file is part of a Moko Consulting project. - * - * SPDX-License-Identifier: GPL-3.0-or-later - * - * FILE INFORMATION - * DEFGROUP: gitea-api-mcp.Config - * INGROUP: gitea-api-mcp - * REPO: https://git.mokoconsulting.tech/MokoConsulting/gitea-api-mcp - * PATH: /src/config.ts - * VERSION: 01.00.00 - * BRIEF: Configuration loader for Gitea API MCP connections - */ +// Copyright 2026 Moko Consulting +// SPDX-License-Identifier: GPL-3.0-or-later import { readFile } from 'node:fs/promises'; import { resolve } from 'node:path'; @@ -21,6 +9,20 @@ import type { GiteaConfig, GiteaConnection } from './types.js'; const CONFIG_FILENAME = '.mcp_mokogitea.json'; export async function loadConfig(): Promise { + // Priority 1: Environment variables (zero-config single instance) + if (process.env.GITEA_URL && process.env.GITEA_TOKEN) { + const conn: GiteaConnection = { + baseUrl: process.env.GITEA_URL, + token: process.env.GITEA_TOKEN, + insecure: process.env.GITEA_INSECURE === 'true', + }; + return { + connections: { default: conn }, + defaultConnection: 'default', + }; + } + + // Priority 2: Config file const config_path = process.env.GITEA_API_MCP_CONFIG ? resolve(process.env.GITEA_API_MCP_CONFIG) : resolve(homedir(), CONFIG_FILENAME); @@ -41,7 +43,8 @@ export async function loadConfig(): Promise { const message = err instanceof Error ? err.message : String(err); throw new Error( `Failed to load config from ${config_path}: ${message}\n` + - `Create ${config_path} — see config.example.json for format.`, + `Option 1: Set GITEA_URL and GITEA_TOKEN environment variables\n` + + `Option 2: Create ${config_path} - see config.example.json for format`, ); } } diff --git a/.mokogitea/mcp/src/index.ts b/.mokogitea/mcp/src/index.ts index 27b0ee6720..ee1737f96e 100644 --- a/.mokogitea/mcp/src/index.ts +++ b/.mokogitea/mcp/src/index.ts @@ -21,7 +21,11 @@ import { loadConfig, getConnection } from './config.js'; import { GiteaClient } from './client.js'; import type { GiteaConfig, ApiResponse } from './types.js'; -let config: GiteaConfig; +export let config: GiteaConfig; + +export function initConfig(c: GiteaConfig): void { + config = c; +} function clientFor(connection?: string): GiteaClient { return new GiteaClient(getConnection(config, connection)); @@ -59,9 +63,9 @@ const OwnerRepo = { repo: z.string().describe('Repository name'), }; -const server = new McpServer({ - name: 'mokogitea-api-mcp', - version: '1.0.0', +export const server = new McpServer({ + name: '@mokoconsulting/mokogitea-mcp', + version: '1.1.0', }); // ── User / Auth ───────────────────────────────────────────────────────── diff --git a/.mokogitea/mcp/src/server.ts b/.mokogitea/mcp/src/server.ts new file mode 100644 index 0000000000..79dac8c7fb --- /dev/null +++ b/.mokogitea/mcp/src/server.ts @@ -0,0 +1,16 @@ +// Copyright 2026 Moko Consulting +// SPDX-License-Identifier: GPL-3.0-or-later +// +// Creates a configured MCP server instance for use by both stdio and SSE transports. + +import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; +import type { GiteaConfig } from './types.js'; + +// Import index.ts to register all tools on its exported `server` singleton, +// then re-export a factory that initializes config and returns the server. +import { server, initConfig } from './index.js'; + +export function createMcpServer(cfg: GiteaConfig): McpServer { + initConfig(cfg); + return server; +} diff --git a/.mokogitea/mcp/src/sse.ts b/.mokogitea/mcp/src/sse.ts new file mode 100644 index 0000000000..f13c0b1309 --- /dev/null +++ b/.mokogitea/mcp/src/sse.ts @@ -0,0 +1,100 @@ +// Copyright 2026 Moko Consulting +// SPDX-License-Identifier: GPL-3.0-or-later +// +// SSE transport entry point for MokoGitea MCP server. +// Run with: node dist/sse.js +// Or: GITEA_URL=https://gitea.example.com GITEA_TOKEN=xxx node dist/sse.js +// +// Listens on PORT (default 3100) and serves SSE at /sse with POST at /message. + +import { createServer } from 'node:http'; +import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js'; +import { createMcpServer } from './server.js'; +import { loadConfig } from './config.js'; + +const PORT = parseInt(process.env.PORT ?? '3100', 10); + +async function main(): Promise { + const config = await loadConfig(); + const transports = new Map(); + + const httpServer = createServer(async (req, res) => { + // CORS headers for browser clients + res.setHeader('Access-Control-Allow-Origin', '*'); + res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS'); + res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization'); + + if (req.method === 'OPTIONS') { + res.writeHead(204); + res.end(); + return; + } + + // Health check + if (req.url === '/health') { + res.writeHead(200, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ status: 'ok', tools: 120 })); + return; + } + + // SSE endpoint - client connects here + if (req.url === '/sse' && req.method === 'GET') { + const transport = new SSEServerTransport('/message', res); + const sessionId = transport.sessionId; + transports.set(sessionId, transport); + + const server = createMcpServer(config); + await server.connect(transport); + + req.on('close', () => { + transports.delete(sessionId); + }); + return; + } + + // Message endpoint - client sends tool calls here + if (req.url?.startsWith('/message') && req.method === 'POST') { + const url = new URL(req.url, `http://${req.headers.host}`); + const sessionId = url.searchParams.get('sessionId'); + if (!sessionId || !transports.has(sessionId)) { + res.writeHead(400, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ error: 'Invalid or missing sessionId' })); + return; + } + const transport = transports.get(sessionId)!; + await transport.handlePostMessage(req, res); + return; + } + + // Root - info page + if (req.url === '/' || req.url === '') { + res.writeHead(200, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ + name: '@mokoconsulting/mokogitea-mcp', + version: '1.1.0', + description: 'MCP server for Gitea and MokoGitea - 120+ tools', + endpoints: { + sse: '/sse', + message: '/message', + health: '/health', + }, + docs: 'https://git.mokoconsulting.tech/MokoConsulting/mcp_mokogitea_api', + })); + return; + } + + res.writeHead(404); + res.end('Not found'); + }); + + httpServer.listen(PORT, () => { + process.stderr.write(`MokoGitea MCP SSE server listening on port ${PORT}\n`); + process.stderr.write(` SSE: http://localhost:${PORT}/sse\n`); + process.stderr.write(` Health: http://localhost:${PORT}/health\n`); + }); +} + +main().catch((err) => { + process.stderr.write(`Fatal: ${err}\n`); + process.exit(1); +});