From b58ad0dfd60425d975852adfecd4bac1904766eb Mon Sep 17 00:00:00 2001 From: Jonathan Miller Date: Thu, 7 May 2026 16:17:26 -0500 Subject: [PATCH] =?UTF-8?q?feat(tools):=20expand=20to=2088=20tools=20?= =?UTF-8?q?=E2=80=94=20topics,=20collaborators,=20deploy=20keys,=20branch?= =?UTF-8?q?=20protection,=20org=20labels,=20actions=20secrets,=20mirrors,?= =?UTF-8?q?=20stats,=20compare,=20admin,=20issue=20labels?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.6 (1M context) --- CHANGELOG.md | 122 ++++++ CONTRIBUTING.md | 189 ++++++++++ README.md | 286 +++++++++++++++ SECURITY.md | 91 +++++ docs/API.md | 857 +++++++++++++++++++++++++++++++++++++++++++ docs/ARCHITECTURE.md | 140 +++++++ docs/INSTALLATION.md | 166 +++++++++ docs/index.md | 20 + src/index.ts | 309 ++++++++++++++++ 9 files changed, 2180 insertions(+) create mode 100644 CHANGELOG.md create mode 100644 CONTRIBUTING.md create mode 100644 README.md create mode 100644 SECURITY.md create mode 100644 docs/API.md create mode 100644 docs/ARCHITECTURE.md create mode 100644 docs/INSTALLATION.md create mode 100644 docs/index.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..0df9187 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,122 @@ + + +# 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). + +## [0.0.1] - 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 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..bef88d5 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,189 @@ + + +# Contributing to gitea-api-mcp + +Thank you for your interest in contributing to gitea-api-mcp. This document provides guidelines and information for contributors. + +## Table of Contents + +- [Getting Started](#getting-started) +- [Development Setup](#development-setup) +- [Gitea API Explorer](#gitea-api-explorer) +- [Code Style](#code-style) +- [Commit Conventions](#commit-conventions) +- [Branch Protection Rules](#branch-protection-rules) +- [Pull Request Process](#pull-request-process) +- [Adding a New Tool](#adding-a-new-tool) + +## Getting Started + +1. Fork the repository on Gitea: https://git.mokoconsulting.tech/MokoConsulting/gitea-api-mcp +2. Clone your fork locally +3. Create a feature branch from `main` +4. Make your changes +5. Submit a pull request + +## Development Setup + +```bash +git clone https://git.mokoconsulting.tech/YourUsername/gitea-api-mcp.git +cd gitea-api-mcp +npm install +npm run build +``` + +For live recompilation during development: + +```bash +npm run dev +``` + +Ensure you have a valid `~/.gitea-api-mcp.json` config file pointing to a test Gitea instance before testing. + +## Gitea API Explorer + +When adding or modifying tools, refer to your Gitea instance's built-in API explorer: + +``` +https://your-gitea-instance/api/swagger +``` + +For the Moko Consulting instance: + +``` +https://git.mokoconsulting.tech/api/swagger +``` + +The Swagger UI provides: +- Full endpoint documentation with request/response schemas +- Interactive API testing ("Try it out" button) +- Authentication configuration for testing +- Parameter type and validation details + +## Code Style + +- Use TypeScript strict mode +- Follow the existing patterns in `src/index.ts` for tool registration +- Use the `OwnerRepo`, `PaginationParams`, and `ConnectionParam` shared parameter objects +- Format responses through `formatResponse()` +- Include mokostandards file headers on all new source files + +### File Header Template + +```typescript +/* 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. + * INGROUP: gitea-api-mcp + * REPO: https://git.mokoconsulting.tech/MokoConsulting/gitea-api-mcp + * PATH: /src/.ts + * VERSION: 01.00.00 + * BRIEF: + */ +``` + +## Commit Conventions + +This project uses [Conventional Commits](https://www.conventionalcommits.org/): + +``` +(): + +[optional body] + +[optional footer(s)] +``` + +### Types + +| Type | Description | +|------|-------------| +| `feat` | New feature (new tool, new parameter) | +| `fix` | Bug fix | +| `docs` | Documentation only | +| `refactor` | Code change that neither fixes a bug nor adds a feature | +| `chore` | Build process, dependency updates | +| `test` | Adding or updating tests | + +### Scope + +Use the tool category as scope when applicable: + +``` +feat(issues): add gitea_issue_lock tool +fix(client): handle 204 No Content responses +docs(readme): update tool count +``` + +## Branch Protection Rules + +The `main` branch has the following protections: + +- Direct pushes to `main` are not allowed +- All changes must come through pull requests +- At least one approval is required before merging +- Status checks must pass before merging +- Force pushes are disabled + +### Branch Naming + +``` +feat/short-description +fix/issue-number-description +docs/what-changed +refactor/what-changed +``` + +## Pull Request Process + +1. Ensure your branch is up to date with `main` +2. Update documentation if you added or changed tools +3. Update `CHANGELOG.md` under an `[Unreleased]` section +4. Fill out the PR template with a clear description +5. Request review from a maintainer +6. Address any review feedback +7. Squash-merge will be used for final integration + +## Adding a New Tool + +1. Identify the Gitea API endpoint in the Swagger explorer +2. Add the tool registration in `src/index.ts` under the appropriate category section +3. Follow the existing parameter patterns: + - Use `OwnerRepo` for tools that operate on a specific repository + - Use `PaginationParams` for list endpoints + - Always include `ConnectionParam` for multi-connection support +4. Use `z.string().describe('...')` for all parameters with clear descriptions +5. Route through `formatResponse()` for consistent output +6. Update the tool tables in `README.md` and `docs/API.md` +7. Add the tool to `CHANGELOG.md` + +### Example + +```typescript +server.tool( + 'gitea_example_action', + 'Description of what this tool does', + { + ...OwnerRepo, + some_param: z.string().describe('What this parameter controls'), + ...PaginationParams, + ...ConnectionParam, + }, + async ({ owner, repo, some_param, page, limit, connection }) => { + const params: Record = { ...pageQuery({ page, limit }) }; + if (some_param) params['some_param'] = some_param; + return formatResponse( + await clientFor(connection).get(`/repos/${owner}/${repo}/example`, params), + ); + }, +); +``` diff --git a/README.md b/README.md new file mode 100644 index 0000000..74e7908 --- /dev/null +++ b/README.md @@ -0,0 +1,286 @@ + + +# gitea-api-mcp + +[![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) + +> MCP server for Gitea REST API v1 operations -- 61 tools for complete Gitea instance management from Claude Code and other MCP clients. + +## 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 + +```bash +git clone https://git.mokoconsulting.tech/MokoConsulting/gitea-api-mcp.git +cd gitea-api-mcp +npm install +npm run build +``` + +## Configuration + +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`): + +```json +{ + "mcpServers": { + "gitea-moko": { + "command": "node", + "args": ["/path/to/gitea-api-mcp/dist/index.js"] + } + } +} +``` + +### Multi-Connection Usage in Claude Code + +When using multiple connections, pass the `connection` parameter to any tool: + +``` +Use gitea_repo_get with connection "github-mirror" to get owner/repo details. +``` + +If `connection` is omitted, the `defaultConnection` is used. + +## Tools + +### User / Auth (3 tools) + +| 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 | + +### Repositories (8 tools) + +| 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 | + +### File Contents (5 tools) + +| 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) | + +### Branches (4 tools) + +| 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 | + +### Commits (2 tools) + +| Tool | Description | +|------|-------------| +| `gitea_commits_list` | List commits in a repository | +| `gitea_commit_get` | Get a specific commit | + +### Issues (7 tools) + +| 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 | + +### Labels (2 tools) + +| Tool | Description | +|------|-------------| +| `gitea_labels_list` | List labels in a repository | +| `gitea_label_create` | Create a label | + +### Milestones (2 tools) + +| Tool | Description | +|------|-------------| +| `gitea_milestones_list` | List milestones in a repository | +| `gitea_milestone_create` | Create a milestone | + +### Pull Requests (6 tools) + +| 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 | + +### 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. + +## 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 | diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..f6032e8 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,91 @@ + + +# Security Policy + +## Supported Versions + +| Version | Supported | +|---------|-----------| +| 0.0.x | Yes | + +## Reporting a Vulnerability + +To report a security vulnerability, please email **hello@mokoconsulting.tech** with the subject line `[SECURITY] gitea-api-mcp`. Do not open a public issue for security vulnerabilities. + +We will acknowledge receipt within 48 hours and provide an initial assessment within 5 business days. + +## Token Storage Security + +### Configuration File + +The config file `~/.gitea-api-mcp.json` stores Gitea API tokens in plaintext. Follow these practices to protect your tokens: + +#### File Permissions + +Set restrictive permissions on the config file so only your user can read it: + +```bash +chmod 600 ~/.gitea-api-mcp.json +``` + +On Windows, ensure the file is only readable by your user account through the file properties security tab. + +#### What to Avoid + +- **Never** commit `~/.gitea-api-mcp.json` or any file containing tokens to version control +- **Never** share config files containing real tokens +- **Never** log or print token values in debug output +- **Never** store tokens in environment variables visible to other processes if avoidable + +#### Token Scope + +When generating Gitea access tokens, follow the principle of least privilege: + +- Only grant the scopes (permissions) your workflow requires +- Use separate tokens for separate purposes or environments +- Rotate tokens periodically +- Revoke tokens that are no longer needed + +#### Token Generation + +1. Navigate to your Gitea instance Settings > Applications +2. Under "Manage Access Tokens," enter a token name +3. Select only the required scopes +4. Click "Generate Token" +5. Copy the token immediately -- it will not be shown again + +### Network Security + +#### TLS Verification + +By default, the client verifies TLS certificates. The `insecure: true` option disables certificate verification for self-signed certificates. Use this only for: + +- Local development instances +- Internal instances with self-signed certificates where the network is trusted + +**Never** use `insecure: true` for production instances accessible over the public internet. + +#### API Prefix + +All requests are sent to `/api/v1` endpoints with: + +- `Authorization: token ` header +- `Content-Type: application/json` header +- 30-second request timeout + +### MCP Transport Security + +This server uses stdio transport, meaning it communicates through standard input/output with the MCP client (e.g., Claude Code). The token is never exposed through network ports or HTTP endpoints by the MCP server itself. + +## Security Checklist + +- [ ] Config file permissions set to `600` (Unix) or user-only (Windows) +- [ ] Tokens scoped to minimum required permissions +- [ ] Config file excluded from version control (`.gitignore`) +- [ ] `insecure` flag only used for trusted internal instances +- [ ] Tokens rotated on a regular schedule +- [ ] Unused tokens revoked promptly diff --git a/docs/API.md b/docs/API.md new file mode 100644 index 0000000..8455150 --- /dev/null +++ b/docs/API.md @@ -0,0 +1,857 @@ + + +# API Reference + +Full parameter reference for all 61 tools in gitea-api-mcp. + +All tools accept an optional `connection` parameter (string) to select a named connection from your config. If omitted, the default connection is used. + +## Table of Contents + +- [User / Auth](#user--auth) +- [Repositories](#repositories) +- [File Contents](#file-contents) +- [Branches](#branches) +- [Commits](#commits) +- [Issues](#issues) +- [Labels](#labels) +- [Milestones](#milestones) +- [Pull Requests](#pull-requests) +- [Releases](#releases) +- [Tags](#tags) +- [Actions](#actions) +- [Organizations](#organizations) +- [Users](#users) +- [Webhooks](#webhooks) +- [Wiki](#wiki) +- [Notifications](#notifications) +- [Generic](#generic) + +--- + +## User / Auth + +### gitea_me + +Get the authenticated user info. + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `connection` | string | No | Named connection from config | + +### gitea_user_orgs + +List organizations the authenticated user belongs to. + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `page` | number | No | Page number (1-based) | +| `limit` | number | No | Items per page (max 50) | +| `connection` | string | No | Named connection from config | + +### gitea_user_repos + +List repositories owned by the authenticated user. + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `page` | number | No | Page number (1-based) | +| `limit` | number | No | Items per page (max 50) | +| `connection` | string | No | Named connection from config | + +--- + +## Repositories + +### gitea_repo_get + +Get repository details. + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `owner` | string | Yes | Repository owner (user or org) | +| `repo` | string | Yes | Repository name | +| `connection` | string | No | Named connection from config | + +### gitea_repo_create + +Create a new repository. + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `name` | string | Yes | Repository name | +| `org` | string | No | Organization (omit for personal) | +| `description` | string | No | Description | +| `private` | boolean | No | Private repository | +| `auto_init` | boolean | No | Initialize with README | +| `default_branch` | string | No | Default branch (default "main") | +| `template` | boolean | No | Mark as template repository | +| `connection` | string | No | Named connection from config | + +### gitea_repo_delete + +Delete a repository. + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `owner` | string | Yes | Repository owner (user or org) | +| `repo` | string | Yes | Repository name | +| `connection` | string | No | Named connection from config | + +### gitea_repo_edit + +Edit repository settings. + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `owner` | string | Yes | Repository owner (user or org) | +| `repo` | string | Yes | Repository name | +| `description` | string | No | New description | +| `private` | boolean | No | Set private/public | +| `has_issues` | boolean | No | Enable issues | +| `has_wiki` | boolean | No | Enable wiki | +| `has_pull_requests` | boolean | No | Enable PRs | +| `default_branch` | string | No | Default branch | +| `archived` | boolean | No | Archive/unarchive | +| `connection` | string | No | Named connection from config | + +### gitea_repo_fork + +Fork a repository. + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `owner` | string | Yes | Repository owner (user or org) | +| `repo` | string | Yes | Repository name | +| `organization` | string | No | Fork to this org (omit for personal) | +| `name` | string | No | Custom name for fork | +| `connection` | string | No | Named connection from config | + +### gitea_repo_search + +Search repositories. + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `q` | string | Yes | Search query | +| `topic` | boolean | No | Search in topics | +| `sort` | string | No | Sort field: `alpha`, `created`, `updated`, `size`, `stars`, `forks` | +| `order` | string | No | Sort order: `asc`, `desc` | +| `page` | number | No | Page number (1-based) | +| `limit` | number | No | Items per page (max 50) | +| `connection` | string | No | Named connection from config | + +### gitea_org_repos + +List repositories in an organization. + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `org` | string | Yes | Organization name | +| `page` | number | No | Page number (1-based) | +| `limit` | number | No | Items per page (max 50) | +| `connection` | string | No | Named connection from config | + +### gitea_list_connections + +List configured Gitea connections. + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| *(none)* | | | | + +--- + +## File Contents + +### gitea_file_get + +Get file contents from a repository. + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `owner` | string | Yes | Repository owner (user or org) | +| `repo` | string | Yes | Repository name | +| `filepath` | string | Yes | File path (e.g. "src/index.ts") | +| `ref` | string | No | Branch/tag/commit (default: default branch) | +| `connection` | string | No | Named connection from config | + +### gitea_dir_get + +Get directory contents (file listing) from a repository. + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `owner` | string | Yes | Repository owner (user or org) | +| `repo` | string | Yes | Repository name | +| `dirpath` | string | No | Directory path (default: root) | +| `ref` | string | No | Branch/tag/commit | +| `connection` | string | No | Named connection from config | + +### gitea_file_create_or_update + +Create or update a file in a repository. + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `owner` | string | Yes | Repository owner (user or org) | +| `repo` | string | Yes | Repository name | +| `filepath` | string | Yes | File path | +| `content` | string | Yes | File content (will be base64-encoded automatically) | +| `message` | string | Yes | Commit message | +| `branch` | string | No | Branch (default: default branch) | +| `sha` | string | No | SHA of existing file (required for updates) | +| `connection` | string | No | Named connection from config | + +### gitea_file_delete + +Delete a file from a repository. + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `owner` | string | Yes | Repository owner (user or org) | +| `repo` | string | Yes | Repository name | +| `filepath` | string | Yes | File path to delete | +| `sha` | string | Yes | SHA of file to delete | +| `message` | string | Yes | Commit message | +| `branch` | string | No | Branch | +| `connection` | string | No | Named connection from config | + +### gitea_tree_get + +Get the git tree for a repository (recursive file listing). + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `owner` | string | Yes | Repository owner (user or org) | +| `repo` | string | Yes | Repository name | +| `sha` | string | Yes | Tree SHA or branch name | +| `recursive` | boolean | No | Recursive listing (default true) | +| `connection` | string | No | Named connection from config | + +--- + +## Branches + +### gitea_branches_list + +List branches in a repository. + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `owner` | string | Yes | Repository owner (user or org) | +| `repo` | string | Yes | Repository name | +| `page` | number | No | Page number (1-based) | +| `limit` | number | No | Items per page (max 50) | +| `connection` | string | No | Named connection from config | + +### gitea_branch_get + +Get a specific branch. + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `owner` | string | Yes | Repository owner (user or org) | +| `repo` | string | Yes | Repository name | +| `branch` | string | Yes | Branch name | +| `connection` | string | No | Named connection from config | + +### gitea_branch_create + +Create a new branch. + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `owner` | string | Yes | Repository owner (user or org) | +| `repo` | string | Yes | Repository name | +| `new_branch_name` | string | Yes | Name for new branch | +| `old_branch_name` | string | No | Source branch (default: default branch) | +| `connection` | string | No | Named connection from config | + +### gitea_branch_delete + +Delete a branch. + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `owner` | string | Yes | Repository owner (user or org) | +| `repo` | string | Yes | Repository name | +| `branch` | string | Yes | Branch name | +| `connection` | string | No | Named connection from config | + +--- + +## Commits + +### gitea_commits_list + +List commits in a repository. + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `owner` | string | Yes | Repository owner (user or org) | +| `repo` | string | Yes | Repository name | +| `sha` | string | No | Branch or commit SHA | +| `path` | string | No | Filter by file path | +| `page` | number | No | Page number (1-based) | +| `limit` | number | No | Items per page (max 50) | +| `connection` | string | No | Named connection from config | + +### gitea_commit_get + +Get a specific commit. + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `owner` | string | Yes | Repository owner (user or org) | +| `repo` | string | Yes | Repository name | +| `sha` | string | Yes | Commit SHA | +| `connection` | string | No | Named connection from config | + +--- + +## Issues + +### gitea_issues_list + +List issues in a repository. + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `owner` | string | Yes | Repository owner (user or org) | +| `repo` | string | Yes | Repository name | +| `state` | string | No | Issue state filter: `open`, `closed`, `all` | +| `type` | string | No | Filter by type: `issues`, `pulls` | +| `labels` | string | No | Comma-separated label names | +| `milestones` | string | No | Comma-separated milestone names | +| `q` | string | No | Search query | +| `page` | number | No | Page number (1-based) | +| `limit` | number | No | Items per page (max 50) | +| `connection` | string | No | Named connection from config | + +### gitea_issue_get + +Get a single issue by number. + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `owner` | string | Yes | Repository owner (user or org) | +| `repo` | string | Yes | Repository name | +| `number` | number | Yes | Issue number | +| `connection` | string | No | Named connection from config | + +### gitea_issue_create + +Create a new issue. + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `owner` | string | Yes | Repository owner (user or org) | +| `repo` | string | Yes | Repository name | +| `title` | string | Yes | Issue title | +| `body` | string | No | Issue body (markdown) | +| `labels` | number[] | No | Label IDs | +| `milestone` | number | No | Milestone ID | +| `assignees` | string[] | No | Usernames to assign | +| `connection` | string | No | Named connection from config | + +### gitea_issue_update + +Update an issue. + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `owner` | string | Yes | Repository owner (user or org) | +| `repo` | string | Yes | Repository name | +| `number` | number | Yes | Issue number | +| `title` | string | No | New title | +| `body` | string | No | New body | +| `state` | string | No | State: `open`, `closed` | +| `assignees` | string[] | No | Assignees | +| `milestone` | number | No | Milestone ID | +| `connection` | string | No | Named connection from config | + +### gitea_issue_comments_list + +List comments on an issue. + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `owner` | string | Yes | Repository owner (user or org) | +| `repo` | string | Yes | Repository name | +| `number` | number | Yes | Issue number | +| `page` | number | No | Page number (1-based) | +| `limit` | number | No | Items per page (max 50) | +| `connection` | string | No | Named connection from config | + +### gitea_issue_comment_create + +Add a comment to an issue. + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `owner` | string | Yes | Repository owner (user or org) | +| `repo` | string | Yes | Repository name | +| `number` | number | Yes | Issue number | +| `body` | string | Yes | Comment body (markdown) | +| `connection` | string | No | Named connection from config | + +### gitea_issue_search + +Search issues across all repositories. + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `q` | string | Yes | Search query | +| `state` | string | No | State filter: `open`, `closed`, `all` | +| `labels` | string | No | Comma-separated label IDs | +| `type` | string | No | Filter type: `issues`, `pulls` | +| `page` | number | No | Page number (1-based) | +| `limit` | number | No | Items per page (max 50) | +| `connection` | string | No | Named connection from config | + +--- + +## Labels + +### gitea_labels_list + +List labels in a repository. + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `owner` | string | Yes | Repository owner (user or org) | +| `repo` | string | Yes | Repository name | +| `page` | number | No | Page number (1-based) | +| `limit` | number | No | Items per page (max 50) | +| `connection` | string | No | Named connection from config | + +### gitea_label_create + +Create a label. + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `owner` | string | Yes | Repository owner (user or org) | +| `repo` | string | Yes | Repository name | +| `name` | string | Yes | Label name | +| `color` | string | Yes | Color hex (e.g. "#d73a4a") | +| `description` | string | No | Label description | +| `connection` | string | No | Named connection from config | + +--- + +## Milestones + +### gitea_milestones_list + +List milestones in a repository. + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `owner` | string | Yes | Repository owner (user or org) | +| `repo` | string | Yes | Repository name | +| `state` | string | No | State filter: `open`, `closed`, `all` | +| `page` | number | No | Page number (1-based) | +| `limit` | number | No | Items per page (max 50) | +| `connection` | string | No | Named connection from config | + +### gitea_milestone_create + +Create a milestone. + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `owner` | string | Yes | Repository owner (user or org) | +| `repo` | string | Yes | Repository name | +| `title` | string | Yes | Milestone title | +| `description` | string | No | Description | +| `due_on` | string | No | Due date (ISO 8601) | +| `connection` | string | No | Named connection from config | + +--- + +## Pull Requests + +### gitea_pulls_list + +List pull requests. + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `owner` | string | Yes | Repository owner (user or org) | +| `repo` | string | Yes | Repository name | +| `state` | string | No | State filter: `open`, `closed`, `all` | +| `sort` | string | No | Sort: `oldest`, `recentupdate`, `leastupdate`, `mostcomment`, `leastcomment`, `priority` | +| `labels` | string | No | Comma-separated label IDs | +| `milestone` | number | No | Milestone ID | +| `page` | number | No | Page number (1-based) | +| `limit` | number | No | Items per page (max 50) | +| `connection` | string | No | Named connection from config | + +### gitea_pull_get + +Get a single pull request. + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `owner` | string | Yes | Repository owner (user or org) | +| `repo` | string | Yes | Repository name | +| `number` | number | Yes | PR number | +| `connection` | string | No | Named connection from config | + +### gitea_pull_create + +Create a pull request. + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `owner` | string | Yes | Repository owner (user or org) | +| `repo` | string | Yes | Repository name | +| `title` | string | Yes | PR title | +| `head` | string | Yes | Source branch | +| `base` | string | Yes | Target branch | +| `body` | string | No | PR description (markdown) | +| `labels` | number[] | No | Label IDs | +| `milestone` | number | No | Milestone ID | +| `assignees` | string[] | No | Assignees | +| `connection` | string | No | Named connection from config | + +### gitea_pull_merge + +Merge a pull request. + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `owner` | string | Yes | Repository owner (user or org) | +| `repo` | string | Yes | Repository name | +| `number` | number | Yes | PR number | +| `merge_type` | string | No | Merge method: `merge`, `rebase`, `squash`, `rebase-merge` (default: merge) | +| `title` | string | No | Custom merge commit title | +| `message` | string | No | Custom merge commit message | +| `delete_branch_after_merge` | boolean | No | Delete head branch after merge | +| `connection` | string | No | Named connection from config | + +### gitea_pull_files + +List files changed in a pull request. + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `owner` | string | Yes | Repository owner (user or org) | +| `repo` | string | Yes | Repository name | +| `number` | number | Yes | PR number | +| `page` | number | No | Page number (1-based) | +| `limit` | number | No | Items per page (max 50) | +| `connection` | string | No | Named connection from config | + +### gitea_pull_review_create + +Create a pull request review. + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `owner` | string | Yes | Repository owner (user or org) | +| `repo` | string | Yes | Repository name | +| `number` | number | Yes | PR number | +| `event` | string | Yes | Review action: `APPROVED`, `REQUEST_CHANGES`, `COMMENT` | +| `body` | string | No | Review comment | +| `connection` | string | No | Named connection from config | + +--- + +## Releases + +### gitea_releases_list + +List releases. + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `owner` | string | Yes | Repository owner (user or org) | +| `repo` | string | Yes | Repository name | +| `page` | number | No | Page number (1-based) | +| `limit` | number | No | Items per page (max 50) | +| `connection` | string | No | Named connection from config | + +### gitea_release_get + +Get a single release by ID. + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `owner` | string | Yes | Repository owner (user or org) | +| `repo` | string | Yes | Repository name | +| `id` | number | Yes | Release ID | +| `connection` | string | No | Named connection from config | + +### gitea_release_latest + +Get the latest release. + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `owner` | string | Yes | Repository owner (user or org) | +| `repo` | string | Yes | Repository name | +| `connection` | string | No | Named connection from config | + +### gitea_release_create + +Create a new release. + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `owner` | string | Yes | Repository owner (user or org) | +| `repo` | string | Yes | Repository name | +| `tag_name` | string | Yes | Tag name (e.g. "v1.0.0") | +| `name` | string | No | Release title | +| `body` | string | No | Release notes (markdown) | +| `target_commitish` | string | No | Target branch/commit | +| `draft` | boolean | No | Create as draft | +| `prerelease` | boolean | No | Mark as prerelease | +| `connection` | string | No | Named connection from config | + +### gitea_release_delete + +Delete a release. + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `owner` | string | Yes | Repository owner (user or org) | +| `repo` | string | Yes | Repository name | +| `id` | number | Yes | Release ID | +| `connection` | string | No | Named connection from config | + +--- + +## Tags + +### gitea_tags_list + +List tags. + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `owner` | string | Yes | Repository owner (user or org) | +| `repo` | string | Yes | Repository name | +| `page` | number | No | Page number (1-based) | +| `limit` | number | No | Items per page (max 50) | +| `connection` | string | No | Named connection from config | + +### gitea_tag_create + +Create a tag. + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `owner` | string | Yes | Repository owner (user or org) | +| `repo` | string | Yes | Repository name | +| `tag_name` | string | Yes | Tag name | +| `target` | string | No | Target branch/commit SHA | +| `message` | string | No | Tag message (annotated tag) | +| `connection` | string | No | Named connection from config | + +### gitea_tag_delete + +Delete a tag. + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `owner` | string | Yes | Repository owner (user or org) | +| `repo` | string | Yes | Repository name | +| `tag` | string | Yes | Tag name | +| `connection` | string | No | Named connection from config | + +--- + +## Actions + +### gitea_actions_runs_list + +List workflow runs for a repository. + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `owner` | string | Yes | Repository owner (user or org) | +| `repo` | string | Yes | Repository name | +| `page` | number | No | Page number (1-based) | +| `limit` | number | No | Items per page (max 50) | +| `connection` | string | No | Named connection from config | + +### gitea_actions_run_get + +Get a specific workflow run. + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `owner` | string | Yes | Repository owner (user or org) | +| `repo` | string | Yes | Repository name | +| `run_id` | number | Yes | Run ID | +| `connection` | string | No | Named connection from config | + +--- + +## Organizations + +### gitea_org_get + +Get organization details. + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `org` | string | Yes | Organization name | +| `connection` | string | No | Named connection from config | + +### gitea_org_teams_list + +List teams in an organization. + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `org` | string | Yes | Organization name | +| `page` | number | No | Page number (1-based) | +| `limit` | number | No | Items per page (max 50) | +| `connection` | string | No | Named connection from config | + +### gitea_org_members_list + +List members of an organization. + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `org` | string | Yes | Organization name | +| `page` | number | No | Page number (1-based) | +| `limit` | number | No | Items per page (max 50) | +| `connection` | string | No | Named connection from config | + +--- + +## Users + +### gitea_user_get + +Get a user profile. + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `username` | string | Yes | Username | +| `connection` | string | No | Named connection from config | + +### gitea_users_search + +Search users. + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `q` | string | Yes | Search query | +| `page` | number | No | Page number (1-based) | +| `limit` | number | No | Items per page (max 50) | +| `connection` | string | No | Named connection from config | + +--- + +## Webhooks + +### gitea_webhooks_list + +List webhooks for a repository. + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `owner` | string | Yes | Repository owner (user or org) | +| `repo` | string | Yes | Repository name | +| `page` | number | No | Page number (1-based) | +| `limit` | number | No | Items per page (max 50) | +| `connection` | string | No | Named connection from config | + +### gitea_webhook_create + +Create a webhook. + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `owner` | string | Yes | Repository owner (user or org) | +| `repo` | string | Yes | Repository name | +| `type` | string | Yes | Hook type: `gitea`, `slack`, `discord`, `dingtalk`, `telegram`, `msteams`, `feishu`, `matrix`, `wechatwork`, `packagist` | +| `url` | string | Yes | Webhook URL | +| `events` | string[] | No | Events to listen for (e.g. `["push", "pull_request"]`) | +| `active` | boolean | No | Active status | +| `connection` | string | No | Named connection from config | + +--- + +## Wiki + +### gitea_wiki_pages_list + +List wiki pages. + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `owner` | string | Yes | Repository owner (user or org) | +| `repo` | string | Yes | Repository name | +| `page` | number | No | Page number (1-based) | +| `limit` | number | No | Items per page (max 50) | +| `connection` | string | No | Named connection from config | + +### gitea_wiki_page_get + +Get a wiki page. + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `owner` | string | Yes | Repository owner (user or org) | +| `repo` | string | Yes | Repository name | +| `page_name` | string | Yes | Page name/slug | +| `connection` | string | No | Named connection from config | + +--- + +## Notifications + +### gitea_notifications_list + +List notifications for the authenticated user. + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `status_types` | string | No | Comma-separated: `read`, `unread`, `pinned` | +| `page` | number | No | Page number (1-based) | +| `limit` | number | No | Items per page (max 50) | +| `connection` | string | No | Named connection from config | + +### gitea_notifications_read + +Mark all notifications as read. + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `connection` | string | No | Named connection from config | + +--- + +## Generic + +### gitea_api_request + +Make a raw API request to any Gitea v1 endpoint. + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `method` | string | Yes | HTTP method: `GET`, `POST`, `PUT`, `PATCH`, `DELETE` | +| `endpoint` | string | Yes | API endpoint path (e.g. "/repos/owner/repo") | +| `body` | object | No | Request body | +| `params` | object | No | Query parameters | +| `connection` | string | No | Named connection from config | + +### gitea_list_connections + +List configured Gitea connections. + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| *(none)* | | | | diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md new file mode 100644 index 0000000..9975bce --- /dev/null +++ b/docs/ARCHITECTURE.md @@ -0,0 +1,140 @@ + + +# Architecture + +## Component Overview + +``` +src/ + index.ts -- MCP server entry point, tool registrations (61 tools) + client.ts -- HTTP client for Gitea REST API v1 + config.ts -- Configuration loader (multi-connection support) + types.ts -- TypeScript type definitions +``` + +### index.ts -- Server Entry Point + +The main module that: + +- Creates the `McpServer` instance with stdio transport +- Loads configuration on startup via `loadConfig()` +- Registers all 61 tools organized by category +- Provides shared parameter schemas (`OwnerRepo`, `PaginationParams`, `ConnectionParam`) +- Routes tool calls through `clientFor(connection)` to resolve the target Gitea instance +- Formats all API responses through `formatResponse()` for consistent MCP output + +### client.ts -- HTTP Client + +`GiteaClient` is a zero-dependency HTTP client built on `node:https` and `node:http`: + +- Prepends `/api/v1` to all endpoint paths +- Sets `Authorization: token ` header on every request (Gitea's native auth format) +- Supports `GET`, `POST`, `PUT`, `PATCH`, `DELETE` methods +- Handles query parameter construction via `URL.searchParams` +- Parses JSON responses automatically +- 30-second request timeout +- Optional TLS certificate verification bypass (`insecure` flag) + +### config.ts -- Configuration Loader + +Loads the connection configuration from disk: + +- Default path: `~/.gitea-api-mcp.json` +- Override via `GITEA_API_MCP_CONFIG` environment variable +- Validates that at least one connection exists +- Sets `defaultConnection` to the first connection if not explicitly specified +- `getConnection()` resolves a named connection or falls back to the default + +### types.ts -- Type Definitions + +Three core interfaces: + +- `GiteaConnection` -- Single connection config (`baseUrl`, `token`, `insecure?`) +- `GiteaConfig` -- Full config with named connections map and default +- `ApiResponse` -- Standardized response wrapper (`status`, `data`) + +## Design Decisions + +### Token Authentication + +Gitea uses `Authorization: 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. + +### Multi-Connection Architecture + +Every tool accepts an optional `connection` parameter. This enables: + +- Managing multiple Gitea instances from a single MCP server +- Switching between production, staging, and development instances +- Cross-instance operations within a single Claude Code session + +The connection is resolved at call time, not at startup, so different tool calls can target different instances. + +### node:https (Zero HTTP Dependencies) + +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: + +- Eliminates supply-chain risk from HTTP client dependencies +- Reduces `node_modules` size +- Avoids compatibility issues across Node.js versions +- Provides full control over TLS configuration (needed for `insecure` flag) + +### Stdio Transport + +The MCP server uses stdio transport (stdin/stdout) rather than HTTP/SSE. This means: + +- No network ports are opened by the server +- Tokens are never exposed through HTTP endpoints +- The server runs as a child process of the MCP client +- Communication is process-local, reducing attack surface + +### Shared Parameter Objects + +Common parameter patterns are defined as reusable objects: + +- `OwnerRepo` -- `{ owner, repo }` for all repository-scoped operations +- `PaginationParams` -- `{ page, limit }` for all list endpoints +- `ConnectionParam` -- `{ connection }` for multi-connection routing + +This ensures consistency across all 61 tools and simplifies adding new tools. + +## Data Flow + +``` + +-----------------+ + | Claude Code / | + | MCP Client | + +--------+--------+ + | + stdio (JSON-RPC) + | + +--------v--------+ + | index.ts | + | McpServer | + | (tool router) | + +--------+--------+ + | + +-----------+-----------+ + | | + +------v------+ +-------v-------+ + | config.ts | | client.ts | + | loadConfig | | GiteaClient | + | getConn | | (HTTP calls) | + +------+------+ +-------+-------+ + | | + +------v------+ HTTPS / HTTP + | ~/.gitea- | | + | api-mcp.json| +--------v--------+ + +-------------+ | Gitea Instance | + | /api/v1/* | + +-----------------+ +``` + +1. Claude Code sends a JSON-RPC tool call over stdio +2. `McpServer` routes the call to the registered tool handler +3. The handler calls `clientFor(connection)` which resolves the connection from config +4. `GiteaClient` constructs the HTTP request and sends it to the Gitea API +5. The JSON response is wrapped in `formatResponse()` and returned to the MCP client diff --git a/docs/INSTALLATION.md b/docs/INSTALLATION.md new file mode 100644 index 0000000..d878c36 --- /dev/null +++ b/docs/INSTALLATION.md @@ -0,0 +1,166 @@ + + +# Installation Guide + +## Prerequisites + +- **Node.js** >= 20.0.0 ([download](https://nodejs.org)) +- **npm** (included with Node.js) +- A **Gitea instance** with API access enabled +- A **Gitea access token** with appropriate scopes + +## Install + +### Clone and Build + +```bash +git clone https://git.mokoconsulting.tech/MokoConsulting/gitea-api-mcp.git +cd gitea-api-mcp +npm install +npm run build +``` + +The compiled output is placed in `dist/index.js`. + +### Verify the Build + +```bash +node dist/index.js --help +``` + +## Gitea Token Generation + +1. Log in to your Gitea instance (e.g., `https://git.mokoconsulting.tech`) +2. Navigate to **Settings** (click your avatar, top-right corner) +3. Select **Applications** from the sidebar +4. Under **Manage Access Tokens**, enter a descriptive token name (e.g., `mcp-server`) +5. Select the required permission scopes: + - `repo` -- for repository operations + - `admin:org` -- for organization management + - `notification` -- for notification tools + - `user` -- for user profile operations + - `issue` -- for issue and pull request tools + - `package` -- if using package-related endpoints +6. Click **Generate Token** +7. **Copy the token immediately** -- it will not be displayed again + +## Configuration + +### Config File Format + +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 | +|-------|------|----------|-------------| +| `defaultConnection` | string | No | Name of the default connection (defaults to the first one) | +| `connections` | object | Yes | Map of named connections | +| `connections..baseUrl` | string | Yes | Base URL of the Gitea instance (no trailing slash) | +| `connections..token` | string | Yes | Gitea API access token | +| `connections..insecure` | boolean | No | Skip TLS certificate verification (default: `false`) | + +### Custom Config Path + +Override the default config location with an environment variable: + +```bash +export GITEA_API_MCP_CONFIG=/path/to/custom-config.json +``` + +### File Permissions + +Secure your config file since it contains API tokens: + +```bash +chmod 600 ~/.gitea-api-mcp.json +``` + +## Claude Code Registration + +### Project-Level (`.mcp.json`) + +Create or edit `.mcp.json` in your project root: + +```json +{ + "mcpServers": { + "gitea-moko": { + "command": "node", + "args": ["/absolute/path/to/gitea-api-mcp/dist/index.js"] + } + } +} +``` + +### Global-Level (`~/.claude/claude_desktop_config.json`) + +```json +{ + "mcpServers": { + "gitea-moko": { + "command": "node", + "args": ["/absolute/path/to/gitea-api-mcp/dist/index.js"] + } + } +} +``` + +After registering, restart Claude Code or run `/mcp` to verify the server is connected. + +## Troubleshooting + +### "Failed to load config" Error + +- Verify `~/.gitea-api-mcp.json` exists and is valid JSON +- Check that at least one connection is defined in `connections` +- If using `GITEA_API_MCP_CONFIG`, verify the path is correct and the file is readable + +### "Connection not found" Error + +- The `connection` parameter you passed does not match any key in `connections` +- Run `gitea_list_connections` to see available connections +- Check for typos in the connection name + +### Authentication Failures (HTTP 401) + +- Verify your token is correct and has not expired +- Ensure the token has the required scopes for the operation +- Check that the `baseUrl` points to the correct Gitea instance +- Confirm the Gitea instance has API access enabled (admin setting) + +### TLS / Certificate Errors + +- For self-signed certificates, set `"insecure": true` in the connection config +- Ensure the Gitea instance URL uses the correct protocol (`https://` vs `http://`) + +### Timeout Errors + +- The default request timeout is 30 seconds +- Check network connectivity to the Gitea instance +- Verify the Gitea instance is running and responsive +- For large responses (e.g., recursive tree listings), consider pagination + +### MCP Server Not Appearing in Claude Code + +- Ensure the path in your MCP config is an absolute path to `dist/index.js` +- Verify the build completed successfully (`npm run build`) +- Check that Node.js >= 20.0.0 is in your PATH +- Restart Claude Code after modifying MCP configuration diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..5d0c60b --- /dev/null +++ b/docs/index.md @@ -0,0 +1,20 @@ + + +# gitea-api-mcp Documentation + +## Contents + +- [INSTALLATION.md](INSTALLATION.md) -- Prerequisites, installation, configuration, and troubleshooting +- [ARCHITECTURE.md](ARCHITECTURE.md) -- Component overview, design decisions, and data flow +- [API.md](API.md) -- Full parameter reference for all 61 tools + +## Project-Level Documentation + +- [README](../README.md) -- Project overview, quick start, and tool tables +- [CHANGELOG](../CHANGELOG.md) -- Version history and release notes +- [CONTRIBUTING](../CONTRIBUTING.md) -- Development guidelines and contribution process +- [SECURITY](../SECURITY.md) -- Security policy and token storage best practices diff --git a/src/index.ts b/src/index.ts index 8debb80..e7aaead 100644 --- a/src/index.ts +++ b/src/index.ts @@ -864,6 +864,315 @@ server.tool( async ({ connection }) => formatResponse(await clientFor(connection).put('/notifications', { })), ); +// ── Topics ────────────────────────────────────────────────────────────── + +server.tool( + 'gitea_repo_topics', + 'Get topics (tags) for a repository', + { ...OwnerRepo, ...ConnectionParam }, + async ({ owner, repo, connection }) => formatResponse(await clientFor(connection).get(`/repos/${owner}/${repo}/topics`)), +); + +server.tool( + 'gitea_repo_topics_set', + 'Set topics for a repository (replaces all existing)', + { + ...OwnerRepo, + topics: z.array(z.string()).describe('Array of topic names'), + ...ConnectionParam, + }, + async ({ owner, repo, topics, connection }) => formatResponse(await clientFor(connection).put(`/repos/${owner}/${repo}/topics`, { topics })), +); + +server.tool( + 'gitea_topic_search', + 'Search topics across all repositories', + { + q: z.string().describe('Search query'), + ...PaginationParams, + ...ConnectionParam, + }, + async ({ q, page, limit, connection }) => formatResponse(await clientFor(connection).get('/topics/search', { q, ...pageQuery({ page, limit }) })), +); + +// ── Collaborators ─────────────────────────────────────────────────────── + +server.tool( + 'gitea_collaborators_list', + 'List collaborators on a repository', + { ...OwnerRepo, ...PaginationParams, ...ConnectionParam }, + async ({ owner, repo, page, limit, connection }) => formatResponse(await clientFor(connection).get(`/repos/${owner}/${repo}/collaborators`, pageQuery({ page, limit }))), +); + +server.tool( + 'gitea_collaborator_add', + 'Add a collaborator to a repository', + { + ...OwnerRepo, + collaborator: z.string().describe('Username to add'), + permission: z.enum(['read', 'write', 'admin']).optional().describe('Permission level (default: write)'), + ...ConnectionParam, + }, + async ({ owner, repo, collaborator, permission, connection }) => { + const body: Record = {}; + if (permission) body.permission = permission; + return formatResponse(await clientFor(connection).put(`/repos/${owner}/${repo}/collaborators/${collaborator}`, body)); + }, +); + +server.tool( + 'gitea_collaborator_remove', + 'Remove a collaborator from a repository', + { + ...OwnerRepo, + collaborator: z.string().describe('Username to remove'), + ...ConnectionParam, + }, + async ({ owner, repo, collaborator, connection }) => formatResponse(await clientFor(connection).delete(`/repos/${owner}/${repo}/collaborators/${collaborator}`)), +); + +// ── Deploy Keys ───────────────────────────────────────────────────────── + +server.tool( + 'gitea_deploy_keys_list', + 'List deploy keys for a repository', + { ...OwnerRepo, ...PaginationParams, ...ConnectionParam }, + async ({ owner, repo, page, limit, connection }) => formatResponse(await clientFor(connection).get(`/repos/${owner}/${repo}/keys`, pageQuery({ page, limit }))), +); + +server.tool( + 'gitea_deploy_key_create', + 'Add a deploy key to a repository', + { + ...OwnerRepo, + title: z.string().describe('Key title'), + key: z.string().describe('SSH public key content'), + read_only: z.boolean().optional().describe('Read-only access (default: true)'), + ...ConnectionParam, + }, + async ({ owner, repo, title, key, read_only, connection }) => { + return formatResponse(await clientFor(connection).post(`/repos/${owner}/${repo}/keys`, { + title, + key, + read_only: read_only ?? true, + })); + }, +); + +server.tool( + 'gitea_deploy_key_delete', + 'Remove a deploy key from a repository', + { + ...OwnerRepo, + id: z.number().describe('Deploy key ID'), + ...ConnectionParam, + }, + async ({ owner, repo, id, connection }) => formatResponse(await clientFor(connection).delete(`/repos/${owner}/${repo}/keys/${id}`)), +); + +// ── Branch Protection ─────────────────────────────────────────────────── + +server.tool( + 'gitea_branch_protections_list', + 'List branch protection rules for a repository', + { ...OwnerRepo, ...ConnectionParam }, + async ({ owner, repo, connection }) => formatResponse(await clientFor(connection).get(`/repos/${owner}/${repo}/branch_protections`)), +); + +server.tool( + 'gitea_branch_protection_create', + 'Create a branch protection rule', + { + ...OwnerRepo, + branch_name: z.string().describe('Branch name or pattern (e.g. "main", "release/*")'), + enable_push: z.boolean().optional().describe('Allow push (default: true)'), + enable_push_whitelist: z.boolean().optional().describe('Enable push whitelist'), + push_whitelist_usernames: z.array(z.string()).optional().describe('Users allowed to push'), + require_signed_commits: z.boolean().optional().describe('Require signed commits'), + required_approvals: z.number().optional().describe('Required PR approvals (0 = none)'), + enable_status_check: z.boolean().optional().describe('Require status checks'), + status_check_contexts: z.array(z.string()).optional().describe('Required status check names'), + ...ConnectionParam, + }, + async ({ owner, repo, branch_name, enable_push, enable_push_whitelist, push_whitelist_usernames, require_signed_commits, required_approvals, enable_status_check, status_check_contexts, connection }) => { + const body: Record = { branch_name }; + if (enable_push !== undefined) body.enable_push = enable_push; + if (enable_push_whitelist !== undefined) body.enable_push_whitelist = enable_push_whitelist; + if (push_whitelist_usernames) body.push_whitelist_usernames = push_whitelist_usernames; + if (require_signed_commits !== undefined) body.require_signed_commits = require_signed_commits; + if (required_approvals !== undefined) body.required_approvals = required_approvals; + if (enable_status_check !== undefined) body.enable_status_check = enable_status_check; + if (status_check_contexts) body.status_check_contexts = status_check_contexts; + return formatResponse(await clientFor(connection).post(`/repos/${owner}/${repo}/branch_protections`, body)); + }, +); + +server.tool( + 'gitea_branch_protection_delete', + 'Delete a branch protection rule', + { + ...OwnerRepo, + name: z.string().describe('Branch protection rule name'), + ...ConnectionParam, + }, + async ({ owner, repo, name, connection }) => formatResponse(await clientFor(connection).delete(`/repos/${owner}/${repo}/branch_protections/${name}`)), +); + +// ── Organization Labels ───────────────────────────────────────────────── + +server.tool( + 'gitea_org_labels_list', + 'List labels for an organization (shared across repos)', + { + org: z.string().describe('Organization name'), + ...PaginationParams, + ...ConnectionParam, + }, + async ({ org, page, limit, connection }) => formatResponse(await clientFor(connection).get(`/orgs/${org}/labels`, pageQuery({ page, limit }))), +); + +server.tool( + 'gitea_org_label_create', + 'Create an organization-level label', + { + org: z.string().describe('Organization name'), + name: z.string().describe('Label name'), + color: z.string().describe('Color hex (e.g. "#d73a4a")'), + description: z.string().optional().describe('Label description'), + ...ConnectionParam, + }, + async ({ org, name, color, description, connection }) => { + const body: Record = { name, color }; + if (description) body.description = description; + return formatResponse(await clientFor(connection).post(`/orgs/${org}/labels`, body)); + }, +); + +// ── Repository Secrets (Actions) ──────────────────────────────────────── + +server.tool( + 'gitea_repo_actions_secrets_list', + 'List Actions secrets for a repository (names only, values hidden)', + { ...OwnerRepo, ...PaginationParams, ...ConnectionParam }, + async ({ owner, repo, page, limit, connection }) => formatResponse(await clientFor(connection).get(`/repos/${owner}/${repo}/actions/secrets`, pageQuery({ page, limit }))), +); + +server.tool( + 'gitea_repo_actions_secret_create', + 'Create or update an Actions secret', + { + ...OwnerRepo, + name: z.string().describe('Secret name'), + data: z.string().describe('Secret value'), + ...ConnectionParam, + }, + async ({ owner, repo, name, data, connection }) => formatResponse(await clientFor(connection).put(`/repos/${owner}/${repo}/actions/secrets/${name}`, { data })), +); + +server.tool( + 'gitea_repo_actions_secret_delete', + 'Delete an Actions secret', + { + ...OwnerRepo, + name: z.string().describe('Secret name'), + ...ConnectionParam, + }, + async ({ owner, repo, name, connection }) => formatResponse(await clientFor(connection).delete(`/repos/${owner}/${repo}/actions/secrets/${name}`)), +); + +// ── Repo Transfer / Mirror ────────────────────────────────────────────── + +server.tool( + 'gitea_repo_mirror_sync', + 'Trigger a push mirror sync for a repository', + { ...OwnerRepo, ...ConnectionParam }, + async ({ owner, repo, connection }) => formatResponse(await clientFor(connection).post(`/repos/${owner}/${repo}/mirror-sync`, {})), +); + +server.tool( + 'gitea_repo_mirrors_list', + 'List push mirrors for a repository', + { ...OwnerRepo, ...ConnectionParam }, + async ({ owner, repo, connection }) => formatResponse(await clientFor(connection).get(`/repos/${owner}/${repo}/push_mirrors`)), +); + +// ── Repo Statistics ───────────────────────────────────────────────────── + +server.tool( + 'gitea_repo_languages', + 'Get language breakdown for a repository', + { ...OwnerRepo, ...ConnectionParam }, + async ({ owner, repo, connection }) => formatResponse(await clientFor(connection).get(`/repos/${owner}/${repo}/languages`)), +); + +server.tool( + 'gitea_repo_contributors', + 'List contributors with commit counts', + { ...OwnerRepo, ...ConnectionParam }, + async ({ owner, repo, connection }) => formatResponse(await clientFor(connection).get(`/repos/${owner}/${repo}/contributors`)), +); + +// ── Issue Labels (Bulk) ───────────────────────────────────────────────── + +server.tool( + 'gitea_issue_labels_set', + 'Set labels on an issue (replaces existing)', + { + ...OwnerRepo, + number: z.number().describe('Issue/PR number'), + labels: z.array(z.number()).describe('Label IDs to set'), + ...ConnectionParam, + }, + async ({ owner, repo, number, labels, connection }) => formatResponse(await clientFor(connection).put(`/repos/${owner}/${repo}/issues/${number}/labels`, { labels })), +); + +// ── Diff / Compare ────────────────────────────────────────────────────── + +server.tool( + 'gitea_compare', + 'Compare two branches, tags, or commits (returns diff stats)', + { + ...OwnerRepo, + base: z.string().describe('Base branch/tag/SHA'), + head: z.string().describe('Head branch/tag/SHA'), + ...ConnectionParam, + }, + async ({ owner, repo, base, head, connection }) => formatResponse(await clientFor(connection).get(`/repos/${owner}/${repo}/compare/${base}...${head}`)), +); + +// ── Gitea Admin (Instance-Level) ──────────────────────────────────────── + +server.tool( + 'gitea_admin_orgs_list', + 'List all organizations (admin only)', + { ...PaginationParams, ...ConnectionParam }, + async ({ page, limit, connection }) => formatResponse(await clientFor(connection).get('/admin/orgs', pageQuery({ page, limit }))), +); + +server.tool( + 'gitea_admin_users_list', + 'List all users (admin only)', + { ...PaginationParams, ...ConnectionParam }, + async ({ page, limit, connection }) => formatResponse(await clientFor(connection).get('/admin/users', pageQuery({ page, limit }))), +); + +server.tool( + 'gitea_admin_cron_list', + 'List cron tasks and their last run time (admin only)', + { ...ConnectionParam }, + async ({ connection }) => formatResponse(await clientFor(connection).get('/admin/cron')), +); + +server.tool( + 'gitea_admin_cron_run', + 'Trigger a cron task (admin only)', + { + task: z.string().describe('Cron task name (e.g. "repo_health_check", "resync_all_hooks", "repo_archive_cleanup")'), + ...ConnectionParam, + }, + async ({ task, connection }) => formatResponse(await clientFor(connection).post(`/admin/cron/${task}`, {})), +); + // ── Generic API Call ──────────────────────────────────────────────────── server.tool(