update readme
This commit is contained in:
commit
63d058c607
16 changed files with 1354 additions and 270 deletions
27
README.md
27
README.md
|
|
@ -24,7 +24,7 @@ These servers aim to demonstrate MCP features and the Typescript and Python SDK.
|
||||||
- **[PostgreSQL](src/postgres)** - Read-only database access with schema inspection
|
- **[PostgreSQL](src/postgres)** - Read-only database access with schema inspection
|
||||||
- **[Puppeteer](src/puppeteer)** - Browser automation and web scraping
|
- **[Puppeteer](src/puppeteer)** - Browser automation and web scraping
|
||||||
- **[Sentry](src/sentry)** - Retrieving and analyzing issues from Sentry.io
|
- **[Sentry](src/sentry)** - Retrieving and analyzing issues from Sentry.io
|
||||||
- **[Sequential Thinking](src/sequential-thinking)** - Dynamic and reflective problem-solving through thought sequences
|
- **[Sequential Thinking](src/sequentialthinking)** - Dynamic and reflective problem-solving through thought sequences
|
||||||
- **[Slack](src/slack)** - Channel management and messaging capabilities
|
- **[Slack](src/slack)** - Channel management and messaging capabilities
|
||||||
- **[Sqlite](src/sqlite)** - Database interaction and business intelligence capabilities
|
- **[Sqlite](src/sqlite)** - Database interaction and business intelligence capabilities
|
||||||
|
|
||||||
|
|
@ -34,8 +34,11 @@ These servers aim to demonstrate MCP features and the Typescript and Python SDK.
|
||||||
|
|
||||||
Official integrations are maintained by companies building production ready MCP servers for their platforms.
|
Official integrations are maintained by companies building production ready MCP servers for their platforms.
|
||||||
|
|
||||||
|
- <img height="12" width="12" src="https://axiom.co/favicon.ico" alt="Axiom Logo" /> **[Axiom](https://github.com/axiomhq/mcp-server-axiom)** - Query and analyze your Axiom logs, traces, and all other event data in natural language
|
||||||
|
- <img height="12" width="12" src="https://browserbase.com/favicon.ico" alt="Browserbase Logo" /> **[Browserbase](https://github.com/browserbase/mcp-server-browserbase)** - Automate browser interactions in the cloud (e.g. web navigation, data extraction, form filling, and more)
|
||||||
- <img height="12" width="12" src="https://cdn.simpleicons.org/cloudflare" /> **[Cloudflare](https://github.com/cloudflare/mcp-server-cloudflare)** - Deploy, configure & interrogate your resources on the Cloudflare developer platform (e.g. Workers/KV/R2/D1)
|
- <img height="12" width="12" src="https://cdn.simpleicons.org/cloudflare" /> **[Cloudflare](https://github.com/cloudflare/mcp-server-cloudflare)** - Deploy, configure & interrogate your resources on the Cloudflare developer platform (e.g. Workers/KV/R2/D1)
|
||||||
- **[Raygun](https://github.com/MindscapeHQ/mcp-server-raygun)** - Interact with your crash reporting and real using monitoring data on your Raygun account
|
- **[Raygun](https://github.com/MindscapeHQ/mcp-server-raygun)** - Interact with your crash reporting and real using monitoring data on your Raygun account
|
||||||
|
- **[Obsidian Markdown Notes](https://github.com/calclavia/mcp-obsidian)** - Read and search through your Obsidian vault or any directory containing Markdown notes
|
||||||
- <img height="12" width="12" src="https://e2b.dev/favicon.ico" alt="E2B Logo" /> **[E2B](https://github.com/e2b-dev/mcp-server)** - Run code in secure sandboxes hosted by [E2B](https://e2b.dev)
|
- <img height="12" width="12" src="https://e2b.dev/favicon.ico" alt="E2B Logo" /> **[E2B](https://github.com/e2b-dev/mcp-server)** - Run code in secure sandboxes hosted by [E2B](https://e2b.dev)
|
||||||
- **[Neon](https://github.com/neondatabase/mcp-server-neon)** - Interact with the Neon serverless Postgres platform
|
- **[Neon](https://github.com/neondatabase/mcp-server-neon)** - Interact with the Neon serverless Postgres platform
|
||||||
- <img height="12" width="12" src="https://www.tinybird.co/favicon.ico" alt="Tinybird Logo" /> **[Tinybird](https://github.com/tinybirdco/mcp-tinybird)** - Interact with Tinybird serverless ClickHouse platform
|
- <img height="12" width="12" src="https://www.tinybird.co/favicon.ico" alt="Tinybird Logo" /> **[Tinybird](https://github.com/tinybirdco/mcp-tinybird)** - Interact with Tinybird serverless ClickHouse platform
|
||||||
|
|
@ -49,30 +52,42 @@ A growing set of community-developed and maintained servers demonstrates various
|
||||||
> **Note:** Community servers are **untested** and should be used at **your own risk**. They are not affiliated with or endorsed by Anthropic.
|
> **Note:** Community servers are **untested** and should be used at **your own risk**. They are not affiliated with or endorsed by Anthropic.
|
||||||
|
|
||||||
- **[MCP Installer](https://github.com/anaisbetts/mcp-installer)** - This server is a server that installs other MCP servers for you.
|
- **[MCP Installer](https://github.com/anaisbetts/mcp-installer)** - This server is a server that installs other MCP servers for you.
|
||||||
- **[Spotify MCP](https://github.com/varunneal/spotify-mcp)** - This MCP allows an LLM to play and use Spotify.
|
- **[Spotify](https://github.com/varunneal/spotify-mcp)** - This MCP allows an LLM to play and use Spotify.
|
||||||
- **[Inoyu](https://github.com/sergehuber/inoyu-mcp-unomi-server)** - Interact with an Apache Unomi CDP customer data platform to retrieve and update customer profiles
|
- **[Inoyu](https://github.com/sergehuber/inoyu-mcp-unomi-server)** - Interact with an Apache Unomi CDP customer data platform to retrieve and update customer profiles
|
||||||
|
- **[Snowflake](https://github.com/datawiz168/mcp-snowflake-service)** - This MCP server enables LLMs to interact with Snowflake databases, allowing for secure and controlled data operations.
|
||||||
- **[MySQL](https://github.com/designcomputer/mysql_mcp_server)** - MySQL database integration with configurable access controls and schema inspection
|
- **[MySQL](https://github.com/designcomputer/mysql_mcp_server)** - MySQL database integration with configurable access controls and schema inspection
|
||||||
|
- **[MSSQL](https://github.com/aekanun2020/mcp-server/)** - MSSQL database integration with configurable access controls and schema inspection
|
||||||
- **[BigQuery](https://github.com/LucasHild/mcp-server-bigquery)** (by LucasHild) - This server enables LLMs to inspect database schemas and execute queries on BigQuery.
|
- **[BigQuery](https://github.com/LucasHild/mcp-server-bigquery)** (by LucasHild) - This server enables LLMs to inspect database schemas and execute queries on BigQuery.
|
||||||
- **[BigQuery](https://github.com/ergut/mcp-bigquery-server)** (by ergut) - Server implementation for Google BigQuery integration that enables direct BigQuery database access and querying capabilities
|
- **[BigQuery](https://github.com/ergut/mcp-bigquery-server)** (by ergut) - Server implementation for Google BigQuery integration that enables direct BigQuery database access and querying capabilities
|
||||||
- **[Todoist](https://github.com/abhiz123/todoist-mcp-server)** - Interact with Todoist to manage your tasks.
|
- **[Todoist](https://github.com/abhiz123/todoist-mcp-server)** - Interact with Todoist to manage your tasks.
|
||||||
|
- **[Tavily search](https://github.com/RamXX/mcp-tavily")** - An MCP server for Tavily's search & news API, with explicit site inclusions/exclusions
|
||||||
- **[Linear](https://github.com/jerhadf/linear-mcp-server)** - Allows LLM to interact with Linear's API for project management, including searching, creating, and updating issues.
|
- **[Linear](https://github.com/jerhadf/linear-mcp-server)** - Allows LLM to interact with Linear's API for project management, including searching, creating, and updating issues.
|
||||||
- **[Playwright MCP](https://github.com/executeautomation/mcp-playwright)** - This MCP Server will help you run browser automation and webscraping using Playwright
|
- **[Playwright](https://github.com/executeautomation/mcp-playwright)** - This MCP Server will help you run browser automation and webscraping using Playwright
|
||||||
- **[AWS](https://github.com/rishikavikondala/mcp-server-aws)** - Perform operations on your AWS resources using an LLM
|
- **[AWS](https://github.com/rishikavikondala/mcp-server-aws)** - Perform operations on your AWS resources using an LLM
|
||||||
- **[LlamaCloud](https://github.com/run-llama/mcp-server-llamacloud)** (by marcusschiesser) - Integrate the data stored in a managed index on [LlamaCloud](https://cloud.llamaindex.ai/)
|
- **[LlamaCloud](https://github.com/run-llama/mcp-server-llamacloud)** (by marcusschiesser) - Integrate the data stored in a managed index on [LlamaCloud](https://cloud.llamaindex.ai/)
|
||||||
- **[Any Chat Completions](https://github.com/pyroprompts/any-chat-completions-mcp)** - Interact with any OpenAI SDK Compatible Chat Completions API like OpenAI, Perplexity, Groq, xAI and many more.
|
- **[Any Chat Completions](https://github.com/pyroprompts/any-chat-completions-mcp)** - Interact with any OpenAI SDK Compatible Chat Completions API like OpenAI, Perplexity, Groq, xAI and many more.
|
||||||
- **[Windows CLI](https://github.com/SimonB97/win-cli-mcp-server)** - MCP server for secure command-line interactions on Windows systems, enabling controlled access to PowerShell, CMD, and Git Bash shells.
|
- **[Windows CLI](https://github.com/SimonB97/win-cli-mcp-server)** - MCP server for secure command-line interactions on Windows systems, enabling controlled access to PowerShell, CMD, and Git Bash shells.
|
||||||
- **[OpenRPC](https://github.com/shanejonas/openrpc-mpc-server)** - Interact with and discover JSON-RPC APIs via [OpenRPC](https://open-rpc.org).
|
- **[OpenRPC](https://github.com/shanejonas/openrpc-mpc-server)** - Interact with and discover JSON-RPC APIs via [OpenRPC](https://open-rpc.org).
|
||||||
- **[ChatSum](https://github.com/mcpservers/mcp-server-chatsum)** - Query and Summarize wechat messages with LLM.
|
- **[FireCrawl](https://github.com/vrknetha/mcp-server-firecrawl)** - Advanced web scraping with JavaScript rendering, PDF support, and smart rate limiting
|
||||||
|
- **[AlphaVantage](https://github.com/calvernaz/alphavantage)** - MCP server for stock market data API [AlphaVantage](https://www.alphavantage.co)
|
||||||
|
- **[Docker](https://github.com/ckreiling/mcp-server-docker)** - Integrate with Docker to manage containers, images, volumes, and networks.
|
||||||
|
- **[Kubernetes](https://github.com/Flux159/mcp-server-kubernetes)** - Connect to Kubernetes cluster and manage pods, deployments, and services.
|
||||||
|
- **[OpenAPI](https://github.com/snaggle-ai/openapi-mcp-server)** - Interact with [OpenAPI](https://www.openapis.org/) APIs.
|
||||||
|
- **[Pandoc](https://github.com/vivekVells/mcp-pandoc)** - MCP server for seamless document format conversion using Pandoc, supporting Markdown, HTML, and plain text, with other formats like PDF, csv and docx in development.
|
||||||
|
- **[ChatSum](https://github.com/mcpso/mcp-server-chatsum)** - Query and Summarize chat messages with LLM.
|
||||||
|
|
||||||
|
|
||||||
## 📚 Resources
|
## 📚 Resources
|
||||||
|
|
||||||
Additional resources on MCP.
|
Additional resources on MCP.
|
||||||
|
|
||||||
- **[Awesome MCP Servers by punkpeye](https://github.com/punkpeye/awesome-mcp-servers)** - A curated list of MCP servers by **[Frank Fiegel](https://github.com/punkpeye)**
|
- **[Awesome MCP Servers by punkpeye](https://github.com/punkpeye/awesome-mcp-servers)** (**[website](https://glama.ai/mcp/servers)**) - A curated list of MCP servers by **[Frank Fiegel](https://github.com/punkpeye)**
|
||||||
- **[Awesome MCP Servers by wong2](https://github.com/wong2/awesome-mcp-servers)** - A curated list of MCP servers by **[wong2](https://github.com/wong2)**
|
- **[Awesome MCP Servers by wong2](https://github.com/wong2/awesome-mcp-servers)** (**[website](https://mcpservers.org)**) - A curated list of MCP servers by **[wong2](https://github.com/wong2)**
|
||||||
- **[Awesome MCP Servers by appcypher](https://github.com/appcypher/awesome-mcp-servers)** - A curated list of MCP servers by **[Stephen Akinyemi](https://github.com/appcypher)**
|
- **[Awesome MCP Servers by appcypher](https://github.com/appcypher/awesome-mcp-servers)** - A curated list of MCP servers by **[Stephen Akinyemi](https://github.com/appcypher)**
|
||||||
|
- **[Discord Server](https://glama.ai/mcp/discord)** – A community discord server dedicated to MCP by **[Frank Fiegel](https://github.com/punkpeye)**
|
||||||
- **[mcp-get](https://mcp-get.com)** - Command line tool for installing and managing MCP servers by **[Michael Latman](https://github.com/michaellatman)**
|
- **[mcp-get](https://mcp-get.com)** - Command line tool for installing and managing MCP servers by **[Michael Latman](https://github.com/michaellatman)**
|
||||||
- **[mcp-cli](https://github.com/wong2/mcp-cli)** - A CLI inspector for the Model Context Protocol by **[wong2](https://github.com/wong2)**
|
- **[mcp-cli](https://github.com/wong2/mcp-cli)** - A CLI inspector for the Model Context Protocol by **[wong2](https://github.com/wong2)**
|
||||||
|
- **[r/mcp](https://www.reddit.com/r/mcp)** – A Reddit community dedicated to MCP by **[Frank Fiegel](https://github.com/punkpeye)**
|
||||||
|
|
||||||
## 🚀 Getting Started
|
## 🚀 Getting Started
|
||||||
|
|
||||||
|
|
|
||||||
29
package-lock.json
generated
29
package-lock.json
generated
|
|
@ -1523,6 +1523,12 @@
|
||||||
"@types/node": "*"
|
"@types/node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/diff": {
|
||||||
|
"version": "5.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/diff/-/diff-5.2.3.tgz",
|
||||||
|
"integrity": "sha512-K0Oqlrq3kQMaO2RhfrNQX5trmt+XLyom88zS0u84nnIcLvFnRUMRRHmrGny5GSM+kNO9IZLARsdQHDzkhAgmrQ==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/@types/express": {
|
"node_modules/@types/express": {
|
||||||
"version": "5.0.0",
|
"version": "5.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.0.tgz",
|
||||||
|
|
@ -2258,6 +2264,14 @@
|
||||||
"resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1367902.tgz",
|
"resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1367902.tgz",
|
||||||
"integrity": "sha512-XxtPuC3PGakY6PD7dG66/o8KwJ/LkH2/EKe19Dcw58w53dv4/vSQEkn/SzuyhHE2q4zPgCkxQBxus3VV4ql+Pg=="
|
"integrity": "sha512-XxtPuC3PGakY6PD7dG66/o8KwJ/LkH2/EKe19Dcw58w53dv4/vSQEkn/SzuyhHE2q4zPgCkxQBxus3VV4ql+Pg=="
|
||||||
},
|
},
|
||||||
|
"node_modules/diff": {
|
||||||
|
"version": "5.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz",
|
||||||
|
"integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.3.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/dotenv": {
|
"node_modules/dotenv": {
|
||||||
"version": "16.4.6",
|
"version": "16.4.6",
|
||||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.6.tgz",
|
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.6.tgz",
|
||||||
|
|
@ -4983,6 +4997,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"src/aws-kb-retrieval-server": {
|
"src/aws-kb-retrieval-server": {
|
||||||
|
"name": "@modelcontextprotocol/server-aws-kb-retrieval",
|
||||||
"version": "0.6.2",
|
"version": "0.6.2",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
@ -5167,7 +5182,8 @@
|
||||||
"version": "0.6.2",
|
"version": "0.6.2",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@modelcontextprotocol/sdk": "1.0.1",
|
"@modelcontextprotocol/sdk": "0.5.0",
|
||||||
|
"diff": "^5.1.0",
|
||||||
"glob": "^10.3.10",
|
"glob": "^10.3.10",
|
||||||
"zod-to-json-schema": "^3.23.5"
|
"zod-to-json-schema": "^3.23.5"
|
||||||
},
|
},
|
||||||
|
|
@ -5175,6 +5191,7 @@
|
||||||
"mcp-server-filesystem": "dist/index.js"
|
"mcp-server-filesystem": "dist/index.js"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/diff": "^5.0.9",
|
||||||
"@types/node": "^20.11.0",
|
"@types/node": "^20.11.0",
|
||||||
"shx": "^0.3.4",
|
"shx": "^0.3.4",
|
||||||
"typescript": "^5.3.3"
|
"typescript": "^5.3.3"
|
||||||
|
|
@ -5287,8 +5304,10 @@
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@modelcontextprotocol/sdk": "1.0.1",
|
"@modelcontextprotocol/sdk": "1.0.1",
|
||||||
|
"@types/node": "^20.11.0",
|
||||||
"@types/node-fetch": "^2.6.12",
|
"@types/node-fetch": "^2.6.12",
|
||||||
"node-fetch": "^3.3.2",
|
"node-fetch": "^3.3.2",
|
||||||
|
"zod": "^3.22.4",
|
||||||
"zod-to-json-schema": "^3.23.5"
|
"zod-to-json-schema": "^3.23.5"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
|
|
@ -5309,6 +5328,14 @@
|
||||||
"zod": "^3.23.8"
|
"zod": "^3.23.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"src/github/node_modules/@types/node": {
|
||||||
|
"version": "20.17.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.9.tgz",
|
||||||
|
"integrity": "sha512-0JOXkRyLanfGPE2QRCwgxhzlBAvaRdCNMcvbd7jFfpmD4eEXll7LRwy5ymJmyeZqk7Nh7eD2LeUyQ68BbndmXw==",
|
||||||
|
"dependencies": {
|
||||||
|
"undici-types": "~6.19.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"src/github/node_modules/data-uri-to-buffer": {
|
"src/github/node_modules/data-uri-to-buffer": {
|
||||||
"version": "4.0.1",
|
"version": "4.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz",
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,30 @@ Node.js server implementing Model Context Protocol (MCP) for filesystem operatio
|
||||||
- `path` (string): File location
|
- `path` (string): File location
|
||||||
- `content` (string): File content
|
- `content` (string): File content
|
||||||
|
|
||||||
|
- **edit_file**
|
||||||
|
- Make selective edits using advanced pattern matching and formatting
|
||||||
|
- Features:
|
||||||
|
- Line-based and multi-line content matching
|
||||||
|
- Whitespace normalization with indentation preservation
|
||||||
|
- Fuzzy matching with confidence scoring
|
||||||
|
- Multiple simultaneous edits with correct positioning
|
||||||
|
- Indentation style detection and preservation
|
||||||
|
- Git-style diff output with context
|
||||||
|
- Preview changes with dry run mode
|
||||||
|
- Failed match debugging with confidence scores
|
||||||
|
- Inputs:
|
||||||
|
- `path` (string): File to edit
|
||||||
|
- `edits` (array): List of edit operations
|
||||||
|
- `oldText` (string): Text to search for (can be substring)
|
||||||
|
- `newText` (string): Text to replace with
|
||||||
|
- `dryRun` (boolean): Preview changes without applying (default: false)
|
||||||
|
- `options` (object): Optional formatting settings
|
||||||
|
- `preserveIndentation` (boolean): Keep existing indentation (default: true)
|
||||||
|
- `normalizeWhitespace` (boolean): Normalize spaces while preserving structure (default: true)
|
||||||
|
- `partialMatch` (boolean): Enable fuzzy matching (default: true)
|
||||||
|
- Returns detailed diff and match information for dry runs, otherwise applies changes
|
||||||
|
- Best Practice: Always use dryRun first to preview changes before applying them
|
||||||
|
|
||||||
- **create_directory**
|
- **create_directory**
|
||||||
- Create new directory or ensure it exists
|
- Create new directory or ensure it exists
|
||||||
- Input: `path` (string)
|
- Input: `path` (string)
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ import path from "path";
|
||||||
import os from 'os';
|
import os from 'os';
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { zodToJsonSchema } from "zod-to-json-schema";
|
import { zodToJsonSchema } from "zod-to-json-schema";
|
||||||
|
import { diffLines, createTwoFilesPatch } from 'diff';
|
||||||
|
|
||||||
// Command line argument parsing
|
// Command line argument parsing
|
||||||
const args = process.argv.slice(2);
|
const args = process.argv.slice(2);
|
||||||
|
|
@ -106,6 +107,17 @@ const WriteFileArgsSchema = z.object({
|
||||||
content: z.string(),
|
content: z.string(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const EditOperation = z.object({
|
||||||
|
oldText: z.string().describe('Text to search for - must match exactly'),
|
||||||
|
newText: z.string().describe('Text to replace with')
|
||||||
|
});
|
||||||
|
|
||||||
|
const EditFileArgsSchema = z.object({
|
||||||
|
path: z.string(),
|
||||||
|
edits: z.array(EditOperation),
|
||||||
|
dryRun: z.boolean().default(false).describe('Preview changes using git-style diff format')
|
||||||
|
});
|
||||||
|
|
||||||
const CreateDirectoryArgsSchema = z.object({
|
const CreateDirectoryArgsSchema = z.object({
|
||||||
path: z.string(),
|
path: z.string(),
|
||||||
});
|
});
|
||||||
|
|
@ -202,6 +214,104 @@ async function searchFiles(
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// file editing and diffing utilities
|
||||||
|
function normalizeLineEndings(text: string): string {
|
||||||
|
return text.replace(/\r\n/g, '\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
function createUnifiedDiff(originalContent: string, newContent: string, filepath: string = 'file'): string {
|
||||||
|
// Ensure consistent line endings for diff
|
||||||
|
const normalizedOriginal = normalizeLineEndings(originalContent);
|
||||||
|
const normalizedNew = normalizeLineEndings(newContent);
|
||||||
|
|
||||||
|
return createTwoFilesPatch(
|
||||||
|
filepath,
|
||||||
|
filepath,
|
||||||
|
normalizedOriginal,
|
||||||
|
normalizedNew,
|
||||||
|
'original',
|
||||||
|
'modified'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function applyFileEdits(
|
||||||
|
filePath: string,
|
||||||
|
edits: Array<{oldText: string, newText: string}>,
|
||||||
|
dryRun = false
|
||||||
|
): Promise<string> {
|
||||||
|
// Read file content and normalize line endings
|
||||||
|
const content = normalizeLineEndings(await fs.readFile(filePath, 'utf-8'));
|
||||||
|
|
||||||
|
// Apply edits sequentially
|
||||||
|
let modifiedContent = content;
|
||||||
|
for (const edit of edits) {
|
||||||
|
const normalizedOld = normalizeLineEndings(edit.oldText);
|
||||||
|
const normalizedNew = normalizeLineEndings(edit.newText);
|
||||||
|
|
||||||
|
// If exact match exists, use it
|
||||||
|
if (modifiedContent.includes(normalizedOld)) {
|
||||||
|
modifiedContent = modifiedContent.replace(normalizedOld, normalizedNew);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, try line-by-line matching with flexibility for whitespace
|
||||||
|
const oldLines = normalizedOld.split('\n');
|
||||||
|
const contentLines = modifiedContent.split('\n');
|
||||||
|
let matchFound = false;
|
||||||
|
|
||||||
|
for (let i = 0; i <= contentLines.length - oldLines.length; i++) {
|
||||||
|
const potentialMatch = contentLines.slice(i, i + oldLines.length);
|
||||||
|
|
||||||
|
// Compare lines with normalized whitespace
|
||||||
|
const isMatch = oldLines.every((oldLine, j) => {
|
||||||
|
const contentLine = potentialMatch[j];
|
||||||
|
return oldLine.trim() === contentLine.trim();
|
||||||
|
});
|
||||||
|
|
||||||
|
if (isMatch) {
|
||||||
|
// Preserve original indentation of first line
|
||||||
|
const originalIndent = contentLines[i].match(/^\s*/)?.[0] || '';
|
||||||
|
const newLines = normalizedNew.split('\n').map((line, j) => {
|
||||||
|
if (j === 0) return originalIndent + line.trimStart();
|
||||||
|
// For subsequent lines, try to preserve relative indentation
|
||||||
|
const oldIndent = oldLines[j]?.match(/^\s*/)?.[0] || '';
|
||||||
|
const newIndent = line.match(/^\s*/)?.[0] || '';
|
||||||
|
if (oldIndent && newIndent) {
|
||||||
|
const relativeIndent = newIndent.length - oldIndent.length;
|
||||||
|
return originalIndent + ' '.repeat(Math.max(0, relativeIndent)) + line.trimStart();
|
||||||
|
}
|
||||||
|
return line;
|
||||||
|
});
|
||||||
|
|
||||||
|
contentLines.splice(i, oldLines.length, ...newLines);
|
||||||
|
modifiedContent = contentLines.join('\n');
|
||||||
|
matchFound = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!matchFound) {
|
||||||
|
throw new Error(`Could not find exact match for edit:\n${edit.oldText}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create unified diff
|
||||||
|
const diff = createUnifiedDiff(content, modifiedContent, filePath);
|
||||||
|
|
||||||
|
// Format diff with appropriate number of backticks
|
||||||
|
let numBackticks = 3;
|
||||||
|
while (diff.includes('`'.repeat(numBackticks))) {
|
||||||
|
numBackticks++;
|
||||||
|
}
|
||||||
|
const formattedDiff = `${'`'.repeat(numBackticks)}diff\n${diff}${'`'.repeat(numBackticks)}\n\n`;
|
||||||
|
|
||||||
|
if (!dryRun) {
|
||||||
|
await fs.writeFile(filePath, modifiedContent, 'utf-8');
|
||||||
|
}
|
||||||
|
|
||||||
|
return formattedDiff;
|
||||||
|
}
|
||||||
|
|
||||||
// Tool handlers
|
// Tool handlers
|
||||||
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
||||||
return {
|
return {
|
||||||
|
|
@ -233,6 +343,14 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
||||||
"Handles text content with proper encoding. Only works within allowed directories.",
|
"Handles text content with proper encoding. Only works within allowed directories.",
|
||||||
inputSchema: zodToJsonSchema(WriteFileArgsSchema) as ToolInput,
|
inputSchema: zodToJsonSchema(WriteFileArgsSchema) as ToolInput,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "edit_file",
|
||||||
|
description:
|
||||||
|
"Make line-based edits to a text file. Each edit replaces exact line sequences " +
|
||||||
|
"with new content. Returns a git-style diff showing the changes made. " +
|
||||||
|
"Only works within allowed directories.",
|
||||||
|
inputSchema: zodToJsonSchema(EditFileArgsSchema) as ToolInput,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "create_directory",
|
name: "create_directory",
|
||||||
description:
|
description:
|
||||||
|
|
@ -346,6 +464,18 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case "edit_file": {
|
||||||
|
const parsed = EditFileArgsSchema.safeParse(args);
|
||||||
|
if (!parsed.success) {
|
||||||
|
throw new Error(`Invalid arguments for edit_file: ${parsed.error}`);
|
||||||
|
}
|
||||||
|
const validPath = await validatePath(parsed.data.path);
|
||||||
|
const result = await applyFileEdits(validPath, parsed.data.edits, parsed.data.dryRun);
|
||||||
|
return {
|
||||||
|
content: [{ type: "text", text: result }],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
case "create_directory": {
|
case "create_directory": {
|
||||||
const parsed = CreateDirectoryArgsSchema.safeParse(args);
|
const parsed = CreateDirectoryArgsSchema.safeParse(args);
|
||||||
if (!parsed.success) {
|
if (!parsed.success) {
|
||||||
|
|
|
||||||
|
|
@ -19,11 +19,13 @@
|
||||||
"watch": "tsc --watch"
|
"watch": "tsc --watch"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@modelcontextprotocol/sdk": "1.0.1",
|
"@modelcontextprotocol/sdk": "0.5.0",
|
||||||
|
"diff": "^5.1.0",
|
||||||
"glob": "^10.3.10",
|
"glob": "^10.3.10",
|
||||||
"zod-to-json-schema": "^3.23.5"
|
"zod-to-json-schema": "^3.23.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/diff": "^5.0.9",
|
||||||
"@types/node": "^20.11.0",
|
"@types/node": "^20.11.0",
|
||||||
"shx": "^0.3.4",
|
"shx": "^0.3.4",
|
||||||
"typescript": "^5.3.3"
|
"typescript": "^5.3.3"
|
||||||
|
|
|
||||||
|
|
@ -26,34 +26,41 @@ Please note that mcp-server-git is currently in early development. The functiona
|
||||||
- `repo_path` (string): Path to Git repository
|
- `repo_path` (string): Path to Git repository
|
||||||
- Returns: Diff output of staged changes
|
- Returns: Diff output of staged changes
|
||||||
|
|
||||||
4. `git_commit`
|
4. `git_diff`
|
||||||
|
- Shows differences between branches or commits
|
||||||
|
- Inputs:
|
||||||
|
- `repo_path` (string): Path to Git repository
|
||||||
|
- `target` (string): Target branch or commit to compare with
|
||||||
|
- Returns: Diff output comparing current state with target
|
||||||
|
|
||||||
|
5. `git_commit`
|
||||||
- Records changes to the repository
|
- Records changes to the repository
|
||||||
- Inputs:
|
- Inputs:
|
||||||
- `repo_path` (string): Path to Git repository
|
- `repo_path` (string): Path to Git repository
|
||||||
- `message` (string): Commit message
|
- `message` (string): Commit message
|
||||||
- Returns: Confirmation with new commit hash
|
- Returns: Confirmation with new commit hash
|
||||||
|
|
||||||
5. `git_add`
|
6. `git_add`
|
||||||
- Adds file contents to the staging area
|
- Adds file contents to the staging area
|
||||||
- Inputs:
|
- Inputs:
|
||||||
- `repo_path` (string): Path to Git repository
|
- `repo_path` (string): Path to Git repository
|
||||||
- `files` (string[]): Array of file paths to stage
|
- `files` (string[]): Array of file paths to stage
|
||||||
- Returns: Confirmation of staged files
|
- Returns: Confirmation of staged files
|
||||||
|
|
||||||
6. `git_reset`
|
7. `git_reset`
|
||||||
- Unstages all staged changes
|
- Unstages all staged changes
|
||||||
- Input:
|
- Input:
|
||||||
- `repo_path` (string): Path to Git repository
|
- `repo_path` (string): Path to Git repository
|
||||||
- Returns: Confirmation of reset operation
|
- Returns: Confirmation of reset operation
|
||||||
|
|
||||||
7. `git_log`
|
8. `git_log`
|
||||||
- Shows the commit logs
|
- Shows the commit logs
|
||||||
- Inputs:
|
- Inputs:
|
||||||
- `repo_path` (string): Path to Git repository
|
- `repo_path` (string): Path to Git repository
|
||||||
- `max_count` (number, optional): Maximum number of commits to show (default: 10)
|
- `max_count` (number, optional): Maximum number of commits to show (default: 10)
|
||||||
- Returns: Array of commit entries with hash, author, date, and message
|
- Returns: Array of commit entries with hash, author, date, and message
|
||||||
|
|
||||||
8. `git_create_branch`
|
9. `git_create_branch`
|
||||||
- Creates a new branch
|
- Creates a new branch
|
||||||
- Inputs:
|
- Inputs:
|
||||||
- `repo_path` (string): Path to Git repository
|
- `repo_path` (string): Path to Git repository
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,10 @@ class GitDiffUnstaged(BaseModel):
|
||||||
class GitDiffStaged(BaseModel):
|
class GitDiffStaged(BaseModel):
|
||||||
repo_path: str
|
repo_path: str
|
||||||
|
|
||||||
|
class GitDiff(BaseModel):
|
||||||
|
repo_path: str
|
||||||
|
target: str
|
||||||
|
|
||||||
class GitCommit(BaseModel):
|
class GitCommit(BaseModel):
|
||||||
repo_path: str
|
repo_path: str
|
||||||
message: str
|
message: str
|
||||||
|
|
@ -48,6 +52,7 @@ class GitTools(str, Enum):
|
||||||
STATUS = "git_status"
|
STATUS = "git_status"
|
||||||
DIFF_UNSTAGED = "git_diff_unstaged"
|
DIFF_UNSTAGED = "git_diff_unstaged"
|
||||||
DIFF_STAGED = "git_diff_staged"
|
DIFF_STAGED = "git_diff_staged"
|
||||||
|
DIFF = "git_diff"
|
||||||
COMMIT = "git_commit"
|
COMMIT = "git_commit"
|
||||||
ADD = "git_add"
|
ADD = "git_add"
|
||||||
RESET = "git_reset"
|
RESET = "git_reset"
|
||||||
|
|
@ -63,6 +68,9 @@ def git_diff_unstaged(repo: git.Repo) -> str:
|
||||||
def git_diff_staged(repo: git.Repo) -> str:
|
def git_diff_staged(repo: git.Repo) -> str:
|
||||||
return repo.git.diff("--cached")
|
return repo.git.diff("--cached")
|
||||||
|
|
||||||
|
def git_diff(repo: git.Repo, target: str) -> str:
|
||||||
|
return repo.git.diff(target)
|
||||||
|
|
||||||
def git_commit(repo: git.Repo, message: str) -> str:
|
def git_commit(repo: git.Repo, message: str) -> str:
|
||||||
commit = repo.index.commit(message)
|
commit = repo.index.commit(message)
|
||||||
return f"Changes committed successfully with hash {commit.hexsha}"
|
return f"Changes committed successfully with hash {commit.hexsha}"
|
||||||
|
|
@ -127,6 +135,11 @@ async def serve(repository: Path | None) -> None:
|
||||||
description="Shows changes that are staged for commit",
|
description="Shows changes that are staged for commit",
|
||||||
inputSchema=GitDiffStaged.schema(),
|
inputSchema=GitDiffStaged.schema(),
|
||||||
),
|
),
|
||||||
|
Tool(
|
||||||
|
name=GitTools.DIFF,
|
||||||
|
description="Shows differences between branches or commits",
|
||||||
|
inputSchema=GitDiff.schema(),
|
||||||
|
),
|
||||||
Tool(
|
Tool(
|
||||||
name=GitTools.COMMIT,
|
name=GitTools.COMMIT,
|
||||||
description="Records changes to the repository",
|
description="Records changes to the repository",
|
||||||
|
|
@ -210,6 +223,13 @@ async def serve(repository: Path | None) -> None:
|
||||||
text=f"Staged changes:\n{diff}"
|
text=f"Staged changes:\n{diff}"
|
||||||
)]
|
)]
|
||||||
|
|
||||||
|
case GitTools.DIFF:
|
||||||
|
diff = git_diff(repo, arguments["target"])
|
||||||
|
return [TextContent(
|
||||||
|
type="text",
|
||||||
|
text=f"Diff with {arguments['target']}:\n{diff}"
|
||||||
|
)]
|
||||||
|
|
||||||
case GitTools.COMMIT:
|
case GitTools.COMMIT:
|
||||||
result = git_commit(repo, arguments["message"])
|
result = git_commit(repo, arguments["message"])
|
||||||
return [TextContent(
|
return [TextContent(
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
# GitHub MCP Server
|
# GitHub MCP Server
|
||||||
|
|
||||||
MCP Server for the GitHub API, enabling file operations, repository management, and more.
|
MCP Server for the GitHub API, enabling file operations, repository management, search functionality, and more.
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|
||||||
|
|
@ -8,6 +8,7 @@ MCP Server for the GitHub API, enabling file operations, repository management,
|
||||||
- **Comprehensive Error Handling**: Clear error messages for common issues
|
- **Comprehensive Error Handling**: Clear error messages for common issues
|
||||||
- **Git History Preservation**: Operations maintain proper Git history without force pushing
|
- **Git History Preservation**: Operations maintain proper Git history without force pushing
|
||||||
- **Batch Operations**: Support for both single-file and multi-file operations
|
- **Batch Operations**: Support for both single-file and multi-file operations
|
||||||
|
- **Advanced Search**: Support for searching code, issues/PRs, and users
|
||||||
|
|
||||||
|
|
||||||
## Tools
|
## Tools
|
||||||
|
|
@ -102,6 +103,115 @@ MCP Server for the GitHub API, enabling file operations, repository management,
|
||||||
- `from_branch` (optional string): Source branch (defaults to repo default)
|
- `from_branch` (optional string): Source branch (defaults to repo default)
|
||||||
- Returns: Created branch reference
|
- Returns: Created branch reference
|
||||||
|
|
||||||
|
10. `list_issues`
|
||||||
|
- List and filter repository issues
|
||||||
|
- Inputs:
|
||||||
|
- `owner` (string): Repository owner
|
||||||
|
- `repo` (string): Repository name
|
||||||
|
- `state` (optional string): Filter by state ('open', 'closed', 'all')
|
||||||
|
- `labels` (optional string[]): Filter by labels
|
||||||
|
- `sort` (optional string): Sort by ('created', 'updated', 'comments')
|
||||||
|
- `direction` (optional string): Sort direction ('asc', 'desc')
|
||||||
|
- `since` (optional string): Filter by date (ISO 8601 timestamp)
|
||||||
|
- `page` (optional number): Page number
|
||||||
|
- `per_page` (optional number): Results per page
|
||||||
|
- Returns: Array of issue details
|
||||||
|
|
||||||
|
11. `update_issue`
|
||||||
|
- Update an existing issue
|
||||||
|
- Inputs:
|
||||||
|
- `owner` (string): Repository owner
|
||||||
|
- `repo` (string): Repository name
|
||||||
|
- `issue_number` (number): Issue number to update
|
||||||
|
- `title` (optional string): New title
|
||||||
|
- `body` (optional string): New description
|
||||||
|
- `state` (optional string): New state ('open' or 'closed')
|
||||||
|
- `labels` (optional string[]): New labels
|
||||||
|
- `assignees` (optional string[]): New assignees
|
||||||
|
- `milestone` (optional number): New milestone number
|
||||||
|
- Returns: Updated issue details
|
||||||
|
|
||||||
|
12. `add_issue_comment`
|
||||||
|
- Add a comment to an issue
|
||||||
|
- Inputs:
|
||||||
|
- `owner` (string): Repository owner
|
||||||
|
- `repo` (string): Repository name
|
||||||
|
- `issue_number` (number): Issue number to comment on
|
||||||
|
- `body` (string): Comment text
|
||||||
|
- Returns: Created comment details
|
||||||
|
|
||||||
|
13. `search_code`
|
||||||
|
- Search for code across GitHub repositories
|
||||||
|
- Inputs:
|
||||||
|
- `q` (string): Search query using GitHub code search syntax
|
||||||
|
- `sort` (optional string): Sort field ('indexed' only)
|
||||||
|
- `order` (optional string): Sort order ('asc' or 'desc')
|
||||||
|
- `per_page` (optional number): Results per page (max 100)
|
||||||
|
- `page` (optional number): Page number
|
||||||
|
- Returns: Code search results with repository context
|
||||||
|
|
||||||
|
14. `search_issues`
|
||||||
|
- Search for issues and pull requests
|
||||||
|
- Inputs:
|
||||||
|
- `q` (string): Search query using GitHub issues search syntax
|
||||||
|
- `sort` (optional string): Sort field (comments, reactions, created, etc.)
|
||||||
|
- `order` (optional string): Sort order ('asc' or 'desc')
|
||||||
|
- `per_page` (optional number): Results per page (max 100)
|
||||||
|
- `page` (optional number): Page number
|
||||||
|
- Returns: Issue and pull request search results
|
||||||
|
|
||||||
|
15. `search_users`
|
||||||
|
- Search for GitHub users
|
||||||
|
- Inputs:
|
||||||
|
- `q` (string): Search query using GitHub users search syntax
|
||||||
|
- `sort` (optional string): Sort field (followers, repositories, joined)
|
||||||
|
- `order` (optional string): Sort order ('asc' or 'desc')
|
||||||
|
- `per_page` (optional number): Results per page (max 100)
|
||||||
|
- `page` (optional number): Page number
|
||||||
|
- Returns: User search results
|
||||||
|
|
||||||
|
16. `list_commits`
|
||||||
|
- Gets commits of a branch in a repository
|
||||||
|
- Inputs:
|
||||||
|
- `owner` (string): Repository owner
|
||||||
|
- `repo` (string): Repository name
|
||||||
|
- `page` (optional string): page number
|
||||||
|
- `per_page` (optional string): number of record per page
|
||||||
|
- `sha` (optional string): branch name
|
||||||
|
- Returns: List of commits
|
||||||
|
|
||||||
|
17. `get_issue`
|
||||||
|
- Gets the contents of an issue within a repository
|
||||||
|
- Inputs:
|
||||||
|
- `owner` (string): Repository owner
|
||||||
|
- `repo` (string): Repository name
|
||||||
|
- `issue_number` (number): Issue number to retrieve
|
||||||
|
- Returns: Github Issue object & details
|
||||||
|
|
||||||
|
## Search Query Syntax
|
||||||
|
|
||||||
|
### Code Search
|
||||||
|
- `language:javascript`: Search by programming language
|
||||||
|
- `repo:owner/name`: Search in specific repository
|
||||||
|
- `path:app/src`: Search in specific path
|
||||||
|
- `extension:js`: Search by file extension
|
||||||
|
- Example: `q: "import express" language:typescript path:src/`
|
||||||
|
|
||||||
|
### Issues Search
|
||||||
|
- `is:issue` or `is:pr`: Filter by type
|
||||||
|
- `is:open` or `is:closed`: Filter by state
|
||||||
|
- `label:bug`: Search by label
|
||||||
|
- `author:username`: Search by author
|
||||||
|
- Example: `q: "memory leak" is:issue is:open label:bug`
|
||||||
|
|
||||||
|
### Users Search
|
||||||
|
- `type:user` or `type:org`: Filter by account type
|
||||||
|
- `followers:>1000`: Filter by followers
|
||||||
|
- `location:London`: Search by location
|
||||||
|
- Example: `q: "fullstack developer" location:London followers:>100`
|
||||||
|
|
||||||
|
For detailed search syntax, see [GitHub's searching documentation](https://docs.github.com/en/search-github/searching-on-github).
|
||||||
|
|
||||||
## Setup
|
## Setup
|
||||||
|
|
||||||
### Personal Access Token
|
### Personal Access Token
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
|
|
||||||
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
||||||
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
||||||
import {
|
import {
|
||||||
|
|
@ -7,53 +6,72 @@ import {
|
||||||
ListToolsRequestSchema,
|
ListToolsRequestSchema,
|
||||||
} from "@modelcontextprotocol/sdk/types.js";
|
} from "@modelcontextprotocol/sdk/types.js";
|
||||||
import fetch from "node-fetch";
|
import fetch from "node-fetch";
|
||||||
import {
|
|
||||||
GitHubForkSchema,
|
|
||||||
GitHubReferenceSchema,
|
|
||||||
GitHubRepositorySchema,
|
|
||||||
GitHubIssueSchema,
|
|
||||||
GitHubPullRequestSchema,
|
|
||||||
GitHubContentSchema,
|
|
||||||
GitHubCreateUpdateFileResponseSchema,
|
|
||||||
GitHubSearchResponseSchema,
|
|
||||||
GitHubTreeSchema,
|
|
||||||
GitHubCommitSchema,
|
|
||||||
CreateRepositoryOptionsSchema,
|
|
||||||
CreateIssueOptionsSchema,
|
|
||||||
CreatePullRequestOptionsSchema,
|
|
||||||
CreateBranchOptionsSchema,
|
|
||||||
type GitHubFork,
|
|
||||||
type GitHubReference,
|
|
||||||
type GitHubRepository,
|
|
||||||
type GitHubIssue,
|
|
||||||
type GitHubPullRequest,
|
|
||||||
type GitHubContent,
|
|
||||||
type GitHubCreateUpdateFileResponse,
|
|
||||||
type GitHubSearchResponse,
|
|
||||||
type GitHubTree,
|
|
||||||
type GitHubCommit,
|
|
||||||
type FileOperation,
|
|
||||||
CreateOrUpdateFileSchema,
|
|
||||||
SearchRepositoriesSchema,
|
|
||||||
CreateRepositorySchema,
|
|
||||||
GetFileContentsSchema,
|
|
||||||
PushFilesSchema,
|
|
||||||
CreateIssueSchema,
|
|
||||||
CreatePullRequestSchema,
|
|
||||||
ForkRepositorySchema,
|
|
||||||
CreateBranchSchema
|
|
||||||
} from './schemas.js';
|
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import { zodToJsonSchema } from 'zod-to-json-schema';
|
import { zodToJsonSchema } from 'zod-to-json-schema';
|
||||||
|
import {
|
||||||
|
CreateBranchOptionsSchema,
|
||||||
|
CreateBranchSchema,
|
||||||
|
CreateIssueOptionsSchema,
|
||||||
|
CreateIssueSchema,
|
||||||
|
CreateOrUpdateFileSchema,
|
||||||
|
CreatePullRequestOptionsSchema,
|
||||||
|
CreatePullRequestSchema,
|
||||||
|
CreateRepositoryOptionsSchema,
|
||||||
|
CreateRepositorySchema,
|
||||||
|
ForkRepositorySchema,
|
||||||
|
GetFileContentsSchema,
|
||||||
|
GetIssueSchema,
|
||||||
|
GitHubCommitSchema,
|
||||||
|
GitHubContentSchema,
|
||||||
|
GitHubCreateUpdateFileResponseSchema,
|
||||||
|
GitHubForkSchema,
|
||||||
|
GitHubIssueSchema,
|
||||||
|
GitHubListCommits,
|
||||||
|
GitHubListCommitsSchema,
|
||||||
|
GitHubPullRequestSchema,
|
||||||
|
GitHubReferenceSchema,
|
||||||
|
GitHubRepositorySchema,
|
||||||
|
GitHubSearchResponseSchema,
|
||||||
|
GitHubTreeSchema,
|
||||||
|
IssueCommentSchema,
|
||||||
|
ListCommitsSchema,
|
||||||
|
ListIssuesOptionsSchema,
|
||||||
|
PushFilesSchema,
|
||||||
|
SearchCodeResponseSchema,
|
||||||
|
SearchCodeSchema,
|
||||||
|
SearchIssuesResponseSchema,
|
||||||
|
SearchIssuesSchema,
|
||||||
|
SearchRepositoriesSchema,
|
||||||
|
SearchUsersResponseSchema,
|
||||||
|
SearchUsersSchema,
|
||||||
|
UpdateIssueOptionsSchema,
|
||||||
|
type FileOperation,
|
||||||
|
type GitHubCommit,
|
||||||
|
type GitHubContent,
|
||||||
|
type GitHubCreateUpdateFileResponse,
|
||||||
|
type GitHubFork,
|
||||||
|
type GitHubIssue,
|
||||||
|
type GitHubPullRequest,
|
||||||
|
type GitHubReference,
|
||||||
|
type GitHubRepository,
|
||||||
|
type GitHubSearchResponse,
|
||||||
|
type GitHubTree,
|
||||||
|
type SearchCodeResponse,
|
||||||
|
type SearchIssuesResponse,
|
||||||
|
type SearchUsersResponse
|
||||||
|
} from './schemas.js';
|
||||||
|
|
||||||
const server = new Server({
|
const server = new Server(
|
||||||
name: "github-mcp-server",
|
{
|
||||||
version: "0.1.0",
|
name: "github-mcp-server",
|
||||||
}, {
|
version: "0.1.0",
|
||||||
capabilities: {
|
},
|
||||||
tools: {}
|
{
|
||||||
|
capabilities: {
|
||||||
|
tools: {},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
});
|
);
|
||||||
|
|
||||||
const GITHUB_PERSONAL_ACCESS_TOKEN = process.env.GITHUB_PERSONAL_ACCESS_TOKEN;
|
const GITHUB_PERSONAL_ACCESS_TOKEN = process.env.GITHUB_PERSONAL_ACCESS_TOKEN;
|
||||||
|
|
||||||
|
|
@ -74,10 +92,10 @@ async function forkRepository(
|
||||||
const response = await fetch(url, {
|
const response = await fetch(url, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Authorization": `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`,
|
Authorization: `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`,
|
||||||
"Accept": "application/vnd.github.v3+json",
|
Accept: "application/vnd.github.v3+json",
|
||||||
"User-Agent": "github-mcp-server"
|
"User-Agent": "github-mcp-server",
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
|
|
@ -99,15 +117,15 @@ async function createBranch(
|
||||||
{
|
{
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Authorization": `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`,
|
Authorization: `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`,
|
||||||
"Accept": "application/vnd.github.v3+json",
|
Accept: "application/vnd.github.v3+json",
|
||||||
"User-Agent": "github-mcp-server",
|
"User-Agent": "github-mcp-server",
|
||||||
"Content-Type": "application/json"
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
ref: fullRef,
|
ref: fullRef,
|
||||||
sha: options.sha
|
sha: options.sha,
|
||||||
})
|
}),
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -126,10 +144,10 @@ async function getDefaultBranchSHA(
|
||||||
`https://api.github.com/repos/${owner}/${repo}/git/refs/heads/main`,
|
`https://api.github.com/repos/${owner}/${repo}/git/refs/heads/main`,
|
||||||
{
|
{
|
||||||
headers: {
|
headers: {
|
||||||
"Authorization": `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`,
|
Authorization: `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`,
|
||||||
"Accept": "application/vnd.github.v3+json",
|
Accept: "application/vnd.github.v3+json",
|
||||||
"User-Agent": "github-mcp-server"
|
"User-Agent": "github-mcp-server",
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -138,15 +156,17 @@ async function getDefaultBranchSHA(
|
||||||
`https://api.github.com/repos/${owner}/${repo}/git/refs/heads/master`,
|
`https://api.github.com/repos/${owner}/${repo}/git/refs/heads/master`,
|
||||||
{
|
{
|
||||||
headers: {
|
headers: {
|
||||||
"Authorization": `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`,
|
Authorization: `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`,
|
||||||
"Accept": "application/vnd.github.v3+json",
|
Accept: "application/vnd.github.v3+json",
|
||||||
"User-Agent": "github-mcp-server"
|
"User-Agent": "github-mcp-server",
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!masterResponse.ok) {
|
if (!masterResponse.ok) {
|
||||||
throw new Error("Could not find default branch (tried 'main' and 'master')");
|
throw new Error(
|
||||||
|
"Could not find default branch (tried 'main' and 'master')"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = GitHubReferenceSchema.parse(await masterResponse.json());
|
const data = GitHubReferenceSchema.parse(await masterResponse.json());
|
||||||
|
|
@ -170,10 +190,10 @@ async function getFileContents(
|
||||||
|
|
||||||
const response = await fetch(url, {
|
const response = await fetch(url, {
|
||||||
headers: {
|
headers: {
|
||||||
"Authorization": `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`,
|
Authorization: `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`,
|
||||||
"Accept": "application/vnd.github.v3+json",
|
Accept: "application/vnd.github.v3+json",
|
||||||
"User-Agent": "github-mcp-server"
|
"User-Agent": "github-mcp-server",
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
|
|
@ -184,7 +204,7 @@ async function getFileContents(
|
||||||
|
|
||||||
// If it's a file, decode the content
|
// If it's a file, decode the content
|
||||||
if (!Array.isArray(data) && data.content) {
|
if (!Array.isArray(data) && data.content) {
|
||||||
data.content = Buffer.from(data.content, 'base64').toString('utf8');
|
data.content = Buffer.from(data.content, "base64").toString("utf8");
|
||||||
}
|
}
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
|
|
@ -200,12 +220,12 @@ async function createIssue(
|
||||||
{
|
{
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Authorization": `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`,
|
Authorization: `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`,
|
||||||
"Accept": "application/vnd.github.v3+json",
|
Accept: "application/vnd.github.v3+json",
|
||||||
"User-Agent": "github-mcp-server",
|
"User-Agent": "github-mcp-server",
|
||||||
"Content-Type": "application/json"
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
body: JSON.stringify(options)
|
body: JSON.stringify(options),
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -226,12 +246,12 @@ async function createPullRequest(
|
||||||
{
|
{
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Authorization": `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`,
|
Authorization: `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`,
|
||||||
"Accept": "application/vnd.github.v3+json",
|
Accept: "application/vnd.github.v3+json",
|
||||||
"User-Agent": "github-mcp-server",
|
"User-Agent": "github-mcp-server",
|
||||||
"Content-Type": "application/json"
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
body: JSON.stringify(options)
|
body: JSON.stringify(options),
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -251,7 +271,7 @@ async function createOrUpdateFile(
|
||||||
branch: string,
|
branch: string,
|
||||||
sha?: string
|
sha?: string
|
||||||
): Promise<GitHubCreateUpdateFileResponse> {
|
): Promise<GitHubCreateUpdateFileResponse> {
|
||||||
const encodedContent = Buffer.from(content).toString('base64');
|
const encodedContent = Buffer.from(content).toString("base64");
|
||||||
|
|
||||||
let currentSha = sha;
|
let currentSha = sha;
|
||||||
if (!currentSha) {
|
if (!currentSha) {
|
||||||
|
|
@ -261,7 +281,9 @@ async function createOrUpdateFile(
|
||||||
currentSha = existingFile.sha;
|
currentSha = existingFile.sha;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Note: File does not exist in branch, will create new file');
|
console.error(
|
||||||
|
"Note: File does not exist in branch, will create new file"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -271,18 +293,18 @@ async function createOrUpdateFile(
|
||||||
message,
|
message,
|
||||||
content: encodedContent,
|
content: encodedContent,
|
||||||
branch,
|
branch,
|
||||||
...(currentSha ? { sha: currentSha } : {})
|
...(currentSha ? { sha: currentSha } : {}),
|
||||||
};
|
};
|
||||||
|
|
||||||
const response = await fetch(url, {
|
const response = await fetch(url, {
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
headers: {
|
headers: {
|
||||||
"Authorization": `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`,
|
Authorization: `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`,
|
||||||
"Accept": "application/vnd.github.v3+json",
|
Accept: "application/vnd.github.v3+json",
|
||||||
"User-Agent": "github-mcp-server",
|
"User-Agent": "github-mcp-server",
|
||||||
"Content-Type": "application/json"
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
body: JSON.stringify(body)
|
body: JSON.stringify(body),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
|
|
@ -298,11 +320,11 @@ async function createTree(
|
||||||
files: FileOperation[],
|
files: FileOperation[],
|
||||||
baseTree?: string
|
baseTree?: string
|
||||||
): Promise<GitHubTree> {
|
): Promise<GitHubTree> {
|
||||||
const tree = files.map(file => ({
|
const tree = files.map((file) => ({
|
||||||
path: file.path,
|
path: file.path,
|
||||||
mode: '100644' as const,
|
mode: "100644" as const,
|
||||||
type: 'blob' as const,
|
type: "blob" as const,
|
||||||
content: file.content
|
content: file.content,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
|
|
@ -310,15 +332,15 @@ async function createTree(
|
||||||
{
|
{
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Authorization": `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`,
|
Authorization: `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`,
|
||||||
"Accept": "application/vnd.github.v3+json",
|
Accept: "application/vnd.github.v3+json",
|
||||||
"User-Agent": "github-mcp-server",
|
"User-Agent": "github-mcp-server",
|
||||||
"Content-Type": "application/json"
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
tree,
|
tree,
|
||||||
base_tree: baseTree
|
base_tree: baseTree,
|
||||||
})
|
}),
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -341,16 +363,16 @@ async function createCommit(
|
||||||
{
|
{
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Authorization": `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`,
|
Authorization: `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`,
|
||||||
"Accept": "application/vnd.github.v3+json",
|
Accept: "application/vnd.github.v3+json",
|
||||||
"User-Agent": "github-mcp-server",
|
"User-Agent": "github-mcp-server",
|
||||||
"Content-Type": "application/json"
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
message,
|
message,
|
||||||
tree,
|
tree,
|
||||||
parents
|
parents,
|
||||||
})
|
}),
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -372,15 +394,15 @@ async function updateReference(
|
||||||
{
|
{
|
||||||
method: "PATCH",
|
method: "PATCH",
|
||||||
headers: {
|
headers: {
|
||||||
"Authorization": `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`,
|
Authorization: `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`,
|
||||||
"Accept": "application/vnd.github.v3+json",
|
Accept: "application/vnd.github.v3+json",
|
||||||
"User-Agent": "github-mcp-server",
|
"User-Agent": "github-mcp-server",
|
||||||
"Content-Type": "application/json"
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
sha,
|
sha,
|
||||||
force: true
|
force: true,
|
||||||
})
|
}),
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -402,10 +424,10 @@ async function pushFiles(
|
||||||
`https://api.github.com/repos/${owner}/${repo}/git/refs/heads/${branch}`,
|
`https://api.github.com/repos/${owner}/${repo}/git/refs/heads/${branch}`,
|
||||||
{
|
{
|
||||||
headers: {
|
headers: {
|
||||||
"Authorization": `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`,
|
Authorization: `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`,
|
||||||
"Accept": "application/vnd.github.v3+json",
|
Accept: "application/vnd.github.v3+json",
|
||||||
"User-Agent": "github-mcp-server"
|
"User-Agent": "github-mcp-server",
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -417,7 +439,9 @@ async function pushFiles(
|
||||||
const commitSha = ref.object.sha;
|
const commitSha = ref.object.sha;
|
||||||
|
|
||||||
const tree = await createTree(owner, repo, files, commitSha);
|
const tree = await createTree(owner, repo, files, commitSha);
|
||||||
const commit = await createCommit(owner, repo, message, tree.sha, [commitSha]);
|
const commit = await createCommit(owner, repo, message, tree.sha, [
|
||||||
|
commitSha,
|
||||||
|
]);
|
||||||
return await updateReference(owner, repo, `heads/${branch}`, commit.sha);
|
return await updateReference(owner, repo, `heads/${branch}`, commit.sha);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -433,10 +457,10 @@ async function searchRepositories(
|
||||||
|
|
||||||
const response = await fetch(url.toString(), {
|
const response = await fetch(url.toString(), {
|
||||||
headers: {
|
headers: {
|
||||||
"Authorization": `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`,
|
Authorization: `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`,
|
||||||
"Accept": "application/vnd.github.v3+json",
|
Accept: "application/vnd.github.v3+json",
|
||||||
"User-Agent": "github-mcp-server"
|
"User-Agent": "github-mcp-server",
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
|
|
@ -452,12 +476,12 @@ async function createRepository(
|
||||||
const response = await fetch("https://api.github.com/user/repos", {
|
const response = await fetch("https://api.github.com/user/repos", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Authorization": `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`,
|
Authorization: `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`,
|
||||||
"Accept": "application/vnd.github.v3+json",
|
Accept: "application/vnd.github.v3+json",
|
||||||
"User-Agent": "github-mcp-server",
|
"User-Agent": "github-mcp-server",
|
||||||
"Content-Type": "application/json"
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
body: JSON.stringify(options)
|
body: JSON.stringify(options),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
|
|
@ -467,55 +491,323 @@ async function createRepository(
|
||||||
return GitHubRepositorySchema.parse(await response.json());
|
return GitHubRepositorySchema.parse(await response.json());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function listCommits(
|
||||||
|
owner: string,
|
||||||
|
repo: string,
|
||||||
|
page: number = 1,
|
||||||
|
perPage: number = 30,
|
||||||
|
sha?: string,
|
||||||
|
): Promise<GitHubListCommits> {
|
||||||
|
const url = new URL(`https://api.github.com/repos/${owner}/${repo}/commits`);
|
||||||
|
url.searchParams.append("page", page.toString());
|
||||||
|
url.searchParams.append("per_page", perPage.toString());
|
||||||
|
if (sha) {
|
||||||
|
url.searchParams.append("sha", sha);
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetch(
|
||||||
|
url.toString(),
|
||||||
|
{
|
||||||
|
method: "GET",
|
||||||
|
headers: {
|
||||||
|
"Authorization": `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`,
|
||||||
|
"Accept": "application/vnd.github.v3+json",
|
||||||
|
"User-Agent": "github-mcp-server",
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`GitHub API error: ${response.statusText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return GitHubListCommitsSchema.parse(await response.json());
|
||||||
|
}
|
||||||
|
|
||||||
|
async function listIssues(
|
||||||
|
owner: string,
|
||||||
|
repo: string,
|
||||||
|
options: Omit<z.infer<typeof ListIssuesOptionsSchema>, 'owner' | 'repo'>
|
||||||
|
): Promise<GitHubIssue[]> {
|
||||||
|
const url = new URL(`https://api.github.com/repos/${owner}/${repo}/issues`);
|
||||||
|
|
||||||
|
// Add query parameters
|
||||||
|
if (options.state) url.searchParams.append('state', options.state);
|
||||||
|
if (options.labels) url.searchParams.append('labels', options.labels.join(','));
|
||||||
|
if (options.sort) url.searchParams.append('sort', options.sort);
|
||||||
|
if (options.direction) url.searchParams.append('direction', options.direction);
|
||||||
|
if (options.since) url.searchParams.append('since', options.since);
|
||||||
|
if (options.page) url.searchParams.append('page', options.page.toString());
|
||||||
|
if (options.per_page) url.searchParams.append('per_page', options.per_page.toString());
|
||||||
|
|
||||||
|
const response = await fetch(url.toString(), {
|
||||||
|
headers: {
|
||||||
|
"Authorization": `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`,
|
||||||
|
"Accept": "application/vnd.github.v3+json",
|
||||||
|
"User-Agent": "github-mcp-server"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`GitHub API error: ${response.statusText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return z.array(GitHubIssueSchema).parse(await response.json());
|
||||||
|
}
|
||||||
|
|
||||||
|
async function updateIssue(
|
||||||
|
owner: string,
|
||||||
|
repo: string,
|
||||||
|
issueNumber: number,
|
||||||
|
options: Omit<z.infer<typeof UpdateIssueOptionsSchema>, 'owner' | 'repo' | 'issue_number'>
|
||||||
|
): Promise<GitHubIssue> {
|
||||||
|
const response = await fetch(
|
||||||
|
`https://api.github.com/repos/${owner}/${repo}/issues/${issueNumber}`,
|
||||||
|
{
|
||||||
|
method: "PATCH",
|
||||||
|
headers: {
|
||||||
|
"Authorization": `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`,
|
||||||
|
"Accept": "application/vnd.github.v3+json",
|
||||||
|
"User-Agent": "github-mcp-server",
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
title: options.title,
|
||||||
|
body: options.body,
|
||||||
|
state: options.state,
|
||||||
|
labels: options.labels,
|
||||||
|
assignees: options.assignees,
|
||||||
|
milestone: options.milestone
|
||||||
|
})
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`GitHub API error: ${response.statusText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return GitHubIssueSchema.parse(await response.json());
|
||||||
|
}
|
||||||
|
|
||||||
|
async function addIssueComment(
|
||||||
|
owner: string,
|
||||||
|
repo: string,
|
||||||
|
issueNumber: number,
|
||||||
|
body: string
|
||||||
|
): Promise<z.infer<typeof IssueCommentSchema>> {
|
||||||
|
const response = await fetch(
|
||||||
|
`https://api.github.com/repos/${owner}/${repo}/issues/${issueNumber}/comments`,
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Authorization": `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`,
|
||||||
|
"Accept": "application/vnd.github.v3+json",
|
||||||
|
"User-Agent": "github-mcp-server",
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ body })
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`GitHub API error: ${response.statusText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return IssueCommentSchema.parse(await response.json());
|
||||||
|
}
|
||||||
|
|
||||||
|
async function searchCode(
|
||||||
|
params: z.infer<typeof SearchCodeSchema>
|
||||||
|
): Promise<SearchCodeResponse> {
|
||||||
|
const url = new URL("https://api.github.com/search/code");
|
||||||
|
Object.entries(params).forEach(([key, value]) => {
|
||||||
|
if (value !== undefined && value !== null) {
|
||||||
|
url.searchParams.append(key, value.toString());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = await fetch(url.toString(), {
|
||||||
|
headers: {
|
||||||
|
Authorization: `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`,
|
||||||
|
Accept: "application/vnd.github.v3+json",
|
||||||
|
"User-Agent": "github-mcp-server",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`GitHub API error: ${response.statusText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return SearchCodeResponseSchema.parse(await response.json());
|
||||||
|
}
|
||||||
|
|
||||||
|
async function searchIssues(
|
||||||
|
params: z.infer<typeof SearchIssuesSchema>
|
||||||
|
): Promise<SearchIssuesResponse> {
|
||||||
|
const url = new URL("https://api.github.com/search/issues");
|
||||||
|
Object.entries(params).forEach(([key, value]) => {
|
||||||
|
if (value !== undefined && value !== null) {
|
||||||
|
url.searchParams.append(key, value.toString());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = await fetch(url.toString(), {
|
||||||
|
headers: {
|
||||||
|
Authorization: `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`,
|
||||||
|
Accept: "application/vnd.github.v3+json",
|
||||||
|
"User-Agent": "github-mcp-server",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`GitHub API error: ${response.statusText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return SearchIssuesResponseSchema.parse(await response.json());
|
||||||
|
}
|
||||||
|
|
||||||
|
async function searchUsers(
|
||||||
|
params: z.infer<typeof SearchUsersSchema>
|
||||||
|
): Promise<SearchUsersResponse> {
|
||||||
|
const url = new URL("https://api.github.com/search/users");
|
||||||
|
Object.entries(params).forEach(([key, value]) => {
|
||||||
|
if (value !== undefined && value !== null) {
|
||||||
|
url.searchParams.append(key, value.toString());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = await fetch(url.toString(), {
|
||||||
|
headers: {
|
||||||
|
Authorization: `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`,
|
||||||
|
Accept: "application/vnd.github.v3+json",
|
||||||
|
"User-Agent": "github-mcp-server",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`GitHub API error: ${response.statusText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return SearchUsersResponseSchema.parse(await response.json());
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getIssue(
|
||||||
|
owner: string,
|
||||||
|
repo: string,
|
||||||
|
issueNumber: number
|
||||||
|
): Promise<GitHubIssue> {
|
||||||
|
const response = await fetch(
|
||||||
|
`https://api.github.com/repos/${owner}/${repo}/issues/${issueNumber}`,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`,
|
||||||
|
Accept: "application/vnd.github.v3+json",
|
||||||
|
"User-Agent": "github-mcp-server",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Github API error: ${response.statusText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return GitHubIssueSchema.parse(await response.json());
|
||||||
|
}
|
||||||
|
|
||||||
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
||||||
return {
|
return {
|
||||||
tools: [
|
tools: [
|
||||||
{
|
{
|
||||||
name: "create_or_update_file",
|
name: "create_or_update_file",
|
||||||
description: "Create or update a single file in a GitHub repository",
|
description: "Create or update a single file in a GitHub repository",
|
||||||
inputSchema: zodToJsonSchema(CreateOrUpdateFileSchema)
|
inputSchema: zodToJsonSchema(CreateOrUpdateFileSchema),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "search_repositories",
|
name: "search_repositories",
|
||||||
description: "Search for GitHub repositories",
|
description: "Search for GitHub repositories",
|
||||||
inputSchema: zodToJsonSchema(SearchRepositoriesSchema)
|
inputSchema: zodToJsonSchema(SearchRepositoriesSchema),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "create_repository",
|
name: "create_repository",
|
||||||
description: "Create a new GitHub repository in your account",
|
description: "Create a new GitHub repository in your account",
|
||||||
inputSchema: zodToJsonSchema(CreateRepositorySchema)
|
inputSchema: zodToJsonSchema(CreateRepositorySchema),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "get_file_contents",
|
name: "get_file_contents",
|
||||||
description: "Get the contents of a file or directory from a GitHub repository",
|
description:
|
||||||
inputSchema: zodToJsonSchema(GetFileContentsSchema)
|
"Get the contents of a file or directory from a GitHub repository",
|
||||||
|
inputSchema: zodToJsonSchema(GetFileContentsSchema),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "push_files",
|
name: "push_files",
|
||||||
description: "Push multiple files to a GitHub repository in a single commit",
|
description:
|
||||||
inputSchema: zodToJsonSchema(PushFilesSchema)
|
"Push multiple files to a GitHub repository in a single commit",
|
||||||
|
inputSchema: zodToJsonSchema(PushFilesSchema),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "create_issue",
|
name: "create_issue",
|
||||||
description: "Create a new issue in a GitHub repository",
|
description: "Create a new issue in a GitHub repository",
|
||||||
inputSchema: zodToJsonSchema(CreateIssueSchema)
|
inputSchema: zodToJsonSchema(CreateIssueSchema),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "create_pull_request",
|
name: "create_pull_request",
|
||||||
description: "Create a new pull request in a GitHub repository",
|
description: "Create a new pull request in a GitHub repository",
|
||||||
inputSchema: zodToJsonSchema(CreatePullRequestSchema)
|
inputSchema: zodToJsonSchema(CreatePullRequestSchema),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "fork_repository",
|
name: "fork_repository",
|
||||||
description: "Fork a GitHub repository to your account or specified organization",
|
description:
|
||||||
inputSchema: zodToJsonSchema(ForkRepositorySchema)
|
"Fork a GitHub repository to your account or specified organization",
|
||||||
|
inputSchema: zodToJsonSchema(ForkRepositorySchema),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "create_branch",
|
name: "create_branch",
|
||||||
description: "Create a new branch in a GitHub repository",
|
description: "Create a new branch in a GitHub repository",
|
||||||
inputSchema: zodToJsonSchema(CreateBranchSchema)
|
inputSchema: zodToJsonSchema(CreateBranchSchema),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "list_commits",
|
||||||
|
description: "Get list of commits of a branch in a GitHub repository",
|
||||||
|
inputSchema: zodToJsonSchema(ListCommitsSchema)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "list_issues",
|
||||||
|
description: "List issues in a GitHub repository with filtering options",
|
||||||
|
inputSchema: zodToJsonSchema(ListIssuesOptionsSchema)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "update_issue",
|
||||||
|
description: "Update an existing issue in a GitHub repository",
|
||||||
|
inputSchema: zodToJsonSchema(UpdateIssueOptionsSchema)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "add_issue_comment",
|
||||||
|
description: "Add a comment to an existing issue",
|
||||||
|
inputSchema: zodToJsonSchema(IssueCommentSchema)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "search_code",
|
||||||
|
description: "Search for code across GitHub repositories",
|
||||||
|
inputSchema: zodToJsonSchema(SearchCodeSchema),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "search_issues",
|
||||||
|
description:
|
||||||
|
"Search for issues and pull requests across GitHub repositories",
|
||||||
|
inputSchema: zodToJsonSchema(SearchIssuesSchema),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "search_users",
|
||||||
|
description: "Search for users on GitHub",
|
||||||
|
inputSchema: zodToJsonSchema(SearchUsersSchema),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "get_issue",
|
||||||
|
description: "Get details of a specific issue in a GitHub repository.",
|
||||||
|
inputSchema: zodToJsonSchema(GetIssueSchema)
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -528,8 +820,14 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
||||||
switch (request.params.name) {
|
switch (request.params.name) {
|
||||||
case "fork_repository": {
|
case "fork_repository": {
|
||||||
const args = ForkRepositorySchema.parse(request.params.arguments);
|
const args = ForkRepositorySchema.parse(request.params.arguments);
|
||||||
const fork = await forkRepository(args.owner, args.repo, args.organization);
|
const fork = await forkRepository(
|
||||||
return { content: [{ type: "text", text: JSON.stringify(fork, null, 2) }] };
|
args.owner,
|
||||||
|
args.repo,
|
||||||
|
args.organization
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
content: [{ type: "text", text: JSON.stringify(fork, null, 2) }],
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
case "create_branch": {
|
case "create_branch": {
|
||||||
|
|
@ -540,10 +838,10 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
||||||
`https://api.github.com/repos/${args.owner}/${args.repo}/git/refs/heads/${args.from_branch}`,
|
`https://api.github.com/repos/${args.owner}/${args.repo}/git/refs/heads/${args.from_branch}`,
|
||||||
{
|
{
|
||||||
headers: {
|
headers: {
|
||||||
"Authorization": `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`,
|
Authorization: `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`,
|
||||||
"Accept": "application/vnd.github.v3+json",
|
Accept: "application/vnd.github.v3+json",
|
||||||
"User-Agent": "github-mcp-server"
|
"User-Agent": "github-mcp-server",
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -559,28 +857,47 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
||||||
|
|
||||||
const branch = await createBranch(args.owner, args.repo, {
|
const branch = await createBranch(args.owner, args.repo, {
|
||||||
ref: args.branch,
|
ref: args.branch,
|
||||||
sha
|
sha,
|
||||||
});
|
});
|
||||||
|
|
||||||
return { content: [{ type: "text", text: JSON.stringify(branch, null, 2) }] };
|
return {
|
||||||
|
content: [{ type: "text", text: JSON.stringify(branch, null, 2) }],
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
case "search_repositories": {
|
case "search_repositories": {
|
||||||
const args = SearchRepositoriesSchema.parse(request.params.arguments);
|
const args = SearchRepositoriesSchema.parse(request.params.arguments);
|
||||||
const results = await searchRepositories(args.query, args.page, args.perPage);
|
const results = await searchRepositories(
|
||||||
return { content: [{ type: "text", text: JSON.stringify(results, null, 2) }] };
|
args.query,
|
||||||
|
args.page,
|
||||||
|
args.perPage
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
content: [{ type: "text", text: JSON.stringify(results, null, 2) }],
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
case "create_repository": {
|
case "create_repository": {
|
||||||
const args = CreateRepositorySchema.parse(request.params.arguments);
|
const args = CreateRepositorySchema.parse(request.params.arguments);
|
||||||
const repository = await createRepository(args);
|
const repository = await createRepository(args);
|
||||||
return { content: [{ type: "text", text: JSON.stringify(repository, null, 2) }] };
|
return {
|
||||||
|
content: [
|
||||||
|
{ type: "text", text: JSON.stringify(repository, null, 2) },
|
||||||
|
],
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
case "get_file_contents": {
|
case "get_file_contents": {
|
||||||
const args = GetFileContentsSchema.parse(request.params.arguments);
|
const args = GetFileContentsSchema.parse(request.params.arguments);
|
||||||
const contents = await getFileContents(args.owner, args.repo, args.path, args.branch);
|
const contents = await getFileContents(
|
||||||
return { content: [{ type: "text", text: JSON.stringify(contents, null, 2) }] };
|
args.owner,
|
||||||
|
args.repo,
|
||||||
|
args.path,
|
||||||
|
args.branch
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
content: [{ type: "text", text: JSON.stringify(contents, null, 2) }],
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
case "create_or_update_file": {
|
case "create_or_update_file": {
|
||||||
|
|
@ -594,7 +911,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
||||||
args.branch,
|
args.branch,
|
||||||
args.sha
|
args.sha
|
||||||
);
|
);
|
||||||
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
return {
|
||||||
|
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
case "push_files": {
|
case "push_files": {
|
||||||
|
|
@ -606,21 +925,90 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
||||||
args.files,
|
args.files,
|
||||||
args.message
|
args.message
|
||||||
);
|
);
|
||||||
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
return {
|
||||||
|
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
case "create_issue": {
|
case "create_issue": {
|
||||||
const args = CreateIssueSchema.parse(request.params.arguments);
|
const args = CreateIssueSchema.parse(request.params.arguments);
|
||||||
const { owner, repo, ...options } = args;
|
const { owner, repo, ...options } = args;
|
||||||
const issue = await createIssue(owner, repo, options);
|
const issue = await createIssue(owner, repo, options);
|
||||||
return { content: [{ type: "text", text: JSON.stringify(issue, null, 2) }] };
|
return {
|
||||||
|
content: [{ type: "text", text: JSON.stringify(issue, null, 2) }],
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
case "create_pull_request": {
|
case "create_pull_request": {
|
||||||
const args = CreatePullRequestSchema.parse(request.params.arguments);
|
const args = CreatePullRequestSchema.parse(request.params.arguments);
|
||||||
const { owner, repo, ...options } = args;
|
const { owner, repo, ...options } = args;
|
||||||
const pullRequest = await createPullRequest(owner, repo, options);
|
const pullRequest = await createPullRequest(owner, repo, options);
|
||||||
return { content: [{ type: "text", text: JSON.stringify(pullRequest, null, 2) }] };
|
return {
|
||||||
|
content: [
|
||||||
|
{ type: "text", text: JSON.stringify(pullRequest, null, 2) },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
case "search_code": {
|
||||||
|
const args = SearchCodeSchema.parse(request.params.arguments);
|
||||||
|
const results = await searchCode(args);
|
||||||
|
return {
|
||||||
|
content: [{ type: "text", text: JSON.stringify(results, null, 2) }],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
case "search_issues": {
|
||||||
|
const args = SearchIssuesSchema.parse(request.params.arguments);
|
||||||
|
const results = await searchIssues(args);
|
||||||
|
return {
|
||||||
|
content: [{ type: "text", text: JSON.stringify(results, null, 2) }],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
case "search_users": {
|
||||||
|
const args = SearchUsersSchema.parse(request.params.arguments);
|
||||||
|
const results = await searchUsers(args);
|
||||||
|
return {
|
||||||
|
content: [{ type: "text", text: JSON.stringify(results, null, 2) }],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
case "list_issues": {
|
||||||
|
const args = ListIssuesOptionsSchema.parse(request.params.arguments);
|
||||||
|
const { owner, repo, ...options } = args;
|
||||||
|
const issues = await listIssues(owner, repo, options);
|
||||||
|
return { toolResult: issues };
|
||||||
|
}
|
||||||
|
|
||||||
|
case "update_issue": {
|
||||||
|
const args = UpdateIssueOptionsSchema.parse(request.params.arguments);
|
||||||
|
const { owner, repo, issue_number, ...options } = args;
|
||||||
|
const issue = await updateIssue(owner, repo, issue_number, options);
|
||||||
|
return { toolResult: issue };
|
||||||
|
}
|
||||||
|
|
||||||
|
case "add_issue_comment": {
|
||||||
|
const args = IssueCommentSchema.parse(request.params.arguments);
|
||||||
|
const { owner, repo, issue_number, body } = args;
|
||||||
|
const comment = await addIssueComment(owner, repo, issue_number, body);
|
||||||
|
return { toolResult: comment };
|
||||||
|
}
|
||||||
|
|
||||||
|
case "list_commits": {
|
||||||
|
const args = ListCommitsSchema.parse(request.params.arguments);
|
||||||
|
const results = await listCommits(args.owner, args.repo, args.page, args.perPage, args.sha);
|
||||||
|
return { content: [{ type: "text", text: JSON.stringify(results, null, 2) }] };
|
||||||
|
}
|
||||||
|
|
||||||
|
case "get_issue": {
|
||||||
|
const args = z.object({
|
||||||
|
owner: z.string(),
|
||||||
|
repo: z.string(),
|
||||||
|
issue_number: z.number()
|
||||||
|
}).parse(request.params.arguments);
|
||||||
|
const issue = await getIssue(args.owner, args.repo, args.issue_number);
|
||||||
|
return { toolResult: issue };
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
|
@ -628,7 +1016,14 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof z.ZodError) {
|
if (error instanceof z.ZodError) {
|
||||||
throw new Error(`Invalid arguments: ${error.errors.map(e => `${e.path.join('.')}: ${e.message}`).join(', ')}`);
|
throw new Error(
|
||||||
|
`Invalid arguments: ${error.errors
|
||||||
|
.map(
|
||||||
|
(e: z.ZodError["errors"][number]) =>
|
||||||
|
`${e.path.join(".")}: ${e.message}`
|
||||||
|
)
|
||||||
|
.join(", ")}`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,8 +20,10 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@modelcontextprotocol/sdk": "1.0.1",
|
"@modelcontextprotocol/sdk": "1.0.1",
|
||||||
|
"@types/node": "^20.11.0",
|
||||||
"@types/node-fetch": "^2.6.12",
|
"@types/node-fetch": "^2.6.12",
|
||||||
"node-fetch": "^3.3.2",
|
"node-fetch": "^3.3.2",
|
||||||
|
"zod": "^3.22.4",
|
||||||
"zod-to-json-schema": "^3.23.5"
|
"zod-to-json-schema": "^3.23.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
import { z } from 'zod';
|
import { z } from "zod";
|
||||||
|
|
||||||
// Base schemas for common types
|
// Base schemas for common types
|
||||||
export const GitHubAuthorSchema = z.object({
|
export const GitHubAuthorSchema = z.object({
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
email: z.string(),
|
email: z.string(),
|
||||||
date: z.string()
|
date: z.string(),
|
||||||
});
|
});
|
||||||
|
|
||||||
// Repository related schemas
|
// Repository related schemas
|
||||||
|
|
@ -15,7 +15,7 @@ export const GitHubOwnerSchema = z.object({
|
||||||
avatar_url: z.string(),
|
avatar_url: z.string(),
|
||||||
url: z.string(),
|
url: z.string(),
|
||||||
html_url: z.string(),
|
html_url: z.string(),
|
||||||
type: z.string()
|
type: z.string(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const GitHubRepositorySchema = z.object({
|
export const GitHubRepositorySchema = z.object({
|
||||||
|
|
@ -35,7 +35,7 @@ export const GitHubRepositorySchema = z.object({
|
||||||
git_url: z.string(),
|
git_url: z.string(),
|
||||||
ssh_url: z.string(),
|
ssh_url: z.string(),
|
||||||
clone_url: z.string(),
|
clone_url: z.string(),
|
||||||
default_branch: z.string()
|
default_branch: z.string(),
|
||||||
});
|
});
|
||||||
|
|
||||||
// File content schemas
|
// File content schemas
|
||||||
|
|
@ -50,7 +50,7 @@ export const GitHubFileContentSchema = z.object({
|
||||||
url: z.string(),
|
url: z.string(),
|
||||||
git_url: z.string(),
|
git_url: z.string(),
|
||||||
html_url: z.string(),
|
html_url: z.string(),
|
||||||
download_url: z.string()
|
download_url: z.string(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const GitHubDirectoryContentSchema = z.object({
|
export const GitHubDirectoryContentSchema = z.object({
|
||||||
|
|
@ -62,37 +62,56 @@ export const GitHubDirectoryContentSchema = z.object({
|
||||||
url: z.string(),
|
url: z.string(),
|
||||||
git_url: z.string(),
|
git_url: z.string(),
|
||||||
html_url: z.string(),
|
html_url: z.string(),
|
||||||
download_url: z.string().nullable()
|
download_url: z.string().nullable(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const GitHubContentSchema = z.union([
|
export const GitHubContentSchema = z.union([
|
||||||
GitHubFileContentSchema,
|
GitHubFileContentSchema,
|
||||||
z.array(GitHubDirectoryContentSchema)
|
z.array(GitHubDirectoryContentSchema),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Operation schemas
|
// Operation schemas
|
||||||
export const FileOperationSchema = z.object({
|
export const FileOperationSchema = z.object({
|
||||||
path: z.string(),
|
path: z.string(),
|
||||||
content: z.string()
|
content: z.string(),
|
||||||
});
|
});
|
||||||
|
|
||||||
// Tree and commit schemas
|
// Tree and commit schemas
|
||||||
export const GitHubTreeEntrySchema = z.object({
|
export const GitHubTreeEntrySchema = z.object({
|
||||||
path: z.string(),
|
path: z.string(),
|
||||||
mode: z.enum(['100644', '100755', '040000', '160000', '120000']),
|
mode: z.enum(["100644", "100755", "040000", "160000", "120000"]),
|
||||||
type: z.enum(['blob', 'tree', 'commit']),
|
type: z.enum(["blob", "tree", "commit"]),
|
||||||
size: z.number().optional(),
|
size: z.number().optional(),
|
||||||
sha: z.string(),
|
sha: z.string(),
|
||||||
url: z.string()
|
url: z.string(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const GitHubTreeSchema = z.object({
|
export const GitHubTreeSchema = z.object({
|
||||||
sha: z.string(),
|
sha: z.string(),
|
||||||
url: z.string(),
|
url: z.string(),
|
||||||
tree: z.array(GitHubTreeEntrySchema),
|
tree: z.array(GitHubTreeEntrySchema),
|
||||||
truncated: z.boolean()
|
truncated: z.boolean(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const GitHubListCommitsSchema = z.array(z.object({
|
||||||
|
sha: z.string(),
|
||||||
|
node_id: z.string(),
|
||||||
|
commit: z.object({
|
||||||
|
author: GitHubAuthorSchema,
|
||||||
|
committer: GitHubAuthorSchema,
|
||||||
|
message: z.string(),
|
||||||
|
tree: z.object({
|
||||||
|
sha: z.string(),
|
||||||
|
url: z.string()
|
||||||
|
}),
|
||||||
|
url: z.string(),
|
||||||
|
comment_count: z.number(),
|
||||||
|
}),
|
||||||
|
url: z.string(),
|
||||||
|
html_url: z.string(),
|
||||||
|
comments_url: z.string()
|
||||||
|
}));
|
||||||
|
|
||||||
export const GitHubCommitSchema = z.object({
|
export const GitHubCommitSchema = z.object({
|
||||||
sha: z.string(),
|
sha: z.string(),
|
||||||
node_id: z.string(),
|
node_id: z.string(),
|
||||||
|
|
@ -102,12 +121,14 @@ export const GitHubCommitSchema = z.object({
|
||||||
message: z.string(),
|
message: z.string(),
|
||||||
tree: z.object({
|
tree: z.object({
|
||||||
sha: z.string(),
|
sha: z.string(),
|
||||||
url: z.string()
|
url: z.string(),
|
||||||
}),
|
}),
|
||||||
parents: z.array(z.object({
|
parents: z.array(
|
||||||
sha: z.string(),
|
z.object({
|
||||||
url: z.string()
|
sha: z.string(),
|
||||||
}))
|
url: z.string(),
|
||||||
|
})
|
||||||
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
// Reference schema
|
// Reference schema
|
||||||
|
|
@ -118,8 +139,8 @@ export const GitHubReferenceSchema = z.object({
|
||||||
object: z.object({
|
object: z.object({
|
||||||
sha: z.string(),
|
sha: z.string(),
|
||||||
type: z.string(),
|
type: z.string(),
|
||||||
url: z.string()
|
url: z.string(),
|
||||||
})
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
// Input schemas for operations
|
// Input schemas for operations
|
||||||
|
|
@ -127,7 +148,7 @@ export const CreateRepositoryOptionsSchema = z.object({
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
description: z.string().optional(),
|
description: z.string().optional(),
|
||||||
private: z.boolean().optional(),
|
private: z.boolean().optional(),
|
||||||
auto_init: z.boolean().optional()
|
auto_init: z.boolean().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const CreateIssueOptionsSchema = z.object({
|
export const CreateIssueOptionsSchema = z.object({
|
||||||
|
|
@ -135,7 +156,7 @@ export const CreateIssueOptionsSchema = z.object({
|
||||||
body: z.string().optional(),
|
body: z.string().optional(),
|
||||||
assignees: z.array(z.string()).optional(),
|
assignees: z.array(z.string()).optional(),
|
||||||
milestone: z.number().optional(),
|
milestone: z.number().optional(),
|
||||||
labels: z.array(z.string()).optional()
|
labels: z.array(z.string()).optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const CreatePullRequestOptionsSchema = z.object({
|
export const CreatePullRequestOptionsSchema = z.object({
|
||||||
|
|
@ -144,12 +165,12 @@ export const CreatePullRequestOptionsSchema = z.object({
|
||||||
head: z.string(),
|
head: z.string(),
|
||||||
base: z.string(),
|
base: z.string(),
|
||||||
maintainer_can_modify: z.boolean().optional(),
|
maintainer_can_modify: z.boolean().optional(),
|
||||||
draft: z.boolean().optional()
|
draft: z.boolean().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const CreateBranchOptionsSchema = z.object({
|
export const CreateBranchOptionsSchema = z.object({
|
||||||
ref: z.string(),
|
ref: z.string(),
|
||||||
sha: z.string()
|
sha: z.string(),
|
||||||
});
|
});
|
||||||
|
|
||||||
// Response schemas for operations
|
// Response schemas for operations
|
||||||
|
|
@ -164,21 +185,23 @@ export const GitHubCreateUpdateFileResponseSchema = z.object({
|
||||||
committer: GitHubAuthorSchema,
|
committer: GitHubAuthorSchema,
|
||||||
message: z.string(),
|
message: z.string(),
|
||||||
tree: z.object({
|
tree: z.object({
|
||||||
sha: z.string(),
|
|
||||||
url: z.string()
|
|
||||||
}),
|
|
||||||
parents: z.array(z.object({
|
|
||||||
sha: z.string(),
|
sha: z.string(),
|
||||||
url: z.string(),
|
url: z.string(),
|
||||||
html_url: z.string()
|
}),
|
||||||
}))
|
parents: z.array(
|
||||||
})
|
z.object({
|
||||||
|
sha: z.string(),
|
||||||
|
url: z.string(),
|
||||||
|
html_url: z.string(),
|
||||||
|
})
|
||||||
|
),
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const GitHubSearchResponseSchema = z.object({
|
export const GitHubSearchResponseSchema = z.object({
|
||||||
total_count: z.number(),
|
total_count: z.number(),
|
||||||
incomplete_results: z.boolean(),
|
incomplete_results: z.boolean(),
|
||||||
items: z.array(GitHubRepositorySchema)
|
items: z.array(GitHubRepositorySchema),
|
||||||
});
|
});
|
||||||
|
|
||||||
// Fork related schemas
|
// Fork related schemas
|
||||||
|
|
@ -188,14 +211,14 @@ export const GitHubForkParentSchema = z.object({
|
||||||
owner: z.object({
|
owner: z.object({
|
||||||
login: z.string(),
|
login: z.string(),
|
||||||
id: z.number(),
|
id: z.number(),
|
||||||
avatar_url: z.string()
|
avatar_url: z.string(),
|
||||||
}),
|
}),
|
||||||
html_url: z.string()
|
html_url: z.string(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const GitHubForkSchema = GitHubRepositorySchema.extend({
|
export const GitHubForkSchema = GitHubRepositorySchema.extend({
|
||||||
parent: GitHubForkParentSchema,
|
parent: GitHubForkParentSchema,
|
||||||
source: GitHubForkParentSchema
|
source: GitHubForkParentSchema,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Issue related schemas
|
// Issue related schemas
|
||||||
|
|
@ -206,7 +229,7 @@ export const GitHubLabelSchema = z.object({
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
color: z.string(),
|
color: z.string(),
|
||||||
default: z.boolean(),
|
default: z.boolean(),
|
||||||
description: z.string().optional()
|
description: z.string().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const GitHubIssueAssigneeSchema = z.object({
|
export const GitHubIssueAssigneeSchema = z.object({
|
||||||
|
|
@ -214,7 +237,7 @@ export const GitHubIssueAssigneeSchema = z.object({
|
||||||
id: z.number(),
|
id: z.number(),
|
||||||
avatar_url: z.string(),
|
avatar_url: z.string(),
|
||||||
url: z.string(),
|
url: z.string(),
|
||||||
html_url: z.string()
|
html_url: z.string(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const GitHubMilestoneSchema = z.object({
|
export const GitHubMilestoneSchema = z.object({
|
||||||
|
|
@ -226,7 +249,7 @@ export const GitHubMilestoneSchema = z.object({
|
||||||
number: z.number(),
|
number: z.number(),
|
||||||
title: z.string(),
|
title: z.string(),
|
||||||
description: z.string(),
|
description: z.string(),
|
||||||
state: z.string()
|
state: z.string(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const GitHubIssueSchema = z.object({
|
export const GitHubIssueSchema = z.object({
|
||||||
|
|
@ -251,7 +274,7 @@ export const GitHubIssueSchema = z.object({
|
||||||
created_at: z.string(),
|
created_at: z.string(),
|
||||||
updated_at: z.string(),
|
updated_at: z.string(),
|
||||||
closed_at: z.string().nullable(),
|
closed_at: z.string().nullable(),
|
||||||
body: z.string()
|
body: z.string(),
|
||||||
});
|
});
|
||||||
|
|
||||||
// Pull Request related schemas
|
// Pull Request related schemas
|
||||||
|
|
@ -260,7 +283,7 @@ export const GitHubPullRequestHeadSchema = z.object({
|
||||||
ref: z.string(),
|
ref: z.string(),
|
||||||
sha: z.string(),
|
sha: z.string(),
|
||||||
user: GitHubIssueAssigneeSchema,
|
user: GitHubIssueAssigneeSchema,
|
||||||
repo: GitHubRepositorySchema
|
repo: GitHubRepositorySchema,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const GitHubPullRequestSchema = z.object({
|
export const GitHubPullRequestSchema = z.object({
|
||||||
|
|
@ -281,16 +304,16 @@ export const GitHubPullRequestSchema = z.object({
|
||||||
updated_at: z.string(),
|
updated_at: z.string(),
|
||||||
closed_at: z.string().nullable(),
|
closed_at: z.string().nullable(),
|
||||||
merged_at: z.string().nullable(),
|
merged_at: z.string().nullable(),
|
||||||
merge_commit_sha: z.string(),
|
merge_commit_sha: z.string().nullable(),
|
||||||
assignee: GitHubIssueAssigneeSchema.nullable(),
|
assignee: GitHubIssueAssigneeSchema.nullable(),
|
||||||
assignees: z.array(GitHubIssueAssigneeSchema),
|
assignees: z.array(GitHubIssueAssigneeSchema),
|
||||||
head: GitHubPullRequestHeadSchema,
|
head: GitHubPullRequestHeadSchema,
|
||||||
base: GitHubPullRequestHeadSchema
|
base: GitHubPullRequestHeadSchema,
|
||||||
});
|
});
|
||||||
|
|
||||||
const RepoParamsSchema = z.object({
|
const RepoParamsSchema = z.object({
|
||||||
owner: z.string().describe("Repository owner (username or organization)"),
|
owner: z.string().describe("Repository owner (username or organization)"),
|
||||||
repo: z.string().describe("Repository name")
|
repo: z.string().describe("Repository name"),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const CreateOrUpdateFileSchema = RepoParamsSchema.extend({
|
export const CreateOrUpdateFileSchema = RepoParamsSchema.extend({
|
||||||
|
|
@ -298,81 +321,399 @@ export const CreateOrUpdateFileSchema = RepoParamsSchema.extend({
|
||||||
content: z.string().describe("Content of the file"),
|
content: z.string().describe("Content of the file"),
|
||||||
message: z.string().describe("Commit message"),
|
message: z.string().describe("Commit message"),
|
||||||
branch: z.string().describe("Branch to create/update the file in"),
|
branch: z.string().describe("Branch to create/update the file in"),
|
||||||
sha: z.string().optional()
|
sha: z
|
||||||
.describe("SHA of the file being replaced (required when updating existing files)")
|
.string()
|
||||||
|
.optional()
|
||||||
|
.describe(
|
||||||
|
"SHA of the file being replaced (required when updating existing files)"
|
||||||
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const SearchRepositoriesSchema = z.object({
|
export const SearchRepositoriesSchema = z.object({
|
||||||
query: z.string().describe("Search query (see GitHub search syntax)"),
|
query: z.string().describe("Search query (see GitHub search syntax)"),
|
||||||
|
page: z
|
||||||
|
.number()
|
||||||
|
.optional()
|
||||||
|
.describe("Page number for pagination (default: 1)"),
|
||||||
|
perPage: z
|
||||||
|
.number()
|
||||||
|
.optional()
|
||||||
|
.describe("Number of results per page (default: 30, max: 100)"),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const ListCommitsSchema = z.object({
|
||||||
|
owner: z.string().describe("Repository owner (username or organization)"),
|
||||||
|
repo: z.string().describe("Repository name"),
|
||||||
page: z.number().optional().describe("Page number for pagination (default: 1)"),
|
page: z.number().optional().describe("Page number for pagination (default: 1)"),
|
||||||
perPage: z.number().optional().describe("Number of results per page (default: 30, max: 100)")
|
perPage: z.number().optional().describe("Number of results per page (default: 30, max: 100)"),
|
||||||
|
sha: z.string().optional()
|
||||||
|
.describe("SHA of the file being replaced (required when updating existing files)")
|
||||||
});
|
});
|
||||||
|
|
||||||
export const CreateRepositorySchema = z.object({
|
export const CreateRepositorySchema = z.object({
|
||||||
name: z.string().describe("Repository name"),
|
name: z.string().describe("Repository name"),
|
||||||
description: z.string().optional().describe("Repository description"),
|
description: z.string().optional().describe("Repository description"),
|
||||||
private: z.boolean().optional().describe("Whether the repository should be private"),
|
private: z
|
||||||
autoInit: z.boolean().optional().describe("Initialize with README.md")
|
.boolean()
|
||||||
|
.optional()
|
||||||
|
.describe("Whether the repository should be private"),
|
||||||
|
autoInit: z.boolean().optional().describe("Initialize with README.md"),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const GetFileContentsSchema = RepoParamsSchema.extend({
|
export const GetFileContentsSchema = RepoParamsSchema.extend({
|
||||||
path: z.string().describe("Path to the file or directory"),
|
path: z.string().describe("Path to the file or directory"),
|
||||||
branch: z.string().optional().describe("Branch to get contents from")
|
branch: z.string().optional().describe("Branch to get contents from"),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const PushFilesSchema = RepoParamsSchema.extend({
|
export const PushFilesSchema = RepoParamsSchema.extend({
|
||||||
branch: z.string().describe("Branch to push to (e.g., 'main' or 'master')"),
|
branch: z.string().describe("Branch to push to (e.g., 'main' or 'master')"),
|
||||||
files: z.array(z.object({
|
files: z
|
||||||
path: z.string().describe("Path where to create the file"),
|
.array(
|
||||||
content: z.string().describe("Content of the file")
|
z.object({
|
||||||
})).describe("Array of files to push"),
|
path: z.string().describe("Path where to create the file"),
|
||||||
message: z.string().describe("Commit message")
|
content: z.string().describe("Content of the file"),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.describe("Array of files to push"),
|
||||||
|
message: z.string().describe("Commit message"),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const CreateIssueSchema = RepoParamsSchema.extend({
|
export const CreateIssueSchema = RepoParamsSchema.extend({
|
||||||
title: z.string().describe("Issue title"),
|
title: z.string().describe("Issue title"),
|
||||||
body: z.string().optional().describe("Issue body/description"),
|
body: z.string().optional().describe("Issue body/description"),
|
||||||
assignees: z.array(z.string()).optional().describe("Array of usernames to assign"),
|
assignees: z
|
||||||
|
.array(z.string())
|
||||||
|
.optional()
|
||||||
|
.describe("Array of usernames to assign"),
|
||||||
labels: z.array(z.string()).optional().describe("Array of label names"),
|
labels: z.array(z.string()).optional().describe("Array of label names"),
|
||||||
milestone: z.number().optional().describe("Milestone number to assign")
|
milestone: z.number().optional().describe("Milestone number to assign"),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const CreatePullRequestSchema = RepoParamsSchema.extend({
|
export const CreatePullRequestSchema = RepoParamsSchema.extend({
|
||||||
title: z.string().describe("Pull request title"),
|
title: z.string().describe("Pull request title"),
|
||||||
body: z.string().optional().describe("Pull request body/description"),
|
body: z.string().optional().describe("Pull request body/description"),
|
||||||
head: z.string().describe("The name of the branch where your changes are implemented"),
|
head: z
|
||||||
base: z.string().describe("The name of the branch you want the changes pulled into"),
|
.string()
|
||||||
draft: z.boolean().optional().describe("Whether to create the pull request as a draft"),
|
.describe("The name of the branch where your changes are implemented"),
|
||||||
maintainer_can_modify: z.boolean().optional()
|
base: z
|
||||||
.describe("Whether maintainers can modify the pull request")
|
.string()
|
||||||
|
.describe("The name of the branch you want the changes pulled into"),
|
||||||
|
draft: z
|
||||||
|
.boolean()
|
||||||
|
.optional()
|
||||||
|
.describe("Whether to create the pull request as a draft"),
|
||||||
|
maintainer_can_modify: z
|
||||||
|
.boolean()
|
||||||
|
.optional()
|
||||||
|
.describe("Whether maintainers can modify the pull request"),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const ForkRepositorySchema = RepoParamsSchema.extend({
|
export const ForkRepositorySchema = RepoParamsSchema.extend({
|
||||||
organization: z.string().optional()
|
organization: z
|
||||||
.describe("Optional: organization to fork to (defaults to your personal account)")
|
.string()
|
||||||
|
.optional()
|
||||||
|
.describe(
|
||||||
|
"Optional: organization to fork to (defaults to your personal account)"
|
||||||
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const CreateBranchSchema = RepoParamsSchema.extend({
|
export const CreateBranchSchema = RepoParamsSchema.extend({
|
||||||
branch: z.string().describe("Name for the new branch"),
|
branch: z.string().describe("Name for the new branch"),
|
||||||
from_branch: z.string().optional()
|
from_branch: z
|
||||||
.describe("Optional: source branch to create from (defaults to the repository's default branch)")
|
.string()
|
||||||
|
.optional()
|
||||||
|
.describe(
|
||||||
|
"Optional: source branch to create from (defaults to the repository's default branch)"
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Response schema for a code search result item
|
||||||
|
* @see https://docs.github.com/en/rest/search/search?apiVersion=2022-11-28#search-code
|
||||||
|
*/
|
||||||
|
export const SearchCodeItemSchema = z.object({
|
||||||
|
name: z.string().describe("The name of the file"),
|
||||||
|
path: z.string().describe("The path to the file in the repository"),
|
||||||
|
sha: z.string().describe("The SHA hash of the file"),
|
||||||
|
url: z.string().describe("The API URL for this file"),
|
||||||
|
git_url: z.string().describe("The Git URL for this file"),
|
||||||
|
html_url: z.string().describe("The HTML URL to view this file on GitHub"),
|
||||||
|
repository: GitHubRepositorySchema.describe(
|
||||||
|
"The repository where this file was found"
|
||||||
|
),
|
||||||
|
score: z.number().describe("The search result score"),
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Response schema for code search results
|
||||||
|
*/
|
||||||
|
export const SearchCodeResponseSchema = z.object({
|
||||||
|
total_count: z.number().describe("Total number of matching results"),
|
||||||
|
incomplete_results: z
|
||||||
|
.boolean()
|
||||||
|
.describe("Whether the results are incomplete"),
|
||||||
|
items: z.array(SearchCodeItemSchema).describe("The search results"),
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Response schema for an issue search result item
|
||||||
|
* @see https://docs.github.com/en/rest/search/search?apiVersion=2022-11-28#search-issues-and-pull-requests
|
||||||
|
*/
|
||||||
|
export const SearchIssueItemSchema = z.object({
|
||||||
|
url: z.string().describe("The API URL for this issue"),
|
||||||
|
repository_url: z
|
||||||
|
.string()
|
||||||
|
.describe("The API URL for the repository where this issue was found"),
|
||||||
|
labels_url: z.string().describe("The API URL for the labels of this issue"),
|
||||||
|
comments_url: z.string().describe("The API URL for comments of this issue"),
|
||||||
|
events_url: z.string().describe("The API URL for events of this issue"),
|
||||||
|
html_url: z.string().describe("The HTML URL to view this issue on GitHub"),
|
||||||
|
id: z.number().describe("The ID of this issue"),
|
||||||
|
node_id: z.string().describe("The Node ID of this issue"),
|
||||||
|
number: z.number().describe("The number of this issue"),
|
||||||
|
title: z.string().describe("The title of this issue"),
|
||||||
|
user: GitHubIssueAssigneeSchema.describe("The user who created this issue"),
|
||||||
|
labels: z.array(GitHubLabelSchema).describe("The labels of this issue"),
|
||||||
|
state: z.string().describe("The state of this issue"),
|
||||||
|
locked: z.boolean().describe("Whether this issue is locked"),
|
||||||
|
assignee: GitHubIssueAssigneeSchema.nullable().describe(
|
||||||
|
"The assignee of this issue"
|
||||||
|
),
|
||||||
|
assignees: z
|
||||||
|
.array(GitHubIssueAssigneeSchema)
|
||||||
|
.describe("The assignees of this issue"),
|
||||||
|
comments: z.number().describe("The number of comments on this issue"),
|
||||||
|
created_at: z.string().describe("The creation time of this issue"),
|
||||||
|
updated_at: z.string().describe("The last update time of this issue"),
|
||||||
|
closed_at: z.string().nullable().describe("The closure time of this issue"),
|
||||||
|
body: z.string().describe("The body of this issue"),
|
||||||
|
score: z.number().describe("The search result score"),
|
||||||
|
pull_request: z
|
||||||
|
.object({
|
||||||
|
url: z.string().describe("The API URL for this pull request"),
|
||||||
|
html_url: z.string().describe("The HTML URL to view this pull request"),
|
||||||
|
diff_url: z.string().describe("The URL to view the diff"),
|
||||||
|
patch_url: z.string().describe("The URL to view the patch"),
|
||||||
|
})
|
||||||
|
.optional()
|
||||||
|
.describe("Pull request details if this is a PR"),
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Response schema for issue search results
|
||||||
|
*/
|
||||||
|
export const SearchIssuesResponseSchema = z.object({
|
||||||
|
total_count: z.number().describe("Total number of matching results"),
|
||||||
|
incomplete_results: z
|
||||||
|
.boolean()
|
||||||
|
.describe("Whether the results are incomplete"),
|
||||||
|
items: z.array(SearchIssueItemSchema).describe("The search results"),
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Response schema for a user search result item
|
||||||
|
* @see https://docs.github.com/en/rest/search/search?apiVersion=2022-11-28#search-users
|
||||||
|
*/
|
||||||
|
export const SearchUserItemSchema = z.object({
|
||||||
|
login: z.string().describe("The username of the user"),
|
||||||
|
id: z.number().describe("The ID of the user"),
|
||||||
|
node_id: z.string().describe("The Node ID of the user"),
|
||||||
|
avatar_url: z.string().describe("The avatar URL of the user"),
|
||||||
|
gravatar_id: z.string().describe("The Gravatar ID of the user"),
|
||||||
|
url: z.string().describe("The API URL for this user"),
|
||||||
|
html_url: z.string().describe("The HTML URL to view this user on GitHub"),
|
||||||
|
followers_url: z.string().describe("The API URL for followers of this user"),
|
||||||
|
following_url: z.string().describe("The API URL for following of this user"),
|
||||||
|
gists_url: z.string().describe("The API URL for gists of this user"),
|
||||||
|
starred_url: z
|
||||||
|
.string()
|
||||||
|
.describe("The API URL for starred repositories of this user"),
|
||||||
|
subscriptions_url: z
|
||||||
|
.string()
|
||||||
|
.describe("The API URL for subscriptions of this user"),
|
||||||
|
organizations_url: z
|
||||||
|
.string()
|
||||||
|
.describe("The API URL for organizations of this user"),
|
||||||
|
repos_url: z.string().describe("The API URL for repositories of this user"),
|
||||||
|
events_url: z.string().describe("The API URL for events of this user"),
|
||||||
|
received_events_url: z
|
||||||
|
.string()
|
||||||
|
.describe("The API URL for received events of this user"),
|
||||||
|
type: z.string().describe("The type of this user"),
|
||||||
|
site_admin: z.boolean().describe("Whether this user is a site administrator"),
|
||||||
|
score: z.number().describe("The search result score"),
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Response schema for user search results
|
||||||
|
*/
|
||||||
|
export const SearchUsersResponseSchema = z.object({
|
||||||
|
total_count: z.number().describe("Total number of matching results"),
|
||||||
|
incomplete_results: z
|
||||||
|
.boolean()
|
||||||
|
.describe("Whether the results are incomplete"),
|
||||||
|
items: z.array(SearchUserItemSchema).describe("The search results"),
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Input schema for code search
|
||||||
|
* @see https://docs.github.com/en/rest/search/search?apiVersion=2022-11-28#search-code--parameters
|
||||||
|
*/
|
||||||
|
export const SearchCodeSchema = z.object({
|
||||||
|
q: z
|
||||||
|
.string()
|
||||||
|
.describe(
|
||||||
|
"Search query. See GitHub code search syntax: https://docs.github.com/en/search-github/searching-on-github/searching-code"
|
||||||
|
),
|
||||||
|
order: z
|
||||||
|
.enum(["asc", "desc"])
|
||||||
|
.optional()
|
||||||
|
.describe("Sort order (asc or desc)"),
|
||||||
|
per_page: z
|
||||||
|
.number()
|
||||||
|
.min(1)
|
||||||
|
.max(100)
|
||||||
|
.optional()
|
||||||
|
.describe("Results per page (max 100)"),
|
||||||
|
page: z.number().min(1).optional().describe("Page number"),
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Input schema for issues search
|
||||||
|
* @see https://docs.github.com/en/rest/search/search?apiVersion=2022-11-28#search-issues-and-pull-requests--parameters
|
||||||
|
*/
|
||||||
|
export const SearchIssuesSchema = z.object({
|
||||||
|
q: z
|
||||||
|
.string()
|
||||||
|
.describe(
|
||||||
|
"Search query. See GitHub issues search syntax: https://docs.github.com/en/search-github/searching-on-github/searching-issues-and-pull-requests"
|
||||||
|
),
|
||||||
|
sort: z
|
||||||
|
.enum([
|
||||||
|
"comments",
|
||||||
|
"reactions",
|
||||||
|
"reactions-+1",
|
||||||
|
"reactions--1",
|
||||||
|
"reactions-smile",
|
||||||
|
"reactions-thinking_face",
|
||||||
|
"reactions-heart",
|
||||||
|
"reactions-tada",
|
||||||
|
"interactions",
|
||||||
|
"created",
|
||||||
|
"updated",
|
||||||
|
])
|
||||||
|
.optional()
|
||||||
|
.describe("Sort field"),
|
||||||
|
order: z
|
||||||
|
.enum(["asc", "desc"])
|
||||||
|
.optional()
|
||||||
|
.describe("Sort order (asc or desc)"),
|
||||||
|
per_page: z
|
||||||
|
.number()
|
||||||
|
.min(1)
|
||||||
|
.max(100)
|
||||||
|
.optional()
|
||||||
|
.describe("Results per page (max 100)"),
|
||||||
|
page: z.number().min(1).optional().describe("Page number"),
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Input schema for users search
|
||||||
|
* @see https://docs.github.com/en/rest/search/search?apiVersion=2022-11-28#search-users--parameters
|
||||||
|
*/
|
||||||
|
export const SearchUsersSchema = z.object({
|
||||||
|
q: z
|
||||||
|
.string()
|
||||||
|
.describe(
|
||||||
|
"Search query. See GitHub users search syntax: https://docs.github.com/en/search-github/searching-on-github/searching-users"
|
||||||
|
),
|
||||||
|
sort: z
|
||||||
|
.enum(["followers", "repositories", "joined"])
|
||||||
|
.optional()
|
||||||
|
.describe("Sort field"),
|
||||||
|
order: z
|
||||||
|
.enum(["asc", "desc"])
|
||||||
|
.optional()
|
||||||
|
.describe("Sort order (asc or desc)"),
|
||||||
|
per_page: z
|
||||||
|
.number()
|
||||||
|
.min(1)
|
||||||
|
.max(100)
|
||||||
|
.optional()
|
||||||
|
.describe("Results per page (max 100)"),
|
||||||
|
page: z.number().min(1).optional().describe("Page number"),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add these schema definitions for issue management
|
||||||
|
|
||||||
|
export const ListIssuesOptionsSchema = z.object({
|
||||||
|
owner: z.string(),
|
||||||
|
repo: z.string(),
|
||||||
|
state: z.enum(['open', 'closed', 'all']).optional(),
|
||||||
|
labels: z.array(z.string()).optional(),
|
||||||
|
sort: z.enum(['created', 'updated', 'comments']).optional(),
|
||||||
|
direction: z.enum(['asc', 'desc']).optional(),
|
||||||
|
since: z.string().optional(), // ISO 8601 timestamp
|
||||||
|
page: z.number().optional(),
|
||||||
|
per_page: z.number().optional()
|
||||||
|
});
|
||||||
|
|
||||||
|
export const UpdateIssueOptionsSchema = z.object({
|
||||||
|
owner: z.string(),
|
||||||
|
repo: z.string(),
|
||||||
|
issue_number: z.number(),
|
||||||
|
title: z.string().optional(),
|
||||||
|
body: z.string().optional(),
|
||||||
|
state: z.enum(['open', 'closed']).optional(),
|
||||||
|
labels: z.array(z.string()).optional(),
|
||||||
|
assignees: z.array(z.string()).optional(),
|
||||||
|
milestone: z.number().optional()
|
||||||
|
});
|
||||||
|
|
||||||
|
export const IssueCommentSchema = z.object({
|
||||||
|
owner: z.string(),
|
||||||
|
repo: z.string(),
|
||||||
|
issue_number: z.number(),
|
||||||
|
body: z.string()
|
||||||
|
});
|
||||||
|
|
||||||
|
export const GetIssueSchema = z.object({
|
||||||
|
owner: z.string().describe("Repository owner (username or organization)"),
|
||||||
|
repo: z.string().describe("Repository name"),
|
||||||
|
issue_number: z.number().describe("Issue number")
|
||||||
});
|
});
|
||||||
|
|
||||||
// Export types
|
// Export types
|
||||||
export type GitHubAuthor = z.infer<typeof GitHubAuthorSchema>;
|
export type GitHubAuthor = z.infer<typeof GitHubAuthorSchema>;
|
||||||
export type GitHubFork = z.infer<typeof GitHubForkSchema>;
|
export type GitHubFork = z.infer<typeof GitHubForkSchema>;
|
||||||
export type GitHubIssue = z.infer<typeof GitHubIssueSchema>;
|
export type GitHubIssue = z.infer<typeof GitHubIssueSchema>;
|
||||||
export type GitHubPullRequest = z.infer<typeof GitHubPullRequestSchema>;export type GitHubRepository = z.infer<typeof GitHubRepositorySchema>;
|
export type GitHubPullRequest = z.infer<typeof GitHubPullRequestSchema>;
|
||||||
|
export type GitHubRepository = z.infer<typeof GitHubRepositorySchema>;
|
||||||
export type GitHubFileContent = z.infer<typeof GitHubFileContentSchema>;
|
export type GitHubFileContent = z.infer<typeof GitHubFileContentSchema>;
|
||||||
export type GitHubDirectoryContent = z.infer<typeof GitHubDirectoryContentSchema>;
|
export type GitHubDirectoryContent = z.infer<
|
||||||
|
typeof GitHubDirectoryContentSchema
|
||||||
|
>;
|
||||||
export type GitHubContent = z.infer<typeof GitHubContentSchema>;
|
export type GitHubContent = z.infer<typeof GitHubContentSchema>;
|
||||||
export type FileOperation = z.infer<typeof FileOperationSchema>;
|
export type FileOperation = z.infer<typeof FileOperationSchema>;
|
||||||
export type GitHubTree = z.infer<typeof GitHubTreeSchema>;
|
export type GitHubTree = z.infer<typeof GitHubTreeSchema>;
|
||||||
export type GitHubCommit = z.infer<typeof GitHubCommitSchema>;
|
export type GitHubCommit = z.infer<typeof GitHubCommitSchema>;
|
||||||
|
export type GitHubListCommits = z.infer<typeof GitHubListCommitsSchema>;
|
||||||
export type GitHubReference = z.infer<typeof GitHubReferenceSchema>;
|
export type GitHubReference = z.infer<typeof GitHubReferenceSchema>;
|
||||||
export type CreateRepositoryOptions = z.infer<typeof CreateRepositoryOptionsSchema>;
|
export type CreateRepositoryOptions = z.infer<
|
||||||
|
typeof CreateRepositoryOptionsSchema
|
||||||
|
>;
|
||||||
export type CreateIssueOptions = z.infer<typeof CreateIssueOptionsSchema>;
|
export type CreateIssueOptions = z.infer<typeof CreateIssueOptionsSchema>;
|
||||||
export type CreatePullRequestOptions = z.infer<typeof CreatePullRequestOptionsSchema>;
|
export type CreatePullRequestOptions = z.infer<
|
||||||
|
typeof CreatePullRequestOptionsSchema
|
||||||
|
>;
|
||||||
export type CreateBranchOptions = z.infer<typeof CreateBranchOptionsSchema>;
|
export type CreateBranchOptions = z.infer<typeof CreateBranchOptionsSchema>;
|
||||||
export type GitHubCreateUpdateFileResponse = z.infer<typeof GitHubCreateUpdateFileResponseSchema>;
|
export type GitHubCreateUpdateFileResponse = z.infer<
|
||||||
|
typeof GitHubCreateUpdateFileResponseSchema
|
||||||
|
>;
|
||||||
export type GitHubSearchResponse = z.infer<typeof GitHubSearchResponseSchema>;
|
export type GitHubSearchResponse = z.infer<typeof GitHubSearchResponseSchema>;
|
||||||
|
export type SearchCodeItem = z.infer<typeof SearchCodeItemSchema>;
|
||||||
|
export type SearchCodeResponse = z.infer<typeof SearchCodeResponseSchema>;
|
||||||
|
export type SearchIssueItem = z.infer<typeof SearchIssueItemSchema>;
|
||||||
|
export type SearchIssuesResponse = z.infer<typeof SearchIssuesResponseSchema>;
|
||||||
|
export type SearchUserItem = z.infer<typeof SearchUserItemSchema>;
|
||||||
|
export type SearchUsersResponse = z.infer<typeof SearchUsersResponseSchema>;
|
||||||
|
|
|
||||||
|
|
@ -124,6 +124,15 @@ async function ensureBrowser() {
|
||||||
return page!;
|
return page!;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
mcpHelper: {
|
||||||
|
logs: string[],
|
||||||
|
originalConsole: Partial<typeof console>,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function handleToolCall(name: string, args: any): Promise<CallToolResult> {
|
async function handleToolCall(name: string, args: any): Promise<CallToolResult> {
|
||||||
const page = await ensureBrowser();
|
const page = await ensureBrowser();
|
||||||
|
|
||||||
|
|
@ -263,32 +272,34 @@ async function handleToolCall(name: string, args: any): Promise<CallToolResult>
|
||||||
|
|
||||||
case "puppeteer_evaluate":
|
case "puppeteer_evaluate":
|
||||||
try {
|
try {
|
||||||
const result = await page.evaluate((script) => {
|
await page.evaluate(() => {
|
||||||
const logs: string[] = [];
|
window.mcpHelper = {
|
||||||
const originalConsole = { ...console };
|
logs: [],
|
||||||
|
originalConsole: { ...console },
|
||||||
|
};
|
||||||
|
|
||||||
['log', 'info', 'warn', 'error'].forEach(method => {
|
['log', 'info', 'warn', 'error'].forEach(method => {
|
||||||
(console as any)[method] = (...args: any[]) => {
|
(console as any)[method] = (...args: any[]) => {
|
||||||
logs.push(`[${method}] ${args.join(' ')}`);
|
window.mcpHelper.logs.push(`[${method}] ${args.join(' ')}`);
|
||||||
(originalConsole as any)[method](...args);
|
(window.mcpHelper.originalConsole as any)[method](...args);
|
||||||
};
|
};
|
||||||
});
|
} );
|
||||||
|
} );
|
||||||
|
|
||||||
try {
|
const result = await page.evaluate( args.script );
|
||||||
const result = eval(script);
|
|
||||||
Object.assign(console, originalConsole);
|
const logs = await page.evaluate(() => {
|
||||||
return { result, logs };
|
Object.assign(console, window.mcpHelper.originalConsole);
|
||||||
} catch (error) {
|
const logs = window.mcpHelper.logs;
|
||||||
Object.assign(console, originalConsole);
|
delete ( window as any).mcpHelper;
|
||||||
throw error;
|
return logs;
|
||||||
}
|
});
|
||||||
}, args.script);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
content: [
|
content: [
|
||||||
{
|
{
|
||||||
type: "text",
|
type: "text",
|
||||||
text: `Execution result:\n${JSON.stringify(result.result, null, 2)}\n\nConsole output:\n${result.logs.join('\n')}`,
|
text: `Execution result:\n${JSON.stringify(result, null, 2)}\n\nConsole output:\n${logs.join('\n')}`,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
isError: false,
|
isError: false,
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ A Model Context Protocol server for retrieving and analyzing issues from Sentry.
|
||||||
|
|
||||||
### Tools
|
### Tools
|
||||||
|
|
||||||
1. `get-sentry-issue`
|
1. `get_sentry_issue`
|
||||||
- Retrieve and analyze a Sentry issue by ID or URL
|
- Retrieve and analyze a Sentry issue by ID or URL
|
||||||
- Input:
|
- Input:
|
||||||
- `issue_id_or_url` (string): Sentry issue ID or URL to analyze
|
- `issue_id_or_url` (string): Sentry issue ID or URL to analyze
|
||||||
|
|
|
||||||
|
|
@ -223,7 +223,7 @@ async def serve(auth_token: str) -> Server:
|
||||||
async def handle_list_tools() -> list[types.Tool]:
|
async def handle_list_tools() -> list[types.Tool]:
|
||||||
return [
|
return [
|
||||||
types.Tool(
|
types.Tool(
|
||||||
name="get-sentry-issue",
|
name="get_sentry_issue",
|
||||||
description="""Retrieve and analyze a Sentry issue by ID or URL. Use this tool when you need to:
|
description="""Retrieve and analyze a Sentry issue by ID or URL. Use this tool when you need to:
|
||||||
- Investigate production errors and crashes
|
- Investigate production errors and crashes
|
||||||
- Access detailed stacktraces from Sentry
|
- Access detailed stacktraces from Sentry
|
||||||
|
|
@ -247,7 +247,7 @@ async def serve(auth_token: str) -> Server:
|
||||||
async def handle_call_tool(
|
async def handle_call_tool(
|
||||||
name: str, arguments: dict | None
|
name: str, arguments: dict | None
|
||||||
) -> list[types.TextContent | types.ImageContent | types.EmbeddedResource]:
|
) -> list[types.TextContent | types.ImageContent | types.EmbeddedResource]:
|
||||||
if name != "get-sentry-issue":
|
if name != "get_sentry_issue":
|
||||||
raise ValueError(f"Unknown tool: {name}")
|
raise ValueError(f"Unknown tool: {name}")
|
||||||
|
|
||||||
if not arguments or "issue_id_or_url" not in arguments:
|
if not arguments or "issue_id_or_url" not in arguments:
|
||||||
|
|
|
||||||
|
|
@ -22,26 +22,26 @@ The server provides a demonstration prompt:
|
||||||
The server offers six core tools:
|
The server offers six core tools:
|
||||||
|
|
||||||
#### Query Tools
|
#### Query Tools
|
||||||
- `read-query`
|
- `read_query`
|
||||||
- Execute SELECT queries to read data from the database
|
- Execute SELECT queries to read data from the database
|
||||||
- Input:
|
- Input:
|
||||||
- `query` (string): The SELECT SQL query to execute
|
- `query` (string): The SELECT SQL query to execute
|
||||||
- Returns: Query results as array of objects
|
- Returns: Query results as array of objects
|
||||||
|
|
||||||
- `write-query`
|
- `write_query`
|
||||||
- Execute INSERT, UPDATE, or DELETE queries
|
- Execute INSERT, UPDATE, or DELETE queries
|
||||||
- Input:
|
- Input:
|
||||||
- `query` (string): The SQL modification query
|
- `query` (string): The SQL modification query
|
||||||
- Returns: `{ affected_rows: number }`
|
- Returns: `{ affected_rows: number }`
|
||||||
|
|
||||||
- `create-table`
|
- `create_table`
|
||||||
- Create new tables in the database
|
- Create new tables in the database
|
||||||
- Input:
|
- Input:
|
||||||
- `query` (string): CREATE TABLE SQL statement
|
- `query` (string): CREATE TABLE SQL statement
|
||||||
- Returns: Confirmation of table creation
|
- Returns: Confirmation of table creation
|
||||||
|
|
||||||
#### Schema Tools
|
#### Schema Tools
|
||||||
- `list-tables`
|
- `list_tables`
|
||||||
- Get a list of all tables in the database
|
- Get a list of all tables in the database
|
||||||
- No input required
|
- No input required
|
||||||
- Returns: Array of table names
|
- Returns: Array of table names
|
||||||
|
|
@ -53,7 +53,7 @@ The server offers six core tools:
|
||||||
- Returns: Array of column definitions with names and types
|
- Returns: Array of column definitions with names and types
|
||||||
|
|
||||||
#### Analysis Tools
|
#### Analysis Tools
|
||||||
- `append-insight`
|
- `append_insight`
|
||||||
- Add new business insights to the memo resource
|
- Add new business insights to the memo resource
|
||||||
- Input:
|
- Input:
|
||||||
- `insight` (string): Business insight discovered from data analysis
|
- `insight` (string): Business insight discovered from data analysis
|
||||||
|
|
|
||||||
|
|
@ -27,12 +27,12 @@ Resources:
|
||||||
This server exposes one key resource: "memo://insights", which is a business insights memo that gets automatically updated throughout the analysis process. As users analyze the database and discover insights, the memo resource gets updated in real-time to reflect new findings. Resources act as living documents that provide context to the conversation.
|
This server exposes one key resource: "memo://insights", which is a business insights memo that gets automatically updated throughout the analysis process. As users analyze the database and discover insights, the memo resource gets updated in real-time to reflect new findings. Resources act as living documents that provide context to the conversation.
|
||||||
Tools:
|
Tools:
|
||||||
This server provides several SQL-related tools:
|
This server provides several SQL-related tools:
|
||||||
"read-query": Executes SELECT queries to read data from the database
|
"read_query": Executes SELECT queries to read data from the database
|
||||||
"write-query": Executes INSERT, UPDATE, or DELETE queries to modify data
|
"write_query": Executes INSERT, UPDATE, or DELETE queries to modify data
|
||||||
"create-table": Creates new tables in the database
|
"create_table": Creates new tables in the database
|
||||||
"list-tables": Shows all existing tables
|
"list_tables": Shows all existing tables
|
||||||
"describe-table": Shows the schema for a specific table
|
"describe_table": Shows the schema for a specific table
|
||||||
"append-insight": Adds a new business insight to the memo resource
|
"append_insight": Adds a new business insight to the memo resource
|
||||||
</mcp>
|
</mcp>
|
||||||
<demo-instructions>
|
<demo-instructions>
|
||||||
You are an AI assistant tasked with generating a comprehensive business scenario based on a given topic.
|
You are an AI assistant tasked with generating a comprehensive business scenario based on a given topic.
|
||||||
|
|
@ -68,7 +68,7 @@ a. Present 1 additional multiple-choice query options to the user. Its important
|
||||||
b. Explain the purpose of each query option.
|
b. Explain the purpose of each query option.
|
||||||
c. Wait for the user to select one of the query options.
|
c. Wait for the user to select one of the query options.
|
||||||
d. After each query be sure to opine on the results.
|
d. After each query be sure to opine on the results.
|
||||||
e. Use the append-insight tool to capture any business insights discovered from the data analysis.
|
e. Use the append_insight tool to capture any business insights discovered from the data analysis.
|
||||||
|
|
||||||
7. Generate a dashboard:
|
7. Generate a dashboard:
|
||||||
a. Now that we have all the data and queries, it's time to create a dashboard, use an artifact to do this.
|
a. Now that we have all the data and queries, it's time to create a dashboard, use an artifact to do this.
|
||||||
|
|
@ -233,7 +233,7 @@ async def main(db_path: str):
|
||||||
"""List available tools"""
|
"""List available tools"""
|
||||||
return [
|
return [
|
||||||
types.Tool(
|
types.Tool(
|
||||||
name="read-query",
|
name="read_query",
|
||||||
description="Execute a SELECT query on the SQLite database",
|
description="Execute a SELECT query on the SQLite database",
|
||||||
inputSchema={
|
inputSchema={
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
|
@ -244,7 +244,7 @@ async def main(db_path: str):
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
types.Tool(
|
types.Tool(
|
||||||
name="write-query",
|
name="write_query",
|
||||||
description="Execute an INSERT, UPDATE, or DELETE query on the SQLite database",
|
description="Execute an INSERT, UPDATE, or DELETE query on the SQLite database",
|
||||||
inputSchema={
|
inputSchema={
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
|
@ -255,7 +255,7 @@ async def main(db_path: str):
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
types.Tool(
|
types.Tool(
|
||||||
name="create-table",
|
name="create_table",
|
||||||
description="Create a new table in the SQLite database",
|
description="Create a new table in the SQLite database",
|
||||||
inputSchema={
|
inputSchema={
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
|
@ -266,7 +266,7 @@ async def main(db_path: str):
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
types.Tool(
|
types.Tool(
|
||||||
name="list-tables",
|
name="list_tables",
|
||||||
description="List all tables in the SQLite database",
|
description="List all tables in the SQLite database",
|
||||||
inputSchema={
|
inputSchema={
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
|
@ -274,7 +274,7 @@ async def main(db_path: str):
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
types.Tool(
|
types.Tool(
|
||||||
name="describe-table",
|
name="describe_table",
|
||||||
description="Get the schema information for a specific table",
|
description="Get the schema information for a specific table",
|
||||||
inputSchema={
|
inputSchema={
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
|
@ -285,7 +285,7 @@ async def main(db_path: str):
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
types.Tool(
|
types.Tool(
|
||||||
name="append-insight",
|
name="append_insight",
|
||||||
description="Add a business insight to the memo",
|
description="Add a business insight to the memo",
|
||||||
inputSchema={
|
inputSchema={
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
|
@ -303,13 +303,13 @@ async def main(db_path: str):
|
||||||
) -> list[types.TextContent | types.ImageContent | types.EmbeddedResource]:
|
) -> list[types.TextContent | types.ImageContent | types.EmbeddedResource]:
|
||||||
"""Handle tool execution requests"""
|
"""Handle tool execution requests"""
|
||||||
try:
|
try:
|
||||||
if name == "list-tables":
|
if name == "list_tables":
|
||||||
results = db._execute_query(
|
results = db._execute_query(
|
||||||
"SELECT name FROM sqlite_master WHERE type='table'"
|
"SELECT name FROM sqlite_master WHERE type='table'"
|
||||||
)
|
)
|
||||||
return [types.TextContent(type="text", text=str(results))]
|
return [types.TextContent(type="text", text=str(results))]
|
||||||
|
|
||||||
elif name == "describe-table":
|
elif name == "describe_table":
|
||||||
if not arguments or "table_name" not in arguments:
|
if not arguments or "table_name" not in arguments:
|
||||||
raise ValueError("Missing table_name argument")
|
raise ValueError("Missing table_name argument")
|
||||||
results = db._execute_query(
|
results = db._execute_query(
|
||||||
|
|
@ -317,7 +317,7 @@ async def main(db_path: str):
|
||||||
)
|
)
|
||||||
return [types.TextContent(type="text", text=str(results))]
|
return [types.TextContent(type="text", text=str(results))]
|
||||||
|
|
||||||
elif name == "append-insight":
|
elif name == "append_insight":
|
||||||
if not arguments or "insight" not in arguments:
|
if not arguments or "insight" not in arguments:
|
||||||
raise ValueError("Missing insight argument")
|
raise ValueError("Missing insight argument")
|
||||||
|
|
||||||
|
|
@ -332,19 +332,19 @@ async def main(db_path: str):
|
||||||
if not arguments:
|
if not arguments:
|
||||||
raise ValueError("Missing arguments")
|
raise ValueError("Missing arguments")
|
||||||
|
|
||||||
if name == "read-query":
|
if name == "read_query":
|
||||||
if not arguments["query"].strip().upper().startswith("SELECT"):
|
if not arguments["query"].strip().upper().startswith("SELECT"):
|
||||||
raise ValueError("Only SELECT queries are allowed for read-query")
|
raise ValueError("Only SELECT queries are allowed for read_query")
|
||||||
results = db._execute_query(arguments["query"])
|
results = db._execute_query(arguments["query"])
|
||||||
return [types.TextContent(type="text", text=str(results))]
|
return [types.TextContent(type="text", text=str(results))]
|
||||||
|
|
||||||
elif name == "write-query":
|
elif name == "write_query":
|
||||||
if arguments["query"].strip().upper().startswith("SELECT"):
|
if arguments["query"].strip().upper().startswith("SELECT"):
|
||||||
raise ValueError("SELECT queries are not allowed for write-query")
|
raise ValueError("SELECT queries are not allowed for write_query")
|
||||||
results = db._execute_query(arguments["query"])
|
results = db._execute_query(arguments["query"])
|
||||||
return [types.TextContent(type="text", text=str(results))]
|
return [types.TextContent(type="text", text=str(results))]
|
||||||
|
|
||||||
elif name == "create-table":
|
elif name == "create_table":
|
||||||
if not arguments["query"].strip().upper().startswith("CREATE TABLE"):
|
if not arguments["query"].strip().upper().startswith("CREATE TABLE"):
|
||||||
raise ValueError("Only CREATE TABLE statements are allowed")
|
raise ValueError("Only CREATE TABLE statements are allowed")
|
||||||
db._execute_query(arguments["query"])
|
db._execute_query(arguments["query"])
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue