server.ts•5.91 kB
import express from 'express';
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
import { z } from "zod";
import keychainService from "./services/keychain.js";
// Create an MCP server
const server = new McpServer({
  name: "ServeMyAPI",
  version: "1.0.0"
});
// Tool to store an API key
server.tool(
  "store-api-key",
  {
    name: z.string().min(1).describe("The name/identifier for the API key"),
    key: z.string().min(1).describe("The API key to store"),
  },
  async ({ name, key }) => {
    try {
      await keychainService.storeKey(name, key);
      return {
        content: [{ 
          type: "text", 
          text: `Successfully stored API key with name: ${name}` 
        }]
      };
    } catch (error) {
      return {
        content: [{ 
          type: "text", 
          text: `Error storing API key: ${(error as Error).message}` 
        }],
        isError: true
      };
    }
  }
);
// Tool to retrieve an API key
server.tool(
  "get-api-key",
  {
    name: z.string().min(1).describe("The name/identifier of the API key to retrieve"),
  },
  async ({ name }) => {
    try {
      const key = await keychainService.getKey(name);
      
      if (!key) {
        return {
          content: [{ 
            type: "text", 
            text: `No API key found with name: ${name}` 
          }],
          isError: true
        };
      }
      
      return {
        content: [{ 
          type: "text", 
          text: key
        }]
      };
    } catch (error) {
      return {
        content: [{ 
          type: "text", 
          text: `Error retrieving API key: ${(error as Error).message}` 
        }],
        isError: true
      };
    }
  }
);
// Tool to delete an API key
server.tool(
  "delete-api-key",
  {
    name: z.string().min(1).describe("The name/identifier of the API key to delete"),
  },
  async ({ name }) => {
    try {
      const success = await keychainService.deleteKey(name);
      
      if (!success) {
        return {
          content: [{ 
            type: "text", 
            text: `No API key found with name: ${name}` 
          }],
          isError: true
        };
      }
      
      return {
        content: [{ 
          type: "text", 
          text: `Successfully deleted API key with name: ${name}` 
        }]
      };
    } catch (error) {
      return {
        content: [{ 
          type: "text", 
          text: `Error deleting API key: ${(error as Error).message}` 
        }],
        isError: true
      };
    }
  }
);
// Tool to list all stored API keys
server.tool(
  "list-api-keys",
  {},
  async () => {
    try {
      const keys = await keychainService.listKeys();
      
      if (keys.length === 0) {
        return {
          content: [{ 
            type: "text", 
            text: "No API keys found" 
          }]
        };
      }
      
      return {
        content: [{ 
          type: "text", 
          text: `Available API keys:\n${keys.join("\n")}` 
        }]
      };
    } catch (error) {
      return {
        content: [{ 
          type: "text", 
          text: `Error listing API keys: ${(error as Error).message}` 
        }],
        isError: true
      };
    }
  }
);
// Set up Express app for HTTP transport
const app = express();
const port = process.env.PORT || 3000;
// Store active transports
const activeTransports = new Map<string, any>();
app.get("/sse", async (req, res) => {
  const id = Date.now().toString();
  const transport = new SSEServerTransport("/messages", res);
  
  activeTransports.set(id, transport);
  
  // Set headers for SSE
  res.setHeader('Content-Type', 'text/event-stream');
  res.setHeader('Cache-Control', 'no-cache');
  res.setHeader('Connection', 'keep-alive');
  
  // Handle client disconnect
  req.on('close', () => {
    activeTransports.delete(id);
  });
  
  await server.connect(transport);
});
app.post("/messages", express.json(), (req, res) => {
  // Get the last transport - in a production app, you'd want to maintain sessions
  const lastTransportId = Array.from(activeTransports.keys()).pop();
  
  if (!lastTransportId) {
    res.status(400).json({ error: "No active connections" });
    return;
  }
  
  const transport = activeTransports.get(lastTransportId);
  transport.handlePostMessage(req, res).catch((error: Error) => {
    console.error("Error handling message:", error);
    if (!res.headersSent) {
      res.status(500).json({ error: "Internal server error" });
    }
  });
});
// Simple home page
app.get("/", (req, res) => {
  res.send(`
    <html>
      <head>
        <title>ServeMyAPI</title>
        <style>
          body { font-family: -apple-system, BlinkMacSystemFont, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; }
          h1 { color: #333; }
          p { line-height: 1.6; }
          pre { background: #f5f5f5; padding: 10px; border-radius: 5px; }
        </style>
      </head>
      <body>
        <h1>ServeMyAPI</h1>
        <p>This is a personal MCP server for securely storing and accessing API keys across projects using the macOS Keychain.</p>
        <p>The server exposes the following tools:</p>
        <ul>
          <li><strong>store-api-key</strong> - Store an API key in the keychain</li>
          <li><strong>get-api-key</strong> - Retrieve an API key from the keychain</li>
          <li><strong>delete-api-key</strong> - Delete an API key from the keychain</li>
          <li><strong>list-api-keys</strong> - List all stored API keys</li>
        </ul>
        <p>This server is running with HTTP SSE transport. Connect to /sse for the SSE endpoint and post messages to /messages.</p>
      </body>
    </html>
  `);
});
// Start the server
app.listen(port, () => {
  console.log(`ServeMyAPI HTTP server is running on port ${port}`);
});
export { server };