Bläddra i källkod

Implement MCP server for Gogs git provider

Add complete Model Context Protocol server implementation with:
- TypeScript-based MCP server using official SDK
- Full Gogs REST API v1 client with type safety
- 13 MCP tools for user, repository, and content operations
- Comprehensive documentation and usage examples
- Environment-based configuration for server URL and authentication

Tools included:
- User management: get current user, get user, search users
- Repository operations: list, search, get, create, delete
- Content access: get contents, raw content, branches, commits

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Claude (AgentBox) 9 månader sedan
förälder
incheckning
94b9ca3ebf
11 ändrade filer med 1403 tillägg och 1 borttagningar
  1. 3 0
      .env.example
  2. 15 0
      .gitignore
  3. 3 0
      .gitmodules
  4. 252 1
      README.md
  5. 223 0
      USAGE_EXAMPLES.md
  6. 1 0
      docs-api
  7. 34 0
      package.json
  8. 179 0
      src/gogs-client.ts
  9. 589 0
      src/index.ts
  10. 84 0
      src/types.ts
  11. 20 0
      tsconfig.json

+ 3 - 0
.env.example

@@ -0,0 +1,3 @@
+# Gogs Server Configuration
+GOGS_SERVER_URL=https://your-gogs-server.com
+GOGS_ACCESS_TOKEN=your-access-token-here

+ 15 - 0
.gitignore

@@ -28,3 +28,18 @@ build/Release
 # https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git
 node_modules
 
+# TypeScript build output
+dist/
+*.tsbuildinfo
+
+# Environment variables
+.env
+.env.local
+
+# IDE
+.vscode/
+.idea/
+*.swp
+*.swo
+*~
+

+ 3 - 0
.gitmodules

@@ -0,0 +1,3 @@
+[submodule "docs-api"]
+	path = docs-api
+	url = https://github.com/gogs/docs-api.git

+ 252 - 1
README.md

@@ -1,2 +1,253 @@
-# gogs-mcp
+# Gogs MCP Server
+
+A Model Context Protocol (MCP) server that provides access to Gogs git hosting APIs. This server enables AI assistants like Claude to interact with Gogs repositories, users, and other resources.
+
+## Features
+
+This MCP server provides tools to:
+
+- **User Management**
+  - Get authenticated user information
+  - Get specific user details
+  - Search for users
+
+- **Repository Management**
+  - List user repositories
+  - Search repositories
+  - Get repository information
+  - Create new repositories
+  - Delete repositories
+
+- **Content Access**
+  - Get file and directory contents
+  - Get raw file content
+  - List repository branches
+  - Get commit history
+
+## Installation
+
+### Prerequisites
+
+- Node.js 18 or higher
+- npm or yarn
+- A Gogs server instance
+- Gogs access token (optional, but recommended for authenticated access)
+
+### Setup
+
+1. Clone this repository:
+   ```bash
+   git clone <repository-url>
+   cd gogs-mcp-server
+   ```
+
+2. Install dependencies:
+   ```bash
+   npm install
+   ```
+
+3. Build the project:
+   ```bash
+   npm run build
+   ```
+
+## Configuration
+
+### Generate Gogs Access Token
+
+1. Log in to your Gogs instance
+2. Navigate to Settings → Applications
+3. Create a new access token with appropriate permissions
+4. Copy the token for use in configuration
+
+### Environment Variables
+
+Create a `.env` file or set environment variables:
+
+```bash
+GOGS_SERVER_URL=https://your-gogs-server.com
+GOGS_ACCESS_TOKEN=your-access-token-here
+```
+
+The access token is optional but recommended. Without it:
+- User email fields may be empty (anti-spam measure)
+- Some operations requiring authentication will fail
+- Only public repositories will be accessible
+
+## Usage
+
+### With Claude Desktop
+
+Add the server to your Claude Desktop configuration file:
+
+**macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
+**Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
+
+```json
+{
+  "mcpServers": {
+    "gogs": {
+      "command": "node",
+      "args": ["/absolute/path/to/gogs-mcp-server/dist/index.js"],
+      "env": {
+        "GOGS_SERVER_URL": "https://your-gogs-server.com",
+        "GOGS_ACCESS_TOKEN": "your-access-token-here"
+      }
+    }
+  }
+}
+```
+
+### With Other MCP Clients
+
+The server uses stdio transport and can be integrated with any MCP-compatible client:
+
+```bash
+# Set environment variables
+export GOGS_SERVER_URL=https://your-gogs-server.com
+export GOGS_ACCESS_TOKEN=your-access-token-here
+
+# Run the server
+npm start
+```
+
+## Available Tools
+
+### User Tools
+
+#### `get_current_user`
+Get information about the authenticated user.
+
+#### `get_user`
+Get information about a specific user.
+- `username` (string, required): Username to look up
+
+#### `search_users`
+Search for users by username.
+- `query` (string, required): Search query
+- `limit` (number, optional): Maximum results (default: 10)
+
+### Repository Tools
+
+#### `list_user_repositories`
+List repositories for a user.
+- `username` (string, optional): Username (omit for authenticated user)
+
+#### `search_repositories`
+Search for repositories.
+- `query` (string, required): Search query
+- `uid` (number, optional): User ID to filter by
+- `limit` (number, optional): Maximum results (default: 10)
+- `page` (number, optional): Page number (default: 1)
+
+#### `get_repository`
+Get information about a specific repository.
+- `owner` (string, required): Repository owner username
+- `repo` (string, required): Repository name
+
+#### `create_repository`
+Create a new repository.
+- `name` (string, required): Repository name
+- `description` (string, optional): Repository description
+- `private` (boolean, optional): Create as private (default: false)
+- `auto_init` (boolean, optional): Initialize with README (default: false)
+- `gitignores` (string, optional): Gitignore templates (e.g., "Go,VisualStudioCode")
+- `license` (string, optional): License template (e.g., "MIT License")
+- `readme` (string, optional): README template (default: "Default")
+
+#### `delete_repository`
+Delete a repository (requires admin access).
+- `owner` (string, required): Repository owner username
+- `repo` (string, required): Repository name
+
+### Content Tools
+
+#### `get_contents`
+Get file or directory contents from a repository.
+- `owner` (string, required): Repository owner username
+- `repo` (string, required): Repository name
+- `path` (string, required): File or directory path
+- `ref` (string, optional): Branch, tag, or commit SHA (default: default branch)
+
+#### `get_raw_content`
+Get raw file content from a repository.
+- `owner` (string, required): Repository owner username
+- `repo` (string, required): Repository name
+- `ref` (string, required): Branch, tag, or commit SHA
+- `path` (string, required): File path
+
+#### `list_branches`
+List all branches in a repository.
+- `owner` (string, required): Repository owner username
+- `repo` (string, required): Repository name
+
+#### `get_commits`
+Get commit history from a repository.
+- `owner` (string, required): Repository owner username
+- `repo` (string, required): Repository name
+- `sha` (string, optional): Branch name or commit SHA
+- `page` (number, optional): Page number (default: 1)
+
+## Development
+
+### Run in Development Mode
+
+```bash
+npm run dev
+```
+
+### Build
+
+```bash
+npm run build
+```
+
+### Watch Mode
+
+```bash
+npm run watch
+```
+
+## API Documentation
+
+The Gogs API documentation is included in the `docs-api` submodule. For more information about the Gogs REST API, see:
+- [Gogs API Documentation](https://github.com/gogs/docs-api)
+- Local docs: `docs-api/README.md`
+
+## Security Considerations
+
+- **Access Tokens**: Keep your access tokens secure. Never commit them to version control.
+- **Permissions**: The access token's permissions determine what operations are available.
+- **Private Repositories**: Access to private repositories requires authentication.
+- **Rate Limiting**: Be aware of any rate limiting imposed by your Gogs server.
+
+## Troubleshooting
+
+### "GOGS_SERVER_URL environment variable is required"
+Make sure you've set the `GOGS_SERVER_URL` environment variable or included it in your MCP client configuration.
+
+### "404 Not Found" errors
+This can indicate:
+- Invalid server URL
+- Repository or user doesn't exist
+- Authentication required but not provided
+- Insufficient permissions
+
+### "401 Unauthorized" errors
+- Check that your access token is correct and hasn't expired
+- Verify the token has appropriate permissions for the operation
+
+## License
+
+MIT
+
+## Contributing
+
+Contributions are welcome! Please feel free to submit a Pull Request.
+
+## Acknowledgments
+
+- Built with the [Model Context Protocol SDK](https://github.com/modelcontextprotocol)
+- Designed for [Gogs](https://gogs.io) git hosting
+- API documentation from [gogs/docs-api](https://github.com/gogs/docs-api)
 

+ 223 - 0
USAGE_EXAMPLES.md

@@ -0,0 +1,223 @@
+# Gogs MCP Server Usage Examples
+
+This document provides practical examples of using the Gogs MCP Server with AI assistants like Claude.
+
+## Setup Example
+
+Once configured in Claude Desktop, you can interact naturally with your Gogs server:
+
+```json
+{
+  "mcpServers": {
+    "gogs": {
+      "command": "node",
+      "args": ["/path/to/gogs-mcp-server/dist/index.js"],
+      "env": {
+        "GOGS_SERVER_URL": "https://gogs.example.com",
+        "GOGS_ACCESS_TOKEN": "abc123..."
+      }
+    }
+  }
+}
+```
+
+## Example Conversations
+
+### Exploring Repositories
+
+**You**: "List all my repositories on Gogs"
+
+**Claude** will use the `list_user_repositories` tool to fetch your repositories and present them in a readable format.
+
+---
+
+**You**: "Search for repositories containing 'docker' in their name"
+
+**Claude** will use the `search_repositories` tool with query "docker" and show results.
+
+---
+
+**You**: "Show me information about the repository user/my-project"
+
+**Claude** will use the `get_repository` tool to fetch and display repository details.
+
+### Reading Code
+
+**You**: "Show me the contents of README.md from owner/repo"
+
+**Claude** will use the `get_contents` tool to fetch and display the file contents.
+
+---
+
+**You**: "Get the raw content of src/main.go from the develop branch of owner/repo"
+
+**Claude** will use the `get_raw_content` tool with the specified parameters.
+
+---
+
+**You**: "What files are in the src/ directory of owner/repo?"
+
+**Claude** will use the `get_contents` tool to list directory contents.
+
+### Repository Management
+
+**You**: "Create a new repository called 'test-project' with description 'My test project'"
+
+**Claude** will use the `create_repository` tool to create the repository.
+
+---
+
+**You**: "Create a private Go repository with MIT license and appropriate gitignore"
+
+**Claude** will use the `create_repository` tool with:
+- `private: true`
+- `license: "MIT License"`
+- `gitignores: "Go"`
+- `auto_init: true`
+
+### Branch and Commit History
+
+**You**: "List all branches in owner/repo"
+
+**Claude** will use the `list_branches` tool to fetch branch information.
+
+---
+
+**You**: "Show me the recent commits from the main branch of owner/repo"
+
+**Claude** will use the `get_commits` tool to fetch commit history.
+
+---
+
+**You**: "What are the latest 5 commits on the develop branch?"
+
+**Claude** will use the `get_commits` tool with `sha: "develop"` to get recent commits.
+
+### User Management
+
+**You**: "Search for users named 'john'"
+
+**Claude** will use the `search_users` tool with query "john".
+
+---
+
+**You**: "Get information about user 'johndoe'"
+
+**Claude** will use the `get_user` tool to fetch user details.
+
+---
+
+**You**: "Who am I logged in as?"
+
+**Claude** will use the `get_current_user` tool to show your authenticated user info.
+
+## Complex Workflows
+
+### Code Review Workflow
+
+**You**: "Review the latest changes in owner/repo on the feature-branch branch"
+
+**Claude** will:
+1. Use `get_commits` to fetch recent commits from feature-branch
+2. Use `get_contents` to read modified files
+3. Provide analysis and feedback on the code
+
+### Repository Analysis
+
+**You**: "Analyze the structure of owner/repo and tell me what kind of project it is"
+
+**Claude** will:
+1. Use `get_repository` to get basic info
+2. Use `get_contents` to explore root directory
+3. Use `get_contents` or `get_raw_content` to read key files (package.json, go.mod, etc.)
+4. Analyze and describe the project structure and type
+
+### Migration Planning
+
+**You**: "I want to migrate my project from GitHub to Gogs. Can you help me create the repository?"
+
+**Claude** will:
+1. Ask for necessary details (repository name, description, etc.)
+2. Use `create_repository` with appropriate parameters
+3. Provide instructions for pushing code to the new repository
+
+## Tips for Effective Use
+
+### Be Specific
+Instead of: "Look at that file"
+Use: "Show me the contents of src/main.go from the master branch of owner/repo"
+
+### Use Natural Language
+The MCP server allows natural interaction. You can say:
+- "Find repositories about kubernetes"
+- "What's in the config directory?"
+- "Create a new private Python project"
+
+### Combine Operations
+You can ask Claude to perform multiple operations:
+- "List all branches in owner/repo and show me the latest commit on each"
+- "Search for all repositories by user 'alice' and tell me which ones are private"
+
+### Context Awareness
+Claude can maintain context across multiple queries:
+- "List my repositories" → "Tell me more about the second one" → "Show me its README"
+
+## Error Handling
+
+If you encounter errors, Claude will explain them:
+
+- **404 Not Found**: Repository or user doesn't exist, or you don't have access
+- **401 Unauthorized**: Authentication required or token is invalid
+- **Environment variable not set**: Configuration issue with the MCP server
+
+## Performance Considerations
+
+- Large file contents are automatically decoded and presented
+- Directory listings show all contents at once
+- Commit history is paginated (use the `page` parameter for more commits)
+- Search results are limited by default (use `limit` parameter to adjust)
+
+## Security Notes
+
+When using the MCP server:
+- Your access token provides the same permissions as using the Gogs web interface
+- All operations are logged on the Gogs server
+- Be cautious with delete operations - they cannot be undone
+- Private repository access requires authentication
+
+## Advanced Examples
+
+### Finding a Specific Function
+
+**You**: "Search through the codebase of owner/repo for the function 'handleRequest'"
+
+**Claude** will:
+1. Use `get_contents` to explore directories
+2. Use `get_raw_content` to read likely files
+3. Search for the function and show its location and implementation
+
+### Comparing Branches
+
+**You**: "What are the differences between the main and develop branches in owner/repo?"
+
+**Claude** will:
+1. Use `get_commits` on both branches
+2. Identify differing commits
+3. Summarize the differences
+
+### Repository Setup Automation
+
+**You**: "Create a new Node.js repository with TypeScript setup, MIT license, and Node.js gitignore"
+
+**Claude** will use `create_repository` with:
+```
+{
+  "name": "<project-name>",
+  "description": "TypeScript Node.js project",
+  "auto_init": true,
+  "gitignores": "Node",
+  "license": "MIT License"
+}
+```
+
+Then provide instructions for setting up TypeScript configuration locally.

+ 1 - 0
docs-api

@@ -0,0 +1 @@
+Subproject commit 9740f402bd1e28892fe544eaac83ee016a7202df

+ 34 - 0
package.json

@@ -0,0 +1,34 @@
+{
+  "name": "gogs-mcp-server",
+  "version": "1.0.0",
+  "description": "MCP server for Gogs git provider",
+  "type": "module",
+  "main": "dist/index.js",
+  "bin": {
+    "gogs-mcp-server": "dist/index.js"
+  },
+  "scripts": {
+    "build": "tsc",
+    "watch": "tsc --watch",
+    "dev": "tsx src/index.ts",
+    "start": "node dist/index.js"
+  },
+  "keywords": [
+    "mcp",
+    "gogs",
+    "git",
+    "model-context-protocol"
+  ],
+  "author": "",
+  "license": "MIT",
+  "dependencies": {
+    "@modelcontextprotocol/sdk": "^1.0.4",
+    "axios": "^1.7.9",
+    "zod": "^3.24.1"
+  },
+  "devDependencies": {
+    "@types/node": "^22.10.5",
+    "tsx": "^4.19.2",
+    "typescript": "^5.7.2"
+  }
+}

+ 179 - 0
src/gogs-client.ts

@@ -0,0 +1,179 @@
+/**
+ * Gogs API Client
+ * Provides methods to interact with a Gogs server REST API
+ */
+
+import axios, { AxiosInstance } from 'axios';
+import type {
+  GogsUser,
+  GogsRepository,
+  GogsSearchResponse,
+  GogsFileContent,
+  GogsBranch,
+  GogsCommit,
+  GogsConfig,
+} from './types.js';
+
+export class GogsClient {
+  private client: AxiosInstance;
+  private serverUrl: string;
+
+  constructor(config: GogsConfig) {
+    this.serverUrl = config.serverUrl.replace(/\/$/, '');
+
+    this.client = axios.create({
+      baseURL: `${this.serverUrl}/api/v1`,
+      headers: {
+        'Content-Type': 'application/json',
+        ...(config.accessToken && { Authorization: `token ${config.accessToken}` }),
+      },
+    });
+  }
+
+  /**
+   * Get information about the authenticated user
+   */
+  async getCurrentUser(): Promise<GogsUser> {
+    const response = await this.client.get<GogsUser>('/user');
+    return response.data;
+  }
+
+  /**
+   * Get information about a specific user
+   */
+  async getUser(username: string): Promise<GogsUser> {
+    const response = await this.client.get<GogsUser>(`/users/${username}`);
+    return response.data;
+  }
+
+  /**
+   * Search for users
+   */
+  async searchUsers(query: string, limit: number = 10): Promise<GogsUser[]> {
+    const response = await this.client.get<GogsSearchResponse<GogsUser>>('/users/search', {
+      params: { q: query, limit },
+    });
+    return response.data.data;
+  }
+
+  /**
+   * List repositories for the authenticated user
+   */
+  async listMyRepositories(): Promise<GogsRepository[]> {
+    const response = await this.client.get<GogsRepository[]>('/user/repos');
+    return response.data;
+  }
+
+  /**
+   * List repositories for a specific user
+   */
+  async listUserRepositories(username: string): Promise<GogsRepository[]> {
+    const response = await this.client.get<GogsRepository[]>(`/users/${username}/repos`);
+    return response.data;
+  }
+
+  /**
+   * Search for repositories
+   */
+  async searchRepositories(
+    query: string,
+    options?: { uid?: number; limit?: number; page?: number }
+  ): Promise<GogsRepository[]> {
+    const response = await this.client.get<GogsSearchResponse<GogsRepository>>('/repos/search', {
+      params: {
+        q: query,
+        uid: options?.uid || 0,
+        limit: options?.limit || 10,
+        page: options?.page || 1,
+      },
+    });
+    return response.data.data;
+  }
+
+  /**
+   * Get information about a specific repository
+   */
+  async getRepository(owner: string, repo: string): Promise<GogsRepository> {
+    const response = await this.client.get<GogsRepository>(`/repos/${owner}/${repo}`);
+    return response.data;
+  }
+
+  /**
+   * Create a new repository
+   */
+  async createRepository(data: {
+    name: string;
+    description?: string;
+    private?: boolean;
+    auto_init?: boolean;
+    gitignores?: string;
+    license?: string;
+    readme?: string;
+  }): Promise<GogsRepository> {
+    const response = await this.client.post<GogsRepository>('/user/repos', data);
+    return response.data;
+  }
+
+  /**
+   * Delete a repository
+   */
+  async deleteRepository(owner: string, repo: string): Promise<void> {
+    await this.client.delete(`/repos/${owner}/${repo}`);
+  }
+
+  /**
+   * Get file or directory contents
+   */
+  async getContents(
+    owner: string,
+    repo: string,
+    path: string,
+    ref?: string
+  ): Promise<GogsFileContent | GogsFileContent[]> {
+    const response = await this.client.get<GogsFileContent | GogsFileContent[]>(
+      `/repos/${owner}/${repo}/contents/${path}`,
+      {
+        params: ref ? { ref } : undefined,
+      }
+    );
+    return response.data;
+  }
+
+  /**
+   * Get raw file content
+   */
+  async getRawContent(owner: string, repo: string, ref: string, path: string): Promise<string> {
+    const response = await this.client.get<string>(
+      `/repos/${owner}/${repo}/raw/${ref}/${path}`,
+      {
+        responseType: 'text',
+      }
+    );
+    return response.data;
+  }
+
+  /**
+   * List branches in a repository
+   */
+  async listBranches(owner: string, repo: string): Promise<GogsBranch[]> {
+    const response = await this.client.get<GogsBranch[]>(`/repos/${owner}/${repo}/branches`);
+    return response.data;
+  }
+
+  /**
+   * Get commits from a repository
+   */
+  async getCommits(
+    owner: string,
+    repo: string,
+    options?: { sha?: string; page?: number }
+  ): Promise<GogsCommit[]> {
+    const response = await this.client.get<GogsCommit[]>(`/repos/${owner}/${repo}/commits`, {
+      params: {
+        sha: options?.sha,
+        page: options?.page || 1,
+      },
+    });
+    return response.data;
+  }
+}

+ 589 - 0
src/index.ts

@@ -0,0 +1,589 @@
+#!/usr/bin/env node
+
+/**
+ * Gogs MCP Server
+ * Provides Model Context Protocol server for Gogs git hosting
+ */
+
+import { Server } from '@modelcontextprotocol/sdk/server/index.js';
+import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
+import {
+  CallToolRequestSchema,
+  ListToolsRequestSchema,
+  ListResourcesRequestSchema,
+  ReadResourceRequestSchema,
+} from '@modelcontextprotocol/sdk/types.js';
+import { z } from 'zod';
+import { GogsClient } from './gogs-client.js';
+
+// Get configuration from environment variables
+const GOGS_SERVER_URL = process.env.GOGS_SERVER_URL;
+const GOGS_ACCESS_TOKEN = process.env.GOGS_ACCESS_TOKEN;
+
+if (!GOGS_SERVER_URL) {
+  console.error('Error: GOGS_SERVER_URL environment variable is required');
+  process.exit(1);
+}
+
+// Initialize Gogs client
+const gogsClient = new GogsClient({
+  serverUrl: GOGS_SERVER_URL,
+  accessToken: GOGS_ACCESS_TOKEN,
+});
+
+// Create MCP server
+const server = new Server(
+  {
+    name: 'gogs-mcp-server',
+    version: '1.0.0',
+  },
+  {
+    capabilities: {
+      tools: {},
+      resources: {},
+    },
+  }
+);
+
+// Define tool schemas
+const GetUserSchema = z.object({
+  username: z.string().describe('Username to get information for'),
+});
+
+const SearchUsersSchema = z.object({
+  query: z.string().describe('Search query for users'),
+  limit: z.number().optional().describe('Maximum number of results (default: 10)'),
+});
+
+const ListUserReposSchema = z.object({
+  username: z.string().optional().describe('Username (omit for authenticated user)'),
+});
+
+const SearchReposSchema = z.object({
+  query: z.string().describe('Search query for repositories'),
+  uid: z.number().optional().describe('User ID to filter repositories'),
+  limit: z.number().optional().describe('Maximum number of results (default: 10)'),
+  page: z.number().optional().describe('Page number (default: 1)'),
+});
+
+const GetRepoSchema = z.object({
+  owner: z.string().describe('Repository owner username'),
+  repo: z.string().describe('Repository name'),
+});
+
+const CreateRepoSchema = z.object({
+  name: z.string().describe('Repository name'),
+  description: z.string().optional().describe('Repository description'),
+  private: z.boolean().optional().describe('Create as private repository (default: false)'),
+  auto_init: z.boolean().optional().describe('Initialize with README (default: false)'),
+  gitignores: z.string().optional().describe('Gitignore templates (e.g., "Go,VisualStudioCode")'),
+  license: z.string().optional().describe('License template (e.g., "MIT License")'),
+  readme: z.string().optional().describe('README template (default: "Default")'),
+});
+
+const DeleteRepoSchema = z.object({
+  owner: z.string().describe('Repository owner username'),
+  repo: z.string().describe('Repository name'),
+});
+
+const GetContentsSchema = z.object({
+  owner: z.string().describe('Repository owner username'),
+  repo: z.string().describe('Repository name'),
+  path: z.string().describe('File or directory path'),
+  ref: z.string().optional().describe('Branch, tag, or commit SHA (default: default branch)'),
+});
+
+const GetRawContentSchema = z.object({
+  owner: z.string().describe('Repository owner username'),
+  repo: z.string().describe('Repository name'),
+  ref: z.string().describe('Branch, tag, or commit SHA'),
+  path: z.string().describe('File path'),
+});
+
+const ListBranchesSchema = z.object({
+  owner: z.string().describe('Repository owner username'),
+  repo: z.string().describe('Repository name'),
+});
+
+const GetCommitsSchema = z.object({
+  owner: z.string().describe('Repository owner username'),
+  repo: z.string().describe('Repository name'),
+  sha: z.string().optional().describe('Branch name or commit SHA'),
+  page: z.number().optional().describe('Page number (default: 1)'),
+});
+
+// Register tools
+server.setRequestHandler(ListToolsRequestSchema, async () => {
+  return {
+    tools: [
+      {
+        name: 'get_current_user',
+        description: 'Get information about the authenticated user',
+        inputSchema: {
+          type: 'object',
+          properties: {},
+        },
+      },
+      {
+        name: 'get_user',
+        description: 'Get information about a specific user',
+        inputSchema: {
+          type: 'object',
+          properties: {
+            username: {
+              type: 'string',
+              description: 'Username to get information for',
+            },
+          },
+          required: ['username'],
+        },
+      },
+      {
+        name: 'search_users',
+        description: 'Search for users by username',
+        inputSchema: {
+          type: 'object',
+          properties: {
+            query: {
+              type: 'string',
+              description: 'Search query for users',
+            },
+            limit: {
+              type: 'number',
+              description: 'Maximum number of results (default: 10)',
+            },
+          },
+          required: ['query'],
+        },
+      },
+      {
+        name: 'list_user_repositories',
+        description: 'List repositories for a user (or authenticated user if no username provided)',
+        inputSchema: {
+          type: 'object',
+          properties: {
+            username: {
+              type: 'string',
+              description: 'Username (omit for authenticated user)',
+            },
+          },
+        },
+      },
+      {
+        name: 'search_repositories',
+        description: 'Search for repositories',
+        inputSchema: {
+          type: 'object',
+          properties: {
+            query: {
+              type: 'string',
+              description: 'Search query for repositories',
+            },
+            uid: {
+              type: 'number',
+              description: 'User ID to filter repositories',
+            },
+            limit: {
+              type: 'number',
+              description: 'Maximum number of results (default: 10)',
+            },
+            page: {
+              type: 'number',
+              description: 'Page number (default: 1)',
+            },
+          },
+          required: ['query'],
+        },
+      },
+      {
+        name: 'get_repository',
+        description: 'Get information about a specific repository',
+        inputSchema: {
+          type: 'object',
+          properties: {
+            owner: {
+              type: 'string',
+              description: 'Repository owner username',
+            },
+            repo: {
+              type: 'string',
+              description: 'Repository name',
+            },
+          },
+          required: ['owner', 'repo'],
+        },
+      },
+      {
+        name: 'create_repository',
+        description: 'Create a new repository for the authenticated user',
+        inputSchema: {
+          type: 'object',
+          properties: {
+            name: {
+              type: 'string',
+              description: 'Repository name',
+            },
+            description: {
+              type: 'string',
+              description: 'Repository description',
+            },
+            private: {
+              type: 'boolean',
+              description: 'Create as private repository (default: false)',
+            },
+            auto_init: {
+              type: 'boolean',
+              description: 'Initialize with README (default: false)',
+            },
+            gitignores: {
+              type: 'string',
+              description: 'Gitignore templates (e.g., "Go,VisualStudioCode")',
+            },
+            license: {
+              type: 'string',
+              description: 'License template (e.g., "MIT License")',
+            },
+            readme: {
+              type: 'string',
+              description: 'README template (default: "Default")',
+            },
+          },
+          required: ['name'],
+        },
+      },
+      {
+        name: 'delete_repository',
+        description: 'Delete a repository (requires admin access)',
+        inputSchema: {
+          type: 'object',
+          properties: {
+            owner: {
+              type: 'string',
+              description: 'Repository owner username',
+            },
+            repo: {
+              type: 'string',
+              description: 'Repository name',
+            },
+          },
+          required: ['owner', 'repo'],
+        },
+      },
+      {
+        name: 'get_contents',
+        description: 'Get file or directory contents from a repository',
+        inputSchema: {
+          type: 'object',
+          properties: {
+            owner: {
+              type: 'string',
+              description: 'Repository owner username',
+            },
+            repo: {
+              type: 'string',
+              description: 'Repository name',
+            },
+            path: {
+              type: 'string',
+              description: 'File or directory path',
+            },
+            ref: {
+              type: 'string',
+              description: 'Branch, tag, or commit SHA (default: default branch)',
+            },
+          },
+          required: ['owner', 'repo', 'path'],
+        },
+      },
+      {
+        name: 'get_raw_content',
+        description: 'Get raw file content from a repository',
+        inputSchema: {
+          type: 'object',
+          properties: {
+            owner: {
+              type: 'string',
+              description: 'Repository owner username',
+            },
+            repo: {
+              type: 'string',
+              description: 'Repository name',
+            },
+            ref: {
+              type: 'string',
+              description: 'Branch, tag, or commit SHA',
+            },
+            path: {
+              type: 'string',
+              description: 'File path',
+            },
+          },
+          required: ['owner', 'repo', 'ref', 'path'],
+        },
+      },
+      {
+        name: 'list_branches',
+        description: 'List all branches in a repository',
+        inputSchema: {
+          type: 'object',
+          properties: {
+            owner: {
+              type: 'string',
+              description: 'Repository owner username',
+            },
+            repo: {
+              type: 'string',
+              description: 'Repository name',
+            },
+          },
+          required: ['owner', 'repo'],
+        },
+      },
+      {
+        name: 'get_commits',
+        description: 'Get commit history from a repository',
+        inputSchema: {
+          type: 'object',
+          properties: {
+            owner: {
+              type: 'string',
+              description: 'Repository owner username',
+            },
+            repo: {
+              type: 'string',
+              description: 'Repository name',
+            },
+            sha: {
+              type: 'string',
+              description: 'Branch name or commit SHA',
+            },
+            page: {
+              type: 'number',
+              description: 'Page number (default: 1)',
+            },
+          },
+          required: ['owner', 'repo'],
+        },
+      },
+    ],
+  };
+});
+
+// Handle tool calls
+server.setRequestHandler(CallToolRequestSchema, async (request) => {
+  try {
+    const { name, arguments: args } = request.params;
+
+    switch (name) {
+      case 'get_current_user': {
+        const user = await gogsClient.getCurrentUser();
+        return {
+          content: [
+            {
+              type: 'text',
+              text: JSON.stringify(user, null, 2),
+            },
+          ],
+        };
+      }
+
+      case 'get_user': {
+        const { username } = GetUserSchema.parse(args);
+        const user = await gogsClient.getUser(username);
+        return {
+          content: [
+            {
+              type: 'text',
+              text: JSON.stringify(user, null, 2),
+            },
+          ],
+        };
+      }
+
+      case 'search_users': {
+        const { query, limit } = SearchUsersSchema.parse(args);
+        const users = await gogsClient.searchUsers(query, limit);
+        return {
+          content: [
+            {
+              type: 'text',
+              text: JSON.stringify(users, null, 2),
+            },
+          ],
+        };
+      }
+
+      case 'list_user_repositories': {
+        const { username } = ListUserReposSchema.parse(args);
+        const repos = username
+          ? await gogsClient.listUserRepositories(username)
+          : await gogsClient.listMyRepositories();
+        return {
+          content: [
+            {
+              type: 'text',
+              text: JSON.stringify(repos, null, 2),
+            },
+          ],
+        };
+      }
+
+      case 'search_repositories': {
+        const { query, uid, limit, page } = SearchReposSchema.parse(args);
+        const repos = await gogsClient.searchRepositories(query, { uid, limit, page });
+        return {
+          content: [
+            {
+              type: 'text',
+              text: JSON.stringify(repos, null, 2),
+            },
+          ],
+        };
+      }
+
+      case 'get_repository': {
+        const { owner, repo } = GetRepoSchema.parse(args);
+        const repository = await gogsClient.getRepository(owner, repo);
+        return {
+          content: [
+            {
+              type: 'text',
+              text: JSON.stringify(repository, null, 2),
+            },
+          ],
+        };
+      }
+
+      case 'create_repository': {
+        const data = CreateRepoSchema.parse(args);
+        const repo = await gogsClient.createRepository(data);
+        return {
+          content: [
+            {
+              type: 'text',
+              text: JSON.stringify(repo, null, 2),
+            },
+          ],
+        };
+      }
+
+      case 'delete_repository': {
+        const { owner, repo } = DeleteRepoSchema.parse(args);
+        await gogsClient.deleteRepository(owner, repo);
+        return {
+          content: [
+            {
+              type: 'text',
+              text: `Repository ${owner}/${repo} deleted successfully`,
+            },
+          ],
+        };
+      }
+
+      case 'get_contents': {
+        const { owner, repo, path, ref } = GetContentsSchema.parse(args);
+        const contents = await gogsClient.getContents(owner, repo, path, ref);
+
+        // If it's a file with base64 content, decode it
+        if (!Array.isArray(contents) && contents.type === 'file' && contents.content) {
+          const decodedContent = Buffer.from(contents.content, 'base64').toString('utf-8');
+          return {
+            content: [
+              {
+                type: 'text',
+                text: `File: ${contents.path}\nSize: ${contents.size} bytes\nSHA: ${contents.sha}\n\nContent:\n${decodedContent}`,
+              },
+            ],
+          };
+        }
+
+        return {
+          content: [
+            {
+              type: 'text',
+              text: JSON.stringify(contents, null, 2),
+            },
+          ],
+        };
+      }
+
+      case 'get_raw_content': {
+        const { owner, repo, ref, path } = GetRawContentSchema.parse(args);
+        const content = await gogsClient.getRawContent(owner, repo, ref, path);
+        return {
+          content: [
+            {
+              type: 'text',
+              text: content,
+            },
+          ],
+        };
+      }
+
+      case 'list_branches': {
+        const { owner, repo } = ListBranchesSchema.parse(args);
+        const branches = await gogsClient.listBranches(owner, repo);
+        return {
+          content: [
+            {
+              type: 'text',
+              text: JSON.stringify(branches, null, 2),
+            },
+          ],
+        };
+      }
+
+      case 'get_commits': {
+        const { owner, repo, sha, page } = GetCommitsSchema.parse(args);
+        const commits = await gogsClient.getCommits(owner, repo, { sha, page });
+        return {
+          content: [
+            {
+              type: 'text',
+              text: JSON.stringify(commits, null, 2),
+            },
+          ],
+        };
+      }
+
+      default:
+        throw new Error(`Unknown tool: ${name}`);
+    }
+  } catch (error) {
+    if (error instanceof Error) {
+      return {
+        content: [
+          {
+            type: 'text',
+            text: `Error: ${error.message}`,
+          },
+        ],
+        isError: true,
+      };
+    }
+    throw error;
+  }
+});
+
+// Handle resources (optional - for browsing repository structure)
+server.setRequestHandler(ListResourcesRequestSchema, async () => {
+  return {
+    resources: [],
+  };
+});
+
+server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
+  throw new Error('Resource reading not yet implemented');
+});
+
+// Start server
+async function main() {
+  const transport = new StdioServerTransport();
+  await server.connect(transport);
+  console.error('Gogs MCP Server running on stdio');
+}
+
+main().catch((error) => {
+  console.error('Fatal error:', error);
+  process.exit(1);
+});

+ 84 - 0
src/types.ts

@@ -0,0 +1,84 @@
+/**
+ * Type definitions for Gogs API responses
+ */
+
+export interface GogsUser {
+  id: number;
+  username: string;
+  full_name: string;
+  email: string;
+  avatar_url: string;
+}
+
+export interface GogsRepository {
+  id: number;
+  owner: GogsUser;
+  name?: string;
+  full_name: string;
+  description?: string;
+  private: boolean;
+  fork: boolean;
+  html_url: string;
+  clone_url: string;
+  ssh_url: string;
+  permissions?: {
+    admin: boolean;
+    push: boolean;
+    pull: boolean;
+  };
+  default_branch?: string;
+}
+
+export interface GogsSearchResponse<T> {
+  data: T[];
+  ok: boolean;
+}
+
+export interface GogsFileContent {
+  type: 'file' | 'dir' | 'symlink' | 'submodule';
+  encoding?: string;
+  size: number;
+  name: string;
+  path: string;
+  content?: string;
+  sha: string;
+  url: string;
+  git_url: string;
+  html_url: string;
+  download_url: string;
+  target?: string;
+  submodule_git_url?: string;
+}
+
+export interface GogsBranch {
+  name: string;
+  commit: {
+    id: string;
+    message: string;
+    url: string;
+  };
+}
+
+export interface GogsCommit {
+  sha: string;
+  commit: {
+    author: {
+      name: string;
+      email: string;
+      date: string;
+    };
+    committer: {
+      name: string;
+      email: string;
+      date: string;
+    };
+    message: string;
+  };
+  author?: GogsUser;
+  committer?: GogsUser;
+}
+
+export interface GogsConfig {
+  serverUrl: string;
+  accessToken?: string;
+}

+ 20 - 0
tsconfig.json

@@ -0,0 +1,20 @@
+{
+  "compilerOptions": {
+    "target": "ES2022",
+    "module": "Node16",
+    "lib": ["ES2022"],
+    "moduleResolution": "Node16",
+    "rootDir": "./src",
+    "outDir": "./dist",
+    "strict": true,
+    "esModuleInterop": true,
+    "skipLibCheck": true,
+    "forceConsistentCasingInFileNames": true,
+    "resolveJsonModule": true,
+    "declaration": true,
+    "declarationMap": true,
+    "sourceMap": true
+  },
+  "include": ["src/**/*"],
+  "exclude": ["node_modules", "dist"]
+}