Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 082c550bc4 | |||
| b1da31420f | |||
| 30d7ddc375 | |||
| 8fa56271de | |||
| 8535928a04 | |||
| d0853b874f |
@@ -1,129 +0,0 @@
|
||||
<!-- Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
DEFGROUP: gitea-api-mcp.Documentation
|
||||
REPO: https://git.mokoconsulting.tech/MokoConsulting/gitea-api-mcp
|
||||
-->
|
||||
|
||||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Changed
|
||||
- **Renamed** package from `@mokoconsulting/gitea-api-mcp` to `@mokoconsulting/mokogitea-api-mcp` to distinguish Moko's forked Gitea MCP from upstream
|
||||
- **Renamed** McpServer name and bin entry to `mokogitea-api-mcp`
|
||||
|
||||
|
||||
## [0.0] - 2026-05-07
|
||||
|
||||
### Added
|
||||
|
||||
#### User / Auth (3 tools)
|
||||
- `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
|
||||
|
||||
#### Repositories (8 tools)
|
||||
- `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
|
||||
|
||||
#### File Contents (5 tools)
|
||||
- `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)
|
||||
|
||||
#### Branches (4 tools)
|
||||
- `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
|
||||
|
||||
#### Commits (2 tools)
|
||||
- `gitea_commits_list` -- List commits in a repository
|
||||
- `gitea_commit_get` -- Get a specific commit
|
||||
|
||||
#### Issues (7 tools)
|
||||
- `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
|
||||
|
||||
#### Labels (2 tools)
|
||||
- `gitea_labels_list` -- List labels in a repository
|
||||
- `gitea_label_create` -- Create a label
|
||||
|
||||
#### Milestones (2 tools)
|
||||
- `gitea_milestones_list` -- List milestones in a repository
|
||||
- `gitea_milestone_create` -- Create a milestone
|
||||
|
||||
#### Pull Requests (6 tools)
|
||||
- `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
|
||||
|
||||
#### Releases (5 tools)
|
||||
- `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)
|
||||
- `gitea_tags_list` -- List tags
|
||||
- `gitea_tag_create` -- Create a tag
|
||||
- `gitea_tag_delete` -- Delete a tag
|
||||
|
||||
#### Actions (2 tools)
|
||||
- `gitea_actions_runs_list` -- List workflow runs for a repository
|
||||
- `gitea_actions_run_get` -- Get a specific workflow run
|
||||
|
||||
#### Organizations (3 tools)
|
||||
- `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)
|
||||
- `gitea_user_get` -- Get a user profile
|
||||
- `gitea_users_search` -- Search users
|
||||
|
||||
#### Webhooks (2 tools)
|
||||
- `gitea_webhooks_list` -- List webhooks for a repository
|
||||
- `gitea_webhook_create` -- Create a webhook
|
||||
|
||||
#### Wiki (2 tools)
|
||||
- `gitea_wiki_pages_list` -- List wiki pages
|
||||
- `gitea_wiki_page_get` -- Get a wiki page
|
||||
|
||||
#### Notifications (2 tools)
|
||||
- `gitea_notifications_list` -- List notifications for the authenticated user
|
||||
- `gitea_notifications_read` -- Mark all notifications as read
|
||||
|
||||
#### Generic (2 tools)
|
||||
- `gitea_api_request` -- Make a raw API request to any Gitea v1 endpoint
|
||||
- `gitea_list_connections` -- List configured Gitea connections
|
||||
|
||||
### Infrastructure
|
||||
- Multi-connection config support via `~/.gitea-api-mcp.json`
|
||||
- Token-based authentication (Gitea native `Authorization: token` header)
|
||||
- Built on `node:https` / `node:http` (zero HTTP dependencies)
|
||||
- MCP SDK v1.12.x with stdio transport
|
||||
|
||||
[0.0.1]: https://git.mokoconsulting.tech/MokoConsulting/gitea-api-mcp/releases/tag/v0.0.1
|
||||
@@ -1,18 +0,0 @@
|
||||
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"]
|
||||
@@ -1,116 +0,0 @@
|
||||
# MokoGitea MCP Server
|
||||
|
||||
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.
|
||||
|
||||
Works with any Gitea instance. MokoGitea-specific features degrade gracefully on vanilla Gitea.
|
||||
|
||||
## Quick Start
|
||||
|
||||
### npx (no install)
|
||||
|
||||
```bash
|
||||
GITEA_URL=https://gitea.example.com GITEA_TOKEN=your_token npx @mokoconsulting/mokogitea-mcp
|
||||
```
|
||||
|
||||
### Claude Code
|
||||
|
||||
Add to `.claude.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"mokogitea": {
|
||||
"command": "npx",
|
||||
"args": ["@mokoconsulting/mokogitea-mcp"],
|
||||
"env": {
|
||||
"GITEA_URL": "https://gitea.example.com",
|
||||
"GITEA_TOKEN": "your_token"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Docker (SSE mode)
|
||||
|
||||
```bash
|
||||
docker run -p 3100:3100 \
|
||||
-e GITEA_URL=https://gitea.example.com \
|
||||
-e GITEA_TOKEN=your_token \
|
||||
mokoconsulting/mokogitea-mcp
|
||||
```
|
||||
|
||||
Connect MCP client to `http://localhost:3100/sse`.
|
||||
|
||||
### Multi-instance config
|
||||
|
||||
Create `~/.mcp_mokogitea.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"defaultConnection": "production",
|
||||
"connections": {
|
||||
"production": { "baseUrl": "https://gitea.example.com", "token": "your_token" },
|
||||
"dev": { "baseUrl": "https://dev.gitea.example.com", "token": "dev_token" }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
| 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 |
|
||||
|
||||
## Tools (120+)
|
||||
|
||||
### 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`
|
||||
|
||||
### 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`
|
||||
|
||||
### Pull Requests
|
||||
`gitea_pull_create` `gitea_pull_get` `gitea_pulls_list` `gitea_pull_merge` `gitea_pull_files` `gitea_pull_review_create`
|
||||
|
||||
### Branches and Tags
|
||||
`gitea_branches_list` `gitea_branch_create` `gitea_branch_delete` `gitea_branch_get` `gitea_tags_list` `gitea_tag_create` `gitea_tag_delete`
|
||||
|
||||
### Releases
|
||||
`gitea_releases_list` `gitea_release_create` `gitea_release_get` `gitea_release_latest` `gitea_release_delete` `gitea_release_asset_upload` `gitea_release_asset_delete`
|
||||
|
||||
### Files and Trees
|
||||
`gitea_file_get` `gitea_file_create_or_update` `gitea_file_delete` `gitea_dir_get` `gitea_tree_get` `gitea_bulk_file_push`
|
||||
|
||||
### 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`
|
||||
|
||||
### Organizations
|
||||
`gitea_org_get` `gitea_org_repos` `gitea_org_members_list` `gitea_org_teams_list` `gitea_org_labels_list` `gitea_org_label_create`
|
||||
|
||||
### Wiki
|
||||
`gitea_wiki_pages_list` `gitea_wiki_page_get`
|
||||
|
||||
### 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`
|
||||
|
||||
### 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`
|
||||
|
||||
## SSE Server
|
||||
|
||||
For hosted deployments:
|
||||
|
||||
```
|
||||
GET / Server info
|
||||
GET /sse SSE connection endpoint
|
||||
POST /message Tool call messages
|
||||
GET /health Health check
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
GPL-3.0-or-later - [Moko Consulting](https://mokoconsulting.tech)
|
||||
@@ -1,13 +0,0 @@
|
||||
{
|
||||
"defaultConnection": "moko",
|
||||
"connections": {
|
||||
"moko": {
|
||||
"baseUrl": "https://git.mokoconsulting.tech",
|
||||
"token": "your-gitea-access-token"
|
||||
},
|
||||
"github-mirror": {
|
||||
"baseUrl": "https://gitea.example.com",
|
||||
"token": "your-other-token"
|
||||
}
|
||||
}
|
||||
}
|
||||
Generated
-1198
File diff suppressed because it is too large
Load Diff
@@ -1,58 +0,0 @@
|
||||
{
|
||||
"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-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"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.15.3",
|
||||
"typescript": "^5.8.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
},
|
||||
"license": "GPL-3.0-or-later",
|
||||
"author": "Moko Consulting <hello@mokoconsulting.tech>",
|
||||
"homepage": "https://git.mokoconsulting.tech/MokoConsulting/mcp_mokogitea_api",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://git.mokoconsulting.tech/MokoConsulting/mcp_mokogitea_api.git"
|
||||
},
|
||||
"files": [
|
||||
"dist/",
|
||||
"config.example.json",
|
||||
"README.md",
|
||||
"LICENSE"
|
||||
],
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
# mcp_mokogitea_api PowerShell Profile
|
||||
# Source this with: . ./profile.ps1
|
||||
|
||||
$env:MCP_ROOT = $PSScriptRoot
|
||||
$env:TEMP = 'A:\temp'
|
||||
$env:TMP = 'A:\temp'
|
||||
|
||||
function mcp { Set-Location $PSScriptRoot }
|
||||
function mcp-src { Set-Location (Join-Path $PSScriptRoot 'src') }
|
||||
function mcp-build { Set-Location $PSScriptRoot; npm run build }
|
||||
function mcp-dev { Set-Location $PSScriptRoot; npm run dev }
|
||||
|
||||
Write-Host "mcp_mokogitea_api profile loaded" -ForegroundColor Cyan
|
||||
Write-Host " Commands: mcp-build, mcp-dev" -ForegroundColor DarkGray
|
||||
Write-Host " Navigate: mcp, mcp-src" -ForegroundColor DarkGray
|
||||
@@ -1,40 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
* BRIEF: Interactive setup — prompts for Gitea connection details
|
||||
*/
|
||||
import { createInterface } from 'node:readline/promises';
|
||||
import { readFile, writeFile } from 'node:fs/promises';
|
||||
import { resolve } from 'node:path';
|
||||
import { homedir } from 'node:os';
|
||||
|
||||
const CONFIG_PATH = resolve(homedir(), '.gitea-api-mcp.json');
|
||||
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
||||
|
||||
async function prompt(q, d) { const a = await rl.question(`${q}${d ? ` [${d}]` : ''}: `); return a.trim() || d || ''; }
|
||||
async function promptRequired(q) { let a = ''; while (!a) { a = (await rl.question(`${q}: `)).trim(); if (!a) console.log(' Required.'); } return a; }
|
||||
|
||||
async function main() {
|
||||
console.log('\n=== gitea-api-mcp Setup ===\n');
|
||||
let existing = null;
|
||||
try { existing = JSON.parse(await readFile(CONFIG_PATH, 'utf-8')); console.log(`Existing: ${Object.keys(existing.connections).join(', ')}\n`); } catch {}
|
||||
|
||||
const name = await prompt('Connection name', 'moko');
|
||||
const baseUrl = await promptRequired('Gitea URL (e.g. https://git.mokoconsulting.tech)');
|
||||
const token = await promptRequired('Access token (Settings > Applications > Generate Token)');
|
||||
const insecure = (await prompt('Skip TLS verification? (y/N)', 'N')).toLowerCase() === 'y';
|
||||
|
||||
const conn = { baseUrl: baseUrl.replace(/\/+$/, ''), token };
|
||||
if (insecure) conn.insecure = true;
|
||||
|
||||
const config = existing ?? { defaultConnection: name, connections: {} };
|
||||
config.connections[name] = conn;
|
||||
if (!existing) config.defaultConnection = name;
|
||||
else if ((await prompt(`Set "${name}" as default? (y/N)`, 'N')).toLowerCase() === 'y') config.defaultConnection = name;
|
||||
|
||||
await writeFile(CONFIG_PATH, JSON.stringify(config, null, '\t') + '\n', 'utf-8');
|
||||
console.log(`\nConfig written to ${CONFIG_PATH}\n`);
|
||||
rl.close();
|
||||
}
|
||||
|
||||
main().catch(e => { console.error(e.message); rl.close(); process.exit(1); });
|
||||
@@ -1,120 +0,0 @@
|
||||
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
*
|
||||
* This file is part of a Moko Consulting project.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* FILE INFORMATION
|
||||
* DEFGROUP: gitea-api-mcp.Client
|
||||
* INGROUP: gitea-api-mcp
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/gitea-api-mcp
|
||||
* PATH: /src/client.ts
|
||||
* VERSION: 01.00.00
|
||||
* BRIEF: HTTP client for Gitea REST API v1
|
||||
*/
|
||||
|
||||
import * as https from 'node:https';
|
||||
import * as http from 'node:http';
|
||||
import type { GiteaConnection, ApiResponse } from './types.js';
|
||||
|
||||
const API_PREFIX = '/api/v1';
|
||||
const TIMEOUT_MS = 30_000;
|
||||
|
||||
export class GiteaClient {
|
||||
private readonly base_url: string;
|
||||
private readonly headers: Record<string, string>;
|
||||
private readonly insecure: boolean;
|
||||
|
||||
constructor(conn: GiteaConnection) {
|
||||
this.base_url = conn.baseUrl.replace(/\/+$/, '') + API_PREFIX;
|
||||
this.headers = {
|
||||
'Authorization': `token ${conn.token}`,
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
};
|
||||
this.insecure = conn.insecure ?? false;
|
||||
}
|
||||
|
||||
async get(endpoint: string, params?: Record<string, string>): Promise<ApiResponse> {
|
||||
return this.request(this.buildUrl(endpoint, params), 'GET');
|
||||
}
|
||||
|
||||
async post(endpoint: string, body?: unknown): Promise<ApiResponse> {
|
||||
return this.request(this.buildUrl(endpoint), 'POST', body);
|
||||
}
|
||||
|
||||
async patch(endpoint: string, body: unknown): Promise<ApiResponse> {
|
||||
return this.request(this.buildUrl(endpoint), 'PATCH', body);
|
||||
}
|
||||
|
||||
async put(endpoint: string, body: unknown): Promise<ApiResponse> {
|
||||
return this.request(this.buildUrl(endpoint), 'PUT', body);
|
||||
}
|
||||
|
||||
async delete(endpoint: string): Promise<ApiResponse> {
|
||||
return this.request(this.buildUrl(endpoint), 'DELETE');
|
||||
}
|
||||
|
||||
private buildUrl(endpoint: string, params?: Record<string, string>): string {
|
||||
const path = endpoint.startsWith('/') ? endpoint : `/${endpoint}`;
|
||||
const url = new URL(`${this.base_url}${path}`);
|
||||
if (params) {
|
||||
for (const [key, value] of Object.entries(params)) {
|
||||
url.searchParams.set(key, value);
|
||||
}
|
||||
}
|
||||
return url.toString();
|
||||
}
|
||||
|
||||
private request(url: string, method: string, body?: unknown): Promise<ApiResponse> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const parsed = new URL(url);
|
||||
const is_https = parsed.protocol === 'https:';
|
||||
const transport = is_https ? https : http;
|
||||
|
||||
const options: https.RequestOptions = {
|
||||
hostname: parsed.hostname,
|
||||
port: parsed.port || (is_https ? 443 : 80),
|
||||
path: parsed.pathname + parsed.search,
|
||||
method,
|
||||
headers: { ...this.headers },
|
||||
timeout: TIMEOUT_MS,
|
||||
};
|
||||
|
||||
if (this.insecure && is_https) {
|
||||
options.rejectUnauthorized = false;
|
||||
}
|
||||
|
||||
const payload = body !== undefined ? JSON.stringify(body) : undefined;
|
||||
if (payload) {
|
||||
(options.headers as Record<string, string>)['Content-Length'] = Buffer.byteLength(payload).toString();
|
||||
}
|
||||
|
||||
const req = transport.request(options, (res) => {
|
||||
const chunks: Buffer[] = [];
|
||||
res.on('data', (chunk: Buffer) => chunks.push(chunk));
|
||||
res.on('end', () => {
|
||||
const raw = Buffer.concat(chunks).toString('utf-8');
|
||||
let data: unknown;
|
||||
try {
|
||||
data = JSON.parse(raw);
|
||||
} catch {
|
||||
data = raw;
|
||||
}
|
||||
resolve({ status: res.statusCode ?? 0, data });
|
||||
});
|
||||
});
|
||||
|
||||
req.on('error', (err) => reject(err));
|
||||
req.on('timeout', () => {
|
||||
req.destroy();
|
||||
reject(new Error('Request timed out'));
|
||||
});
|
||||
|
||||
if (payload) {
|
||||
req.write(payload);
|
||||
}
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import { readFile } from 'node:fs/promises';
|
||||
import { resolve } from 'node:path';
|
||||
import { homedir } from 'node:os';
|
||||
import type { GiteaConfig, GiteaConnection } from './types.js';
|
||||
|
||||
const CONFIG_FILENAME = '.mcp_mokogitea.json';
|
||||
|
||||
export async function loadConfig(): Promise<GiteaConfig> {
|
||||
// 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);
|
||||
|
||||
try {
|
||||
const raw = await readFile(config_path, 'utf-8');
|
||||
const parsed = JSON.parse(raw) as Partial<GiteaConfig>;
|
||||
|
||||
if (!parsed.connections || Object.keys(parsed.connections).length === 0) {
|
||||
throw new Error('No connections defined in config');
|
||||
}
|
||||
|
||||
return {
|
||||
connections: parsed.connections,
|
||||
defaultConnection: parsed.defaultConnection ?? Object.keys(parsed.connections)[0],
|
||||
};
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
throw new Error(
|
||||
`Failed to load config from ${config_path}: ${message}\n` +
|
||||
`Option 1: Set GITEA_URL and GITEA_TOKEN environment variables\n` +
|
||||
`Option 2: Create ${config_path} - see config.example.json for format`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function getConnection(config: GiteaConfig, name?: string): GiteaConnection {
|
||||
const key = name ?? config.defaultConnection;
|
||||
const conn = config.connections[key];
|
||||
if (!conn) {
|
||||
throw new Error(
|
||||
`Connection "${key}" not found. Available: ${Object.keys(config.connections).join(', ')}`,
|
||||
);
|
||||
}
|
||||
return conn;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,16 +0,0 @@
|
||||
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
// 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;
|
||||
}
|
||||
@@ -1,100 +0,0 @@
|
||||
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
// 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<void> {
|
||||
const config = await loadConfig();
|
||||
const transports = new Map<string, SSEServerTransport>();
|
||||
|
||||
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);
|
||||
});
|
||||
@@ -1,37 +0,0 @@
|
||||
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
*
|
||||
* This file is part of a Moko Consulting project.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* FILE INFORMATION
|
||||
* DEFGROUP: gitea-api-mcp.Types
|
||||
* INGROUP: gitea-api-mcp
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/gitea-api-mcp
|
||||
* PATH: /src/types.ts
|
||||
* VERSION: 01.00.00
|
||||
* BRIEF: TypeScript type definitions for Gitea API MCP server
|
||||
*/
|
||||
|
||||
export interface GiteaConnection {
|
||||
baseUrl: string;
|
||||
token: string;
|
||||
/** Skip TLS certificate verification (self-signed certs) */
|
||||
insecure?: boolean;
|
||||
}
|
||||
|
||||
export interface GitHubBackupConfig {
|
||||
token: string;
|
||||
org: string;
|
||||
}
|
||||
|
||||
export interface GiteaConfig {
|
||||
connections: Record<string, GiteaConnection>;
|
||||
defaultConnection: string;
|
||||
github?: GitHubBackupConfig;
|
||||
}
|
||||
|
||||
export interface ApiResponse {
|
||||
status: number;
|
||||
data: unknown;
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "Node16",
|
||||
"moduleResolution": "Node16",
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"resolveJsonModule": true,
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"sourceMap": true
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
Submodule
+1
Submodule mcp-mokogitea-api added at bc8bc0b53a
@@ -126,17 +126,6 @@
|
||||
</select>
|
||||
<p class="help">{{ctx.Locale.Tr "repo.settings.manifest_package_type_help"}}</p>
|
||||
</div>
|
||||
{{end}}
|
||||
<div class="field">
|
||||
<label>{{ctx.Locale.Tr "repo.settings.manifest_element_full"}}</label>
|
||||
<input name="element_name" value="{{.Manifest.ElementName}}" placeholder="{{.Manifest.AutoElementName}}">
|
||||
{{if .Manifest.ElementNameMismatch}}
|
||||
<p class="help tw-text-yellow-600">{{ctx.Locale.Tr "repo.settings.manifest_element_mismatch" .Manifest.AutoElementName}}</p>
|
||||
{{else}}
|
||||
<p class="help">{{ctx.Locale.Tr "repo.settings.manifest_element_full_help"}}</p>
|
||||
{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
<div class="field">
|
||||
<label>{{ctx.Locale.Tr "repo.settings.manifest_element_full"}}</label>
|
||||
<input name="element_name" value="{{.Manifest.ElementName}}" placeholder="{{.Manifest.AutoElementName}}">
|
||||
|
||||
Reference in New Issue
Block a user