Initial commit

This commit is contained in:
Steve White 2025-01-06 16:33:00 -06:00
commit 60014c1971
7 changed files with 917 additions and 0 deletions

1
.clinerules Normal file
View File

@ -0,0 +1 @@
- Use descriptive names for MCP functions. e.g. "add_file_to_repo" instead of "create_file".

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
node_modules/
build/
.env
*.log

70
README.md Normal file
View File

@ -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.

288
package-lock.json generated Normal file
View File

@ -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"
}
}
}
}

27
package.json Normal file
View File

@ -0,0 +1,27 @@
{
"name": "gitea-server",
"version": "0.1.0",
"description": "&#34;An MCP server providing access to gitea.r8z.us&#34;",
"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"
}
}

512
src/index.ts Normal file
View File

@ -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);

15
tsconfig.json Normal file
View File

@ -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"]
}