UNPKG

safevibe

Version:

Safevibe CLI - Simple personal secret vault for AI developers and amateur vibe coders

228 lines (227 loc) • 7.07 kB
#!/usr/bin/env node import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { z } from "zod"; import { SafevibeClient } from "./client.js"; /** * Safevibe MCP Server * * Provides MCP tools for secure secret management: * - save_key: Store encrypted secrets * - get_key: Retrieve encrypted secrets * - list_keys: List available secrets * - rotate_key: Rotate secret versions * * Simplified to work without projects - everything is user-scoped */ // Initialize the MCP server const server = new McpServer({ name: "safevibe-mcp-server", version: "0.1.0", }); // Initialize Safevibe client const safevibeClient = new SafevibeClient(); /** * Tool: save_key * Saves an encrypted secret to Safevibe */ server.registerTool("save_key", { title: "Save Secret Key", description: "Store an encrypted secret in Safevibe vault", inputSchema: { name: z.string().describe("Name/identifier for the secret"), value: z.string().describe("The secret value to encrypt and store"), }, }, async ({ name, value }) => { try { // Encrypt the secret locally before sending const ciphertext = await safevibeClient.encryptSecret(value); // Save to backend via tRPC const result = await safevibeClient.trpc.secret.save.mutate({ name, ciphertext, }); return { content: [ { type: "text", text: `āœ… Secret "${name}" saved successfully (version ${result.version})`, }, ], }; } catch (error) { return { content: [ { type: "text", text: `āŒ Failed to save secret: ${error instanceof Error ? error.message : 'Unknown error'}`, }, ], isError: true, }; } }); /** * Tool: get_key * Retrieves and decrypts a secret from Safevibe */ server.registerTool("get_key", { title: "Get Secret Key", description: "Retrieve and decrypt a secret from Safevibe vault", inputSchema: { name: z.string().describe("Name/identifier of the secret to retrieve"), }, }, async ({ name }) => { try { // Retrieve encrypted secret from backend const result = await safevibeClient.trpc.secret.get.query({ name, }); // Decrypt the secret locally const decryptedValue = await safevibeClient.decryptSecret(result.ciphertext); return { content: [ { type: "text", text: `šŸ”“ Retrieved secret "${name}" (version ${result.version}): ${decryptedValue}`, }, ], }; } catch (error) { return { content: [ { type: "text", text: `āŒ Failed to retrieve secret: ${error instanceof Error ? error.message : 'Unknown error'}`, }, ], isError: true, }; } }); /** * Tool: list_keys * Lists all secrets for the user */ server.registerTool("list_keys", { title: "List Secret Keys", description: "List all secrets stored in Safevibe for the current user", }, async (params = {}) => { try { const secrets = await safevibeClient.trpc.secret.list.query(); if (secrets.length === 0) { return { content: [ { type: "text", text: "šŸ“­ No secrets found for your account", }, ], }; } const secretsList = secrets .map((secret, index) => { const updatedAt = typeof secret.updatedAt === 'string' ? secret.updatedAt : secret.updatedAt.toISOString(); return `${index + 1}. ${secret.name} (updated: ${updatedAt})`; }) .join("\n"); return { content: [ { type: "text", text: `šŸ“‹ Found ${secrets.length} secret(s):\n${secretsList}`, }, ], }; } catch (error) { return { content: [ { type: "text", text: `āŒ Failed to list secrets: ${error instanceof Error ? error.message : 'Unknown error'}`, }, ], isError: true, }; } }); /** * Tool: rotate_key * Creates a new version of an existing secret (key rotation) */ server.registerTool("rotate_key", { title: "Rotate Secret Key", description: "Create a new version of an existing secret (key rotation)", inputSchema: { name: z.string().describe("Name/identifier of the secret to rotate"), newValue: z.string().describe("New secret value to replace the old one"), }, }, async ({ name, newValue }) => { try { // Encrypt the new secret value locally const newCiphertext = await safevibeClient.encryptSecret(newValue); // Rotate the secret via tRPC const result = await safevibeClient.trpc.secret.rotate.mutate({ name, newCiphertext, }); return { content: [ { type: "text", text: `šŸ”„ Secret "${name}" rotated successfully to version ${result.version}`, }, ], }; } catch (error) { return { content: [ { type: "text", text: `āŒ Failed to rotate secret: ${error instanceof Error ? error.message : 'Unknown error'}`, }, ], isError: true, }; } }); /** * Initialize and start the MCP server */ async function main() { try { // Initialize Safevibe client (authentication, key setup, etc.) await safevibeClient.initialize(); // Start the MCP server with stdio transport const transport = new StdioServerTransport(); await server.connect(transport); console.error("šŸ” Safevibe MCP Server started successfully"); console.error("Available tools: save_key, get_key, list_keys, rotate_key"); } catch (error) { console.error("āŒ Failed to start Safevibe MCP Server:", error); process.exit(1); } } // Handle graceful shutdown process.on("SIGINT", async () => { console.error("\nšŸ‘‹ Shutting down Safevibe MCP Server..."); await safevibeClient.cleanup(); process.exit(0); }); process.on("SIGTERM", async () => { console.error("\nšŸ‘‹ Shutting down Safevibe MCP Server..."); await safevibeClient.cleanup(); process.exit(0); }); // Start the server main().catch((error) => { console.error("āŒ Fatal error:", error); process.exit(1); });