#
tokens: 24601/50000 18/19 files (page 1/2)
lines: on (toggle) GitHub
raw markdown copy reset
This is page 1 of 2. Use http://codebase.md/jktfe/servemyapi?lines=true&page={x} to view the full context.

# Directory Structure

```
├── .gitignore
├── build_dmg.sh
├── CLAUDE.md
├── CONTRIBUTING.md
├── Dockerfile
├── examples
│   ├── claude_desktop_config.json
│   └── windsurf_config.json
├── How to improve serveMyAPI.pdf
├── icon.png
├── icon.svg
├── mcp-server-template.md
├── MCP-TypeScript-Readme.md
├── package.json
├── README.md
├── smithery.yaml
├── src
│   ├── cli.js
│   ├── cli.ts
│   ├── index.ts
│   ├── server.ts
│   └── services
│       └── keychain.ts
└── tsconfig.json
```

# Files

--------------------------------------------------------------------------------
/.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 | 
```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
  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
```

--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------

```markdown
 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.
```

--------------------------------------------------------------------------------
/CLAUDE.md:
--------------------------------------------------------------------------------

```markdown
  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: [email protected] | 07793988228
 39 | -~- Personal Contact: [email protected] | 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 | '''
```

--------------------------------------------------------------------------------
/examples/claude_desktop_config.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "mcpServers": {
 3 |     "serveMyAPI": {
 4 |       "command": "node",
 5 |       "args": [
 6 |         "/ABSOLUTE/PATH/TO/servemyapi/dist/index.js"
 7 |       ]
 8 |     }
 9 |   }
10 | }
```

--------------------------------------------------------------------------------
/examples/windsurf_config.json:
--------------------------------------------------------------------------------

```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:
--------------------------------------------------------------------------------

```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 | }
```

--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------

```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:
--------------------------------------------------------------------------------

```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 | <?xml version="1.0" encoding="UTF-8" standalone="no"?>
 2 | <svg width="512" height="512" viewBox="0 0 512 512" fill="none" xmlns="http://www.w3.org/2000/svg">
 3 |   <!-- Background -->
 4 |   <rect width="512" height="512" rx="100" fill="white"/>
 5 |   
 6 |   <!-- Server with API symbol -->
 7 |   <rect x="126" y="156" width="260" height="200" rx="20" fill="#0063B2"/>
 8 |   
 9 |   <!-- Server lights -->
10 |   <circle cx="156" cy="186" r="10" fill="#4CAF50"/>
11 |   <circle cx="186" cy="186" r="10" fill="#FFC107"/>
12 |   <circle cx="216" cy="186" r="10" fill="#F44336"/>
13 |   
14 |   <!-- Server slots -->
15 |   <rect x="146" y="216" width="220" height="20" rx="5" fill="#004884"/>
16 |   <rect x="146" y="246" width="220" height="20" rx="5" fill="#004884"/>
17 |   <rect x="146" y="276" width="220" height="20" rx="5" fill="#004884"/>
18 |   <rect x="146" y="306" width="220" height="20" rx="5" fill="#004884"/>
19 |   
20 |   <!-- API Text -->
21 |   <path d="M176 376H336" stroke="#0063B2" stroke-width="16" stroke-linecap="round"/>
22 |   <path d="M196 350L176 376L196 402" stroke="#0063B2" stroke-width="16" stroke-linecap="round" stroke-linejoin="round"/>
23 |   <path d="M316 350L336 376L316 402" stroke="#0063B2" stroke-width="16" stroke-linecap="round" stroke-linejoin="round"/>
24 |   
25 |   <!-- Shield symbolizing security -->
26 |   <path d="M256 116C290 132 324 140 358 132C358 194 334 250 256 286C178 250 154 194 154 132C188 140 222 132 256 116Z" fill="#00C2B8"/>
27 |   
28 |   <!-- Key symbolizing keychain access -->
29 |   <circle cx="256" cy="156" r="15" fill="white"/>
30 |   <rect x="246" y="156" width="40" height="15" rx="5" fill="white"/>
31 |   <rect x="271" y="156" width="10" height="25" rx="5" fill="white"/>
32 |   <rect x="281" y="166" width="10" height="10" rx="5" fill="white"/>
33 | </svg>
```

--------------------------------------------------------------------------------
/smithery.yaml:
--------------------------------------------------------------------------------

```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
```

--------------------------------------------------------------------------------
/build_dmg.sh:
--------------------------------------------------------------------------------

```bash
  1 | #!/bin/bash
  2 | 
  3 | # Script to package serveMyAPI into a DMG for easy distribution
  4 | # This creates a proper macOS app bundle with menu bar integration
  5 | 
  6 | # Configuration
  7 | APP_NAME="serveMyAPI"
  8 | VERSION="1.0.0"
  9 | BUILD_DIR="build"
 10 | RELEASE_DIR="$BUILD_DIR/release"
 11 | APP_DIR="$RELEASE_DIR/$APP_NAME.app"
 12 | CONTENTS_DIR="$APP_DIR/Contents"
 13 | MACOS_DIR="$CONTENTS_DIR/MacOS"
 14 | RESOURCES_DIR="$CONTENTS_DIR/Resources"
 15 | 
 16 | # Ensure script exits on error
 17 | set -e
 18 | 
 19 | echo "Building $APP_NAME v$VERSION DMG..."
 20 | 
 21 | # Create necessary directories
 22 | mkdir -p "$MACOS_DIR"
 23 | mkdir -p "$RESOURCES_DIR"
 24 | 
 25 | # Create Info.plist file for the app
 26 | cat > "$CONTENTS_DIR/Info.plist" << EOF
 27 | <?xml version="1.0" encoding="UTF-8"?>
 28 | <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
 29 | <plist version="1.0">
 30 | <dict>
 31 |     <key>CFBundleExecutable</key>
 32 |     <string>run.sh</string>
 33 |     <key>CFBundleIconFile</key>
 34 |     <string>AppIcon</string>
 35 |     <key>CFBundleIdentifier</key>
 36 |     <string>com.newmodel.$APP_NAME</string>
 37 |     <key>CFBundleInfoDictionaryVersion</key>
 38 |     <string>6.0</string>
 39 |     <key>CFBundleName</key>
 40 |     <string>$APP_NAME</string>
 41 |     <key>CFBundlePackageType</key>
 42 |     <string>APPL</string>
 43 |     <key>CFBundleShortVersionString</key>
 44 |     <string>$VERSION</string>
 45 |     <key>CFBundleVersion</key>
 46 |     <string>$VERSION</string>
 47 |     <key>LSApplicationCategoryType</key>
 48 |     <string>public.app-category.utilities</string>
 49 |     <key>LSMinimumSystemVersion</key>
 50 |     <string>12.0</string>
 51 |     <key>LSUIElement</key>
 52 |     <true/>
 53 |     <key>NSHumanReadableCopyright</key>
 54 |     <string>Copyright © 2025 James King. All rights reserved.</string>
 55 | </dict>
 56 | </plist>
 57 | EOF
 58 | 
 59 | # Create launcher script
 60 | cat > "$MACOS_DIR/run.sh" << EOF
 61 | #!/bin/bash
 62 | cd "\$(dirname "\$0")/../Resources"
 63 | ./node main.js
 64 | EOF
 65 | 
 66 | # Make it executable
 67 | chmod +x "$MACOS_DIR/run.sh"
 68 | 
 69 | # Copy Node.js executable
 70 | cp "$(which node)" "$RESOURCES_DIR/"
 71 | 
 72 | # Copy application files
 73 | cp -R *.js package.json package-lock.json "$RESOURCES_DIR/"
 74 | mkdir -p "$RESOURCES_DIR/node_modules"
 75 | cp -R node_modules/* "$RESOURCES_DIR/node_modules/"
 76 | 
 77 | # Create README for installation
 78 | cat > "$RESOURCES_DIR/README.txt" << EOF
 79 | serveMyAPI
 80 | ==========
 81 | 
 82 | This application provides system services for myKYCpal.
 83 | It runs in the background as a menu bar application.
 84 | 
 85 | For more information, visit: https://newmodel.vc
 86 | EOF
 87 | 
 88 | # Create DMG
 89 | DMG_PATH="$RELEASE_DIR/$APP_NAME-$VERSION.dmg"
 90 | echo "Creating DMG at $DMG_PATH..."
 91 | 
 92 | # Create temporary directory for DMG contents
 93 | DMG_TEMP="$RELEASE_DIR/dmg_temp"
 94 | mkdir -p "$DMG_TEMP"
 95 | 
 96 | # Copy app to DMG temp folder
 97 | cp -R "$APP_DIR" "$DMG_TEMP/"
 98 | 
 99 | # Create symlink to Applications folder
100 | ln -s /Applications "$DMG_TEMP/Applications"
101 | 
102 | # Create DMG
103 | hdiutil create -volname "$APP_NAME" -srcfolder "$DMG_TEMP" -ov -format UDZO "$DMG_PATH"
104 | 
105 | # Clean up
106 | rm -rf "$DMG_TEMP"
107 | 
108 | echo "Build completed successfully!"
109 | echo "DMG file created at: $DMG_PATH"
```

--------------------------------------------------------------------------------
/src/cli.ts:
--------------------------------------------------------------------------------

```typescript
  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 <command> [options]
 97 | 
 98 | Commands:
 99 |   list                          List all stored API keys
100 |   get <key-name>                Retrieve the value of an API key
101 |   store <key-name> <key-value>  Store a new API key
102 |   add <key-name> <key-value>    Alias for 'store'
103 |   delete <key-name>             Delete an API key
104 |   remove <key-name>             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:
--------------------------------------------------------------------------------

```typescript
  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:
--------------------------------------------------------------------------------

```javascript
  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 <name>: Get a specific API key
 13 | //  - set <name> <value>: Set an API key
 14 | //  - delete <name>: 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 <name>, store <name> <value>, delete <name>');
 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 <name>           Get the value of a specific API key
145 |   store <name> <value>   Set the value of an API key
146 |   delete <name>        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:
--------------------------------------------------------------------------------

```typescript
  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<void> {
 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<void> {
 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<void> {
 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<string | null> {
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<boolean> {
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<string[]> {
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:
--------------------------------------------------------------------------------

```typescript
  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<string, any>();
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 |     <html>
200 |       <head>
201 |         <title>ServeMyAPI</title>
202 |         <style>
203 |           body { font-family: -apple-system, BlinkMacSystemFont, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; }
204 |           h1 { color: #333; }
205 |           p { line-height: 1.6; }
206 |           pre { background: #f5f5f5; padding: 10px; border-radius: 5px; }
207 |         </style>
208 |       </head>
209 |       <body>
210 |         <h1>ServeMyAPI</h1>
211 |         <p>This is a personal MCP server for securely storing and accessing API keys across projects using the macOS Keychain.</p>
212 |         <p>The server exposes the following tools:</p>
213 |         <ul>
214 |           <li><strong>store-api-key</strong> - Store an API key in the keychain</li>
215 |           <li><strong>get-api-key</strong> - Retrieve an API key from the keychain</li>
216 |           <li><strong>delete-api-key</strong> - Delete an API key from the keychain</li>
217 |           <li><strong>list-api-keys</strong> - List all stored API keys</li>
218 |         </ul>
219 |         <p>This server is running with HTTP SSE transport. Connect to /sse for the SSE endpoint and post messages to /messages.</p>
220 |       </body>
221 |     </html>
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 };
```

--------------------------------------------------------------------------------
/MCP-TypeScript-Readme.md:
--------------------------------------------------------------------------------

```markdown
  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<string, any[]>(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.
```
Page 1/2FirstPrevNextLast