mirror of
https://github.com/modelcontextprotocol/servers.git
synced 2026-04-20 12:55:21 +02:00
Merge branch 'main' into ticketmaster-mcp-server
This commit is contained in:
@@ -16,7 +16,9 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "tsc && shx chmod +x dist/*.js",
|
"build": "tsc && shx chmod +x dist/*.js",
|
||||||
"prepare": "npm run build",
|
"prepare": "npm run build",
|
||||||
"watch": "tsc --watch"
|
"watch": "tsc --watch",
|
||||||
|
"start": "node dist/index.js",
|
||||||
|
"start:sse": "node dist/sse.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@modelcontextprotocol/sdk": "1.0.1",
|
"@modelcontextprotocol/sdk": "1.0.1",
|
||||||
|
|||||||
@@ -124,7 +124,7 @@ Note: all directories must be mounted to `/projects` by default.
|
|||||||
"--mount", "type=bind,src=/path/to/other/allowed/dir,dst=/projects/other/allowed/dir,ro",
|
"--mount", "type=bind,src=/path/to/other/allowed/dir,dst=/projects/other/allowed/dir,ro",
|
||||||
"--mount", "type=bind,src=/path/to/file.txt,dst=/projects/path/to/file.txt",
|
"--mount", "type=bind,src=/path/to/file.txt,dst=/projects/path/to/file.txt",
|
||||||
"mcp/filesystem",
|
"mcp/filesystem",
|
||||||
"/projects",
|
"/projects"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
import { google } from "googleapis";
|
import { google } from "googleapis";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
|
import { fileURLToPath } from 'url';
|
||||||
|
|
||||||
const drive = google.drive("v3");
|
const drive = google.drive("v3");
|
||||||
|
|
||||||
@@ -176,7 +177,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const credentialsPath = process.env.GDRIVE_CREDENTIALS_PATH || path.join(
|
const credentialsPath = process.env.GDRIVE_CREDENTIALS_PATH || path.join(
|
||||||
path.dirname(new URL(import.meta.url).pathname),
|
path.dirname(fileURLToPath(import.meta.url)),
|
||||||
"../../../.gdrive-server-credentials.json",
|
"../../../.gdrive-server-credentials.json",
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -184,7 +185,7 @@ async function authenticateAndSaveCredentials() {
|
|||||||
console.log("Launching auth flow…");
|
console.log("Launching auth flow…");
|
||||||
const auth = await authenticate({
|
const auth = await authenticate({
|
||||||
keyfilePath: process.env.GDRIVE_OAUTH_PATH || path.join(
|
keyfilePath: process.env.GDRIVE_OAUTH_PATH || path.join(
|
||||||
path.dirname(new URL(import.meta.url).pathname),
|
path.dirname(fileURLToPath(import.meta.url)),
|
||||||
"../../../gcp-oauth.keys.json",
|
"../../../gcp-oauth.keys.json",
|
||||||
),
|
),
|
||||||
scopes: ["https://www.googleapis.com/auth/drive.readonly"],
|
scopes: ["https://www.googleapis.com/auth/drive.readonly"],
|
||||||
|
|||||||
@@ -67,18 +67,23 @@ Please note that mcp-server-git is currently in early development. The functiona
|
|||||||
- `branch_name` (string): Name of the new branch
|
- `branch_name` (string): Name of the new branch
|
||||||
- `start_point` (string, optional): Starting point for the new branch
|
- `start_point` (string, optional): Starting point for the new branch
|
||||||
- Returns: Confirmation of branch creation
|
- Returns: Confirmation of branch creation
|
||||||
8. `git_checkout`
|
10. `git_checkout`
|
||||||
- Switches branches
|
- Switches branches
|
||||||
- Inputs:
|
- Inputs:
|
||||||
- `repo_path` (string): Path to Git repository
|
- `repo_path` (string): Path to Git repository
|
||||||
- `branch_name` (string): Name of branch to checkout
|
- `branch_name` (string): Name of branch to checkout
|
||||||
- Returns: Confirmation of branch switch
|
- Returns: Confirmation of branch switch
|
||||||
9. `git_show`
|
11. `git_show`
|
||||||
- Shows the contents of a commit
|
- Shows the contents of a commit
|
||||||
- Inputs:
|
- Inputs:
|
||||||
- `repo_path` (string): Path to Git repository
|
- `repo_path` (string): Path to Git repository
|
||||||
- `revision` (string): The revision (commit hash, branch name, tag) to show
|
- `revision` (string): The revision (commit hash, branch name, tag) to show
|
||||||
- Returns: Contents of the specified commit
|
- Returns: Contents of the specified commit
|
||||||
|
12. `git_init`
|
||||||
|
- Initializes a Git repository
|
||||||
|
- Inputs:
|
||||||
|
- `repo_path` (string): Path to directory to initialize git repo
|
||||||
|
- Returns: Confirmation of repository initialization
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
|
|||||||
@@ -56,6 +56,9 @@ class GitShow(BaseModel):
|
|||||||
repo_path: str
|
repo_path: str
|
||||||
revision: str
|
revision: str
|
||||||
|
|
||||||
|
class GitInit(BaseModel):
|
||||||
|
repo_path: str
|
||||||
|
|
||||||
class GitTools(str, Enum):
|
class GitTools(str, Enum):
|
||||||
STATUS = "git_status"
|
STATUS = "git_status"
|
||||||
DIFF_UNSTAGED = "git_diff_unstaged"
|
DIFF_UNSTAGED = "git_diff_unstaged"
|
||||||
@@ -68,6 +71,7 @@ class GitTools(str, Enum):
|
|||||||
CREATE_BRANCH = "git_create_branch"
|
CREATE_BRANCH = "git_create_branch"
|
||||||
CHECKOUT = "git_checkout"
|
CHECKOUT = "git_checkout"
|
||||||
SHOW = "git_show"
|
SHOW = "git_show"
|
||||||
|
INIT = "git_init"
|
||||||
|
|
||||||
def git_status(repo: git.Repo) -> str:
|
def git_status(repo: git.Repo) -> str:
|
||||||
return repo.git.status()
|
return repo.git.status()
|
||||||
@@ -118,6 +122,13 @@ def git_checkout(repo: git.Repo, branch_name: str) -> str:
|
|||||||
repo.git.checkout(branch_name)
|
repo.git.checkout(branch_name)
|
||||||
return f"Switched to branch '{branch_name}'"
|
return f"Switched to branch '{branch_name}'"
|
||||||
|
|
||||||
|
def git_init(repo_path: str) -> str:
|
||||||
|
try:
|
||||||
|
repo = git.Repo.init(path=repo_path, mkdir=True)
|
||||||
|
return f"Initialized empty Git repository in {repo.git_dir}"
|
||||||
|
except Exception as e:
|
||||||
|
return f"Error initializing repository: {str(e)}"
|
||||||
|
|
||||||
def git_show(repo: git.Repo, revision: str) -> str:
|
def git_show(repo: git.Repo, revision: str) -> str:
|
||||||
commit = repo.commit(revision)
|
commit = repo.commit(revision)
|
||||||
output = [
|
output = [
|
||||||
@@ -206,6 +217,11 @@ async def serve(repository: Path | None) -> None:
|
|||||||
name=GitTools.SHOW,
|
name=GitTools.SHOW,
|
||||||
description="Shows the contents of a commit",
|
description="Shows the contents of a commit",
|
||||||
inputSchema=GitShow.schema(),
|
inputSchema=GitShow.schema(),
|
||||||
|
),
|
||||||
|
Tool(
|
||||||
|
name=GitTools.INIT,
|
||||||
|
description="Initialize a new Git repository",
|
||||||
|
inputSchema=GitInit.schema(),
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -241,6 +257,16 @@ async def serve(repository: Path | None) -> None:
|
|||||||
@server.call_tool()
|
@server.call_tool()
|
||||||
async def call_tool(name: str, arguments: dict) -> list[TextContent]:
|
async def call_tool(name: str, arguments: dict) -> list[TextContent]:
|
||||||
repo_path = Path(arguments["repo_path"])
|
repo_path = Path(arguments["repo_path"])
|
||||||
|
|
||||||
|
# Handle git init separately since it doesn't require an existing repo
|
||||||
|
if name == GitTools.INIT:
|
||||||
|
result = git_init(str(repo_path))
|
||||||
|
return [TextContent(
|
||||||
|
type="text",
|
||||||
|
text=result
|
||||||
|
)]
|
||||||
|
|
||||||
|
# For all other commands, we need an existing repo
|
||||||
repo = git.Repo(repo_path)
|
repo = git.Repo(repo_path)
|
||||||
|
|
||||||
match name:
|
match name:
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
|
import { getUserAgent } from "universal-user-agent";
|
||||||
import { createGitHubError } from "./errors.js";
|
import { createGitHubError } from "./errors.js";
|
||||||
|
import { VERSION } from "./version.js";
|
||||||
|
|
||||||
type RequestOptions = {
|
type RequestOptions = {
|
||||||
method?: string;
|
method?: string;
|
||||||
body?: unknown;
|
body?: unknown;
|
||||||
headers?: Record<string, string>;
|
headers?: Record<string, string>;
|
||||||
};
|
}
|
||||||
|
|
||||||
async function parseResponseBody(response: Response): Promise<unknown> {
|
async function parseResponseBody(response: Response): Promise<unknown> {
|
||||||
const contentType = response.headers.get("content-type");
|
const contentType = response.headers.get("content-type");
|
||||||
@@ -24,6 +26,8 @@ export function buildUrl(baseUrl: string, params: Record<string, string | number
|
|||||||
return url.toString();
|
return url.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const USER_AGENT = `modelcontextprotocol/servers/github/v${VERSION} ${getUserAgent()}`;
|
||||||
|
|
||||||
export async function githubRequest(
|
export async function githubRequest(
|
||||||
url: string,
|
url: string,
|
||||||
options: RequestOptions = {}
|
options: RequestOptions = {}
|
||||||
@@ -31,6 +35,7 @@ export async function githubRequest(
|
|||||||
const headers: Record<string, string> = {
|
const headers: Record<string, string> = {
|
||||||
"Accept": "application/vnd.github.v3+json",
|
"Accept": "application/vnd.github.v3+json",
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
|
"User-Agent": USER_AGENT,
|
||||||
...options.headers,
|
...options.headers,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
1
src/github/common/version.ts
Normal file
1
src/github/common/version.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export const VERSION = "0.6.2";
|
||||||
@@ -25,11 +25,12 @@ import {
|
|||||||
GitHubConflictError,
|
GitHubConflictError,
|
||||||
isGitHubError,
|
isGitHubError,
|
||||||
} from './common/errors.js';
|
} from './common/errors.js';
|
||||||
|
import { VERSION } from "./common/version.js";
|
||||||
|
|
||||||
const server = new Server(
|
const server = new Server(
|
||||||
{
|
{
|
||||||
name: "github-mcp-server",
|
name: "github-mcp-server",
|
||||||
version: "0.1.0",
|
version: VERSION,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
capabilities: {
|
capabilities: {
|
||||||
@@ -289,21 +290,27 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|||||||
const args = issues.ListIssuesOptionsSchema.parse(request.params.arguments);
|
const args = issues.ListIssuesOptionsSchema.parse(request.params.arguments);
|
||||||
const { owner, repo, ...options } = args;
|
const { owner, repo, ...options } = args;
|
||||||
const result = await issues.listIssues(owner, repo, options);
|
const result = await issues.listIssues(owner, repo, options);
|
||||||
return { toolResult: result };
|
return {
|
||||||
|
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
case "update_issue": {
|
case "update_issue": {
|
||||||
const args = issues.UpdateIssueOptionsSchema.parse(request.params.arguments);
|
const args = issues.UpdateIssueOptionsSchema.parse(request.params.arguments);
|
||||||
const { owner, repo, issue_number, ...options } = args;
|
const { owner, repo, issue_number, ...options } = args;
|
||||||
const result = await issues.updateIssue(owner, repo, issue_number, options);
|
const result = await issues.updateIssue(owner, repo, issue_number, options);
|
||||||
return { toolResult: result };
|
return {
|
||||||
|
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
case "add_issue_comment": {
|
case "add_issue_comment": {
|
||||||
const args = issues.IssueCommentSchema.parse(request.params.arguments);
|
const args = issues.IssueCommentSchema.parse(request.params.arguments);
|
||||||
const { owner, repo, issue_number, body } = args;
|
const { owner, repo, issue_number, body } = args;
|
||||||
const result = await issues.addIssueComment(owner, repo, issue_number, body);
|
const result = await issues.addIssueComment(owner, repo, issue_number, body);
|
||||||
return { toolResult: result };
|
return {
|
||||||
|
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
case "list_commits": {
|
case "list_commits": {
|
||||||
@@ -323,7 +330,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|||||||
case "get_issue": {
|
case "get_issue": {
|
||||||
const args = issues.GetIssueSchema.parse(request.params.arguments);
|
const args = issues.GetIssueSchema.parse(request.params.arguments);
|
||||||
const issue = await issues.getIssue(args.owner, args.repo, args.issue_number);
|
const issue = await issues.getIssue(args.owner, args.repo, args.issue_number);
|
||||||
return { toolResult: issue };
|
return {
|
||||||
|
content: [{ type: "text", text: JSON.stringify(issue, null, 2) }],
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -23,6 +23,7 @@
|
|||||||
"@types/node": "^22",
|
"@types/node": "^22",
|
||||||
"@types/node-fetch": "^2.6.12",
|
"@types/node-fetch": "^2.6.12",
|
||||||
"node-fetch": "^3.3.2",
|
"node-fetch": "^3.3.2",
|
||||||
|
"universal-user-agent": "^7.0.2",
|
||||||
"zod": "^3.22.4",
|
"zod": "^3.22.4",
|
||||||
"zod-to-json-schema": "^3.23.5"
|
"zod-to-json-schema": "^3.23.5"
|
||||||
},
|
},
|
||||||
@@ -30,4 +31,4 @@
|
|||||||
"shx": "^0.3.4",
|
"shx": "^0.3.4",
|
||||||
"typescript": "^5.6.2"
|
"typescript": "^5.6.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -117,6 +117,8 @@ Add the following to your `claude_desktop_config.json`:
|
|||||||
"command": "docker",
|
"command": "docker",
|
||||||
"args": [
|
"args": [
|
||||||
"run",
|
"run",
|
||||||
|
"--rm",
|
||||||
|
"-i",
|
||||||
"-e",
|
"-e",
|
||||||
"GITLAB_PERSONAL_ACCESS_TOKEN",
|
"GITLAB_PERSONAL_ACCESS_TOKEN",
|
||||||
"-e",
|
"-e",
|
||||||
@@ -167,4 +169,4 @@ docker build -t vonwig/gitlab:mcp -f src/gitlab/Dockerfile .
|
|||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
This MCP server is licensed under the MIT License. This means you are free to use, modify, and distribute the software, subject to the terms and conditions of the MIT License. For more details, please see the LICENSE file in the project repository.
|
This MCP server is licensed under the MIT License. This means you are free to use, modify, and distribute the software, subject to the terms and conditions of the MIT License. For more details, please see the LICENSE file in the project repository.
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ Add the following to your `claude_desktop_config.json`:
|
|||||||
Docker build:
|
Docker build:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker build -t vonwig/google-maps:mcp -f src/google-maps/Dockerfile .
|
docker build -t mcp/google-maps -f src/google-maps/Dockerfile .
|
||||||
```
|
```
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|||||||
@@ -137,7 +137,7 @@ Add this to your claude_desktop_config.json:
|
|||||||
"mcpServers": {
|
"mcpServers": {
|
||||||
"memory": {
|
"memory": {
|
||||||
"command": "docker",
|
"command": "docker",
|
||||||
"args": ["run", "-i", "--rm", "mcp/memory"]
|
"args": ["run", "-i", "-v", "claude-memory:/app/dist", "--rm", "mcp/memory"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -158,6 +158,29 @@ Add this to your claude_desktop_config.json:
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### NPX with custom setting
|
||||||
|
|
||||||
|
The server can be configured using the following environment variables:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"mcpServers": {
|
||||||
|
"memory": {
|
||||||
|
"command": "npx",
|
||||||
|
"args": [
|
||||||
|
"-y",
|
||||||
|
"@modelcontextprotocol/server-memory"
|
||||||
|
],
|
||||||
|
"env": {
|
||||||
|
"MEMORY_FILE_PATH": "/path/to/custom/memory.json"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `MEMORY_FILE_PATH`: Path to the memory storage JSON file (default: `memory.json` in the server directory)
|
||||||
|
|
||||||
### System Prompt
|
### System Prompt
|
||||||
|
|
||||||
The prompt for utilizing memory depends on the use case. Changing the prompt will help the model determine the frequency and types of memories created.
|
The prompt for utilizing memory depends on the use case. Changing the prompt will help the model determine the frequency and types of memories created.
|
||||||
@@ -200,4 +223,4 @@ docker build -t mcp/memory -f src/memory/Dockerfile .
|
|||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
This MCP server is licensed under the MIT License. This means you are free to use, modify, and distribute the software, subject to the terms and conditions of the MIT License. For more details, please see the LICENSE file in the project repository.
|
This MCP server is licensed under the MIT License. This means you are free to use, modify, and distribute the software, subject to the terms and conditions of the MIT License. For more details, please see the LICENSE file in the project repository.
|
||||||
|
|||||||
@@ -10,10 +10,15 @@ import { promises as fs } from 'fs';
|
|||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { fileURLToPath } from 'url';
|
import { fileURLToPath } from 'url';
|
||||||
|
|
||||||
|
// Define memory file path using environment variable with fallback
|
||||||
|
const defaultMemoryPath = path.join(path.dirname(fileURLToPath(import.meta.url)), 'memory.json');
|
||||||
|
|
||||||
// Define the path to the JSONL file, you can change this to your desired local path
|
// If MEMORY_FILE_PATH is just a filename, put it in the same directory as the script
|
||||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
const MEMORY_FILE_PATH = process.env.MEMORY_FILE_PATH
|
||||||
const MEMORY_FILE_PATH = path.join(__dirname, 'memory.json');
|
? path.isAbsolute(process.env.MEMORY_FILE_PATH)
|
||||||
|
? process.env.MEMORY_FILE_PATH
|
||||||
|
: path.join(path.dirname(fileURLToPath(import.meta.url)), process.env.MEMORY_FILE_PATH)
|
||||||
|
: defaultMemoryPath;
|
||||||
|
|
||||||
// We are storing our memory using entities, relations, and observations in a graph structure
|
// We are storing our memory using entities, relations, and observations in a graph structure
|
||||||
interface Entity {
|
interface Entity {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@modelcontextprotocol/server-memory",
|
"name": "@modelcontextprotocol/server-memory",
|
||||||
"version": "0.6.2",
|
"version": "0.6.3",
|
||||||
"description": "MCP server for enabling memory for Claude through a knowledge graph",
|
"description": "MCP server for enabling memory for Claude through a knowledge graph",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"author": "Anthropic, PBC (https://anthropic.com)",
|
"author": "Anthropic, PBC (https://anthropic.com)",
|
||||||
|
|||||||
23
src/redis/Dockerfile
Normal file
23
src/redis/Dockerfile
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
FROM node:22.12-alpine as builder
|
||||||
|
|
||||||
|
COPY src/redis /app
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
RUN --mount=type=cache,target=/root/.npm npm install
|
||||||
|
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
FROM node:22-alpine AS release
|
||||||
|
|
||||||
|
COPY --from=builder /app/build /app/build
|
||||||
|
COPY --from=builder /app/package.json /app/package.json
|
||||||
|
COPY --from=builder /app/package-lock.json /app/package-lock.json
|
||||||
|
|
||||||
|
ENV NODE_ENV=production
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
RUN npm ci --ignore-scripts --omit-dev
|
||||||
|
|
||||||
|
ENTRYPOINT ["node", "build/index.js"]
|
||||||
80
src/redis/README.md
Normal file
80
src/redis/README.md
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
# Redis
|
||||||
|
|
||||||
|
A Model Context Protocol server that provides access to Redis databases. This server enables LLMs to interact with Redis key-value stores through a set of standardized tools.
|
||||||
|
|
||||||
|
## Components
|
||||||
|
|
||||||
|
### Tools
|
||||||
|
|
||||||
|
- **set**
|
||||||
|
- Set a Redis key-value pair with optional expiration
|
||||||
|
- Input:
|
||||||
|
- `key` (string): Redis key
|
||||||
|
- `value` (string): Value to store
|
||||||
|
- `expireSeconds` (number, optional): Expiration time in seconds
|
||||||
|
|
||||||
|
- **get**
|
||||||
|
- Get value by key from Redis
|
||||||
|
- Input: `key` (string): Redis key to retrieve
|
||||||
|
|
||||||
|
- **delete**
|
||||||
|
- Delete one or more keys from Redis
|
||||||
|
- Input: `key` (string | string[]): Key or array of keys to delete
|
||||||
|
|
||||||
|
- **list**
|
||||||
|
- List Redis keys matching a pattern
|
||||||
|
- Input: `pattern` (string, optional): Pattern to match keys (default: *)
|
||||||
|
|
||||||
|
## Usage with Claude Desktop
|
||||||
|
|
||||||
|
To use this server with the Claude Desktop app, add the following configuration to the "mcpServers" section of your `claude_desktop_config.json`:
|
||||||
|
|
||||||
|
### Docker
|
||||||
|
|
||||||
|
* when running docker on macos, use host.docker.internal if the server is running on the host network (eg localhost)
|
||||||
|
* Redis URL can be specified as an argument, defaults to "redis://localhost:6379"
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"mcpServers": {
|
||||||
|
"redis": {
|
||||||
|
"command": "docker",
|
||||||
|
"args": [
|
||||||
|
"run",
|
||||||
|
"-i",
|
||||||
|
"--rm",
|
||||||
|
"mcp/redis",
|
||||||
|
"redis://host.docker.internal:6379"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### NPX
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"mcpServers": {
|
||||||
|
"redis": {
|
||||||
|
"command": "npx",
|
||||||
|
"args": [
|
||||||
|
"-y",
|
||||||
|
"@modelcontextprotocol/server-redis",
|
||||||
|
"redis://localhost:6379"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Building
|
||||||
|
|
||||||
|
Docker:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
docker build -t mcp/redis -f src/redis/Dockerfile .
|
||||||
|
```
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
This MCP server is licensed under the MIT License. This means you are free to use, modify, and distribute the software, subject to the terms and conditions of the MIT License. For more details, please see the LICENSE file in the project repository.
|
||||||
28
src/redis/package.json
Normal file
28
src/redis/package.json
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
"name": "redis",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"main": "index.js",
|
||||||
|
"type": "module",
|
||||||
|
"bin": {
|
||||||
|
"redis": "./build/index.js"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsc && node -e \"require('fs').chmodSync('build/index.js', '755')\""
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"build"
|
||||||
|
],
|
||||||
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"description": "",
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^22.10.2",
|
||||||
|
"typescript": "^5.7.2"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@modelcontextprotocol/sdk": "^0.4.0",
|
||||||
|
"@types/redis": "^4.0.10",
|
||||||
|
"redis": "^4.7.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
236
src/redis/src/index.ts
Normal file
236
src/redis/src/index.ts
Normal file
@@ -0,0 +1,236 @@
|
|||||||
|
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 { z } from "zod";
|
||||||
|
import { createClient } from 'redis';
|
||||||
|
|
||||||
|
// Get Redis URL from command line args or use default
|
||||||
|
const REDIS_URL = process.argv[2] || "redis://localhost:6379";
|
||||||
|
const redisClient = createClient({
|
||||||
|
url: REDIS_URL
|
||||||
|
});
|
||||||
|
|
||||||
|
// Define Zod schemas for validation
|
||||||
|
const SetArgumentsSchema = z.object({
|
||||||
|
key: z.string(),
|
||||||
|
value: z.string(),
|
||||||
|
expireSeconds: z.number().optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const GetArgumentsSchema = z.object({
|
||||||
|
key: z.string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const DeleteArgumentsSchema = z.object({
|
||||||
|
key: z.string().or(z.array(z.string())),
|
||||||
|
});
|
||||||
|
|
||||||
|
const ListArgumentsSchema = z.object({
|
||||||
|
pattern: z.string().default("*"),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create server instance
|
||||||
|
const server = new Server(
|
||||||
|
{
|
||||||
|
name: "redis",
|
||||||
|
version: "1.0.0"
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// List available tools
|
||||||
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
||||||
|
return {
|
||||||
|
tools: [
|
||||||
|
{
|
||||||
|
name: "set",
|
||||||
|
description: "Set a Redis key-value pair with optional expiration",
|
||||||
|
inputSchema: {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
key: {
|
||||||
|
type: "string",
|
||||||
|
description: "Redis key",
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
type: "string",
|
||||||
|
description: "Value to store",
|
||||||
|
},
|
||||||
|
expireSeconds: {
|
||||||
|
type: "number",
|
||||||
|
description: "Optional expiration time in seconds",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
required: ["key", "value"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "get",
|
||||||
|
description: "Get value by key from Redis",
|
||||||
|
inputSchema: {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
key: {
|
||||||
|
type: "string",
|
||||||
|
description: "Redis key to retrieve",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
required: ["key"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "delete",
|
||||||
|
description: "Delete one or more keys from Redis",
|
||||||
|
inputSchema: {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
key: {
|
||||||
|
oneOf: [
|
||||||
|
{ type: "string" },
|
||||||
|
{ type: "array", items: { type: "string" } }
|
||||||
|
],
|
||||||
|
description: "Key or array of keys to delete",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
required: ["key"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "list",
|
||||||
|
description: "List Redis keys matching a pattern",
|
||||||
|
inputSchema: {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
pattern: {
|
||||||
|
type: "string",
|
||||||
|
description: "Pattern to match keys (default: *)",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle tool execution
|
||||||
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
||||||
|
const { name, arguments: args } = request.params;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (name === "set") {
|
||||||
|
const { key, value, expireSeconds } = SetArgumentsSchema.parse(args);
|
||||||
|
|
||||||
|
if (expireSeconds) {
|
||||||
|
await redisClient.setEx(key, expireSeconds, value);
|
||||||
|
} else {
|
||||||
|
await redisClient.set(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: "text",
|
||||||
|
text: `Successfully set key: ${key}`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
} else if (name === "get") {
|
||||||
|
const { key } = GetArgumentsSchema.parse(args);
|
||||||
|
const value = await redisClient.get(key);
|
||||||
|
|
||||||
|
if (value === null) {
|
||||||
|
return {
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: "text",
|
||||||
|
text: `Key not found: ${key}`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: "text",
|
||||||
|
text: `${value}`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
} else if (name === "delete") {
|
||||||
|
const { key } = DeleteArgumentsSchema.parse(args);
|
||||||
|
|
||||||
|
if (Array.isArray(key)) {
|
||||||
|
await redisClient.del(key);
|
||||||
|
return {
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: "text",
|
||||||
|
text: `Successfully deleted ${key.length} keys`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
await redisClient.del(key);
|
||||||
|
return {
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: "text",
|
||||||
|
text: `Successfully deleted key: ${key}`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else if (name === "list") {
|
||||||
|
const { pattern } = ListArgumentsSchema.parse(args);
|
||||||
|
const keys = await redisClient.keys(pattern);
|
||||||
|
|
||||||
|
return {
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: "text",
|
||||||
|
text: keys.length > 0
|
||||||
|
? `Found keys:\n${keys.join('\n')}`
|
||||||
|
: "No keys found matching pattern",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
throw new Error(`Unknown tool: ${name}`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof z.ZodError) {
|
||||||
|
throw new Error(
|
||||||
|
`Invalid arguments: ${error.errors
|
||||||
|
.map((e) => `${e.path.join(".")}: ${e.message}`)
|
||||||
|
.join(", ")}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Start the server
|
||||||
|
async function main() {
|
||||||
|
try {
|
||||||
|
// Connect to Redis
|
||||||
|
redisClient.on('error', (err: Error) => console.error('Redis Client Error', err));
|
||||||
|
await redisClient.connect();
|
||||||
|
console.error(`Connected to Redis successfully at ${REDIS_URL}`);
|
||||||
|
|
||||||
|
const transport = new StdioServerTransport();
|
||||||
|
await server.connect(transport);
|
||||||
|
console.error("Redis MCP Server running on stdio");
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error during startup:", error);
|
||||||
|
await redisClient.quit();
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch((error) => {
|
||||||
|
console.error("Fatal error in main():", error);
|
||||||
|
redisClient.quit().finally(() => process.exit(1));
|
||||||
|
});
|
||||||
16
src/redis/tsconfig.json
Normal file
16
src/redis/tsconfig.json
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2022",
|
||||||
|
"module": "Node16",
|
||||||
|
"moduleResolution": "Node16",
|
||||||
|
"outDir": "./build",
|
||||||
|
"rootDir": "./src",
|
||||||
|
"strict": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"forceConsistentCasingInFileNames": true
|
||||||
|
},
|
||||||
|
"include": ["src/**/*"],
|
||||||
|
"exclude": ["node_modules"]
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import os
|
||||||
|
import sys
|
||||||
import sqlite3
|
import sqlite3
|
||||||
import logging
|
import logging
|
||||||
from contextlib import closing
|
from contextlib import closing
|
||||||
@@ -9,6 +11,12 @@ import mcp.server.stdio
|
|||||||
from pydantic import AnyUrl
|
from pydantic import AnyUrl
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
|
# reconfigure UnicodeEncodeError prone default (i.e. windows-1252) to utf-8
|
||||||
|
if sys.platform == "win32" and os.environ.get('PYTHONIOENCODING') is None:
|
||||||
|
sys.stdin.reconfigure(encoding="utf-8")
|
||||||
|
sys.stdout.reconfigure(encoding="utf-8")
|
||||||
|
sys.stderr.reconfigure(encoding="utf-8")
|
||||||
|
|
||||||
logger = logging.getLogger('mcp_sqlite_server')
|
logger = logging.getLogger('mcp_sqlite_server')
|
||||||
logger.info("Starting MCP SQLite Server")
|
logger.info("Starting MCP SQLite Server")
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user