├── icon.png ├── How to improve serveMyAPI.pdf ├── examples ├── windsurf_config.json └── claude_desktop_config.json ├── tsconfig.json ├── .gitignore ├── Dockerfile ├── package.json ├── icon.svg ├── CONTRIBUTING.md ├── smithery.yaml ├── src ├── cli.ts ├── index.ts ├── cli.js ├── services │ └── keychain.ts └── server.ts ├── README.md ├── MCP-TypeScript-Readme.md └── CLAUDE.md /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jktfe/serveMyAPI/HEAD/icon.png -------------------------------------------------------------------------------- /How to improve serveMyAPI.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jktfe/serveMyAPI/HEAD/How to improve serveMyAPI.pdf -------------------------------------------------------------------------------- /examples/windsurf_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "mcpServers": { 3 | "serveMyAPI": { 4 | "command": "node", 5 | "args": [ 6 | "/ABSOLUTE/PATH/TO/servemyapi/dist/index.js" 7 | ] 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /examples/claude_desktop_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "mcpServers": { 3 | "serveMyAPI": { 4 | "command": "node", 5 | "args": [ 6 | "/ABSOLUTE/PATH/TO/servemyapi/dist/index.js" 7 | ] 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2022", 4 | "module": "NodeNext", 5 | "moduleResolution": "NodeNext", 6 | "esModuleInterop": true, 7 | "strict": true, 8 | "outDir": "dist", 9 | "rootDir": "src", 10 | "sourceMap": true, 11 | "declaration": true, 12 | "declarationMap": true 13 | }, 14 | "include": ["src/**/*"], 15 | "exclude": ["node_modules", "dist"] 16 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | node_modules/ 3 | package-lock.json 4 | yarn.lock 5 | 6 | # Build output 7 | dist/ 8 | build/ 9 | 10 | # Logs 11 | logs/ 12 | *.log 13 | npm-debug.log* 14 | yarn-debug.log* 15 | yarn-error.log* 16 | 17 | # Environment variables 18 | .env 19 | .env.local 20 | .env.development.local 21 | .env.test.local 22 | .env.production.local 23 | 24 | # Editor directories and files 25 | .idea/ 26 | .vscode/ 27 | *.swp 28 | *.swo 29 | 30 | # OS generated files 31 | .DS_Store 32 | .DS_Store? 33 | ._* 34 | .Spotlight-V100 35 | .Trashes 36 | ehthumbs.db 37 | Thumbs.db 38 | 39 | CLAUDE.md 40 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:20-slim 2 | 3 | # Set environment variable to indicate Docker environment 4 | ENV DOCKER_ENV=true 5 | ENV STORAGE_DIR=/app/data 6 | 7 | WORKDIR /app 8 | 9 | # Copy package files and install dependencies 10 | COPY package*.json ./ 11 | RUN npm install 12 | 13 | # Copy source code 14 | COPY . . 15 | 16 | # Build the TypeScript code 17 | RUN npm run build 18 | 19 | # Create data directory for file-based storage 20 | RUN mkdir -p /app/data && chmod 777 /app/data 21 | 22 | # Expose port (if using HTTP server version) 23 | EXPOSE 3000 24 | 25 | # Health check 26 | HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ 27 | CMD curl -f http://localhost:3000/ || exit 1 28 | 29 | # Set the entry command 30 | CMD ["node", "dist/index.js"] 31 | 32 | # Add a prominent note about this MCP server's intended use 33 | LABEL org.opencontainers.image.description="ServeMyAPI MCP server - Securely store and access API keys. For optimal security, run natively on macOS to use Keychain. Container mode uses file-based storage as a fallback." 34 | LABEL org.opencontainers.image.authors="James King" 35 | LABEL org.opencontainers.image.url="https://github.com/Jktfe/serveMyAPI" -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "servemyapi", 3 | "version": "1.0.0", 4 | "description": "Personal MCP server for securely storing and accessing API keys across projects using the macOS Keychain", 5 | "type": "module", 6 | "main": "dist/index.js", 7 | "bin": { 8 | "api-key": "dist/cli.js" 9 | }, 10 | "scripts": { 11 | "build": "tsc", 12 | "start": "node dist/index.js", 13 | "dev": "nodemon --watch 'src/**/*.ts' --exec 'ts-node' src/index.ts", 14 | "lint": "eslint 'src/**/*.ts'", 15 | "test": "echo \"Error: no test specified\" && exit 1", 16 | "cli": "node dist/cli.js" 17 | }, 18 | "keywords": ["mcp", "api", "keychain", "macos"], 19 | "author": "James King", 20 | "license": "ISC", 21 | "dependencies": { 22 | "@modelcontextprotocol/sdk": "^1.7.0", 23 | "@types/express": "^5.0.0", 24 | "@types/node": "^22.13.10", 25 | "express": "^5.0.1", 26 | "keytar": "^7.9.0", 27 | "ts-node": "^10.9.2", 28 | "typescript": "^5.8.2", 29 | "zod": "^3.24.2" 30 | }, 31 | "devDependencies": { 32 | "@typescript-eslint/eslint-plugin": "^8.26.1", 33 | "@typescript-eslint/parser": "^8.26.1", 34 | "eslint": "^9.22.0", 35 | "nodemon": "^3.1.9" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to ServeMyAPI 2 | 3 | Thank you for considering contributing to ServeMyAPI! This document provides guidelines and instructions for contributing to this project. 4 | 5 | ## Code of Conduct 6 | 7 | Please be respectful and considerate of others when contributing to this project. We aim to foster an inclusive and welcoming community. 8 | 9 | ## Getting Started 10 | 11 | 1. Fork the repository 12 | 2. Clone your fork: `git clone https://github.com/yourusername/servemyapi.git` 13 | 3. Install dependencies: `npm install` 14 | 4. Create a new branch for your changes: `git checkout -b feature/your-feature-name` 15 | 16 | ## Development Workflow 17 | 18 | 1. Make your changes 19 | 2. Run `npm run lint` to ensure code style consistency 20 | 3. Test your changes: 21 | - For stdio mode: `npm run dev` 22 | - For HTTP mode: `npm run build && node dist/server.js` 23 | 4. Commit your changes with a descriptive commit message 24 | 5. Push to your branch: `git push origin feature/your-feature-name` 25 | 6. Open a pull request against the main repository 26 | 27 | ## Pull Request Process 28 | 29 | 1. Ensure your code passes linting 30 | 2. Update the README.md with details of your changes if appropriate 31 | 3. Include a clear description of what your changes do and why they should be included 32 | 4. Your PR will be reviewed by the maintainers, who may request changes 33 | 34 | ## Adding New Features 35 | 36 | When adding new features: 37 | 1. Consider backward compatibility 38 | 2. Add appropriate documentation 39 | 3. Follow the existing code style 40 | 41 | ## Future Development Ideas 42 | 43 | Some potential areas for contribution: 44 | - Adding support for other secure credential storage systems on different platforms 45 | - Enhancing the web UI for managing keys directly 46 | - Adding authentication for the HTTP server 47 | - Creating client libraries for different programming languages 48 | 49 | ## License 50 | 51 | By contributing to ServeMyAPI, you agree that your contributions will be licensed under the project's MIT license. -------------------------------------------------------------------------------- /smithery.yaml: -------------------------------------------------------------------------------- 1 | # Smithery configuration for ServeMyAPI 2 | # Note: This MCP server is macOS-only due to its dependency on macOS Keychain 3 | 4 | startCommand: 5 | type: stdio 6 | configSchema: 7 | type: object 8 | properties: {} 9 | additionalProperties: false 10 | commandFunction: | 11 | function(config) { 12 | // This is a macOS-only service that uses the macOS Keychain 13 | // The container will start but will not function correctly on non-macOS systems 14 | return { 15 | command: "node", 16 | args: ["dist/index.js"], 17 | env: { 18 | "NODE_ENV": "production" 19 | } 20 | }; 21 | } 22 | 23 | tools: 24 | store-api-key: 25 | name: "store-api-key" 26 | description: "Store an API key securely in the keychain" 27 | parameters: 28 | $schema: "http://json-schema.org/draft-07/schema#" 29 | type: object 30 | additionalProperties: false 31 | properties: 32 | name: 33 | type: string 34 | minLength: 1 35 | description: "The name/identifier for the API key" 36 | key: 37 | type: string 38 | minLength: 1 39 | description: "The API key to store" 40 | required: ["name", "key"] 41 | get-api-key: 42 | name: "get-api-key" 43 | description: "Retrieve an API key from the keychain" 44 | parameters: 45 | $schema: "http://json-schema.org/draft-07/schema#" 46 | type: object 47 | additionalProperties: false 48 | properties: 49 | name: 50 | type: string 51 | minLength: 1 52 | description: "The name/identifier of the API key to retrieve" 53 | required: ["name"] 54 | delete-api-key: 55 | name: "delete-api-key" 56 | description: "Delete an API key from the keychain" 57 | parameters: 58 | $schema: "http://json-schema.org/draft-07/schema#" 59 | type: object 60 | additionalProperties: false 61 | properties: 62 | name: 63 | type: string 64 | minLength: 1 65 | description: "The name/identifier of the API key to delete" 66 | required: ["name"] 67 | list-api-keys: 68 | name: "list-api-keys" 69 | description: "List all stored API keys" 70 | parameters: 71 | $schema: "http://json-schema.org/draft-07/schema#" 72 | type: object 73 | additionalProperties: false 74 | properties: {} 75 | 76 | build: 77 | dockerfile: Dockerfile 78 | dockerBuildPath: "." 79 | 80 | # This comment explains that the service is macOS-only 81 | # While the Dockerfile and smithery.yaml enable deployment compatibility, 82 | # the service depends on macOS Keychain and will not function on other platforms -------------------------------------------------------------------------------- /src/cli.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import keychain from './services/keychain.js'; 4 | 5 | // Get the command line arguments 6 | const args = process.argv.slice(2); 7 | const command = args[0]?.toLowerCase(); 8 | 9 | async function main() { 10 | try { 11 | switch (command) { 12 | case 'list': 13 | // List all API keys 14 | const keys = await keychain.listKeys(); 15 | if (keys.length === 0) { 16 | console.log('No API keys found.'); 17 | } else { 18 | console.log('Stored API keys:'); 19 | keys.forEach(key => console.log(` - ${key}`)); 20 | } 21 | break; 22 | 23 | case 'get': 24 | // Get an API key by name 25 | const keyName = args[1]; 26 | if (!keyName) { 27 | console.error('Error: Please provide a key name.'); 28 | printUsage(); 29 | process.exit(1); 30 | } 31 | 32 | const value = await keychain.getKey(keyName); 33 | if (value) { 34 | console.log(`${keyName}: ${value}`); 35 | } else { 36 | console.error(`Error: Key '${keyName}' not found.`); 37 | process.exit(1); 38 | } 39 | break; 40 | 41 | case 'store': 42 | case 'add': 43 | // Store an API key 44 | const storeName = args[1]; 45 | const storeValue = args[2]; 46 | 47 | if (!storeName || !storeValue) { 48 | console.error('Error: Please provide both a key name and value.'); 49 | printUsage(); 50 | process.exit(1); 51 | } 52 | 53 | await keychain.storeKey(storeName, storeValue); 54 | console.log(`Key '${storeName}' stored successfully.`); 55 | break; 56 | 57 | case 'delete': 58 | case 'remove': 59 | // Delete an API key 60 | const deleteName = args[1]; 61 | 62 | if (!deleteName) { 63 | console.error('Error: Please provide a key name to delete.'); 64 | printUsage(); 65 | process.exit(1); 66 | } 67 | 68 | const deleted = await keychain.deleteKey(deleteName); 69 | if (deleted) { 70 | console.log(`Key '${deleteName}' deleted successfully.`); 71 | } else { 72 | console.error(`Error: Failed to delete key '${deleteName}'. Key may not exist.`); 73 | process.exit(1); 74 | } 75 | break; 76 | 77 | case '--help': 78 | case '-h': 79 | case 'help': 80 | printUsage(); 81 | break; 82 | 83 | default: 84 | console.error(`Error: Unknown command '${command}'.`); 85 | printUsage(); 86 | process.exit(1); 87 | } 88 | } catch (error) { 89 | console.error('Error:', error instanceof Error ? error.message : String(error)); 90 | process.exit(1); 91 | } 92 | } 93 | 94 | function printUsage() { 95 | console.log(` 96 | Usage: api-key [options] 97 | 98 | Commands: 99 | list List all stored API keys 100 | get Retrieve the value of an API key 101 | store Store a new API key 102 | add Alias for 'store' 103 | delete Delete an API key 104 | remove Alias for 'delete' 105 | help Show this help message 106 | 107 | Examples: 108 | api-key list 109 | api-key get github_token 110 | api-key store github_token ghp_123456789abcdefg 111 | api-key delete github_token 112 | `); 113 | } 114 | 115 | main().catch(err => { 116 | console.error('Error:', err instanceof Error ? err.message : String(err)); 117 | process.exit(1); 118 | }); -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; 2 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; 3 | import { z } from "zod"; 4 | import keychainService from "./services/keychain.js"; 5 | import keytar from 'keytar'; 6 | 7 | // Create an MCP server 8 | const server = new McpServer({ 9 | name: "ServeMyAPI", 10 | version: "1.0.0" 11 | }); 12 | 13 | // Tool to store an API key 14 | server.tool( 15 | "store-api-key", 16 | { 17 | name: z.string().min(1).describe("The name/identifier for the API key"), 18 | key: z.string().min(1).describe("The API key to store"), 19 | }, 20 | async ({ name, key }) => { 21 | try { 22 | await keychainService.storeKey(name, key); 23 | return { 24 | content: [{ 25 | type: "text", 26 | text: `Successfully stored API key with name: ${name}` 27 | }] 28 | }; 29 | } catch (error) { 30 | return { 31 | content: [{ 32 | type: "text", 33 | text: `Error storing API key: ${(error as Error).message}` 34 | }], 35 | isError: true 36 | }; 37 | } 38 | } 39 | ); 40 | 41 | // Tool to retrieve an API key 42 | server.tool( 43 | "get-api-key", 44 | { 45 | name: z.string().min(1).describe("The name/identifier of the API key to retrieve"), 46 | }, 47 | async ({ name }) => { 48 | try { 49 | const key = await keychainService.getKey(name); 50 | 51 | if (!key) { 52 | return { 53 | content: [{ 54 | type: "text", 55 | text: `No API key found with name: ${name}` 56 | }], 57 | isError: true 58 | }; 59 | } 60 | 61 | return { 62 | content: [{ 63 | type: "text", 64 | text: key 65 | }] 66 | }; 67 | } catch (error) { 68 | return { 69 | content: [{ 70 | type: "text", 71 | text: `Error retrieving API key: ${(error as Error).message}` 72 | }], 73 | isError: true 74 | }; 75 | } 76 | } 77 | ); 78 | 79 | // Tool to delete an API key 80 | server.tool( 81 | "delete-api-key", 82 | { 83 | name: z.string().min(1).describe("The name/identifier of the API key to delete"), 84 | }, 85 | async ({ name }) => { 86 | try { 87 | const success = await keychainService.deleteKey(name); 88 | 89 | if (!success) { 90 | return { 91 | content: [{ 92 | type: "text", 93 | text: `No API key found with name: ${name}` 94 | }], 95 | isError: true 96 | }; 97 | } 98 | 99 | return { 100 | content: [{ 101 | type: "text", 102 | text: `Successfully deleted API key with name: ${name}` 103 | }] 104 | }; 105 | } catch (error) { 106 | return { 107 | content: [{ 108 | type: "text", 109 | text: `Error deleting API key: ${(error as Error).message}` 110 | }], 111 | isError: true 112 | }; 113 | } 114 | } 115 | ); 116 | 117 | // Tool to list all stored API keys 118 | server.tool( 119 | "list-api-keys", 120 | {}, 121 | async () => { 122 | try { 123 | const keys = await keychainService.listKeys(); 124 | 125 | if (keys.length === 0) { 126 | return { 127 | content: [{ 128 | type: "text", 129 | text: "No API keys found" 130 | }] 131 | }; 132 | } 133 | 134 | return { 135 | content: [{ 136 | type: "text", 137 | text: `Available API keys:\n${keys.join("\n")}` 138 | }] 139 | }; 140 | } catch (error) { 141 | return { 142 | content: [{ 143 | type: "text", 144 | text: `Error listing API keys: ${(error as Error).message}` 145 | }], 146 | isError: true 147 | }; 148 | } 149 | } 150 | ); 151 | 152 | // Start receiving messages on stdin and sending messages on stdout 153 | const transport = new StdioServerTransport(); 154 | server.connect(transport).then(() => { 155 | console.error("ServeMyAPI MCP server is running..."); 156 | }).catch(error => { 157 | console.error("Error starting ServeMyAPI MCP server:", error); 158 | }); -------------------------------------------------------------------------------- /src/cli.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import { Client } from '@modelcontextprotocol/sdk/client'; 4 | import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js'; 5 | import { spawn } from 'child_process'; 6 | import path from 'path'; 7 | import { fileURLToPath } from 'url'; 8 | 9 | // Create an MCP client that connects to the ServeMyAPI server 10 | // Usage: 11 | // - list: List all API keys 12 | // - get : Get a specific API key 13 | // - set : Set an API key 14 | // - delete : Delete an API key 15 | async function main() { 16 | // Start the server process 17 | const __dirname = path.dirname(fileURLToPath(import.meta.url)); 18 | const serverPath = path.join(__dirname, 'server.js'); 19 | 20 | console.log('Starting ServeMyAPI server...'); 21 | 22 | // We'll let the StdioClientTransport handle the process management 23 | // No need to spawn the process manually 24 | 25 | // Create a transport that connects to the local server process 26 | const transport = new StdioClientTransport({ 27 | command: 'node', 28 | args: [serverPath] 29 | }); 30 | 31 | // Create a new MCP client with name and version 32 | const client = new Client("ServeMyAPIClient", "1.0.0"); 33 | 34 | try { 35 | // Connect to the server using the transport 36 | await client.connect(transport); 37 | console.log('Connected to ServeMyAPI server'); 38 | 39 | // Parse command line arguments 40 | const args = process.argv.slice(2); 41 | const command = args[0] || 'list'; 42 | 43 | let result; 44 | 45 | switch(command) { 46 | case 'list': 47 | console.log('Listing all API keys...'); 48 | result = await client.callTool({ 49 | name: 'list-api-keys', 50 | arguments: {} 51 | }); 52 | break; 53 | 54 | case 'get': 55 | const keyName = args[1]; 56 | if (!keyName) { 57 | console.error('Error: Key name is required for get command'); 58 | process.exit(1); 59 | } 60 | console.log(`Getting API key: ${keyName}`); 61 | result = await client.callTool({ 62 | name: 'get-api-key', 63 | arguments: { name: keyName } 64 | }); 65 | break; 66 | 67 | case 'store': 68 | const setKeyName = args[1]; 69 | const keyValue = args[2]; 70 | if (!setKeyName || !keyValue) { 71 | console.error('Error: Both key name and value are required for store command'); 72 | process.exit(1); 73 | } 74 | console.log(`Setting API key: ${setKeyName}`); 75 | result = await client.callTool({ 76 | name: 'store-api-key', 77 | arguments: { name: setKeyName, key: keyValue } 78 | }); 79 | break; 80 | 81 | case 'delete': 82 | const deleteKeyName = args[1]; 83 | if (!deleteKeyName) { 84 | console.error('Error: Key name is required for delete command'); 85 | process.exit(1); 86 | } 87 | console.log(`Deleting API key: ${deleteKeyName}`); 88 | result = await client.callTool({ 89 | name: 'delete-api-key', 90 | arguments: { name: deleteKeyName } 91 | }); 92 | break; 93 | 94 | default: 95 | console.error(`Unknown command: ${command}`); 96 | console.log('Available commands: list, get , store , delete '); 97 | process.exit(1); 98 | } 99 | 100 | // Display the results 101 | // Display the results based on the command 102 | 103 | if (result.content && result.content.length > 0) { 104 | const textContent = result.content.find(item => item.type === 'text'); 105 | 106 | if (textContent && textContent.text) { 107 | if (command === 'list') { 108 | console.log('\nAvailable API Keys:'); 109 | if (textContent.text === 'No API keys found') { 110 | console.log('No API keys found'); 111 | } else { 112 | // Split the keys by newline and display them 113 | const keys = textContent.text.replace('Available API keys:\n', '').split('\n'); 114 | keys.forEach(key => { 115 | console.log(`- ${key}`); 116 | }); 117 | } 118 | } else { 119 | // For other commands, just display the text content 120 | console.log(`\nResult: ${textContent.text}`); 121 | } 122 | } 123 | } else { 124 | console.log('No data returned from the server'); 125 | } 126 | } catch (error) { 127 | console.error('Error:', error.message); 128 | process.exit(1); 129 | } finally { 130 | // Disconnect from the server 131 | await client.disconnect(); 132 | // Transport will handle closing the server process 133 | } 134 | } 135 | 136 | // Display usage information 137 | function printUsage() { 138 | console.log(` 139 | ServeMyAPI CLI Usage: 140 | node cli.js [command] [options] 141 | 142 | Commands: 143 | list List all API keys (default) 144 | get Get the value of a specific API key 145 | store Set the value of an API key 146 | delete Delete an API key 147 | 148 | Examples: 149 | node cli.js list 150 | node cli.js get myApiKey 151 | node cli.js store myApiKey abc123def456 152 | node cli.js delete myApiKey 153 | `); 154 | } 155 | 156 | // Check if help flag is present 157 | if (process.argv.includes('--help') || process.argv.includes('-h')) { 158 | printUsage(); 159 | process.exit(0); 160 | } 161 | 162 | // Run the main function 163 | main(); 164 | -------------------------------------------------------------------------------- /src/services/keychain.ts: -------------------------------------------------------------------------------- 1 | import keytar from 'keytar'; 2 | import fs from 'fs'; 3 | import path from 'path'; 4 | 5 | const SERVICE_NAME = 'serveMyAPI'; 6 | const PERMISSION_MARKER = '_permission_granted'; 7 | const STORAGE_DIR = process.env.STORAGE_DIR || '/app/data'; 8 | const IS_DOCKER = process.env.DOCKER_ENV === 'true'; 9 | 10 | /** 11 | * Service for securely storing and retrieving API keys 12 | * - Uses macOS Keychain on macOS systems 13 | * - Falls back to file-based storage in Docker environments 14 | */ 15 | export class KeychainService { 16 | private hasStoredPermissionMarker = false; 17 | 18 | constructor() { 19 | // Check for permission marker on initialization 20 | if (!IS_DOCKER) { 21 | this.checkPermissionMarker(); 22 | } else { 23 | this.ensureStorageDirectory(); 24 | } 25 | } 26 | 27 | /** 28 | * Ensure the storage directory exists for Docker environments 29 | */ 30 | private ensureStorageDirectory(): void { 31 | if (IS_DOCKER) { 32 | try { 33 | if (!fs.existsSync(STORAGE_DIR)) { 34 | fs.mkdirSync(STORAGE_DIR, { recursive: true }); 35 | } 36 | } catch (error) { 37 | console.error('Error creating storage directory:', error); 38 | } 39 | } 40 | } 41 | 42 | /** 43 | * Check if the permission marker exists, which indicates 44 | * that the user has previously granted permission 45 | */ 46 | private async checkPermissionMarker(): Promise { 47 | if (IS_DOCKER) return; 48 | 49 | try { 50 | const marker = await keytar.getPassword(SERVICE_NAME, PERMISSION_MARKER); 51 | this.hasStoredPermissionMarker = !!marker; 52 | 53 | if (!this.hasStoredPermissionMarker) { 54 | // If no marker exists, create one to consolidate permission requests 55 | await keytar.setPassword(SERVICE_NAME, PERMISSION_MARKER, 'true'); 56 | this.hasStoredPermissionMarker = true; 57 | } 58 | } catch (error) { 59 | console.error('Error checking permission marker:', error); 60 | } 61 | } 62 | 63 | /** 64 | * Store an API key 65 | * @param name The name/identifier for the API key 66 | * @param key The API key to store 67 | * @returns Promise that resolves when the key is stored 68 | */ 69 | async storeKey(name: string, key: string): Promise { 70 | if (IS_DOCKER) { 71 | return this.storeKeyFile(name, key); 72 | } 73 | 74 | // Ensure permission marker exists before storing key 75 | if (!this.hasStoredPermissionMarker) { 76 | await this.checkPermissionMarker(); 77 | } 78 | return keytar.setPassword(SERVICE_NAME, name, key); 79 | } 80 | 81 | /** 82 | * Store an API key in a file (Docker fallback) 83 | */ 84 | private async storeKeyFile(name: string, key: string): Promise { 85 | try { 86 | const filePath = path.join(STORAGE_DIR, `${name}.key`); 87 | fs.writeFileSync(filePath, key, { encoding: 'utf8' }); 88 | } catch (error) { 89 | console.error(`Error storing key ${name} in file:`, error); 90 | throw error; 91 | } 92 | } 93 | 94 | /** 95 | * Retrieve an API key 96 | * @param name The name/identifier of the API key to retrieve 97 | * @returns Promise that resolves with the API key or null if not found 98 | */ 99 | async getKey(name: string): Promise { 100 | if (IS_DOCKER) { 101 | return this.getKeyFile(name); 102 | } 103 | 104 | // Ensure permission marker exists before retrieving key 105 | if (!this.hasStoredPermissionMarker) { 106 | await this.checkPermissionMarker(); 107 | } 108 | return keytar.getPassword(SERVICE_NAME, name); 109 | } 110 | 111 | /** 112 | * Retrieve an API key from a file (Docker fallback) 113 | */ 114 | private getKeyFile(name: string): string | null { 115 | try { 116 | const filePath = path.join(STORAGE_DIR, `${name}.key`); 117 | if (fs.existsSync(filePath)) { 118 | return fs.readFileSync(filePath, { encoding: 'utf8' }); 119 | } 120 | return null; 121 | } catch (error) { 122 | console.error(`Error retrieving key ${name} from file:`, error); 123 | return null; 124 | } 125 | } 126 | 127 | /** 128 | * Delete an API key 129 | * @param name The name/identifier of the API key to delete 130 | * @returns Promise that resolves with true if deleted, false otherwise 131 | */ 132 | async deleteKey(name: string): Promise { 133 | if (IS_DOCKER) { 134 | return this.deleteKeyFile(name); 135 | } 136 | 137 | // Ensure permission marker exists before deleting key 138 | if (!this.hasStoredPermissionMarker) { 139 | await this.checkPermissionMarker(); 140 | } 141 | return keytar.deletePassword(SERVICE_NAME, name); 142 | } 143 | 144 | /** 145 | * Delete an API key file (Docker fallback) 146 | */ 147 | private deleteKeyFile(name: string): boolean { 148 | try { 149 | const filePath = path.join(STORAGE_DIR, `${name}.key`); 150 | if (fs.existsSync(filePath)) { 151 | fs.unlinkSync(filePath); 152 | return true; 153 | } 154 | return false; 155 | } catch (error) { 156 | console.error(`Error deleting key ${name} file:`, error); 157 | return false; 158 | } 159 | } 160 | 161 | /** 162 | * List all stored API keys 163 | * @returns Promise that resolves with an array of key names 164 | */ 165 | async listKeys(): Promise { 166 | if (IS_DOCKER) { 167 | return this.listKeyFiles(); 168 | } 169 | 170 | // Ensure permission marker exists before listing keys 171 | if (!this.hasStoredPermissionMarker) { 172 | await this.checkPermissionMarker(); 173 | } 174 | 175 | const credentials = await keytar.findCredentials(SERVICE_NAME); 176 | // Filter out the permission marker from the list of keys 177 | return credentials 178 | .map(cred => cred.account) 179 | .filter(account => account !== PERMISSION_MARKER); 180 | } 181 | 182 | /** 183 | * List all stored API key files (Docker fallback) 184 | */ 185 | private listKeyFiles(): string[] { 186 | try { 187 | const files = fs.readdirSync(STORAGE_DIR); 188 | return files 189 | .filter(file => file.endsWith('.key')) 190 | .map(file => file.replace(/\.key$/, '')); 191 | } catch (error) { 192 | console.error('Error listing key files:', error); 193 | return []; 194 | } 195 | } 196 | } 197 | 198 | export default new KeychainService(); -------------------------------------------------------------------------------- /src/server.ts: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; 3 | import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js"; 4 | import { z } from "zod"; 5 | import keychainService from "./services/keychain.js"; 6 | 7 | // Create an MCP server 8 | const server = new McpServer({ 9 | name: "ServeMyAPI", 10 | version: "1.0.0" 11 | }); 12 | 13 | // Tool to store an API key 14 | server.tool( 15 | "store-api-key", 16 | { 17 | name: z.string().min(1).describe("The name/identifier for the API key"), 18 | key: z.string().min(1).describe("The API key to store"), 19 | }, 20 | async ({ name, key }) => { 21 | try { 22 | await keychainService.storeKey(name, key); 23 | return { 24 | content: [{ 25 | type: "text", 26 | text: `Successfully stored API key with name: ${name}` 27 | }] 28 | }; 29 | } catch (error) { 30 | return { 31 | content: [{ 32 | type: "text", 33 | text: `Error storing API key: ${(error as Error).message}` 34 | }], 35 | isError: true 36 | }; 37 | } 38 | } 39 | ); 40 | 41 | // Tool to retrieve an API key 42 | server.tool( 43 | "get-api-key", 44 | { 45 | name: z.string().min(1).describe("The name/identifier of the API key to retrieve"), 46 | }, 47 | async ({ name }) => { 48 | try { 49 | const key = await keychainService.getKey(name); 50 | 51 | if (!key) { 52 | return { 53 | content: [{ 54 | type: "text", 55 | text: `No API key found with name: ${name}` 56 | }], 57 | isError: true 58 | }; 59 | } 60 | 61 | return { 62 | content: [{ 63 | type: "text", 64 | text: key 65 | }] 66 | }; 67 | } catch (error) { 68 | return { 69 | content: [{ 70 | type: "text", 71 | text: `Error retrieving API key: ${(error as Error).message}` 72 | }], 73 | isError: true 74 | }; 75 | } 76 | } 77 | ); 78 | 79 | // Tool to delete an API key 80 | server.tool( 81 | "delete-api-key", 82 | { 83 | name: z.string().min(1).describe("The name/identifier of the API key to delete"), 84 | }, 85 | async ({ name }) => { 86 | try { 87 | const success = await keychainService.deleteKey(name); 88 | 89 | if (!success) { 90 | return { 91 | content: [{ 92 | type: "text", 93 | text: `No API key found with name: ${name}` 94 | }], 95 | isError: true 96 | }; 97 | } 98 | 99 | return { 100 | content: [{ 101 | type: "text", 102 | text: `Successfully deleted API key with name: ${name}` 103 | }] 104 | }; 105 | } catch (error) { 106 | return { 107 | content: [{ 108 | type: "text", 109 | text: `Error deleting API key: ${(error as Error).message}` 110 | }], 111 | isError: true 112 | }; 113 | } 114 | } 115 | ); 116 | 117 | // Tool to list all stored API keys 118 | server.tool( 119 | "list-api-keys", 120 | {}, 121 | async () => { 122 | try { 123 | const keys = await keychainService.listKeys(); 124 | 125 | if (keys.length === 0) { 126 | return { 127 | content: [{ 128 | type: "text", 129 | text: "No API keys found" 130 | }] 131 | }; 132 | } 133 | 134 | return { 135 | content: [{ 136 | type: "text", 137 | text: `Available API keys:\n${keys.join("\n")}` 138 | }] 139 | }; 140 | } catch (error) { 141 | return { 142 | content: [{ 143 | type: "text", 144 | text: `Error listing API keys: ${(error as Error).message}` 145 | }], 146 | isError: true 147 | }; 148 | } 149 | } 150 | ); 151 | 152 | // Set up Express app for HTTP transport 153 | const app = express(); 154 | const port = process.env.PORT || 3000; 155 | 156 | // Store active transports 157 | const activeTransports = new Map(); 158 | 159 | app.get("/sse", async (req, res) => { 160 | const id = Date.now().toString(); 161 | const transport = new SSEServerTransport("/messages", res); 162 | 163 | activeTransports.set(id, transport); 164 | 165 | // Set headers for SSE 166 | res.setHeader('Content-Type', 'text/event-stream'); 167 | res.setHeader('Cache-Control', 'no-cache'); 168 | res.setHeader('Connection', 'keep-alive'); 169 | 170 | // Handle client disconnect 171 | req.on('close', () => { 172 | activeTransports.delete(id); 173 | }); 174 | 175 | await server.connect(transport); 176 | }); 177 | 178 | app.post("/messages", express.json(), (req, res) => { 179 | // Get the last transport - in a production app, you'd want to maintain sessions 180 | const lastTransportId = Array.from(activeTransports.keys()).pop(); 181 | 182 | if (!lastTransportId) { 183 | res.status(400).json({ error: "No active connections" }); 184 | return; 185 | } 186 | 187 | const transport = activeTransports.get(lastTransportId); 188 | transport.handlePostMessage(req, res).catch((error: Error) => { 189 | console.error("Error handling message:", error); 190 | if (!res.headersSent) { 191 | res.status(500).json({ error: "Internal server error" }); 192 | } 193 | }); 194 | }); 195 | 196 | // Simple home page 197 | app.get("/", (req, res) => { 198 | res.send(` 199 | 200 | 201 | ServeMyAPI 202 | 208 | 209 | 210 |

ServeMyAPI

211 |

This is a personal MCP server for securely storing and accessing API keys across projects using the macOS Keychain.

212 |

The server exposes the following tools:

213 |
    214 |
  • store-api-key - Store an API key in the keychain
  • 215 |
  • get-api-key - Retrieve an API key from the keychain
  • 216 |
  • delete-api-key - Delete an API key from the keychain
  • 217 |
  • list-api-keys - List all stored API keys
  • 218 |
219 |

This server is running with HTTP SSE transport. Connect to /sse for the SSE endpoint and post messages to /messages.

220 | 221 | 222 | `); 223 | }); 224 | 225 | // Start the server 226 | app.listen(port, () => { 227 | console.log(`ServeMyAPI HTTP server is running on port ${port}`); 228 | }); 229 | 230 | export { server }; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ServeMyAPI 2 | 3 | [![smithery badge](https://smithery.ai/badge/@Jktfe/servemyapi)](https://smithery.ai/server/@Jktfe/servemyapi) 4 | 5 | A personal MCP (Model Context Protocol) server for securely storing and accessing API keys across projects using the macOS Keychain. 6 | 7 | > **IMPORTANT**: ServeMyAPI is a macOS-specific tool that relies on the macOS Keychain for secure storage. It is not compatible with Windows or Linux operating systems. See the security notes section for more details. 8 | 9 | ## Overview 10 | 11 | ServeMyAPI allows you to store API keys securely in the macOS Keychain and access them through a consistent MCP interface. This makes it easy to: 12 | 13 | - Store API keys securely (they're never visible in .env files or config files) 14 | - Access the same keys across multiple projects 15 | - Use natural language to store and retrieve keys (when used with LLMs like Claude) 16 | - Provide keys directly to your AI assistant when it needs to access services 17 | 18 | ## Why ServeMyAPI over .ENV Files? 19 | 20 | Using ServeMyAPI instead of traditional .ENV files solves several common problems: 21 | 22 | 1. **GitHub Security Conflicts**: 23 | - .ENV files need to be excluded from Git repositories for security (via .gitignore) 24 | - This creates a "hidden context" problem where important configuration is invisible to collaborators and LLMs 25 | - New developers often struggle with setting up the correct environment variables 26 | 27 | 2. **LLM Integration Challenges**: 28 | - LLMs like Claude can't directly access your .ENV files due to security constraints 29 | - When LLMs need API keys to complete tasks, you often need manual workarounds 30 | - ServeMyAPI lets your AI assistant request keys through natural language 31 | 32 | 3. **Cross-Project Consistency**: 33 | - With .ENV files, you typically need to duplicate API keys across multiple projects 34 | - When keys change, you need to update multiple files 35 | - ServeMyAPI provides a central storage location accessible from any project 36 | 37 | This approach gives you the best of both worlds: secure storage of sensitive credentials without sacrificing visibility and accessibility for your AI tools. 38 | 39 | ## Features 40 | 41 | - Secure storage of API keys in the macOS Keychain 42 | - Simple MCP tools for storing, retrieving, listing, and deleting keys 43 | - Convenient CLI interface for terminal-based key management 44 | - Support for both stdio and HTTP/SSE transports 45 | - Compatible with any MCP client (Claude Desktop, etc.) 46 | 47 | ## Installation 48 | 49 | ```bash 50 | # Clone the repository 51 | git clone https://github.com/yourusername/servemyapi.git 52 | cd servemyapi 53 | 54 | # Install dependencies 55 | npm install 56 | 57 | # Build the project 58 | npm run build 59 | ``` 60 | 61 | ## Usage 62 | 63 | ### CLI Interface 64 | 65 | ServeMyAPI comes with a command-line interface for quick key management directly from your terminal: 66 | 67 | ```bash 68 | # Install the CLI globally 69 | npm run build 70 | npm link 71 | 72 | # List all stored API keys 73 | api-key list 74 | 75 | # Get a specific API key 76 | api-key get github_token 77 | 78 | # Store a new API key 79 | api-key store github_token ghp_123456789abcdefg 80 | 81 | # Delete an API key 82 | api-key delete github_token 83 | 84 | # Display help 85 | api-key help 86 | ``` 87 | 88 | ### Running as a stdio server 89 | 90 | This is the simplest way to use ServeMyAPI as an MCP server, especially when working with Claude Desktop: 91 | 92 | ```bash 93 | npm start 94 | ``` 95 | 96 | ### Running as an HTTP server 97 | 98 | For applications that require HTTP access: 99 | 100 | ```bash 101 | node dist/server.js 102 | ``` 103 | 104 | This will start the server on port 3000 (or the port specified in the PORT environment variable). 105 | 106 | ### Using Smithery 107 | 108 | ServeMyAPI is available as a hosted service on [Smithery](https://smithery.ai/server/@Jktfe/servemyapi). 109 | 110 | ```javascript 111 | import { createTransport } from "@smithery/sdk/transport.js" 112 | 113 | const transport = createTransport("https://server.smithery.ai/@Jktfe/servemyapi") 114 | 115 | // Create MCP client 116 | import { Client } from "@modelcontextprotocol/sdk/client/index.js" 117 | 118 | const client = new Client({ 119 | name: "Test client", 120 | version: "1.0.0" 121 | }) 122 | await client.connect(transport) 123 | 124 | // Use the server tools with your LLM application 125 | const tools = await client.listTools() 126 | console.log(`Available tools: ${tools.map(t => t.name).join(", ")}`) 127 | ``` 128 | 129 | For more details, see the [Smithery API documentation](https://smithery.ai/server/@Jktfe/servemyapi/api). 130 | 131 | ### Configuring MCP Clients 132 | 133 | ServeMyAPI works with any MCP-compatible client. Example configuration files are provided in the `examples` directory. 134 | 135 | #### Claude Desktop 136 | 137 | To use ServeMyAPI with Claude Desktop: 138 | 139 | 1. Locate or create the Claude Desktop configuration file: 140 | - **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json` 141 | - **Windows**: `%AppData%\Claude\claude_desktop_config.json` 142 | 143 | 2. Add ServeMyAPI to the `mcpServers` section (you can copy from `examples/claude_desktop_config.json`): 144 | ```json 145 | { 146 | "mcpServers": { 147 | "serveMyAPI": { 148 | "command": "node", 149 | "args": [ 150 | "/ABSOLUTE/PATH/TO/servemyapi/dist/index.js" 151 | ] 152 | } 153 | } 154 | } 155 | ``` 156 | 157 | 3. Replace `/ABSOLUTE/PATH/TO/servemyapi` with the actual path to your ServeMyAPI installation. 158 | 4. Restart Claude Desktop. 159 | 160 | #### Windsurf 161 | 162 | To use ServeMyAPI with Windsurf: 163 | 164 | 1. Open Windsurf editor and navigate to Settings 165 | 2. Add ServeMyAPI to your MCP server configuration using the example in `examples/windsurf_config.json` 166 | 3. Adapt the paths to your local installation 167 | 168 | ## MCP Tools 169 | 170 | ServeMyAPI exposes the following tools: 171 | 172 | ### store-api-key 173 | 174 | Store an API key in the keychain. 175 | 176 | Parameters: 177 | - `name`: The name/identifier for the API key 178 | - `key`: The API key to store 179 | 180 | Example (from Claude): 181 | ``` 182 | Using serveMyAPI, store my API key ABC123XYZ as "OpenAI API Key" 183 | ``` 184 | 185 | ### get-api-key 186 | 187 | Retrieve an API key from the keychain. 188 | 189 | Parameters: 190 | - `name`: The name/identifier of the API key to retrieve 191 | 192 | Example (from Claude): 193 | ``` 194 | Using serveMyAPI, get the API key named "OpenAI API Key" 195 | ``` 196 | 197 | ### delete-api-key 198 | 199 | Delete an API key from the keychain. 200 | 201 | Parameters: 202 | - `name`: The name/identifier of the API key to delete 203 | 204 | Example (from Claude): 205 | ``` 206 | Using serveMyAPI, delete the API key named "OpenAI API Key" 207 | ``` 208 | 209 | ### list-api-keys 210 | 211 | List all stored API keys. 212 | 213 | No parameters required. 214 | 215 | Example (from Claude): 216 | ``` 217 | Using serveMyAPI, list all my stored API keys 218 | ``` 219 | 220 | ## Security Notes 221 | 222 | - All API keys are stored securely in the macOS Keychain 223 | - Keys are only accessible to the current user 224 | - The keychain requires authentication for access 225 | - No keys are stored in plaintext or logged anywhere 226 | 227 | ## Roadmap 228 | 229 | Future plans for ServeMyAPI include: 230 | 231 | - **Code Scanner Tool**: A tool that automatically scans your codebase for API endpoints, sensitive URLs, and environment variables, then suggests names to store them in the Keychain. This would allow developers to continue using .ENV files in their regular workflow while ensuring credentials are also available to LLMs and other tools when needed. 232 | 233 | - **Cross-Platform Support**: Investigating secure credential storage options for Windows and Linux to make ServeMyAPI more widely accessible. 234 | 235 | - **Integration with Popular Frameworks**: Providing easy integration with frameworks like Next.js, Express, and others. 236 | 237 | - **UI for Key Management**: A simple web interface for managing your stored API keys directly. 238 | 239 | Feel free to suggest additional features or contribute to the roadmap by opening an issue or pull request. 240 | 241 | ## Development 242 | 243 | ```bash 244 | # Run in development mode with hot reload 245 | npm run dev 246 | 247 | # Use the CLI during development 248 | npm run cli list 249 | 250 | # Lint the code 251 | npm run lint 252 | 253 | # Build for production 254 | npm run build 255 | ``` 256 | 257 | ## License 258 | 259 | MIT -------------------------------------------------------------------------------- /MCP-TypeScript-Readme.md: -------------------------------------------------------------------------------- 1 | # MCP TypeScript SDK ![NPM Version](https://img.shields.io/npm/v/%40modelcontextprotocol%2Fsdk) ![MIT licensed](https://img.shields.io/npm/l/%40modelcontextprotocol%2Fsdk) 2 | 3 | ## Table of Contents 4 | - [Overview](#overview) 5 | - [Installation](#installation) 6 | - [Quickstart](#quickstart) 7 | - [What is MCP?](#what-is-mcp) 8 | - [Core Concepts](#core-concepts) 9 | - [Server](#server) 10 | - [Resources](#resources) 11 | - [Tools](#tools) 12 | - [Prompts](#prompts) 13 | - [Running Your Server](#running-your-server) 14 | - [stdio](#stdio) 15 | - [HTTP with SSE](#http-with-sse) 16 | - [Testing and Debugging](#testing-and-debugging) 17 | - [Examples](#examples) 18 | - [Echo Server](#echo-server) 19 | - [SQLite Explorer](#sqlite-explorer) 20 | - [Advanced Usage](#advanced-usage) 21 | - [Low-Level Server](#low-level-server) 22 | - [Writing MCP Clients](#writing-mcp-clients) 23 | - [Server Capabilities](#server-capabilities) 24 | 25 | ## Overview 26 | 27 | The Model Context Protocol allows applications to provide context for LLMs in a standardized way, separating the concerns of providing context from the actual LLM interaction. This TypeScript SDK implements the full MCP specification, making it easy to: 28 | 29 | - Build MCP clients that can connect to any MCP server 30 | - Create MCP servers that expose resources, prompts and tools 31 | - Use standard transports like stdio and SSE 32 | - Handle all MCP protocol messages and lifecycle events 33 | 34 | ## Installation 35 | 36 | ```bash 37 | npm install @modelcontextprotocol/sdk 38 | ``` 39 | 40 | ## Quick Start 41 | 42 | Let's create a simple MCP server that exposes a calculator tool and some data: 43 | 44 | ```typescript 45 | import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js"; 46 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; 47 | import { z } from "zod"; 48 | 49 | // Create an MCP server 50 | const server = new McpServer({ 51 | name: "Demo", 52 | version: "1.0.0" 53 | }); 54 | 55 | // Add an addition tool 56 | server.tool("add", 57 | { a: z.number(), b: z.number() }, 58 | async ({ a, b }) => ({ 59 | content: [{ type: "text", text: String(a + b) }] 60 | }) 61 | ); 62 | 63 | // Add a dynamic greeting resource 64 | server.resource( 65 | "greeting", 66 | new ResourceTemplate("greeting://{name}", { list: undefined }), 67 | async (uri, { name }) => ({ 68 | contents: [{ 69 | uri: uri.href, 70 | text: `Hello, ${name}!` 71 | }] 72 | }) 73 | ); 74 | 75 | // Start receiving messages on stdin and sending messages on stdout 76 | const transport = new StdioServerTransport(); 77 | await server.connect(transport); 78 | ``` 79 | 80 | ## What is MCP? 81 | 82 | The [Model Context Protocol (MCP)](https://modelcontextprotocol.io) lets you build servers that expose data and functionality to LLM applications in a secure, standardized way. Think of it like a web API, but specifically designed for LLM interactions. MCP servers can: 83 | 84 | - Expose data through **Resources** (think of these sort of like GET endpoints; they are used to load information into the LLM's context) 85 | - Provide functionality through **Tools** (sort of like POST endpoints; they are used to execute code or otherwise produce a side effect) 86 | - Define interaction patterns through **Prompts** (reusable templates for LLM interactions) 87 | - And more! 88 | 89 | ## Core Concepts 90 | 91 | ### Server 92 | 93 | The McpServer is your core interface to the MCP protocol. It handles connection management, protocol compliance, and message routing: 94 | 95 | ```typescript 96 | const server = new McpServer({ 97 | name: "My App", 98 | version: "1.0.0" 99 | }); 100 | ``` 101 | 102 | ### Resources 103 | 104 | Resources are how you expose data to LLMs. They're similar to GET endpoints in a REST API - they provide data but shouldn't perform significant computation or have side effects: 105 | 106 | ```typescript 107 | // Static resource 108 | server.resource( 109 | "config", 110 | "config://app", 111 | async (uri) => ({ 112 | contents: [{ 113 | uri: uri.href, 114 | text: "App configuration here" 115 | }] 116 | }) 117 | ); 118 | 119 | // Dynamic resource with parameters 120 | server.resource( 121 | "user-profile", 122 | new ResourceTemplate("users://{userId}/profile", { list: undefined }), 123 | async (uri, { userId }) => ({ 124 | contents: [{ 125 | uri: uri.href, 126 | text: `Profile data for user ${userId}` 127 | }] 128 | }) 129 | ); 130 | ``` 131 | 132 | ### Tools 133 | 134 | Tools let LLMs take actions through your server. Unlike resources, tools are expected to perform computation and have side effects: 135 | 136 | ```typescript 137 | // Simple tool with parameters 138 | server.tool( 139 | "calculate-bmi", 140 | { 141 | weightKg: z.number(), 142 | heightM: z.number() 143 | }, 144 | async ({ weightKg, heightM }) => ({ 145 | content: [{ 146 | type: "text", 147 | text: String(weightKg / (heightM * heightM)) 148 | }] 149 | }) 150 | ); 151 | 152 | // Async tool with external API call 153 | server.tool( 154 | "fetch-weather", 155 | { city: z.string() }, 156 | async ({ city }) => { 157 | const response = await fetch(`https://api.weather.com/${city}`); 158 | const data = await response.text(); 159 | return { 160 | content: [{ type: "text", text: data }] 161 | }; 162 | } 163 | ); 164 | ``` 165 | 166 | ### Prompts 167 | 168 | Prompts are reusable templates that help LLMs interact with your server effectively: 169 | 170 | ```typescript 171 | server.prompt( 172 | "review-code", 173 | { code: z.string() }, 174 | ({ code }) => ({ 175 | messages: [{ 176 | role: "user", 177 | content: { 178 | type: "text", 179 | text: `Please review this code:\n\n${code}` 180 | } 181 | }] 182 | }) 183 | ); 184 | ``` 185 | 186 | ## Running Your Server 187 | 188 | MCP servers in TypeScript need to be connected to a transport to communicate with clients. How you start the server depends on the choice of transport: 189 | 190 | ### stdio 191 | 192 | For command-line tools and direct integrations: 193 | 194 | ```typescript 195 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; 196 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; 197 | 198 | const server = new McpServer({ 199 | name: "example-server", 200 | version: "1.0.0" 201 | }); 202 | 203 | // ... set up server resources, tools, and prompts ... 204 | 205 | const transport = new StdioServerTransport(); 206 | await server.connect(transport); 207 | ``` 208 | 209 | ### HTTP with SSE 210 | 211 | For remote servers, start a web server with a Server-Sent Events (SSE) endpoint, and a separate endpoint for the client to send its messages to: 212 | 213 | ```typescript 214 | import express from "express"; 215 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; 216 | import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js"; 217 | 218 | const server = new McpServer({ 219 | name: "example-server", 220 | version: "1.0.0" 221 | }); 222 | 223 | // ... set up server resources, tools, and prompts ... 224 | 225 | const app = express(); 226 | 227 | app.get("/sse", async (req, res) => { 228 | const transport = new SSEServerTransport("/messages", res); 229 | await server.connect(transport); 230 | }); 231 | 232 | app.post("/messages", async (req, res) => { 233 | // Note: to support multiple simultaneous connections, these messages will 234 | // need to be routed to a specific matching transport. (This logic isn't 235 | // implemented here, for simplicity.) 236 | await transport.handlePostMessage(req, res); 237 | }); 238 | 239 | app.listen(3001); 240 | ``` 241 | 242 | ### Testing and Debugging 243 | 244 | To test your server, you can use the [MCP Inspector](https://github.com/modelcontextprotocol/inspector). See its README for more information. 245 | 246 | ## Examples 247 | 248 | ### Echo Server 249 | 250 | A simple server demonstrating resources, tools, and prompts: 251 | 252 | ```typescript 253 | import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js"; 254 | import { z } from "zod"; 255 | 256 | const server = new McpServer({ 257 | name: "Echo", 258 | version: "1.0.0" 259 | }); 260 | 261 | server.resource( 262 | "echo", 263 | new ResourceTemplate("echo://{message}", { list: undefined }), 264 | async (uri, { message }) => ({ 265 | contents: [{ 266 | uri: uri.href, 267 | text: `Resource echo: ${message}` 268 | }] 269 | }) 270 | ); 271 | 272 | server.tool( 273 | "echo", 274 | { message: z.string() }, 275 | async ({ message }) => ({ 276 | content: [{ type: "text", text: `Tool echo: ${message}` }] 277 | }) 278 | ); 279 | 280 | server.prompt( 281 | "echo", 282 | { message: z.string() }, 283 | ({ message }) => ({ 284 | messages: [{ 285 | role: "user", 286 | content: { 287 | type: "text", 288 | text: `Please process this message: ${message}` 289 | } 290 | }] 291 | }) 292 | ); 293 | ``` 294 | 295 | ### SQLite Explorer 296 | 297 | A more complex example showing database integration: 298 | 299 | ```typescript 300 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; 301 | import sqlite3 from "sqlite3"; 302 | import { promisify } from "util"; 303 | import { z } from "zod"; 304 | 305 | const server = new McpServer({ 306 | name: "SQLite Explorer", 307 | version: "1.0.0" 308 | }); 309 | 310 | // Helper to create DB connection 311 | const getDb = () => { 312 | const db = new sqlite3.Database("database.db"); 313 | return { 314 | all: promisify(db.all.bind(db)), 315 | close: promisify(db.close.bind(db)) 316 | }; 317 | }; 318 | 319 | server.resource( 320 | "schema", 321 | "schema://main", 322 | async (uri) => { 323 | const db = getDb(); 324 | try { 325 | const tables = await db.all( 326 | "SELECT sql FROM sqlite_master WHERE type='table'" 327 | ); 328 | return { 329 | contents: [{ 330 | uri: uri.href, 331 | text: tables.map((t: {sql: string}) => t.sql).join("\n") 332 | }] 333 | }; 334 | } finally { 335 | await db.close(); 336 | } 337 | } 338 | ); 339 | 340 | server.tool( 341 | "query", 342 | { sql: z.string() }, 343 | async ({ sql }) => { 344 | const db = getDb(); 345 | try { 346 | const results = await db.all(sql); 347 | return { 348 | content: [{ 349 | type: "text", 350 | text: JSON.stringify(results, null, 2) 351 | }] 352 | }; 353 | } catch (err: unknown) { 354 | const error = err as Error; 355 | return { 356 | content: [{ 357 | type: "text", 358 | text: `Error: ${error.message}` 359 | }], 360 | isError: true 361 | }; 362 | } finally { 363 | await db.close(); 364 | } 365 | } 366 | ); 367 | ``` 368 | 369 | ## Advanced Usage 370 | 371 | ### Low-Level Server 372 | 373 | For more control, you can use the low-level Server class directly: 374 | 375 | ```typescript 376 | import { Server } from "@modelcontextprotocol/sdk/server/index.js"; 377 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; 378 | import { 379 | ListPromptsRequestSchema, 380 | GetPromptRequestSchema 381 | } from "@modelcontextprotocol/sdk/types.js"; 382 | 383 | const server = new Server( 384 | { 385 | name: "example-server", 386 | version: "1.0.0" 387 | }, 388 | { 389 | capabilities: { 390 | prompts: {} 391 | } 392 | } 393 | ); 394 | 395 | server.setRequestHandler(ListPromptsRequestSchema, async () => { 396 | return { 397 | prompts: [{ 398 | name: "example-prompt", 399 | description: "An example prompt template", 400 | arguments: [{ 401 | name: "arg1", 402 | description: "Example argument", 403 | required: true 404 | }] 405 | }] 406 | }; 407 | }); 408 | 409 | server.setRequestHandler(GetPromptRequestSchema, async (request) => { 410 | if (request.params.name !== "example-prompt") { 411 | throw new Error("Unknown prompt"); 412 | } 413 | return { 414 | description: "Example prompt", 415 | messages: [{ 416 | role: "user", 417 | content: { 418 | type: "text", 419 | text: "Example prompt text" 420 | } 421 | }] 422 | }; 423 | }); 424 | 425 | const transport = new StdioServerTransport(); 426 | await server.connect(transport); 427 | ``` 428 | 429 | ### Writing MCP Clients 430 | 431 | The SDK provides a high-level client interface: 432 | 433 | ```typescript 434 | import { Client } from "@modelcontextprotocol/sdk/client/index.js"; 435 | import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js"; 436 | 437 | const transport = new StdioClientTransport({ 438 | command: "node", 439 | args: ["server.js"] 440 | }); 441 | 442 | const client = new Client( 443 | { 444 | name: "example-client", 445 | version: "1.0.0" 446 | }, 447 | { 448 | capabilities: { 449 | prompts: {}, 450 | resources: {}, 451 | tools: {} 452 | } 453 | } 454 | ); 455 | 456 | await client.connect(transport); 457 | 458 | // List prompts 459 | const prompts = await client.listPrompts(); 460 | 461 | // Get a prompt 462 | const prompt = await client.getPrompt("example-prompt", { 463 | arg1: "value" 464 | }); 465 | 466 | // List resources 467 | const resources = await client.listResources(); 468 | 469 | // Read a resource 470 | const resource = await client.readResource("file:///example.txt"); 471 | 472 | // Call a tool 473 | const result = await client.callTool({ 474 | name: "example-tool", 475 | arguments: { 476 | arg1: "value" 477 | } 478 | }); 479 | ``` 480 | 481 | ## Documentation 482 | 483 | - [Model Context Protocol documentation](https://modelcontextprotocol.io) 484 | - [MCP Specification](https://spec.modelcontextprotocol.io) 485 | - [Example Servers](https://github.com/modelcontextprotocol/servers) 486 | 487 | ## Contributing 488 | 489 | Issues and pull requests are welcome on GitHub at https://github.com/modelcontextprotocol/typescript-sdk. 490 | 491 | ## License 492 | 493 | This project is licensed under the MIT License—see the [LICENSE](LICENSE) file for details. -------------------------------------------------------------------------------- /CLAUDE.md: -------------------------------------------------------------------------------- 1 | # serveMyAPI Project Guide 2 | 3 | ## Project Purpose 4 | This project aims to create a personal MCP server for securely storing and accessing API keys across projects using the macOS Keychain. 5 | 6 | ## Build & Commands 7 | - Setup: `npm install` 8 | - Start server: `npm run dev` 9 | - Test: `npm test` 10 | - Lint: `npm run lint` 11 | - Build: `npm run build` 12 | 13 | ## Code Style Guidelines 14 | - **Formatting**: Follow TypeScript standard practices with 2-space indentation 15 | - **Imports**: Group imports by type (core, third-party, local) 16 | - **Naming**: Use camelCase for variables/functions, PascalCase for classes/interfaces 17 | - **Error Handling**: Use try/catch blocks for error management 18 | - **Security**: Never log or expose API keys in plaintext 19 | - **Documentation**: Document all public functions with JSDoc comments 20 | 21 | ## Key Technologies 22 | - TypeScript SDK for Model Context Protocol (MCP) 23 | - macOS Keychain API for secure credential storage 24 | - Express.js for API endpoints (if needed) 25 | 26 | # myAI Memory 27 | 28 | # General Response Style 29 | ## Use this in every response 30 | -~- You can be very concise 31 | -~- Always double check references and provide links to sources with validation of reference and trustworthy nature of the source, make use of the MCPs available 32 | 33 | # Personal Information 34 | ## User specific and personal information 35 | -~- Name: James William Peter King (James, JWPK) 36 | -~- Date of Birth: 29.03.1985 (40 years old) 37 | -~- Location: London, UK 38 | -~- Work Contact: james@newmodel.vc | 07793988228 39 | -~- Personal Contact: j.w.p.king@gmail.com | 07515900330 40 | -~- Family: Wife Roxanne (39), son Fletcher (8), daughter Viola (5) (pronounced Vi'la) 41 | 42 | ## Childhood & Education Background 43 | -~- Early Years: Grew up at Rendcomb boarding school in the Cotswolds where parents were housemasters 44 | -~- Primary Education: Cirencester County Juniors 45 | -~- Secondary Education: 46 | -~- Kimbolton School (GCSEs and French AS) 47 | -~- Leicester Grammar (3As, 2Bs at A-Level 2003) 48 | -~- Higher Education: 49 | -~- Imperial College (Computer Engineering first year) 50 | -~- Leeds University: BSc Computing (Scholarship) 2:1 (2005-2008) 51 | -~- Professional Qualifications: FCA Authorised principal 52 | -~- Learning Style: Quick to grasp concepts, excellent pattern recognition, quick to consider practical applications and implications 53 | 54 | ## Professional Background 55 | **Current Role**: CEO, Founder, and Director at New Model Venture Capital (NMVC, New Model) 56 | -~- Founded New Model in May 2013 to deliver "fast, intelligent capital to high-growth companies" 57 | -~- Developed investment products including the creation of the Guaranteed Venture Portfolio Loan (GVPL), which he raised £15.85m for as the first of its kind in the world 58 | **Previous Companies**: 59 | -~- Fig VC (Dec 2009-2023) - Founder 60 | -~- U Account (Jan-Jun 2019) 61 | -~- Wazoku (Jan 2011-Jun 2019) - Founder 62 | -~- Brightsparks (Oct 2012-Jul 2015) 63 | -~- MuJo (Jan 2011-Nov 2014) – Co-Founder 64 | -~- Students Work (Apr 2008-Dec 2009) 65 | -~- KPMG (Sep 2004-Aug 2005) 66 | 67 | ## Education 68 | -~- BSc Computing (Scholarship) 2:1 Leeds University (2005-2008) 69 | -~- FCA Authorised principal 70 | -~- Previous Education: 71 | -~- Leicester Grammar (3As, 2Bs at A-Level 2003) 72 | -~- Imperial College (Computer Engineering first year) 73 | -~- Kimbolton School (GCSEs and French AS) 74 | 75 | ## Key Achievements & Skills 76 | -~- Raised hundreds of millions in Debt and Equity 77 | -~- Delivered >30% IRR for past 15 years 78 | -~- Created Guaranteed Venture Portfolio Loan product, a new way of funding venture companies 79 | **Core Skills**: Financial Modelling, Deal structuring, Strategy development, Investment Analysis 80 | **Management Experience**: Teams up to 40 people, FCA MiFID compliance, professional qualification oversight 81 | 82 | ## Technical Expertise 83 | -~- Full-stack developer specializing in TypeScript, Svelte, and web technologies 84 | -~- Created GVPL Calculator - a sophisticated financial modelling platform 85 | -~- Developed myAImemory - AI memory synchronization tool 86 | -~- Built multiple Model Context Protocol (MCP) frameworks 87 | -~- Systems thinking and practical application to real-world problems 88 | 89 | # Personal Characteristics & Thinking Style 90 | -~- Excellent pattern recognition across different domains 91 | -~- Ability to see multiple perspectives ("windows") that others miss 92 | -~- Strong focus on fairness and equity in systems 93 | -~- Analytical approach to problems with multifaceted thinking 94 | -~- Quick to grasp concepts and translate complex ideas for others 95 | -~- Values transparency and documentation (always keen to see detailed records) 96 | -~- Draws clear boundaries when core principles are at stake 97 | -~- Weighs endurance/principles based on the significance of the issue 98 | -~- Willing to make personal sacrifices to establish precedents or prove concepts 99 | -~- Learning style: develops systematic approaches through direct observation, conversation and reading with a keen eye for evaluating the quality of sources 100 | -~- Incredibly quick to understand incentives and their implications 101 | 102 | # Interests 103 | -~- Rugby (qualified coach and referee) 104 | -~- Chess 105 | -~- Photography 106 | -~- Science and Technology 107 | -~- Snowboarding and Skiing 108 | -~- Golf 109 | -~- Guitar 110 | -~- Audiobooks and literature 111 | -~- Systems thinking and pattern recognition 112 | -~- His children and anything they do 113 | 114 | # Company Information 115 | **New Model Venture Capital**: 116 | -~- Mark Hanington (Managing Director) 117 | -~- James Stephenson (Head of Analysis) 118 | -~- Christian Fallesen (NED) 119 | -~- Keith Morris OBE (Advisor) 120 | -~- Matt Cunningham (Legal Counsel) 121 | -~- Team of 8 including legal, finance, and operations specialists 122 | 123 | ## Current Coding Projects 124 | -~- GVPL Calculator - Financial modelling platform for investment optimization 125 | -~- myAImemory - AI memory synchronization tool (Last modified: March 21, 2025) 126 | -~- myKYCpal - Know Your Client platform (Last modified: March 19, 2025) 127 | -~- serveMyAPI - API service for integrating with AI models 128 | -~- Multiple web applications using Svelte/TypeScript/Vercel/Neon including a Fantasy Rugby App 129 | 130 | # Technical Preferences 131 | ## Refer to this when considering examples and user experience testing 132 | -~- Frontend: Svelte 5 133 | -~- Development: Windsurf IDE 134 | -~- Hosting: Vercel 135 | -~- Database: Neon 136 | -~- Hardware: Mac Mini M4, MacBook Pro, iOS devices (iPhone 15 Pro, iPhone 16, iPad Pro) 137 | -~- Other devices: Apple TV, HomePod mini, HomePod, XReal One, GoPro 13 Hero Black, Nikon D50, Whoop fitness tracking 138 | -~- Visual Aids: uses visual explanations for complex concepts 139 | 140 | # AI & MCP Preferences 141 | ## Use this as a guide when working on technical projects 142 | -~- File Access: If a file is in .gitignore and need to read it, ask for permission to use the filesystem MCP 143 | -~- API credentials/services: Use the serveMyAPI MCP by @Jktfe 144 | -~- Response Style: Concise, UK English spelling, GBP (£) as default currency 145 | -~- Technical Documentation: Maintain "Project Variables" document with details on variables, functions, tables, etc. 146 | -~- AI Tools: Prefer Claude-based systems with pattern recognition capabilities 147 | -~- MCP Framework: Utilize multiple platform synchronization where possible 148 | -~- when creating an MCP it's important to add a proper MCP.js script with JSON-RPC handling 149 | 150 | # Communication Preferences 151 | -~- Respond in English 152 | -~- Use UK English Spellings 153 | -~- Use £ (GBP) as the default currency, if you need to use conversions put them in brackets i.e. £1.10 ($1.80) 154 | -~- Direct and straightforward communication 155 | -~- Appreciation for humour and casual conversation 156 | -~- Values authenticity over formality 157 | -~- Prefers evidence-based discussions 158 | -~- Open to new perspectives but requires logical reasoning 159 | -~- Expects transparency and honesty in professional relationships 160 | -~- Willing to challenge conventional wisdom when it doesn't align with practical outcomes 161 | -~- Respects expertise but will question assumptions that don't match observed reality 162 | 163 | # Available MCPs 164 | ## Use these details combined with serveMyAPI to create mcp config files 165 | 166 | ## Ask which servers to install, from this list; 167 | 168 | -~- "apple-shortcuts" - 1 tool: run_shortcut. Provides natural language access to Apple Shortcuts automation, allowing creation and triggering of macOS shortcuts. 169 | 170 | -~- "brave-search" - 2 tools: brave_web_search and brave_local_search. Enables web searches using Brave's privacy-focused search engine with filtering and customisation options. 171 | 172 | -~- "fastAPItoSVG" - 1 tool: generate_api_diagram. Converts FastAPI documentation to SVG diagrams for visualising API endpoints and schemas. 173 | 174 | -~- "fetch" - 1 tool: fetch. Retrieves content from web URLs with options to extract text, process HTML, and manage content length. 175 | 176 | -~- "filesystem" - 5 tools: ls, cat, write_file, rm, and mkdir. Provides file operations and directory management for local filesystem access. 177 | 178 | -~- "fooocus" - 9 tools: configure_api, check_status, start_server, stop_server, generate_image, get_job_status, get_available_styles, get_available_models, and upscale_or_vary_image. Interface to the Fooocus image generation AI with style presets, various models and upscaling options. 179 | 180 | -~- "google-search" - 1 tool: google_search. Performs web searches using Google's search engine with customisable parameters. 181 | 182 | -~- "localviz" - 6 tools: test_api, manage_api, list_styles, list_aspect_ratios, generate_image, and check_job_status. Local image generation interface for Fooocus with style presets and aspect ratio controls. 183 | 184 | -~- "leonardoAI" - 3 tools: generate_image, get_models, and check_generation. Creates AI-generated images with Leonardo.ai's advanced image generation capabilities. 185 | 186 | -~- "markdownify" - 1 tool: markdownify. Converts content between different formats with a focus on generating clean markdown. 187 | 188 | -~- "neon" - 11 tools: create_project, describe_project, list_projects, delete_project, create_branch, delete_branch, describe_branch, get_connection_string, get_database_tables, describe_table_schema, and run_sql. Manages Neon serverless PostgreSQL databases with branch, migration and query functionality. 189 | 190 | -~- "myai-memory-sync" - 8 tools: get_template, update_template, get_section, update_section, list_platforms, sync_platforms, list_presets, and create_preset. Synchronises and manages AI memory sections and templates across platforms. 191 | 192 | -~- "puppeteer" - 7 tools: navigate, screenshot, evaluate, click, fill, hover, and select. Automates browser interactions for web scraping, testing and automated workflows. 193 | 194 | -~- "sequential-thinking" - 1 tool: sequentialthinking. Facilitates step-by-step problem-solving through structured thought processes. 195 | 196 | -~- "serveMyAPI" - 4 tools: store-api-key, get-api-key, delete-api-key, and list-api-keys. Securely stores and retrieves API keys from the macOS Keychain for use across projects. 197 | 198 | -~- "wcgw" - 1 tool: analyze_code. "What Could Go Wrong" AI-powered code analysis for identifying potential issues and bugs. 199 | 200 | -~- "agentql" - 2 tools: create_agent and query_agent. Integrates with AgentQL to build and manage autonomous agents with various capabilities. 201 | 202 | -~- "mcp-compass" - 2 tools: get_coordinates and get_directions. Navigation and location-based services with mapping and direction capabilities. 203 | 204 | -~- "xcode-server" - 4 tools: list_projects, build_project, run_tests, and deploy_app. Interfaces with Xcode for iOS/macOS development workflows and build processes. 205 | 206 | 207 | ## once selected output the json with this format 208 | ''' 209 | { 210 | "mcpServers": { 211 | [mcpserver details][,] 212 | } 213 | } 214 | ''' 215 | 216 | # the individual mcp server details 217 | 218 | ''' 219 | "fetch": { 220 | "command": "uvx", 221 | "args": [ 222 | "mcp-server-fetch" 223 | ] 224 | } 225 | ''' 226 | 227 | ''' 228 | "filesystem": { 229 | "command": "npx", 230 | "args": [ 231 | "-y", 232 | "@modelcontextprotocol/server-filesystem", 233 | "/Users/jamesking/" 234 | ] 235 | } 236 | ''' 237 | 238 | ''' 239 | "puppeteer": { 240 | "command": "npx", 241 | "args": [ 242 | "-y", 243 | "@modelcontextprotocol/server-puppeteer" 244 | ] 245 | } 246 | ''' 247 | 248 | ''' 249 | "markdownify": { 250 | "command": "node", 251 | "args": [ 252 | "/Users/jamesking/CascadeProjects/markdownify-mcp/dist/index.js" 253 | ], 254 | "env": { 255 | "UV_PATH": "/Users/jamesking/.local/bin/uv" 256 | } 257 | } 258 | ''' 259 | 260 | ''' 261 | "apple-shortcuts": { 262 | "command": "npx", 263 | "args": [ 264 | "/Users/jamesking/CascadeProjects/mcp-server-apple-shortcuts/build/index.js" 265 | ] 266 | } 267 | ''' 268 | 269 | ''' 270 | "brave-search": { 271 | "command": "npx", 272 | "args": [ 273 | "-y", 274 | "@modelcontextprotocol/server-brave-search" 275 | ], 276 | "env": { 277 | "BRAVE_API_KEY": "${await serveMyAPI.getKey('brave_search')}" 278 | } 279 | } 280 | ''' 281 | 282 | ''' 283 | "serveMyAPI": { 284 | "command": "node", 285 | "args": [ 286 | "/Users/jamesking/CascadeProjects/serveMyAPI/dist/index.js" 287 | ] 288 | } 289 | ''' 290 | 291 | ''' 292 | "perplexity-ask": { 293 | "command": "docker", 294 | "args": [ 295 | "run", 296 | "-i", 297 | "--rm", 298 | "-e", 299 | "PERPLEXITY_API_KEY", 300 | "mcp/perplexity-ask" 301 | ], 302 | "env": { 303 | "PERPLEXITY_API_KEY": "${await serveMyAPI.getKey('perplexity')}" 304 | } 305 | } 306 | ''' 307 | 308 | ''' 309 | "google-search": { 310 | "command": "npx", 311 | "args": [ 312 | "-y", 313 | "@adenot/mcp-google-search" 314 | ], 315 | "env": { 316 | "GOOGLE_API_KEY": "${await serveMyAPI.getKey('google_search')}", 317 | "GOOGLE_SEARCH_ENGINE_ID": "${await serveMyAPI.getKey('google_search_engine_id')}" 318 | } 319 | } 320 | ''' 321 | 322 | ''' 323 | "myai-memory-sync": { 324 | "command": "node", 325 | "args": [ 326 | "/Users/jamesking/CascadeProjects/myAImemory/dist/index.js" 327 | ], 328 | "transport":"stdio" 329 | } 330 | ''' 331 | 332 | ''' 333 | "neon": { 334 | "command": "npx", 335 | "args": [ 336 | "-y", 337 | "@neondatabase/mcp-server-neon", 338 | "start", 339 | "${await serveMyAPI.getKey('neon_database')}" 340 | ] 341 | } 342 | ''' 343 | 344 | ''' 345 | "wcgw": { 346 | "command": "uv", 347 | "args": [ 348 | "tool", 349 | "run", 350 | "--from", 351 | "wcgw@latest", 352 | "--python", 353 | "3.12", 354 | "wcgw_mcp" 355 | ] 356 | } 357 | ''' 358 | 359 | ''' 360 | "agentql": { 361 | "command": "npx", 362 | "args": [ 363 | "-y", 364 | "agentql-mcp" 365 | ], 366 | "env": { 367 | "AGENTQL_API_KEY": "${await serveMyAPI.getKey('agentql')}" 368 | } 369 | } 370 | ''' 371 | 372 | ''' 373 | "mcp-compass": { 374 | "command": "npx", 375 | "args": [ 376 | "-y", 377 | "@liuyoshio/mcp-compass" 378 | ] 379 | } 380 | ''' 381 | 382 | ''' 383 | "server-sequential-thinking": { 384 | "command": "npx", 385 | "args": [ 386 | "-y", 387 | "@smithery/cli@latest", 388 | "run", 389 | "@smithery-ai/server-sequential-thinking", 390 | "--config", 391 | "\"{}\"" 392 | ] 393 | } 394 | ''' 395 | 396 | ''' 397 | "fooocus": { 398 | "command": "bash", 399 | "args": [ 400 | "/Users/jamesking/CascadeProjects/localViz/fooocus_mcp_wrapper.sh" 401 | ], 402 | "transport": "stdio", 403 | "functions": [ 404 | { 405 | "name": "configure_api", 406 | "description": "Configure the Fooocus API connection settings with host, port, paths and auto-start options" 407 | }, 408 | { 409 | "name": "check_status", 410 | "description": "Check if the Fooocus API is responding and get version information" 411 | }, 412 | { 413 | "name": "start_server", 414 | "description": "Start the Fooocus API server if it's not already running, with optional paths" 415 | }, 416 | { 417 | "name": "stop_server", 418 | "description": "Stop the Fooocus API server if it was started by this instance" 419 | }, 420 | { 421 | "name": "generate_image", 422 | "description": "Generate images from text prompt using Fooocus with style, seed and resolution options" 423 | }, 424 | { 425 | "name": "get_job_status", 426 | "description": "Get the status of a previously submitted asynchronous generation job" 427 | }, 428 | { 429 | "name": "get_available_styles", 430 | "description": "Get all available style presets from Fooocus" 431 | }, 432 | { 433 | "name": "get_available_models", 434 | "description": "Get all available AI models from Fooocus" 435 | }, 436 | { 437 | "name": "upscale_or_vary_image", 438 | "description": "Upscale or create variations of an existing image with specified parameters" 439 | } 440 | ] 441 | } 442 | ''' 443 | 444 | ''' 445 | "localviz": { 446 | "command": "bash", 447 | "args": [ 448 | "/Users/jamesking/CascadeProjects/localViz-mcp/start-v1.sh" 449 | ], 450 | "transport": "stdio", 451 | "env": { 452 | "FOOOCUS_API_PATH": "/Users/jamesking/CascadeProjects/Fooocus-API", 453 | "FOOOCUS_PATH": "/Users/jamesking/CascadeProjects/Fooocus", 454 | "OUTPUT_DIR": "/Users/jamesking/New Model Dropbox/James King/Air - JK Work/imageGens", 455 | "FOOOCUS_API_URL": "http://127.0.0.1:8888", 456 | "MANAGE_API": "true" 457 | }, 458 | "functions": [ 459 | { 460 | "name": "test_api", 461 | "description": "Test connection to the Fooocus API" 462 | }, 463 | { 464 | "name": "manage_api", 465 | "description": "Manually start or stop the Fooocus API" 466 | }, 467 | { 468 | "name": "list_styles", 469 | "description": "List all available style presets for image generation" 470 | }, 471 | { 472 | "name": "list_aspect_ratios", 473 | "description": "List available aspect ratios for image generation" 474 | }, 475 | { 476 | "name": "generate_image", 477 | "description": "Generate an image based on a text description using Fooocus API. Results will be saved locally." 478 | }, 479 | { 480 | "name": "check_job_status", 481 | "description": "Check the status of an image generation job" 482 | } 483 | ] 484 | } 485 | ''' 486 | 487 | ''' 488 | "leonardoAI": { 489 | "command": "npm", 490 | "args": [ 491 | "start" 492 | ], 493 | "cwd": "/Users/jamesking/CascadeProjects/leoViz", 494 | "transport": "stdio", 495 | "env": { 496 | "LEONARDO_API_KEY": "${await serveMyAPI.getKey('cMax Leonardo API')}" 497 | } 498 | } 499 | ''' --------------------------------------------------------------------------------