#
tokens: 18104/50000 18/19 files (page 1/2)
lines: off (toggle) GitHub
raw markdown copy
This is page 1 of 2. Use http://codebase.md/jktfe/servemyapi?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:
--------------------------------------------------------------------------------

```
# Dependencies
node_modules/
package-lock.json
yarn.lock

# Build output
dist/
build/

# Logs
logs/
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# Environment variables
.env
.env.local
.env.development.local
.env.test.local
.env.production.local

# Editor directories and files
.idea/
.vscode/
*.swp
*.swo

# OS generated files
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db

CLAUDE.md

```

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

```markdown
# ServeMyAPI

[![smithery badge](https://smithery.ai/badge/@Jktfe/servemyapi)](https://smithery.ai/server/@Jktfe/servemyapi)

A personal MCP (Model Context Protocol) server for securely storing and accessing API keys across projects using the macOS Keychain.

> **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.

## Overview

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:

- Store API keys securely (they're never visible in .env files or config files)
- Access the same keys across multiple projects
- Use natural language to store and retrieve keys (when used with LLMs like Claude)
- Provide keys directly to your AI assistant when it needs to access services

## Why ServeMyAPI over .ENV Files?

Using ServeMyAPI instead of traditional .ENV files solves several common problems:

1. **GitHub Security Conflicts**: 
   - .ENV files need to be excluded from Git repositories for security (via .gitignore)
   - This creates a "hidden context" problem where important configuration is invisible to collaborators and LLMs
   - New developers often struggle with setting up the correct environment variables

2. **LLM Integration Challenges**:
   - LLMs like Claude can't directly access your .ENV files due to security constraints
   - When LLMs need API keys to complete tasks, you often need manual workarounds
   - ServeMyAPI lets your AI assistant request keys through natural language

3. **Cross-Project Consistency**:
   - With .ENV files, you typically need to duplicate API keys across multiple projects
   - When keys change, you need to update multiple files
   - ServeMyAPI provides a central storage location accessible from any project

This approach gives you the best of both worlds: secure storage of sensitive credentials without sacrificing visibility and accessibility for your AI tools.

## Features

- Secure storage of API keys in the macOS Keychain
- Simple MCP tools for storing, retrieving, listing, and deleting keys
- Convenient CLI interface for terminal-based key management
- Support for both stdio and HTTP/SSE transports
- Compatible with any MCP client (Claude Desktop, etc.)

## Installation

```bash
# Clone the repository
git clone https://github.com/yourusername/servemyapi.git
cd servemyapi

# Install dependencies
npm install

# Build the project
npm run build
```

## Usage

### CLI Interface

ServeMyAPI comes with a command-line interface for quick key management directly from your terminal:

```bash
# Install the CLI globally
npm run build
npm link

# List all stored API keys
api-key list

# Get a specific API key
api-key get github_token

# Store a new API key
api-key store github_token ghp_123456789abcdefg

# Delete an API key
api-key delete github_token

# Display help
api-key help
```

### Running as a stdio server

This is the simplest way to use ServeMyAPI as an MCP server, especially when working with Claude Desktop:

```bash
npm start
```

### Running as an HTTP server

For applications that require HTTP access:

```bash
node dist/server.js
```

This will start the server on port 3000 (or the port specified in the PORT environment variable).

### Using Smithery

ServeMyAPI is available as a hosted service on [Smithery](https://smithery.ai/server/@Jktfe/servemyapi).

```javascript
import { createTransport } from "@smithery/sdk/transport.js"

const transport = createTransport("https://server.smithery.ai/@Jktfe/servemyapi")

// Create MCP client
import { Client } from "@modelcontextprotocol/sdk/client/index.js"

const client = new Client({
	name: "Test client",
	version: "1.0.0"
})
await client.connect(transport)

// Use the server tools with your LLM application
const tools = await client.listTools()
console.log(`Available tools: ${tools.map(t => t.name).join(", ")}`)
```

For more details, see the [Smithery API documentation](https://smithery.ai/server/@Jktfe/servemyapi/api).

### Configuring MCP Clients

ServeMyAPI works with any MCP-compatible client. Example configuration files are provided in the `examples` directory.

#### Claude Desktop

To use ServeMyAPI with Claude Desktop:

1. Locate or create the Claude Desktop configuration file:
   - **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
   - **Windows**: `%AppData%\Claude\claude_desktop_config.json`

2. Add ServeMyAPI to the `mcpServers` section (you can copy from `examples/claude_desktop_config.json`):
   ```json
   {
     "mcpServers": {
       "serveMyAPI": {
         "command": "node",
         "args": [
           "/ABSOLUTE/PATH/TO/servemyapi/dist/index.js"
         ]
       }
     }
   }
   ```

3. Replace `/ABSOLUTE/PATH/TO/servemyapi` with the actual path to your ServeMyAPI installation.
4. Restart Claude Desktop.

#### Windsurf

To use ServeMyAPI with Windsurf:

1. Open Windsurf editor and navigate to Settings
2. Add ServeMyAPI to your MCP server configuration using the example in `examples/windsurf_config.json`
3. Adapt the paths to your local installation

## MCP Tools

ServeMyAPI exposes the following tools:

### store-api-key

Store an API key in the keychain.

Parameters:
- `name`: The name/identifier for the API key
- `key`: The API key to store

Example (from Claude):
```
Using serveMyAPI, store my API key ABC123XYZ as "OpenAI API Key"
```

### get-api-key

Retrieve an API key from the keychain.

Parameters:
- `name`: The name/identifier of the API key to retrieve

Example (from Claude):
```
Using serveMyAPI, get the API key named "OpenAI API Key"
```

### delete-api-key

Delete an API key from the keychain.

Parameters:
- `name`: The name/identifier of the API key to delete

Example (from Claude):
```
Using serveMyAPI, delete the API key named "OpenAI API Key"
```

### list-api-keys

List all stored API keys.

No parameters required.

Example (from Claude):
```
Using serveMyAPI, list all my stored API keys
```

## Security Notes

- All API keys are stored securely in the macOS Keychain
- Keys are only accessible to the current user
- The keychain requires authentication for access
- No keys are stored in plaintext or logged anywhere

## Roadmap

Future plans for ServeMyAPI include:

- **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.

- **Cross-Platform Support**: Investigating secure credential storage options for Windows and Linux to make ServeMyAPI more widely accessible.

- **Integration with Popular Frameworks**: Providing easy integration with frameworks like Next.js, Express, and others.

- **UI for Key Management**: A simple web interface for managing your stored API keys directly.

Feel free to suggest additional features or contribute to the roadmap by opening an issue or pull request.

## Development

```bash
# Run in development mode with hot reload
npm run dev

# Use the CLI during development
npm run cli list

# Lint the code
npm run lint

# Build for production
npm run build
```

## License

MIT
```

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

```markdown
# Contributing to ServeMyAPI

Thank you for considering contributing to ServeMyAPI! This document provides guidelines and instructions for contributing to this project.

## Code of Conduct

Please be respectful and considerate of others when contributing to this project. We aim to foster an inclusive and welcoming community.

## Getting Started

1. Fork the repository
2. Clone your fork: `git clone https://github.com/yourusername/servemyapi.git`
3. Install dependencies: `npm install`
4. Create a new branch for your changes: `git checkout -b feature/your-feature-name`

## Development Workflow

1. Make your changes
2. Run `npm run lint` to ensure code style consistency
3. Test your changes:
   - For stdio mode: `npm run dev`
   - For HTTP mode: `npm run build && node dist/server.js`
4. Commit your changes with a descriptive commit message
5. Push to your branch: `git push origin feature/your-feature-name`
6. Open a pull request against the main repository

## Pull Request Process

1. Ensure your code passes linting
2. Update the README.md with details of your changes if appropriate
3. Include a clear description of what your changes do and why they should be included
4. Your PR will be reviewed by the maintainers, who may request changes

## Adding New Features

When adding new features:
1. Consider backward compatibility
2. Add appropriate documentation
3. Follow the existing code style

## Future Development Ideas

Some potential areas for contribution:
- Adding support for other secure credential storage systems on different platforms
- Enhancing the web UI for managing keys directly
- Adding authentication for the HTTP server
- Creating client libraries for different programming languages

## License

By contributing to ServeMyAPI, you agree that your contributions will be licensed under the project's MIT license.
```

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

```markdown
# serveMyAPI Project Guide

## Project Purpose
This project aims to create a personal MCP server for securely storing and accessing API keys across projects using the macOS Keychain.

## Build & Commands
- Setup: `npm install`
- Start server: `npm run dev`
- Test: `npm test`
- Lint: `npm run lint`
- Build: `npm run build`

## Code Style Guidelines
- **Formatting**: Follow TypeScript standard practices with 2-space indentation
- **Imports**: Group imports by type (core, third-party, local)
- **Naming**: Use camelCase for variables/functions, PascalCase for classes/interfaces
- **Error Handling**: Use try/catch blocks for error management
- **Security**: Never log or expose API keys in plaintext
- **Documentation**: Document all public functions with JSDoc comments

## Key Technologies
- TypeScript SDK for Model Context Protocol (MCP)
- macOS Keychain API for secure credential storage
- Express.js for API endpoints (if needed)

# myAI Memory

# General Response Style
## Use this in every response
-~- You can be very concise
-~- 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

# Personal Information
## User specific and personal information
-~- Name: James William Peter King (James, JWPK)
-~- Date of Birth: 29.03.1985 (40 years old)
-~- Location: London, UK
-~- Work Contact: [email protected] | 07793988228
-~- Personal Contact: [email protected] | 07515900330 
-~- Family: Wife Roxanne (39), son Fletcher (8), daughter Viola (5) (pronounced Vi'la)

## Childhood & Education Background
-~- Early Years: Grew up at Rendcomb boarding school in the Cotswolds where parents were housemasters
-~- Primary Education: Cirencester County Juniors
-~- Secondary Education:
-~- Kimbolton School (GCSEs and French AS)
-~- Leicester Grammar (3As, 2Bs at A-Level 2003)
-~- Higher Education:
-~- Imperial College (Computer Engineering first year)
-~- Leeds University: BSc Computing (Scholarship) 2:1 (2005-2008)
-~- Professional Qualifications: FCA Authorised principal
-~- Learning Style: Quick to grasp concepts, excellent pattern recognition, quick to consider practical applications and implications 

## Professional Background
**Current Role**: CEO, Founder, and Director at New Model Venture Capital (NMVC, New Model)
-~- Founded New Model in May 2013 to deliver "fast, intelligent capital to high-growth companies"
-~- 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
**Previous Companies**:
-~- Fig VC (Dec 2009-2023) - Founder
-~- U Account (Jan-Jun 2019)
-~- Wazoku (Jan 2011-Jun 2019) - Founder 
-~- Brightsparks (Oct 2012-Jul 2015)
-~- MuJo (Jan 2011-Nov 2014) – Co-Founder
-~- Students Work (Apr 2008-Dec 2009)
-~- KPMG (Sep 2004-Aug 2005)

## Education
-~- BSc Computing (Scholarship) 2:1 Leeds University (2005-2008)
-~- FCA Authorised principal
-~- Previous Education:
-~- Leicester Grammar (3As, 2Bs at A-Level 2003)
-~- Imperial College (Computer Engineering first year)
-~- Kimbolton School (GCSEs and French AS)

## Key Achievements & Skills
-~- Raised hundreds of millions in Debt and Equity
-~- Delivered >30% IRR for past 15 years
-~- Created Guaranteed Venture Portfolio Loan product, a new way of funding venture companies
**Core Skills**: Financial Modelling, Deal structuring, Strategy development, Investment Analysis
**Management Experience**: Teams up to 40 people, FCA MiFID compliance, professional qualification oversight

## Technical Expertise
-~- Full-stack developer specializing in TypeScript, Svelte, and web technologies
-~- Created GVPL Calculator - a sophisticated financial modelling platform
-~- Developed myAImemory - AI memory synchronization tool
-~- Built multiple Model Context Protocol (MCP) frameworks
-~- Systems thinking and practical application to real-world problems

# Personal Characteristics & Thinking Style
-~- Excellent pattern recognition across different domains
-~- Ability to see multiple perspectives ("windows") that others miss
-~- Strong focus on fairness and equity in systems
-~- Analytical approach to problems with multifaceted thinking
-~- Quick to grasp concepts and translate complex ideas for others
-~- Values transparency and documentation (always keen to see detailed records)
-~- Draws clear boundaries when core principles are at stake
-~- Weighs endurance/principles based on the significance of the issue
-~- Willing to make personal sacrifices to establish precedents or prove concepts
-~- Learning style: develops systematic approaches through direct observation, conversation and reading with a keen eye for evaluating the quality of sources
-~- Incredibly quick to understand incentives and their implications

# Interests
-~- Rugby (qualified coach and referee)
-~- Chess
-~- Photography
-~- Science and Technology
-~- Snowboarding and Skiing
-~- Golf
-~- Guitar
-~- Audiobooks and literature
-~- Systems thinking and pattern recognition
-~- His children and anything they do

# Company Information
**New Model Venture Capital**:
-~- Mark Hanington (Managing Director)
-~- James Stephenson (Head of Analysis)
-~- Christian Fallesen (NED)
-~- Keith Morris OBE (Advisor)
-~- Matt Cunningham (Legal Counsel)
-~- Team of 8 including legal, finance, and operations specialists

## Current Coding Projects
-~- GVPL Calculator - Financial modelling platform for investment optimization
-~- myAImemory - AI memory synchronization tool (Last modified: March 21, 2025)
-~- myKYCpal - Know Your Client platform (Last modified: March 19, 2025)
-~- serveMyAPI - API service for integrating with AI models
-~- Multiple web applications using Svelte/TypeScript/Vercel/Neon including a Fantasy Rugby App

# Technical Preferences
## Refer to this when considering examples and user experience testing
-~- Frontend: Svelte 5
-~- Development: Windsurf IDE
-~- Hosting: Vercel
-~- Database: Neon
-~- Hardware: Mac Mini M4, MacBook Pro, iOS devices (iPhone 15 Pro, iPhone 16, iPad Pro)
-~- Other devices: Apple TV, HomePod mini, HomePod, XReal One, GoPro 13 Hero Black, Nikon D50, Whoop fitness tracking
-~- Visual Aids: uses visual explanations for complex concepts

# AI & MCP Preferences
## Use this as a guide when working on technical projects
-~- File Access: If a file is in .gitignore and need to read it, ask for permission to use the filesystem MCP
-~- API credentials/services: Use the serveMyAPI MCP by @Jktfe
-~- Response Style: Concise, UK English spelling, GBP (£) as default currency
-~- Technical Documentation: Maintain "Project Variables" document with details on variables, functions, tables, etc.
-~- AI Tools: Prefer Claude-based systems with pattern recognition capabilities
-~- MCP Framework: Utilize multiple platform synchronization where possible
-~- when creating an MCP it's important to add a proper MCP.js script with JSON-RPC handling

# Communication Preferences
-~- Respond in English 
-~- Use UK English Spellings
-~- Use £ (GBP) as the default currency, if you need to use conversions put them in brackets i.e. £1.10 ($1.80)
-~- Direct and straightforward communication
-~- Appreciation for humour and casual conversation
-~- Values authenticity over formality
-~- Prefers evidence-based discussions
-~- Open to new perspectives but requires logical reasoning
-~- Expects transparency and honesty in professional relationships
-~- Willing to challenge conventional wisdom when it doesn't align with practical outcomes
-~- Respects expertise but will question assumptions that don't match observed reality

# Available MCPs
## Use these details combined with serveMyAPI to create mcp config files

## Ask which servers to install, from this list;

-~- "apple-shortcuts" - 1 tool: run_shortcut. Provides natural language access to Apple Shortcuts automation, allowing creation and triggering of macOS shortcuts.

-~- "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.

-~- "fastAPItoSVG" - 1 tool: generate_api_diagram. Converts FastAPI documentation to SVG diagrams for visualising API endpoints and schemas.

-~- "fetch" - 1 tool: fetch. Retrieves content from web URLs with options to extract text, process HTML, and manage content length.

-~- "filesystem" - 5 tools: ls, cat, write_file, rm, and mkdir. Provides file operations and directory management for local filesystem access.

-~- "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.

-~- "google-search" - 1 tool: google_search. Performs web searches using Google's search engine with customisable parameters.

-~- "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.

-~- "leonardoAI" - 3 tools: generate_image, get_models, and check_generation. Creates AI-generated images with Leonardo.ai's advanced image generation capabilities.

-~- "markdownify" - 1 tool: markdownify. Converts content between different formats with a focus on generating clean markdown.

-~- "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.

-~- "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.

-~- "puppeteer" - 7 tools: navigate, screenshot, evaluate, click, fill, hover, and select. Automates browser interactions for web scraping, testing and automated workflows.

-~- "sequential-thinking" - 1 tool: sequentialthinking. Facilitates step-by-step problem-solving through structured thought processes.

-~- "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.

-~- "wcgw" - 1 tool: analyze_code. "What Could Go Wrong" AI-powered code analysis for identifying potential issues and bugs.

-~- "agentql" - 2 tools: create_agent and query_agent. Integrates with AgentQL to build and manage autonomous agents with various capabilities.

-~- "mcp-compass" - 2 tools: get_coordinates and get_directions. Navigation and location-based services with mapping and direction capabilities.

-~- "xcode-server" - 4 tools: list_projects, build_project, run_tests, and deploy_app. Interfaces with Xcode for iOS/macOS development workflows and build processes.


## once selected output the json with this format
'''
{
  "mcpServers": {
  	[mcpserver details][,]
  	}
}
'''

# the individual mcp server details

'''
"fetch": {
  "command": "uvx",
  "args": [
    "mcp-server-fetch"
  ]
}
'''

'''
"filesystem": {
  "command": "npx",
  "args": [
    "-y",
    "@modelcontextprotocol/server-filesystem",
    "/Users/jamesking/"
  ]
}
'''

'''
"puppeteer": {
  "command": "npx",
  "args": [
    "-y",
    "@modelcontextprotocol/server-puppeteer"
  ]
}
'''

'''
"markdownify": {
  "command": "node",
  "args": [
    "/Users/jamesking/CascadeProjects/markdownify-mcp/dist/index.js"
  ],
  "env": {
    "UV_PATH": "/Users/jamesking/.local/bin/uv"
  }
}
'''

'''
"apple-shortcuts": {
  "command": "npx",
  "args": [
    "/Users/jamesking/CascadeProjects/mcp-server-apple-shortcuts/build/index.js"
  ]
}
'''

'''
"brave-search": {
  "command": "npx",
  "args": [
    "-y",
    "@modelcontextprotocol/server-brave-search"
  ],
  "env": {
    "BRAVE_API_KEY": "${await serveMyAPI.getKey('brave_search')}"
  }
}
'''

'''
"serveMyAPI": {
  "command": "node",
  "args": [
    "/Users/jamesking/CascadeProjects/serveMyAPI/dist/index.js"
  ]
}
'''

'''
"perplexity-ask": {
  "command": "docker",
  "args": [
    "run",
    "-i",
    "--rm",
    "-e",
    "PERPLEXITY_API_KEY",
    "mcp/perplexity-ask"
  ],
  "env": {
    "PERPLEXITY_API_KEY": "${await serveMyAPI.getKey('perplexity')}"
  }
}
'''

'''
"google-search": {
  "command": "npx",
  "args": [
    "-y",
    "@adenot/mcp-google-search"
  ],
  "env": {
    "GOOGLE_API_KEY": "${await serveMyAPI.getKey('google_search')}",
    "GOOGLE_SEARCH_ENGINE_ID": "${await serveMyAPI.getKey('google_search_engine_id')}"
  }
}
'''

'''
  "myai-memory-sync": {
    "command": "node",
    "args": [
      "/Users/jamesking/CascadeProjects/myAImemory/dist/index.js"
    ],
    "transport":"stdio"
  }
'''

'''
"neon": {
  "command": "npx",
  "args": [
    "-y",
    "@neondatabase/mcp-server-neon",
    "start",
    "${await serveMyAPI.getKey('neon_database')}"
  ]
}
'''

'''
"wcgw": {
  "command": "uv",
  "args": [
    "tool",
    "run",
    "--from",
    "wcgw@latest",
    "--python",
    "3.12",
    "wcgw_mcp"
  ]
}
'''

'''
"agentql": {
  "command": "npx",
  "args": [
    "-y",
    "agentql-mcp"
  ],
  "env": {
    "AGENTQL_API_KEY": "${await serveMyAPI.getKey('agentql')}"
  }
}
'''

'''
"mcp-compass": {
  "command": "npx",
  "args": [
    "-y",
    "@liuyoshio/mcp-compass"
  ]
}
'''

'''
"server-sequential-thinking": {
  "command": "npx",
  "args": [
    "-y",
    "@smithery/cli@latest",
    "run",
    "@smithery-ai/server-sequential-thinking",
    "--config",
    "\"{}\""
  ]
}
'''

'''
"fooocus": {
  "command": "bash",
  "args": [
    "/Users/jamesking/CascadeProjects/localViz/fooocus_mcp_wrapper.sh"
  ],
  "transport": "stdio",
  "functions": [
    {
      "name": "configure_api",
      "description": "Configure the Fooocus API connection settings with host, port, paths and auto-start options"
    },
    {
      "name": "check_status",
      "description": "Check if the Fooocus API is responding and get version information"
    },
    {
      "name": "start_server",
      "description": "Start the Fooocus API server if it's not already running, with optional paths"
    },
    {
      "name": "stop_server",
      "description": "Stop the Fooocus API server if it was started by this instance"
    },
    {
      "name": "generate_image",
      "description": "Generate images from text prompt using Fooocus with style, seed and resolution options"
    },
    {
      "name": "get_job_status",
      "description": "Get the status of a previously submitted asynchronous generation job"
    },
    {
      "name": "get_available_styles",
      "description": "Get all available style presets from Fooocus"
    },
    {
      "name": "get_available_models",
      "description": "Get all available AI models from Fooocus"
    },
    {
      "name": "upscale_or_vary_image",
      "description": "Upscale or create variations of an existing image with specified parameters"
    }
  ]
}
'''

'''
"localviz": {
  "command": "bash",
  "args": [
    "/Users/jamesking/CascadeProjects/localViz-mcp/start-v1.sh"
  ],
  "transport": "stdio",
  "env": {
    "FOOOCUS_API_PATH": "/Users/jamesking/CascadeProjects/Fooocus-API",
    "FOOOCUS_PATH": "/Users/jamesking/CascadeProjects/Fooocus",
    "OUTPUT_DIR": "/Users/jamesking/New Model Dropbox/James King/Air - JK Work/imageGens",
    "FOOOCUS_API_URL": "http://127.0.0.1:8888",
    "MANAGE_API": "true"
  },
  "functions": [
    {
      "name": "test_api",
      "description": "Test connection to the Fooocus API"
    },
    {
      "name": "manage_api",
      "description": "Manually start or stop the Fooocus API"
    },
    {
      "name": "list_styles",
      "description": "List all available style presets for image generation"
    },
    {
      "name": "list_aspect_ratios",
      "description": "List available aspect ratios for image generation"
    },
    {
      "name": "generate_image",
      "description": "Generate an image based on a text description using Fooocus API. Results will be saved locally."
    },
    {
      "name": "check_job_status",
      "description": "Check the status of an image generation job"
    }
  ]
}
'''

'''
"leonardoAI": {
  "command": "npm",
  "args": [
    "start"
  ],
  "cwd": "/Users/jamesking/CascadeProjects/leoViz",
  "transport": "stdio",
  "env": {
    "LEONARDO_API_KEY": "${await serveMyAPI.getKey('cMax Leonardo API')}"
  }
}
'''
```

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

```json
{
  "mcpServers": {
    "serveMyAPI": {
      "command": "node",
      "args": [
        "/ABSOLUTE/PATH/TO/servemyapi/dist/index.js"
      ]
    }
  }
}
```

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

```json
{
  "mcpServers": {
    "serveMyAPI": {
      "command": "node",
      "args": [
        "/ABSOLUTE/PATH/TO/servemyapi/dist/index.js"
      ]
    }
  }
}
```

--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------

```json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "esModuleInterop": true,
    "strict": true,
    "outDir": "dist",
    "rootDir": "src",
    "sourceMap": true,
    "declaration": true,
    "declarationMap": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"]
}
```

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

```dockerfile
FROM node:20-slim

# Set environment variable to indicate Docker environment
ENV DOCKER_ENV=true
ENV STORAGE_DIR=/app/data

WORKDIR /app

# Copy package files and install dependencies
COPY package*.json ./
RUN npm install

# Copy source code
COPY . .

# Build the TypeScript code
RUN npm run build

# Create data directory for file-based storage
RUN mkdir -p /app/data && chmod 777 /app/data

# Expose port (if using HTTP server version)
EXPOSE 3000

# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
  CMD curl -f http://localhost:3000/ || exit 1

# Set the entry command
CMD ["node", "dist/index.js"]

# Add a prominent note about this MCP server's intended use
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."
LABEL org.opencontainers.image.authors="James King"
LABEL org.opencontainers.image.url="https://github.com/Jktfe/serveMyAPI"
```

--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------

```json
{
  "name": "servemyapi",
  "version": "1.0.0",
  "description": "Personal MCP server for securely storing and accessing API keys across projects using the macOS Keychain",
  "type": "module",
  "main": "dist/index.js",
  "bin": {
    "api-key": "dist/cli.js"
  },
  "scripts": {
    "build": "tsc",
    "start": "node dist/index.js",
    "dev": "nodemon --watch 'src/**/*.ts' --exec 'ts-node' src/index.ts",
    "lint": "eslint 'src/**/*.ts'",
    "test": "echo \"Error: no test specified\" && exit 1",
    "cli": "node dist/cli.js"
  },
  "keywords": ["mcp", "api", "keychain", "macos"],
  "author": "James King",
  "license": "ISC",
  "dependencies": {
    "@modelcontextprotocol/sdk": "^1.7.0",
    "@types/express": "^5.0.0",
    "@types/node": "^22.13.10",
    "express": "^5.0.1",
    "keytar": "^7.9.0",
    "ts-node": "^10.9.2",
    "typescript": "^5.8.2",
    "zod": "^3.24.2"
  },
  "devDependencies": {
    "@typescript-eslint/eslint-plugin": "^8.26.1",
    "@typescript-eslint/parser": "^8.26.1",
    "eslint": "^9.22.0",
    "nodemon": "^3.1.9"
  }
}

```

--------------------------------------------------------------------------------
/icon.svg:
--------------------------------------------------------------------------------

```
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="512" height="512" viewBox="0 0 512 512" fill="none" xmlns="http://www.w3.org/2000/svg">
  <!-- Background -->
  <rect width="512" height="512" rx="100" fill="white"/>
  
  <!-- Server with API symbol -->
  <rect x="126" y="156" width="260" height="200" rx="20" fill="#0063B2"/>
  
  <!-- Server lights -->
  <circle cx="156" cy="186" r="10" fill="#4CAF50"/>
  <circle cx="186" cy="186" r="10" fill="#FFC107"/>
  <circle cx="216" cy="186" r="10" fill="#F44336"/>
  
  <!-- Server slots -->
  <rect x="146" y="216" width="220" height="20" rx="5" fill="#004884"/>
  <rect x="146" y="246" width="220" height="20" rx="5" fill="#004884"/>
  <rect x="146" y="276" width="220" height="20" rx="5" fill="#004884"/>
  <rect x="146" y="306" width="220" height="20" rx="5" fill="#004884"/>
  
  <!-- API Text -->
  <path d="M176 376H336" stroke="#0063B2" stroke-width="16" stroke-linecap="round"/>
  <path d="M196 350L176 376L196 402" stroke="#0063B2" stroke-width="16" stroke-linecap="round" stroke-linejoin="round"/>
  <path d="M316 350L336 376L316 402" stroke="#0063B2" stroke-width="16" stroke-linecap="round" stroke-linejoin="round"/>
  
  <!-- Shield symbolizing security -->
  <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"/>
  
  <!-- Key symbolizing keychain access -->
  <circle cx="256" cy="156" r="15" fill="white"/>
  <rect x="246" y="156" width="40" height="15" rx="5" fill="white"/>
  <rect x="271" y="156" width="10" height="25" rx="5" fill="white"/>
  <rect x="281" y="166" width="10" height="10" rx="5" fill="white"/>
</svg>
```

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

```yaml
# Smithery configuration for ServeMyAPI
# Note: This MCP server is macOS-only due to its dependency on macOS Keychain

startCommand:
  type: stdio
  configSchema:
    type: object
    properties: {}
    additionalProperties: false
  commandFunction: |
    function(config) {
      // This is a macOS-only service that uses the macOS Keychain
      // The container will start but will not function correctly on non-macOS systems
      return {
        command: "node",
        args: ["dist/index.js"],
        env: {
          "NODE_ENV": "production"
        }
      };
    }

tools:
  store-api-key:
    name: "store-api-key"
    description: "Store an API key securely in the keychain"
    parameters:
      $schema: "http://json-schema.org/draft-07/schema#"
      type: object
      additionalProperties: false
      properties:
        name:
          type: string
          minLength: 1
          description: "The name/identifier for the API key"
        key:
          type: string
          minLength: 1
          description: "The API key to store"
      required: ["name", "key"]
  get-api-key:
    name: "get-api-key"
    description: "Retrieve an API key from the keychain"
    parameters:
      $schema: "http://json-schema.org/draft-07/schema#"
      type: object
      additionalProperties: false
      properties:
        name:
          type: string
          minLength: 1
          description: "The name/identifier of the API key to retrieve"
      required: ["name"]
  delete-api-key:
    name: "delete-api-key"
    description: "Delete an API key from the keychain"
    parameters:
      $schema: "http://json-schema.org/draft-07/schema#"
      type: object
      additionalProperties: false
      properties:
        name:
          type: string
          minLength: 1
          description: "The name/identifier of the API key to delete"
      required: ["name"]
  list-api-keys:
    name: "list-api-keys"
    description: "List all stored API keys"
    parameters:
      $schema: "http://json-schema.org/draft-07/schema#"
      type: object
      additionalProperties: false
      properties: {}

build:
  dockerfile: Dockerfile
  dockerBuildPath: "."

# This comment explains that the service is macOS-only
# While the Dockerfile and smithery.yaml enable deployment compatibility,
# the service depends on macOS Keychain and will not function on other platforms
```

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

```bash
#!/bin/bash

# Script to package serveMyAPI into a DMG for easy distribution
# This creates a proper macOS app bundle with menu bar integration

# Configuration
APP_NAME="serveMyAPI"
VERSION="1.0.0"
BUILD_DIR="build"
RELEASE_DIR="$BUILD_DIR/release"
APP_DIR="$RELEASE_DIR/$APP_NAME.app"
CONTENTS_DIR="$APP_DIR/Contents"
MACOS_DIR="$CONTENTS_DIR/MacOS"
RESOURCES_DIR="$CONTENTS_DIR/Resources"

# Ensure script exits on error
set -e

echo "Building $APP_NAME v$VERSION DMG..."

# Create necessary directories
mkdir -p "$MACOS_DIR"
mkdir -p "$RESOURCES_DIR"

# Create Info.plist file for the app
cat > "$CONTENTS_DIR/Info.plist" << EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>CFBundleExecutable</key>
    <string>run.sh</string>
    <key>CFBundleIconFile</key>
    <string>AppIcon</string>
    <key>CFBundleIdentifier</key>
    <string>com.newmodel.$APP_NAME</string>
    <key>CFBundleInfoDictionaryVersion</key>
    <string>6.0</string>
    <key>CFBundleName</key>
    <string>$APP_NAME</string>
    <key>CFBundlePackageType</key>
    <string>APPL</string>
    <key>CFBundleShortVersionString</key>
    <string>$VERSION</string>
    <key>CFBundleVersion</key>
    <string>$VERSION</string>
    <key>LSApplicationCategoryType</key>
    <string>public.app-category.utilities</string>
    <key>LSMinimumSystemVersion</key>
    <string>12.0</string>
    <key>LSUIElement</key>
    <true/>
    <key>NSHumanReadableCopyright</key>
    <string>Copyright © 2025 James King. All rights reserved.</string>
</dict>
</plist>
EOF

# Create launcher script
cat > "$MACOS_DIR/run.sh" << EOF
#!/bin/bash
cd "\$(dirname "\$0")/../Resources"
./node main.js
EOF

# Make it executable
chmod +x "$MACOS_DIR/run.sh"

# Copy Node.js executable
cp "$(which node)" "$RESOURCES_DIR/"

# Copy application files
cp -R *.js package.json package-lock.json "$RESOURCES_DIR/"
mkdir -p "$RESOURCES_DIR/node_modules"
cp -R node_modules/* "$RESOURCES_DIR/node_modules/"

# Create README for installation
cat > "$RESOURCES_DIR/README.txt" << EOF
serveMyAPI
==========

This application provides system services for myKYCpal.
It runs in the background as a menu bar application.

For more information, visit: https://newmodel.vc
EOF

# Create DMG
DMG_PATH="$RELEASE_DIR/$APP_NAME-$VERSION.dmg"
echo "Creating DMG at $DMG_PATH..."

# Create temporary directory for DMG contents
DMG_TEMP="$RELEASE_DIR/dmg_temp"
mkdir -p "$DMG_TEMP"

# Copy app to DMG temp folder
cp -R "$APP_DIR" "$DMG_TEMP/"

# Create symlink to Applications folder
ln -s /Applications "$DMG_TEMP/Applications"

# Create DMG
hdiutil create -volname "$APP_NAME" -srcfolder "$DMG_TEMP" -ov -format UDZO "$DMG_PATH"

# Clean up
rm -rf "$DMG_TEMP"

echo "Build completed successfully!"
echo "DMG file created at: $DMG_PATH"
```

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

```typescript
#!/usr/bin/env node

import keychain from './services/keychain.js';

// Get the command line arguments
const args = process.argv.slice(2);
const command = args[0]?.toLowerCase();

async function main() {
  try {
    switch (command) {
      case 'list':
        // List all API keys
        const keys = await keychain.listKeys();
        if (keys.length === 0) {
          console.log('No API keys found.');
        } else {
          console.log('Stored API keys:');
          keys.forEach(key => console.log(` - ${key}`));
        }
        break;
        
      case 'get':
        // Get an API key by name
        const keyName = args[1];
        if (!keyName) {
          console.error('Error: Please provide a key name.');
          printUsage();
          process.exit(1);
        }
        
        const value = await keychain.getKey(keyName);
        if (value) {
          console.log(`${keyName}: ${value}`);
        } else {
          console.error(`Error: Key '${keyName}' not found.`);
          process.exit(1);
        }
        break;
        
      case 'store':
      case 'add':
        // Store an API key
        const storeName = args[1];
        const storeValue = args[2];
        
        if (!storeName || !storeValue) {
          console.error('Error: Please provide both a key name and value.');
          printUsage();
          process.exit(1);
        }
        
        await keychain.storeKey(storeName, storeValue);
        console.log(`Key '${storeName}' stored successfully.`);
        break;
        
      case 'delete':
      case 'remove':
        // Delete an API key
        const deleteName = args[1];
        
        if (!deleteName) {
          console.error('Error: Please provide a key name to delete.');
          printUsage();
          process.exit(1);
        }
        
        const deleted = await keychain.deleteKey(deleteName);
        if (deleted) {
          console.log(`Key '${deleteName}' deleted successfully.`);
        } else {
          console.error(`Error: Failed to delete key '${deleteName}'. Key may not exist.`);
          process.exit(1);
        }
        break;
        
      case '--help':
      case '-h':
      case 'help':
        printUsage();
        break;
        
      default:
        console.error(`Error: Unknown command '${command}'.`);
        printUsage();
        process.exit(1);
    }
  } catch (error) {
    console.error('Error:', error instanceof Error ? error.message : String(error));
    process.exit(1);
  }
}

function printUsage() {
  console.log(`
Usage: api-key <command> [options]

Commands:
  list                          List all stored API keys
  get <key-name>                Retrieve the value of an API key
  store <key-name> <key-value>  Store a new API key
  add <key-name> <key-value>    Alias for 'store'
  delete <key-name>             Delete an API key
  remove <key-name>             Alias for 'delete'
  help                          Show this help message

Examples:
  api-key list
  api-key get github_token
  api-key store github_token ghp_123456789abcdefg
  api-key delete github_token
`);
}

main().catch(err => {
  console.error('Error:', err instanceof Error ? err.message : String(err));
  process.exit(1);
});
```

--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------

```typescript
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import keychainService from "./services/keychain.js";
import keytar from 'keytar';

// Create an MCP server
const server = new McpServer({
  name: "ServeMyAPI",
  version: "1.0.0"
});

// Tool to store an API key
server.tool(
  "store-api-key",
  {
    name: z.string().min(1).describe("The name/identifier for the API key"),
    key: z.string().min(1).describe("The API key to store"),
  },
  async ({ name, key }) => {
    try {
      await keychainService.storeKey(name, key);
      return {
        content: [{ 
          type: "text", 
          text: `Successfully stored API key with name: ${name}` 
        }]
      };
    } catch (error) {
      return {
        content: [{ 
          type: "text", 
          text: `Error storing API key: ${(error as Error).message}` 
        }],
        isError: true
      };
    }
  }
);

// Tool to retrieve an API key
server.tool(
  "get-api-key",
  {
    name: z.string().min(1).describe("The name/identifier of the API key to retrieve"),
  },
  async ({ name }) => {
    try {
      const key = await keychainService.getKey(name);
      
      if (!key) {
        return {
          content: [{ 
            type: "text", 
            text: `No API key found with name: ${name}` 
          }],
          isError: true
        };
      }
      
      return {
        content: [{ 
          type: "text", 
          text: key
        }]
      };
    } catch (error) {
      return {
        content: [{ 
          type: "text", 
          text: `Error retrieving API key: ${(error as Error).message}` 
        }],
        isError: true
      };
    }
  }
);

// Tool to delete an API key
server.tool(
  "delete-api-key",
  {
    name: z.string().min(1).describe("The name/identifier of the API key to delete"),
  },
  async ({ name }) => {
    try {
      const success = await keychainService.deleteKey(name);
      
      if (!success) {
        return {
          content: [{ 
            type: "text", 
            text: `No API key found with name: ${name}` 
          }],
          isError: true
        };
      }
      
      return {
        content: [{ 
          type: "text", 
          text: `Successfully deleted API key with name: ${name}` 
        }]
      };
    } catch (error) {
      return {
        content: [{ 
          type: "text", 
          text: `Error deleting API key: ${(error as Error).message}` 
        }],
        isError: true
      };
    }
  }
);

// Tool to list all stored API keys
server.tool(
  "list-api-keys",
  {},
  async () => {
    try {
      const keys = await keychainService.listKeys();
      
      if (keys.length === 0) {
        return {
          content: [{ 
            type: "text", 
            text: "No API keys found" 
          }]
        };
      }
      
      return {
        content: [{ 
          type: "text", 
          text: `Available API keys:\n${keys.join("\n")}` 
        }]
      };
    } catch (error) {
      return {
        content: [{ 
          type: "text", 
          text: `Error listing API keys: ${(error as Error).message}` 
        }],
        isError: true
      };
    }
  }
);

// Start receiving messages on stdin and sending messages on stdout
const transport = new StdioServerTransport();
server.connect(transport).then(() => {
  console.error("ServeMyAPI MCP server is running...");
}).catch(error => {
  console.error("Error starting ServeMyAPI MCP server:", error);
});
```

--------------------------------------------------------------------------------
/src/cli.js:
--------------------------------------------------------------------------------

```javascript
#!/usr/bin/env node

import { Client } from '@modelcontextprotocol/sdk/client';
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
import { spawn } from 'child_process';
import path from 'path';
import { fileURLToPath } from 'url';

// Create an MCP client that connects to the ServeMyAPI server
// Usage:
//  - list: List all API keys
//  - get <name>: Get a specific API key
//  - set <name> <value>: Set an API key
//  - delete <name>: Delete an API key
async function main() {
  // Start the server process
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
  const serverPath = path.join(__dirname, 'server.js');
  
  console.log('Starting ServeMyAPI server...');
  
  // We'll let the StdioClientTransport handle the process management
  // No need to spawn the process manually
  
  // Create a transport that connects to the local server process
  const transport = new StdioClientTransport({
    command: 'node',
    args: [serverPath]
  });
  
  // Create a new MCP client with name and version
  const client = new Client("ServeMyAPIClient", "1.0.0");
  
  try {
    // Connect to the server using the transport
    await client.connect(transport);
    console.log('Connected to ServeMyAPI server');
    
    // Parse command line arguments
    const args = process.argv.slice(2);
    const command = args[0] || 'list';
    
    let result;
    
    switch(command) {
      case 'list':
        console.log('Listing all API keys...');
        result = await client.callTool({
          name: 'list-api-keys',
          arguments: {}
        });
        break;
      
      case 'get':
        const keyName = args[1];
        if (!keyName) {
          console.error('Error: Key name is required for get command');
          process.exit(1);
        }
        console.log(`Getting API key: ${keyName}`);
        result = await client.callTool({
          name: 'get-api-key',
          arguments: { name: keyName }
        });
        break;
        
      case 'store':
        const setKeyName = args[1];
        const keyValue = args[2];
        if (!setKeyName || !keyValue) {
          console.error('Error: Both key name and value are required for store command');
          process.exit(1);
        }
        console.log(`Setting API key: ${setKeyName}`);
        result = await client.callTool({
          name: 'store-api-key',
          arguments: { name: setKeyName, key: keyValue }
        });
        break;
        
      case 'delete':
        const deleteKeyName = args[1];
        if (!deleteKeyName) {
          console.error('Error: Key name is required for delete command');
          process.exit(1);
        }
        console.log(`Deleting API key: ${deleteKeyName}`);
        result = await client.callTool({
          name: 'delete-api-key',
          arguments: { name: deleteKeyName }
        });
        break;
        
      default:
        console.error(`Unknown command: ${command}`);
        console.log('Available commands: list, get <name>, store <name> <value>, delete <name>');
        process.exit(1);
    }
    
    // Display the results
    // Display the results based on the command
    
    if (result.content && result.content.length > 0) {
      const textContent = result.content.find(item => item.type === 'text');
      
      if (textContent && textContent.text) {
        if (command === 'list') {
          console.log('\nAvailable API Keys:');
          if (textContent.text === 'No API keys found') {
            console.log('No API keys found');
          } else {
            // Split the keys by newline and display them
            const keys = textContent.text.replace('Available API keys:\n', '').split('\n');
            keys.forEach(key => {
              console.log(`- ${key}`);
            });
          }
        } else {
          // For other commands, just display the text content
          console.log(`\nResult: ${textContent.text}`);
        }
      }
    } else {
      console.log('No data returned from the server');
    }
  } catch (error) {
    console.error('Error:', error.message);
    process.exit(1);
  } finally {
    // Disconnect from the server
    await client.disconnect();
    // Transport will handle closing the server process
  }
}

// Display usage information
function printUsage() {
  console.log(`
ServeMyAPI CLI Usage:
  node cli.js [command] [options]

Commands:
  list                 List all API keys (default)
  get <name>           Get the value of a specific API key
  store <name> <value>   Set the value of an API key
  delete <name>        Delete an API key

Examples:
  node cli.js list
  node cli.js get myApiKey
  node cli.js store myApiKey abc123def456
  node cli.js delete myApiKey
`);
}

// Check if help flag is present
if (process.argv.includes('--help') || process.argv.includes('-h')) {
  printUsage();
  process.exit(0);
}

// Run the main function
main();

```

--------------------------------------------------------------------------------
/src/services/keychain.ts:
--------------------------------------------------------------------------------

```typescript
import keytar from 'keytar';
import fs from 'fs';
import path from 'path';

const SERVICE_NAME = 'serveMyAPI';
const PERMISSION_MARKER = '_permission_granted';
const STORAGE_DIR = process.env.STORAGE_DIR || '/app/data';
const IS_DOCKER = process.env.DOCKER_ENV === 'true';

/**
 * Service for securely storing and retrieving API keys
 * - Uses macOS Keychain on macOS systems
 * - Falls back to file-based storage in Docker environments
 */
export class KeychainService {
  private hasStoredPermissionMarker = false;

  constructor() {
    // Check for permission marker on initialization
    if (!IS_DOCKER) {
      this.checkPermissionMarker();
    } else {
      this.ensureStorageDirectory();
    }
  }

  /**
   * Ensure the storage directory exists for Docker environments
   */
  private ensureStorageDirectory(): void {
    if (IS_DOCKER) {
      try {
        if (!fs.existsSync(STORAGE_DIR)) {
          fs.mkdirSync(STORAGE_DIR, { recursive: true });
        }
      } catch (error) {
        console.error('Error creating storage directory:', error);
      }
    }
  }

  /**
   * Check if the permission marker exists, which indicates
   * that the user has previously granted permission
   */
  private async checkPermissionMarker(): Promise<void> {
    if (IS_DOCKER) return;
    
    try {
      const marker = await keytar.getPassword(SERVICE_NAME, PERMISSION_MARKER);
      this.hasStoredPermissionMarker = !!marker;
      
      if (!this.hasStoredPermissionMarker) {
        // If no marker exists, create one to consolidate permission requests
        await keytar.setPassword(SERVICE_NAME, PERMISSION_MARKER, 'true');
        this.hasStoredPermissionMarker = true;
      }
    } catch (error) {
      console.error('Error checking permission marker:', error);
    }
  }

  /**
   * Store an API key
   * @param name The name/identifier for the API key
   * @param key The API key to store
   * @returns Promise that resolves when the key is stored
   */
  async storeKey(name: string, key: string): Promise<void> {
    if (IS_DOCKER) {
      return this.storeKeyFile(name, key);
    }
    
    // Ensure permission marker exists before storing key
    if (!this.hasStoredPermissionMarker) {
      await this.checkPermissionMarker();
    }
    return keytar.setPassword(SERVICE_NAME, name, key);
  }

  /**
   * Store an API key in a file (Docker fallback)
   */
  private async storeKeyFile(name: string, key: string): Promise<void> {
    try {
      const filePath = path.join(STORAGE_DIR, `${name}.key`);
      fs.writeFileSync(filePath, key, { encoding: 'utf8' });
    } catch (error) {
      console.error(`Error storing key ${name} in file:`, error);
      throw error;
    }
  }

  /**
   * Retrieve an API key
   * @param name The name/identifier of the API key to retrieve
   * @returns Promise that resolves with the API key or null if not found
   */
  async getKey(name: string): Promise<string | null> {
    if (IS_DOCKER) {
      return this.getKeyFile(name);
    }
    
    // Ensure permission marker exists before retrieving key
    if (!this.hasStoredPermissionMarker) {
      await this.checkPermissionMarker();
    }
    return keytar.getPassword(SERVICE_NAME, name);
  }

  /**
   * Retrieve an API key from a file (Docker fallback)
   */
  private getKeyFile(name: string): string | null {
    try {
      const filePath = path.join(STORAGE_DIR, `${name}.key`);
      if (fs.existsSync(filePath)) {
        return fs.readFileSync(filePath, { encoding: 'utf8' });
      }
      return null;
    } catch (error) {
      console.error(`Error retrieving key ${name} from file:`, error);
      return null;
    }
  }

  /**
   * Delete an API key
   * @param name The name/identifier of the API key to delete
   * @returns Promise that resolves with true if deleted, false otherwise
   */
  async deleteKey(name: string): Promise<boolean> {
    if (IS_DOCKER) {
      return this.deleteKeyFile(name);
    }
    
    // Ensure permission marker exists before deleting key
    if (!this.hasStoredPermissionMarker) {
      await this.checkPermissionMarker();
    }
    return keytar.deletePassword(SERVICE_NAME, name);
  }

  /**
   * Delete an API key file (Docker fallback)
   */
  private deleteKeyFile(name: string): boolean {
    try {
      const filePath = path.join(STORAGE_DIR, `${name}.key`);
      if (fs.existsSync(filePath)) {
        fs.unlinkSync(filePath);
        return true;
      }
      return false;
    } catch (error) {
      console.error(`Error deleting key ${name} file:`, error);
      return false;
    }
  }

  /**
   * List all stored API keys
   * @returns Promise that resolves with an array of key names
   */
  async listKeys(): Promise<string[]> {
    if (IS_DOCKER) {
      return this.listKeyFiles();
    }
    
    // Ensure permission marker exists before listing keys
    if (!this.hasStoredPermissionMarker) {
      await this.checkPermissionMarker();
    }
    
    const credentials = await keytar.findCredentials(SERVICE_NAME);
    // Filter out the permission marker from the list of keys
    return credentials
      .map(cred => cred.account)
      .filter(account => account !== PERMISSION_MARKER);
  }

  /**
   * List all stored API key files (Docker fallback)
   */
  private listKeyFiles(): string[] {
    try {
      const files = fs.readdirSync(STORAGE_DIR);
      return files
        .filter(file => file.endsWith('.key'))
        .map(file => file.replace(/\.key$/, ''));
    } catch (error) {
      console.error('Error listing key files:', error);
      return [];
    }
  }
}

export default new KeychainService();
```

--------------------------------------------------------------------------------
/src/server.ts:
--------------------------------------------------------------------------------

```typescript
import express from 'express';
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
import { z } from "zod";
import keychainService from "./services/keychain.js";

// Create an MCP server
const server = new McpServer({
  name: "ServeMyAPI",
  version: "1.0.0"
});

// Tool to store an API key
server.tool(
  "store-api-key",
  {
    name: z.string().min(1).describe("The name/identifier for the API key"),
    key: z.string().min(1).describe("The API key to store"),
  },
  async ({ name, key }) => {
    try {
      await keychainService.storeKey(name, key);
      return {
        content: [{ 
          type: "text", 
          text: `Successfully stored API key with name: ${name}` 
        }]
      };
    } catch (error) {
      return {
        content: [{ 
          type: "text", 
          text: `Error storing API key: ${(error as Error).message}` 
        }],
        isError: true
      };
    }
  }
);

// Tool to retrieve an API key
server.tool(
  "get-api-key",
  {
    name: z.string().min(1).describe("The name/identifier of the API key to retrieve"),
  },
  async ({ name }) => {
    try {
      const key = await keychainService.getKey(name);
      
      if (!key) {
        return {
          content: [{ 
            type: "text", 
            text: `No API key found with name: ${name}` 
          }],
          isError: true
        };
      }
      
      return {
        content: [{ 
          type: "text", 
          text: key
        }]
      };
    } catch (error) {
      return {
        content: [{ 
          type: "text", 
          text: `Error retrieving API key: ${(error as Error).message}` 
        }],
        isError: true
      };
    }
  }
);

// Tool to delete an API key
server.tool(
  "delete-api-key",
  {
    name: z.string().min(1).describe("The name/identifier of the API key to delete"),
  },
  async ({ name }) => {
    try {
      const success = await keychainService.deleteKey(name);
      
      if (!success) {
        return {
          content: [{ 
            type: "text", 
            text: `No API key found with name: ${name}` 
          }],
          isError: true
        };
      }
      
      return {
        content: [{ 
          type: "text", 
          text: `Successfully deleted API key with name: ${name}` 
        }]
      };
    } catch (error) {
      return {
        content: [{ 
          type: "text", 
          text: `Error deleting API key: ${(error as Error).message}` 
        }],
        isError: true
      };
    }
  }
);

// Tool to list all stored API keys
server.tool(
  "list-api-keys",
  {},
  async () => {
    try {
      const keys = await keychainService.listKeys();
      
      if (keys.length === 0) {
        return {
          content: [{ 
            type: "text", 
            text: "No API keys found" 
          }]
        };
      }
      
      return {
        content: [{ 
          type: "text", 
          text: `Available API keys:\n${keys.join("\n")}` 
        }]
      };
    } catch (error) {
      return {
        content: [{ 
          type: "text", 
          text: `Error listing API keys: ${(error as Error).message}` 
        }],
        isError: true
      };
    }
  }
);

// Set up Express app for HTTP transport
const app = express();
const port = process.env.PORT || 3000;

// Store active transports
const activeTransports = new Map<string, any>();

app.get("/sse", async (req, res) => {
  const id = Date.now().toString();
  const transport = new SSEServerTransport("/messages", res);
  
  activeTransports.set(id, transport);
  
  // Set headers for SSE
  res.setHeader('Content-Type', 'text/event-stream');
  res.setHeader('Cache-Control', 'no-cache');
  res.setHeader('Connection', 'keep-alive');
  
  // Handle client disconnect
  req.on('close', () => {
    activeTransports.delete(id);
  });
  
  await server.connect(transport);
});

app.post("/messages", express.json(), (req, res) => {
  // Get the last transport - in a production app, you'd want to maintain sessions
  const lastTransportId = Array.from(activeTransports.keys()).pop();
  
  if (!lastTransportId) {
    res.status(400).json({ error: "No active connections" });
    return;
  }
  
  const transport = activeTransports.get(lastTransportId);
  transport.handlePostMessage(req, res).catch((error: Error) => {
    console.error("Error handling message:", error);
    if (!res.headersSent) {
      res.status(500).json({ error: "Internal server error" });
    }
  });
});

// Simple home page
app.get("/", (req, res) => {
  res.send(`
    <html>
      <head>
        <title>ServeMyAPI</title>
        <style>
          body { font-family: -apple-system, BlinkMacSystemFont, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; }
          h1 { color: #333; }
          p { line-height: 1.6; }
          pre { background: #f5f5f5; padding: 10px; border-radius: 5px; }
        </style>
      </head>
      <body>
        <h1>ServeMyAPI</h1>
        <p>This is a personal MCP server for securely storing and accessing API keys across projects using the macOS Keychain.</p>
        <p>The server exposes the following tools:</p>
        <ul>
          <li><strong>store-api-key</strong> - Store an API key in the keychain</li>
          <li><strong>get-api-key</strong> - Retrieve an API key from the keychain</li>
          <li><strong>delete-api-key</strong> - Delete an API key from the keychain</li>
          <li><strong>list-api-keys</strong> - List all stored API keys</li>
        </ul>
        <p>This server is running with HTTP SSE transport. Connect to /sse for the SSE endpoint and post messages to /messages.</p>
      </body>
    </html>
  `);
});

// Start the server
app.listen(port, () => {
  console.log(`ServeMyAPI HTTP server is running on port ${port}`);
});

export { server };
```

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

```markdown
# MCP TypeScript SDK ![NPM Version](https://img.shields.io/npm/v/%40modelcontextprotocol%2Fsdk) ![MIT licensed](https://img.shields.io/npm/l/%40modelcontextprotocol%2Fsdk)

## Table of Contents
- [Overview](#overview)
- [Installation](#installation)
- [Quickstart](#quickstart)
- [What is MCP?](#what-is-mcp)
- [Core Concepts](#core-concepts)
  - [Server](#server)
  - [Resources](#resources)
  - [Tools](#tools)
  - [Prompts](#prompts)
- [Running Your Server](#running-your-server)
  - [stdio](#stdio)
  - [HTTP with SSE](#http-with-sse)
  - [Testing and Debugging](#testing-and-debugging)
- [Examples](#examples)
  - [Echo Server](#echo-server)
  - [SQLite Explorer](#sqlite-explorer)
- [Advanced Usage](#advanced-usage)
  - [Low-Level Server](#low-level-server)
  - [Writing MCP Clients](#writing-mcp-clients)
  - [Server Capabilities](#server-capabilities)

## Overview

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:

- Build MCP clients that can connect to any MCP server
- Create MCP servers that expose resources, prompts and tools
- Use standard transports like stdio and SSE
- Handle all MCP protocol messages and lifecycle events

## Installation

```bash
npm install @modelcontextprotocol/sdk
```

## Quick Start

Let's create a simple MCP server that exposes a calculator tool and some data:

```typescript
import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";

// Create an MCP server
const server = new McpServer({
  name: "Demo",
  version: "1.0.0"
});

// Add an addition tool
server.tool("add",
  { a: z.number(), b: z.number() },
  async ({ a, b }) => ({
    content: [{ type: "text", text: String(a + b) }]
  })
);

// Add a dynamic greeting resource
server.resource(
  "greeting",
  new ResourceTemplate("greeting://{name}", { list: undefined }),
  async (uri, { name }) => ({
    contents: [{
      uri: uri.href,
      text: `Hello, ${name}!`
    }]
  })
);

// Start receiving messages on stdin and sending messages on stdout
const transport = new StdioServerTransport();
await server.connect(transport);
```

## What is MCP?

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:

- Expose data through **Resources** (think of these sort of like GET endpoints; they are used to load information into the LLM's context)
- Provide functionality through **Tools** (sort of like POST endpoints; they are used to execute code or otherwise produce a side effect)
- Define interaction patterns through **Prompts** (reusable templates for LLM interactions)
- And more!

## Core Concepts

### Server

The McpServer is your core interface to the MCP protocol. It handles connection management, protocol compliance, and message routing:

```typescript
const server = new McpServer({
  name: "My App",
  version: "1.0.0"
});
```

### Resources

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:

```typescript
// Static resource
server.resource(
  "config",
  "config://app",
  async (uri) => ({
    contents: [{
      uri: uri.href,
      text: "App configuration here"
    }]
  })
);

// Dynamic resource with parameters
server.resource(
  "user-profile",
  new ResourceTemplate("users://{userId}/profile", { list: undefined }),
  async (uri, { userId }) => ({
    contents: [{
      uri: uri.href,
      text: `Profile data for user ${userId}`
    }]
  })
);
```

### Tools

Tools let LLMs take actions through your server. Unlike resources, tools are expected to perform computation and have side effects:

```typescript
// Simple tool with parameters
server.tool(
  "calculate-bmi",
  {
    weightKg: z.number(),
    heightM: z.number()
  },
  async ({ weightKg, heightM }) => ({
    content: [{
      type: "text",
      text: String(weightKg / (heightM * heightM))
    }]
  })
);

// Async tool with external API call
server.tool(
  "fetch-weather",
  { city: z.string() },
  async ({ city }) => {
    const response = await fetch(`https://api.weather.com/${city}`);
    const data = await response.text();
    return {
      content: [{ type: "text", text: data }]
    };
  }
);
```

### Prompts

Prompts are reusable templates that help LLMs interact with your server effectively:

```typescript
server.prompt(
  "review-code",
  { code: z.string() },
  ({ code }) => ({
    messages: [{
      role: "user",
      content: {
        type: "text",
        text: `Please review this code:\n\n${code}`
      }
    }]
  })
);
```

## Running Your Server

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:

### stdio

For command-line tools and direct integrations:

```typescript
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";

const server = new McpServer({
  name: "example-server",
  version: "1.0.0"
});

// ... set up server resources, tools, and prompts ...

const transport = new StdioServerTransport();
await server.connect(transport);
```

### HTTP with SSE

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:

```typescript
import express from "express";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";

const server = new McpServer({
  name: "example-server",
  version: "1.0.0"
});

// ... set up server resources, tools, and prompts ...

const app = express();

app.get("/sse", async (req, res) => {
  const transport = new SSEServerTransport("/messages", res);
  await server.connect(transport);
});

app.post("/messages", async (req, res) => {
  // Note: to support multiple simultaneous connections, these messages will
  // need to be routed to a specific matching transport. (This logic isn't
  // implemented here, for simplicity.)
  await transport.handlePostMessage(req, res);
});

app.listen(3001);
```

### Testing and Debugging

To test your server, you can use the [MCP Inspector](https://github.com/modelcontextprotocol/inspector). See its README for more information.

## Examples

### Echo Server

A simple server demonstrating resources, tools, and prompts:

```typescript
import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";

const server = new McpServer({
  name: "Echo",
  version: "1.0.0"
});

server.resource(
  "echo",
  new ResourceTemplate("echo://{message}", { list: undefined }),
  async (uri, { message }) => ({
    contents: [{
      uri: uri.href,
      text: `Resource echo: ${message}`
    }]
  })
);

server.tool(
  "echo",
  { message: z.string() },
  async ({ message }) => ({
    content: [{ type: "text", text: `Tool echo: ${message}` }]
  })
);

server.prompt(
  "echo",
  { message: z.string() },
  ({ message }) => ({
    messages: [{
      role: "user",
      content: {
        type: "text",
        text: `Please process this message: ${message}`
      }
    }]
  })
);
```

### SQLite Explorer

A more complex example showing database integration:

```typescript
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import sqlite3 from "sqlite3";
import { promisify } from "util";
import { z } from "zod";

const server = new McpServer({
  name: "SQLite Explorer",
  version: "1.0.0"
});

// Helper to create DB connection
const getDb = () => {
  const db = new sqlite3.Database("database.db");
  return {
    all: promisify<string, any[]>(db.all.bind(db)),
    close: promisify(db.close.bind(db))
  };
};

server.resource(
  "schema",
  "schema://main",
  async (uri) => {
    const db = getDb();
    try {
      const tables = await db.all(
        "SELECT sql FROM sqlite_master WHERE type='table'"
      );
      return {
        contents: [{
          uri: uri.href,
          text: tables.map((t: {sql: string}) => t.sql).join("\n")
        }]
      };
    } finally {
      await db.close();
    }
  }
);

server.tool(
  "query",
  { sql: z.string() },
  async ({ sql }) => {
    const db = getDb();
    try {
      const results = await db.all(sql);
      return {
        content: [{
          type: "text",
          text: JSON.stringify(results, null, 2)
        }]
      };
    } catch (err: unknown) {
      const error = err as Error;
      return {
        content: [{
          type: "text",
          text: `Error: ${error.message}`
        }],
        isError: true
      };
    } finally {
      await db.close();
    }
  }
);
```

## Advanced Usage

### Low-Level Server

For more control, you can use the low-level Server class directly:

```typescript
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
  ListPromptsRequestSchema,
  GetPromptRequestSchema
} from "@modelcontextprotocol/sdk/types.js";

const server = new Server(
  {
    name: "example-server",
    version: "1.0.0"
  },
  {
    capabilities: {
      prompts: {}
    }
  }
);

server.setRequestHandler(ListPromptsRequestSchema, async () => {
  return {
    prompts: [{
      name: "example-prompt",
      description: "An example prompt template",
      arguments: [{
        name: "arg1",
        description: "Example argument",
        required: true
      }]
    }]
  };
});

server.setRequestHandler(GetPromptRequestSchema, async (request) => {
  if (request.params.name !== "example-prompt") {
    throw new Error("Unknown prompt");
  }
  return {
    description: "Example prompt",
    messages: [{
      role: "user",
      content: {
        type: "text",
        text: "Example prompt text"
      }
    }]
  };
});

const transport = new StdioServerTransport();
await server.connect(transport);
```

### Writing MCP Clients

The SDK provides a high-level client interface:

```typescript
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";

const transport = new StdioClientTransport({
  command: "node",
  args: ["server.js"]
});

const client = new Client(
  {
    name: "example-client",
    version: "1.0.0"
  },
  {
    capabilities: {
      prompts: {},
      resources: {},
      tools: {}
    }
  }
);

await client.connect(transport);

// List prompts
const prompts = await client.listPrompts();

// Get a prompt
const prompt = await client.getPrompt("example-prompt", {
  arg1: "value"
});

// List resources
const resources = await client.listResources();

// Read a resource
const resource = await client.readResource("file:///example.txt");

// Call a tool
const result = await client.callTool({
  name: "example-tool",
  arguments: {
    arg1: "value"
  }
});
```

## Documentation

- [Model Context Protocol documentation](https://modelcontextprotocol.io)
- [MCP Specification](https://spec.modelcontextprotocol.io)
- [Example Servers](https://github.com/modelcontextprotocol/servers)

## Contributing

Issues and pull requests are welcome on GitHub at https://github.com/modelcontextprotocol/typescript-sdk.

## License

This project is licensed under the MIT License—see the [LICENSE](LICENSE) file for details.
```
Page 1/2FirstPrevNextLast