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