Initial commit
This commit is contained in:
commit
60014c1971
|
@ -0,0 +1 @@
|
|||
- Use descriptive names for MCP functions. e.g. "add_file_to_repo" instead of "create_file".
|
|
@ -0,0 +1,4 @@
|
|||
node_modules/
|
||||
build/
|
||||
.env
|
||||
*.log
|
|
@ -0,0 +1,70 @@
|
|||
# gitea-server MCP Server
|
||||
|
||||
"An MCP server providing access to gitea.r8z.us"
|
||||
|
||||
This is a TypeScript-based MCP server that implements a simple notes system. It demonstrates core MCP concepts by providing:
|
||||
|
||||
- Resources representing text notes with URIs and metadata
|
||||
- Tools for creating new notes
|
||||
- Prompts for generating summaries of notes
|
||||
|
||||
## Features
|
||||
|
||||
### Resources
|
||||
- List and access notes via `note://` URIs
|
||||
- Each note has a title, content and metadata
|
||||
- Plain text mime type for simple content access
|
||||
|
||||
### Tools
|
||||
- `create_note` - Create new text notes
|
||||
- Takes title and content as required parameters
|
||||
- Stores note in server state
|
||||
|
||||
### Prompts
|
||||
- `summarize_notes` - Generate a summary of all stored notes
|
||||
- Includes all note contents as embedded resources
|
||||
- Returns structured prompt for LLM summarization
|
||||
|
||||
## Development
|
||||
|
||||
Install dependencies:
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
Build the server:
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
For development with auto-rebuild:
|
||||
```bash
|
||||
npm run watch
|
||||
```
|
||||
|
||||
## Installation
|
||||
|
||||
To use with Claude Desktop, add the server config:
|
||||
|
||||
On MacOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
|
||||
On Windows: `%APPDATA%/Claude/claude_desktop_config.json`
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"gitea-server": {
|
||||
"command": "/path/to/gitea-server/build/index.js"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Debugging
|
||||
|
||||
Since MCP servers communicate over stdio, debugging can be challenging. We recommend using the [MCP Inspector](https://github.com/modelcontextprotocol/inspector), which is available as a package script:
|
||||
|
||||
```bash
|
||||
npm run inspector
|
||||
```
|
||||
|
||||
The Inspector will provide a URL to access debugging tools in your browser.
|
|
@ -0,0 +1,288 @@
|
|||
{
|
||||
"name": "gitea-server",
|
||||
"version": "0.1.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "gitea-server",
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/sdk": "0.6.0",
|
||||
"axios": "^1.7.9"
|
||||
},
|
||||
"bin": {
|
||||
"gitea-server": "build/index.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.11.24",
|
||||
"typescript": "^5.3.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@modelcontextprotocol/sdk": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-0.6.0.tgz",
|
||||
"integrity": "sha512-9rsDudGhDtMbvxohPoMMyAUOmEzQsOK+XFchh6gZGqo8sx9sBuZQs+CUttXqa8RZXKDaJRCN2tUtgGof7jRkkw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"content-type": "^1.0.5",
|
||||
"raw-body": "^3.0.0",
|
||||
"zod": "^3.23.8"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "20.17.12",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.12.tgz",
|
||||
"integrity": "sha512-vo/wmBgMIiEA23A/knMfn/cf37VnuF52nZh5ZoW0GWt4e4sxNquibrMRJ7UQsA06+MBx9r/H1jsI9grYjQCQlw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": "~6.19.2"
|
||||
}
|
||||
},
|
||||
"node_modules/asynckit": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "1.7.9",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz",
|
||||
"integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.6",
|
||||
"form-data": "^4.0.0",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/bytes": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
|
||||
"integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/combined-stream": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"delayed-stream": "~1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/content-type": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
|
||||
"integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/delayed-stream": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/depd": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
|
||||
"integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.15.9",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
|
||||
"integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/RubenVerborgh"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"debug": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/form-data": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz",
|
||||
"integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"mime-types": "^2.1.12"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/http-errors": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
|
||||
"integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"depd": "2.0.0",
|
||||
"inherits": "2.0.4",
|
||||
"setprototypeof": "1.2.0",
|
||||
"statuses": "2.0.1",
|
||||
"toidentifier": "1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/iconv-lite": {
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
|
||||
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/inherits": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/mime-db": {
|
||||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-types": {
|
||||
"version": "2.1.35",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mime-db": "1.52.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/proxy-from-env": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/raw-body": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz",
|
||||
"integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"bytes": "3.1.2",
|
||||
"http-errors": "2.0.0",
|
||||
"iconv-lite": "0.6.3",
|
||||
"unpipe": "1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/safer-buffer": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/setprototypeof": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
|
||||
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/statuses": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
|
||||
"integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/toidentifier": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
|
||||
"integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "5.7.2",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.2.tgz",
|
||||
"integrity": "sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "6.19.8",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
|
||||
"integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/unpipe": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
|
||||
"integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/zod": {
|
||||
"version": "3.24.1",
|
||||
"resolved": "https://registry.npmjs.org/zod/-/zod-3.24.1.tgz",
|
||||
"integrity": "sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/colinhacks"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
{
|
||||
"name": "gitea-server",
|
||||
"version": "0.1.0",
|
||||
"description": ""An MCP server providing access to gitea.r8z.us"",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"bin": {
|
||||
"gitea-server": "./build/index.js"
|
||||
},
|
||||
"files": [
|
||||
"build"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "tsc && node -e \"require('fs').chmodSync('build/index.js', '755')\"",
|
||||
"prepare": "npm run build",
|
||||
"watch": "tsc --watch",
|
||||
"inspector": "npx @modelcontextprotocol/inspector build/index.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/sdk": "0.6.0",
|
||||
"axios": "^1.7.9"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.11.24",
|
||||
"typescript": "^5.3.3"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,512 @@
|
|||
#!/usr/bin/env node
|
||||
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
||||
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
||||
import {
|
||||
CallToolRequestSchema,
|
||||
ErrorCode,
|
||||
ListToolsRequestSchema,
|
||||
McpError,
|
||||
} from '@modelcontextprotocol/sdk/types.js';
|
||||
import axios, { AxiosInstance } from 'axios';
|
||||
|
||||
interface RepoArgs {
|
||||
owner: string;
|
||||
repo: string;
|
||||
}
|
||||
|
||||
interface CreateRepoArgs {
|
||||
name: string;
|
||||
description?: string;
|
||||
private?: boolean;
|
||||
autoInit?: boolean;
|
||||
}
|
||||
|
||||
interface CreateFileArgs extends RepoArgs {
|
||||
path: string;
|
||||
content?: string;
|
||||
file_text?: string; // Alternative parameter name for content
|
||||
message: string;
|
||||
branch?: string;
|
||||
}
|
||||
|
||||
interface GetContentsArgs extends RepoArgs {
|
||||
path: string;
|
||||
ref?: string;
|
||||
}
|
||||
|
||||
interface CreateBranchArgs extends RepoArgs {
|
||||
branch: string;
|
||||
from?: string;
|
||||
}
|
||||
|
||||
interface IssueArgs extends RepoArgs {
|
||||
state?: 'open' | 'closed';
|
||||
}
|
||||
|
||||
interface CreateIssueArgs extends RepoArgs {
|
||||
title: string;
|
||||
body: string;
|
||||
}
|
||||
|
||||
interface PullRequestArgs extends RepoArgs {
|
||||
state?: 'open' | 'closed' | 'all';
|
||||
}
|
||||
|
||||
const GITEA_TOKEN = process.env.GITEA_TOKEN;
|
||||
if (!GITEA_TOKEN) {
|
||||
throw new Error('GITEA_TOKEN environment variable is required');
|
||||
}
|
||||
const GITEA_API_URL = 'https://gitea.r8z.us/api/v1';
|
||||
|
||||
class GiteaServer {
|
||||
private server: Server;
|
||||
private api: AxiosInstance;
|
||||
|
||||
constructor() {
|
||||
this.server = new Server(
|
||||
{
|
||||
name: 'gitea-server',
|
||||
version: '0.1.0',
|
||||
},
|
||||
{
|
||||
capabilities: {
|
||||
tools: {},
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
this.api = axios.create({
|
||||
baseURL: GITEA_API_URL,
|
||||
headers: {
|
||||
'Authorization': `token ${GITEA_TOKEN}`,
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
this.setupToolHandlers();
|
||||
|
||||
this.server.onerror = (error) => console.error('[MCP Error]', error);
|
||||
process.on('SIGINT', async () => {
|
||||
await this.server.close();
|
||||
process.exit(0);
|
||||
});
|
||||
}
|
||||
|
||||
private setupToolHandlers() {
|
||||
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
||||
tools: [
|
||||
{
|
||||
name: 'list_repositories',
|
||||
description: 'List repositories for the authenticated user',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {},
|
||||
required: [],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'get_repository',
|
||||
description: 'Get details about a specific repository',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
owner: {
|
||||
type: 'string',
|
||||
description: 'Repository owner',
|
||||
},
|
||||
repo: {
|
||||
type: 'string',
|
||||
description: 'Repository name',
|
||||
},
|
||||
},
|
||||
required: ['owner', 'repo'],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'list_issues',
|
||||
description: 'List issues in a repository',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
owner: {
|
||||
type: 'string',
|
||||
description: 'Repository owner',
|
||||
},
|
||||
repo: {
|
||||
type: 'string',
|
||||
description: 'Repository name',
|
||||
},
|
||||
state: {
|
||||
type: 'string',
|
||||
description: 'Issue state (open/closed)',
|
||||
enum: ['open', 'closed'],
|
||||
},
|
||||
},
|
||||
required: ['owner', 'repo'],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'create_issue',
|
||||
description: 'Create a new issue in a repository',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
owner: {
|
||||
type: 'string',
|
||||
description: 'Repository owner',
|
||||
},
|
||||
repo: {
|
||||
type: 'string',
|
||||
description: 'Repository name',
|
||||
},
|
||||
title: {
|
||||
type: 'string',
|
||||
description: 'Issue title',
|
||||
},
|
||||
body: {
|
||||
type: 'string',
|
||||
description: 'Issue body/description',
|
||||
},
|
||||
},
|
||||
required: ['owner', 'repo', 'title', 'body'],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'list_pull_requests',
|
||||
description: 'List pull requests in a repository',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
owner: {
|
||||
type: 'string',
|
||||
description: 'Repository owner',
|
||||
},
|
||||
repo: {
|
||||
type: 'string',
|
||||
description: 'Repository name',
|
||||
},
|
||||
state: {
|
||||
type: 'string',
|
||||
description: 'PR state (open/closed/all)',
|
||||
enum: ['open', 'closed', 'all'],
|
||||
},
|
||||
},
|
||||
required: ['owner', 'repo'],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'create_repository',
|
||||
description: 'Create a new repository',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: {
|
||||
type: 'string',
|
||||
description: 'Repository name',
|
||||
},
|
||||
description: {
|
||||
type: 'string',
|
||||
description: 'Repository description',
|
||||
},
|
||||
private: {
|
||||
type: 'boolean',
|
||||
description: 'Whether the repository is private',
|
||||
},
|
||||
autoInit: {
|
||||
type: 'boolean',
|
||||
description: 'Whether to initialize with README',
|
||||
},
|
||||
},
|
||||
required: ['name'],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'get_contents',
|
||||
description: 'Get contents of a file or directory in a repository',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
owner: {
|
||||
type: 'string',
|
||||
description: 'Repository owner',
|
||||
},
|
||||
repo: {
|
||||
type: 'string',
|
||||
description: 'Repository name',
|
||||
},
|
||||
path: {
|
||||
type: 'string',
|
||||
description: 'Path to file or directory',
|
||||
},
|
||||
ref: {
|
||||
type: 'string',
|
||||
description: 'Git reference (branch/tag/commit)',
|
||||
},
|
||||
},
|
||||
required: ['owner', 'repo', 'path'],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'add_file_to_repo',
|
||||
description: 'Add a new file to a repository (supports both "content" and "file_text" parameters)',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
owner: {
|
||||
type: 'string',
|
||||
description: 'Repository owner',
|
||||
},
|
||||
repo: {
|
||||
type: 'string',
|
||||
description: 'Repository name',
|
||||
},
|
||||
path: {
|
||||
type: 'string',
|
||||
description: 'Path to file',
|
||||
},
|
||||
content: {
|
||||
type: 'string',
|
||||
description: 'File content (will be base64 encoded). Either content or file_text must be provided.',
|
||||
},
|
||||
file_text: {
|
||||
type: 'string',
|
||||
description: 'Alternative parameter for file content. Either content or file_text must be provided.',
|
||||
},
|
||||
message: {
|
||||
type: 'string',
|
||||
description: 'Commit message',
|
||||
},
|
||||
branch: {
|
||||
type: 'string',
|
||||
description: 'Branch name',
|
||||
},
|
||||
},
|
||||
required: ['owner', 'repo', 'path', 'message'],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'create_branch',
|
||||
description: 'Create a new branch in a repository',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
owner: {
|
||||
type: 'string',
|
||||
description: 'Repository owner',
|
||||
},
|
||||
repo: {
|
||||
type: 'string',
|
||||
description: 'Repository name',
|
||||
},
|
||||
branch: {
|
||||
type: 'string',
|
||||
description: 'New branch name',
|
||||
},
|
||||
from: {
|
||||
type: 'string',
|
||||
description: 'Base branch/commit to create from (defaults to default branch)',
|
||||
},
|
||||
},
|
||||
required: ['owner', 'repo', 'branch'],
|
||||
},
|
||||
},
|
||||
],
|
||||
}));
|
||||
|
||||
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
||||
try {
|
||||
switch (request.params.name) {
|
||||
case 'list_repositories': {
|
||||
const response = await this.api.get('/user/repos');
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: JSON.stringify(response.data, null, 2),
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
case 'get_repository': {
|
||||
const { owner, repo } = request.params.arguments as unknown as RepoArgs;
|
||||
const response = await this.api.get(`/repos/${owner}/${repo}`);
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: JSON.stringify(response.data, null, 2),
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
case 'list_issues': {
|
||||
const { owner, repo, state } = request.params.arguments as unknown as IssueArgs;
|
||||
const response = await this.api.get(`/repos/${owner}/${repo}/issues`, {
|
||||
params: { state },
|
||||
});
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: JSON.stringify(response.data, null, 2),
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
case 'create_issue': {
|
||||
const { owner, repo, title, body } = request.params.arguments as unknown as CreateIssueArgs;
|
||||
const response = await this.api.post(`/repos/${owner}/${repo}/issues`, {
|
||||
title,
|
||||
body,
|
||||
});
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: JSON.stringify(response.data, null, 2),
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
case 'list_pull_requests': {
|
||||
const { owner, repo, state } = request.params.arguments as unknown as PullRequestArgs;
|
||||
const response = await this.api.get(`/repos/${owner}/${repo}/pulls`, {
|
||||
params: { state },
|
||||
});
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: JSON.stringify(response.data, null, 2),
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
case 'create_repository': {
|
||||
const { name, description, private: isPrivate, autoInit } = request.params.arguments as unknown as CreateRepoArgs;
|
||||
const response = await this.api.post('/user/repos', {
|
||||
name,
|
||||
description,
|
||||
private: isPrivate,
|
||||
auto_init: autoInit,
|
||||
});
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: JSON.stringify(response.data, null, 2),
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
case 'get_contents': {
|
||||
const { owner, repo, path, ref } = request.params.arguments as unknown as GetContentsArgs;
|
||||
const response = await this.api.get(`/repos/${owner}/${repo}/contents/${path}`, {
|
||||
params: { ref },
|
||||
});
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: JSON.stringify(response.data, null, 2),
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
case 'add_file_to_repo': {
|
||||
const args = request.params.arguments as unknown as CreateFileArgs;
|
||||
// Support both 'content' and 'file_text' for backward compatibility
|
||||
const fileContent = args.content || (args as any).file_text;
|
||||
if (!fileContent) {
|
||||
throw new McpError(
|
||||
ErrorCode.InvalidParams,
|
||||
'Either "content" or "file_text" parameter is required'
|
||||
);
|
||||
}
|
||||
const response = await this.api.post(`/repos/${args.owner}/${args.repo}/contents/${args.path}`, {
|
||||
content: Buffer.from(fileContent).toString('base64'),
|
||||
message: args.message,
|
||||
branch: args.branch,
|
||||
});
|
||||
|
||||
// Verify the file exists after creation
|
||||
const verifyResponse = await this.api.get(`/repos/${args.owner}/${args.repo}/contents/${args.path}`);
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: JSON.stringify({
|
||||
success: true,
|
||||
message: 'File created successfully',
|
||||
file: {
|
||||
path: args.path,
|
||||
url: response.data.content.html_url,
|
||||
commit: response.data.commit,
|
||||
},
|
||||
content: verifyResponse.data,
|
||||
}, null, 2),
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
case 'create_branch': {
|
||||
const { owner, repo, branch, from } = request.params.arguments as unknown as CreateBranchArgs;
|
||||
|
||||
// Get the default branch if 'from' is not specified
|
||||
const sourceBranch = from || (await this.api.get(`/repos/${owner}/${repo}`)).data.default_branch;
|
||||
|
||||
// Get the commit SHA from the source branch
|
||||
const branchResponse = await this.api.get(`/repos/${owner}/${repo}/branches/${sourceBranch}`);
|
||||
const sha = branchResponse.data.commit.id;
|
||||
|
||||
// Create the new branch
|
||||
const response = await this.api.post(`/repos/${owner}/${repo}/branches`, {
|
||||
new_branch_name: branch,
|
||||
old_branch_name: sourceBranch
|
||||
});
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: JSON.stringify(response.data, null, 2),
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
default:
|
||||
throw new McpError(
|
||||
ErrorCode.MethodNotFound,
|
||||
`Unknown tool: ${request.params.name}`
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
if (axios.isAxiosError(error)) {
|
||||
throw new McpError(
|
||||
ErrorCode.InternalError,
|
||||
`Gitea API error: ${error.response?.data?.message ?? error.message}`
|
||||
);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async run() {
|
||||
const transport = new StdioServerTransport();
|
||||
await this.server.connect(transport);
|
||||
console.error('Gitea MCP server running on stdio');
|
||||
}
|
||||
}
|
||||
|
||||
const server = new GiteaServer();
|
||||
server.run().catch(console.error);
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "Node16",
|
||||
"moduleResolution": "Node16",
|
||||
"outDir": "./build",
|
||||
"rootDir": "./src",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
Loading…
Reference in New Issue