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