mirror of
https://github.com/modelcontextprotocol/servers.git
synced 2026-04-22 05:45:22 +02:00
Merge branch 'main' into mahesh/update-postgres
This commit is contained in:
106
package-lock.json
generated
106
package-lock.json
generated
@@ -14,6 +14,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@modelcontextprotocol/server-everything": "*",
|
"@modelcontextprotocol/server-everything": "*",
|
||||||
"@modelcontextprotocol/server-gdrive": "*",
|
"@modelcontextprotocol/server-gdrive": "*",
|
||||||
|
"@modelcontextprotocol/server-memory": "*",
|
||||||
"@modelcontextprotocol/server-postgres": "*",
|
"@modelcontextprotocol/server-postgres": "*",
|
||||||
"@modelcontextprotocol/server-puppeteer": "*",
|
"@modelcontextprotocol/server-puppeteer": "*",
|
||||||
"@modelcontextprotocol/server-slack": "*"
|
"@modelcontextprotocol/server-slack": "*"
|
||||||
@@ -72,6 +73,14 @@
|
|||||||
"resolved": "src/gdrive",
|
"resolved": "src/gdrive",
|
||||||
"link": true
|
"link": true
|
||||||
},
|
},
|
||||||
|
"node_modules/@modelcontextprotocol/server-google-maps": {
|
||||||
|
"resolved": "src/google-maps",
|
||||||
|
"link": true
|
||||||
|
},
|
||||||
|
"node_modules/@modelcontextprotocol/server-memory": {
|
||||||
|
"resolved": "src/memory",
|
||||||
|
"link": true
|
||||||
|
},
|
||||||
"node_modules/@modelcontextprotocol/server-postgres": {
|
"node_modules/@modelcontextprotocol/server-postgres": {
|
||||||
"resolved": "src/postgres",
|
"resolved": "src/postgres",
|
||||||
"link": true
|
"link": true
|
||||||
@@ -190,11 +199,20 @@
|
|||||||
"version": "22.9.0",
|
"version": "22.9.0",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.9.0.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.9.0.tgz",
|
||||||
"integrity": "sha512-vuyHg81vvWA1Z1ELfvLko2c8f34gyA0zaic0+Rllc5lbCnbSyuvb2Oxpm6TAUAC/2xZN3QGqxBNggD1nNR2AfQ==",
|
"integrity": "sha512-vuyHg81vvWA1Z1ELfvLko2c8f34gyA0zaic0+Rllc5lbCnbSyuvb2Oxpm6TAUAC/2xZN3QGqxBNggD1nNR2AfQ==",
|
||||||
"devOptional": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"undici-types": "~6.19.8"
|
"undici-types": "~6.19.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/node-fetch": {
|
||||||
|
"version": "2.6.12",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.12.tgz",
|
||||||
|
"integrity": "sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": "*",
|
||||||
|
"form-data": "^4.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/pg": {
|
"node_modules/@types/pg": {
|
||||||
"version": "8.11.10",
|
"version": "8.11.10",
|
||||||
"resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.11.10.tgz",
|
"resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.11.10.tgz",
|
||||||
@@ -343,6 +361,12 @@
|
|||||||
"node": ">=4"
|
"node": ">=4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/asynckit": {
|
||||||
|
"version": "0.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||||
|
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/b4a": {
|
"node_modules/b4a": {
|
||||||
"version": "1.6.7",
|
"version": "1.6.7",
|
||||||
"resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.7.tgz",
|
"resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.7.tgz",
|
||||||
@@ -589,6 +613,18 @@
|
|||||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
|
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
|
||||||
},
|
},
|
||||||
|
"node_modules/combined-stream": {
|
||||||
|
"version": "1.0.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||||
|
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"delayed-stream": "~1.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/concat-map": {
|
"node_modules/concat-map": {
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||||
@@ -697,6 +733,15 @@
|
|||||||
"node": ">= 14"
|
"node": ">= 14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/delayed-stream": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/depd": {
|
"node_modules/depd": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
|
||||||
@@ -973,6 +1018,20 @@
|
|||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/form-data": {
|
||||||
|
"version": "4.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz",
|
||||||
|
"integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"asynckit": "^0.4.0",
|
||||||
|
"combined-stream": "^1.0.8",
|
||||||
|
"mime-types": "^2.1.12"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/forwarded": {
|
"node_modules/forwarded": {
|
||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
|
||||||
@@ -2659,8 +2718,7 @@
|
|||||||
"node_modules/undici-types": {
|
"node_modules/undici-types": {
|
||||||
"version": "6.19.8",
|
"version": "6.19.8",
|
||||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
|
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
|
||||||
"integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==",
|
"integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw=="
|
||||||
"devOptional": true
|
|
||||||
},
|
},
|
||||||
"node_modules/universalify": {
|
"node_modules/universalify": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
@@ -2873,6 +2931,48 @@
|
|||||||
"typescript": "^5.6.2"
|
"typescript": "^5.6.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"src/google-maps": {
|
||||||
|
"name": "@modelcontextprotocol/server-google-maps",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@modelcontextprotocol/sdk": "0.6.0",
|
||||||
|
"@types/node-fetch": "^2.6.12"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"mcp-server-google-maps": "dist/index.js"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"shx": "^0.3.4",
|
||||||
|
"typescript": "^5.6.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"src/google-maps/node_modules/@modelcontextprotocol/sdk": {
|
||||||
|
"version": "0.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-0.6.0.tgz",
|
||||||
|
"integrity": "sha512-9rsDudGhDtMbvxohPoMMyAUOmEzQsOK+XFchh6gZGqo8sx9sBuZQs+CUttXqa8RZXKDaJRCN2tUtgGof7jRkkw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"content-type": "^1.0.5",
|
||||||
|
"raw-body": "^3.0.0",
|
||||||
|
"zod": "^3.23.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"src/memory": {
|
||||||
|
"name": "@modelcontextprotocol/server-memory",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@modelcontextprotocol/sdk": "0.5.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"mcp-server-memory": "dist/index.js"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"shx": "^0.3.4",
|
||||||
|
"typescript": "^5.6.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"src/postgres": {
|
"src/postgres": {
|
||||||
"name": "@modelcontextprotocol/server-postgres",
|
"name": "@modelcontextprotocol/server-postgres",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
|
|||||||
@@ -21,6 +21,7 @@
|
|||||||
"@modelcontextprotocol/server-gdrive": "*",
|
"@modelcontextprotocol/server-gdrive": "*",
|
||||||
"@modelcontextprotocol/server-postgres": "*",
|
"@modelcontextprotocol/server-postgres": "*",
|
||||||
"@modelcontextprotocol/server-puppeteer": "*",
|
"@modelcontextprotocol/server-puppeteer": "*",
|
||||||
"@modelcontextprotocol/server-slack": "*"
|
"@modelcontextprotocol/server-slack": "*",
|
||||||
|
"@modelcontextprotocol/server-memory": "*"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,15 +2,37 @@
|
|||||||
|
|
||||||
This MCP server integrates with Google Drive to allow listing, reading, and searching over files.
|
This MCP server integrates with Google Drive to allow listing, reading, and searching over files.
|
||||||
|
|
||||||
|
## Components
|
||||||
|
|
||||||
|
### Tools
|
||||||
|
|
||||||
|
- **search**
|
||||||
|
- Search for files in Google Drive
|
||||||
|
- Input: `query` (string): Search query
|
||||||
|
- Returns file names and MIME types of matching files
|
||||||
|
|
||||||
|
### Resources
|
||||||
|
|
||||||
|
The server provides access to Google Drive files:
|
||||||
|
|
||||||
|
- **Files** (`gdrive:///<file_id>`)
|
||||||
|
- Supports all file types
|
||||||
|
- Google Workspace files are automatically exported:
|
||||||
|
- Docs → Markdown
|
||||||
|
- Sheets → CSV
|
||||||
|
- Presentations → Plain text
|
||||||
|
- Drawings → PNG
|
||||||
|
- Other files are provided in their native format
|
||||||
|
|
||||||
## Getting started
|
## Getting started
|
||||||
|
|
||||||
1. Create a new Google Cloud project
|
1. [Create a new Google Cloud project](https://console.cloud.google.com/projectcreate)
|
||||||
2. Enable the Google Drive API
|
2. [Enable the Google Drive API](https://console.cloud.google.com/workspace-api/products)
|
||||||
3. Configure an OAuth consent screen ("internal" is fine for testing)
|
3. [Configure an OAuth consent screen](https://console.cloud.google.com/apis/credentials/consent) ("internal" is fine for testing)
|
||||||
4. Add OAuth scope `https://www.googleapis.com/auth/drive.readonly`
|
4. Add OAuth scope `https://www.googleapis.com/auth/drive.readonly`
|
||||||
5. Create an OAuth Client ID for application type "Desktop App"
|
5. [Create an OAuth Client ID](https://console.cloud.google.com/apis/credentials/oauthclient) for application type "Desktop App"
|
||||||
6. Download the JSON file of your client's OAuth keys
|
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
|
7. Rename the key file to `gcp-oauth.keys.json` and place into the root of this repo (i.e. `servers/gcp-oauth.keys.json`)
|
||||||
|
|
||||||
Make sure to build the server with either `npm run build` or `npm run watch`.
|
Make sure to build the server with either `npm run build` or `npm run watch`.
|
||||||
|
|
||||||
@@ -18,16 +40,18 @@ Make sure to build the server with either `npm run build` or `npm run watch`.
|
|||||||
|
|
||||||
To authenticate and save credentials:
|
To authenticate and save credentials:
|
||||||
|
|
||||||
1. Run the server with the `auth` argument: `node build/gdrive auth`
|
1. Run the server with the `auth` argument: `node ./dist auth`
|
||||||
2. This will open an authentication flow in your system browser
|
2. This will open an authentication flow in your system browser
|
||||||
3. Complete the authentication process
|
3. Complete the authentication process
|
||||||
4. Credentials will be saved for future use
|
4. Credentials will be saved in the root of this repo (i.e. `servers/.gdrive-server-credentials.json`)
|
||||||
|
|
||||||
### Running the server
|
### Usage with Desktop App
|
||||||
|
|
||||||
After authenticating:
|
To integrate this server with the desktop app, add the following to your app's server configuration:
|
||||||
|
|
||||||
1. Run the server normally: `node build/gdrive`
|
```json
|
||||||
2. The server will load the saved credentials and start
|
{
|
||||||
|
"mcp-server-gdrive": {
|
||||||
Note: If you haven't authenticated yet, the server will prompt you to run with the `auth` argument first.
|
"command": "mcp-server-gdrive"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -44,7 +44,7 @@ server.setRequestHandler(ListResourcesRequestSchema, async (request) => {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
resources: files.map((file) => ({
|
resources: files.map((file) => ({
|
||||||
uri: `gdrive://${file.id}`,
|
uri: `gdrive:///${file.id}`,
|
||||||
mimeType: file.mimeType,
|
mimeType: file.mimeType,
|
||||||
name: file.name,
|
name: file.name,
|
||||||
})),
|
})),
|
||||||
@@ -53,7 +53,7 @@ server.setRequestHandler(ListResourcesRequestSchema, async (request) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
||||||
const fileId = request.params.uri.replace("gdrive://", "");
|
const fileId = request.params.uri.replace("gdrive:///", "");
|
||||||
|
|
||||||
// First get file metadata to check mime type
|
// First get file metadata to check mime type
|
||||||
const file = await drive.files.get({
|
const file = await drive.files.get({
|
||||||
@@ -149,10 +149,12 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|||||||
|
|
||||||
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
||||||
if (request.params.name === "search") {
|
if (request.params.name === "search") {
|
||||||
const query = request.params.arguments?.query as string;
|
const userQuery = request.params.arguments?.query as string;
|
||||||
|
const escapedQuery = userQuery.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
|
||||||
|
const formattedQuery = `fullText contains '${escapedQuery}'`;
|
||||||
|
|
||||||
const res = await drive.files.list({
|
const res = await drive.files.list({
|
||||||
q: query,
|
q: formattedQuery,
|
||||||
pageSize: 10,
|
pageSize: 10,
|
||||||
fields: "files(id, name, mimeType, modifiedTime, size)",
|
fields: "files(id, name, mimeType, modifiedTime, size)",
|
||||||
});
|
});
|
||||||
@@ -175,7 +177,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|||||||
|
|
||||||
const credentialsPath = path.join(
|
const credentialsPath = path.join(
|
||||||
path.dirname(new URL(import.meta.url).pathname),
|
path.dirname(new URL(import.meta.url).pathname),
|
||||||
"../../.gdrive-server-credentials.json",
|
"../../../.gdrive-server-credentials.json",
|
||||||
);
|
);
|
||||||
|
|
||||||
async function authenticateAndSaveCredentials() {
|
async function authenticateAndSaveCredentials() {
|
||||||
@@ -183,7 +185,7 @@ async function authenticateAndSaveCredentials() {
|
|||||||
const auth = await authenticate({
|
const auth = await authenticate({
|
||||||
keyfilePath: path.join(
|
keyfilePath: path.join(
|
||||||
path.dirname(new URL(import.meta.url).pathname),
|
path.dirname(new URL(import.meta.url).pathname),
|
||||||
"../../gcp-oauth.keys.json",
|
"../../../gcp-oauth.keys.json",
|
||||||
),
|
),
|
||||||
scopes: ["https://www.googleapis.com/auth/drive.readonly"],
|
scopes: ["https://www.googleapis.com/auth/drive.readonly"],
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
3.11
|
3.10
|
||||||
|
|||||||
@@ -1,9 +1,13 @@
|
|||||||
# mcp-git
|
# mcp-git: A git MCP server
|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||
|
Please note that mcp-git is currently in early development. The functionality and available tools are subject to change and expansion as we continue to develop and improve the server.
|
||||||
|
|
||||||
## Available Tools
|
## Available Tools
|
||||||
|
|
||||||
|
The current list of tools includes:
|
||||||
|
|
||||||
- `git_read_file`: Read contents of a file at a specific Git reference
|
- `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_list_files`: List all files in a repository or subdirectory
|
||||||
- `git_file_history`: Get commit history for a specific file
|
- `git_file_history`: Get commit history for a specific file
|
||||||
@@ -12,10 +16,15 @@ A Model Context Protocol server for Git repository interaction and automation. T
|
|||||||
- `git_get_diff`: View diffs between Git references
|
- `git_get_diff`: View diffs between Git references
|
||||||
- `git_get_repo_structure`: View repository file structure
|
- `git_get_repo_structure`: View repository file structure
|
||||||
- `git_list_repos`: List available Git repositories
|
- `git_list_repos`: List available Git repositories
|
||||||
|
- `git_log`: Retrieve commit log for the repository
|
||||||
|
- `git_list_branches`: List all branches in the repository
|
||||||
|
- `git_list_tags`: List all tags in the repository
|
||||||
|
|
||||||
|
This list is expected to grow as we add more functionality to the server. We welcome contributions from the community to expand and enhance the available tools.
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
### Using uv
|
### Using uv (recommended)
|
||||||
|
|
||||||
When using [`uv`](https://docs.astral.sh/uv/) no specific installation is needed. We will
|
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*.
|
use [`uvx`](https://docs.astral.sh/uv/guides/tools/) to directly run *mcp-git*.
|
||||||
@@ -35,36 +44,6 @@ python -m mcp_git
|
|||||||
```
|
```
|
||||||
|
|
||||||
## Configuration
|
## 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
|
### Configure for Claude.app
|
||||||
|
|
||||||
Add to your Claude settings:
|
Add to your Claude settings:
|
||||||
@@ -73,7 +52,7 @@ Add to your Claude settings:
|
|||||||
"mcpServers": {
|
"mcpServers": {
|
||||||
"mcp-git": {
|
"mcp-git": {
|
||||||
"command": "uvx",
|
"command": "uvx",
|
||||||
"args": ["mcp-git"]
|
"args": ["mcp-git", "--repository", "path/to/git/repo"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -84,14 +63,41 @@ Alternatively, if using pip installation:
|
|||||||
"mcpServers": {
|
"mcpServers": {
|
||||||
"mcp-git": {
|
"mcp-git": {
|
||||||
"command": "python",
|
"command": "python",
|
||||||
"args": ["-m", "mcp_git"]
|
"args": ["-m", "mcp_git", "--repository", "path/to/git/repo"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Configure for Zed
|
||||||
|
|
||||||
|
Add to your Zed settings.json:
|
||||||
|
|
||||||
|
```json
|
||||||
|
"context_servers": [
|
||||||
|
"mcp-git": {
|
||||||
|
"command": "uvx",
|
||||||
|
"args": ["mcp-git"]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
```
|
||||||
|
|
||||||
|
Alternatively, if using pip installation:
|
||||||
|
|
||||||
|
```json
|
||||||
|
"context_servers": {
|
||||||
|
"mcp-git": {
|
||||||
|
"command": "python",
|
||||||
|
"args": ["-m", "mcp-git"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
For examples of other MCP servers and implementation patterns, see:
|
We encourage contributions to help expand and improve mcp-git. Whether you want to add new tools, enhance existing functionality, or improve documentation, your input is valuable.
|
||||||
https://github.com/modelcontextprotocol/example-servers/
|
|
||||||
|
|
||||||
Pull requests welcome!
|
For examples of other MCP servers and implementation patterns, see:
|
||||||
|
https://github.com/modelcontextprotocol/servers
|
||||||
|
|
||||||
|
Pull requests are welcome! Feel free to contribute new ideas, bug fixes, or enhancements to make mcp-git even more powerful and useful.
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "mcp-git"
|
name = "mcp-git"
|
||||||
version = "0.1.0"
|
version = "0.2.0"
|
||||||
description = "A Model Context Protocol server providing tools to read, search, and manipulate Git repositories programmatically via LLMs"
|
description = "A Model Context Protocol server providing tools to read, search, and manipulate Git repositories programmatically via LLMs"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.11"
|
requires-python = ">=3.10"
|
||||||
authors = [{ name = "Anthropic, PBC." }]
|
authors = [{ name = "Anthropic, PBC." }]
|
||||||
maintainers = [{ name = "David Soria Parra", email = "davidsp@anthropic.com" }]
|
maintainers = [{ name = "David Soria Parra", email = "davidsp@anthropic.com" }]
|
||||||
keywords = ["git", "mcp", "llm", "automation"]
|
keywords = ["git", "mcp", "llm", "automation"]
|
||||||
@@ -13,12 +13,12 @@ classifiers = [
|
|||||||
"Intended Audience :: Developers",
|
"Intended Audience :: Developers",
|
||||||
"License :: OSI Approved :: MIT License",
|
"License :: OSI Approved :: MIT License",
|
||||||
"Programming Language :: Python :: 3",
|
"Programming Language :: Python :: 3",
|
||||||
"Programming Language :: Python :: 3.11",
|
"Programming Language :: Python :: 3.10",
|
||||||
]
|
]
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"click>=8.1.7",
|
"click>=8.1.7",
|
||||||
"gitpython>=3.1.43",
|
"gitpython>=3.1.43",
|
||||||
"mcp-python~=0.6.0",
|
"mcp>=0.6.0",
|
||||||
"pydantic>=2.0.0",
|
"pydantic>=2.0.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -30,5 +30,4 @@ requires = ["hatchling"]
|
|||||||
build-backend = "hatchling.build"
|
build-backend = "hatchling.build"
|
||||||
|
|
||||||
[tool.uv]
|
[tool.uv]
|
||||||
index-strategy = "unsafe-best-match"
|
|
||||||
dev-dependencies = ["ruff>=0.7.3"]
|
dev-dependencies = ["ruff>=0.7.3"]
|
||||||
|
|||||||
@@ -1,17 +1,26 @@
|
|||||||
import logging
|
import logging
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
import click
|
import click
|
||||||
import anyio
|
import anyio
|
||||||
import anyio.lowlevel
|
import anyio.lowlevel
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from git.types import Sequence
|
from git.types import Sequence
|
||||||
from mcp_python.server import Server
|
from mcp.server import Server
|
||||||
from mcp_python.server.stdio import stdio_server
|
from mcp.server.session import ServerSession
|
||||||
from mcp_python.types import Tool
|
from mcp.server.stdio import stdio_server
|
||||||
from mcp_python.server.types import EmbeddedResource, ImageContent
|
from mcp.types import (
|
||||||
from enum import StrEnum
|
ClientCapabilities,
|
||||||
|
TextContent,
|
||||||
|
Tool,
|
||||||
|
EmbeddedResource,
|
||||||
|
ImageContent,
|
||||||
|
ListRootsResult,
|
||||||
|
RootsCapability,
|
||||||
|
)
|
||||||
|
from enum import Enum
|
||||||
import git
|
import git
|
||||||
from git.objects import Blob, Tree
|
from git.objects import Blob, Tree
|
||||||
from mcp_python import ServerSession
|
|
||||||
|
|
||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, Field
|
||||||
from typing import List, Optional
|
from typing import List, Optional
|
||||||
@@ -67,7 +76,21 @@ class ListReposInput(BaseModel):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class GitTools(StrEnum):
|
class GitLogInput(BaseModel):
|
||||||
|
repo_path: str
|
||||||
|
max_count: int = 10
|
||||||
|
ref: str = "HEAD"
|
||||||
|
|
||||||
|
|
||||||
|
class ListBranchesInput(BaseModel):
|
||||||
|
repo_path: str
|
||||||
|
|
||||||
|
|
||||||
|
class ListTagsInput(BaseModel):
|
||||||
|
repo_path: str
|
||||||
|
|
||||||
|
|
||||||
|
class GitTools(str, Enum):
|
||||||
READ_FILE = "git_read_file"
|
READ_FILE = "git_read_file"
|
||||||
LIST_FILES = "git_list_files"
|
LIST_FILES = "git_list_files"
|
||||||
FILE_HISTORY = "git_file_history"
|
FILE_HISTORY = "git_file_history"
|
||||||
@@ -76,12 +99,19 @@ class GitTools(StrEnum):
|
|||||||
GET_DIFF = "git_get_diff"
|
GET_DIFF = "git_get_diff"
|
||||||
GET_REPO_STRUCTURE = "git_get_repo_structure"
|
GET_REPO_STRUCTURE = "git_get_repo_structure"
|
||||||
LIST_REPOS = "git_list_repos"
|
LIST_REPOS = "git_list_repos"
|
||||||
|
GIT_LOG = "git_log"
|
||||||
|
LIST_BRANCHES = "git_list_branches"
|
||||||
|
LIST_TAGS = "git_list_tags"
|
||||||
|
|
||||||
|
|
||||||
def git_read_file(repo: git.Repo, file_path: str, ref: str = "HEAD") -> str:
|
def git_read_file(repo: git.Repo, file_path: str, ref: str = "HEAD") -> str:
|
||||||
tree = repo.commit(ref).tree
|
tree = repo.commit(ref).tree
|
||||||
blob = tree / file_path
|
blob = tree / file_path
|
||||||
|
try:
|
||||||
return blob.data_stream.read().decode("utf-8", errors="replace")
|
return blob.data_stream.read().decode("utf-8", errors="replace")
|
||||||
|
except UnicodeDecodeError:
|
||||||
|
# If it's a binary file, return a message indicating that
|
||||||
|
return "[Binary file content not shown]"
|
||||||
|
|
||||||
|
|
||||||
def git_list_files(repo: git.Repo, path: str = "", ref: str = "HEAD") -> Sequence[str]:
|
def git_list_files(repo: git.Repo, path: str = "", ref: str = "HEAD") -> Sequence[str]:
|
||||||
@@ -123,10 +153,14 @@ def git_search_code(
|
|||||||
tree = repo.commit(ref).tree
|
tree = repo.commit(ref).tree
|
||||||
for blob in tree.traverse():
|
for blob in tree.traverse():
|
||||||
if isinstance(blob, Blob) and Path(blob.path).match(file_pattern):
|
if isinstance(blob, Blob) and Path(blob.path).match(file_pattern):
|
||||||
content = blob.data_stream.read().decode("utf-8")
|
try:
|
||||||
|
content = blob.data_stream.read().decode("utf-8", errors="replace")
|
||||||
for i, line in enumerate(content.splitlines()):
|
for i, line in enumerate(content.splitlines()):
|
||||||
if query in line:
|
if query in line:
|
||||||
results.append(f"{blob.path}:{i+1}: {line}")
|
results.append(f"{blob.path}:{i+1}: {line}")
|
||||||
|
except UnicodeDecodeError:
|
||||||
|
# Skip binary files
|
||||||
|
continue
|
||||||
return results
|
return results
|
||||||
|
|
||||||
|
|
||||||
@@ -154,20 +188,41 @@ def git_get_repo_structure(repo: git.Repo, ref: str = "HEAD") -> str:
|
|||||||
return str(structure)
|
return str(structure)
|
||||||
|
|
||||||
|
|
||||||
|
def git_log(repo: git.Repo, max_count: int = 10, ref: str = "HEAD") -> list[str]:
|
||||||
|
commits = list(repo.iter_commits(ref, max_count=max_count))
|
||||||
|
log = []
|
||||||
|
for commit in commits:
|
||||||
|
log.append(
|
||||||
|
f"Commit: {commit.hexsha}\n"
|
||||||
|
f"Author: {commit.author}\n"
|
||||||
|
f"Date: {commit.authored_datetime}\n"
|
||||||
|
f"Message: {commit.message}\n"
|
||||||
|
)
|
||||||
|
return log
|
||||||
|
|
||||||
|
|
||||||
|
def git_list_branches(repo: git.Repo) -> list[str]:
|
||||||
|
return [str(branch) for branch in repo.branches]
|
||||||
|
|
||||||
|
|
||||||
|
def git_list_tags(repo: git.Repo) -> list[str]:
|
||||||
|
return [str(tag) for tag in repo.tags]
|
||||||
|
|
||||||
|
|
||||||
async def serve(repository: Path | None) -> None:
|
async def serve(repository: Path | None) -> None:
|
||||||
# Set up logging
|
# Set up logging
|
||||||
logging.basicConfig(level=logging.DEBUG)
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
if repository is not None:
|
if repository is not None:
|
||||||
try:
|
try:
|
||||||
git.Repo(repository)
|
git.Repo(repository)
|
||||||
|
logger.info(f"Using repository at {repository}")
|
||||||
except git.InvalidGitRepositoryError:
|
except git.InvalidGitRepositoryError:
|
||||||
logger.error(f"{repository} is not a valid Git repository")
|
logger.error(f"{repository} is not a valid Git repository")
|
||||||
return
|
return
|
||||||
|
|
||||||
# Create server
|
# Create server
|
||||||
server = Server("git-mcp")
|
server = Server("mcp-git")
|
||||||
|
|
||||||
@server.list_tools()
|
@server.list_tools()
|
||||||
async def list_tools() -> list[Tool]:
|
async def list_tools() -> list[Tool]:
|
||||||
@@ -235,6 +290,28 @@ async def serve(repository: Path | None) -> None:
|
|||||||
"accessible to the current session.",
|
"accessible to the current session.",
|
||||||
inputSchema=ListReposInput.schema(),
|
inputSchema=ListReposInput.schema(),
|
||||||
),
|
),
|
||||||
|
Tool(
|
||||||
|
name=GitTools.GIT_LOG,
|
||||||
|
description="Retrieves the commit log for the repository, showing the "
|
||||||
|
"history of commits including commit hashes, authors, dates, and "
|
||||||
|
"commit messages. This tool provides an overview of the project's "
|
||||||
|
"development history.",
|
||||||
|
inputSchema=GitLogInput.schema(),
|
||||||
|
),
|
||||||
|
Tool(
|
||||||
|
name=GitTools.LIST_BRANCHES,
|
||||||
|
description="Lists all branches in the Git repository. This tool "
|
||||||
|
"provides an overview of the different lines of development in the "
|
||||||
|
"project.",
|
||||||
|
inputSchema=ListBranchesInput.schema(),
|
||||||
|
),
|
||||||
|
Tool(
|
||||||
|
name=GitTools.LIST_TAGS,
|
||||||
|
description="Lists all tags in the Git repository. This tool "
|
||||||
|
"provides an overview of the tagged versions or releases in the "
|
||||||
|
"project.",
|
||||||
|
inputSchema=ListTagsInput.schema(),
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
async def list_repos() -> Sequence[str]:
|
async def list_repos() -> Sequence[str]:
|
||||||
@@ -244,7 +321,14 @@ async def serve(repository: Path | None) -> None:
|
|||||||
"server.request_context.session must be a ServerSession"
|
"server.request_context.session must be a ServerSession"
|
||||||
)
|
)
|
||||||
|
|
||||||
roots_result = await server.request_context.session.list_roots()
|
if not server.request_context.session.check_client_capability(
|
||||||
|
ClientCapabilities(roots=RootsCapability())
|
||||||
|
):
|
||||||
|
return []
|
||||||
|
|
||||||
|
roots_result: ListRootsResult = (
|
||||||
|
await server.request_context.session.list_roots()
|
||||||
|
)
|
||||||
logger.debug(f"Roots result: {roots_result}")
|
logger.debug(f"Roots result: {roots_result}")
|
||||||
repo_paths = []
|
repo_paths = []
|
||||||
for root in roots_result.roots:
|
for root in roots_result.roots:
|
||||||
@@ -267,58 +351,128 @@ async def serve(repository: Path | None) -> None:
|
|||||||
@server.call_tool()
|
@server.call_tool()
|
||||||
async def call_tool(
|
async def call_tool(
|
||||||
name: str, arguments: dict
|
name: str, arguments: dict
|
||||||
) -> Sequence[str | ImageContent | EmbeddedResource]:
|
) -> list[TextContent | ImageContent | EmbeddedResource]:
|
||||||
if name == GitTools.LIST_REPOS:
|
if name == GitTools.LIST_REPOS:
|
||||||
return await list_repos()
|
result = await list_repos()
|
||||||
|
logging.debug(f"repos={result}")
|
||||||
|
return [
|
||||||
|
TextContent(
|
||||||
|
type="text",
|
||||||
|
text=f"Here is some JSON that contains a list of git repositories: {json.dumps(result)}",
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
repo_path = Path(arguments["repo_path"])
|
repo_path = Path(arguments["repo_path"])
|
||||||
repo = git.Repo(repo_path)
|
repo = git.Repo(repo_path)
|
||||||
|
|
||||||
match name:
|
match name:
|
||||||
case GitTools.READ_FILE:
|
case GitTools.READ_FILE:
|
||||||
return [
|
content = git_read_file(
|
||||||
git_read_file(
|
|
||||||
repo, arguments["file_path"], arguments.get("ref", "HEAD")
|
repo, arguments["file_path"], arguments.get("ref", "HEAD")
|
||||||
)
|
)
|
||||||
|
return [
|
||||||
|
TextContent(
|
||||||
|
type="text",
|
||||||
|
text=f"Here is some JSON that contains the contents of a file: {json.dumps({'content': content})}",
|
||||||
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
case GitTools.LIST_FILES:
|
case GitTools.LIST_FILES:
|
||||||
return [
|
files = git_list_files(
|
||||||
str(f)
|
|
||||||
for f in git_list_files(
|
|
||||||
repo, arguments.get("path", ""), arguments.get("ref", "HEAD")
|
repo, arguments.get("path", ""), arguments.get("ref", "HEAD")
|
||||||
)
|
)
|
||||||
|
return [
|
||||||
|
TextContent(
|
||||||
|
type="text",
|
||||||
|
text=f"Here is some JSON that contains a list of files: {json.dumps({'files': list(files)})}",
|
||||||
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
case GitTools.FILE_HISTORY:
|
case GitTools.FILE_HISTORY:
|
||||||
return git_file_history(
|
history = git_file_history(
|
||||||
repo, arguments["file_path"], arguments.get("max_entries", 10)
|
repo, arguments["file_path"], arguments.get("max_entries", 10)
|
||||||
)
|
)
|
||||||
|
return [
|
||||||
|
TextContent(
|
||||||
|
type="text",
|
||||||
|
text=f"Here is some JSON that contains a file's history: {json.dumps({'history': list(history)})}",
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
case GitTools.COMMIT:
|
case GitTools.COMMIT:
|
||||||
result = git_commit(repo, arguments["message"], arguments.get("files"))
|
result = git_commit(repo, arguments["message"], arguments.get("files"))
|
||||||
return [result]
|
return [
|
||||||
|
TextContent(
|
||||||
|
type="text",
|
||||||
|
text=f"Here is some JSON that contains the commit result: {json.dumps({'result': result})}",
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
case GitTools.SEARCH_CODE:
|
case GitTools.SEARCH_CODE:
|
||||||
return git_search_code(
|
results = git_search_code(
|
||||||
repo,
|
repo,
|
||||||
arguments["query"],
|
arguments["query"],
|
||||||
arguments.get("file_pattern", "*"),
|
arguments.get("file_pattern", "*"),
|
||||||
arguments.get("ref", "HEAD"),
|
arguments.get("ref", "HEAD"),
|
||||||
)
|
)
|
||||||
|
return [
|
||||||
|
TextContent(
|
||||||
|
type="text",
|
||||||
|
text=f"Here is some JSON that contains code search matches: {json.dumps({'matches': results})}",
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
case GitTools.GET_DIFF:
|
case GitTools.GET_DIFF:
|
||||||
return [
|
diff = git_get_diff(
|
||||||
git_get_diff(
|
|
||||||
repo,
|
repo,
|
||||||
arguments["ref1"],
|
arguments["ref1"],
|
||||||
arguments["ref2"],
|
arguments["ref2"],
|
||||||
arguments.get("file_path"),
|
arguments.get("file_path"),
|
||||||
)
|
)
|
||||||
|
return [
|
||||||
|
TextContent(
|
||||||
|
type="text",
|
||||||
|
text=f"Here is some JSON that contains a diff: {json.dumps({'diff': diff})}",
|
||||||
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
case GitTools.GET_REPO_STRUCTURE:
|
case GitTools.GET_REPO_STRUCTURE:
|
||||||
return [git_get_repo_structure(repo, arguments.get("ref", "HEAD"))]
|
structure = git_get_repo_structure(repo, arguments.get("ref", "HEAD"))
|
||||||
|
return [
|
||||||
|
TextContent(
|
||||||
|
type="text",
|
||||||
|
text=f"Here is some JSON that contains the repository structure: {json.dumps({'structure': structure})}",
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
case GitTools.GIT_LOG:
|
||||||
|
log = git_log(
|
||||||
|
repo, arguments.get("max_count", 10), arguments.get("ref", "HEAD")
|
||||||
|
)
|
||||||
|
return [
|
||||||
|
TextContent(
|
||||||
|
type="text",
|
||||||
|
text=f"Here is some JSON that contains the git log: {json.dumps({'log': log})}",
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
case GitTools.LIST_BRANCHES:
|
||||||
|
branches = git_list_branches(repo)
|
||||||
|
return [
|
||||||
|
TextContent(
|
||||||
|
type="text",
|
||||||
|
text=f"Here is some JSON that contains a list of branches: {json.dumps({'branches': branches})}",
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
case GitTools.LIST_TAGS:
|
||||||
|
tags = git_list_tags(repo)
|
||||||
|
return [
|
||||||
|
TextContent(
|
||||||
|
type="text",
|
||||||
|
text=f"Here is some JSON that contains a list of tags: {json.dumps({'tags': tags})}",
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
case _:
|
case _:
|
||||||
raise ValueError(f"Unknown tool: {name}")
|
raise ValueError(f"Unknown tool: {name}")
|
||||||
@@ -326,12 +480,19 @@ async def serve(repository: Path | None) -> None:
|
|||||||
# Run the server
|
# Run the server
|
||||||
options = server.create_initialization_options()
|
options = server.create_initialization_options()
|
||||||
async with stdio_server() as (read_stream, write_stream):
|
async with stdio_server() as (read_stream, write_stream):
|
||||||
await server.run(read_stream, write_stream, options)
|
await server.run(read_stream, write_stream, options, raise_exceptions=True)
|
||||||
|
|
||||||
|
|
||||||
@click.command()
|
@click.command()
|
||||||
@click.option("-r", "--repository", type=click.Path(path_type=Path, dir_okay=True))
|
@click.option("-r", "--repository", type=click.Path(path_type=Path, dir_okay=True))
|
||||||
def main(repository: Path | None):
|
@click.option("-v", "--verbose", count=True)
|
||||||
|
def main(repository: Path | None, verbose: int):
|
||||||
|
logging_level = logging.WARN
|
||||||
|
if verbose == 1:
|
||||||
|
logging_level = logging.INFO
|
||||||
|
elif verbose >= 2:
|
||||||
|
logging_level = logging.DEBUG
|
||||||
|
logging.basicConfig(level=logging_level, stream=sys.stderr)
|
||||||
anyio.run(serve, repository)
|
anyio.run(serve, repository)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
134
src/git/uv.lock
generated
134
src/git/uv.lock
generated
@@ -1,5 +1,5 @@
|
|||||||
version = 1
|
version = 1
|
||||||
requires-python = ">=3.11"
|
requires-python = ">=3.10"
|
||||||
resolution-markers = [
|
resolution-markers = [
|
||||||
"python_full_version < '3.13'",
|
"python_full_version < '3.13'",
|
||||||
"python_full_version >= '3.13'",
|
"python_full_version >= '3.13'",
|
||||||
@@ -19,8 +19,10 @@ name = "anyio"
|
|||||||
version = "4.6.2.post1"
|
version = "4.6.2.post1"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
{ name = "exceptiongroup", marker = "python_full_version < '3.11'" },
|
||||||
{ name = "idna" },
|
{ name = "idna" },
|
||||||
{ name = "sniffio" },
|
{ name = "sniffio" },
|
||||||
|
{ name = "typing-extensions", marker = "python_full_version < '3.11'" },
|
||||||
]
|
]
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/9f/09/45b9b7a6d4e45c6bcb5bf61d19e3ab87df68e0601fa8c5293de3542546cc/anyio-4.6.2.post1.tar.gz", hash = "sha256:4c8bc31ccdb51c7f7bd251f51c609e038d63e34219b44aa86e47576389880b4c", size = 173422 }
|
sdist = { url = "https://files.pythonhosted.org/packages/9f/09/45b9b7a6d4e45c6bcb5bf61d19e3ab87df68e0601fa8c5293de3542546cc/anyio-4.6.2.post1.tar.gz", hash = "sha256:4c8bc31ccdb51c7f7bd251f51c609e038d63e34219b44aa86e47576389880b4c", size = 173422 }
|
||||||
wheels = [
|
wheels = [
|
||||||
@@ -51,9 +53,19 @@ wheels = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "colorama"
|
name = "colorama"
|
||||||
version = "0.4.6"
|
version = "0.4.6"
|
||||||
source = { registry = "https://artifactory.infra.ant.dev/artifactory/api/pypi/pypi-internal/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 }
|
||||||
wheels = [
|
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" },
|
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "exceptiongroup"
|
||||||
|
version = "1.2.2"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/09/35/2495c4ac46b980e4ca1f6ad6db102322ef3ad2410b79fdde159a4b0f3b92/exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc", size = 28883 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/02/cc/b7e31358aac6ed1ef2bb790a9746ac2c69bcb3c8588b41616914eb106eaf/exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b", size = 16453 },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -83,22 +95,23 @@ wheels = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "h11"
|
name = "h11"
|
||||||
version = "0.14.0"
|
version = "0.14.0"
|
||||||
source = { registry = "https://artifactory.infra.ant.dev/artifactory/api/pypi/pypi-internal/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/f5/38/3af3d3633a34a3316095b39c8e8fb4853a28a536e55d347bd8d8e9a14b03/h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d", size = 100418 }
|
||||||
wheels = [
|
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" },
|
{ url = "https://files.pythonhosted.org/packages/95/04/ff642e65ad6b90db43e668d70ffb6736436c7ce41fcc549f4e9472234127/h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761", size = 58259 },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "httpcore"
|
name = "httpcore"
|
||||||
version = "1.0.6"
|
version = "1.0.7"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "certifi" },
|
{ name = "certifi" },
|
||||||
{ name = "h11" },
|
{ name = "h11" },
|
||||||
]
|
]
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/b6/44/ed0fa6a17845fb033bd885c03e842f08c1b9406c86a2e60ac1ae1b9206a6/httpcore-1.0.6.tar.gz", hash = "sha256:73f6dbd6eb8c21bbf7ef8efad555481853f5f6acdeaff1edb0694289269ee17f", size = 85180 }
|
sdist = { url = "https://files.pythonhosted.org/packages/6a/41/d7d0a89eb493922c37d343b607bc1b5da7f5be7e383740b4753ad8943e90/httpcore-1.0.7.tar.gz", hash = "sha256:8551cb62a169ec7162ac7be8d4817d561f60e08eaa485234898414bb5a8a0b4c", size = 85196 }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/06/89/b161908e2f51be56568184aeb4a880fd287178d176fd1c860d2217f41106/httpcore-1.0.6-py3-none-any.whl", hash = "sha256:27b59625743b85577a8c0e10e55b50b5368a4f2cfe8cc7bcfa9cf00829c2682f", size = 78011 },
|
{ url = "https://files.pythonhosted.org/packages/87/f5/72347bc88306acb359581ac4d52f23c0ef445b57157adedb9aee0cd689d2/httpcore-1.0.7-py3-none-any.whl", hash = "sha256:a3fff8f43dc260d5bd363d9f9cf1830fa3a458b332856f34282de498ed420edd", size = 78551 },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -135,14 +148,31 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 },
|
{ url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mcp"
|
||||||
|
version = "0.9.1"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "anyio" },
|
||||||
|
{ name = "httpx" },
|
||||||
|
{ name = "httpx-sse" },
|
||||||
|
{ name = "pydantic" },
|
||||||
|
{ name = "sse-starlette" },
|
||||||
|
{ name = "starlette" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/e7/1c/932818470ffd49c33509110c835101a8dc4c9cdd06028b9f647fb3dde237/mcp-0.9.1.tar.gz", hash = "sha256:e8509a37c2ab546095788ed170e0fb4d7ce0cf5a3ee56b6449c78af27321a425", size = 78218 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b3/a0/2ee813d456b57a726d583868417d1ad900fbe12ee3c8cd866e3e804ca486/mcp-0.9.1-py3-none-any.whl", hash = "sha256:7f640fcfb0be486aa510594df309920ae1d375cdca1f8aff21db3a96d837f303", size = 31562 },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mcp-git"
|
name = "mcp-git"
|
||||||
version = "0.1.0"
|
version = "0.2.0"
|
||||||
source = { editable = "." }
|
source = { editable = "." }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "click" },
|
{ name = "click" },
|
||||||
{ name = "gitpython" },
|
{ name = "gitpython" },
|
||||||
{ name = "mcp-python" },
|
{ name = "mcp" },
|
||||||
{ name = "pydantic" },
|
{ name = "pydantic" },
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -155,30 +185,13 @@ dev = [
|
|||||||
requires-dist = [
|
requires-dist = [
|
||||||
{ name = "click", specifier = ">=8.1.7" },
|
{ name = "click", specifier = ">=8.1.7" },
|
||||||
{ name = "gitpython", specifier = ">=3.1.43" },
|
{ name = "gitpython", specifier = ">=3.1.43" },
|
||||||
{ name = "mcp-python", specifier = "~=0.6.0" },
|
{ name = "mcp", specifier = ">=0.6.0" },
|
||||||
{ name = "pydantic", specifier = ">=2.0.0" },
|
{ name = "pydantic", specifier = ">=2.0.0" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.metadata.requires-dev]
|
[package.metadata.requires-dev]
|
||||||
dev = [{ name = "ruff", specifier = ">=0.7.3" }]
|
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]]
|
[[package]]
|
||||||
name = "pydantic"
|
name = "pydantic"
|
||||||
version = "2.9.2"
|
version = "2.9.2"
|
||||||
@@ -202,6 +215,18 @@ dependencies = [
|
|||||||
]
|
]
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/e2/aa/6b6a9b9f8537b872f552ddd46dd3da230367754b6f707b8e1e963f515ea3/pydantic_core-2.23.4.tar.gz", hash = "sha256:2584f7cf844ac4d970fba483a717dbe10c1c1c96a969bf65d61ffe94df1b2863", size = 402156 }
|
sdist = { url = "https://files.pythonhosted.org/packages/e2/aa/6b6a9b9f8537b872f552ddd46dd3da230367754b6f707b8e1e963f515ea3/pydantic_core-2.23.4.tar.gz", hash = "sha256:2584f7cf844ac4d970fba483a717dbe10c1c1c96a969bf65d61ffe94df1b2863", size = 402156 }
|
||||||
wheels = [
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5c/8b/d3ae387f66277bd8104096d6ec0a145f4baa2966ebb2cad746c0920c9526/pydantic_core-2.23.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:b10bd51f823d891193d4717448fab065733958bdb6a6b351967bd349d48d5c9b", size = 1867835 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/46/76/f68272e4c3a7df8777798282c5e47d508274917f29992d84e1898f8908c7/pydantic_core-2.23.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4fc714bdbfb534f94034efaa6eadd74e5b93c8fa6315565a222f7b6f42ca1166", size = 1776689 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/cc/69/5f945b4416f42ea3f3bc9d2aaec66c76084a6ff4ff27555bf9415ab43189/pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63e46b3169866bd62849936de036f901a9356e36376079b05efa83caeaa02ceb", size = 1800748 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/50/ab/891a7b0054bcc297fb02d44d05c50e68154e31788f2d9d41d0b72c89fdf7/pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed1a53de42fbe34853ba90513cea21673481cd81ed1be739f7f2efb931b24916", size = 1806469 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/31/7c/6e3fa122075d78f277a8431c4c608f061881b76c2b7faca01d317ee39b5d/pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cfdd16ab5e59fc31b5e906d1a3f666571abc367598e3e02c83403acabc092e07", size = 2002246 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ad/6f/22d5692b7ab63fc4acbc74de6ff61d185804a83160adba5e6cc6068e1128/pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:255a8ef062cbf6674450e668482456abac99a5583bbafb73f9ad469540a3a232", size = 2659404 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/11/ac/1e647dc1121c028b691028fa61a4e7477e6aeb5132628fde41dd34c1671f/pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a7cd62e831afe623fbb7aabbb4fe583212115b3ef38a9f6b71869ba644624a2", size = 2053940 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/91/75/984740c17f12c3ce18b5a2fcc4bdceb785cce7df1511a4ce89bca17c7e2d/pydantic_core-2.23.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f09e2ff1f17c2b51f2bc76d1cc33da96298f0a036a137f5440ab3ec5360b624f", size = 1921437 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a0/74/13c5f606b64d93f0721e7768cd3e8b2102164866c207b8cd6f90bb15d24f/pydantic_core-2.23.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e38e63e6f3d1cec5a27e0afe90a085af8b6806ee208b33030e65b6516353f1a3", size = 1966129 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/18/03/9c4aa5919457c7b57a016c1ab513b1a926ed9b2bb7915bf8e506bf65c34b/pydantic_core-2.23.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0dbd8dbed2085ed23b5c04afa29d8fd2771674223135dc9bc937f3c09284d071", size = 2110908 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/92/2c/053d33f029c5dc65e5cf44ff03ceeefb7cce908f8f3cca9265e7f9b540c8/pydantic_core-2.23.4-cp310-none-win32.whl", hash = "sha256:6531b7ca5f951d663c339002e91aaebda765ec7d61b7d1e3991051906ddde119", size = 1735278 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/de/81/7dfe464eca78d76d31dd661b04b5f2036ec72ea8848dd87ab7375e185c23/pydantic_core-2.23.4-cp310-none-win_amd64.whl", hash = "sha256:7c9129eb40958b3d4500fa2467e6a83356b3b61bfff1b414c7361d9220f9ae8f", size = 1917453 },
|
||||||
{ 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/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/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/75/9a/7816295124a6b08c24c96f9ce73085032d8bcbaf7e5a781cd41aa910c891/pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df49e7a0861a8c36d089c1ed57d308623d60416dab2647a4a17fe050ba85de0e", size = 1799244 },
|
||||||
@@ -238,31 +263,39 @@ wheels = [
|
|||||||
{ 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/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/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 },
|
{ url = "https://files.pythonhosted.org/packages/a5/ae/e14b0ff8b3f48e02394d8acd911376b7b66e164535687ef7dc24ea03072f/pydantic_core-2.23.4-cp313-none-win_amd64.whl", hash = "sha256:5a1504ad17ba4210df3a045132a7baeeba5a200e930f57512ee02909fc5c4cb5", size = 1919411 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/13/a9/5d582eb3204464284611f636b55c0a7410d748ff338756323cb1ce721b96/pydantic_core-2.23.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f455ee30a9d61d3e1a15abd5068827773d6e4dc513e795f380cdd59932c782d5", size = 1857135 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2c/57/faf36290933fe16717f97829eabfb1868182ac495f99cf0eda9f59687c9d/pydantic_core-2.23.4-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:1e90d2e3bd2c3863d48525d297cd143fe541be8bbf6f579504b9712cb6b643ec", size = 1740583 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/91/7c/d99e3513dc191c4fec363aef1bf4c8af9125d8fa53af7cb97e8babef4e40/pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e203fdf807ac7e12ab59ca2bfcabb38c7cf0b33c41efeb00f8e5da1d86af480", size = 1793637 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/29/18/812222b6d18c2d13eebbb0f7cdc170a408d9ced65794fdb86147c77e1982/pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e08277a400de01bc72436a0ccd02bdf596631411f592ad985dcee21445bd0068", size = 1941963 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/0f/36/c1f3642ac3f05e6bb4aec3ffc399fa3f84895d259cf5f0ce3054b7735c29/pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f220b0eea5965dec25480b6333c788fb72ce5f9129e8759ef876a1d805d00801", size = 1915332 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f7/ca/9c0854829311fb446020ebb540ee22509731abad886d2859c855dd29b904/pydantic_core-2.23.4-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:d06b0c8da4f16d1d1e352134427cb194a0a6e19ad5db9161bf32b2113409e728", size = 1957926 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c0/1c/7836b67c42d0cd4441fcd9fafbf6a027ad4b79b6559f80cf11f89fd83648/pydantic_core-2.23.4-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:ba1a0996f6c2773bd83e63f18914c1de3c9dd26d55f4ac302a7efe93fb8e7433", size = 2100342 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a9/f9/b6bcaf874f410564a78908739c80861a171788ef4d4f76f5009656672dfe/pydantic_core-2.23.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:9a5bce9d23aac8f0cf0836ecfc033896aa8443b501c58d0602dbfd5bd5b37753", size = 1920344 },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ruff"
|
name = "ruff"
|
||||||
version = "0.7.3"
|
version = "0.7.4"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
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 }
|
sdist = { url = "https://files.pythonhosted.org/packages/0b/8b/bc4e0dfa1245b07cf14300e10319b98e958a53ff074c1dd86b35253a8c2a/ruff-0.7.4.tar.gz", hash = "sha256:cd12e35031f5af6b9b93715d8c4f40360070b2041f81273d0527683d5708fce2", size = 3275547 }
|
||||||
wheels = [
|
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/e6/4b/f5094719e254829766b807dadb766841124daba75a37da83e292ae5ad12f/ruff-0.7.4-py3-none-linux_armv6l.whl", hash = "sha256:a4919925e7684a3f18e18243cd6bea7cfb8e968a6eaa8437971f681b7ec51478", size = 10447512 },
|
||||||
{ 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/9e/1d/3d2d2c9f601cf6044799c5349ff5267467224cefed9b35edf5f1f36486e9/ruff-0.7.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:cfb365c135b830778dda8c04fb7d4280ed0b984e1aec27f574445231e20d6c63", size = 10235436 },
|
||||||
{ 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/62/83/42a6ec6216ded30b354b13e0e9327ef75a3c147751aaf10443756cb690e9/ruff-0.7.4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:63a569b36bc66fbadec5beaa539dd81e0527cb258b94e29e0531ce41bacc1f20", size = 9888936 },
|
||||||
{ 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/4d/26/e1e54893b13046a6ad05ee9b89ee6f71542ba250f72b4c7a7d17c3dbf73d/ruff-0.7.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d06218747d361d06fd2fdac734e7fa92df36df93035db3dc2ad7aa9852cb109", size = 10697353 },
|
||||||
{ 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/21/24/98d2e109c4efc02bfef144ec6ea2c3e1217e7ce0cfddda8361d268dfd499/ruff-0.7.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e0cea28d0944f74ebc33e9f934238f15c758841f9f5edd180b5315c203293452", size = 10228078 },
|
||||||
{ 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/ad/b7/964c75be9bc2945fc3172241b371197bb6d948cc69e28bc4518448c368f3/ruff-0.7.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:80094ecd4793c68b2571b128f91754d60f692d64bc0d7272ec9197fdd09bf9ea", size = 11264823 },
|
||||||
{ 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/12/8d/20abdbf705969914ce40988fe71a554a918deaab62c38ec07483e77866f6/ruff-0.7.4-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:997512325c6620d1c4c2b15db49ef59543ef9cd0f4aa8065ec2ae5103cedc7e7", size = 11951855 },
|
||||||
{ 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/b8/fc/6519ce58c57b4edafcdf40920b7273dfbba64fc6ebcaae7b88e4dc1bf0a8/ruff-0.7.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:00b4cf3a6b5fad6d1a66e7574d78956bbd09abfd6c8a997798f01f5da3d46a05", size = 11516580 },
|
||||||
{ 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/37/1a/5ec1844e993e376a86eb2456496831ed91b4398c434d8244f89094758940/ruff-0.7.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7dbdc7d8274e1422722933d1edddfdc65b4336abf0b16dfcb9dedd6e6a517d06", size = 12692057 },
|
||||||
{ 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/50/90/76867152b0d3c05df29a74bb028413e90f704f0f6701c4801174ba47f959/ruff-0.7.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e92dfb5f00eaedb1501b2f906ccabfd67b2355bdf117fea9719fc99ac2145bc", size = 11085137 },
|
||||||
{ 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/c8/eb/0a7cb6059ac3555243bd026bb21785bbc812f7bbfa95a36c101bd72b47ae/ruff-0.7.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:3bd726099f277d735dc38900b6a8d6cf070f80828877941983a57bca1cd92172", size = 10681243 },
|
||||||
{ 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/5e/76/2270719dbee0fd35780b05c08a07b7a726c3da9f67d9ae89ef21fc18e2e5/ruff-0.7.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:2e32829c429dd081ee5ba39aef436603e5b22335c3d3fff013cd585806a6486a", size = 10319187 },
|
||||||
{ 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/9f/e5/39100f72f8ba70bec1bd329efc880dea8b6c1765ea1cb9d0c1c5f18b8d7f/ruff-0.7.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:662a63b4971807623f6f90c1fb664613f67cc182dc4d991471c23c541fee62dd", size = 10803715 },
|
||||||
{ 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/a5/89/40e904784f305fb56850063f70a998a64ebba68796d823dde67e89a24691/ruff-0.7.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:876f5e09eaae3eb76814c1d3b68879891d6fde4824c015d48e7a7da4cf066a3a", size = 11162912 },
|
||||||
{ 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/8d/1b/dd77503b3875c51e3dbc053fd8367b845ab8b01c9ca6d0c237082732856c/ruff-0.7.4-py3-none-win32.whl", hash = "sha256:75c53f54904be42dd52a548728a5b572344b50d9b2873d13a3f8c5e3b91f5cac", size = 8702767 },
|
||||||
{ 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/63/76/253ddc3e89e70165bba952ecca424b980b8d3c2598ceb4fc47904f424953/ruff-0.7.4-py3-none-win_amd64.whl", hash = "sha256:745775c7b39f914238ed1f1b0bebed0b9155a17cd8bc0b08d3c87e4703b990d6", size = 9497534 },
|
||||||
{ url = "https://files.pythonhosted.org/packages/3f/77/b587cba6febd5e2003374f37eb89633f79f161e71084f94057c8653b7fb3/ruff-0.7.3-py3-none-win_arm64.whl", hash = "sha256:1713e2c5545863cdbfe2cbce21f69ffaf37b813bfd1fb3b90dc9a6f1963f5a8c", size = 8725228 },
|
{ url = "https://files.pythonhosted.org/packages/aa/70/f8724f31abc0b329ca98b33d73c14020168babcf71b0cba3cded5d9d0e66/ruff-0.7.4-py3-none-win_arm64.whl", hash = "sha256:11bff065102c3ae9d3ea4dc9ecdfe5a5171349cdd0787c1fc64761212fc9cf1f", size = 8851590 },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -299,14 +332,14 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "starlette"
|
name = "starlette"
|
||||||
version = "0.41.2"
|
version = "0.41.3"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "anyio" },
|
{ name = "anyio" },
|
||||||
]
|
]
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/3e/da/1fb4bdb72ae12b834becd7e1e7e47001d32f91ec0ce8d7bc1b618d9f0bd9/starlette-0.41.2.tar.gz", hash = "sha256:9834fd799d1a87fd346deb76158668cfa0b0d56f85caefe8268e2d97c3468b62", size = 2573867 }
|
sdist = { url = "https://files.pythonhosted.org/packages/1a/4c/9b5764bd22eec91c4039ef4c55334e9187085da2d8a2df7bd570869aae18/starlette-0.41.3.tar.gz", hash = "sha256:0e4ab3d16522a255be6b28260b938eae2482f98ce5cc934cb08dce8dc3ba5835", size = 2574159 }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/54/43/f185bfd0ca1d213beb4293bed51d92254df23d8ceaf6c0e17146d508a776/starlette-0.41.2-py3-none-any.whl", hash = "sha256:fbc189474b4731cf30fcef52f18a8d070e3f3b46c6a04c97579e85e6ffca942d", size = 73259 },
|
{ url = "https://files.pythonhosted.org/packages/96/00/2b325970b3060c7cecebab6d295afe763365822b1306a12eeab198f74323/starlette-0.41.3-py3-none-any.whl", hash = "sha256:44cedb2b7c77a9de33a8b74b2b90e9f50d11fcf25d8270ea525ad71a25374ff7", size = 73225 },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -325,6 +358,7 @@ source = { registry = "https://pypi.org/simple" }
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "click" },
|
{ name = "click" },
|
||||||
{ name = "h11" },
|
{ name = "h11" },
|
||||||
|
{ name = "typing-extensions", marker = "python_full_version < '3.11'" },
|
||||||
]
|
]
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/e0/fc/1d785078eefd6945f3e5bab5c076e4230698046231eb0f3747bc5c8fa992/uvicorn-0.32.0.tar.gz", hash = "sha256:f78b36b143c16f54ccdb8190d0a26b5f1901fe5a3c777e1ab29f26391af8551e", size = 77564 }
|
sdist = { url = "https://files.pythonhosted.org/packages/e0/fc/1d785078eefd6945f3e5bab5c076e4230698046231eb0f3747bc5c8fa992/uvicorn-0.32.0.tar.gz", hash = "sha256:f78b36b143c16f54ccdb8190d0a26b5f1901fe5a3c777e1ab29f26391af8551e", size = 77564 }
|
||||||
wheels = [
|
wheels = [
|
||||||
|
|||||||
65
src/google-maps/README.md
Normal file
65
src/google-maps/README.md
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
# Google Maps MCP Server
|
||||||
|
|
||||||
|
MCP Server for the Google Maps API.
|
||||||
|
|
||||||
|
## Tools
|
||||||
|
|
||||||
|
1. `geocode`
|
||||||
|
- Convert address to coordinates
|
||||||
|
- Input: `address` (string)
|
||||||
|
- Returns: location, formatted_address, place_id
|
||||||
|
|
||||||
|
2. `reverse_geocode`
|
||||||
|
- Convert coordinates to address
|
||||||
|
- Inputs:
|
||||||
|
- `latitude` (number)
|
||||||
|
- `longitude` (number)
|
||||||
|
- Returns: formatted_address, place_id, address_components
|
||||||
|
|
||||||
|
3. `search_places`
|
||||||
|
- Search for places using text query
|
||||||
|
- Inputs:
|
||||||
|
- `query` (string)
|
||||||
|
- `location` (optional): { latitude: number, longitude: number }
|
||||||
|
- `radius` (optional): number (meters, max 50000)
|
||||||
|
- Returns: array of places with names, addresses, locations
|
||||||
|
|
||||||
|
4. `get_place_details`
|
||||||
|
- Get detailed information about a place
|
||||||
|
- Input: `place_id` (string)
|
||||||
|
- Returns: name, address, contact info, ratings, reviews, opening hours
|
||||||
|
|
||||||
|
5. `get_distance_matrix`
|
||||||
|
- Calculate distances and times between points
|
||||||
|
- Inputs:
|
||||||
|
- `origins` (string[])
|
||||||
|
- `destinations` (string[])
|
||||||
|
- `mode` (optional): "driving" | "walking" | "bicycling" | "transit"
|
||||||
|
- Returns: distances and durations matrix
|
||||||
|
|
||||||
|
6. `get_elevation`
|
||||||
|
- Get elevation data for locations
|
||||||
|
- Input: `locations` (array of {latitude, longitude})
|
||||||
|
- Returns: elevation data for each point
|
||||||
|
|
||||||
|
7. `get_directions`
|
||||||
|
- Get directions between points
|
||||||
|
- Inputs:
|
||||||
|
- `origin` (string)
|
||||||
|
- `destination` (string)
|
||||||
|
- `mode` (optional): "driving" | "walking" | "bicycling" | "transit"
|
||||||
|
- Returns: route details with steps, distance, duration
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
1. Get a Google Maps API key by following the instructions [here](https://developers.google.com/maps/documentation/javascript/get-api-key#create-api-keys).
|
||||||
|
|
||||||
|
2. To use this with Claude Desktop, add the following to your `claude_desktop_config.json`:
|
||||||
|
```json
|
||||||
|
"mcp-server-google-maps": {
|
||||||
|
"command": "mcp-server-google-maps",
|
||||||
|
"env": {
|
||||||
|
"GOOGLE_MAPS_API_KEY": "<YOUR_API_KEY>"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
710
src/google-maps/index.ts
Normal file
710
src/google-maps/index.ts
Normal file
@@ -0,0 +1,710 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
||||||
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
||||||
|
import {
|
||||||
|
CallToolRequestSchema,
|
||||||
|
ListToolsRequestSchema,
|
||||||
|
Tool,
|
||||||
|
} from "@modelcontextprotocol/sdk/types.js";
|
||||||
|
import fetch from "node-fetch";
|
||||||
|
|
||||||
|
// Response interfaces
|
||||||
|
interface GoogleMapsResponse {
|
||||||
|
status: string;
|
||||||
|
error_message?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface GeocodeResponse extends GoogleMapsResponse {
|
||||||
|
results: Array<{
|
||||||
|
place_id: string;
|
||||||
|
formatted_address: string;
|
||||||
|
geometry: {
|
||||||
|
location: {
|
||||||
|
lat: number;
|
||||||
|
lng: number;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
address_components: Array<{
|
||||||
|
long_name: string;
|
||||||
|
short_name: string;
|
||||||
|
types: string[];
|
||||||
|
}>;
|
||||||
|
}>;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PlacesSearchResponse extends GoogleMapsResponse {
|
||||||
|
results: Array<{
|
||||||
|
name: string;
|
||||||
|
place_id: string;
|
||||||
|
formatted_address: string;
|
||||||
|
geometry: {
|
||||||
|
location: {
|
||||||
|
lat: number;
|
||||||
|
lng: number;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
rating?: number;
|
||||||
|
types: string[];
|
||||||
|
}>;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PlaceDetailsResponse extends GoogleMapsResponse {
|
||||||
|
result: {
|
||||||
|
name: string;
|
||||||
|
place_id: string;
|
||||||
|
formatted_address: string;
|
||||||
|
formatted_phone_number?: string;
|
||||||
|
website?: string;
|
||||||
|
rating?: number;
|
||||||
|
reviews?: Array<{
|
||||||
|
author_name: string;
|
||||||
|
rating: number;
|
||||||
|
text: string;
|
||||||
|
time: number;
|
||||||
|
}>;
|
||||||
|
opening_hours?: {
|
||||||
|
weekday_text: string[];
|
||||||
|
open_now: boolean;
|
||||||
|
};
|
||||||
|
geometry: {
|
||||||
|
location: {
|
||||||
|
lat: number;
|
||||||
|
lng: number;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DistanceMatrixResponse extends GoogleMapsResponse {
|
||||||
|
origin_addresses: string[];
|
||||||
|
destination_addresses: string[];
|
||||||
|
rows: Array<{
|
||||||
|
elements: Array<{
|
||||||
|
status: string;
|
||||||
|
duration: {
|
||||||
|
text: string;
|
||||||
|
value: number;
|
||||||
|
};
|
||||||
|
distance: {
|
||||||
|
text: string;
|
||||||
|
value: number;
|
||||||
|
};
|
||||||
|
}>;
|
||||||
|
}>;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ElevationResponse extends GoogleMapsResponse {
|
||||||
|
results: Array<{
|
||||||
|
elevation: number;
|
||||||
|
location: {
|
||||||
|
lat: number;
|
||||||
|
lng: number;
|
||||||
|
};
|
||||||
|
resolution: number;
|
||||||
|
}>;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DirectionsResponse extends GoogleMapsResponse {
|
||||||
|
routes: Array<{
|
||||||
|
summary: string;
|
||||||
|
legs: Array<{
|
||||||
|
distance: {
|
||||||
|
text: string;
|
||||||
|
value: number;
|
||||||
|
};
|
||||||
|
duration: {
|
||||||
|
text: string;
|
||||||
|
value: number;
|
||||||
|
};
|
||||||
|
steps: Array<{
|
||||||
|
html_instructions: string;
|
||||||
|
distance: {
|
||||||
|
text: string;
|
||||||
|
value: number;
|
||||||
|
};
|
||||||
|
duration: {
|
||||||
|
text: string;
|
||||||
|
value: number;
|
||||||
|
};
|
||||||
|
travel_mode: string;
|
||||||
|
}>;
|
||||||
|
}>;
|
||||||
|
}>;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getApiKey(): string {
|
||||||
|
const apiKey = process.env.GOOGLE_MAPS_API_KEY;
|
||||||
|
if (!apiKey) {
|
||||||
|
console.error("GOOGLE_MAPS_API_KEY environment variable is not set");
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
return apiKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
const GOOGLE_MAPS_API_KEY = getApiKey();
|
||||||
|
|
||||||
|
// Tool definitions
|
||||||
|
const GEOCODE_TOOL: Tool = {
|
||||||
|
name: "maps_geocode",
|
||||||
|
description: "Convert an address into geographic coordinates",
|
||||||
|
inputSchema: {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
address: {
|
||||||
|
type: "string",
|
||||||
|
description: "The address to geocode"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
required: ["address"]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const REVERSE_GEOCODE_TOOL: Tool = {
|
||||||
|
name: "maps_reverse_geocode",
|
||||||
|
description: "Convert coordinates into an address",
|
||||||
|
inputSchema: {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
latitude: {
|
||||||
|
type: "number",
|
||||||
|
description: "Latitude coordinate"
|
||||||
|
},
|
||||||
|
longitude: {
|
||||||
|
type: "number",
|
||||||
|
description: "Longitude coordinate"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
required: ["latitude", "longitude"]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const SEARCH_PLACES_TOOL: Tool = {
|
||||||
|
name: "maps_search_places",
|
||||||
|
description: "Search for places using Google Places API",
|
||||||
|
inputSchema: {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
query: {
|
||||||
|
type: "string",
|
||||||
|
description: "Search query"
|
||||||
|
},
|
||||||
|
location: {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
latitude: { type: "number" },
|
||||||
|
longitude: { type: "number" }
|
||||||
|
},
|
||||||
|
description: "Optional center point for the search"
|
||||||
|
},
|
||||||
|
radius: {
|
||||||
|
type: "number",
|
||||||
|
description: "Search radius in meters (max 50000)"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
required: ["query"]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const PLACE_DETAILS_TOOL: Tool = {
|
||||||
|
name: "maps_place_details",
|
||||||
|
description: "Get detailed information about a specific place",
|
||||||
|
inputSchema: {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
place_id: {
|
||||||
|
type: "string",
|
||||||
|
description: "The place ID to get details for"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
required: ["place_id"]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const DISTANCE_MATRIX_TOOL: Tool = {
|
||||||
|
name: "maps_distance_matrix",
|
||||||
|
description: "Calculate travel distance and time for multiple origins and destinations",
|
||||||
|
inputSchema: {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
origins: {
|
||||||
|
type: "array",
|
||||||
|
items: { type: "string" },
|
||||||
|
description: "Array of origin addresses or coordinates"
|
||||||
|
},
|
||||||
|
destinations: {
|
||||||
|
type: "array",
|
||||||
|
items: { type: "string" },
|
||||||
|
description: "Array of destination addresses or coordinates"
|
||||||
|
},
|
||||||
|
mode: {
|
||||||
|
type: "string",
|
||||||
|
description: "Travel mode (driving, walking, bicycling, transit)",
|
||||||
|
enum: ["driving", "walking", "bicycling", "transit"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
required: ["origins", "destinations"]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const ELEVATION_TOOL: Tool = {
|
||||||
|
name: "maps_elevation",
|
||||||
|
description: "Get elevation data for locations on the earth",
|
||||||
|
inputSchema: {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
locations: {
|
||||||
|
type: "array",
|
||||||
|
items: {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
latitude: { type: "number" },
|
||||||
|
longitude: { type: "number" }
|
||||||
|
},
|
||||||
|
required: ["latitude", "longitude"]
|
||||||
|
},
|
||||||
|
description: "Array of locations to get elevation for"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
required: ["locations"]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const DIRECTIONS_TOOL: Tool = {
|
||||||
|
name: "maps_directions",
|
||||||
|
description: "Get directions between two points",
|
||||||
|
inputSchema: {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
origin: {
|
||||||
|
type: "string",
|
||||||
|
description: "Starting point address or coordinates"
|
||||||
|
},
|
||||||
|
destination: {
|
||||||
|
type: "string",
|
||||||
|
description: "Ending point address or coordinates"
|
||||||
|
},
|
||||||
|
mode: {
|
||||||
|
type: "string",
|
||||||
|
description: "Travel mode (driving, walking, bicycling, transit)",
|
||||||
|
enum: ["driving", "walking", "bicycling", "transit"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
required: ["origin", "destination"]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const MAPS_TOOLS = [
|
||||||
|
GEOCODE_TOOL,
|
||||||
|
REVERSE_GEOCODE_TOOL,
|
||||||
|
SEARCH_PLACES_TOOL,
|
||||||
|
PLACE_DETAILS_TOOL,
|
||||||
|
DISTANCE_MATRIX_TOOL,
|
||||||
|
ELEVATION_TOOL,
|
||||||
|
DIRECTIONS_TOOL,
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
// API handlers
|
||||||
|
async function handleGeocode(address: string) {
|
||||||
|
const url = new URL("https://maps.googleapis.com/maps/api/geocode/json");
|
||||||
|
url.searchParams.append("address", address);
|
||||||
|
url.searchParams.append("key", GOOGLE_MAPS_API_KEY);
|
||||||
|
|
||||||
|
const response = await fetch(url.toString());
|
||||||
|
const data = await response.json() as GeocodeResponse;
|
||||||
|
|
||||||
|
if (data.status !== "OK") {
|
||||||
|
return {
|
||||||
|
toolResult: {
|
||||||
|
content: [{
|
||||||
|
type: "text",
|
||||||
|
text: `Geocoding failed: ${data.error_message || data.status}`
|
||||||
|
}],
|
||||||
|
isError: true
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
toolResult: {
|
||||||
|
content: [{
|
||||||
|
type: "text",
|
||||||
|
text: JSON.stringify({
|
||||||
|
location: data.results[0].geometry.location,
|
||||||
|
formatted_address: data.results[0].formatted_address,
|
||||||
|
place_id: data.results[0].place_id
|
||||||
|
}, null, 2)
|
||||||
|
}],
|
||||||
|
isError: false
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleReverseGeocode(latitude: number, longitude: number) {
|
||||||
|
const url = new URL("https://maps.googleapis.com/maps/api/geocode/json");
|
||||||
|
url.searchParams.append("latlng", `${latitude},${longitude}`);
|
||||||
|
url.searchParams.append("key", GOOGLE_MAPS_API_KEY);
|
||||||
|
|
||||||
|
const response = await fetch(url.toString());
|
||||||
|
const data = await response.json() as GeocodeResponse;
|
||||||
|
|
||||||
|
if (data.status !== "OK") {
|
||||||
|
return {
|
||||||
|
toolResult: {
|
||||||
|
content: [{
|
||||||
|
type: "text",
|
||||||
|
text: `Reverse geocoding failed: ${data.error_message || data.status}`
|
||||||
|
}],
|
||||||
|
isError: true
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
toolResult: {
|
||||||
|
content: [{
|
||||||
|
type: "text",
|
||||||
|
text: JSON.stringify({
|
||||||
|
formatted_address: data.results[0].formatted_address,
|
||||||
|
place_id: data.results[0].place_id,
|
||||||
|
address_components: data.results[0].address_components
|
||||||
|
}, null, 2)
|
||||||
|
}],
|
||||||
|
isError: false
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handlePlaceSearch(
|
||||||
|
query: string,
|
||||||
|
location?: { latitude: number; longitude: number },
|
||||||
|
radius?: number
|
||||||
|
) {
|
||||||
|
const url = new URL("https://maps.googleapis.com/maps/api/place/textsearch/json");
|
||||||
|
url.searchParams.append("query", query);
|
||||||
|
url.searchParams.append("key", GOOGLE_MAPS_API_KEY);
|
||||||
|
|
||||||
|
if (location) {
|
||||||
|
url.searchParams.append("location", `${location.latitude},${location.longitude}`);
|
||||||
|
}
|
||||||
|
if (radius) {
|
||||||
|
url.searchParams.append("radius", radius.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetch(url.toString());
|
||||||
|
const data = await response.json() as PlacesSearchResponse;
|
||||||
|
|
||||||
|
if (data.status !== "OK") {
|
||||||
|
return {
|
||||||
|
toolResult: {
|
||||||
|
content: [{
|
||||||
|
type: "text",
|
||||||
|
text: `Place search failed: ${data.error_message || data.status}`
|
||||||
|
}],
|
||||||
|
isError: true
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
toolResult: {
|
||||||
|
content: [{
|
||||||
|
type: "text",
|
||||||
|
text: JSON.stringify({
|
||||||
|
places: data.results.map((place) => ({
|
||||||
|
name: place.name,
|
||||||
|
formatted_address: place.formatted_address,
|
||||||
|
location: place.geometry.location,
|
||||||
|
place_id: place.place_id,
|
||||||
|
rating: place.rating,
|
||||||
|
types: place.types
|
||||||
|
}))
|
||||||
|
}, null, 2)
|
||||||
|
}],
|
||||||
|
isError: false
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handlePlaceDetails(place_id: string) {
|
||||||
|
const url = new URL("https://maps.googleapis.com/maps/api/place/details/json");
|
||||||
|
url.searchParams.append("place_id", place_id);
|
||||||
|
url.searchParams.append("key", GOOGLE_MAPS_API_KEY);
|
||||||
|
|
||||||
|
const response = await fetch(url.toString());
|
||||||
|
const data = await response.json() as PlaceDetailsResponse;
|
||||||
|
|
||||||
|
if (data.status !== "OK") {
|
||||||
|
return {
|
||||||
|
toolResult: {
|
||||||
|
content: [{
|
||||||
|
type: "text",
|
||||||
|
text: `Place details request failed: ${data.error_message || data.status}`
|
||||||
|
}],
|
||||||
|
isError: true
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
toolResult: {
|
||||||
|
content: [{
|
||||||
|
type: "text",
|
||||||
|
text: JSON.stringify({
|
||||||
|
name: data.result.name,
|
||||||
|
formatted_address: data.result.formatted_address,
|
||||||
|
location: data.result.geometry.location,
|
||||||
|
formatted_phone_number: data.result.formatted_phone_number,
|
||||||
|
website: data.result.website,
|
||||||
|
rating: data.result.rating,
|
||||||
|
reviews: data.result.reviews,
|
||||||
|
opening_hours: data.result.opening_hours
|
||||||
|
}, null, 2)
|
||||||
|
}],
|
||||||
|
isError: false
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
async function handleDistanceMatrix(
|
||||||
|
origins: string[],
|
||||||
|
destinations: string[],
|
||||||
|
mode: "driving" | "walking" | "bicycling" | "transit" = "driving"
|
||||||
|
) {
|
||||||
|
const url = new URL("https://maps.googleapis.com/maps/api/distancematrix/json");
|
||||||
|
url.searchParams.append("origins", origins.join("|"));
|
||||||
|
url.searchParams.append("destinations", destinations.join("|"));
|
||||||
|
url.searchParams.append("mode", mode);
|
||||||
|
url.searchParams.append("key", GOOGLE_MAPS_API_KEY);
|
||||||
|
|
||||||
|
const response = await fetch(url.toString());
|
||||||
|
const data = await response.json() as DistanceMatrixResponse;
|
||||||
|
|
||||||
|
if (data.status !== "OK") {
|
||||||
|
return {
|
||||||
|
toolResult: {
|
||||||
|
content: [{
|
||||||
|
type: "text",
|
||||||
|
text: `Distance matrix request failed: ${data.error_message || data.status}`
|
||||||
|
}],
|
||||||
|
isError: true
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
toolResult: {
|
||||||
|
content: [{
|
||||||
|
type: "text",
|
||||||
|
text: JSON.stringify({
|
||||||
|
origin_addresses: data.origin_addresses,
|
||||||
|
destination_addresses: data.destination_addresses,
|
||||||
|
results: data.rows.map((row) => ({
|
||||||
|
elements: row.elements.map((element) => ({
|
||||||
|
status: element.status,
|
||||||
|
duration: element.duration,
|
||||||
|
distance: element.distance
|
||||||
|
}))
|
||||||
|
}))
|
||||||
|
}, null, 2)
|
||||||
|
}],
|
||||||
|
isError: false
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleElevation(locations: Array<{ latitude: number; longitude: number }>) {
|
||||||
|
const url = new URL("https://maps.googleapis.com/maps/api/elevation/json");
|
||||||
|
const locationString = locations
|
||||||
|
.map((loc) => `${loc.latitude},${loc.longitude}`)
|
||||||
|
.join("|");
|
||||||
|
url.searchParams.append("locations", locationString);
|
||||||
|
url.searchParams.append("key", GOOGLE_MAPS_API_KEY);
|
||||||
|
|
||||||
|
const response = await fetch(url.toString());
|
||||||
|
const data = await response.json() as ElevationResponse;
|
||||||
|
|
||||||
|
if (data.status !== "OK") {
|
||||||
|
return {
|
||||||
|
toolResult: {
|
||||||
|
content: [{
|
||||||
|
type: "text",
|
||||||
|
text: `Elevation request failed: ${data.error_message || data.status}`
|
||||||
|
}],
|
||||||
|
isError: true
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
toolResult: {
|
||||||
|
content: [{
|
||||||
|
type: "text",
|
||||||
|
text: JSON.stringify({
|
||||||
|
results: data.results.map((result) => ({
|
||||||
|
elevation: result.elevation,
|
||||||
|
location: result.location,
|
||||||
|
resolution: result.resolution
|
||||||
|
}))
|
||||||
|
}, null, 2)
|
||||||
|
}],
|
||||||
|
isError: false
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleDirections(
|
||||||
|
origin: string,
|
||||||
|
destination: string,
|
||||||
|
mode: "driving" | "walking" | "bicycling" | "transit" = "driving"
|
||||||
|
) {
|
||||||
|
const url = new URL("https://maps.googleapis.com/maps/api/directions/json");
|
||||||
|
url.searchParams.append("origin", origin);
|
||||||
|
url.searchParams.append("destination", destination);
|
||||||
|
url.searchParams.append("mode", mode);
|
||||||
|
url.searchParams.append("key", GOOGLE_MAPS_API_KEY);
|
||||||
|
|
||||||
|
const response = await fetch(url.toString());
|
||||||
|
const data = await response.json() as DirectionsResponse;
|
||||||
|
|
||||||
|
if (data.status !== "OK") {
|
||||||
|
return {
|
||||||
|
toolResult: {
|
||||||
|
content: [{
|
||||||
|
type: "text",
|
||||||
|
text: `Directions request failed: ${data.error_message || data.status}`
|
||||||
|
}],
|
||||||
|
isError: true
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
toolResult: {
|
||||||
|
content: [{
|
||||||
|
type: "text",
|
||||||
|
text: JSON.stringify({
|
||||||
|
routes: data.routes.map((route) => ({
|
||||||
|
summary: route.summary,
|
||||||
|
distance: route.legs[0].distance,
|
||||||
|
duration: route.legs[0].duration,
|
||||||
|
steps: route.legs[0].steps.map((step) => ({
|
||||||
|
instructions: step.html_instructions,
|
||||||
|
distance: step.distance,
|
||||||
|
duration: step.duration,
|
||||||
|
travel_mode: step.travel_mode
|
||||||
|
}))
|
||||||
|
}))
|
||||||
|
}, null, 2)
|
||||||
|
}],
|
||||||
|
isError: false
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Server setup
|
||||||
|
const server = new Server(
|
||||||
|
{
|
||||||
|
name: "mcp-server/google-maps",
|
||||||
|
version: "0.1.0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
capabilities: {
|
||||||
|
tools: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// Set up request handlers
|
||||||
|
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
||||||
|
tools: MAPS_TOOLS,
|
||||||
|
}));
|
||||||
|
|
||||||
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
||||||
|
try {
|
||||||
|
switch (request.params.name) {
|
||||||
|
case "maps_geocode": {
|
||||||
|
const { address } = request.params.arguments as { address: string };
|
||||||
|
return await handleGeocode(address);
|
||||||
|
}
|
||||||
|
|
||||||
|
case "maps_reverse_geocode": {
|
||||||
|
const { latitude, longitude } = request.params.arguments as {
|
||||||
|
latitude: number;
|
||||||
|
longitude: number;
|
||||||
|
};
|
||||||
|
return await handleReverseGeocode(latitude, longitude);
|
||||||
|
}
|
||||||
|
|
||||||
|
case "maps_search_places": {
|
||||||
|
const { query, location, radius } = request.params.arguments as {
|
||||||
|
query: string;
|
||||||
|
location?: { latitude: number; longitude: number };
|
||||||
|
radius?: number;
|
||||||
|
};
|
||||||
|
return await handlePlaceSearch(query, location, radius);
|
||||||
|
}
|
||||||
|
|
||||||
|
case "maps_place_details": {
|
||||||
|
const { place_id } = request.params.arguments as { place_id: string };
|
||||||
|
return await handlePlaceDetails(place_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
case "maps_distance_matrix": {
|
||||||
|
const { origins, destinations, mode } = request.params.arguments as {
|
||||||
|
origins: string[];
|
||||||
|
destinations: string[];
|
||||||
|
mode?: "driving" | "walking" | "bicycling" | "transit";
|
||||||
|
};
|
||||||
|
return await handleDistanceMatrix(origins, destinations, mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
case "maps_elevation": {
|
||||||
|
const { locations } = request.params.arguments as {
|
||||||
|
locations: Array<{ latitude: number; longitude: number }>;
|
||||||
|
};
|
||||||
|
return await handleElevation(locations);
|
||||||
|
}
|
||||||
|
|
||||||
|
case "maps_directions": {
|
||||||
|
const { origin, destination, mode } = request.params.arguments as {
|
||||||
|
origin: string;
|
||||||
|
destination: string;
|
||||||
|
mode?: "driving" | "walking" | "bicycling" | "transit";
|
||||||
|
};
|
||||||
|
return await handleDirections(origin, destination, mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
return {
|
||||||
|
toolResult: {
|
||||||
|
content: [{
|
||||||
|
type: "text",
|
||||||
|
text: `Unknown tool: ${request.params.name}`
|
||||||
|
}],
|
||||||
|
isError: true
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
toolResult: {
|
||||||
|
content: [{
|
||||||
|
type: "text",
|
||||||
|
text: `Error: ${error instanceof Error ? error.message : String(error)}`
|
||||||
|
}],
|
||||||
|
isError: true
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
async function runServer() {
|
||||||
|
const transport = new StdioServerTransport();
|
||||||
|
await server.connect(transport);
|
||||||
|
console.error("Google Maps MCP Server running on stdio");
|
||||||
|
}
|
||||||
|
|
||||||
|
runServer().catch((error) => {
|
||||||
|
console.error("Fatal error running server:", error);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
29
src/google-maps/package.json
Normal file
29
src/google-maps/package.json
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"name": "@modelcontextprotocol/server-google-maps",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"description": "MCP server for using the Google Maps API",
|
||||||
|
"license": "MIT",
|
||||||
|
"author": "Anthropic, PBC (https://anthropic.com)",
|
||||||
|
"homepage": "https://modelcontextprotocol.io",
|
||||||
|
"bugs": "https://github.com/modelcontextprotocol/servers/issues",
|
||||||
|
"type": "module",
|
||||||
|
"bin": {
|
||||||
|
"mcp-server-google-maps": "dist/index.js"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"dist"
|
||||||
|
],
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsc && shx chmod +x dist/*.js",
|
||||||
|
"prepare": "npm run build",
|
||||||
|
"watch": "tsc --watch"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@modelcontextprotocol/sdk": "0.6.0",
|
||||||
|
"@types/node-fetch": "^2.6.12"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"shx": "^0.3.4",
|
||||||
|
"typescript": "^5.6.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
10
src/google-maps/tsconfig.json
Normal file
10
src/google-maps/tsconfig.json
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "./dist",
|
||||||
|
"rootDir": "."
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"./**/*.ts"
|
||||||
|
]
|
||||||
|
}
|
||||||
101
src/memory/README.md
Normal file
101
src/memory/README.md
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
# Knowledge Graph Memory Server
|
||||||
|
A basic MCP server implementation that provides persistent memory using a knowledge-graph. The server manages entities, their observations, and the relationships between them using a JSON-based file system.
|
||||||
|
|
||||||
|
This lets Claude remember information about the user across chats and projects, and lets them bypass the issues of having super long chats
|
||||||
|
|
||||||
|
# Core Concepts
|
||||||
|
|
||||||
|
## Entities
|
||||||
|
Entities are the primary nodes in the knowledge graph. Each entity has:
|
||||||
|
- A unique name (identifier)
|
||||||
|
- An entity type (e.g., "person", "organization", "event")
|
||||||
|
- A list of observations
|
||||||
|
|
||||||
|
Example:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "John_Smith",
|
||||||
|
"entityType": "person",
|
||||||
|
"observations": ["Lives in New York", "Works as a software engineer"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Relations
|
||||||
|
Relations define directed connections between entities. They are always stored in active voice and describe how entities interact or relate to each other.
|
||||||
|
Example:
|
||||||
|
```jsonCopy{
|
||||||
|
"from": "John_Smith",
|
||||||
|
"to": "TechCorp",
|
||||||
|
"relationType": "works_at"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
## Observations
|
||||||
|
Observations are discrete pieces of information about an entity. They are:
|
||||||
|
|
||||||
|
- Stored as strings
|
||||||
|
- Attached to specific entities
|
||||||
|
- Can be added or removed independently
|
||||||
|
- Should be atomic (one fact per observation)
|
||||||
|
|
||||||
|
Example:
|
||||||
|
```jsonCopy{
|
||||||
|
"entityName": "John_Smith",
|
||||||
|
"observations": [
|
||||||
|
"Speaks fluent Spanish",
|
||||||
|
"Graduated in 2019",
|
||||||
|
"Prefers morning meetings"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
# Tools
|
||||||
|
|
||||||
|
## Entity Management
|
||||||
|
|
||||||
|
- create_entities: Create new entities in the knowledge graph with names, types, and observations
|
||||||
|
- delete_entities: Remove entities and their associated relations from the graph
|
||||||
|
- add_observations: Add new observations to existing entities
|
||||||
|
- delete_observations: Remove specific observations from entities
|
||||||
|
|
||||||
|
|
||||||
|
## Relation Management
|
||||||
|
|
||||||
|
- create_relations: Establish relationships between entities in active voice
|
||||||
|
- delete_relations: Remove specific relationships between entities
|
||||||
|
|
||||||
|
|
||||||
|
## Query Tools
|
||||||
|
|
||||||
|
- read_graph: Retrieve the entire knowledge graph
|
||||||
|
- search_nodes: Search for nodes based on names, types, and observation content
|
||||||
|
- open_nodes: Access specific nodes by their names
|
||||||
|
|
||||||
|
# Prompts
|
||||||
|
|
||||||
|
The prompt for utilizing memory depends on the use case, but here is an example prompt for chat personalization. You could use this prompt in the "Custom Instructions" field of a Project
|
||||||
|
|
||||||
|
```
|
||||||
|
Follow these steps for each interaction:
|
||||||
|
|
||||||
|
1. User Identification:
|
||||||
|
- You should assume that you are interacting with default_user
|
||||||
|
- If you have not identified default_user, proactively try to do so.
|
||||||
|
|
||||||
|
2. Memory Retrieval:
|
||||||
|
- Always begin your chat by saying only "Remembering..." and retrieve all relevant information from your knowledge graph
|
||||||
|
- Always refer to your knowledge as your "memory"
|
||||||
|
|
||||||
|
3. Memory
|
||||||
|
- While conversing with the user, be attentive to any new information that falls into these categories:
|
||||||
|
a) Basic Identity (Age, gender, location, Job title, education level, etc.)
|
||||||
|
b) Behaviors (interests, habits, etc.)
|
||||||
|
c) Preferences (communication style, preferred language, etc.)
|
||||||
|
d) Goals/Psychology (Goals, targets, aspirations, etc.)
|
||||||
|
e) Relationships (personal and professional relationships up to 3 degrees of separation)
|
||||||
|
|
||||||
|
4. Memory Update:
|
||||||
|
- If any new information was gathered during the interaction, update your memory as follows:
|
||||||
|
a) Create nodes for recurring organizations, people, and significant events, connecting them to the current node.
|
||||||
|
b) Store most facts as observations within these nodes
|
||||||
|
- Try to perform all updates in one operation using the create and delete functions.
|
||||||
|
```
|
||||||
414
src/memory/index.ts
Normal file
414
src/memory/index.ts
Normal file
@@ -0,0 +1,414 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
||||||
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
||||||
|
import {
|
||||||
|
CallToolRequestSchema,
|
||||||
|
ListToolsRequestSchema,
|
||||||
|
} from "@modelcontextprotocol/sdk/types.js";
|
||||||
|
import { promises as fs } from 'fs';
|
||||||
|
import path from 'path';
|
||||||
|
import { fileURLToPath } from 'url';
|
||||||
|
|
||||||
|
|
||||||
|
// Define the path to the JSONL file, you can change this to your desired local path
|
||||||
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||||
|
const MEMORY_FILE_PATH = path.join(__dirname, 'memory.json');
|
||||||
|
|
||||||
|
// We are storing our memory using entities, relations, and observations in a graph structure
|
||||||
|
interface Entity {
|
||||||
|
name: string;
|
||||||
|
entityType: string;
|
||||||
|
observations: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Relation {
|
||||||
|
from: string;
|
||||||
|
to: string;
|
||||||
|
relationType: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface KnowledgeGraph {
|
||||||
|
entities: Entity[];
|
||||||
|
relations: Relation[];
|
||||||
|
}
|
||||||
|
|
||||||
|
// The KnowledgeGraphManager class contains all operations to interact with the knowledge graph
|
||||||
|
class KnowledgeGraphManager {
|
||||||
|
private async loadGraph(): Promise<KnowledgeGraph> {
|
||||||
|
try {
|
||||||
|
const data = await fs.readFile(MEMORY_FILE_PATH, "utf-8");
|
||||||
|
const lines = data.split("\n").filter(line => line.trim() !== "");
|
||||||
|
return lines.reduce((graph: KnowledgeGraph, line) => {
|
||||||
|
const item = JSON.parse(line);
|
||||||
|
if (item.type === "entity") graph.entities.push(item as Entity);
|
||||||
|
if (item.type === "relation") graph.relations.push(item as Relation);
|
||||||
|
return graph;
|
||||||
|
}, { entities: [], relations: [] });
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof Error && 'code' in error && (error as any).code === "ENOENT") {
|
||||||
|
return { entities: [], relations: [] };
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async saveGraph(graph: KnowledgeGraph): Promise<void> {
|
||||||
|
const lines = [
|
||||||
|
...graph.entities.map(e => JSON.stringify({ type: "entity", ...e })),
|
||||||
|
...graph.relations.map(r => JSON.stringify({ type: "relation", ...r })),
|
||||||
|
];
|
||||||
|
await fs.writeFile(MEMORY_FILE_PATH, lines.join("\n"));
|
||||||
|
}
|
||||||
|
|
||||||
|
async createEntities(entities: Entity[]): Promise<Entity[]> {
|
||||||
|
const graph = await this.loadGraph();
|
||||||
|
const newEntities = entities.filter(e => !graph.entities.some(existingEntity => existingEntity.name === e.name));
|
||||||
|
graph.entities.push(...newEntities);
|
||||||
|
await this.saveGraph(graph);
|
||||||
|
return newEntities;
|
||||||
|
}
|
||||||
|
|
||||||
|
async createRelations(relations: Relation[]): Promise<Relation[]> {
|
||||||
|
const graph = await this.loadGraph();
|
||||||
|
const newRelations = relations.filter(r => !graph.relations.some(existingRelation =>
|
||||||
|
existingRelation.from === r.from &&
|
||||||
|
existingRelation.to === r.to &&
|
||||||
|
existingRelation.relationType === r.relationType
|
||||||
|
));
|
||||||
|
graph.relations.push(...newRelations);
|
||||||
|
await this.saveGraph(graph);
|
||||||
|
return newRelations;
|
||||||
|
}
|
||||||
|
|
||||||
|
async addObservations(observations: { entityName: string; contents: string[] }[]): Promise<{ entityName: string; addedObservations: string[] }[]> {
|
||||||
|
const graph = await this.loadGraph();
|
||||||
|
const results = observations.map(o => {
|
||||||
|
const entity = graph.entities.find(e => e.name === o.entityName);
|
||||||
|
if (!entity) {
|
||||||
|
throw new Error(`Entity with name ${o.entityName} not found`);
|
||||||
|
}
|
||||||
|
const newObservations = o.contents.filter(content => !entity.observations.includes(content));
|
||||||
|
entity.observations.push(...newObservations);
|
||||||
|
return { entityName: o.entityName, addedObservations: newObservations };
|
||||||
|
});
|
||||||
|
await this.saveGraph(graph);
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteEntities(entityNames: string[]): Promise<void> {
|
||||||
|
const graph = await this.loadGraph();
|
||||||
|
graph.entities = graph.entities.filter(e => !entityNames.includes(e.name));
|
||||||
|
graph.relations = graph.relations.filter(r => !entityNames.includes(r.from) && !entityNames.includes(r.to));
|
||||||
|
await this.saveGraph(graph);
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteObservations(deletions: { entityName: string; observations: string[] }[]): Promise<void> {
|
||||||
|
const graph = await this.loadGraph();
|
||||||
|
deletions.forEach(d => {
|
||||||
|
const entity = graph.entities.find(e => e.name === d.entityName);
|
||||||
|
if (entity) {
|
||||||
|
entity.observations = entity.observations.filter(o => !d.observations.includes(o));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
await this.saveGraph(graph);
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteRelations(relations: Relation[]): Promise<void> {
|
||||||
|
const graph = await this.loadGraph();
|
||||||
|
graph.relations = graph.relations.filter(r => !relations.some(delRelation =>
|
||||||
|
r.from === delRelation.from &&
|
||||||
|
r.to === delRelation.to &&
|
||||||
|
r.relationType === delRelation.relationType
|
||||||
|
));
|
||||||
|
await this.saveGraph(graph);
|
||||||
|
}
|
||||||
|
|
||||||
|
async readGraph(): Promise<KnowledgeGraph> {
|
||||||
|
return this.loadGraph();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Very basic search function
|
||||||
|
async searchNodes(query: string): Promise<KnowledgeGraph> {
|
||||||
|
const graph = await this.loadGraph();
|
||||||
|
|
||||||
|
// Filter entities
|
||||||
|
const filteredEntities = graph.entities.filter(e =>
|
||||||
|
e.name.toLowerCase().includes(query.toLowerCase()) ||
|
||||||
|
e.entityType.toLowerCase().includes(query.toLowerCase()) ||
|
||||||
|
e.observations.some(o => o.toLowerCase().includes(query.toLowerCase()))
|
||||||
|
);
|
||||||
|
|
||||||
|
// Create a Set of filtered entity names for quick lookup
|
||||||
|
const filteredEntityNames = new Set(filteredEntities.map(e => e.name));
|
||||||
|
|
||||||
|
// Filter relations to only include those between filtered entities
|
||||||
|
const filteredRelations = graph.relations.filter(r =>
|
||||||
|
filteredEntityNames.has(r.from) && filteredEntityNames.has(r.to)
|
||||||
|
);
|
||||||
|
|
||||||
|
const filteredGraph: KnowledgeGraph = {
|
||||||
|
entities: filteredEntities,
|
||||||
|
relations: filteredRelations,
|
||||||
|
};
|
||||||
|
|
||||||
|
return filteredGraph;
|
||||||
|
}
|
||||||
|
|
||||||
|
async openNodes(names: string[]): Promise<KnowledgeGraph> {
|
||||||
|
const graph = await this.loadGraph();
|
||||||
|
|
||||||
|
// Filter entities
|
||||||
|
const filteredEntities = graph.entities.filter(e => names.includes(e.name));
|
||||||
|
|
||||||
|
// Create a Set of filtered entity names for quick lookup
|
||||||
|
const filteredEntityNames = new Set(filteredEntities.map(e => e.name));
|
||||||
|
|
||||||
|
// Filter relations to only include those between filtered entities
|
||||||
|
const filteredRelations = graph.relations.filter(r =>
|
||||||
|
filteredEntityNames.has(r.from) && filteredEntityNames.has(r.to)
|
||||||
|
);
|
||||||
|
|
||||||
|
const filteredGraph: KnowledgeGraph = {
|
||||||
|
entities: filteredEntities,
|
||||||
|
relations: filteredRelations,
|
||||||
|
};
|
||||||
|
|
||||||
|
return filteredGraph;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const knowledgeGraphManager = new KnowledgeGraphManager();
|
||||||
|
|
||||||
|
|
||||||
|
// The server instance and tools exposed to Claude
|
||||||
|
const server = new Server({
|
||||||
|
name: "memory-server",
|
||||||
|
version: "1.0.0",
|
||||||
|
}, {
|
||||||
|
capabilities: {
|
||||||
|
tools: {},
|
||||||
|
},
|
||||||
|
},);
|
||||||
|
|
||||||
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
||||||
|
return {
|
||||||
|
tools: [
|
||||||
|
{
|
||||||
|
name: "create_entities",
|
||||||
|
description: "Create multiple new entities in the knowledge graph",
|
||||||
|
inputSchema: {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
entities: {
|
||||||
|
type: "array",
|
||||||
|
items: {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
name: { type: "string", description: "The name of the entity" },
|
||||||
|
entityType: { type: "string", description: "The type of the entity" },
|
||||||
|
observations: {
|
||||||
|
type: "array",
|
||||||
|
items: { type: "string" },
|
||||||
|
description: "An array of observation contents associated with the entity"
|
||||||
|
},
|
||||||
|
},
|
||||||
|
required: ["name", "entityType", "observations"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
required: ["entities"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "create_relations",
|
||||||
|
description: "Create multiple new relations between entities in the knowledge graph. Relations should be in active voice",
|
||||||
|
inputSchema: {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
relations: {
|
||||||
|
type: "array",
|
||||||
|
items: {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
from: { type: "string", description: "The name of the entity where the relation starts" },
|
||||||
|
to: { type: "string", description: "The name of the entity where the relation ends" },
|
||||||
|
relationType: { type: "string", description: "The type of the relation" },
|
||||||
|
},
|
||||||
|
required: ["from", "to", "relationType"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
required: ["relations"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "add_observations",
|
||||||
|
description: "Add new observations to existing entities in the knowledge graph",
|
||||||
|
inputSchema: {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
observations: {
|
||||||
|
type: "array",
|
||||||
|
items: {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
entityName: { type: "string", description: "The name of the entity to add the observations to" },
|
||||||
|
contents: {
|
||||||
|
type: "array",
|
||||||
|
items: { type: "string" },
|
||||||
|
description: "An array of observation contents to add"
|
||||||
|
},
|
||||||
|
},
|
||||||
|
required: ["entityName", "contents"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
required: ["observations"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "delete_entities",
|
||||||
|
description: "Delete multiple entities and their associated relations from the knowledge graph",
|
||||||
|
inputSchema: {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
entityNames: {
|
||||||
|
type: "array",
|
||||||
|
items: { type: "string" },
|
||||||
|
description: "An array of entity names to delete"
|
||||||
|
},
|
||||||
|
},
|
||||||
|
required: ["entityNames"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "delete_observations",
|
||||||
|
description: "Delete specific observations from entities in the knowledge graph",
|
||||||
|
inputSchema: {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
deletions: {
|
||||||
|
type: "array",
|
||||||
|
items: {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
entityName: { type: "string", description: "The name of the entity containing the observations" },
|
||||||
|
observations: {
|
||||||
|
type: "array",
|
||||||
|
items: { type: "string" },
|
||||||
|
description: "An array of observations to delete"
|
||||||
|
},
|
||||||
|
},
|
||||||
|
required: ["entityName", "observations"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
required: ["deletions"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "delete_relations",
|
||||||
|
description: "Delete multiple relations from the knowledge graph",
|
||||||
|
inputSchema: {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
relations: {
|
||||||
|
type: "array",
|
||||||
|
items: {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
from: { type: "string", description: "The name of the entity where the relation starts" },
|
||||||
|
to: { type: "string", description: "The name of the entity where the relation ends" },
|
||||||
|
relationType: { type: "string", description: "The type of the relation" },
|
||||||
|
},
|
||||||
|
required: ["from", "to", "relationType"],
|
||||||
|
},
|
||||||
|
description: "An array of relations to delete"
|
||||||
|
},
|
||||||
|
},
|
||||||
|
required: ["relations"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "read_graph",
|
||||||
|
description: "Read the entire knowledge graph",
|
||||||
|
inputSchema: {
|
||||||
|
type: "object",
|
||||||
|
properties: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "search_nodes",
|
||||||
|
description: "Search for nodes in the knowledge graph based on a query",
|
||||||
|
inputSchema: {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
query: { type: "string", description: "The search query to match against entity names, types, and observation content" },
|
||||||
|
},
|
||||||
|
required: ["query"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "open_nodes",
|
||||||
|
description: "Open specific nodes in the knowledge graph by their names",
|
||||||
|
inputSchema: {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
names: {
|
||||||
|
type: "array",
|
||||||
|
items: { type: "string" },
|
||||||
|
description: "An array of entity names to retrieve",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
required: ["names"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
||||||
|
const { name, arguments: args } = request.params;
|
||||||
|
|
||||||
|
if (!args) {
|
||||||
|
throw new Error(`No arguments provided for tool: ${name}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (name) {
|
||||||
|
case "create_entities":
|
||||||
|
return { toolResult: await knowledgeGraphManager.createEntities(args.entities as Entity[]) };
|
||||||
|
case "create_relations":
|
||||||
|
return { toolResult: await knowledgeGraphManager.createRelations(args.relations as Relation[]) };
|
||||||
|
case "add_observations":
|
||||||
|
return { toolResult: await knowledgeGraphManager.addObservations(args.observations as { entityName: string; contents: string[] }[]) };
|
||||||
|
case "delete_entities":
|
||||||
|
await knowledgeGraphManager.deleteEntities(args.entityNames as string[]);
|
||||||
|
return { toolResult: "Entities deleted successfully" };
|
||||||
|
case "delete_observations":
|
||||||
|
await knowledgeGraphManager.deleteObservations(args.deletions as { entityName: string; observations: string[] }[]);
|
||||||
|
return { toolResult: "Observations deleted successfully" };
|
||||||
|
case "delete_relations":
|
||||||
|
await knowledgeGraphManager.deleteRelations(args.relations as Relation[]);
|
||||||
|
return { toolResult: "Relations deleted successfully" };
|
||||||
|
case "read_graph":
|
||||||
|
return { toolResult: await knowledgeGraphManager.readGraph() };
|
||||||
|
case "search_nodes":
|
||||||
|
return { toolResult: await knowledgeGraphManager.searchNodes(args.query as string) };
|
||||||
|
case "open_nodes":
|
||||||
|
return { toolResult: await knowledgeGraphManager.openNodes(args.names as string[]) };
|
||||||
|
default:
|
||||||
|
throw new Error(`Unknown tool: ${name}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
const transport = new StdioServerTransport();
|
||||||
|
await server.connect(transport);
|
||||||
|
console.error("Knowledge Graph MCP Server running on stdio");
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch((error) => {
|
||||||
|
console.error("Fatal error in main():", error);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
28
src/memory/package.json
Normal file
28
src/memory/package.json
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
"name": "@modelcontextprotocol/server-memory",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"description": "MCP server for enabling memory for Claude through a knowledge graph",
|
||||||
|
"license": "MIT",
|
||||||
|
"author": "Anthropic, PBC (https://anthropic.com)",
|
||||||
|
"homepage": "https://modelcontextprotocol.io",
|
||||||
|
"bugs": "https://github.com/modelcontextprotocol/servers/issues",
|
||||||
|
"type": "module",
|
||||||
|
"bin": {
|
||||||
|
"mcp-server-memory": "dist/index.js"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"dist"
|
||||||
|
],
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsc && shx chmod +x dist/*.js",
|
||||||
|
"prepare": "npm run build",
|
||||||
|
"watch": "tsc --watch"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@modelcontextprotocol/sdk": "0.5.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"shx": "^0.3.4",
|
||||||
|
"typescript": "^5.6.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
11
src/memory/tsconfig.json
Normal file
11
src/memory/tsconfig.json
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "./dist",
|
||||||
|
"rootDir": "."
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"./**/*.ts"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,9 +1,11 @@
|
|||||||
from . import server
|
from . import server
|
||||||
import asyncio
|
import asyncio
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
"""Main entry point for the package."""
|
"""Main entry point for the package."""
|
||||||
asyncio.run(server.main())
|
asyncio.run(server.main())
|
||||||
|
|
||||||
|
|
||||||
# Optionally expose other important items at package level
|
# Optionally expose other important items at package level
|
||||||
__all__ = ['main', 'server']
|
__all__ = ["main", "server"]
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ class McpServer(Server):
|
|||||||
if cursor.fetchone()[0] == 0:
|
if cursor.fetchone()[0] == 0:
|
||||||
cursor.execute(
|
cursor.execute(
|
||||||
"INSERT INTO notes (name, content) VALUES (?, ?)",
|
"INSERT INTO notes (name, content) VALUES (?, ?)",
|
||||||
("example", "This is an example note.")
|
("example", "This is an example note."),
|
||||||
)
|
)
|
||||||
conn.commit()
|
conn.commit()
|
||||||
|
|
||||||
@@ -55,7 +55,7 @@ class McpServer(Server):
|
|||||||
with closing(conn.cursor()) as cursor:
|
with closing(conn.cursor()) as cursor:
|
||||||
cursor.execute(
|
cursor.execute(
|
||||||
"INSERT OR REPLACE INTO notes (name, content) VALUES (?, ?)",
|
"INSERT OR REPLACE INTO notes (name, content) VALUES (?, ?)",
|
||||||
(name, content)
|
(name, content),
|
||||||
)
|
)
|
||||||
conn.commit()
|
conn.commit()
|
||||||
|
|
||||||
@@ -118,10 +118,14 @@ class McpServer(Server):
|
|||||||
"""Generate a prompt using notes from the database"""
|
"""Generate a prompt using notes from the database"""
|
||||||
if name != "summarize-notes":
|
if name != "summarize-notes":
|
||||||
raise ValueError(f"Unknown prompt: {name}")
|
raise ValueError(f"Unknown prompt: {name}")
|
||||||
notes = "<notes>\n" + "\n".join(
|
notes = (
|
||||||
|
"<notes>\n"
|
||||||
|
+ "\n".join(
|
||||||
f"<note name='{name}'>\n{content}\n</note>"
|
f"<note name='{name}'>\n{content}\n</note>"
|
||||||
for name, content in self._get_notes().items()
|
for name, content in self._get_notes().items()
|
||||||
) + "\n</notes>"
|
)
|
||||||
|
+ "\n</notes>"
|
||||||
|
)
|
||||||
style = (arguments or {}).get("style", "simple")
|
style = (arguments or {}).get("style", "simple")
|
||||||
prompt = """
|
prompt = """
|
||||||
Your task is to provide a summary of the notes provided below.
|
Your task is to provide a summary of the notes provided below.
|
||||||
|
|||||||
Reference in New Issue
Block a user