mirror of
https://github.com/modelcontextprotocol/servers.git
synced 2026-04-19 08:33:23 +02:00
Initial commit
This commit is contained in:
3
src/everything/README.md
Normal file
3
src/everything/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# Everything server
|
||||
|
||||
This MCP server attempts to exercise all the features of the MCP protocol. It is not intended to be a useful server, but rather a test server for builders of MCP clients.
|
||||
441
src/everything/everything.ts
Normal file
441
src/everything/everything.ts
Normal file
File diff suppressed because one or more lines are too long
23
src/everything/index.ts
Normal file
23
src/everything/index.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
||||
import { createServer } from "./everything.js";
|
||||
|
||||
async function main() {
|
||||
const transport = new StdioServerTransport();
|
||||
const { server, cleanup } = createServer();
|
||||
|
||||
await server.connect(transport);
|
||||
|
||||
// Cleanup on exit
|
||||
process.on("SIGINT", async () => {
|
||||
await cleanup();
|
||||
await server.close();
|
||||
process.exit(0);
|
||||
});
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
console.error("Server error:", error);
|
||||
process.exit(1);
|
||||
});
|
||||
32
src/everything/sse.ts
Normal file
32
src/everything/sse.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
|
||||
import express from "express";
|
||||
import { createServer } from "./everything.js";
|
||||
|
||||
const app = express();
|
||||
|
||||
const { server, cleanup } = createServer();
|
||||
|
||||
let transport: SSEServerTransport;
|
||||
|
||||
app.get("/sse", async (req, res) => {
|
||||
console.log("Received connection");
|
||||
transport = new SSEServerTransport("/message", res);
|
||||
await server.connect(transport);
|
||||
|
||||
server.onclose = async () => {
|
||||
await cleanup();
|
||||
await server.close();
|
||||
process.exit(0);
|
||||
};
|
||||
});
|
||||
|
||||
app.post("/message", async (req, res) => {
|
||||
console.log("Received message");
|
||||
|
||||
await transport.handlePostMessage(req, res);
|
||||
});
|
||||
|
||||
const PORT = process.env.PORT || 3001;
|
||||
app.listen(PORT, () => {
|
||||
console.log(`Server is running on port ${PORT}`);
|
||||
});
|
||||
33
src/gdrive/README.md
Normal file
33
src/gdrive/README.md
Normal file
@@ -0,0 +1,33 @@
|
||||
# Google Drive server
|
||||
|
||||
This MCP server integrates with Google Drive to allow listing, reading, and searching over files.
|
||||
|
||||
## Getting started
|
||||
|
||||
1. Create a new Google Cloud project
|
||||
2. Enable the Google Drive API
|
||||
3. Configure an OAuth consent screen ("internal" is fine for testing)
|
||||
4. Add OAuth scope `https://www.googleapis.com/auth/drive.readonly`
|
||||
5. Create an OAuth Client ID for application type "Desktop App"
|
||||
6. Download the JSON file of your client's OAuth keys
|
||||
7. Rename the key file to `gcp-oauth.keys.json` and place into the root of this repo
|
||||
|
||||
Make sure to build the server with either `npm run build` or `npm run watch`.
|
||||
|
||||
### Authentication
|
||||
|
||||
To authenticate and save credentials:
|
||||
|
||||
1. Run the server with the `auth` argument: `node build/gdrive auth`
|
||||
2. This will open an authentication flow in your system browser
|
||||
3. Complete the authentication process
|
||||
4. Credentials will be saved for future use
|
||||
|
||||
### Running the server
|
||||
|
||||
After authenticating:
|
||||
|
||||
1. Run the server normally: `node build/gdrive`
|
||||
2. The server will load the saved credentials and start
|
||||
|
||||
Note: If you haven't authenticated yet, the server will prompt you to run with the `auth` argument first.
|
||||
216
src/gdrive/index.ts
Normal file
216
src/gdrive/index.ts
Normal file
@@ -0,0 +1,216 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { authenticate } from "@google-cloud/local-auth";
|
||||
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
||||
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
||||
import {
|
||||
CallToolRequestSchema,
|
||||
ListResourcesRequestSchema,
|
||||
ListToolsRequestSchema,
|
||||
ReadResourceRequestSchema,
|
||||
} from "@modelcontextprotocol/sdk/types.js";
|
||||
import fs from "fs";
|
||||
import { google } from "googleapis";
|
||||
import path from "path";
|
||||
|
||||
const drive = google.drive("v3");
|
||||
|
||||
const server = new Server(
|
||||
{
|
||||
name: "example-servers/gdrive",
|
||||
version: "0.1.0",
|
||||
},
|
||||
{
|
||||
capabilities: {
|
||||
resources: {},
|
||||
tools: {},
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
server.setRequestHandler(ListResourcesRequestSchema, async (request) => {
|
||||
const pageSize = 10;
|
||||
const params: any = {
|
||||
pageSize,
|
||||
fields: "nextPageToken, files(id, name, mimeType)",
|
||||
};
|
||||
|
||||
if (request.params?.cursor) {
|
||||
params.pageToken = request.params.cursor;
|
||||
}
|
||||
|
||||
const res = await drive.files.list(params);
|
||||
const files = res.data.files!;
|
||||
|
||||
return {
|
||||
resources: files.map((file) => ({
|
||||
uri: `gdrive://${file.id}`,
|
||||
mimeType: file.mimeType,
|
||||
name: file.name,
|
||||
})),
|
||||
nextCursor: res.data.nextPageToken,
|
||||
};
|
||||
});
|
||||
|
||||
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
||||
const fileId = request.params.uri.replace("gdrive://", "");
|
||||
|
||||
// First get file metadata to check mime type
|
||||
const file = await drive.files.get({
|
||||
fileId,
|
||||
fields: "mimeType",
|
||||
});
|
||||
|
||||
// For Google Docs/Sheets/etc we need to export
|
||||
if (file.data.mimeType?.startsWith("application/vnd.google-apps")) {
|
||||
let exportMimeType: string;
|
||||
switch (file.data.mimeType) {
|
||||
case "application/vnd.google-apps.document":
|
||||
exportMimeType = "text/markdown";
|
||||
break;
|
||||
case "application/vnd.google-apps.spreadsheet":
|
||||
exportMimeType = "text/csv";
|
||||
break;
|
||||
case "application/vnd.google-apps.presentation":
|
||||
exportMimeType = "text/plain";
|
||||
break;
|
||||
case "application/vnd.google-apps.drawing":
|
||||
exportMimeType = "image/png";
|
||||
break;
|
||||
default:
|
||||
exportMimeType = "text/plain";
|
||||
}
|
||||
|
||||
const res = await drive.files.export(
|
||||
{ fileId, mimeType: exportMimeType },
|
||||
{ responseType: "text" },
|
||||
);
|
||||
|
||||
return {
|
||||
contents: [
|
||||
{
|
||||
uri: request.params.uri,
|
||||
mimeType: exportMimeType,
|
||||
text: res.data,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
// For regular files download content
|
||||
const res = await drive.files.get(
|
||||
{ fileId, alt: "media" },
|
||||
{ responseType: "arraybuffer" },
|
||||
);
|
||||
const mimeType = file.data.mimeType || "application/octet-stream";
|
||||
if (mimeType.startsWith("text/") || mimeType === "application/json") {
|
||||
return {
|
||||
contents: [
|
||||
{
|
||||
uri: request.params.uri,
|
||||
mimeType: mimeType,
|
||||
text: Buffer.from(res.data as ArrayBuffer).toString("utf-8"),
|
||||
},
|
||||
],
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
contents: [
|
||||
{
|
||||
uri: request.params.uri,
|
||||
mimeType: mimeType,
|
||||
blob: Buffer.from(res.data as ArrayBuffer).toString("base64"),
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
||||
return {
|
||||
tools: [
|
||||
{
|
||||
name: "search",
|
||||
description: "Search for files in Google Drive",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
query: {
|
||||
type: "string",
|
||||
description: "Search query",
|
||||
},
|
||||
},
|
||||
required: ["query"],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
});
|
||||
|
||||
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
||||
if (request.params.name === "search") {
|
||||
const query = request.params.arguments?.query as string;
|
||||
|
||||
const res = await drive.files.list({
|
||||
q: query,
|
||||
pageSize: 10,
|
||||
fields: "files(id, name, mimeType, modifiedTime, size)",
|
||||
});
|
||||
|
||||
const fileList = res.data.files
|
||||
?.map((file: any) => `${file.name} (${file.mimeType})`)
|
||||
.join("\n");
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Found ${res.data.files?.length ?? 0} files:\n${fileList}`,
|
||||
},
|
||||
],
|
||||
isError: false,
|
||||
};
|
||||
}
|
||||
throw new Error("Tool not found");
|
||||
});
|
||||
|
||||
const credentialsPath = path.join(
|
||||
path.dirname(new URL(import.meta.url).pathname),
|
||||
"../../.gdrive-server-credentials.json",
|
||||
);
|
||||
|
||||
async function authenticateAndSaveCredentials() {
|
||||
console.log("Launching auth flow…");
|
||||
const auth = await authenticate({
|
||||
keyfilePath: path.join(
|
||||
path.dirname(new URL(import.meta.url).pathname),
|
||||
"../../gcp-oauth.keys.json",
|
||||
),
|
||||
scopes: ["https://www.googleapis.com/auth/drive.readonly"],
|
||||
});
|
||||
fs.writeFileSync(credentialsPath, JSON.stringify(auth.credentials));
|
||||
console.log("Credentials saved. You can now run the server.");
|
||||
}
|
||||
|
||||
async function loadCredentialsAndRunServer() {
|
||||
if (!fs.existsSync(credentialsPath)) {
|
||||
console.error(
|
||||
"Credentials not found. Please run with 'auth' argument first.",
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const credentials = JSON.parse(fs.readFileSync(credentialsPath, "utf-8"));
|
||||
const auth = new google.auth.OAuth2();
|
||||
auth.setCredentials(credentials);
|
||||
google.options({ auth });
|
||||
|
||||
console.log("Credentials loaded. Starting server.");
|
||||
const transport = new StdioServerTransport();
|
||||
await server.connect(transport);
|
||||
}
|
||||
|
||||
if (process.argv[2] === "auth") {
|
||||
authenticateAndSaveCredentials().catch(console.error);
|
||||
} else {
|
||||
loadCredentialsAndRunServer().catch(console.error);
|
||||
}
|
||||
2
src/git/.gitignore
vendored
Normal file
2
src/git/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
__pycache__
|
||||
.venv
|
||||
1
src/git/.python-version
Normal file
1
src/git/.python-version
Normal file
@@ -0,0 +1 @@
|
||||
3.11
|
||||
7
src/git/LICENSE
Normal file
7
src/git/LICENSE
Normal file
@@ -0,0 +1,7 @@
|
||||
Copyright (c) 2024 Anthropic, PBC.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
97
src/git/README.md
Normal file
97
src/git/README.md
Normal file
@@ -0,0 +1,97 @@
|
||||
# mcp-git
|
||||
|
||||
A Model Context Protocol server for Git repository interaction and automation. This server provides tools to read, search, and manipulate Git repositories via Large Language Models.
|
||||
|
||||
## Available Tools
|
||||
|
||||
- `git_read_file`: Read contents of a file at a specific Git reference
|
||||
- `git_list_files`: List all files in a repository or subdirectory
|
||||
- `git_file_history`: Get commit history for a specific file
|
||||
- `git_commit`: Create Git commits with messages and specified files
|
||||
- `git_search_code`: Search repository content with pattern matching
|
||||
- `git_get_diff`: View diffs between Git references
|
||||
- `git_get_repo_structure`: View repository file structure
|
||||
- `git_list_repos`: List available Git repositories
|
||||
|
||||
## Installation
|
||||
|
||||
### Using uv
|
||||
|
||||
When using [`uv`](https://docs.astral.sh/uv/) no specific installation is needed. We will
|
||||
use [`uvx`](https://docs.astral.sh/uv/guides/tools/) to directly run *mcp-git*.
|
||||
|
||||
### Using PIP
|
||||
|
||||
Alternatively you can install `mcp-git` via pip:
|
||||
|
||||
```
|
||||
pip install mcp-git
|
||||
```
|
||||
|
||||
After installation, you can run it as a script using:
|
||||
|
||||
```
|
||||
python -m mcp_git
|
||||
```
|
||||
|
||||
## Configuration
|
||||
### Configure for Zed
|
||||
|
||||
Add to your Zed settings.json:
|
||||
|
||||
```json
|
||||
"experimental.context_servers": {
|
||||
"servers": [
|
||||
{
|
||||
"id": "mcp-git",
|
||||
"executable": "uvx",
|
||||
"args": ["mcp-git"]
|
||||
}
|
||||
]
|
||||
},
|
||||
```
|
||||
|
||||
Alternatively, if using pip installation:
|
||||
|
||||
```json
|
||||
"experimental.context_servers": {
|
||||
"servers": [
|
||||
{
|
||||
"id": "mcp-git",
|
||||
"executable": "python",
|
||||
"args": ["-m", "mcp_git"]
|
||||
}
|
||||
]
|
||||
},
|
||||
```
|
||||
|
||||
### Configure for Claude.app
|
||||
|
||||
Add to your Claude settings:
|
||||
|
||||
```json
|
||||
"mcpServers": {
|
||||
"mcp-git": {
|
||||
"command": "uvx",
|
||||
"args": ["mcp-git"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Alternatively, if using pip installation:
|
||||
|
||||
```json
|
||||
"mcpServers": {
|
||||
"mcp-git": {
|
||||
"command": "python",
|
||||
"args": ["-m", "mcp_git"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
For examples of other MCP servers and implementation patterns, see:
|
||||
https://github.com/modelcontextprotocol/example-servers/
|
||||
|
||||
Pull requests welcome!
|
||||
34
src/git/pyproject.toml
Normal file
34
src/git/pyproject.toml
Normal file
@@ -0,0 +1,34 @@
|
||||
[project]
|
||||
name = "mcp-git"
|
||||
version = "0.1.0"
|
||||
description = "A Model Context Protocol server providing tools to read, search, and manipulate Git repositories programmatically via LLMs"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.11"
|
||||
authors = [{ name = "Anthropic, PBC." }]
|
||||
maintainers = [{ name = "David Soria Parra", email = "davidsp@anthropic.com" }]
|
||||
keywords = ["git", "mcp", "llm", "automation"]
|
||||
license = { text = "MIT" }
|
||||
classifiers = [
|
||||
"Development Status :: 4 - Beta",
|
||||
"Intended Audience :: Developers",
|
||||
"License :: OSI Approved :: MIT License",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
]
|
||||
dependencies = [
|
||||
"click>=8.1.7",
|
||||
"gitpython>=3.1.43",
|
||||
"mcp-python~=0.6.0",
|
||||
"pydantic>=2.0.0",
|
||||
]
|
||||
|
||||
[project.scripts]
|
||||
mcp-git = "mcp_git:main"
|
||||
|
||||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
|
||||
[tool.uv]
|
||||
index-strategy = "unsafe-best-match"
|
||||
dev-dependencies = ["ruff>=0.7.3"]
|
||||
339
src/git/src/mcp_git/__init__.py
Normal file
339
src/git/src/mcp_git/__init__.py
Normal file
@@ -0,0 +1,339 @@
|
||||
import logging
|
||||
import click
|
||||
import anyio
|
||||
import anyio.lowlevel
|
||||
from pathlib import Path
|
||||
from git.types import Sequence
|
||||
from mcp_python.server import Server
|
||||
from mcp_python.server.stdio import stdio_server
|
||||
from mcp_python.types import Tool
|
||||
from mcp_python.server.types import EmbeddedResource, ImageContent
|
||||
from enum import StrEnum
|
||||
import git
|
||||
from git.objects import Blob, Tree
|
||||
from mcp_python import ServerSession
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
from typing import List, Optional
|
||||
|
||||
|
||||
class ReadFileInput(BaseModel):
|
||||
repo_path: str
|
||||
file_path: str
|
||||
ref: str = "HEAD"
|
||||
|
||||
|
||||
class ListFilesInput(BaseModel):
|
||||
repo_path: str
|
||||
path: str = ""
|
||||
ref: str = "HEAD"
|
||||
|
||||
|
||||
class FileHistoryInput(BaseModel):
|
||||
repo_path: str
|
||||
file_path: str
|
||||
max_entries: int = 10
|
||||
|
||||
|
||||
class CommitInput(BaseModel):
|
||||
repo_path: str
|
||||
message: str
|
||||
files: Optional[List[str]] = Field(
|
||||
None,
|
||||
description="List of files to stage and commit. If omitted, all changes will be staged.",
|
||||
)
|
||||
|
||||
|
||||
class SearchCodeInput(BaseModel):
|
||||
repo_path: str
|
||||
query: str
|
||||
file_pattern: str = "*"
|
||||
ref: str = "HEAD"
|
||||
|
||||
|
||||
class GetDiffInput(BaseModel):
|
||||
repo_path: str
|
||||
ref1: str
|
||||
ref2: str
|
||||
file_path: Optional[str] = None
|
||||
|
||||
|
||||
class GetRepoStructureInput(BaseModel):
|
||||
repo_path: str
|
||||
ref: str = "HEAD"
|
||||
|
||||
|
||||
class ListReposInput(BaseModel):
|
||||
pass
|
||||
|
||||
|
||||
class GitTools(StrEnum):
|
||||
READ_FILE = "git_read_file"
|
||||
LIST_FILES = "git_list_files"
|
||||
FILE_HISTORY = "git_file_history"
|
||||
COMMIT = "git_commit"
|
||||
SEARCH_CODE = "git_search_code"
|
||||
GET_DIFF = "git_get_diff"
|
||||
GET_REPO_STRUCTURE = "git_get_repo_structure"
|
||||
LIST_REPOS = "git_list_repos"
|
||||
|
||||
|
||||
def git_read_file(repo: git.Repo, file_path: str, ref: str = "HEAD") -> str:
|
||||
tree = repo.commit(ref).tree
|
||||
blob = tree / file_path
|
||||
return blob.data_stream.read().decode("utf-8", errors="replace")
|
||||
|
||||
|
||||
def git_list_files(repo: git.Repo, path: str = "", ref: str = "HEAD") -> Sequence[str]:
|
||||
tree = repo.commit(ref).tree
|
||||
if path:
|
||||
tree = tree / path
|
||||
# Use traverse() and isinstance() to get only blobs (files) recursively
|
||||
return [str(o.path) for o in tree.traverse() if isinstance(o, Blob)]
|
||||
|
||||
|
||||
def git_file_history(
|
||||
repo: git.Repo, file_path: str, max_entries: int = 10
|
||||
) -> Sequence[str]:
|
||||
commits = list(repo.iter_commits(paths=file_path, max_count=max_entries))
|
||||
history = []
|
||||
for commit in commits:
|
||||
history.append(
|
||||
f"Commit: {commit.hexsha}\n"
|
||||
f"Author: {commit.author}\n"
|
||||
f"Date: {commit.authored_datetime}\n"
|
||||
f"Message: {commit.message}\n"
|
||||
)
|
||||
return history
|
||||
|
||||
|
||||
def git_commit(repo: git.Repo, message: str, files: list[str] | None = None) -> str:
|
||||
if files is not None:
|
||||
repo.index.add(files)
|
||||
else:
|
||||
repo.index.add("*") # Stage all changes
|
||||
commit = repo.index.commit(message)
|
||||
return f"Changes committed successfully with hash {commit.hexsha}"
|
||||
|
||||
|
||||
def git_search_code(
|
||||
repo: git.Repo, query: str, file_pattern: str = "*", ref: str = "HEAD"
|
||||
) -> list[str]:
|
||||
results = []
|
||||
tree = repo.commit(ref).tree
|
||||
for blob in tree.traverse():
|
||||
if isinstance(blob, Blob) and Path(blob.path).match(file_pattern):
|
||||
content = blob.data_stream.read().decode("utf-8")
|
||||
for i, line in enumerate(content.splitlines()):
|
||||
if query in line:
|
||||
results.append(f"{blob.path}:{i+1}: {line}")
|
||||
return results
|
||||
|
||||
|
||||
def git_get_diff(
|
||||
repo: git.Repo, ref1: str, ref2: str, file_path: str | None = None
|
||||
) -> str:
|
||||
if file_path:
|
||||
return repo.git.diff(ref1, ref2, "--", file_path)
|
||||
return repo.git.diff(ref1, ref2)
|
||||
|
||||
|
||||
def git_get_repo_structure(repo: git.Repo, ref: str = "HEAD") -> str:
|
||||
tree = repo.commit(ref).tree
|
||||
|
||||
def build_tree(tree_obj: Tree) -> dict:
|
||||
result = {}
|
||||
for item in tree_obj:
|
||||
if isinstance(item, Tree):
|
||||
result[item.name] = build_tree(item)
|
||||
else:
|
||||
result[item.name] = item.type
|
||||
return result
|
||||
|
||||
structure = build_tree(tree)
|
||||
return str(structure)
|
||||
|
||||
|
||||
async def serve(repository: Path | None) -> None:
|
||||
# Set up logging
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
if repository is not None:
|
||||
try:
|
||||
git.Repo(repository)
|
||||
except git.InvalidGitRepositoryError:
|
||||
logger.error(f"{repository} is not a valid Git repository")
|
||||
return
|
||||
|
||||
# Create server
|
||||
server = Server("git-mcp")
|
||||
|
||||
@server.list_tools()
|
||||
async def list_tools() -> list[Tool]:
|
||||
return [
|
||||
Tool(
|
||||
name=GitTools.READ_FILE,
|
||||
description="Retrieves and returns the content of a specified file from "
|
||||
"a Git repository at a given reference (commit, branch, or tag). This "
|
||||
"allows you to view file contents at any point in the repository's "
|
||||
"history.",
|
||||
inputSchema=ReadFileInput.schema(),
|
||||
),
|
||||
Tool(
|
||||
name=GitTools.LIST_FILES,
|
||||
description="Enumerates all files in a Git repository or a specific "
|
||||
"directory within the repository. This tool can be used to explore the "
|
||||
"file structure of a project at a particular reference.",
|
||||
inputSchema=ListFilesInput.schema(),
|
||||
),
|
||||
Tool(
|
||||
name=GitTools.FILE_HISTORY,
|
||||
description="Retrieves the commit history for a specific file, showing "
|
||||
"how it has changed over time. This includes commit hashes, authors, "
|
||||
"dates, and commit messages, allowing you to track the evolution of a "
|
||||
"file.",
|
||||
inputSchema=FileHistoryInput.schema(),
|
||||
),
|
||||
Tool(
|
||||
name=GitTools.COMMIT,
|
||||
description="Commits changes to the repository. You can "
|
||||
"specify particular files to commit or commit all staged changes. This "
|
||||
"tool allows you to create new snapshots of your project with "
|
||||
"descriptive commit messages.",
|
||||
inputSchema=CommitInput.schema(),
|
||||
),
|
||||
Tool(
|
||||
name=GitTools.SEARCH_CODE,
|
||||
description="Searches for specific patterns or text across all files in "
|
||||
"the repository. This powerful tool allows you to find occurrences of "
|
||||
"code, comments, or any text within your project, optionally filtering "
|
||||
"by file patterns and at a specific reference.",
|
||||
inputSchema=SearchCodeInput.schema(),
|
||||
),
|
||||
Tool(
|
||||
name=GitTools.GET_DIFF,
|
||||
description="Computes and displays the differences between two Git "
|
||||
"references (commits, branches, or tags). This tool is crucial for "
|
||||
"understanding changes between different versions of your codebase, "
|
||||
"optionally focusing on a specific file.",
|
||||
inputSchema=GetDiffInput.schema(),
|
||||
),
|
||||
Tool(
|
||||
name=GitTools.GET_REPO_STRUCTURE,
|
||||
description="Generates a representation of the repository's file and "
|
||||
"directory structure at a given reference. This provides a high-level "
|
||||
"overview of your project's organization, helping you understand the "
|
||||
"layout of your codebase.",
|
||||
inputSchema=GetRepoStructureInput.schema(),
|
||||
),
|
||||
Tool(
|
||||
name=GitTools.LIST_REPOS,
|
||||
description="Enumerates all available Git repositories from the "
|
||||
"specified roots. This tool helps you manage and navigate multiple "
|
||||
"repositories, providing a comprehensive list of Git projects "
|
||||
"accessible to the current session.",
|
||||
inputSchema=ListReposInput.schema(),
|
||||
),
|
||||
]
|
||||
|
||||
async def list_repos() -> Sequence[str]:
|
||||
async def by_roots() -> Sequence[str]:
|
||||
if not isinstance(server.request_context.session, ServerSession):
|
||||
raise TypeError(
|
||||
"server.request_context.session must be a ServerSession"
|
||||
)
|
||||
|
||||
roots_result = await server.request_context.session.list_roots()
|
||||
logger.debug(f"Roots result: {roots_result}")
|
||||
repo_paths = []
|
||||
for root in roots_result.roots:
|
||||
path = root.uri.path
|
||||
try:
|
||||
# Verify this is a git repo
|
||||
git.Repo(path)
|
||||
repo_paths.append(str(path))
|
||||
except git.InvalidGitRepositoryError:
|
||||
pass
|
||||
return repo_paths
|
||||
|
||||
def by_commandline() -> Sequence[str]:
|
||||
return [str(repository)] if repository is not None else []
|
||||
|
||||
cmd_repos = by_commandline()
|
||||
root_repos = await by_roots()
|
||||
return [*root_repos, *cmd_repos]
|
||||
|
||||
@server.call_tool()
|
||||
async def call_tool(
|
||||
name: str, arguments: dict
|
||||
) -> Sequence[str | ImageContent | EmbeddedResource]:
|
||||
if name == GitTools.LIST_REPOS:
|
||||
return await list_repos()
|
||||
|
||||
repo_path = Path(arguments["repo_path"])
|
||||
repo = git.Repo(repo_path)
|
||||
|
||||
match name:
|
||||
case GitTools.READ_FILE:
|
||||
return [
|
||||
git_read_file(
|
||||
repo, arguments["file_path"], arguments.get("ref", "HEAD")
|
||||
)
|
||||
]
|
||||
|
||||
case GitTools.LIST_FILES:
|
||||
return [
|
||||
str(f)
|
||||
for f in git_list_files(
|
||||
repo, arguments.get("path", ""), arguments.get("ref", "HEAD")
|
||||
)
|
||||
]
|
||||
|
||||
case GitTools.FILE_HISTORY:
|
||||
return git_file_history(
|
||||
repo, arguments["file_path"], arguments.get("max_entries", 10)
|
||||
)
|
||||
|
||||
case GitTools.COMMIT:
|
||||
result = git_commit(repo, arguments["message"], arguments.get("files"))
|
||||
return [result]
|
||||
|
||||
case GitTools.SEARCH_CODE:
|
||||
return git_search_code(
|
||||
repo,
|
||||
arguments["query"],
|
||||
arguments.get("file_pattern", "*"),
|
||||
arguments.get("ref", "HEAD"),
|
||||
)
|
||||
|
||||
case GitTools.GET_DIFF:
|
||||
return [
|
||||
git_get_diff(
|
||||
repo,
|
||||
arguments["ref1"],
|
||||
arguments["ref2"],
|
||||
arguments.get("file_path"),
|
||||
)
|
||||
]
|
||||
|
||||
case GitTools.GET_REPO_STRUCTURE:
|
||||
return [git_get_repo_structure(repo, arguments.get("ref", "HEAD"))]
|
||||
|
||||
case _:
|
||||
raise ValueError(f"Unknown tool: {name}")
|
||||
|
||||
# Run the server
|
||||
options = server.create_initialization_options()
|
||||
async with stdio_server() as (read_stream, write_stream):
|
||||
await server.run(read_stream, write_stream, options)
|
||||
|
||||
|
||||
@click.command()
|
||||
@click.option("-r", "--repository", type=click.Path(path_type=Path, dir_okay=True))
|
||||
def main(repository: Path | None):
|
||||
anyio.run(serve, repository)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
5
src/git/src/mcp_git/__main__.py
Normal file
5
src/git/src/mcp_git/__main__.py
Normal file
@@ -0,0 +1,5 @@
|
||||
# __main__.py
|
||||
|
||||
from mcp_git import main
|
||||
|
||||
main()
|
||||
332
src/git/uv.lock
generated
Normal file
332
src/git/uv.lock
generated
Normal file
@@ -0,0 +1,332 @@
|
||||
version = 1
|
||||
requires-python = ">=3.11"
|
||||
resolution-markers = [
|
||||
"python_full_version < '3.13'",
|
||||
"python_full_version >= '3.13'",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "annotated-types"
|
||||
version = "0.7.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyio"
|
||||
version = "4.6.2.post1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "idna" },
|
||||
{ name = "sniffio" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/9f/09/45b9b7a6d4e45c6bcb5bf61d19e3ab87df68e0601fa8c5293de3542546cc/anyio-4.6.2.post1.tar.gz", hash = "sha256:4c8bc31ccdb51c7f7bd251f51c609e038d63e34219b44aa86e47576389880b4c", size = 173422 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e4/f5/f2b75d2fc6f1a260f340f0e7c6a060f4dd2961cc16884ed851b0d18da06a/anyio-4.6.2.post1-py3-none-any.whl", hash = "sha256:6d170c36fba3bdd840c73d3868c1e777e33676a69c3a72cf0a0d5d6d8009b61d", size = 90377 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "certifi"
|
||||
version = "2024.8.30"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b0/ee/9b19140fe824b367c04c5e1b369942dd754c4c5462d5674002f75c4dedc1/certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9", size = 168507 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/12/90/3c9ff0512038035f59d279fddeb79f5f1eccd8859f06d6163c58798b9487/certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8", size = 167321 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "click"
|
||||
version = "8.1.7"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "colorama", marker = "platform_system == 'Windows'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/96/d3/f04c7bfcf5c1862a2a5b845c6b2b360488cf47af55dfa79c98f6a6bf98b5/click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de", size = 336121 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/00/2e/d53fa4befbf2cfa713304affc7ca780ce4fc1fd8710527771b58311a3229/click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28", size = 97941 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "colorama"
|
||||
version = "0.4.6"
|
||||
source = { registry = "https://artifactory.infra.ant.dev/artifactory/api/pypi/pypi-internal/simple" }
|
||||
wheels = [
|
||||
{ url = "https://artifactory.infra.ant.dev/artifactory/api/pypi/pypi-internal/colorama/0.4.6/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gitdb"
|
||||
version = "4.0.11"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "smmap" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/19/0d/bbb5b5ee188dec84647a4664f3e11b06ade2bde568dbd489d9d64adef8ed/gitdb-4.0.11.tar.gz", hash = "sha256:bf5421126136d6d0af55bc1e7c1af1c397a34f5b7bd79e776cd3e89785c2b04b", size = 394469 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/fd/5b/8f0c4a5bb9fd491c277c21eff7ccae71b47d43c4446c9d0c6cff2fe8c2c4/gitdb-4.0.11-py3-none-any.whl", hash = "sha256:81a3407ddd2ee8df444cbacea00e2d038e40150acfa3001696fe0dcf1d3adfa4", size = 62721 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gitpython"
|
||||
version = "3.1.43"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "gitdb" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b6/a1/106fd9fa2dd989b6fb36e5893961f82992cf676381707253e0bf93eb1662/GitPython-3.1.43.tar.gz", hash = "sha256:35f314a9f878467f5453cc1fee295c3e18e52f1b99f10f6cf5b1682e968a9e7c", size = 214149 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e9/bd/cc3a402a6439c15c3d4294333e13042b915bbeab54edc457c723931fed3f/GitPython-3.1.43-py3-none-any.whl", hash = "sha256:eec7ec56b92aad751f9912a73404bc02ba212a23adb2c7098ee668417051a1ff", size = 207337 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "h11"
|
||||
version = "0.14.0"
|
||||
source = { registry = "https://artifactory.infra.ant.dev/artifactory/api/pypi/pypi-internal/simple" }
|
||||
wheels = [
|
||||
{ url = "https://artifactory.infra.ant.dev/artifactory/api/pypi/pypi-internal/h11/0.14.0/h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "httpcore"
|
||||
version = "1.0.6"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "certifi" },
|
||||
{ name = "h11" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b6/44/ed0fa6a17845fb033bd885c03e842f08c1b9406c86a2e60ac1ae1b9206a6/httpcore-1.0.6.tar.gz", hash = "sha256:73f6dbd6eb8c21bbf7ef8efad555481853f5f6acdeaff1edb0694289269ee17f", size = 85180 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/06/89/b161908e2f51be56568184aeb4a880fd287178d176fd1c860d2217f41106/httpcore-1.0.6-py3-none-any.whl", hash = "sha256:27b59625743b85577a8c0e10e55b50b5368a4f2cfe8cc7bcfa9cf00829c2682f", size = 78011 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "httpx"
|
||||
version = "0.27.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "anyio" },
|
||||
{ name = "certifi" },
|
||||
{ name = "httpcore" },
|
||||
{ name = "idna" },
|
||||
{ name = "sniffio" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/78/82/08f8c936781f67d9e6b9eeb8a0c8b4e406136ea4c3d1f89a5db71d42e0e6/httpx-0.27.2.tar.gz", hash = "sha256:f7c2be1d2f3c3c3160d441802406b206c2b76f5947b11115e6df10c6c65e66c2", size = 144189 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/56/95/9377bcb415797e44274b51d46e3249eba641711cf3348050f76ee7b15ffc/httpx-0.27.2-py3-none-any.whl", hash = "sha256:7bb2708e112d8fdd7829cd4243970f0c223274051cb35ee80c03301ee29a3df0", size = 76395 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "httpx-sse"
|
||||
version = "0.4.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/4c/60/8f4281fa9bbf3c8034fd54c0e7412e66edbab6bc74c4996bd616f8d0406e/httpx-sse-0.4.0.tar.gz", hash = "sha256:1e81a3a3070ce322add1d3529ed42eb5f70817f45ed6ec915ab753f961139721", size = 12624 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e1/9b/a181f281f65d776426002f330c31849b86b31fc9d848db62e16f03ff739f/httpx_sse-0.4.0-py3-none-any.whl", hash = "sha256:f329af6eae57eaa2bdfd962b42524764af68075ea87370a2de920af5341e318f", size = 7819 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "3.10"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mcp-git"
|
||||
version = "0.1.0"
|
||||
source = { editable = "." }
|
||||
dependencies = [
|
||||
{ name = "click" },
|
||||
{ name = "gitpython" },
|
||||
{ name = "mcp-python" },
|
||||
{ name = "pydantic" },
|
||||
]
|
||||
|
||||
[package.dev-dependencies]
|
||||
dev = [
|
||||
{ name = "ruff" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "click", specifier = ">=8.1.7" },
|
||||
{ name = "gitpython", specifier = ">=3.1.43" },
|
||||
{ name = "mcp-python", specifier = "~=0.6.0" },
|
||||
{ name = "pydantic", specifier = ">=2.0.0" },
|
||||
]
|
||||
|
||||
[package.metadata.requires-dev]
|
||||
dev = [{ name = "ruff", specifier = ">=0.7.3" }]
|
||||
|
||||
[[package]]
|
||||
name = "mcp-python"
|
||||
version = "0.6.1"
|
||||
source = { registry = "https://artifactory.infra.ant.dev/artifactory/api/pypi/pypi-internal/simple" }
|
||||
dependencies = [
|
||||
{ name = "anyio" },
|
||||
{ name = "httpx" },
|
||||
{ name = "httpx-sse" },
|
||||
{ name = "pydantic" },
|
||||
{ name = "sse-starlette" },
|
||||
{ name = "starlette" },
|
||||
]
|
||||
sdist = { url = "https://artifactory.infra.ant.dev/artifactory/api/pypi/pypi-internal/mcp-python/0.6.1/mcp_python-0.6.1.tar.gz", hash = "sha256:e8a2a6067e36b5790397d678686ffe1642ca1d2249f667d3b9f44bcf3b506b1c" }
|
||||
wheels = [
|
||||
{ url = "https://artifactory.infra.ant.dev/artifactory/api/pypi/pypi-internal/mcp-python/0.6.1/mcp_python-0.6.1-py3-none-any.whl", hash = "sha256:812cf7e7da61b6ca5a2498150d152417bdd8519a0ee24a6964442f473599aa8c" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pydantic"
|
||||
version = "2.9.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "annotated-types" },
|
||||
{ name = "pydantic-core" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/a9/b7/d9e3f12af310e1120c21603644a1cd86f59060e040ec5c3a80b8f05fae30/pydantic-2.9.2.tar.gz", hash = "sha256:d155cef71265d1e9807ed1c32b4c8deec042a44a50a4188b25ac67ecd81a9c0f", size = 769917 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/df/e4/ba44652d562cbf0bf320e0f3810206149c8a4e99cdbf66da82e97ab53a15/pydantic-2.9.2-py3-none-any.whl", hash = "sha256:f048cec7b26778210e28a0459867920654d48e5e62db0958433636cde4254f12", size = 434928 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pydantic-core"
|
||||
version = "2.23.4"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/e2/aa/6b6a9b9f8537b872f552ddd46dd3da230367754b6f707b8e1e963f515ea3/pydantic_core-2.23.4.tar.gz", hash = "sha256:2584f7cf844ac4d970fba483a717dbe10c1c1c96a969bf65d61ffe94df1b2863", size = 402156 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/5d/30/890a583cd3f2be27ecf32b479d5d615710bb926d92da03e3f7838ff3e58b/pydantic_core-2.23.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:77733e3892bb0a7fa797826361ce8a9184d25c8dffaec60b7ffe928153680ba8", size = 1865160 },
|
||||
{ url = "https://files.pythonhosted.org/packages/1d/9a/b634442e1253bc6889c87afe8bb59447f106ee042140bd57680b3b113ec7/pydantic_core-2.23.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b84d168f6c48fabd1f2027a3d1bdfe62f92cade1fb273a5d68e621da0e44e6d", size = 1776777 },
|
||||
{ url = "https://files.pythonhosted.org/packages/75/9a/7816295124a6b08c24c96f9ce73085032d8bcbaf7e5a781cd41aa910c891/pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df49e7a0861a8c36d089c1ed57d308623d60416dab2647a4a17fe050ba85de0e", size = 1799244 },
|
||||
{ url = "https://files.pythonhosted.org/packages/a9/8f/89c1405176903e567c5f99ec53387449e62f1121894aa9fc2c4fdc51a59b/pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ff02b6d461a6de369f07ec15e465a88895f3223eb75073ffea56b84d9331f607", size = 1805307 },
|
||||
{ url = "https://files.pythonhosted.org/packages/d5/a5/1a194447d0da1ef492e3470680c66048fef56fc1f1a25cafbea4bc1d1c48/pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:996a38a83508c54c78a5f41456b0103c30508fed9abcad0a59b876d7398f25fd", size = 2000663 },
|
||||
{ url = "https://files.pythonhosted.org/packages/13/a5/1df8541651de4455e7d587cf556201b4f7997191e110bca3b589218745a5/pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d97683ddee4723ae8c95d1eddac7c192e8c552da0c73a925a89fa8649bf13eea", size = 2655941 },
|
||||
{ url = "https://files.pythonhosted.org/packages/44/31/a3899b5ce02c4316865e390107f145089876dff7e1dfc770a231d836aed8/pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:216f9b2d7713eb98cb83c80b9c794de1f6b7e3145eef40400c62e86cee5f4e1e", size = 2052105 },
|
||||
{ url = "https://files.pythonhosted.org/packages/1b/aa/98e190f8745d5ec831f6d5449344c48c0627ac5fed4e5340a44b74878f8e/pydantic_core-2.23.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6f783e0ec4803c787bcea93e13e9932edab72068f68ecffdf86a99fd5918878b", size = 1919967 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ae/35/b6e00b6abb2acfee3e8f85558c02a0822e9a8b2f2d812ea8b9079b118ba0/pydantic_core-2.23.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d0776dea117cf5272382634bd2a5c1b6eb16767c223c6a5317cd3e2a757c61a0", size = 1964291 },
|
||||
{ url = "https://files.pythonhosted.org/packages/13/46/7bee6d32b69191cd649bbbd2361af79c472d72cb29bb2024f0b6e350ba06/pydantic_core-2.23.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d5f7a395a8cf1621939692dba2a6b6a830efa6b3cee787d82c7de1ad2930de64", size = 2109666 },
|
||||
{ url = "https://files.pythonhosted.org/packages/39/ef/7b34f1b122a81b68ed0a7d0e564da9ccdc9a2924c8d6c6b5b11fa3a56970/pydantic_core-2.23.4-cp311-none-win32.whl", hash = "sha256:74b9127ffea03643e998e0c5ad9bd3811d3dac8c676e47db17b0ee7c3c3bf35f", size = 1732940 },
|
||||
{ url = "https://files.pythonhosted.org/packages/2f/76/37b7e76c645843ff46c1d73e046207311ef298d3f7b2f7d8f6ac60113071/pydantic_core-2.23.4-cp311-none-win_amd64.whl", hash = "sha256:98d134c954828488b153d88ba1f34e14259284f256180ce659e8d83e9c05eaa3", size = 1916804 },
|
||||
{ url = "https://files.pythonhosted.org/packages/74/7b/8e315f80666194b354966ec84b7d567da77ad927ed6323db4006cf915f3f/pydantic_core-2.23.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f3e0da4ebaef65158d4dfd7d3678aad692f7666877df0002b8a522cdf088f231", size = 1856459 },
|
||||
{ url = "https://files.pythonhosted.org/packages/14/de/866bdce10ed808323d437612aca1ec9971b981e1c52e5e42ad9b8e17a6f6/pydantic_core-2.23.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f69a8e0b033b747bb3e36a44e7732f0c99f7edd5cea723d45bc0d6e95377ffee", size = 1770007 },
|
||||
{ url = "https://files.pythonhosted.org/packages/dc/69/8edd5c3cd48bb833a3f7ef9b81d7666ccddd3c9a635225214e044b6e8281/pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:723314c1d51722ab28bfcd5240d858512ffd3116449c557a1336cbe3919beb87", size = 1790245 },
|
||||
{ url = "https://files.pythonhosted.org/packages/80/33/9c24334e3af796ce80d2274940aae38dd4e5676298b4398eff103a79e02d/pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bb2802e667b7051a1bebbfe93684841cc9351004e2badbd6411bf357ab8d5ac8", size = 1801260 },
|
||||
{ url = "https://files.pythonhosted.org/packages/a5/6f/e9567fd90104b79b101ca9d120219644d3314962caa7948dd8b965e9f83e/pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d18ca8148bebe1b0a382a27a8ee60350091a6ddaf475fa05ef50dc35b5df6327", size = 1996872 },
|
||||
{ url = "https://files.pythonhosted.org/packages/2d/ad/b5f0fe9e6cfee915dd144edbd10b6e9c9c9c9d7a56b69256d124b8ac682e/pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33e3d65a85a2a4a0dc3b092b938a4062b1a05f3a9abde65ea93b233bca0e03f2", size = 2661617 },
|
||||
{ url = "https://files.pythonhosted.org/packages/06/c8/7d4b708f8d05a5cbfda3243aad468052c6e99de7d0937c9146c24d9f12e9/pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:128585782e5bfa515c590ccee4b727fb76925dd04a98864182b22e89a4e6ed36", size = 2071831 },
|
||||
{ url = "https://files.pythonhosted.org/packages/89/4d/3079d00c47f22c9a9a8220db088b309ad6e600a73d7a69473e3a8e5e3ea3/pydantic_core-2.23.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:68665f4c17edcceecc112dfed5dbe6f92261fb9d6054b47d01bf6371a6196126", size = 1917453 },
|
||||
{ url = "https://files.pythonhosted.org/packages/e9/88/9df5b7ce880a4703fcc2d76c8c2d8eb9f861f79d0c56f4b8f5f2607ccec8/pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:20152074317d9bed6b7a95ade3b7d6054845d70584216160860425f4fbd5ee9e", size = 1968793 },
|
||||
{ url = "https://files.pythonhosted.org/packages/e3/b9/41f7efe80f6ce2ed3ee3c2dcfe10ab7adc1172f778cc9659509a79518c43/pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9261d3ce84fa1d38ed649c3638feefeae23d32ba9182963e465d58d62203bd24", size = 2116872 },
|
||||
{ url = "https://files.pythonhosted.org/packages/63/08/b59b7a92e03dd25554b0436554bf23e7c29abae7cce4b1c459cd92746811/pydantic_core-2.23.4-cp312-none-win32.whl", hash = "sha256:4ba762ed58e8d68657fc1281e9bb72e1c3e79cc5d464be146e260c541ec12d84", size = 1738535 },
|
||||
{ url = "https://files.pythonhosted.org/packages/88/8d/479293e4d39ab409747926eec4329de5b7129beaedc3786eca070605d07f/pydantic_core-2.23.4-cp312-none-win_amd64.whl", hash = "sha256:97df63000f4fea395b2824da80e169731088656d1818a11b95f3b173747b6cd9", size = 1917992 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ad/ef/16ee2df472bf0e419b6bc68c05bf0145c49247a1095e85cee1463c6a44a1/pydantic_core-2.23.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7530e201d10d7d14abce4fb54cfe5b94a0aefc87da539d0346a484ead376c3cc", size = 1856143 },
|
||||
{ url = "https://files.pythonhosted.org/packages/da/fa/bc3dbb83605669a34a93308e297ab22be82dfb9dcf88c6cf4b4f264e0a42/pydantic_core-2.23.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:df933278128ea1cd77772673c73954e53a1c95a4fdf41eef97c2b779271bd0bd", size = 1770063 },
|
||||
{ url = "https://files.pythonhosted.org/packages/4e/48/e813f3bbd257a712303ebdf55c8dc46f9589ec74b384c9f652597df3288d/pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cb3da3fd1b6a5d0279a01877713dbda118a2a4fc6f0d821a57da2e464793f05", size = 1790013 },
|
||||
{ url = "https://files.pythonhosted.org/packages/b4/e0/56eda3a37929a1d297fcab1966db8c339023bcca0b64c5a84896db3fcc5c/pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:42c6dcb030aefb668a2b7009c85b27f90e51e6a3b4d5c9bc4c57631292015b0d", size = 1801077 },
|
||||
{ url = "https://files.pythonhosted.org/packages/04/be/5e49376769bfbf82486da6c5c1683b891809365c20d7c7e52792ce4c71f3/pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:696dd8d674d6ce621ab9d45b205df149399e4bb9aa34102c970b721554828510", size = 1996782 },
|
||||
{ url = "https://files.pythonhosted.org/packages/bc/24/e3ee6c04f1d58cc15f37bcc62f32c7478ff55142b7b3e6d42ea374ea427c/pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2971bb5ffe72cc0f555c13e19b23c85b654dd2a8f7ab493c262071377bfce9f6", size = 2661375 },
|
||||
{ url = "https://files.pythonhosted.org/packages/c1/f8/11a9006de4e89d016b8de74ebb1db727dc100608bb1e6bbe9d56a3cbbcce/pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8394d940e5d400d04cad4f75c0598665cbb81aecefaca82ca85bd28264af7f9b", size = 2071635 },
|
||||
{ url = "https://files.pythonhosted.org/packages/7c/45/bdce5779b59f468bdf262a5bc9eecbae87f271c51aef628d8c073b4b4b4c/pydantic_core-2.23.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0dff76e0602ca7d4cdaacc1ac4c005e0ce0dcfe095d5b5259163a80d3a10d327", size = 1916994 },
|
||||
{ url = "https://files.pythonhosted.org/packages/d8/fa/c648308fe711ee1f88192cad6026ab4f925396d1293e8356de7e55be89b5/pydantic_core-2.23.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7d32706badfe136888bdea71c0def994644e09fff0bfe47441deaed8e96fdbc6", size = 1968877 },
|
||||
{ url = "https://files.pythonhosted.org/packages/16/16/b805c74b35607d24d37103007f899abc4880923b04929547ae68d478b7f4/pydantic_core-2.23.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ed541d70698978a20eb63d8c5d72f2cc6d7079d9d90f6b50bad07826f1320f5f", size = 2116814 },
|
||||
{ url = "https://files.pythonhosted.org/packages/d1/58/5305e723d9fcdf1c5a655e6a4cc2a07128bf644ff4b1d98daf7a9dbf57da/pydantic_core-2.23.4-cp313-none-win32.whl", hash = "sha256:3d5639516376dce1940ea36edf408c554475369f5da2abd45d44621cb616f769", size = 1738360 },
|
||||
{ url = "https://files.pythonhosted.org/packages/a5/ae/e14b0ff8b3f48e02394d8acd911376b7b66e164535687ef7dc24ea03072f/pydantic_core-2.23.4-cp313-none-win_amd64.whl", hash = "sha256:5a1504ad17ba4210df3a045132a7baeeba5a200e930f57512ee02909fc5c4cb5", size = 1919411 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.7.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/4b/06/09d1276df977eece383d0ed66052fc24ec4550a61f8fbc0a11200e690496/ruff-0.7.3.tar.gz", hash = "sha256:e1d1ba2e40b6e71a61b063354d04be669ab0d39c352461f3d789cac68b54a313", size = 3243664 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/c0/56/933d433c2489e4642487b835f53dd9ff015fb3d8fa459b09bb2ce42d7c4b/ruff-0.7.3-py3-none-linux_armv6l.whl", hash = "sha256:34f2339dc22687ec7e7002792d1f50712bf84a13d5152e75712ac08be565d344", size = 10372090 },
|
||||
{ url = "https://files.pythonhosted.org/packages/20/ea/1f0a22a6bcdd3fc26c73f63a025d05bd565901b729d56bcb093c722a6c4c/ruff-0.7.3-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:fb397332a1879b9764a3455a0bb1087bda876c2db8aca3a3cbb67b3dbce8cda0", size = 10190037 },
|
||||
{ url = "https://files.pythonhosted.org/packages/16/74/aca75666e0d481fe394e76a8647c44ea919087748024924baa1a17371e3e/ruff-0.7.3-py3-none-macosx_11_0_arm64.whl", hash = "sha256:37d0b619546103274e7f62643d14e1adcbccb242efda4e4bdb9544d7764782e9", size = 9811998 },
|
||||
{ url = "https://files.pythonhosted.org/packages/20/a1/cf446a0d7f78ea1f0bd2b9171c11dfe746585c0c4a734b25966121eb4f5d/ruff-0.7.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d59f0c3ee4d1a6787614e7135b72e21024875266101142a09a61439cb6e38a5", size = 10620626 },
|
||||
{ url = "https://files.pythonhosted.org/packages/cd/c1/82b27d09286ae855f5d03b1ad37cf243f21eb0081732d4d7b0d658d439cb/ruff-0.7.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:44eb93c2499a169d49fafd07bc62ac89b1bc800b197e50ff4633aed212569299", size = 10177598 },
|
||||
{ url = "https://files.pythonhosted.org/packages/b9/42/c0acac22753bf74013d035a5ef6c5c4c40ad4d6686bfb3fda7c6f37d9b37/ruff-0.7.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6d0242ce53f3a576c35ee32d907475a8d569944c0407f91d207c8af5be5dae4e", size = 11171963 },
|
||||
{ url = "https://files.pythonhosted.org/packages/43/18/bb0befb7fb9121dd9009e6a72eb98e24f1bacb07c6f3ecb55f032ba98aed/ruff-0.7.3-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:6b6224af8b5e09772c2ecb8dc9f3f344c1aa48201c7f07e7315367f6dd90ac29", size = 11856157 },
|
||||
{ url = "https://files.pythonhosted.org/packages/5e/91/04e98d7d6e32eca9d1372be595f9abc7b7f048795e32eb2edbd8794d50bd/ruff-0.7.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c50f95a82b94421c964fae4c27c0242890a20fe67d203d127e84fbb8013855f5", size = 11440331 },
|
||||
{ url = "https://files.pythonhosted.org/packages/f5/dc/3fe99f2ce10b76d389041a1b9f99e7066332e479435d4bebcceea16caff5/ruff-0.7.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7f3eff9961b5d2644bcf1616c606e93baa2d6b349e8aa8b035f654df252c8c67", size = 12725354 },
|
||||
{ url = "https://files.pythonhosted.org/packages/43/7b/1daa712de1c5bc6cbbf9fa60e9c41cc48cda962dc6d2c4f2a224d2c3007e/ruff-0.7.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8963cab06d130c4df2fd52c84e9f10d297826d2e8169ae0c798b6221be1d1d2", size = 11010091 },
|
||||
{ url = "https://files.pythonhosted.org/packages/b6/db/1227a903587432eb569e57a95b15a4f191a71fe315cde4c0312df7bc85da/ruff-0.7.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:61b46049d6edc0e4317fb14b33bd693245281a3007288b68a3f5b74a22a0746d", size = 10610687 },
|
||||
{ url = "https://files.pythonhosted.org/packages/db/e2/dc41ee90c3085aadad4da614d310d834f641aaafddf3dfbba08210c616ce/ruff-0.7.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:10ebce7696afe4644e8c1a23b3cf8c0f2193a310c18387c06e583ae9ef284de2", size = 10254843 },
|
||||
{ url = "https://files.pythonhosted.org/packages/6f/09/5f6cac1c91542bc5bd33d40b4c13b637bf64d7bb29e091dadb01b62527fe/ruff-0.7.3-py3-none-musllinux_1_2_i686.whl", hash = "sha256:3f36d56326b3aef8eeee150b700e519880d1aab92f471eefdef656fd57492aa2", size = 10730962 },
|
||||
{ url = "https://files.pythonhosted.org/packages/d3/42/89a4b9a24ef7d00269e24086c417a006f9a3ffeac2c80f2629eb5ce140ee/ruff-0.7.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:5d024301109a0007b78d57ab0ba190087b43dce852e552734ebf0b0b85e4fb16", size = 11101907 },
|
||||
{ url = "https://files.pythonhosted.org/packages/b0/5c/efdb4777686683a8edce94ffd812783bddcd3d2454d38c5ac193fef7c500/ruff-0.7.3-py3-none-win32.whl", hash = "sha256:4ba81a5f0c5478aa61674c5a2194de8b02652f17addf8dfc40c8937e6e7d79fc", size = 8611095 },
|
||||
{ url = "https://files.pythonhosted.org/packages/bb/b8/28fbc6a4efa50178f973972d1c84b2d0a33cdc731588522ab751ac3da2f5/ruff-0.7.3-py3-none-win_amd64.whl", hash = "sha256:588a9ff2fecf01025ed065fe28809cd5a53b43505f48b69a1ac7707b1b7e4088", size = 9418283 },
|
||||
{ url = "https://files.pythonhosted.org/packages/3f/77/b587cba6febd5e2003374f37eb89633f79f161e71084f94057c8653b7fb3/ruff-0.7.3-py3-none-win_arm64.whl", hash = "sha256:1713e2c5545863cdbfe2cbce21f69ffaf37b813bfd1fb3b90dc9a6f1963f5a8c", size = 8725228 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smmap"
|
||||
version = "5.0.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/88/04/b5bf6d21dc4041000ccba7eb17dd3055feb237e7ffc2c20d3fae3af62baa/smmap-5.0.1.tar.gz", hash = "sha256:dceeb6c0028fdb6734471eb07c0cd2aae706ccaecab45965ee83f11c8d3b1f62", size = 22291 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/a7/a5/10f97f73544edcdef54409f1d839f6049a0d79df68adbc1ceb24d1aaca42/smmap-5.0.1-py3-none-any.whl", hash = "sha256:e6d8668fa5f93e706934a62d7b4db19c8d9eb8cf2adbb75ef1b675aa332b69da", size = 24282 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sniffio"
|
||||
version = "1.3.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sse-starlette"
|
||||
version = "2.1.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "anyio" },
|
||||
{ name = "starlette" },
|
||||
{ name = "uvicorn" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/72/fc/56ab9f116b2133521f532fce8d03194cf04dcac25f583cf3d839be4c0496/sse_starlette-2.1.3.tar.gz", hash = "sha256:9cd27eb35319e1414e3d2558ee7414487f9529ce3b3cf9b21434fd110e017169", size = 19678 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/52/aa/36b271bc4fa1d2796311ee7c7283a3a1c348bad426d37293609ca4300eef/sse_starlette-2.1.3-py3-none-any.whl", hash = "sha256:8ec846438b4665b9e8c560fcdea6bc8081a3abf7942faa95e5a744999d219772", size = 9383 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "starlette"
|
||||
version = "0.41.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "anyio" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/3e/da/1fb4bdb72ae12b834becd7e1e7e47001d32f91ec0ce8d7bc1b618d9f0bd9/starlette-0.41.2.tar.gz", hash = "sha256:9834fd799d1a87fd346deb76158668cfa0b0d56f85caefe8268e2d97c3468b62", size = 2573867 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/54/43/f185bfd0ca1d213beb4293bed51d92254df23d8ceaf6c0e17146d508a776/starlette-0.41.2-py3-none-any.whl", hash = "sha256:fbc189474b4731cf30fcef52f18a8d070e3f3b46c6a04c97579e85e6ffca942d", size = 73259 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typing-extensions"
|
||||
version = "4.12.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/df/db/f35a00659bc03fec321ba8bce9420de607a1d37f8342eee1863174c69557/typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8", size = 85321 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", size = 37438 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uvicorn"
|
||||
version = "0.32.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "click" },
|
||||
{ name = "h11" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/e0/fc/1d785078eefd6945f3e5bab5c076e4230698046231eb0f3747bc5c8fa992/uvicorn-0.32.0.tar.gz", hash = "sha256:f78b36b143c16f54ccdb8190d0a26b5f1901fe5a3c777e1ab29f26391af8551e", size = 77564 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/eb/14/78bd0e95dd2444b6caacbca2b730671d4295ccb628ef58b81bee903629df/uvicorn-0.32.0-py3-none-any.whl", hash = "sha256:60b8f3a5ac027dcd31448f411ced12b5ef452c646f76f02f8cc3f25d8d26fd82", size = 63723 },
|
||||
]
|
||||
3
src/postgres/README.md
Normal file
3
src/postgres/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# PostgreSQL server
|
||||
|
||||
This MCP server provides **resources** and **tools** for interacting with a Postgres database.
|
||||
143
src/postgres/index.ts
Normal file
143
src/postgres/index.ts
Normal file
@@ -0,0 +1,143 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
||||
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
||||
import {
|
||||
CallToolRequestSchema,
|
||||
ListResourcesRequestSchema,
|
||||
ListToolsRequestSchema,
|
||||
ReadResourceRequestSchema,
|
||||
} from "@modelcontextprotocol/sdk/types.js";
|
||||
import pg from "pg";
|
||||
|
||||
const server = new Server(
|
||||
{
|
||||
name: "example-servers/postgres",
|
||||
version: "0.1.0",
|
||||
},
|
||||
{
|
||||
capabilities: {
|
||||
resources: {},
|
||||
tools: {},
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const args = process.argv.slice(2);
|
||||
if (args.length === 0) {
|
||||
console.error("Please provide a database URL as a command-line argument");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const databaseUrl = args[0];
|
||||
|
||||
const resourceBaseUrl = new URL(databaseUrl);
|
||||
resourceBaseUrl.protocol = "postgres:";
|
||||
resourceBaseUrl.password = "";
|
||||
|
||||
const pool = new pg.Pool({
|
||||
connectionString: databaseUrl,
|
||||
});
|
||||
|
||||
const SCHEMA_PATH = "schema";
|
||||
|
||||
server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
||||
const client = await pool.connect();
|
||||
try {
|
||||
const result = await client.query(
|
||||
"SELECT table_name FROM information_schema.tables WHERE table_schema = 'public'",
|
||||
);
|
||||
return {
|
||||
resources: result.rows.map((row) => ({
|
||||
uri: new URL(`${row.table_name}/${SCHEMA_PATH}`, resourceBaseUrl).href,
|
||||
mimeType: "application/json",
|
||||
name: `"${row.table_name}" database schema`,
|
||||
})),
|
||||
};
|
||||
} finally {
|
||||
client.release();
|
||||
}
|
||||
});
|
||||
|
||||
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
||||
const resourceUrl = new URL(request.params.uri);
|
||||
|
||||
const pathComponents = resourceUrl.pathname.split("/");
|
||||
const schema = pathComponents.pop();
|
||||
const tableName = pathComponents.pop();
|
||||
|
||||
if (schema !== SCHEMA_PATH) {
|
||||
throw new Error("Invalid resource URI");
|
||||
}
|
||||
|
||||
const client = await pool.connect();
|
||||
try {
|
||||
const result = await client.query(
|
||||
"SELECT column_name, data_type FROM information_schema.columns WHERE table_name = $1",
|
||||
[tableName],
|
||||
);
|
||||
|
||||
return {
|
||||
contents: [
|
||||
{
|
||||
uri: request.params.uri,
|
||||
mimeType: "application/json",
|
||||
text: JSON.stringify(result.rows, null, 2),
|
||||
},
|
||||
],
|
||||
};
|
||||
} finally {
|
||||
client.release();
|
||||
}
|
||||
});
|
||||
|
||||
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
||||
return {
|
||||
tools: [
|
||||
{
|
||||
name: "query",
|
||||
description: "Run a read-only SQL query",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
sql: { type: "string" },
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
});
|
||||
|
||||
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
||||
if (request.params.name === "query") {
|
||||
const sql = request.params.arguments?.sql as string;
|
||||
|
||||
const client = await pool.connect();
|
||||
try {
|
||||
await client.query("BEGIN TRANSACTION READ ONLY");
|
||||
const result = await client.query(sql);
|
||||
return {
|
||||
content: [{ type: "text", text: JSON.stringify(result.rows, null, 2) }],
|
||||
isError: false,
|
||||
};
|
||||
} catch (error) {
|
||||
throw error;
|
||||
} finally {
|
||||
client
|
||||
.query("ROLLBACK")
|
||||
.catch((error) =>
|
||||
console.warn("Could not roll back transaction:", error),
|
||||
);
|
||||
|
||||
client.release();
|
||||
}
|
||||
}
|
||||
throw new Error(`Unknown tool: ${request.params.name}`);
|
||||
});
|
||||
|
||||
async function runServer() {
|
||||
const transport = new StdioServerTransport();
|
||||
await server.connect(transport);
|
||||
}
|
||||
|
||||
runServer().catch(console.error);
|
||||
3
src/puppeteer/README.md
Normal file
3
src/puppeteer/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# Puppeteer server
|
||||
|
||||
This MCP server provides **resources** and **tools** for interacting with a browser and web pages, using [Puppeteer](https://pptr.dev/).
|
||||
110
src/puppeteer/index.ts
Normal file
110
src/puppeteer/index.ts
Normal file
@@ -0,0 +1,110 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
||||
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
||||
import {
|
||||
CallToolRequestSchema,
|
||||
ListResourcesRequestSchema,
|
||||
ListToolsRequestSchema,
|
||||
ReadResourceRequestSchema,
|
||||
} from "@modelcontextprotocol/sdk/types.js";
|
||||
import puppeteer from "puppeteer";
|
||||
|
||||
const server = new Server(
|
||||
{
|
||||
name: "example-servers/puppeteer",
|
||||
version: "0.1.0",
|
||||
},
|
||||
{
|
||||
capabilities: {
|
||||
resources: {
|
||||
listChanged: true,
|
||||
},
|
||||
tools: {},
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
let browser: puppeteer.Browser | undefined;
|
||||
let consoleLogs: string[] = [];
|
||||
|
||||
server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
||||
return {
|
||||
resources: [
|
||||
{
|
||||
uri: "console://logs",
|
||||
mimeType: "text/plain",
|
||||
name: "Browser console logs",
|
||||
},
|
||||
],
|
||||
};
|
||||
});
|
||||
|
||||
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
||||
if (request.params.uri.toString() === "console://logs") {
|
||||
return {
|
||||
contents: [
|
||||
{
|
||||
uri: "console://logs",
|
||||
mimeType: "text/plain",
|
||||
text: consoleLogs.join("\n"),
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
console.error("Resource not found:", request.params.uri);
|
||||
throw new Error("Resource not found");
|
||||
});
|
||||
|
||||
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
||||
return {
|
||||
tools: [
|
||||
{
|
||||
name: "navigate",
|
||||
description: "Navigate to a URL",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
url: { type: "string" },
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
});
|
||||
|
||||
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
||||
if (request.params.name === "navigate") {
|
||||
const url = request.params.arguments?.url as string;
|
||||
|
||||
if (!browser) {
|
||||
browser = await puppeteer.launch({ headless: false });
|
||||
|
||||
const pages = await browser.pages();
|
||||
pages[0].on("console", (msg) => {
|
||||
const logEntry = `[${msg.type()}] ${msg.text()}`;
|
||||
consoleLogs.push(logEntry);
|
||||
server.notification({
|
||||
method: "notifications/resources/updated",
|
||||
params: { uri: "console://logs" },
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const pages = await browser.pages();
|
||||
await pages[0].goto(url);
|
||||
|
||||
return {
|
||||
content: [{ type: "text", text: `Navigated to ${url}` }],
|
||||
isError: false,
|
||||
};
|
||||
}
|
||||
throw new Error(`Unknown tool: ${request.params.name}`);
|
||||
});
|
||||
|
||||
async function runServer() {
|
||||
const transport = new StdioServerTransport();
|
||||
await server.connect(transport);
|
||||
}
|
||||
|
||||
runServer().catch(console.error);
|
||||
Reference in New Issue
Block a user