safevibe
Version:
Safevibe CLI - Simple personal secret vault for AI developers and amateur vibe coders
228 lines (227 loc) ⢠7.07 kB
JavaScript
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);
});