@kazuph/mcp-obsidian

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)); });