UNPKG

@modelcontextprotocol/server-redis

Version:

MCP server for using Redis

266 lines (265 loc) • 9.02 kB
#!/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 { z } from "zod"; import { createClient } from 'redis'; // Configuration const REDIS_URL = process.argv[2] || "redis://localhost:6379"; const MAX_RETRIES = 5; const MIN_RETRY_DELAY = 1000; // 1 second const MAX_RETRY_DELAY = 30000; // 30 seconds // Create Redis client with retry strategy const redisClient = createClient({ url: REDIS_URL, socket: { reconnectStrategy: (retries) => { if (retries >= MAX_RETRIES) { console.error(`[Redis Error] Maximum retries (${MAX_RETRIES}) reached. Giving up.`); console.error(`[Redis Error] Connection: ${REDIS_URL}`); return new Error('Max retries reached'); } const delay = Math.min(Math.pow(2, retries) * MIN_RETRY_DELAY, MAX_RETRY_DELAY); console.error(`[Redis Retry] Attempt ${retries + 1}/${MAX_RETRIES} failed`); console.error(`[Redis Retry] Next attempt in ${delay}ms`); console.error(`[Redis Retry] Connection: ${REDIS_URL}`); return delay; } } }); // 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: "0.0.1" }, { capabilities: { tools: {} } }); // 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; } }); // Set up Redis event handlers redisClient.on('error', (err) => { console.error(`[Redis Error] ${err.name}: ${err.message}`); console.error(`[Redis Error] Connection: ${REDIS_URL}`); console.error(`[Redis Error] Stack: ${err.stack}`); }); redisClient.on('connect', () => { console.error(`[Redis Connected] Successfully connected to ${REDIS_URL}`); }); redisClient.on('reconnecting', () => { console.error('[Redis Reconnecting] Connection lost, attempting to reconnect...'); }); redisClient.on('end', () => { console.error('[Redis Disconnected] Connection closed'); }); async function runServer() { try { // Connect to Redis await redisClient.connect(); // Set up MCP server const transport = new StdioServerTransport(); await server.connect(transport); console.error("Redis MCP Server running on stdio"); } catch (error) { const err = error; console.error("[Redis Fatal] Server initialization failed"); console.error(`[Redis Fatal] Error: ${err.name}: ${err.message}`); console.error(`[Redis Fatal] Connection: ${REDIS_URL}`); console.error(`[Redis Fatal] Stack: ${err.stack}`); await redisClient.quit().catch(() => { }); process.exit(1); } } // Handle process termination process.on('SIGINT', async () => { await redisClient.quit().catch(() => { }); process.exit(0); }); process.on('SIGTERM', async () => { await redisClient.quit().catch(() => { }); process.exit(0); }); runServer();