# Getting Your API Key Source: https://docs.leanmcp.com/ai-gateway/api-keys Create a LeanMCP API key to use the AI Gateway # Getting Your API Key To use the LeanMCP AI Gateway, you need an API key. This key authenticates your requests and tracks your usage. ## Prerequisites You need credits on your LeanMCP account before using the AI Gateway. Purchase credits at [app.leanmcp.com/billing](https://app.leanmcp.com/billing) first. ## Creating an API Key Navigate to [app.leanmcp.com/api-keys](https://app.leanmcp.com/api-keys) API Keys page Click the **Create API Key** button in the top right corner. Fill in the details: * **Name**: A descriptive name (e.g., "Windsurf Development", "Production App") * **Permissions**: Select **SDK** permissions for AI Gateway access Create API key modal Your API key will be displayed **only once**. Copy it immediately and store it securely. API keys are shown only at creation time. If you lose it, you'll need to create a new one. ## API Key Format LeanMCP API keys are prefixed with `leanmcp_` for easy identification: ``` leanmcp_859fc75e29e90aaf85d3a1eb55803c902995a0b796ef3b93cdac445692be53ea ``` ## Using Your API Key Once you have your API key, use it in place of your AI provider's API key: ```bash theme={null} export LEANMCP_API_KEY="leanmcp_your_key_here" ``` ```typescript theme={null} const client = new OpenAI({ baseURL: 'https://aigateway.leanmcp.com/v1/openai', apiKey: 'leanmcp_your_key_here', }); ``` Paste your API key in the API Key field of your IDE's AI configuration. ## Managing API Keys ### View All Keys Go to [app.leanmcp.com/api-keys](https://app.leanmcp.com/api-keys) to see all your API keys: * Creation date * Last used * Usage statistics ### Delete a Key To revoke an API key: 1. Go to the API Keys page 2. Find the key you want to delete 3. Click the **Delete** button 4. Confirm deletion Deleting an API key immediately stops all requests using that key. Make sure to update your applications before deleting. ### Rotate Keys For security, we recommend rotating your API keys periodically: 1. Create a new API key 2. Update your applications to use the new key 3. Verify everything works 4. Delete the old key ## Best Practices Create separate API keys for development, staging, and production. This makes it easier to track usage and rotate keys without affecting all environments. Use environment variables or secret management tools. Add `.env` files to your `.gitignore`. Check the API Keys page regularly to monitor usage and detect any unusual activity. Configure alerts in your dashboard to be notified when you're approaching credit limits. ## Next Steps Now that you have your API key, set up the AI Gateway in your preferred tool: Configure Cursor IDE Configure Windsurf IDE Configure Raycast AI Configure OpenCode CLI Use in your applications *** View your first logged AI request at **app.leanmcp.com/observability** # Claude Code Source: https://docs.leanmcp.com/ai-gateway/claude-code Route Claude Code through LeanMCP AI Gateway for full observability — every prompt, token count, and tool call logged. # Claude Code Route Claude Code through LeanMCP AI Gateway. Every request gets logged — prompts, responses, token usage — visible in your dashboard in real time. **Takes 2 minutes to set up.** *** ## Before you start You need two things: Go to [app.leanmcp.com/billing](https://app.leanmcp.com/billing) to top up credits. Credits are required before the gateway will accept requests. Go to [app.leanmcp.com/api-keys](https://app.leanmcp.com/api-keys) → **Create API Key** → select **SDK** permissions → copy the key immediately (shown once only). Your key format: `leanmcp_859fc75e...` Already have a key and credits? Skip straight to [Configuration](#configuration) below. *** ## Configuration Claude Code reads environment variables from `~/.claude/settings.json`. You're pointing it at the LeanMCP gateway instead of Anthropic directly. | OS | Path | | ------------- | ------------------------------------------- | | macOS / Linux | `~/.claude/settings.json` | | Windows | `C:\Users\\.claude\settings.json` | If the file doesn't exist, create it. Add (or merge) the `env` block into your `settings.json`: ```json settings.json theme={null} { "autoUpdatesChannel": "latest", "env": { "ANTHROPIC_BASE_URL": "https://aigateway.leanmcp.com/v1/anthropic", "ANTHROPIC_AUTH_TOKEN": "leanmcp_your_api_key_here", "ANTHROPIC_API_KEY": "" } } ``` Set `ANTHROPIC_API_KEY` to an empty string `""`. If both keys are present, they conflict and requests will fail. Run any Claude Code command as normal: ```bash theme={null} claude "say hello" ``` Then open your dashboard at [app.leanmcp.com/observability](https://app.leanmcp.com/observability). You should see the request appear within a few seconds. If you see the log entry: you're done. ✓ *** ## What you'll see in the dashboard Once configured, go to [app.leanmcp.com/observability](https://app.leanmcp.com/observability). Every Claude Code request is tracked: | Field | What it shows | | ------------- | ------------------------------------- | | Request Body | Full content sent to the AI | | Response Body | Complete response received | | Model | Which model was used | | Tokens | Input, output, and total token counts | | Latency | Response time in milliseconds | | Status | Success or error | | Timestamp | When the request was made | *** ## Troubleshooting 1. Double-check `ANTHROPIC_BASE_URL` is exactly `https://aigateway.leanmcp.com/v1/anthropic` with no trailing slash 2. Confirm `ANTHROPIC_API_KEY` is set to `""` — a leftover value will bypass the gateway 3. Confirm your account has credits at [app.leanmcp.com/billing](https://app.leanmcp.com/billing) — zero credits means requests are rejected Your `ANTHROPIC_AUTH_TOKEN` is wrong or the key was deleted. Go to [app.leanmcp.com/api-keys](https://app.leanmcp.com/api-keys), create a new key, and update `settings.json`. Remove the `env` block from `settings.json` and restore your `ANTHROPIC_API_KEY`. The gateway is a proxy — removing it has no side effects. You can also set env vars in your shell profile as an alternative to `settings.json`: ```bash theme={null} export ANTHROPIC_BASE_URL="https://aigateway.leanmcp.com/v1/anthropic" export ANTHROPIC_AUTH_TOKEN="leanmcp_your_api_key_here" export ANTHROPIC_API_KEY="" ``` *** ## Next steps Open the dashboard to see your Claude Code sessions. What gets logged, sensitive data detection, export options, and alerts. Also using Cursor, Windsurf, or Cline? Connect them all to the same gateway. Route your whole team through a shared gateway. # Cline Source: https://docs.leanmcp.com/ai-gateway/cline Use the LeanMCP AI Gateway with Cline # Cline Integration [Cline](https://github.com/cline/cline) is an autonomous coding agent. You can configure it to use the LeanMCP AI Gateway as a custom provider or via its OpenRouter/Anthropic integration points. ## Configuration To use LeanMCP with Cline, you need to update your global state configuration file. This allows you to point Cline's API requests to the LeanMCP AI Gateway. Open your Cline global state file. On Windows, this is typically located at: `C:\Users\\.cline\data\globalState.json` Ensure Cline is closed before editing this file to prevent your changes from being overwritten. Add or modify the following fields in your `globalState.json`. This configures the API provider to use LeanMCP and sets the necessary authentication details. ```json theme={null} { "actModeApiProvider": "leanmcp", "planModeApiProvider": "leanmcp", "leanmcp": { "apiKey": "leanmcp_your_api_key_here", "baseURL": "https://aigateway.leanmcp.com/v1/anthropic", "modelId": "anthropic/claude-opus-4.5" }, "apiProvider": "leanmcp" } ``` Replace `leanmcp_your_api_key_here` with your actual API key from the [LeanMCP Dashboard](https://leanmcp.com/api-keys). ## Example Configuration Here is a more complete example of what your `globalState.json` might look like: ```json theme={null} { "welcomeViewCompleted": true, "actModeApiProvider": "leanmcp", "planModeApiProvider": "leanmcp", "openAiHeaders": {}, "sapAiCoreUseOrchestrationMode": true, "ocaMode": "internal", "autoApprovalSettings": { "version": 7, "enabled": true, "maxRequests": 20, "actions": { "readFiles": true, "executeSafeCommands": true, "useMcp": true } }, "leanmcp": { "apiKey": "leanmcp_your_api_key_here", "baseURL": "https://aigateway.leanmcp.com/v1/anthropic", "modelId": "anthropic/claude-opus-4.5" }, "apiProvider": "leanmcp" } ``` # Cursor Source: https://docs.leanmcp.com/ai-gateway/cursor Use the LeanMCP AI Gateway with Cursor IDE # Cursor Integration [Cursor](https://cursor.sh) is an AI-powered code editor. You can route Cursor's AI requests through the LeanMCP AI Gateway to gain visibility into what code is being sent to AI providers. ## Prerequisites Purchase credits at [leanmcp.com](https://leanmcp.com) Create an API key at [leanmcp.com/api-keys](https://leanmcp.com/api-keys) with **SDK** permissions ## Configuration Press `Cmd + ,` (Mac) or `Ctrl + ,` (Windows/Linux) to open Settings Or go to **Cursor** > **Settings** > **Cursor Settings** Click on **Models** in the left sidebar Find the **OpenAI API Key** section and configure: **API Key:** ``` leanmcp_your_api_key_here ``` **Override OpenAI Base URL:** ``` https://aigateway.leanmcp.com/v1/openai ``` Cursor settings Save your settings and restart Cursor for changes to take effect. ## Verifying the Setup 1. Open any file in Cursor 2. Use Cmd+K (or Ctrl+K) to open the AI prompt 3. Ask a simple question like "What does this file do?" 4. Check your [LeanMCP Dashboard](https://leanmcp.com) to see the request logged Verify Cursor setup ## What You Can See Once configured, you'll be able to see in your LeanMCP dashboard: * **Full context sent** - exactly what code Cursor includes in each request * **Token usage** - how many tokens each request uses * **Model used** - which AI model processed your request * **Sensitive data** - any API keys, passwords, or PII detected in your code ## Supported Models Through the LeanMCP AI Gateway, Cursor can access: | Provider | Models | | ------------- | ---------------------- | | **OpenAI** | All Latest Text Models | | **Anthropic** | All Latest Text Models | Cursor primarily uses the OpenAI endpoint. For Anthropic models, you may need to configure a separate API endpoint. ## Troubleshooting * Verify your API key is correct * Check the base URL is exactly `https://aigateway.leanmcp.com/v1/openai` * Restart Cursor after changing settings * Ensure your API key has **SDK** permissions * Check that you have credits in your account * Verify the API key hasn't been deleted * The gateway adds minimal latency (\~50ms) * If significantly slower, check your internet connection * Try a different model (GPT-5.2 is faster than GPT-5.2) ## Next Steps See all your AI requests Block sensitive data # For Developers Source: https://docs.leanmcp.com/ai-gateway/for-developers Build AI-powered applications with security, observability, and cost control # AI Gateway for Developers If you're building applications that use AI, the AI Gateway provides essential features for production deployments: user management, abuse prevention, cost tracking, and optimization tools. ## Why Developers Need AI Gateway When you release an AI-powered app to users, you face several challenges: Users may try to abuse your AI features, running up costs or extracting your prompts Without limits, a few heavy users can consume your entire AI budget You can't see how users are actually using your AI features You don't know which prompts or models perform best ## Key Features for Developers ### 1. User-Level Tracking Track AI usage per user in your application: ```typescript theme={null} const response = await client.chat.completions.create({ model: 'gpt-5.2', messages: [{ role: 'user', content: userMessage }], }, { headers: { 'X-User-ID': userId, 'X-Session-ID': sessionId, } }); ``` Per-user tracking dashboard This enables: * **Usage limits per user** - prevent abuse * **Cost attribution** - know who's using what * **Behavior analysis** - understand usage patterns ### 2. Abuse Prevention Block malicious users Protect your application from abuse: * **Rate limiting** - limit requests per user/minute * **User blocking** - instantly block abusive users * **Pattern detection** - identify suspicious usage patterns * **Cost caps** - set maximum spend per user ```typescript theme={null} // Block a user via API await leanmcp.gateway.blockUser({ userId: 'abusive-user-123', reason: 'Excessive usage detected', }); ``` ### 3. Competitor Intelligence Understand how similar applications use AI: Competitor analysis * **Prompt patterns** - see what prompts work well * **Model choices** - understand which models others use * **Token efficiency** - compare your usage to benchmarks * **Best practices** - learn from successful implementations ### 4. A/B Testing Test different prompts and models to optimize performance: ```typescript theme={null} // A/B test different prompts const variant = await leanmcp.gateway.getVariant({ experimentId: 'prompt-optimization-v1', userId: userId, }); const systemPrompt = variant === 'A' ? 'You are a helpful assistant.' : 'You are an expert software engineer.'; const response = await client.chat.completions.create({ model: 'gpt-5.2', messages: [ { role: 'system', content: systemPrompt }, { role: 'user', content: userMessage } ], }, { headers: { 'X-Experiment-ID': 'prompt-optimization-v1', 'X-Variant': variant, } }); ``` A/B testing results Track and compare: * **Response quality** - user satisfaction metrics * **Token usage** - cost per variant * **Latency** - response time differences * **Conversion rates** - business impact ## Integration Guide ### Basic Setup ```typescript theme={null} import OpenAI from 'openai'; const client = new OpenAI({ baseURL: 'https://aigateway.leanmcp.com/v1/openai', apiKey: process.env.LEANMCP_API_KEY, }); // All requests now go through the gateway const response = await client.chat.completions.create({ model: 'gpt-5.2', messages: [{ role: 'user', content: 'Hello!' }], }); ``` ### Adding User Context ```typescript theme={null} async function generateResponse(userId: string, sessionId: string, message: string) { return await client.chat.completions.create({ model: 'gpt-5.2', messages: [{ role: 'user', content: message }], }, { headers: { 'X-User-ID': userId, 'X-Session-ID': sessionId, 'X-Request-Source': 'web-app', } }); } ``` ### Implementing Rate Limits Set up rate limits in your dashboard or via API: ```typescript theme={null} // Configure rate limits await leanmcp.gateway.setRateLimit({ userId: userId, limits: { requestsPerMinute: 10, tokensPerDay: 100000, maxCostPerMonth: 50.00, } }); ``` ## Dashboard Features ### Usage Analytics Developer analytics dashboard * **Request volume** over time * **Token usage** by model and user * **Cost breakdown** by feature and user segment * **Error rates** and failure analysis ### User Management User management * View all users and their usage * Set individual limits and permissions * Block or restrict users * Export usage data ### Alerts & Monitoring Set up alerts for: * **Unusual usage spikes** * **Budget thresholds** * **Error rate increases** * **Specific user behaviors** ## Production Best Practices Include X-User-ID and X-Session-ID to enable per-user tracking and limits. Configure maximum spend limits before launch to prevent surprises. Watch your dashboard closely during launch to catch abuse early. Continuously optimize your prompts and model choices with experiments. Regularly check what's being blocked to tune your security rules. ## API Reference Full API documentation for gateway management: ```typescript theme={null} // Gateway Management API leanmcp.gateway.blockUser({ userId, reason }) leanmcp.gateway.unblockUser({ userId }) leanmcp.gateway.setRateLimit({ userId, limits }) leanmcp.gateway.getUsage({ userId, dateRange }) leanmcp.gateway.getVariant({ experimentId, userId }) leanmcp.gateway.recordOutcome({ experimentId, userId, outcome }) ``` ## Next Steps Advanced security and blocking rules Reduce costs and improve efficiency Complete code examples for all providers Deep dive into logging and monitoring *** View your first logged AI request at **app.leanmcp.com/observability** # For Personal Users Source: https://docs.leanmcp.com/ai-gateway/for-personal-users Use AI Gateway to monitor and secure your personal AI coding assistant usage # AI Gateway for Personal Users If you use AI coding assistants like **Windsurf**, **Cursor**, or **VS Code with Copilot alternatives**, the AI Gateway gives you complete visibility into what data is being sent to AI providers. ## Why Should You Care? When you use AI coding assistants, your code is sent to AI providers for processing. This includes: * **Your source code** - potentially proprietary or sensitive * **Environment variables** - which might contain API keys or secrets * **Configuration files** - database credentials, service endpoints * **Comments and documentation** - business logic, internal notes Without visibility, you have no idea if sensitive data like passwords, API keys, or personal information is being sent to AI providers. ## What AI Gateway Shows You Personal Dashboard ### Every Request Logged See exactly what is being sent to AI providers: * **Full request content** - the actual code and context being sent * **Model used** - which AI model processed your request * **Token count** - how many tokens each request used * **Timestamp** - when each request was made ### Sensitive Data Detection The gateway automatically scans for: AWS keys, GitHub tokens, database passwords Email addresses, phone numbers, addresses Credit card numbers, bank account info Private endpoints, internal service URLs ## Setting Up for Personal Use ### Step 1: Get Credits 1. Go to [app.leanmcp.com/billing](https://app.leanmcp.com/billing) 2. Create an account if you haven't already 3. Purchase credits (start with a small amount to test) Credits are required to use the AI Gateway. The cost is similar to direct API usage plus a small gateway fee. ### Step 2: Generate Your API Key 1. Navigate to **Settings** > **API Keys** 2. Create a new API key 3. Copy and save it securely ### Step 3: Configure Your IDE 1. Open Windsurf Settings 2. Navigate to AI Configuration 3. Set Base URL: `https://aigateway.leanmcp.com/v1/openai` 4. Set API Key: Your LeanMCP API key 1. Open Cursor Settings (Cmd/Ctrl + ,) 2. Find the AI/LLM configuration section 3. Update the base URL to: `https://aigateway.leanmcp.com/v1/openai` 4. Enter your LeanMCP API key For any tool that allows custom OpenAI-compatible endpoints: * **Base URL:** `https://aigateway.leanmcp.com/v1/openai` * **API Key:** Your LeanMCP API key ### Step 4: Start Coding Use your IDE normally. All AI requests will now flow through the gateway. ## Viewing Your Data ### Request Logs Access your logs at [app.leanmcp.com/observability](https://app.leanmcp.com/observability): Request Log View Each log entry shows: * Full request content (what was sent to AI) * Response received * Token usage and cost * Any sensitive data detected ### Download Your Data You can export all your logged requests: 1. Open [app.leanmcp.com/observability](https://app.leanmcp.com/observability) 2. Select date range 3. Click **Export** to download as JSON or CSV This gives you a complete record of all data that has been sent to AI providers. ## Security Features ### Sensitive Data Alerts When the gateway detects potentially sensitive data, you'll see: * **Real-time alerts** in your dashboard * **Highlighted entries** in the log viewer * **Summary reports** of detected sensitive data ### Taking Action If you find sensitive data was exposed: 1. **Review the log** - see exactly what was sent 2. **Rotate credentials** - if API keys or passwords were exposed 3. **Update .gitignore** - prevent sensitive files from being included 4. **Set up blocking rules** - prevent future exposure (see [Security](/ai-gateway/security)) ## Cost Tracking Cost Tracking Track your AI spending: * **Daily/weekly/monthly usage** graphs * **Cost per model** breakdown * **Token usage** statistics * **Credit balance** monitoring ## Best Practices Check your logs weekly to ensure no sensitive data is being exposed. Configure notifications for when sensitive data patterns are detected. Keep secrets in .env files and ensure they're in .gitignore. AI assistants send surrounding code as context - be aware of what's nearby. ## Next Steps Set up blocking rules for sensitive data Reduce costs and optimize usage *** View your first logged AI request at **app.leanmcp.com/observability** # Getting Started Source: https://docs.leanmcp.com/ai-gateway/getting-started Set up AI Gateway in minutes and start tracking your AI usage # Getting Started with AI Gateway Follow these steps to start routing your AI requests through the LeanMCP AI Gateway. **Already have a LeanMCP account?** Skip straight to [Step 3: Generate an API Key](#step-3-generate-an-api-key). ## Prerequisites You need credits on your LeanMCP account to use the AI Gateway. Credits are used to cover the cost of AI provider requests plus a small gateway fee. ## Step 1: Create an Account 1. Go to [leanmcp.com](https://leanmcp.com) 2. Sign up with your email or GitHub account 3. Complete the onboarding process Sign up for LeanMCP ## Step 2: Purchase Credits 1. Navigate to **Settings** > **Billing** in your dashboard 2. Select a credit package or set up auto-recharge 3. Complete the payment Credits are used at approximately the same rate as direct API calls, plus a small fee for gateway services (logging, security scanning, etc.). ## Step 3: Generate an API Key 1. Go to **Settings** > **API Keys** 2. Click **Create New API Key** 3. Give your key a descriptive name (e.g., "Windsurf Development") 4. Copy and save your API key securely Your API key is shown only once. Store it securely - you'll need it to authenticate requests. ## Step 4: Configure Your Application Replace your AI provider's base URL and API key with the gateway endpoint: ```typescript theme={null} import OpenAI from 'openai'; const client = new OpenAI({ baseURL: 'https://aigateway.leanmcp.com/v1/openai', apiKey: 'your-leanmcp-api-key', }); ``` ```typescript theme={null} import Anthropic from '@anthropic-ai/sdk'; const client = new Anthropic({ baseURL: 'https://aigateway.leanmcp.com/v1/anthropic', apiKey: 'your-leanmcp-api-key', }); ``` In your IDE settings, update the API configuration: **Base URL:** `https://aigateway.leanmcp.com/v1/openai` **API Key:** Your LeanMCP API key ## Step 5: Verify It's Working Make a test request and check your dashboard: ```typescript theme={null} const response = await client.chat.completions.create({ model: 'gpt-5.2', messages: [{ role: 'user', content: 'Hello!' }], }); console.log(response.choices[0].message.content); ``` 1. Open [app.leanmcp.com/observability](https://app.leanmcp.com/observability) 2. You should see your test request appear within a few seconds AI Gateway Logs ✓ **Saw your request in the logs? You're connected.** → [Open app.leanmcp.com/observability](https://app.leanmcp.com/observability) to start monitoring. ## What's Next? See all your AI requests and responses Block sensitive data and malicious requests Monitor costs and optimize spending Code examples for all providers *** View your first logged AI request at **app.leanmcp.com/observability** # AI Gateway Overview Source: https://docs.leanmcp.com/ai-gateway/introduction Route all your AI requests through a single proxy for complete visibility and control # AI Gateway One line change. Full visibility into every AI request — what was sent, what came back, how many tokens, what it cost. Works with **Claude Code, Cursor, Windsurf, Cline, Raycast** and any app you build. *** ## How it works You're changing one thing: where your AI requests go. ``` # before ANTHROPIC_API_KEY=sk-ant-... → goes directly to Anthropic # after ANTHROPIC_AUTH_TOKEN=leanmcp_... → goes through LeanMCP gateway → then to Anthropic ``` Your tools behave identically. You get a full log of every request in [app.leanmcp.com/observability](https://app.leanmcp.com/observability). *** ## Why use AI Gateway? See exactly what data is being sent to AI providers from your tools and apps — full request body, response, tokens, and cost per request. Automatically detect and block sensitive data — API keys, passwords, PII — before they reach an AI provider. Track token usage per tool, per user, per feature. Run A/B tests on prompts and models to reduce spend. Works with existing SDKs and tools. Change one URL and one env var — nothing else in your workflow changes. *** ## Supported providers | Provider | Gateway endpoint | | ---------- | ------------------------------------- | | Anthropic | `aigateway.leanmcp.com/v1/anthropic` | | OpenAI | `aigateway.leanmcp.com/v1/openai` | | xAI (Grok) | `aigateway.leanmcp.com/v1/xai` | | Fireworks | `aigateway.leanmcp.com/v1/fireworks` | | ElevenLabs | `aigateway.leanmcp.com/v1/elevenlabs` | *** ## Where do you want to start? Set up the gateway for your personal coding tools. See exactly what your AI assistant is sending — your code, context, token usage, and cost — in real time. Add the gateway to your backend. Get per-user tracking, abuse prevention, rate limiting, and cost controls in production. *** ## Quick setup (2 steps) Top up credits at [app.leanmcp.com/billing](https://app.leanmcp.com/billing), then create a key at [app.leanmcp.com/api-keys](https://app.leanmcp.com/api-keys). Replace your provider's base URL and API key with your LeanMCP key. That's it. Pick your tool below for the exact config. *** ## Setup guides by tool ##### Personal tools ##### Developers OpenAI and Anthropic SDK examples with user context headers. Route LiteLLM calls through the gateway for full observability of tool calls and conversations. All providers, auth patterns, rate limiting, and A/B testing. *** ## What you'll see after setup Go to [app.leanmcp.com/observability](https://app.leanmcp.com/observability) after your first request: | Field | What it shows | | ------------- | -------------------------- | | Request Body | Full prompt sent to the AI | | Response Body | Full response received | | Model | Which model was used | | Tokens | Input / output / total | | Latency | Response time in ms | | Status | Success or error | | Timestamp | When the request was made | # LiteLLM Source: https://docs.leanmcp.com/ai-gateway/litellm Route LiteLLM requests through the LeanMCP AI Gateway for full observability # LiteLLM Integration [LiteLLM](https://github.com/BerriAI/litellm) is a Python SDK that lets you call 100+ LLM providers with one unified interface. By pointing LiteLLM's `api_base` at the LeanMCP AI Gateway, every request -- including tool calls, token usage, and cost -- gets logged to your [observability dashboard](https://app.leanmcp.com/observability) with zero changes to your model code. This is useful when you: * Run evaluations or benchmarks across multiple models and want a single place to inspect every call * Use tool-calling agents and need to see the full request/response cycle per tool invocation * Want cost and latency tracking without adding custom instrumentation ## Prerequisites Purchase credits at [app.leanmcp.com/billing](https://app.leanmcp.com/billing) Create an API key at [app.leanmcp.com/api-keys](https://app.leanmcp.com/api-keys) with **SDK** permissions ## Gateway Endpoints | Provider | Gateway Base URL | | -------------- | -------------------------------------------- | | **OpenAI** | `https://aigateway.leanmcp.com/v1/openai` | | **Anthropic** | `https://aigateway.leanmcp.com/v1/anthropic` | | **xAI (Grok)** | `https://aigateway.leanmcp.com/v1/xai` | | **Fireworks** | `https://aigateway.leanmcp.com/v1/fireworks` | *** ## Basic Usage Pass `api_base` and `api_key` to `litellm.completion()`. LiteLLM forwards them to the provider -- except now the request goes through the gateway first. ```python theme={null} import litellm import os response = litellm.completion( model="gpt-4o", messages=[ {"role": "user", "content": "What is the LeanMCP AI Gateway?"} ], api_base="https://aigateway.leanmcp.com/v1/openai", api_key=os.environ["LEANMCP_API_KEY"], ) print(response.choices[0].message.content) ``` ```bash theme={null} curl -X POST https://aigateway.leanmcp.com/v1/openai/chat/completions \ -H "Authorization: Bearer $LEANMCP_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "gpt-4o", "messages": [{"role": "user", "content": "What is the LeanMCP AI Gateway?"}] }' ``` *** ## Using Different Providers Swap the `api_base` URL and use the provider-specific model prefix that LiteLLM expects. ### OpenAI ```python theme={null} import litellm import os response = litellm.completion( model="gpt-4o", messages=[{"role": "user", "content": "Hello from OpenAI via the gateway."}], api_base="https://aigateway.leanmcp.com/v1/openai", api_key=os.environ["LEANMCP_API_KEY"], ) print(response.choices[0].message.content) ``` ```bash theme={null} curl -X POST https://aigateway.leanmcp.com/v1/openai/chat/completions \ -H "Authorization: Bearer $LEANMCP_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "gpt-4o", "messages": [{"role": "user", "content": "Hello from OpenAI via the gateway."}] }' ``` ### Anthropic ```python theme={null} import litellm import os response = litellm.completion( model="anthropic/claude-sonnet-4-5-20250929", messages=[{"role": "user", "content": "Hello from Anthropic via the gateway."}], api_base="https://aigateway.leanmcp.com/v1/anthropic", api_key=os.environ["LEANMCP_API_KEY"], ) print(response.choices[0].message.content) ``` ```bash theme={null} curl -X POST https://aigateway.leanmcp.com/v1/anthropic/v1/messages \ -H "Authorization: Bearer $LEANMCP_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "claude-sonnet-4-5-20250929", "max_tokens": 1024, "messages": [{"role": "user", "content": "Hello from Anthropic via the gateway."}] }' ``` ### Fireworks LiteLLM requires the `fireworks_ai/` prefix for Fireworks models. ```python theme={null} import litellm import os response = litellm.completion( model="fireworks_ai/accounts/fireworks/models/llama-v3p1-8b-instruct", messages=[{"role": "user", "content": "Hello from Fireworks via the gateway."}], max_tokens=1024, temperature=0.0, api_base="https://aigateway.leanmcp.com/v1/fireworks", api_key=os.environ["LEANMCP_API_KEY"], ) print(response.choices[0].message.content) ``` ```bash theme={null} curl -X POST https://aigateway.leanmcp.com/v1/fireworks/chat/completions \ -H "Authorization: Bearer $LEANMCP_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "accounts/fireworks/models/llama-v3p1-8b-instruct", "messages": [{"role": "user", "content": "Hello from Fireworks via the gateway."}], "max_tokens": 1024, "temperature": 0.0 }' ``` *** ## Streaming Streaming works the same way. Set `stream=True` and iterate over chunks. ```python theme={null} import litellm import os response = litellm.completion( model="gpt-4o", messages=[{"role": "user", "content": "Write a short poem."}], api_base="https://aigateway.leanmcp.com/v1/openai", api_key=os.environ["LEANMCP_API_KEY"], stream=True, ) for chunk in response: content = chunk.choices[0].delta.content or "" print(content, end="", flush=True) ``` ```bash theme={null} curl -N -X POST https://aigateway.leanmcp.com/v1/openai/chat/completions \ -H "Authorization: Bearer $LEANMCP_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "gpt-4o", "messages": [{"role": "user", "content": "Write a short poem."}], "stream": true }' ``` *** ## Tool Calling LiteLLM supports tool/function calling. When routed through the gateway, every tool call and its response is captured in the observability dashboard. ```python theme={null} import litellm import os import json tools = [ { "type": "function", "function": { "name": "get_weather", "description": "Get current weather for a city", "parameters": { "type": "object", "properties": { "city": {"type": "string", "description": "City name"} }, "required": ["city"], }, }, } ] response = litellm.completion( model="gpt-4o", messages=[{"role": "user", "content": "What is the weather in London?"}], tools=tools, api_base="https://aigateway.leanmcp.com/v1/openai", api_key=os.environ["LEANMCP_API_KEY"], ) tool_calls = response.choices[0].message.tool_calls if tool_calls: for tc in tool_calls: print(f"Function: {tc.function.name}") print(f"Args: {tc.function.arguments}") ``` Every tool call shows up in [app.leanmcp.com/observability](https://app.leanmcp.com/observability) with the full function name, arguments, and the model's response. *** ## Using with Existing Frameworks LiteLLM is often used as the LLM backend for evaluation frameworks, agent harnesses, and batch pipelines. You can route all of those calls through the gateway by passing `api_base` and `api_key` as extra kwargs. ### Example: Evaluation Framework This pattern comes from a real benchmark runner that uses LiteLLM under the hood. The gateway endpoint and key are passed as JSON kwargs to the framework's CLI: ```bash theme={null} # Route all LLM calls through the gateway LLM_ARGS='{"api_base": "https://aigateway.leanmcp.com/v1/fireworks", "api_key": "'$LEANMCP_API_KEY'"}' python run_eval.py \ --model "fireworks_ai/accounts/fireworks/models/llama-v3p1-8b-instruct" \ --llm-args "$LLM_ARGS" \ --num-tasks 5 ``` Any framework that forwards kwargs to `litellm.completion()` will pick up the gateway routing automatically. *** ## Environment Setup ```bash theme={null} # .env LEANMCP_API_KEY=leanmcp_your_api_key_here ``` ```python theme={null} # Load the key from .env from dotenv import load_dotenv load_dotenv() import os api_key = os.environ["LEANMCP_API_KEY"] ``` *** ## Debugging Turn on LiteLLM verbose logging to see the exact URL, headers, and body of each outgoing request: ```python theme={null} import litellm litellm._turn_on_debug() ``` Or set the environment variable: ```bash theme={null} export LITELLM_LOG=DEBUG ``` This confirms that requests are hitting `aigateway.leanmcp.com` and not the provider directly. *** ## Troubleshooting * Verify `LEANMCP_API_KEY` is set and starts with `leanmcp_` * Check that the key has **SDK** permissions at [app.leanmcp.com/api-keys](https://app.leanmcp.com/api-keys) * Make sure you have credits in your account * Confirm the model string uses the correct LiteLLM prefix (e.g. `fireworks_ai/` for Fireworks, `anthropic/` for Anthropic) * Verify the `api_base` matches the provider (e.g. `/v1/fireworks` for Fireworks models, not `/v1/openai`) * Enable debug logging (`litellm._turn_on_debug()`) and confirm the request URL starts with `https://aigateway.leanmcp.com` * Check [app.leanmcp.com/observability](https://app.leanmcp.com/observability) -- requests appear within a few seconds * Make sure you pass `stream=True` to `litellm.completion()` * The gateway supports streaming for all providers. If you get buffered responses, check your HTTP client settings *** ## Next Steps Inspect every request, response, and token count OpenAI and Anthropic SDK examples Block sensitive data before it reaches providers A/B testing and cost reduction # Observability Source: https://docs.leanmcp.com/ai-gateway/observability Complete visibility into every AI request across your entire codebase # Observability The AI Gateway provides complete visibility into every AI request made from your applications or development tools. Know exactly what data is being sent, track usage patterns, and identify issues before they become problems. ## The Problem When using AI providers directly, you have limited visibility: * **What code is being sent?** You don't know exactly what context is included * **Are secrets exposed?** API keys and passwords might be in the request * **How much are you spending?** Token usage is hard to track across tools * **What's failing?** Errors are logged on the provider's side, not yours ## Complete Request Logging Every request through the AI Gateway is logged with full details: Request detail view ### What's Logged | Field | Description | | ----------------- | ------------------------------------------ | | **Timestamp** | When the request was made | | **Request Body** | Full content sent to the AI provider | | **Response Body** | Complete response received | | **Model** | Which AI model was used | | **Tokens** | Input, output, and total token counts | | **Latency** | Response time in milliseconds | | **User ID** | Which user made the request (if provided) | | **Session ID** | Session tracking for conversation grouping | | **Status** | Success, error, or blocked | ### Viewing Logs Access logs at [app.leanmcp.com/observability](https://app.leanmcp.com/observability): 1. Open [app.leanmcp.com/observability](https://app.leanmcp.com/observability) 2. Use filters to narrow down: * Date range * Model * User ID * Status (success/error/blocked) * Contains text Log filtering options ## See Your Entire Codebase When using AI coding assistants, the gateway shows you exactly what's being sent: Code context visibility ### What Gets Sent to AI AI assistants typically send: * **The file you're editing** * **Surrounding context** from nearby files * **Open tabs** in your editor * **Terminal output** * **Error messages** With the gateway, you can see all of this - no more guessing what context is included. ### Identifying Sensitive Data The gateway automatically detects potentially sensitive data: * API keys (AWS, GitHub, etc.) * Database passwords * OAuth tokens * Private keys * Email addresses * Phone numbers * Social security numbers * Credit card numbers * Internal URLs * Private IP addresses * Server names * Database connection strings * Customer data * Financial figures * Proprietary algorithms * Trade secrets ## Download and Analyze ### Export Options Export your data for analysis: * **JSON** - Full structured data for programmatic analysis * **CSV** - Spreadsheet-compatible for reporting * **Filtered exports** - Only export what you need ```bash theme={null} # Example: Export last 7 days curl -X GET "https://api.leanmcp.com/gateway/logs?days=7" \ -H "Authorization: Bearer your-api-key" \ -o logs.json ``` ### What You Can Discover Search logs for patterns like API keys, passwords, or personal data to find and remediate exposure. Understand which features generate the most AI requests and optimize accordingly. Identify which requests use the most tokens and find opportunities to reduce costs. Find common failure modes and fix issues before users report them. ## Real-Time Monitoring ### Live Log Stream Watch requests in real-time: Live log stream * **Instant visibility** - see requests as they happen * **Error highlighting** - failed requests are immediately visible * **Sensitive data alerts** - get notified when sensitive patterns are detected ### Metrics Dashboard Metrics dashboard Track key metrics: * **Requests per minute/hour/day** * **Token usage over time** * **Error rate trends** * **Latency percentiles** * **Cost accumulation** ## Alerts and Notifications Set up alerts for important events: ### Available Alert Types | Alert | Trigger | | --------------------------- | --------------------------------------------- | | **Sensitive Data Detected** | When credentials or PII are found in requests | | **High Error Rate** | When error rate exceeds threshold | | **Usage Spike** | When request volume suddenly increases | | **Budget Warning** | When approaching spending limits | | **Unusual Pattern** | When usage deviates from normal | ### Configuration ```typescript theme={null} // Set up alerts via API await leanmcp.gateway.createAlert({ type: 'sensitive_data_detected', channels: ['email', 'slack'], config: { patterns: ['api_key', 'password', 'secret'], severity: 'high', } }); ``` ## Integration with Your Tools ### Windsurf / Cursor Usage See exactly what your coding assistant sends: 1. Configure your IDE to use the gateway 2. Code normally 3. Check [app.leanmcp.com/observability](https://app.leanmcp.com/observability) to see what context was included Review your logs after coding sessions to understand what data your AI assistant accessed. ### Application Requests For your own applications, add context headers: ```typescript theme={null} const response = await client.chat.completions.create({ model: 'gpt-5.2', messages: messages, }, { headers: { 'X-Request-Source': 'mobile-app', 'X-Feature': 'chat-completion', 'X-User-ID': userId, } }); ``` These headers are logged, making it easy to filter and analyze requests. ## Data Retention | Plan | Retention | | -------------- | ---------------------- | | **Free** | 7 days | | **Pro** | 30 days | | **Enterprise** | 90 days (customizable) | Export data regularly if you need longer retention. All exports are available for download within the retention period. ## Next Steps Block sensitive data from being sent Use insights to reduce costs *** View all your logged AI requests at **app.leanmcp.com/observability** # OpenClaw Source: https://docs.leanmcp.com/ai-gateway/openclaw Use the LeanMCP AI Gateway with OpenClaw # OpenClaw Integration [OpenClaw](https://github.com/openclaw/openclaw) is an open-source personal AI assistant that you run on your own devices and talk to through the messaging apps you already use. You can configure it to route its Claude API calls through the LeanMCP AI Gateway for monitoring, cost tracking, and content filtering. ## About OpenClaw OpenClaw is an open-source personal AI assistant created by Austrian developer Peter Steinberger as a playground project in late 2025 , originally released under the name Clawdbot and briefly renamed Moltbot before settling on OpenClaw in late January 2026 after trademark concerns from Anthropic's legal team over the original "Clawd" pun on "Claude" . Rather than living in a browser tab, OpenClaw runs locally on your machine and answers you on channels you already use like WhatsApp, Telegram, Slack, Discord, Google Chat, Signal, iMessage, Microsoft Teams, Matrix, LINE, and many more , with persistent memory stored in local Markdown files, a skills system, scheduled wakeups, and integrations for browser control, voice, and canvas rendering. The project went viral almost immediately: it drew 2 million visitors in a single week shortly after launch, reached around 247,000 stars and 47,700 forks on GitHub by early March 2026 , and surpassed 250,000 stars on March 3, 2026, overtaking React to become one of the fastest-growing projects in GitHub history . In February 2026, Steinberger announced he was joining OpenAI and that OpenClaw would move to a foundation to stay open and independent , and the project continues to ship weekly releases driven by its community. ## Monitoring OpenClaw with LeanMCP Because OpenClaw makes its Claude calls through a standard Anthropic-compatible client, you can point it at the LeanMCP AI Gateway instead of the Anthropic API directly. Every request then flows through LeanMCP, where it gets logged, scanned for sensitive data, attributed to a user, and counted against your spending limits — without changing anything about how you talk to your assistant from Discord, Telegram, or WhatsApp. ## Prerequisites Before anything else, sign in at [app.leanmcp.com](https://app.leanmcp.com) and enable billing on your account. Until billing is enabled, gateway requests will be rejected even if you have an API key. Add a payment method and top up credits from the Billing section of the dashboard. Create an API key at [app.leanmcp.com/api-keys](https://app.leanmcp.com/api-keys) with **SDK** permissions. Your key will start with `leanmcp_`. If you don't already have OpenClaw running, install it and run the onboarding wizard: ```bash theme={null} npm install -g openclaw@latest openclaw onboard --install-daemon ``` ## Configuration ### Environment Variables Point OpenClaw at the LeanMCP AI Gateway by setting the Anthropic environment variables on the host running the OpenClaw gateway: ```bash theme={null} # AI Gateway Configuration (instead of direct Anthropic) ANTHROPIC_API_KEY=leanmcp_your_api_key_here ANTHROPIC_BASE_URL=https://aigateway.leanmcp.com/v1/anthropic ``` Add these to `~/.openclaw/.env` (or your shell profile) and restart the gateway: ```bash theme={null} openclaw gateway ``` ### Configuration File If you prefer to configure OpenClaw via its config file rather than environment variables: ```json theme={null} { "anthropic": { "apiKey": "leanmcp_your_api_key_here", "baseUrl": "https://aigateway.leanmcp.com/v1/anthropic" } } ``` ## How It Works ``` Discord / Telegram / WhatsApp / iMessage user message | v OpenClaw (local gateway on your machine) | v LeanMCP AI Gateway <-- Logs request, checks for sensitive data, enforces limits | v Anthropic API | v Claude Response | v Reply on the originating channel ``` ## Benefits for OpenClaw Users Track which messaging users are talking to your assistant and how much Set spending limits to prevent runaway costs from heavy usage or loops Block sensitive prompts before they reach Claude Understand how OpenClaw is being used across all your channels ## Adding Per-User Tracking OpenClaw runs across many channels. To attribute usage to the right person and channel, attach identifying headers when OpenClaw calls the Anthropic SDK: ```javascript theme={null} const response = await anthropic.messages.create({ model: 'claude-sonnet-4-5-20250929', messages: messages, }, { headers: { 'X-User-ID': message.author.id, 'X-Channel': 'discord', // or telegram, whatsapp, imessage, ... 'X-Server-ID': message.guild?.id, } }); ``` This enables per-user usage tracking, per-user limits, and the ability to block specific users from your dashboard. ## Verifying the Setup 1. Restart your OpenClaw gateway after changing environment variables. 2. Send a message to your assistant on any connected channel (Discord, Telegram, WhatsApp, etc.). 3. Open the [LeanMCP Dashboard](https://app.leanmcp.com) and confirm the request appears in the logs. ## Setting Up Rate Limits Protect your assistant from runaway loops or abusive users: ```javascript theme={null} await leanmcp.gateway.setRateLimit({ scope: 'per_user', limits: { requestsPerMinute: 5, requestsPerHour: 50, tokensPerDay: 100000, } }); ``` ## Troubleshooting * Check that the OpenClaw gateway daemon is running (`openclaw status`) * Verify your LeanMCP API key is valid and billing is enabled * Tail the gateway logs for errors * Ensure the base URL ends with `/v1/anthropic` (no trailing slash) * Verify your API key starts with `leanmcp_` * Confirm billing is enabled and you have remaining credits at [app.leanmcp.com](https://app.leanmcp.com) * Confirm OpenClaw is actually using the custom base URL (check the env vars inside the running process) * Restart the gateway after configuration changes * Make sure you're looking at the same workspace in the dashboard that owns the API key ## Resources * [OpenClaw GitHub Repository](https://github.com/openclaw/openclaw) * [OpenClaw Website](https://openclaw.ai/) * [OpenClaw Documentation](https://docs.openclaw.ai/) ## Next Steps Get notified of unusual usage Reduce token usage # OpenCode Source: https://docs.leanmcp.com/ai-gateway/opencode Use the LeanMCP AI Gateway with OpenCode CLI # OpenCode Integration [OpenCode](https://opencode.ai) is a powerful CLI tool for AI-assisted coding that supports 75+ LLM providers. You can configure it to use the LeanMCP AI Gateway as a custom provider. OpenCode uses the AI SDK and supports any OpenAI-compatible API, making it perfect for the LeanMCP AI Gateway. ## Prerequisites Purchase credits at [leanmcp.com](https://leanmcp.com) Create an API key at [leanmcp.com/api-keys](https://leanmcp.com/api-keys) with **SDK** permissions ```bash theme={null} npm install -g opencode # or brew install opencode ``` ## Configuration ### Configuration File Create or update `opencode.json` in your project directory (or `~/.config/opencode/opencode.json` for global use): ```json theme={null} { "$schema": "https://opencode.ai/config.json", "provider": { "leanmcp": { "npm": "@ai-sdk/openai-compatible", "name": "LeanMCP Custom Provider", "options": { "baseURL": "https://aigateway.leanmcp.com/v1/anthropic/", "apiKey": "leanmcp_your_api_key_here" }, "models": { "claude-sonnet-4-5": { "name": "claude-sonnet-4-5" } } } }, "model": "leanmcp/claude-sonnet-4-5" } ``` Replace `leanmcp_your_api_key_here` with your actual API key. ## Using LeanMCP in OpenCode Once configured, you can simply run: ```bash theme={null} opencode ``` Since the `model` is defined in `opencode.json` as `leanmcp/claude-sonnet-4-5`, Opencode will automatically use it. To view available models: ```bash theme={null} opencode /models ``` Select `leanmcp/claude-sonnet-4-5` if it's not already selected. ## Configuration Options | Option | Description | | ----------------- | -------------------------------------------------------- | | `npm` | AI SDK package (`@ai-sdk/openai-compatible` for gateway) | | `name` | Display name in OpenCode UI | | `options.baseURL` | LeanMCP AI Gateway endpoint | | `options.apiKey` | Can be set here or via `/connect` | | `options.headers` | Custom headers for requests | | `models` | Map of available models | ### Setting Token Limits For proper context management, specify model limits: ```json theme={null} { "provider": { "leanmcp": { "npm": "@ai-sdk/openai-compatible", "name": "LeanMCP AI Gateway", "options": { "baseURL": "https://aigateway.leanmcp.com/v1/openai" }, "models": { "gpt-5.2": { "name": "GPT-5.2", "limit": { "context": 128000, "output": 4096 } } } } } } ``` ## Environment Variables Alternatively, set credentials via environment variables: ```bash theme={null} # Add to your shell profile (~/.bashrc, ~/.zshrc) export LEANMCP_API_KEY="leanmcp_your_api_key_here" ``` Then reference in config: ```json theme={null} { "provider": { "leanmcp": { "options": { "baseURL": "https://aigateway.leanmcp.com/v1/openai", "apiKey": "{env:LEANMCP_API_KEY}" } } } } ``` ## Multiple Providers via LeanMCP Route all your AI providers through LeanMCP: ```json theme={null} { "$schema": "https://opencode.ai/config.json", "provider": { "leanmcp-openai": { "npm": "@ai-sdk/openai-compatible", "name": "OpenAI (via LeanMCP)", "options": { "baseURL": "https://aigateway.leanmcp.com/v1/openai" }, "models": { "gpt-5.2": {}, "gpt-5.2": {}, "gpt-5.2": {} } }, "leanmcp-anthropic": { "npm": "@ai-sdk/openai-compatible", "name": "Anthropic (via LeanMCP)", "options": { "baseURL": "https://aigateway.leanmcp.com/v1/anthropic" }, "models": { "claude-sonnet-4-5-20250929": {}, "claude-opus-4-5-20251101": {} } }, "leanmcp-xai": { "npm": "@ai-sdk/openai-compatible", "name": "xAI Grok (via LeanMCP)", "options": { "baseURL": "https://aigateway.leanmcp.com/v1/xai" }, "models": { "grok-beta": {} } } } } ``` ## Verifying Setup 1. Run OpenCode: `opencode` 2. Select a LeanMCP model: `/models` 3. Ask a question 4. Check your [LeanMCP Dashboard](https://app.leanmcp.com) to see the logged request ## Benefits All your OpenCode sessions logged in one place Use OpenAI, Anthropic, xAI through one gateway Track spending across all your coding sessions Detect if sensitive code is being sent ## Troubleshooting * Verify `opencode.json` is in your project root or `~/.config/opencode/` * Check JSON syntax is valid * Restart OpenCode after config changes * Run `/connect` and re-enter your API key * Verify the API key has SDK permissions * Check you have credits in your account * Ensure the model name matches what LeanMCP supports * Check the baseURL matches the provider (openai, anthropic, etc.) * Try a different model to isolate the issue ## Resources * [OpenCode Documentation](https://opencode.ai/docs) * [OpenCode Providers Guide](https://opencode.ai/docs/models/) * [OpenCode GitHub](https://github.com/anomalyco/opencode) ## Next Steps See all your OpenCode requests Use in your own applications # Raycast Source: https://docs.leanmcp.com/ai-gateway/raycast Use the LeanMCP AI Gateway with Raycast AI via BYOK # Raycast Integration [Raycast](https://raycast.com) is a productivity tool for macOS with powerful AI features. Using Raycast's **Bring Your Own Key (BYOK)** feature, you can route AI requests through the LeanMCP AI Gateway. Raycast BYOK lets you use your own API keys from AI providers. By pointing to the LeanMCP AI Gateway, you get full visibility into your AI usage. ## Prerequisites Purchase credits at [leanmcp.com](https://leanmcp.com) Create an API key at [leanmcp.com/api-keys](https://leanmcp.com/api-keys) with **SDK** permissions Download and install [Raycast](https://raycast.com) if you haven't already ## Understanding Raycast BYOK Raycast's Bring Your Own Key feature allows you to: * Use AI models without a Raycast Pro subscription * Pay only for what you use via your own API keys * Use Raycast AI with any OpenAI-compatible endpoint With BYOK, you can send as many AI messages as you want at your own cost, without needing a Pro subscription. ## Configuration 1. Open Raycast (default: `Cmd + Space`) 2. Type "Raycast Settings" and press Enter 3. Navigate to the **AI** section Raycast AI Settings Scroll down to find the **Custom API Keys** section. For OpenAI models via LeanMCP: **Provider:** OpenAI (or OpenRouter for all providers) **API Key:** ``` leanmcp_your_api_key_here ``` Raycast's standard BYOK doesn't support custom base URLs directly. See the OpenRouter method below for full gateway support. Click **Validate** to test the API key connection. ## Using OpenRouter for Full Gateway Support For complete LeanMCP AI Gateway integration with Raycast, use OpenRouter as the provider: In Raycast AI Settings, choose **OpenRouter** as the provider. Enter your LeanMCP API key in the OpenRouter API key field. The LeanMCP AI Gateway is OpenRouter-compatible, so Raycast will route requests through the gateway. Choose from available models. All requests will go through the LeanMCP gateway. ## What Raycast Sends to AI When you use Raycast AI, the following may be sent: * Your prompt/question * Selected text (if using "Ask AI about selection") * Clipboard contents (if using clipboard features) * File contents (if using file-based commands) With the AI Gateway, you can see exactly what's sent in each request. ## Model Selection After configuring BYOK, you'll see a key icon next to models using your API key: Raycast model picker with BYOK Available models depend on your configuration: * **OpenAI**: GPT-5.2, GPT-5.2, GPT-5.2 * **Anthropic**: Claude Sonnet 4.5, Claude Opus 4.5 * **OpenRouter**: Access to 100+ models ## Privacy Considerations When using BYOK (excluding OpenRouter), Raycast processes requests through their servers to unify APIs and add features. With the LeanMCP gateway, you get an additional layer of logging and control. Learn more about Raycast's [AI Privacy + Security](https://manual.raycast.com/ai). ## Local Models Alternative If you want complete privacy, Raycast also supports local models via Ollama: 1. Install [Ollama](https://ollama.com/download) 2. Pull a model: `ollama pull llama2` 3. Select the local model in Raycast Local models run entirely on your machine and don't go through any external service. ## Benefits of Using AI Gateway with Raycast See exactly how much you're using Raycast AI Track spending across all your Raycast AI usage Get notified if clipboard or selection contains secrets Review past AI interactions and their costs ## Troubleshooting * Ensure your LeanMCP API key has credits * Check the key has SDK permissions * Verify you're using the correct provider setting * Restart Raycast after adding the API key * Check Raycast has the latest version * Try removing and re-adding the API key * Confirm you're using the LeanMCP API key * Some requests may go directly to providers depending on Raycast's routing * Try using OpenRouter mode for guaranteed gateway routing ## Resources * [Raycast AI Manual](https://manual.raycast.com/ai) * [Raycast BYOK Documentation](https://manual.raycast.com/ai#bring-your-own-key-byok) ## Next Steps Monitor Raycast AI requests Reduce token usage # SDK Integration Source: https://docs.leanmcp.com/ai-gateway/sdk-integration Use the LeanMCP AI Gateway in your applications with official SDKs # SDK Integration for Developers The LeanMCP AI Gateway works with any OpenAI-compatible SDK or library. Simply change the base URL and API key to route requests through the gateway. **Key Insight**: Any library that supports a custom `baseURL` or `base_url` parameter can use the LeanMCP AI Gateway. ## Prerequisites Purchase credits at [leanmcp.com](https://leanmcp.com) Create an API key at [leanmcp.com/api-keys](https://leanmcp.com/api-keys) with **SDK** permissions ## Gateway Endpoints | Provider | Base URL | | -------------- | --------------------------------------------- | | **OpenAI** | `https://aigateway.leanmcp.com/v1/openai` | | **Anthropic** | `https://aigateway.leanmcp.com/v1/anthropic` | | **xAI (Grok)** | `https://aigateway.leanmcp.com/v1/xai` | | **Fireworks** | `https://aigateway.leanmcp.com/v1/fireworks` | | **ElevenLabs** | `https://aigateway.leanmcp.com/v1/elevenlabs` | *** ## Official SDKs ### OpenAI SDK ```typescript theme={null} import OpenAI from 'openai'; const client = new OpenAI({ baseURL: 'https://aigateway.leanmcp.com/v1/openai', apiKey: process.env.LEANMCP_API_KEY, // leanmcp_xxx }); const response = await client.chat.completions.create({ model: 'gpt-5.2', messages: [ { role: 'user', content: 'Hello!' } ], }); console.log(response.choices[0].message.content); ``` ```python theme={null} from openai import OpenAI import os client = OpenAI( base_url="https://aigateway.leanmcp.com/v1/openai", api_key=os.environ.get("LEANMCP_API_KEY"), # leanmcp_xxx ) response = client.chat.completions.create( model="gpt-5.2", messages=[ {"role": "user", "content": "Hello!"} ] ) print(response.choices[0].message.content) ``` ```bash theme={null} curl https://aigateway.leanmcp.com/v1/openai/chat/completions \ -H "Authorization: Bearer leanmcp_your_api_key" \ -H "Content-Type: application/json" \ -d '{ "model": "gpt-5.2", "messages": [{"role": "user", "content": "Hello!"}] }' ``` ### Anthropic SDK ```typescript theme={null} import Anthropic from '@anthropic-ai/sdk'; const client = new Anthropic({ baseURL: 'https://aigateway.leanmcp.com/v1/anthropic', apiKey: process.env.LEANMCP_API_KEY, }); const response = await client.messages.create({ model: 'claude-sonnet-4-5-20250929', max_tokens: 1024, messages: [ { role: 'user', content: 'Hello!' } ], }); console.log(response.content[0].text); ``` ```python theme={null} import anthropic import os client = anthropic.Anthropic( base_url="https://aigateway.leanmcp.com/v1/anthropic", api_key=os.environ.get("LEANMCP_API_KEY"), ) response = client.messages.create( model="claude-sonnet-4-5-20250929", max_tokens=1024, messages=[ {"role": "user", "content": "Hello!"} ] ) print(response.content[0].text) ``` ### Streaming ```typescript theme={null} import OpenAI from 'openai'; const client = new OpenAI({ baseURL: 'https://aigateway.leanmcp.com/v1/openai', apiKey: process.env.LEANMCP_API_KEY, }); const stream = await client.chat.completions.create({ model: 'gpt-5.2', messages: [{ role: 'user', content: 'Write a poem' }], stream: true, }); for await (const chunk of stream) { process.stdout.write(chunk.choices[0]?.delta?.content || ''); } ``` ```typescript theme={null} import Anthropic from '@anthropic-ai/sdk'; const client = new Anthropic({ baseURL: 'https://aigateway.leanmcp.com/v1/anthropic', apiKey: process.env.LEANMCP_API_KEY, }); const stream = client.messages.stream({ model: 'claude-sonnet-4-5-20250929', max_tokens: 1024, messages: [{ role: 'user', content: 'Write a poem' }], }); for await (const event of stream) { if (event.type === 'content_block_delta') { process.stdout.write(event.delta.text); } } ``` *** ## Framework Integrations ### LangChain ```typescript theme={null} import { ChatOpenAI } from '@langchain/openai'; const model = new ChatOpenAI({ modelName: 'gpt-5.2', configuration: { baseURL: 'https://aigateway.leanmcp.com/v1/openai', apiKey: process.env.LEANMCP_API_KEY, }, }); const response = await model.invoke('Hello!'); console.log(response.content); ``` ```python theme={null} from langchain_openai import ChatOpenAI import os model = ChatOpenAI( model="gpt-5.2", base_url="https://aigateway.leanmcp.com/v1/openai", api_key=os.environ.get("LEANMCP_API_KEY"), ) response = model.invoke("Hello!") print(response.content) ``` ### Vercel AI SDK ```typescript theme={null} import { openai } from '@ai-sdk/openai'; import { generateText } from 'ai'; const customOpenAI = openai.configure({ baseURL: 'https://aigateway.leanmcp.com/v1/openai', apiKey: process.env.LEANMCP_API_KEY, }); const { text } = await generateText({ model: customOpenAI('gpt-5.2'), prompt: 'Hello!', }); console.log(text); ``` ### LlamaIndex ```python theme={null} from llama_index.llms.openai import OpenAI import os llm = OpenAI( model="gpt-5.2", api_base="https://aigateway.leanmcp.com/v1/openai", api_key=os.environ.get("LEANMCP_API_KEY"), ) response = llm.complete("Hello!") print(response.text) ``` *** ## Adding Request Context For better tracking and analytics, add custom headers to your requests: ```typescript theme={null} const response = await client.chat.completions.create({ model: 'gpt-5.2', messages: messages, }, { headers: { 'X-User-ID': userId, // Track per-user usage 'X-Session-ID': sessionId, // Group requests by session 'X-Request-Source': 'web-app', // Identify request source 'X-Feature': 'chat', // Tag by feature } }); ``` These headers appear in your dashboard and enable: * Per-user usage tracking and limits * Session-based request grouping * Feature-level analytics * Source attribution *** ## OpenAI-Compatible Libraries Any library that supports a custom base URL works with the LeanMCP AI Gateway: | Library | Configuration | | ------------------------ | --------------------------- | | **openai** (official) | `baseURL` parameter | | **anthropic** (official) | `baseURL` parameter | | **langchain** | `base_url` in configuration | | **llama-index** | `api_base` parameter | | **vercel/ai** | `baseURL` in configure | | **litellm** | `api_base` parameter | | **guidance** | Custom OpenAI client | | **instructor** | Pass custom OpenAI client | ### Generic Pattern ```typescript theme={null} // Any OpenAI-compatible library const client = new SomeAILibrary({ baseURL: 'https://aigateway.leanmcp.com/v1/openai', // or /anthropic, /xai, etc. apiKey: 'leanmcp_your_api_key', }); ``` *** ## Environment Setup ### Recommended: Environment Variables ```bash theme={null} # .env file LEANMCP_API_KEY=leanmcp_your_api_key_here LEANMCP_OPENAI_BASE_URL=https://aigateway.leanmcp.com/v1/openai LEANMCP_ANTHROPIC_BASE_URL=https://aigateway.leanmcp.com/v1/anthropic ``` ### Multiple Environments ```typescript theme={null} const getBaseURL = () => { if (process.env.NODE_ENV === 'development') { return 'https://aigateway.leanmcp.com/v1/openai'; // Use gateway in dev } return 'https://api.openai.com/v1'; // Direct in production (optional) }; const client = new OpenAI({ baseURL: getBaseURL(), apiKey: process.env.OPENAI_API_KEY, }); ``` *** ## Error Handling ```typescript theme={null} try { const response = await client.chat.completions.create({ model: 'gpt-5.2', messages: [{ role: 'user', content: 'Hello' }], }); } catch (error) { if (error.status === 401) { console.error('Invalid API key'); } else if (error.status === 402) { console.error('Insufficient credits'); } else if (error.status === 429) { console.error('Rate limited'); } else { console.error('API error:', error.message); } } ``` *** ## Benefits for Developers All AI requests logged in one dashboard Track usage per user with custom headers Know which features drive AI costs Test different models and prompts Block malicious users and sensitive data Set limits per user or globally *** ## Next Steps Block users and protect sensitive data A/B testing and cost reduction Monitor all requests Advanced developer features # Security Source: https://docs.leanmcp.com/ai-gateway/security Protect sensitive data and block malicious users from abusing your AI features # Security The AI Gateway provides powerful security features to protect your data and prevent abuse. Block sensitive information from being sent to AI providers, and stop malicious users from exploiting your applications. ## Sensitive Data Protection ### The Risk When using AI assistants or building AI-powered apps, sensitive data can accidentally be exposed: **Real scenarios we've seen:** * AWS keys sent in code context to ChatGPT * Database passwords included in error messages * Customer PII processed by AI for "analysis" * API secrets in environment variable debugging ### Automatic Detection The gateway scans all requests for sensitive patterns: Sensitive data detection **Detected patterns include:** * AWS Access Keys and Secret Keys * GitHub Personal Access Tokens * Database connection strings * API keys (various providers) * Private keys (RSA, SSH, etc.) * Credit card numbers * Social Security Numbers * Email addresses * Phone numbers ### Blocking Sensitive Data Configure the gateway to block requests containing sensitive data: ```typescript theme={null} // Configure blocking rules await leanmcp.gateway.setSecurityRules({ blockPatterns: [ { type: 'aws_key', action: 'block' }, { type: 'api_key', action: 'block' }, { type: 'password', action: 'warn' }, ], alertOnDetection: true, }); ``` #### Action Types | Action | Behavior | | ---------- | ---------------------------------------------------------- | | **block** | Request is rejected, never sent to AI provider | | **warn** | Request proceeds but alert is generated | | **redact** | Sensitive data is replaced with \[REDACTED] before sending | | **log** | Request proceeds, logged for review | ### Remediation When sensitive data is detected: 1. **Review the log** - see exactly what was exposed 2. **Rotate credentials** - change any exposed secrets immediately 3. **Update your code** - ensure secrets aren't in files that get sent to AI 4. **Enable blocking** - prevent future exposure Keep secrets in `.env` files and ensure `.env` is in your `.gitignore`. Most AI assistants respect gitignore patterns. ## Blocking Malicious Users When building AI-powered applications, you need to protect against abuse. ### Common Abuse Patterns Users trying to manipulate your AI to bypass restrictions Users making excessive requests to run up your AI costs Attempts to extract training data or system prompts Trying to make the AI produce harmful content ### User Blocking Block abusive users instantly: Block user interface ```typescript theme={null} // Block a user via API await leanmcp.gateway.blockUser({ userId: 'abusive-user-123', reason: 'Excessive usage and prompt injection attempts', duration: 'permanent', // or '24h', '7d', etc. }); ``` When a blocked user makes a request: * Request is immediately rejected * No tokens are consumed * Event is logged for audit ### Unblocking Users ```typescript theme={null} // Unblock a user await leanmcp.gateway.unblockUser({ userId: 'user-123', reason: 'Issue resolved, user warned', }); ``` ### Viewing Blocked Users Access the block list at [app.leanmcp.com/security](https://app.leanmcp.com/security): 1. Open [app.leanmcp.com/security](https://app.leanmcp.com/security) → **Blocked Users** 2. View all blocked users with reasons and timestamps 3. Manage blocks (extend, reduce, remove) ## Rate Limiting Prevent abuse with intelligent rate limiting: ```typescript theme={null} // Set rate limits await leanmcp.gateway.setRateLimit({ scope: 'per_user', limits: { requestsPerMinute: 20, requestsPerHour: 200, tokensPerDay: 500000, maxCostPerMonth: 100.00, }, action: 'block', // or 'queue', 'throttle' }); ``` ### Rate Limit Strategies | Strategy | Use Case | | --------------- | --------------------------------- | | **Per User** | Limit individual user consumption | | **Per IP** | Prevent anonymous abuse | | **Per API Key** | Limit by integration | | **Global** | Overall service protection | ### Handling Rate Limits When users hit limits: ```typescript theme={null} // Client receives 429 response { "error": { "code": "rate_limit_exceeded", "message": "Too many requests. Please try again in 60 seconds.", "retry_after": 60 } } ``` ## Content Filtering Block requests based on content: ### Input Filtering ```typescript theme={null} // Block certain input patterns await leanmcp.gateway.setContentFilter({ inputFilters: [ { pattern: 'ignore previous instructions', action: 'block' }, { pattern: 'reveal your system prompt', action: 'block' }, { pattern: /jailbreak/i, action: 'warn' }, ] }); ``` ### Output Filtering ```typescript theme={null} // Filter AI responses await leanmcp.gateway.setContentFilter({ outputFilters: [ { pattern: 'internal_api_endpoint', action: 'redact' }, { pattern: /\b\d{4}-\d{4}-\d{4}-\d{4}\b/, action: 'redact' }, // Credit cards ] }); ``` ## Audit Logging All security events are logged: Audit log | Event Type | Details Logged | | ----------------------------- | --------------------------------- | | **blocked\_request** | User, reason, request content | | **sensitive\_data\_detected** | Pattern, location, severity | | **rate\_limit\_hit** | User, limit type, current count | | **user\_blocked** | User, reason, admin who blocked | | **user\_unblocked** | User, reason, admin who unblocked | ### Export Audit Logs For compliance and review: ```bash theme={null} curl -X GET "https://api.leanmcp.com/gateway/audit-logs?days=30" \ -H "Authorization: Bearer your-api-key" \ -o audit-logs.json ``` ## Security Alerts Get notified of security events: ### Alert Configuration ```typescript theme={null} await leanmcp.gateway.createSecurityAlert({ events: ['sensitive_data_detected', 'rate_limit_exceeded', 'suspicious_pattern'], channels: { email: ['security@yourcompany.com'], slack: 'https://hooks.slack.com/...', webhook: 'https://your-server.com/alerts', }, severity: 'medium', // or 'low', 'high', 'critical' }); ``` ### Alert Examples Security alert example ## Best Practices Begin with 'warn' actions to understand what would be blocked, then switch to 'block' once tuned. Check blocked requests weekly to ensure legitimate users aren't being affected. Configure security alerts before launch so you're notified of issues immediately. Set limits that allow normal use while preventing abuse. Adjust based on observed patterns. Make sure users know your usage policies and what behavior will result in blocking. ## Next Steps Monitor all requests and detect issues Reduce costs while maintaining quality *** View your first logged AI request at **app.leanmcp.com/observability** # Token Optimization Source: https://docs.leanmcp.com/ai-gateway/token-optimization Reduce AI costs through A/B testing, usage analysis, and smart optimization # Token Optimization The AI Gateway provides tools to understand, analyze, and optimize your AI token usage. Reduce costs while maintaining quality through data-driven decisions. ## Understanding Your Usage ### Token Analytics Dashboard Token analytics dashboard See exactly where your tokens are going: * **By model** - Compare costs across GPT-5.2, Claude, etc. * **By feature** - Which parts of your app use the most tokens * **By user** - Identify heavy users and usage patterns * **Over time** - Track trends and spot anomalies ### Cost Breakdown | Metric | Description | | ---------------------- | ------------------------------------------------- | | **Input Tokens** | Tokens in the prompt you send | | **Output Tokens** | Tokens in the AI response | | **Total Cost** | Combined cost (output tokens typically cost more) | | **Requests** | Number of API calls | | **Avg Tokens/Request** | Efficiency metric | ## A/B Testing Test different approaches to find the most cost-effective solution: A/B test setup ### What to Test GPT-5.2 vs GPT-5.2 vs Claude * Quality vs cost tradeoffs * Task-specific performance Different system prompts * Shorter vs detailed instructions * Different tones/styles How much context to include * Minimal vs comprehensive * Impact on quality Model creativity settings * Lower for consistent outputs * Higher for variety ### Setting Up an A/B Test ```typescript theme={null} // Create an experiment const experiment = await leanmcp.gateway.createExperiment({ name: 'Model Comparison Q1 2024', variants: [ { name: 'gpt-5.2', weight: 50, config: { model: 'gpt-5.2' } }, { name: 'claude-3', weight: 50, config: { model: 'claude-sonnet-4-5-20250929' } }, ], metrics: ['quality_rating', 'tokens_used', 'latency', 'cost'], duration: '14d', }); ``` ### Using Experiments in Code ```typescript theme={null} // Get variant for user const variant = await leanmcp.gateway.getVariant({ experimentId: experiment.id, userId: userId, }); // Use the assigned model const response = await client.chat.completions.create({ model: variant.config.model, messages: messages, }, { headers: { 'X-Experiment-ID': experiment.id, 'X-Variant': variant.name, } }); ``` ### Tracking Outcomes ```typescript theme={null} // Record quality metric (e.g., from user feedback) await leanmcp.gateway.recordOutcome({ experimentId: experiment.id, userId: userId, outcome: { quality_rating: 4.5, // 1-5 scale user_satisfied: true, } }); ``` ### Analyzing Results A/B test results The dashboard shows: * **Statistical significance** - Is the difference real? * **Cost comparison** - Savings per variant * **Quality metrics** - User satisfaction scores * **Recommendation** - Which variant to choose ## Competitor Insights Learn from how others optimize: Industry benchmarks ### Benchmarking Compare your usage to industry averages: * **Tokens per request** - Are your prompts too long? * **Model distribution** - Are you using expensive models unnecessarily? * **Error rates** - Are you making inefficient retries? ### Learning from Patterns Aggregated, anonymized insights from the platform help you understand best practices without exposing anyone's specific implementation. Common optimizations we've identified: * **60% of GPT-5.2 usage can use GPT-5.2** with minimal quality loss * **Shorter system prompts** often perform equally well * **Caching common queries** reduces costs by 30-40% ## Optimization Strategies ### 1. Right-Size Your Models Not every request needs GPT-5.2: ```typescript theme={null} // Route based on complexity const model = estimateComplexity(message) > 0.7 ? 'gpt-5.2' : 'gpt-5.2'; const response = await client.chat.completions.create({ model: model, messages: messages, }); ``` Smart model routing ### 2. Optimize Prompts Only include information the model actually needs. More context = more tokens. "You are a helpful coding assistant" works as well as a 500-word description for most tasks. Use `max_tokens` to prevent unnecessarily long responses. "Reply in JSON format" or "Answer in one sentence" reduces output tokens. ### 3. Implement Caching Cache identical or similar requests: ```typescript theme={null} // Enable response caching const response = await client.chat.completions.create({ model: 'gpt-5.2', messages: messages, }, { headers: { 'X-Enable-Cache': 'true', 'X-Cache-TTL': '3600', // 1 hour } }); ``` ### 4. Batch Similar Requests Combine multiple small requests: ```typescript theme={null} // Instead of 10 separate calls // Batch into one request with multiple items const response = await client.chat.completions.create({ model: 'gpt-5.2', messages: [{ role: 'user', content: `Analyze these 10 items:\n${items.join('\n')}` }], }); ``` ## Cost Alerts and Limits ### Budget Controls ```typescript theme={null} // Set spending limits await leanmcp.gateway.setBudget({ daily: 50.00, weekly: 200.00, monthly: 500.00, action: 'alert', // or 'block' to hard stop }); ``` ### Alert Configuration ```typescript theme={null} await leanmcp.gateway.createCostAlert({ threshold: 100.00, // dollars period: 'daily', channels: ['email', 'slack'], }); ``` Cost alert notification ## Reporting ### Usage Reports Generate detailed reports: ```typescript theme={null} const report = await leanmcp.gateway.generateReport({ type: 'usage', period: 'monthly', groupBy: ['model', 'feature', 'user'], format: 'pdf', }); ``` ### Export for Analysis ```bash theme={null} # Export token usage data curl -X GET "https://api.leanmcp.com/gateway/usage?period=monthly" \ -H "Authorization: Bearer your-api-key" \ -o usage-report.json ``` ## ROI Calculator Understand the value of optimization: ROI calculator | Scenario | Current Cost | Optimized Cost | Savings | | ------------------- | -------------- | -------------- | ------- | | Model right-sizing | \$1,000/mo | \$600/mo | 40% | | Prompt optimization | \$600/mo | \$450/mo | 25% | | Caching | \$450/mo | \$350/mo | 22% | | **Total** | **\$1,000/mo** | **\$350/mo** | **65%** | ## Best Practices You can't optimize what you don't measure. Set up tracking before making changes. Run isolated A/B tests to understand the impact of each change. The cheapest option isn't always the best. Track quality metrics alongside cost. Usage patterns change. Schedule monthly reviews of your optimization strategies. Budget alerts prevent surprise bills and catch issues quickly. ## Next Steps Set up the AI Gateway Complete code examples *** View your token usage and AI request logs at **app.leanmcp.com/observability** # Windsurf Source: https://docs.leanmcp.com/ai-gateway/windsurf Use the LeanMCP AI Gateway with Windsurf IDE # Windsurf Integration [Windsurf](https://codeium.com/windsurf) is an AI-powered IDE by Codeium. You can route Windsurf's AI requests through the LeanMCP AI Gateway to monitor what code is being sent to AI providers. ## Prerequisites Purchase credits at [leanmcp.com](https://leanmcp.com) Create an API key at [leanmcp.com/api-keys](https://leanmcp.com/api-keys) with **SDK** permissions ## Configuration Press `Cmd + ,` (Mac) or `Ctrl + ,` (Windows/Linux) to open Settings Or click the gear icon in the bottom left corner In the search bar, type "AI" or "API" to find the AI configuration section Look for settings related to custom AI providers or API endpoints: **Base URL / API Endpoint:** ``` https://aigateway.leanmcp.com/v1/openai ``` **API Key:** ``` leanmcp_your_api_key_here ``` Windsurf settings Apply your settings and restart Windsurf. ## Alternative: Settings JSON You can also configure Windsurf via its settings JSON file: ```json theme={null} { "ai.provider": "openai-compatible", "ai.baseUrl": "https://aigateway.leanmcp.com/v1/openai", "ai.apiKey": "leanmcp_your_api_key_here" } ``` ## Verifying the Setup 1. Open a project in Windsurf 2. Use the AI assistant (Cascade) to ask a question about your code 3. Check your [LeanMCP Dashboard](https://leanmcp.com) to see the request logged ## What You Can Monitor With the AI Gateway, you can see: | Data | Description | | ------------------ | ------------------------------------ | | **Code Context** | Exact code snippets sent to the AI | | **File Paths** | Which files are included in requests | | **Token Usage** | Input and output tokens per request | | **Costs** | Real-time cost tracking | | **Sensitive Data** | Detected secrets, keys, or PII | ## Why Use AI Gateway with Windsurf? Windsurf sends surrounding code as context. Know exactly what's included. Get alerts if API keys or passwords are accidentally sent to AI. Monitor your AI spending across all coding sessions. Maintain audit logs for security and compliance requirements. ## Troubleshooting * Check your API key is valid and has credits * Verify the base URL is correct * Ensure Windsurf is using the custom endpoint (check settings) * Make sure to save settings before closing * Try editing the settings JSON file directly * Restart Windsurf after making changes * Confirm you're using the LeanMCP API key (starts with `leanmcp_`) * Wait a few seconds - there may be a brief delay * Check the correct workspace/account in the dashboard ## Next Steps See all your Windsurf AI requests Get notified of sensitive data exposure # Build MCP Server Source: https://docs.leanmcp.com/api-reference/build POST https://api.leanmcp.com/v1/build Create a new MCP server from templates and configuration Build a new MCP server with your tools, resources, and prompts. ## Request Name of your MCP server Template to use. Options: `basic`, `advanced`, `custom` Array of tools your MCP provides Tool identifier (e.g., "send\_email") What this tool does (helps AI understand when to use it) JSON Schema for tool inputs Array of resources your MCP provides Resource URI (e.g., "user://profile") Human-readable name What data this resource provides Content type (default: "application/json") Array of prompt templates your MCP provides Prompt identifier What this prompt helps with Prompt template with variables ## Response Whether the build succeeded Unique ID for your MCP server Name of your MCP server Build status: "building", "ready", "failed" URL where your MCP is accessible Build time in seconds ```bash Example Request theme={null} curl -X POST https://api.leanmcp.com/v1/build \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "email-assistant", "template": "basic", "tools": [ { "name": "send_email", "description": "Send an email to someone", "inputSchema": { "type": "object", "properties": { "to": {"type": "string"}, "subject": {"type": "string"}, "body": {"type": "string"} }, "required": ["to", "subject", "body"] } } ], "resources": [ { "uri": "user://contacts", "name": "User Contacts", "description": "List of user contact information", "mimeType": "application/json" } ] }' ``` ```json Success Response theme={null} { "success": true, "data": { "mcp_id": "mcp_abc123def456", "name": "email-assistant", "status": "ready", "url": "https://mcp-abc123def456.leanmcp.com", "build_time": 8.5 }, "meta": { "request_id": "req_789xyz", "timestamp": "2023-12-01T12:00:00Z" } } ``` ## Common Use Cases ### Simple Tool MCP Build an MCP with just tools (no resources or prompts): ```json theme={null} { "name": "calculator", "template": "basic", "tools": [ { "name": "add", "description": "Add two numbers together", "inputSchema": { "type": "object", "properties": { "a": {"type": "number"}, "b": {"type": "number"} } } } ] } ``` ### Data Access MCP Build an MCP that gives AI access to your data: ```json theme={null} { "name": "user-data", "template": "basic", "resources": [ { "uri": "user://profile", "name": "User Profile", "description": "Current user profile information" }, { "uri": "user://settings", "name": "User Settings", "description": "User application settings" } ] } ``` # Add a new message to a specific chat via API key Source: https://docs.leanmcp.com/api-reference/chat-messages-api-key/add-a-new-message-to-a-specific-chat-via-api-key post /api/chat-messages/chat/id/{chatId} Convenient endpoint to add a message to a chat using API key authentication. MessageIndex is auto-calculated. Requires CHAT scope. # Add multiple messages to a chat via API key Source: https://docs.leanmcp.com/api-reference/chat-messages-api-key/add-multiple-messages-to-a-chat-via-api-key post /api/chat-messages/chat/id/{chatId}/bulk Bulk insert multiple messages into a chat using API key authentication. MessageIndex values are auto-calculated sequentially. Requires CHAT scope. # Create a new message in a chat via API key Source: https://docs.leanmcp.com/api-reference/chat-messages-api-key/create-a-new-message-in-a-chat-via-api-key post /api/chat-messages Create a message with explicit chatId using API key authentication. MessageIndex is auto-calculated if not provided. Requires CHAT scope. # Delete a message via API key Source: https://docs.leanmcp.com/api-reference/chat-messages-api-key/delete-a-message-via-api-key delete /api/chat-messages/id/{id} Delete a specific message using API key authentication. Requires CHAT scope. # Get all messages for a chat via API key Source: https://docs.leanmcp.com/api-reference/chat-messages-api-key/get-all-messages-for-a-chat-via-api-key get /api/chat-messages/chat/id/{chatId} Returns all messages for a specific chat ordered by messageIndex using API key authentication. Requires CHAT scope. # Get message by ID via API key Source: https://docs.leanmcp.com/api-reference/chat-messages-api-key/get-message-by-id-via-api-key get /api/chat-messages/id/{id} Get a specific message by its ID using API key authentication. Requires CHAT scope. # Get message count for a chat via API key Source: https://docs.leanmcp.com/api-reference/chat-messages-api-key/get-message-count-for-a-chat-via-api-key get /api/chat-messages/chat/id/{chatId}/count Returns the total number of messages in a chat using API key authentication. Requires CHAT scope. # Get the latest message in a chat via API key Source: https://docs.leanmcp.com/api-reference/chat-messages-api-key/get-the-latest-message-in-a-chat-via-api-key get /api/chat-messages/chat/id/{chatId}/latest Returns the message with the highest messageIndex in the chat using API key authentication. Requires CHAT scope. # Get user's recent messages via API key Source: https://docs.leanmcp.com/api-reference/chat-messages-api-key/get-users-recent-messages-via-api-key get /api/chat-messages/user/recent Retrieve recent messages by the user across all their chats using API key authentication. Requires CHAT scope. # Update message content via API key Source: https://docs.leanmcp.com/api-reference/chat-messages-api-key/update-message-content-via-api-key patch /api/chat-messages/id/{id} Update message content or metadata using API key authentication. Requires CHAT scope. # Create a new chat via API key Source: https://docs.leanmcp.com/api-reference/chats-api-key/create-a-new-chat-via-api-key post /api/chats Create a new chat using API key authentication. Requires CHAT scope. # Delete chat via API key Source: https://docs.leanmcp.com/api-reference/chats-api-key/delete-chat-via-api-key delete /api/chats/id/{id} Delete entire chat and all its messages using API key authentication. Requires CHAT scope. # Get all user chats via API key Source: https://docs.leanmcp.com/api-reference/chats-api-key/get-all-user-chats-via-api-key get /api/chats Get all chats for the user associated with the API key. Requires CHAT scope. # Get chat by ID via API key Source: https://docs.leanmcp.com/api-reference/chats-api-key/get-chat-by-id-via-api-key get /api/chats/id/{id} Get chat metadata by ID using API key authentication. Requires CHAT scope. # Get chat history (raw messages only) via API key Source: https://docs.leanmcp.com/api-reference/chats-api-key/get-chat-history-raw-messages-only-via-api-key get /api/chats/id/{id}/history/raw Returns just the messages for a chat without full metadata using API key authentication. Requires CHAT scope. # Get complete chat history (chat + messages) via API key Source: https://docs.leanmcp.com/api-reference/chats-api-key/get-complete-chat-history-chat-+-messages-via-api-key get /api/chats/id/{id}/history/full Returns chat metadata along with all messages in the conversation using API key authentication. Requires CHAT scope. # Get current API key information Source: https://docs.leanmcp.com/api-reference/chats-api-key/get-current-api-key-information get /api/chats/api-key/info Returns information about the API key being used for authentication # Get recent chats via API key Source: https://docs.leanmcp.com/api-reference/chats-api-key/get-recent-chats-via-api-key get /api/chats/recent/{limit} Get user's most recent chats up to specified limit using API key authentication. Requires CHAT scope. # Get user chat statistics via API key Source: https://docs.leanmcp.com/api-reference/chats-api-key/get-user-chat-statistics-via-api-key get /api/chats/stats Get chat statistics for the user associated with the API key. Requires CHAT scope. # Update chat metadata via API key Source: https://docs.leanmcp.com/api-reference/chats-api-key/update-chat-metadata-via-api-key patch /api/chats/id/{id} Update chat title, summary, etc. using API key authentication. Requires CHAT scope. # Deploy MCP Server Source: https://docs.leanmcp.com/api-reference/deploy POST https://api.leanmcp.com/v1/deploy Deploy your MCP server to production Deploy your tested MCP server to production environments. ## Request ID of the MCP server to deploy Deployment environment. Options: `production`, `staging`, `development` Deployment configuration Custom domain for your MCP (optional) Auto-scaling configuration Minimum number of instances (default: 1) Maximum number of instances (default: 10) Environment variables for your MCP ## Response Whether the deployment succeeded Unique deployment ID Deployment status: "deploying", "deployed", "failed" Production URL of your MCP Deployment time in seconds Health check endpoint URL ```bash Production Deploy theme={null} curl -X POST https://api.leanmcp.com/v1/deploy \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "mcp_id": "mcp_abc123def456", "environment": "production", "config": { "domain": "my-mcp.example.com", "scaling": { "min_instances": 2, "max_instances": 20 }, "env_vars": { "API_KEY": "your-api-key", "DEBUG": "false" } } }' ``` ```bash Staging Deploy theme={null} curl -X POST https://api.leanmcp.com/v1/deploy \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "mcp_id": "mcp_abc123def456", "environment": "staging" }' ``` ```json Success Response theme={null} { "success": true, "data": { "deployment_id": "deploy_xyz789abc", "status": "deployed", "url": "https://my-mcp.example.com", "deploy_time": 45.2, "health_check": "https://my-mcp.example.com/health" }, "meta": { "request_id": "req_deploy_456", "timestamp": "2023-12-01T12:00:00Z" } } ``` ## Deployment Environments ### Production * **High availability** - Multiple instances across regions * **Auto-scaling** - Scales based on demand * **Monitoring** - Full observability and alerting * **Custom domains** - Use your own domain * **SSL certificates** - Automatic HTTPS ### Staging * **Pre-production testing** - Test before going live * **Shared resources** - Lower cost than production * **Limited scaling** - Fewer instances * **Temporary URLs** - Auto-generated domains ### Development * **Quick iterations** - Fast deploy cycles * **Single instance** - Minimal resources * **Debug mode** - Enhanced logging * **Auto-cleanup** - Removes old deployments ## Deployment Status Check deployment status: ```bash theme={null} GET https://api.leanmcp.com/v1/deploy/deploy_xyz789abc/status ``` Possible statuses: * **deploying**: Deployment in progress * **deployed**: Successfully deployed and running * **failed**: Deployment failed * **stopped**: Deployment manually stopped ## Best Practices ### Before Deploying 1. **Test thoroughly** - Use `/v1/test` endpoint 2. **Check performance** - Monitor response times 3. **Validate tools** - Ensure all tools work correctly 4. **Review logs** - Check for errors in development ### Production Checklist * [ ] All tools tested with AI * [ ] Resource access permissions configured * [ ] Environment variables set * [ ] Custom domain configured (if needed) * [ ] Monitoring alerts configured * [ ] Backup strategy planned ### Rollback Plan If deployment fails: 1. Check deployment logs 2. Fix issues in development 3. Redeploy with fixes 4. Or rollback to previous version ## Monitoring Once deployed, monitor your MCP: * **Health checks** - Automatic uptime monitoring * **Performance metrics** - Response times and throughput * **Error rates** - Track failed tool calls * **Usage analytics** - See how AI agents use your MCP # Attach a subdomain to a deployment via API key Source: https://docs.leanmcp.com/api-reference/deployments-api-key/attach-a-subdomain-to-a-deployment-via-api-key post /api/deployments/{id}/domains Attach a custom subdomain to a deployment using API key authentication. Requires BUILD_AND_DEPLOY scope. # Create a new deployment from a build via API key Source: https://docs.leanmcp.com/api-reference/deployments-api-key/create-a-new-deployment-from-a-build-via-api-key post /api/deployments Create a new deployment from a build using API key authentication. Requires BUILD_AND_DEPLOY scope. # Delete a deployment via API key Source: https://docs.leanmcp.com/api-reference/deployments-api-key/delete-a-deployment-via-api-key delete /api/deployments/{id} Delete a deployment and stop the associated ECS service using API key authentication. Requires BUILD_AND_DEPLOY scope. # Get a deployment by ID via API key Source: https://docs.leanmcp.com/api-reference/deployments-api-key/get-a-deployment-by-id-via-api-key get /api/deployments/{id} Get deployment details by ID using API key authentication. Requires BUILD_AND_DEPLOY scope. # Get current API key information Source: https://docs.leanmcp.com/api-reference/deployments-api-key/get-current-api-key-information get /api/deployments/api-key/info Returns information about the API key being used for authentication # Get deployment for a build via API key Source: https://docs.leanmcp.com/api-reference/deployments-api-key/get-deployment-for-a-build-via-api-key get /api/deployments/build/{buildId} Get deployment associated with a specific build using API key authentication. Requires BUILD_AND_DEPLOY scope. # Get deployments for a project via API key Source: https://docs.leanmcp.com/api-reference/deployments-api-key/get-deployments-for-a-project-via-api-key get /api/deployments/project/{projectId} Get all deployments for a specific project using API key authentication. Requires BUILD_AND_DEPLOY scope. # LeanMCP API Source: https://docs.leanmcp.com/api-reference/introduction Build, test, and deploy MCPs using the LeanMCP API # LeanMCP API Reference Use the LeanMCP API to programmatically build, test, and deploy your MCP servers. ## What You Can Do The LeanMCP API lets you: * **Build** MCP servers from templates * **Test** MCPs with AI agents * **Deploy** to production environments * **Monitor** MCP performance * **Manage** your MCP projects ## Authentication All API requests require an API key: ```bash theme={null} curl -H "Authorization: Bearer YOUR_API_KEY" \ https://api.leanmcp.com/v1/build ``` Get your API key from the [LeanMCP Dashboard](https://dashboard.leanmcp.com). ## Base URL ``` https://api.leanmcp.com/v1 ``` ## Common Patterns ### Build an MCP Server ```bash theme={null} POST /v1/build { "name": "my-mcp-server", "template": "basic", "tools": [...], "resources": [...] } ``` ### Test with AI ```bash theme={null} POST /v1/test { "mcp_id": "your-mcp-id", "message": "Help me use the tools", "ai_model": "claude-3" } ``` ### Deploy to Production ```bash theme={null} POST /v1/deploy { "mcp_id": "your-mcp-id", "environment": "production" } ``` ## Response Format All API responses follow this format: ```json theme={null} { "success": true, "data": { /* response data */ }, "meta": { "request_id": "req_123", "timestamp": "2023-12-01T12:00:00Z" } } ``` ## Error Handling Errors return HTTP status codes with details: ```json theme={null} { "success": false, "error": { "code": "INVALID_MCP", "message": "MCP server configuration is invalid", "details": "Tool 'example' is missing required field 'description'" } } ``` ## Rate Limits * **Free Plan**: 100 requests/hour * **Pro Plan**: 1,000 requests/hour * **Enterprise**: Custom limits Rate limit headers are included in responses: ``` X-RateLimit-Limit: 100 X-RateLimit-Remaining: 95 X-RateLimit-Reset: 1640995200 ``` ## SDKs Official Node.js SDK for LeanMCP API Official Python SDK for LeanMCP API Use any HTTP client with our REST API Command line interface for development ## Need Help? * [API Examples](https://github.com/leanmcp/api-examples) * [Community Discord](https://discord.gg/leanmcp) * [Support Email](mailto:support@leanmcp.com) # Delete a Lambda build (API Key) Source: https://docs.leanmcp.com/api-reference/lambda-builds-sdk/delete-a-lambda-build-api-key delete /api/lambda-builds/{id} # Get a Lambda build by ID (API Key) Source: https://docs.leanmcp.com/api-reference/lambda-builds-sdk/get-a-lambda-build-by-id-api-key get /api/lambda-builds/{id} # Get all Lambda builds for a project (API Key) Source: https://docs.leanmcp.com/api-reference/lambda-builds-sdk/get-all-lambda-builds-for-a-project-api-key get /api/lambda-builds/project/{projectId} # Get Lambda build logs (API Key) Source: https://docs.leanmcp.com/api-reference/lambda-builds-sdk/get-lambda-build-logs-api-key get /api/lambda-builds/{id}/logs # Trigger a Lambda build for a project (API Key) Source: https://docs.leanmcp.com/api-reference/lambda-builds-sdk/trigger-a-lambda-build-for-a-project-api-key post /api/lambda-builds/trigger/{projectId} # Check health of a Lambda deployment (API Key) Source: https://docs.leanmcp.com/api-reference/lambda-deployments-api/check-health-of-a-lambda-deployment-api-key get /api/lambda-deploy/{id}/health # Create a new Lambda deployment from a build (API Key) Source: https://docs.leanmcp.com/api-reference/lambda-deployments-api/create-a-new-lambda-deployment-from-a-build-api-key post /api/lambda-deploy # Delete a Lambda deployment (API Key) Source: https://docs.leanmcp.com/api-reference/lambda-deployments-api/delete-a-lambda-deployment-api-key delete /api/lambda-deploy/{id} # Get a Lambda deployment by ID (API Key) Source: https://docs.leanmcp.com/api-reference/lambda-deployments-api/get-a-lambda-deployment-by-id-api-key get /api/lambda-deploy/{id} # Get all Lambda deployments for the current user (API Key) Source: https://docs.leanmcp.com/api-reference/lambda-deployments-api/get-all-lambda-deployments-for-the-current-user-api-key get /api/lambda-deploy # Get Lambda deployment for a build (API Key) Source: https://docs.leanmcp.com/api-reference/lambda-deployments-api/get-lambda-deployment-for-a-build-api-key get /api/lambda-deploy/build/{buildId} # Get Lambda deployments for a project (API Key) Source: https://docs.leanmcp.com/api-reference/lambda-deployments-api/get-lambda-deployments-for-a-project-api-key get /api/lambda-deploy/project/{projectId} # Update Lambda deployment configuration (API Key) Source: https://docs.leanmcp.com/api-reference/lambda-deployments-api/update-lambda-deployment-configuration-api-key patch /api/lambda-deploy/{id} # Check if a subdomain is available (API Key) Source: https://docs.leanmcp.com/api-reference/lambda-mapping-api/check-if-a-subdomain-is-available-api-key get /api/lambda-mapping/check/{subdomain} # Create a new subdomain mapping (API Key) Source: https://docs.leanmcp.com/api-reference/lambda-mapping-api/create-a-new-subdomain-mapping-api-key post /api/lambda-mapping # Delete a subdomain mapping (API Key) Source: https://docs.leanmcp.com/api-reference/lambda-mapping-api/delete-a-subdomain-mapping-api-key delete /api/lambda-mapping/{subdomain} # Get all mappings for a project (API Key) Source: https://docs.leanmcp.com/api-reference/lambda-mapping-api/get-all-mappings-for-a-project-api-key get /api/lambda-mapping/project/{projectId} # Get all mappings for current user (API Key) Source: https://docs.leanmcp.com/api-reference/lambda-mapping-api/get-all-mappings-for-current-user-api-key get /api/lambda-mapping/user # Get mapping by subdomain (API Key) Source: https://docs.leanmcp.com/api-reference/lambda-mapping-api/get-mapping-by-subdomain-api-key get /api/lambda-mapping/subdomain/{subdomain} # Update a subdomain mapping (API Key) Source: https://docs.leanmcp.com/api-reference/lambda-mapping-api/update-a-subdomain-mapping-api-key put /api/lambda-mapping/{subdomain} # Archive project via API key Source: https://docs.leanmcp.com/api-reference/projects-api-key/archive-project-via-api-key post /api/projects/{id}/archive Archive a project using API key authentication. Requires BUILD_AND_DEPLOY scope. # Create a new project via API key Source: https://docs.leanmcp.com/api-reference/projects-api-key/create-a-new-project-via-api-key post /api/projects Create a new project using API key authentication. Requires BUILD_AND_DEPLOY scope. # Delete project via API key Source: https://docs.leanmcp.com/api-reference/projects-api-key/delete-project-via-api-key delete /api/projects/{id} Delete project using API key authentication. Requires BUILD_AND_DEPLOY scope. # Get all projects for user via API key Source: https://docs.leanmcp.com/api-reference/projects-api-key/get-all-projects-for-user-via-api-key get /api/projects Get all projects for the user associated with the API key. Requires BUILD_AND_DEPLOY scope. # Get current API key information Source: https://docs.leanmcp.com/api-reference/projects-api-key/get-current-api-key-information get /api/projects/api-key/info Returns information about the API key being used for authentication # Get project builds via API key Source: https://docs.leanmcp.com/api-reference/projects-api-key/get-project-builds-via-api-key get /api/projects/{id}/builds Get all builds for a project using API key authentication. Requires BUILD_AND_DEPLOY scope. # Get project by ID via API key Source: https://docs.leanmcp.com/api-reference/projects-api-key/get-project-by-id-via-api-key get /api/projects/{id} Get project details by ID using API key authentication. Requires BUILD_AND_DEPLOY scope. # Get upload URL for project files via API key Source: https://docs.leanmcp.com/api-reference/projects-api-key/get-upload-url-for-project-files-via-api-key post /api/projects/{id}/upload-url Get a pre-signed URL for uploading project files using API key authentication. Requires BUILD_AND_DEPLOY scope. # Start build for project via API key Source: https://docs.leanmcp.com/api-reference/projects-api-key/start-build-for-project-via-api-key post /api/projects/{id}/build Start a new build for the project using API key authentication. Requires BUILD_AND_DEPLOY scope. # Update project S3 location via API key Source: https://docs.leanmcp.com/api-reference/projects-api-key/update-project-s3-location-via-api-key post /api/projects/{id}/s3-location Update project S3 location after successful upload using API key authentication. Requires BUILD_AND_DEPLOY scope. # Update project via API key Source: https://docs.leanmcp.com/api-reference/projects-api-key/update-project-via-api-key patch /api/projects/{id} Update project details using API key authentication. Requires BUILD_AND_DEPLOY scope. # Test MCP Server Source: https://docs.leanmcp.com/api-reference/test POST https://api.leanmcp.com/v1/test Test your MCP server with AI agents Test how AI agents interact with your MCP server. ## Request ID of the MCP server to test Message to send to the AI agent AI model to use for testing. Options: `claude-sonnet-4-5`, `gpt-5.2`, `gpt-5.2` Default: `claude-sonnet-4-5` Additional context for the AI agent User ID for resource access Session-specific data ## Response Whether the test completed successfully The AI agent's response List of tools the AI called List of resources the AI accessed Test execution time in seconds Success rate of tool calls (0-1) ```bash Example Request theme={null} curl -X POST https://api.leanmcp.com/v1/test \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "mcp_id": "mcp_abc123def456", "message": "Send an email to john@example.com saying hello", "ai_model": "claude-sonnet-4-5", "context": { "user_id": "user_123" } }' ``` ```json Success Response theme={null} { "success": true, "data": { "ai_response": "I've sent an email to john@example.com with the subject 'Hello' and a friendly greeting message.", "tools_used": ["send_email"], "resources_accessed": ["user://contacts"], "execution_time": 2.3, "success_rate": 1.0 }, "meta": { "request_id": "req_test_789", "timestamp": "2023-12-01T12:00:00Z" } } ``` ## Testing Best Practices ### Start Simple Test basic tool usage first: ```json theme={null} { "mcp_id": "your-mcp-id", "message": "What tools do you have available?", "ai_model": "claude-sonnet-4-5" } ``` ### Test Edge Cases Try confusing or ambiguous requests: ```json theme={null} { "mcp_id": "your-mcp-id", "message": "Do that thing with the data", "ai_model": "claude-sonnet-4-5" } ``` ### Test Complex Workflows Chain multiple tool calls together: ```json theme={null} { "mcp_id": "your-mcp-id", "message": "Check my contacts, find John's email, and send him a meeting invite for tomorrow at 2pm", "ai_model": "claude-sonnet-4-5" } ``` ## Interpreting Results ### Success Rate * **1.0**: All tool calls worked perfectly * **0.8-0.9**: Mostly successful, minor issues * **0.5-0.7**: Some failures, needs improvement * **\< 0.5**: Major issues, review tool descriptions ### Common Issues * **Wrong tools used**: Tool descriptions too similar * **Missing tools**: AI needs tools that don't exist * **Failed calls**: Input validation or execution errors * **No tools used**: AI didn't understand the request # MCP Best Practices Source: https://docs.leanmcp.com/building/best-practices The art of building MCPs that AI agents love to use # The Art of Building Great MCPs Building good MCPs is an art. You want to minimize wrong tool calls and maximize AI success. ## Core Principles ### 1. Less is More Don't expose every API endpoint as an MCP tool. Pick the most important ones. **❌ Bad:** 50 similar tools confuse AI ```json theme={null} ["create_user", "add_user", "new_user", "register_user", "signup_user"] ``` **✅ Good:** One clear tool per action ```json theme={null} ["create_user"] ``` ### 2. Crystal Clear Descriptions AI depends on your descriptions to understand when to use tools. **❌ Bad:** Vague description ```json theme={null} { "name": "process_data", "description": "Processes data" } ``` **✅ Good:** Specific description ```json theme={null} { "name": "calculate_tax", "description": "Calculate tax amount for a purchase based on item price and location" } ``` ### 3. Simple Schemas Complex input schemas lead to AI errors. **❌ Bad:** Nested complexity ```json theme={null} { "properties": { "user": { "type": "object", "properties": { "profile": { "type": "object", "properties": { "settings": { /* more nesting... */ } } } } } } } ``` **✅ Good:** Flat and simple ```json theme={null} { "properties": { "user_id": {"type": "string"}, "email": {"type": "string"}, "name": {"type": "string"} } } ``` ## Tool Design Patterns ### Action-Oriented Naming Use verbs that clearly indicate what happens: ```json theme={null} { "send_email": "✅ Clear action", "email": "❌ Unclear if reading or sending", "get_email": "✅ Clear action", "email_data": "❌ What happens to the data?" } ``` ### Consistent Naming Conventions Pick a pattern and stick to it: ```json theme={null} { "get_user": "✅ Consistent pattern", "create_user": "✅ Consistent pattern", "fetchUserData": "❌ Different pattern", "user_delete": "❌ Different pattern" } ``` ### Error-Resistant Parameters Design parameters that are hard to mess up: **❌ Error-prone:** Free text that needs parsing ```json theme={null} { "date": {"type": "string", "description": "Date in any format"} } ``` **✅ Error-resistant:** Structured input ```json theme={null} { "year": {"type": "integer", "minimum": 2020, "maximum": 2030}, "month": {"type": "integer", "minimum": 1, "maximum": 12}, "day": {"type": "integer", "minimum": 1, "maximum": 31} } ``` ## Resource Best Practices ### Meaningful URIs Use URIs that make sense to AI: **✅ Good URIs:** ``` user://profile # User's profile data contacts://list # List of contacts calendar://events # Calendar events tasks://pending # Pending tasks ``` **❌ Bad URIs:** ``` resource://1 # What is resource 1? data://x # What is x? api://endpoint # Too generic ``` ### Clear Resource Descriptions Explain what data the resource contains: **❌ Vague:** ```json theme={null} { "uri": "user://data", "description": "User data" } ``` **✅ Specific:** ```json theme={null} { "uri": "user://profile", "description": "User profile including name, email, preferences, and account settings" } ``` ## Prompt Templates ### Use Variables Wisely Make templates flexible but not complex: **✅ Good template:** ``` Hi {name}, Your meeting with {attendee} is scheduled for {date} at {time}. Best regards, {sender} ``` **❌ Complex template:** ``` {greeting_type} {name_with_title}, {conditional_text_based_on_time_of_day} {meeting_type} with {attendee_list_formatted} is {scheduling_verb} for {date_formatted_long} at {time_with_timezone}. {closing_based_on_relationship}, {sender_with_signature} ``` ## Testing Your Designs ### Test with Realistic Prompts Don't just test if tools work - test if AI chooses the right tools: ``` Test prompts: ❌ "Call the send_email function" ✅ "Email John about tomorrow's meeting" ✅ "Let Sarah know I'll be late" ✅ "Send a thank you note to the client" ``` ### Watch for Confusion Patterns Common signs AI is confused: * **Wrong tool selection:** AI picks similar but wrong tool * **Missing parameters:** AI doesn't provide required fields * **Repeated failures:** AI keeps trying the same broken approach * **No tool usage:** AI gives up and doesn't use any tools ## Common Mistakes ### Mistake #1: Too Many Similar Tools ```json theme={null} // ❌ Confusing for AI ["email_send", "send_email", "mail_send", "dispatch_email"] // ✅ Pick one clear name ["send_email"] ``` ### Mistake #2: Boolean Parameters for Actions ```json theme={null} // ❌ AI gets confused by booleans { "name": "manage_user", "parameters": { "create": {"type": "boolean"}, "delete": {"type": "boolean"} } } // ✅ Separate tools for separate actions { "create_user": { /* parameters */ }, "delete_user": { /* parameters */ } } ``` ### Mistake #3: Generic Error Messages ```json theme={null} // ❌ Unhelpful for AI "An error occurred" // ✅ Specific guidance "Email address must be valid format (example@domain.com)" ``` ## Performance Tips ### Optimize for Common Use Cases Design tools around what users actually ask for: **Common request:** "Schedule a meeting with John tomorrow at 2pm" **❌ Poor design:** Separate tools for each step ``` 1. find_user → get_calendar → check_availability → create_event → send_invite ``` **✅ Good design:** One tool for the complete workflow ``` schedule_meeting(attendee, date, time) → handles all steps internally ``` ### Minimize Tool Chains Long chains of tool calls often fail: **❌ Fragile:** 5-step process where any step can fail **✅ Robust:** 2-step process with error handling ## Quality Checklist Before deploying your MCP: * [ ] **Tool names** clearly indicate actions * [ ] **Descriptions** are specific and helpful * [ ] **Parameters** use simple types when possible * [ ] **Similar tools** have distinct purposes * [ ] **Error messages** guide AI toward correct usage * [ ] **Resources** have meaningful URIs * [ ] **Prompts** use clear variable names * [ ] **Test results** show AI picks correct tools * [ ] **Success rate** is above 80% in testing ## Next Steps Learn how to test your MCPs effectively Explore LeanMCP API features # Introduction Source: https://docs.leanmcp.com/building/introduction Build production-ready MCP servers with LeanMCP # LeanMCP LeanMCP is a framework for building **production-ready MCP servers**. It uses TypeScript decorators, service-based architecture, and schema validation to create AI-ready tool interfaces. Built on top of the official `@modelcontextprotocol/sdk`, LeanMCP uses Express for HTTP transport and implements the Streamable HTTP specification. It provides session management, tool registration, and schema validation out of the box. ```typescript theme={null} import { Tool, SchemaConstraint } from "@leanmcp/core"; class GenerateInput { @SchemaConstraint({ description: "Text prompt for image generation" }) prompt!: string; } export class ImageService { @Tool({ description: "Generate an image", inputClass: GenerateInput }) async generate(input: GenerateInput) { const image = await gemini.generateImage(input.prompt); return { url: image.url }; } } ``` ## Why LeanMCP? A basic MCP connects tools to AI agents. But production means solving real problems: | Problem | LeanMCP Solution | | ----------------- | ------------------------------------------------------------ | | **Auth** | Integrate with Auth0, Supabase, Cognito, Firebase, or custom | | **Multi-tenancy** | Per-user API keys and permissions | | **Elicitation** | Handle user input during tool execution | | **Audit** | Logging, monitoring, production observability | ## Core Principles * **Developer Experience first** — decorators, auto-discovery * **Convention over configuration** — sensible defaults * **Type-safe by default** — TypeScript + schema validation * **Production-ready** — HTTP transport, session management ### Building MCPs is Easy. Production MCPs are Hard. Building a basic MCP that connects tools to an AI agent is straightforward — define your tools, add descriptions, done. But the **make-or-break features** that separate a toy from production are much harder: * **Authentication** — OAuth integration, token validation, scope management * **Elicitation** — User input collection with validation * **Payments** — Stripe integration, subscription checks, usage-based billing * **MCP Apps & UI** — Rendering UI components inside ChatGPT, Claude, and other clients These features require deep MCP protocol knowledge and weeks of implementation. LeanMCP handles them out of the box with `@leanmcp/auth`, `@leanmcp/elicitation`, and built-in UI support. ### Protocol Upgrades Without Pain The MCP protocol evolves. When updates come — new capabilities, schema changes, security patches — you'd normally rewrite significant code. With LeanMCP, you update one dependency. Your tools, auth, and elicitation continue working. LeanMCP abstracts protocol complexity so you focus on your business logic, not MCP internals. ## Installation ```bash theme={null} curl -fsSL https://raw.githubusercontent.com/Leanmcp-Community/sdk-examples/refs/heads/main/cli/install.sh | bash ``` ```bash theme={null} npm i -g @leanmcp/cli ``` ```bash theme={null} npx @leanmcp/cli create my-mcp-server ``` Then create your project: ```bash theme={null} leanmcp create my-mcp-server ``` The CLI provides an interactive setup that installs dependencies and starts the dev server automatically. ## Project Structure ``` my-mcp-server/ ├── main.ts # Entry point with HTTP server ├── package.json ├── tsconfig.json └── mcp/ # Services directory (auto-discovered) └── example/ └── index.ts # Your tools, resources, prompts ``` ## Start the Server ```bash theme={null} npm run dev ``` ``` Server running on http://localhost:3001 MCP endpoint: http://localhost:3001/mcp Health check: http://localhost:3001/health ``` ## Test with MCP Inspector ```bash theme={null} npx @modelcontextprotocol/inspector http://localhost:3001/mcp ``` ## Deployment Deploy anywhere Node.js runs. Or use LeanMCP's deployment platform: ```bash theme={null} leanmcp deploy ``` You're not locked in — deploy to AWS, GCP, Vercel, Railway, or any platform. ## Next Steps Build your first MCP in 5 minutes Create tools AI can execute Expose data to AI agents Template prompts for AI Secure your MCP server CLI commands and options ## Support LeanMCP is MIT-licensed open source. * **GitHub**: [github.com/Leanmcp-Community](https://github.com/Leanmcp-Community) * **npm**: [@leanmcp/core](https://www.npmjs.com/package/@leanmcp/core) # Testing & Debugging Source: https://docs.leanmcp.com/building/testing How to test and debug your MCP servers effectively # Testing & Debugging Your MCP Server Testing MCP servers is different from testing regular APIs. You need to verify that AI agents can understand and use your tools correctly, not just that the tools work in isolation. ## Why Testing Matters **Regular API testing:** Does the function work?\ **MCP testing:** Can AI agents use the function correctly? AI agents can fail in ways humans don't: * Pick the wrong tool for the task * Miss required parameters * Misunderstand tool descriptions * Get confused by similar tools ## Testing Strategy Overview Use the built-in test button in our platform Comprehensive testing with our open-source tool Ensure MCP compliance with official tools Test with real AI clients (final step) ## Method 1: Platform Built-in Testing **Best for**: Quick validation during development Our platform includes integrated testing functionality accessible directly from the MCP builder interface. ### How to Use 1. **Build your MCP** using the platform interface 2. **Click the "Test" button** in the interface 3. **Review sandbox results** for any build errors 4. **Fix issues** by prompting the AI with corrections 5. **Re-test** until all checks pass ### What It Tests * **Build process** - Does your MCP compile correctly? * **Dependencies** - Are all required packages available? * **Configuration** - Is your MCP properly configured? * **AI interaction** - Limited AI behavior testing Platform testing validates the build process but doesn't test how AI agents interact with your MCP. Use additional methods for comprehensive testing. ## Method 2: MCP Playground (Recommended) **Best for**: Comprehensive development testing Our open-source MCP Playground provides the most thorough testing environment for MCP development. ### Setup 1. **Repository**: [https://github.com/rosaboyle/mcp-playground](https://github.com/rosaboyle/mcp-playground) 2. **Installation**: Clone and follow setup instructions 3. **Connect**: Add your deployed MCP server URL 4. **Test**: Interactive interface for comprehensive testing ### Testing Features Test individual tools with custom parameters Verify resource accessibility and data format Test error handling with invalid inputs Track response times and identify bottlenecks ### What to Test **Start with simple tests:** * Can AI discover available tools? * Do basic tools execute successfully? * Are required parameters validated? * Do error messages make sense? **Then test edge cases:** * Invalid parameter values * Missing required parameters * Network timeouts * Large data payloads ## Method 3: Protocol Validation **Best for**: Ensuring MCP standard compliance Use official MCP validation tools to ensure your server follows the protocol correctly. ### MCP Inspector Anthropic provides official tools for protocol validation: 1. **Access**: Check [Anthropic's documentation](https://docs.anthropic.com/claude/docs/mcp) for latest tools 2. **Install**: Follow official installation instructions 3. **Validate**: Run compliance checks against your server 4. **Fix**: Address any protocol violations identified ### What Gets Validated * MCP protocol version compatibility * Tool and resource schema compliance * Error response formatting * Connection stability * Message format correctness ## Method 4: AI Integration Testing **Best for**: Real-world usage validation Only use this method after your MCP passes all previous testing methods. This should be your final validation step. ### When to Use AI Testing Only test with AI clients when: * **Platform testing passes** * **MCP Playground testing succeeds** * **Protocol validation passes** * **You're ready for real user scenarios** ### Recommended AI Clients Anthropic's official client AI-powered code editor AI development environment ### AI Testing Process 1. **Connect** your deployed MCP server to the AI client 2. **Create test scenarios** that should trigger your tools 3. **Monitor AI behavior** - does it select the right tools? 4. **Verify responses** - are they what you expected? 5. **Identify confusion points** - where does the AI struggle? ## Common Issues & Solutions **Symptoms**: Tools appear available but fail when called **Debugging steps**: * Check server logs for execution errors * Verify all dependencies are installed * Test tool execution manually in MCP Playground * Review parameter validation logic **Common causes**: * Missing environment variables * Incorrect file paths * Database connection issues * Permission problems **Symptoms**: AI consistently picks similar but incorrect tools **Debugging steps**: * Review tool names and descriptions * Make tool purposes more distinct * Simplify tool selection options * Add clear examples to tool descriptions **Solutions**: * Rename similar tools to be more specific * Improve tool descriptions with clear use cases * Reduce the number of similar tools * Add parameter examples **Symptoms**: AI calls tools without required parameters **Debugging steps**: * Check parameter schema definitions * Verify required fields are marked correctly * Review parameter descriptions * Test with MCP Playground parameter validation **Solutions**: * Simplify parameter requirements * Provide clear parameter descriptions * Add parameter examples * Implement better validation messages **Symptoms**: Long delays between AI requests and tool responses **Debugging steps**: * Use MCP Playground performance monitoring * Check server resource usage * Review database query performance * Monitor network latency **Solutions**: * Optimize database queries * Add caching where appropriate * Reduce external API calls * Improve server resources ## Debugging Checklist Before deploying to production, ensure your MCP server passes all these checks: ### **Platform Testing** * [ ] Build process completes successfully * [ ] No compilation errors * [ ] All dependencies resolve correctly * [ ] Configuration is valid ### **Functional Testing** * [ ] All tools execute successfully with valid inputs * [ ] All resources return expected data formats * [ ] Error handling works for invalid inputs * [ ] Parameter validation catches errors ### **Protocol Compliance** * [ ] MCP Inspector validation passes * [ ] All tool schemas are valid * [ ] Error responses follow MCP format * [ ] Connection handling is stable ### **AI Integration** * [ ] AI can discover and list tools * [ ] AI selects appropriate tools for requests * [ ] AI provides required parameters * [ ] End-to-end workflows complete successfully ### **Performance** * [ ] Response times meet requirements * [ ] Server handles expected load * [ ] Error rates are acceptable * [ ] Resource usage is reasonable ## Testing Best Practices ### 1. Test Early and Often Don't wait until your MCP is "complete" to start testing: * **After each tool**: Test individual tools as you build them * **After major changes**: Re-run your test suite * **Before deployment**: Complete validation workflow ### 2. Create Realistic Test Scenarios Test with scenarios your users will actually encounter: ```text theme={null} // Good test scenarios "Help me send an email to John about the meeting tomorrow" "Show me my calendar for next week" "Create a task to review the quarterly report" // Poor test scenarios "Execute function X with parameter Y" "Call tool Z" "Test the API" ``` ### 3. Document Your Tests Keep track of: * Test scenarios that work well * Common failure patterns * Performance benchmarks * AI behavior observations ### 4. Monitor Production Usage After deployment: * Track tool usage patterns * Monitor error rates * Collect user feedback * Watch for unexpected AI behavior ## Next Steps Ready to deploy your tested MCP server Track your deployed MCPs in production Get our comprehensive testing tool Learn how to build better MCP servers ## Remember **Testing MCPs is about validating the AI-tool interaction, not just the tools themselves.** A perfectly working tool that AI agents can't use correctly is worse than no tool at all. Always prioritize testing how AI agents actually interact with your MCP server. # Changelog Source: https://docs.leanmcp.com/changelog Product updates and announcements for Leanmcp ## Pro Tier Performance and Python Sandbox Support * Pro tier now comes with faster builds and deployments * Sandbox now supports Python - test Python-based MCPs directly in the browser * Performance improvements across the platform * Various bug fixes and stability improvements ## SDK Authentication Providers * LeanMCP SDK now supports authentication with major providers: * Supabase * Clerk * AWS Cognito * And other major authentication providers * Simplified auth integration for MCP servers * Updated SDK documentation with auth examples ## Official SDK Releases * **LeanMCP TypeScript SDK** is now available on NPM * **modelcontextprotocol Python library** is now available on PyPI * Comprehensive SDK documentation * Example projects for both TypeScript and Python ## OAuth and Environment Variables * OAuth is now fully supported on leanmcp.com * Customer environment variables in alpha - available for invited users * Securely manage your MCP environment configuration * Enhanced security features for production deployments ## Auth, Elicitation and Backward Compatibility * Ship.leanmcp.com now supports Auth and Elicitation * Backward compatibility by default for all deployments * Full support for Windsurf and Cursor (which currently do not support Streamable HTTP) * Seamless integration with existing MCP clients ## Public Chats and Marketplace Launch * Public, sharable and forkable chats and projects * Marketplace and explore existing MCPs for inspiration * Users can access 200+ marketplace MCPs directly ## CLI and Enhanced Features * CLI and documentations * Search support for chat * Increase in test sandbox time to 1 hour by default ## UI Updates and Secrets Management * UI updates * Build optimizations reducing build time * Bug fixes * User request updates: Secrets - Users can now store secrets securely. Encrypted at rest and loaded during runtime. Users need not add their secrets in the docker container or maintain their own secrets manager in order to use them ## Ship.leanmcp.com Beta and One-Click Deployment * Ship.leanmcp.com is now in Beta * MCP Deployment in beta - deploy your MCP in one click * Currently supports stateless MCPs built on top of the official modelcontextprotocol SDK (TypeScript) * Streamlined deployment pipeline ## Pro Version and GitHub Integration * Pro version for high usage users, higher chat limits for free users * Install GitHub app [https://github.com/apps/leanmcp-com](https://github.com/apps/leanmcp-com) directly to enable CI/CD into the pipeline * UI updates * Chat history is now persistent * Bug fixes ## Sandbox Testing * Sandboxes are now available for waitlisted users * LeanMCP now supports Sandboxes - test your MCPs directly rather than deploying and then testing * Faster iteration cycles for MCP development * Real-time testing and debugging capabilities ## Project Management and GitHub Integration * LeanMCP now supports persistent project management * GitHub integration for seamless version control * Bug fixes and better file management * Improved project organization ## MCP Auth in Vibe Coding * Ship.leanmcp.com now supports MCP Auth in vibe coding * Authenticate your MCPs directly during development * Streamlined authentication flow * Bug fixes and stability improvements ## Initial Private Beta Launch Ship.leanmcp.com is now available for waitlisted users. Initial launch of our Private beta - users can now just vibe code and deploy MCPs. * Support to chat with the agent and download the MCP and use internally * Support testing our MCPs on our platform * Deploy it on our serverless platform # Authentication Source: https://docs.leanmcp.com/cli/authentication Set up API key authentication for the CLI # CLI Authentication Authenticate the LeanMCP CLI with your API key to access your projects and deployments. ## Get Your API Key 1. Go to [LeanMCP Dashboard](https://leanmcp.com) 2. Navigate to **Settings** → **API Keys** 3. Create a new API key with appropriate permissions 4. Copy the API key (starts with `airtrain_`) ## Login Command ```bash theme={null} leanmcp login ``` The CLI will prompt you interactively for your API key: ``` LeanMCP Login ? Enter your LeanMCP API key: airtrain_xxxx... Validating API key... API key validated successfully! Logged in successfully! ``` ## Verify Authentication Check if you're logged in: ```bash theme={null} leanmcp whoami ``` **Example Output:** ``` LeanMCP Authentication Status Logged in API Key: airtrain_2ef4da... API URL: https://api.leanmcp.com Last updated: 12/18/2025, 4:38:05 PM ``` ## Logout Remove stored credentials: ```bash theme={null} leanmcp logout ``` **Example Output:** ``` Logged out successfully! ``` ## Credential Storage The CLI stores your credentials securely in: * **macOS/Linux**: `~/.leanmcp/config.json` Keep your API key secure. Do not share it or commit it to version control. ## Troubleshooting ### Not Authenticated ``` Not authenticated Run 'leanmcp login' to authenticate. ``` **Solutions:** * Run `leanmcp login` and enter your API key * Verify your API key is correct ### API Key Invalid ``` API key validation failed Please check your API key and try again. ``` **Solutions:** * Verify your API key is correct * Check if the API key has expired * Create a new API key in the dashboard ## Next Steps Start creating and managing MCP projects # Deployment Source: https://docs.leanmcp.com/cli/deployment Deploy MCP servers to LeanMCP cloud # Deployment Deploy your MCP servers to LeanMCP cloud with a single command. ## Basic Usage ```bash theme={null} leanmcp deploy [folder] ``` Deploy the current directory: ```bash theme={null} leanmcp deploy . ``` Deploy a specific folder: ```bash theme={null} leanmcp deploy ./my-mcp-server ``` ## Command Options | Flag | Short | Description | | ------------- | ----- | ------------------------- | | `--subdomain` | `-s` | Subdomain for deployment | | `--yes` | `-y` | Skip confirmation prompts | ## Example Output ```bash theme={null} $ leanmcp deploy . LeanMCP Deploy Generated project name: late-faraday-37 Path: /Users/you/my-mcp-server ✔ Subdomain for your deployment: late-faraday-37 ✔ Subdomain 'late-faraday-37' is available Deployment Details: Project: late-faraday-37 Subdomain: late-faraday-37 URL: https://late-faraday-37.leanmcp.app ✔ Proceed with deployment? Yes ✔ Project created: 30a4c8bf... ✔ Project uploaded ✔ Build complete (324s) ✔ Deployed ✔ Subdomain configured ============================================================ DEPLOYMENT SUCCESSFUL! ============================================================ Your MCP server is now live: URL: https://late-faraday-37.leanmcp.app Test endpoints: curl https://late-faraday-37.leanmcp.app/health curl https://late-faraday-37.leanmcp.app/mcp Total time: 564s Dashboard links: Project: https://leanmcp.com/projects/30a4c8bf-be7f-4a7a-bca7-c2e63e03844a Build: https://leanmcp.com/builds/6f0640ac-d26c-4f66-b2a3-042d6770f916 Deployment: https://leanmcp.com/deployments/99604138-ef7d-49b5-ab42-71b43eb98844 Need help? Join our Discord: https://discord.com/invite/DsRcA3GwPy ``` *** ## Troubleshooting ### Not Authenticated ```bash theme={null} Error: Not authenticated Run: leanmcp login ``` ### Build Failures If the build fails, check the build logs in your dashboard: ```bash theme={null} ✔ Project created: abc123... ✔ Project uploaded ✖ Build failed Build ID: 6f0640ac-d26c-4f66-b2a3-042d6770f916 View logs: https://leanmcp.com/builds/6f0640ac-d26c-4f66-b2a3-042d6770f916 ``` Common build issues: * Missing `package.json` or `requirements.txt` * Invalid TypeScript syntax * Missing dependencies ### Subdomain Already Taken ```bash theme={null} ✖ Subdomain 'my-app' is not available This subdomain is taken by another user. Please choose a different subdomain. ``` Use the `--subdomain` flag to specify a different subdomain: ```bash theme={null} leanmcp deploy . --subdomain my-unique-subdomain ``` # Environment Variables Source: https://docs.leanmcp.com/cli/env-vars Manage Lambda environment variables with the CLI # Environment Variables Manage environment variables for your deployed Lambda functions directly from the CLI. ## Overview The `leanmcp env` command provides a complete set of tools for managing environment variables on your running Lambda deployments. Changes are applied immediately (causing a cold start on next invocation). System variables like `PORT`, `AWS_LWA_*` cannot be modified or deleted as they are required for Lambda Web Adapter. ## List Environment Variables View all environment variables for your deployment: ```bash theme={null} leanmcp env list ``` ### Options | Flag | Short | Description | | -------------- | ----- | -------------------- | | `--reveal` | `-r` | Show unmasked values | | `--project-id` | `-p` | Specify project ID | ### Examples ```bash theme={null} # Basic list (values masked) leanmcp env list # Show actual values leanmcp env list --reveal # List for specific project leanmcp env list --project-id proj_abc123 ``` **Example Output:** ``` Environment Variables for my-mcp-server ──────────────────────────────────────────────── API_KEY: •••••••• DATABASE_URL: •••••••• DEBUG_MODE: •••••••• PORT: 8080 (system) Total: 4 variables ``` **With --reveal:** ``` Environment Variables for my-mcp-server ──────────────────────────────────────────────── API_KEY: sk-1234567890abcdef DATABASE_URL: postgres://user:pass@host:5432/db DEBUG_MODE: true PORT: 8080 (system) Total: 4 variables ``` ## Set Environment Variable Add or update an environment variable: ```bash theme={null} leanmcp env set KEY=value ``` ### Options | Flag | Short | Description | | -------------- | ----- | ------------------ | | `--project-id` | `-p` | Specify project ID | ### Examples ```bash theme={null} # Set a single variable leanmcp env set API_KEY=sk-1234567890abcdef # Set for specific project leanmcp env set DATABASE_URL=postgres://localhost:5432/db --project-id proj_abc123 # Set multiple variables at once leanmcp env set API_KEY=sk-abc123 DEBUG_MODE=true MAX_RETRIES=3 ``` **Example Output:** ``` Setting environment variable(s) for my-mcp-server... Successfully updated: ✓ API_KEY ✓ DEBUG_MODE ✓ MAX_RETRIES Variables updated successfully! Note: Changes will take effect after a cold start. ``` Variable keys are automatically converted to uppercase and sanitized to match Lambda environment variable naming rules. ## Get Environment Variable Retrieve a specific environment variable: ```bash theme={null} leanmcp env get KEY ``` ### Options | Flag | Short | Description | | -------------- | ----- | ------------------- | | `--reveal` | `-r` | Show unmasked value | | `--project-id` | `-p` | Specify project ID | ### Examples ```bash theme={null} # Get masked value leanmcp env get API_KEY # Get actual value leanmcp env get API_KEY --reveal # Get from specific project leanmcp env get DATABASE_URL --project-id proj_abc123 ``` **Example Output:** ``` API_KEY=•••••••• ``` **With --reveal:** ``` API_KEY=sk-1234567890abcdef ``` ## Remove Environment Variable Delete an environment variable: ```bash theme={null} leanmcp env remove KEY ``` ### Options | Flag | Short | Description | | -------------- | ----- | ------------------------ | | `--project-id` | `-p` | Specify project ID | | `--yes` | `-y` | Skip confirmation prompt | ### Examples ```bash theme={null} # Remove with confirmation leanmcp env remove API_KEY # Skip confirmation leanmcp env remove DEBUG_MODE --yes # Remove from specific project leanmcp env remove OLD_CONFIG --project-id proj_abc123 ``` **Example Output:** ``` ? Are you sure you want to delete API_KEY? (y/N) y Removing environment variable: API_KEY Variable removed successfully! Note: Changes will take effect after a cold start. ``` This action cannot be undone. The variable will be permanently deleted from your Lambda function. ## Pull Environment Variables Download environment variables to a local `.env` file: ```bash theme={null} leanmcp env pull ``` ### Options | Flag | Short | Description | | -------------- | ----- | -------------------------------------------- | | `--output` | `-o` | Output file path (default: `.env`) | | `--project-id` | `-p` | Specify project ID | | `--overwrite` | | Overwrite existing file without confirmation | ### Examples ```bash theme={null} # Pull to .env (default) leanmcp env pull # Pull to custom file leanmcp env pull --output .env.production # Overwrite without confirmation leanmcp env pull --overwrite # Pull from specific project leanmcp env pull --project-id proj_abc123 ``` **Example Output:** ``` Pulling environment variables from my-mcp-server... Downloaded 4 variables to .env ✓ API_KEY ✓ DATABASE_URL ✓ DEBUG_MODE ✓ MAX_RETRIES Note: System variables (PORT, AWS_LWA_*) are excluded. ``` **Generated .env file:** ```bash theme={null} # Environment variables from my-mcp-server # Downloaded: 2026-01-21T22:12:00Z API_KEY=sk-1234567890abcdef DATABASE_URL=postgres://user:pass@host:5432/db DEBUG_MODE=true MAX_RETRIES=3 ``` ## Push Environment Variables Upload environment variables from a local `.env` file: ```bash theme={null} leanmcp env push ``` ### Options | Flag | Short | Description | | -------------- | ----- | --------------------------------------- | | `--file` | `-f` | Input file path (default: `.env`) | | `--project-id` | `-p` | Specify project ID | | `--merge` | | Merge with existing variables (default) | | `--replace` | | Replace all existing variables | ### Examples ```bash theme={null} # Push from .env (merge mode) leanmcp env push # Push from custom file leanmcp env push --file .env.production # Replace all variables leanmcp env push --replace # Push to specific project leanmcp env push --project-id proj_abc123 ``` **Example Output (Merge Mode):** ``` Pushing environment variables from .env... Parsing file... Found 5 variables ? This will update 3 and add 2 variables. Continue? (Y/n) y Updating variables on my-mcp-server... Successfully updated: ✓ API_KEY (updated) ✓ DATABASE_URL (updated) ✓ DEBUG_MODE (updated) ✓ NEW_VAR_1 (added) ✓ NEW_VAR_2 (added) Variables pushed successfully! Note: Changes will take effect after a cold start. ``` **Example Output (Replace Mode):** ``` Pushing environment variables from .env... ⚠️ WARNING: Replace mode will delete all existing variables! Current variables (4): - API_KEY - DATABASE_URL - DEBUG_MODE - OLD_CONFIG New variables (3): + API_KEY + DATABASE_URL + NEW_CONFIG ? This will DELETE 2 variables and set 3 variables. Continue? (y/N) y Replacing all variables on my-mcp-server... Variables replaced successfully! Note: Changes will take effect after a cold start. ``` In `--replace` mode, all existing user variables will be deleted and replaced with the contents of your file. System variables are always preserved. ## Full Workflow Example Complete workflow for managing environment variables: ```bash theme={null} # 1. Pull current variables to local file leanmcp env pull --output .env.backup # 2. Check current variables leanmcp env list --reveal # 3. Add new variables leanmcp env set NEW_API_KEY=sk-new123 FEATURE_FLAG=enabled # 4. Update .env file locally (edit as needed) # Edit .env file... # 5. Push updated variables leanmcp env push # 6. Verify changes leanmcp env list # 7. Get specific variable leanmcp env get NEW_API_KEY --reveal # 8. Remove old variable leanmcp env remove OLD_API_KEY --yes ``` ## Protected Variables The following variables are protected and cannot be modified: **AWS Reserved:** * `AWS_*` (all AWS-prefixed variables) **Lambda Web Adapter System:** * `PORT` * `AWS_LWA_PORT` * `AWS_LWA_INVOKE_MODE` * `AWS_LWA_READINESS_CHECK_MIN_UNHEALTHY_STATUS` These variables are automatically managed by AWS Lambda and the Lambda Web Adapter. Attempting to modify them will result in them being skipped with a warning. ## Troubleshooting ### Not Authenticated ``` Not authenticated Run 'leanmcp login' to authenticate. ``` **Solution:** Run `leanmcp login` and enter your API key. ### No Deployment Found ``` No Lambda deployment found for current project ``` **Solution:** Deploy your project first using `leanmcp deploy .` ### Variable Name Validation ``` Invalid variable name: my-key Variable names must match: /^[A-Z][A-Z0-9_]*$/ ``` **Solution:** Use only uppercase letters, numbers, and underscores. Must start with a letter. ### File Not Found ``` File not found: .env ``` **Solution:** Ensure the `.env` file exists or specify a different file with `--file`. ### Parse Error ``` Failed to parse .env file Invalid format at line 5: "INVALID LINE" ``` **Solution:** Ensure your `.env` file uses `KEY=VALUE` format (one per line). ## Next Steps Deploy your MCP server to the cloud View deployment status and logs # Feedback Source: https://docs.leanmcp.com/cli/feedback Send feedback and bug reports directly from the CLI # Send Feedback The LeanMCP CLI includes a built-in command to send feedback, bug reports, and feature requests directly to the LeanMCP team. ## Usage ```bash theme={null} leanmcp send-feedback [message] [options] ``` ### Options | Option | Description | | :--------------- | :----------------------------------------------------- | | `--anon` | Send feedback anonymously (skips authentication check) | | `--include-logs` | Attach recent CLI log files to help debug issues | | `-h, --help` | Display help for command | ## Examples ### Quick Feedback Send a simple one-line message: ```bash theme={null} leanmcp send-feedback "I really like the new project structure!" ``` ### Interactive Mode If you run the command without a message, you can type a multi-line message (press Ctrl+D or Ctrl+Z when finished) or pipe input from another command: ```bash theme={null} # Type interactively leanmcp send-feedback # Pipe from a file or command cat issues.txt | leanmcp send-feedback ``` ### Sending Logs If you encounter an error, you can attach your recent CLI logs to help us debug the issue. This is extremely helpful for troubleshooting deployment or build failures. ```bash theme={null} leanmcp send-feedback "Deploy failed with timeout error" --include-logs ``` **Privacy**: When using `--include-logs`, the CLI attaches the 3 most recent log files from `~/.leanmcp/logs/`. Sensitive information like API keys is automatically redacted from these logs before sending. ### Anonymous Feedback You can send feedback without being logged in or identifying yourself: ```bash theme={null} leanmcp send-feedback "Just trying out the CLI, looks good" --anon ``` ## What happens next? Your feedback is reviewed directly by the engineering team. We take all feedback seriously and use it to prioritize our roadmap. For real-time discussion and support, join our Discord server. # Installation Source: https://docs.leanmcp.com/cli/installation Install the LeanMCP CLI tool # Installing LeanMCP CLI Get the LeanMCP CLI tool to manage your MCP projects from the command line. ## npm (Recommended) Install globally via npm: ```bash theme={null} npm install -g @leanmcp/cli ``` ## Verify Installation ```bash theme={null} leanmcp --version ``` **Example Output:** ``` 1.0.7 ``` ## First Setup After installation, authenticate with your API key: ```bash theme={null} leanmcp login ``` The CLI will prompt you to enter your API key: ``` LeanMCP Login ? Enter your LeanMCP API key: airtrain_xxxx... Validating API key... API key validated successfully! Logged in successfully! ``` Get your API key from the [LeanMCP Dashboard](https://leanmcp.com) → Settings → API Keys ## Quick Start After installation and login, create your first MCP server: ```bash theme={null} # Create a new project with auto-install leanmcp create my-mcp-server --install # Start the development server cd my-mcp-server leanmcp dev ``` ## Next Steps Set up your API key and login Create and manage MCP projects # Project Management Source: https://docs.leanmcp.com/cli/projects Manage MCP projects with the CLI # Project Management Complete guide to managing your MCP projects using the LeanMCP CLI. ## Create Project Create a new MCP server project locally: ```bash theme={null} leanmcp create ``` ### With Auto-Install (Recommended) ```bash theme={null} leanmcp create my-mcp-server --install ``` **Example Output:** ``` Creating project my-mcp-server... Project created successfully! cd my-mcp-server npm run dev Your MCP server is ready at http://localhost:3001 ``` ### Interactive Mode ```bash theme={null} leanmcp create my-mcp-server ``` The CLI will prompt you for: * Install dependencies? (y/N) * Start development server? (y/N) ### Skip All Prompts ```bash theme={null} leanmcp create my-mcp-server --allow-all ``` ## Development Server Start the development server with hot reload: ```bash theme={null} cd my-mcp-server leanmcp dev ``` **Example Output:** ``` Starting development server... my-mcp-server MCP Server Server running at http://localhost:3001 Dashboard: http://localhost:3001/ MCP Endpoint: http://localhost:3001/mcp ``` ## List Projects View all your cloud projects: ```bash theme={null} leanmcp projects list ``` **Example Output:** ``` Fetching projects... Your Projects (3) ──────────────────────────────────────────────────────────── my-mcp-server ID: bc1d06ef-d652-4ae6-8977-614c8677606a Status: active Created: 12/18/2025 email-assistant ID: 4aac8add-acc5-4937-a080-244e77b0e870 Status: active Created: 12/18/2025 database-helper ID: bf0f5a80-3c98-4bcb-aaa7-df48fa4069d8 Status: active Created: 12/18/2025 ``` ## Get Project Details Get detailed information about a specific project: ```bash theme={null} leanmcp projects get ``` **Example:** ```bash theme={null} leanmcp projects get bc1d06ef-d652-4ae6-8977-614c8677606a ``` **Example Output:** ``` Fetching project... Project Details ──────────────────────────────────────────────────────────── Name: my-mcp-server ID: bc1d06ef-d652-4ae6-8977-614c8677606a Status: active Created: 12/18/2025, 4:39:59 PM Updated: 12/18/2025, 4:39:59 PM ``` ## Deploy Project Deploy your MCP server to LeanMCP cloud: ```bash theme={null} cd my-mcp-server leanmcp deploy . ``` **Example Output:** ``` Deploying to LeanMCP cloud... Scanning project files... Found 12 files to upload Uploading project... Upload complete! Building project... Build started: build_abc123 Deployment successful! URL: https://my-mcp-server.leanmcp.com ``` ## Delete Project Remove a project from the cloud: ```bash theme={null} leanmcp projects delete ``` **Example:** ```bash theme={null} leanmcp projects delete bc1d06ef-d652-4ae6-8977-614c8677606a ``` **Example Output:** ``` Deleting project bc1d06ef-d652-4ae6-8977-614c8677606a... Project deleted successfully! ``` ## Full Workflow Example Complete workflow from creation to deployment: ```bash theme={null} # 1. Create a new project with dependencies leanmcp create my-awesome-mcp --install # 2. Navigate to project cd my-awesome-mcp # 3. Start development server (test locally) leanmcp dev # 4. When ready, deploy to cloud leanmcp deploy . # 5. List your projects to see deployment leanmcp projects list # 6. Get details of deployed project leanmcp projects get ``` ## File Scanning Rules When deploying projects, the CLI: **Includes:** * All source code files (`.ts`, `.js`, `.py`, etc.) * Configuration files (`package.json`, `tsconfig.json`, etc.) * Documentation files (`README.md`, etc.) **Excludes (.gitignore respected):** * `node_modules/` * `.git/` * `dist/`, `build/` * `*.log` * OS files (`.DS_Store`, `Thumbs.db`) * IDE files (`.vscode/`, `.idea/`) ## Troubleshooting ### Not Authenticated ``` Not authenticated Run 'leanmcp login' to authenticate. ``` **Solution:** Run `leanmcp login` and enter your API key. ### Project Not Found ``` Project not found ``` **Solution:** Verify the project ID using `leanmcp projects list`. ### Deployment Failed ``` Deployment failed: Build error ``` **Solutions:** * Check build logs in the dashboard * Verify project configuration * Ensure all dependencies are in `package.json` ## Next Steps Deploy your MCP servers to the cloud View deployment status and logs # Prompts Source: https://docs.leanmcp.com/core-concepts/prompts Template prompts for AI agents # Prompts Prompts are **user-driven** templates that provide shortcuts and working examples for AI interactions. They help users get started quickly without figuring out the right way to phrase requests. ## How Prompts Work Prompts can be consumed in two ways, depending on how the client is built: | Mode | Who Triggers | How It Works | | ------------------ | ---------------------------- | --------------------------------------------------------------------- | | **User-Invoked** | User explicitly selects | Slash commands (`/analyze`), dropdown menus, keyboard shortcuts | | **Agent-Selected** | AI agent picks automatically | Agent browses available prompts and selects the best one for the task | **Client Implementation Matters**: Whether prompts are user-invoked or agent-selected depends entirely on how the client application is developed. Some clients expose prompts as slash commands, others let the AI agent discover and use them automatically. ## When to Use Prompts * **Provide examples** of how to use your MCP server effectively * **Create shortcuts** for common workflows users perform repeatedly * **Include dynamic context** that would be tedious to type manually * **Onboard new users** with working examples they can invoke immediately ## Basic Prompt ```typescript theme={null} import { Prompt } from "@leanmcp/core"; export class PromptService { @Prompt({ description: "Customer support assistant" }) supportAssistant() { return { messages: [{ role: "user", content: { type: "text", text: "You are a helpful customer support agent. Be polite and helpful." } }] }; } } ``` ## Prompt with Arguments ```typescript theme={null} import { Prompt, SchemaConstraint } from "@leanmcp/core"; class CodeReviewInput { @SchemaConstraint({ description: "Code to review" }) code!: string; @SchemaConstraint({ description: "Programming language" }) language!: string; } export class CodeService { @Prompt({ description: "Generate code review prompt" }) codeReview(input: CodeReviewInput) { return { messages: [{ role: "user", content: { type: "text", text: `Review this ${input.language} code for bugs, style, and best practices:\n\n${input.code}` } }] }; } } ``` ## Multi-message Prompt Set up a conversation with context: ```typescript theme={null} @Prompt({ description: "Code review conversation" }) codeReviewer() { return { messages: [ { role: "user", content: { type: "text", text: "You are a senior code reviewer." } }, { role: "assistant", content: { type: "text", text: "I'll review the code for bugs, style, and best practices." } } ] }; } ``` ## Specialized Prompts ```typescript theme={null} @Prompt({ description: "SQL query generator" }) sqlHelper(input: { schema: string }) { return { messages: [{ role: "user", content: { type: "text", text: `You are a SQL expert. Generate queries for this schema:\n\n${input.schema}` } }] }; } @Prompt({ description: "API documentation writer" }) apiDocWriter() { return { messages: [{ role: "user", content: { type: "text", text: "You write clear, concise API documentation with examples." } }] }; } ``` ## The Three MCP Primitives Understanding when to use each: | Primitive | Control | Purpose | Example | | ------------- | ------------------ | ----------------------- | ---------------------------------- | | **Tools** | Model-driven | Actions the AI performs | Send email, create task, query API | | **Resources** | Application-driven | Context data for AI | Files, preferences, schedules | | **Prompts** | User-driven | Templates users invoke | `/analyze`, `/review`, `/support` | ## Next Steps Create actions AI can execute Expose data to AI agents # Resources Source: https://docs.leanmcp.com/core-concepts/resources Expose data to AI agents # Resources Resources are **read-only but dynamic** data that applications can expose to AI agents. Unlike Tools (which perform actions), Resources provide contextual information that helps the AI understand your environment. ## How Resources Work Resources are **application-driven** — the client application decides how to use them: | Actor | Control Type | Example | | --------------- | ---------------------------------- | ----------------------------------------- | | **User** | Selects which resources to include | "Add my calendar as context" | | **Application** | Decides how to consume resources | Build embeddings, cache, transform | | **AI Model** | Reads resource data | Uses context to generate better responses | **Client Support Matters**: Resources are only useful if the client application provides UI for users to select and manage them. If your client doesn't expose resource controls, users can't leverage this feature effectively. ## Real-World Examples Resources aren't just for code editors. They provide context in any domain: | Use Case | Resource Examples | | ----------------------------- | ------------------------------------------------------- | | **Coding (Cursor, Windsurf)** | Open files, project structure, git history | | **Travel Agent** | Calendar schedule, travel preferences, past itineraries | | **Customer Support** | User profile, order history, support tickets | | **Research Assistant** | PDF documents, bookmarks, notes | | **Personal Assistant** | Email drafts, contacts, reminders | ## Basic Resource ```typescript theme={null} import { Resource } from "@leanmcp/core"; export class StatusService { // Function name "getServerStatus" becomes resource name @Resource({ description: "Server status", mimeType: "application/json" }) getServerStatus() { return { status: "running", uptime: process.uptime(), memory: process.memoryUsage() }; } } ``` ## Resource with Configuration ```typescript theme={null} @Resource({ description: "System configuration", mimeType: "application/json" }) getConfig() { return { version: "1.0.0", environment: process.env.NODE_ENV, features: { analytics: true, notifications: true } }; } ``` ## Async Resources Resources can be async for database queries or API calls: ```typescript theme={null} @Resource({ description: "Database statistics", mimeType: "application/json" }) async getDatabaseStats() { const stats = await db.getStats(); return { connections: stats.connections, queries: stats.queryCount, uptime: stats.uptime }; } ``` ## Resource with Dynamic Data ```typescript theme={null} @Resource({ description: "Available models", mimeType: "application/json" }) getAvailableModels() { return { contents: [{ uri: "models://available", mimeType: "application/json", text: JSON.stringify({ "gpt-5.2": "Advanced reasoning", "gpt-5.2": "Fast, cost-effective", "gemini-pro": "Multimodal" }) }] }; } ``` ## The Three MCP Primitives Understanding when to use each: | Primitive | Control | Purpose | Example | | ------------- | ------------------ | ----------------------- | ---------------------------------- | | **Tools** | Model-driven | Actions the AI performs | Send email, create task, query API | | **Resources** | Application-driven | Context data for AI | Files, preferences, schedules | | **Prompts** | User-driven | Templates users invoke | `/analyze`, `/review`, `/support` | ## Next Steps Create actions AI can execute Template prompts for AI # Tools Source: https://docs.leanmcp.com/core-concepts/tools Create actions AI agents can perform # Tools Tools are actions AI can perform — the primary way AI interacts with your system. When an AI agent needs to do something (send email, create task, query database), it calls a tool. **Tools are triggered by the AI agent, not the user.** The LLM decides when to call a tool based on the user's request and the tool's description. Users don't directly invoke tools — they ask the AI, and the AI decides which tools to use. ### Testing Your Tools Since tools are AI-triggered, you'll need a way to test them: * **MCP Inspector** — `npx @modelcontextprotocol/inspector http://localhost:3001/mcp` * **Claude Desktop / Cursor** — Connect your MCP and chat with it * **LeanMCP Sandbox** — Test tools directly in the browser * **Postman** — Send raw MCP requests to your server Every tool you add increases token usage. Keep descriptions concise and only expose tools the AI actually needs. See [Reducing Tokens in MCPs](/guides/reducing-tokens) for optimization strategies. ## Tool Structure ```typescript theme={null} import { Tool, SchemaConstraint, Optional } from "@leanmcp/core"; // 1. Define input schema with descriptions for AI class YourToolInput { @SchemaConstraint({ description: "Describe what this param is for" }) requiredParam!: string; @Optional() @SchemaConstraint({ description: "Optional param", default: 10 }) optionalParam?: number; } // 2. Create service class export class YourService { // 3. Decorate method with @Tool + inputClass @Tool({ description: "Clear description of what this tool does", inputClass: YourToolInput }) async yourToolName(input: YourToolInput) { // 4. Implementation return { success: true, data: "result" }; } } ``` ## Full Example: Task Manager A complete task management service with CRUD operations: ```typescript theme={null} // mcp/tasks/index.ts import { Tool, Resource, SchemaConstraint, Optional } from "@leanmcp/core"; // In-memory store (replace with database in production) interface Task { id: string; title: string; description: string; status: "todo" | "in_progress" | "done"; priority: "low" | "medium" | "high"; createdAt: Date; updatedAt: Date; } const tasks: Map = new Map(); // --- Input Schemas --- class CreateTaskInput { @SchemaConstraint({ description: "Task title" }) title!: string; @Optional() @SchemaConstraint({ description: "Task description" }) description?: string; @Optional() @SchemaConstraint({ description: "Priority level", enum: ["low", "medium", "high"], default: "medium" }) priority?: "low" | "medium" | "high"; } class UpdateTaskInput { @SchemaConstraint({ description: "Task ID to update" }) id!: string; @Optional() @SchemaConstraint({ description: "New title" }) title?: string; @Optional() @SchemaConstraint({ description: "New status", enum: ["todo", "in_progress", "done"] }) status?: "todo" | "in_progress" | "done"; @Optional() @SchemaConstraint({ description: "New priority", enum: ["low", "medium", "high"] }) priority?: "low" | "medium" | "high"; } class DeleteTaskInput { @SchemaConstraint({ description: "Task ID to delete" }) id!: string; } class ListTasksInput { @Optional() @SchemaConstraint({ description: "Filter by status", enum: ["todo", "in_progress", "done"] }) status?: "todo" | "in_progress" | "done"; @Optional() @SchemaConstraint({ description: "Filter by priority", enum: ["low", "medium", "high"] }) priority?: "low" | "medium" | "high"; } // --- Service --- export class TaskService { @Tool({ description: "Create a new task", inputClass: CreateTaskInput }) createTask(input: CreateTaskInput) { const id = `task_${Date.now()}`; const now = new Date(); const task: Task = { id, title: input.title, description: input.description || "", status: "todo", priority: input.priority || "medium", createdAt: now, updatedAt: now }; tasks.set(id, task); return { created: true, task: { id: task.id, title: task.title, status: task.status, priority: task.priority } }; } @Tool({ description: "Update an existing task", inputClass: UpdateTaskInput }) updateTask(input: UpdateTaskInput) { const task = tasks.get(input.id); if (!task) throw new Error(`Task ${input.id} not found`); if (input.title) task.title = input.title; if (input.status) task.status = input.status; if (input.priority) task.priority = input.priority; task.updatedAt = new Date(); tasks.set(input.id, task); return { updated: true, task: { id: task.id, title: task.title, status: task.status, priority: task.priority } }; } @Tool({ description: "Delete a task", inputClass: DeleteTaskInput }) deleteTask(input: DeleteTaskInput) { const task = tasks.get(input.id); if (!task) throw new Error(`Task ${input.id} not found`); tasks.delete(input.id); return { deleted: true, id: input.id, title: task.title }; } @Tool({ description: "List tasks with optional filters", inputClass: ListTasksInput }) listTasks(input: ListTasksInput) { let result = Array.from(tasks.values()); if (input.status) result = result.filter(t => t.status === input.status); if (input.priority) result = result.filter(t => t.priority === input.priority); return { count: result.length, tasks: result.map(t => ({ id: t.id, title: t.title, status: t.status, priority: t.priority })) }; } @Resource({ description: "Task statistics", mimeType: "application/json" }) getStats() { const all = Array.from(tasks.values()); return { total: all.length, todo: all.filter(t => t.status === "todo").length, inProgress: all.filter(t => t.status === "in_progress").length, done: all.filter(t => t.status === "done").length }; } } ``` **How AI uses this:** * "Create a task called 'Review PR #42' with high priority" * "List all tasks that are in progress" * "Mark task\_1234 as complete" * "Delete the task about reviewing PR" ## Schema Validation Use `@SchemaConstraint` for input validation and better AI understanding: ```typescript theme={null} import { Tool, SchemaConstraint, Optional } from "@leanmcp/core"; class SendEmailInput { @SchemaConstraint({ description: "Recipient email address", format: "email" }) to!: string; @SchemaConstraint({ description: "Email subject line" }) subject!: string; @SchemaConstraint({ description: "Email body content" }) body!: string; @Optional() @SchemaConstraint({ description: "Priority", enum: ["low", "normal", "high"], default: "normal" }) priority?: string; } export class EmailService { @Tool({ description: "Send an email to a recipient", inputClass: SendEmailInput }) async sendEmail(input: SendEmailInput) { // Send email implementation return { sent: true, messageId: `msg_${Date.now()}` }; } } ``` ## Return Types Tools can return different types: ```typescript theme={null} @Tool({ description: "Get user" }) getUser(input: { id: string }) { return { name: "John", email: "john@example.com" }; } ``` ```typescript theme={null} @Tool({ description: "Generate report" }) generateReport(input: { type: string }) { return "Report generated successfully"; } ``` ```typescript theme={null} @Tool({ description: "Get image" }) getImage(input: { id: string }) { return { content: [ { type: "image", data: base64Data, mimeType: "image/png" } ] }; } ``` ## Async Tools Tools can be async for API calls, database queries, file operations: ```typescript theme={null} @Tool({ description: "Search database" }) async search(input: { query: string }) { const results = await db.query(input.query); return { results, count: results.length }; } ``` ## Error Handling Throw errors — they're caught and returned properly to the AI: ```typescript theme={null} @Tool({ description: "Delete user" }) async deleteUser(input: { id: string }) { const user = await db.users.find(input.id); if (!user) { throw new Error(`User ${input.id} not found`); } await db.users.delete(input.id); return { deleted: true }; } ``` ## Organizing Services Group related tools into services. One file per domain: ``` mcp/ ├── email/index.ts # EmailService - send, draft, search ├── calendar/index.ts # CalendarService - create, list, update events ├── contacts/index.ts # ContactsService - lookup, create, update └── analytics/index.ts # AnalyticsService - reports, metrics ``` ## Best Practices AI uses descriptions to understand when to use tools. **Bad:** `"Process data"` **Good:** `"Search customer orders by date range and status"` Every tool call costs tokens and time. Design tools that accomplish goals in **one call**, not a chain of 7-8 calls. **❌ BAD: Hotel booking with 7+ tool calls** ```mermaid theme={null} flowchart LR A[searchCities] --> B[getLocations] B --> C[listHotels] C --> D[getRoomTypes] D --> E[getQuote] E --> F[holdRoom] F --> G[processPayment] G --> H[issueConfirmation] ``` Each call: AI generates request → waits for response → processes → generates next request. **7 round trips, thousands of tokens.** **✅ GOOD: One tool that does it all** ```typescript theme={null} class BookHotelInput { @SchemaConstraint({ description: "City name" }) city!: string; @SchemaConstraint({ description: "Check-in date (YYYY-MM-DD)" }) checkIn!: string; @SchemaConstraint({ description: "Check-out date (YYYY-MM-DD)" }) checkOut!: string; @SchemaConstraint({ description: "Number of guests" }) guests!: number; @Optional() @SchemaConstraint({ description: "Max price per night in USD" }) maxPrice?: number; } @Tool({ description: "Book a hotel room. Returns available options with prices, or confirms booking if user approves.", inputClass: BookHotelInput }) async bookHotel(input: BookHotelInput) { // All logic happens server-side in one call const options = await this.findAndPriceRooms(input); return { options: options.slice(0, 3), // Top 3 choices message: "Reply with option number to confirm booking" }; } ``` **One call, one response.** The server handles the complexity, not the AI. One tool, one job. Don't combine unrelated actions. **Bad:** `"manageUser"` (create, update, delete in one) **Good:** `"createUser"`, `"updateUser"`, `"deleteUser"` Use `@SchemaConstraint` for all inputs. AI makes fewer mistakes with validated schemas. Return useful data AI can use in follow-up actions. **Bad:** `return { success: true }` **Good:** `return { created: true, id: "123", name: "John" }` Throw clear errors. AI can explain issues to users. ## The Three MCP Primitives Understanding when to use each: | Primitive | Control | Purpose | Example | | ------------- | ------------------ | ----------------------- | ---------------------------------- | | **Tools** | Model-driven | Actions the AI performs | Send email, create task, query API | | **Resources** | Application-driven | Context data for AI | Files, preferences, schedules | | **Prompts** | User-driven | Templates users invoke | `/analyze`, `/review`, `/support` | ## Next Steps Expose data to AI agents Template prompts for AI # Debugging MCP Servers Source: https://docs.leanmcp.com/debugging Learn how to test and debug your MCP servers effectively # Debugging Your MCP Server While developing your MCP server, it's essential to ensure it's doing what it's intended to do. This guide covers the best tools and methods for testing and debugging your MCP servers. ## Why Debug Your MCP Server? Before deploying your MCP server, thorough testing helps you: * Verify all tools and resources work as expected * Catch configuration errors early * Ensure AI agents can properly interact with your server * Identify performance issues and bottlenecks ## Method 1: MCP Playground (Recommended) **Best for**: Initial development and comprehensive testing We've created an open-source platform specifically designed for testing and debugging MCP servers. ### Get Started with MCP Playground 1. **Repository**: [https://github.com/rosaboyle/mcp-playground](https://github.com/rosaboyle/mcp-playground) 2. **Installation**: Clone the repository and follow the setup instructions 3. **Connect**: Add your MCP server URL to the playground 4. **Test**: Interactive interface to test all your MCP tools and resources ### Features Test tools and resources with real-time feedback See exactly what data is being sent and received Detailed error messages and debugging information Track response times and identify bottlenecks ### How to Use 1. **Install the playground** from the GitHub repository 2. **Start your MCP server** (locally or deployed) 3. **Add your MCP server URL** to the playground 4. **Test individual tools** and verify responses 5. **Check resources** to ensure data is accessible 6. **Debug any issues** using the detailed logs ## Method 2: Postman Testing **Best for**: API-level testing and automation Postman provides excellent support for testing MCP servers through its standard HTTP request features. ### Setup Instructions 1. **Open Postman** and create a new collection 2. **Add your MCP server URL** as the base URL 3. **Configure headers** as required by your MCP server 4. **Create requests** for each tool and resource ### Testing Workflow ```json theme={null} // Example: Testing a tool POST {{mcp_server_url}}/tools/execute Content-Type: application/json { "tool_name": "your_tool_name", "arguments": { "param1": "value1", "param2": "value2" } } ``` ### Benefits * **Automated testing** with collection runner * **Environment variables** for different server instances * **Test scripts** for validation * **Easy sharing** with team members ## Method 3: MCP Inspector **Best for**: Protocol-level debugging and validation Anthropic provides an official MCP Inspector tool for testing MCP server compliance and functionality. ### About MCP Inspector The MCP Inspector helps you: * Validate MCP protocol compliance * Test server initialization * Verify tool and resource definitions * Check error handling ### How to Access MCP Inspector is available through Anthropic's official tools. Check the [Anthropic documentation](https://docs.anthropic.com/claude/docs/mcp) for the latest access instructions and installation guide. ### Usage Instructions 1. **Install** the MCP Inspector following Anthropic's documentation 2. **Configure** your MCP server connection 3. **Run diagnostics** to check protocol compliance 4. **Review results** and fix any identified issues 5. **Re-test** until all checks pass ### What It Checks * MCP protocol version compatibility * Tool and resource schema validation * Error response formatting * Connection stability * Performance characteristics ## Method 4: LLM Client Testing **Best for**: End-to-end user experience testing This method is **not recommended** for early debugging. Use it only after your MCP server passes testing with the above methods. ### When to Use This Method Only test with LLM clients when: * ✅ Your MCP server passes all playground tests * ✅ Postman testing shows consistent responses * ✅ MCP Inspector validates protocol compliance * ✅ You're ready to test real user scenarios ### Recommended LLM Clients Test your MCP server with popular AI applications: AI-powered code editor AI development environment Anthropic's official client ### Testing Process 1. **Connect** your MCP server to the LLM client 2. **Create test prompts** that should trigger your tools 3. **Monitor behavior** - how does the AI use your tools? 4. **Check responses** - are they what you expected? 5. **Identify confusion points** - where does the AI struggle? ### Example Test Prompts ```text theme={null} // For a file management MCP "Please list all files in the current directory and then create a new file called 'test.txt' with the content 'Hello World'" // For a database MCP "Show me all users in the database, then add a new user with name 'John Doe' and email 'john@example.com'" // For a task management MCP "Create a new task called 'Review documentation' with priority 'high', then show me all pending tasks" ``` ## Best Practices ### Development Workflow Test basic functionality and fix obvious issues Test error conditions and boundary cases Ensure protocol compliance Test the deployed version before LLM integration Final validation with real AI clients ### Common Issues and Solutions **Symptoms**: Tools appear in the client but don't execute **Solutions**: * Check tool schema definitions * Verify parameter validation * Review error logs for execution failures * Test with MCP Playground first **Symptoms**: AI can't access your data resources **Solutions**: * Verify resource URIs are correct * Check permissions and authentication * Test resource endpoints individually * Review MCP protocol compliance **Symptoms**: Long delays between requests and responses **Solutions**: * Profile your tool execution times * Optimize database queries * Add caching where appropriate * Check network connectivity **Symptoms**: AI calls tools incorrectly or unnecessarily **Solutions**: * Improve tool descriptions and examples * Add better parameter validation * Review tool naming conventions * Test with clearer prompts ### Debugging Checklist Before considering your MCP server production-ready: * [ ] **Basic functionality** tested in MCP Playground * [ ] **All tools** execute successfully with valid inputs * [ ] **All resources** return expected data * [ ] **Error handling** works for invalid inputs * [ ] **Protocol compliance** validated with MCP Inspector * [ ] **Performance** meets your requirements * [ ] **LLM integration** tested with target clients * [ ] **Documentation** updated with any changes ## Next Steps Ready to deploy? Follow our deployment guide Learn how to build better MCP servers Set up CI/CD for your MCP server Need assistance? Contact our support team ## Additional Resources * **MCP Playground**: [https://github.com/rosaboyle/mcp-playground](https://github.com/rosaboyle/mcp-playground) * **Anthropic MCP Documentation**: [https://docs.anthropic.com/claude/docs/mcp](https://docs.anthropic.com/claude/docs/mcp) * **MCP Protocol Specification**: Official protocol documentation * **Community Forum**: Share debugging tips and get help from other developers # Deploy on AWS, GCP, and Azure Source: https://docs.leanmcp.com/deploy/cloud-providers Deploy MCPs on major cloud providers If your organization already uses AWS, GCP, or Azure, you can deploy MCPs on your existing infrastructure. The LeanMCP SDK works on any Node.js runtime. However, deploying on cloud providers requires significant DevOps expertise. You need to configure logging, monitoring, scaling, networking, and security yourself. *** ## The Reality of Cloud Deployment Deploying MCPs properly on AWS, GCP, or Azure is not trivial. To get it right, you need: * **Logging and observability** — CloudWatch, Cloud Logging, or Azure Monitor * **Scaling configuration** — auto-scaling policies, load balancers * **Networking** — VPCs, security groups, IAM roles * **Access controls** — who can deploy, who can access logs * **CI/CD pipelines** — automated builds and deployments * **Health checks and alerting** — know when things break Even experienced teams typically need **6-7 DevOps engineers** and **500-600 hours** to set up production-ready infrastructure. That's months of work before you even start building MCP features. *** ## Recommended Services If you must deploy on cloud providers, use managed container services. They handle scaling and infrastructure, reducing (but not eliminating) the DevOps burden. | Provider | Recommended Service | Description | | --------- | ------------------- | ------------------------------------------- | | **AWS** | Fargate (with ECS) | Serverless containers, no server management | | **GCP** | Cloud Run | Fully managed container platform | | **Azure** | Container Apps | Serverless containers with auto-scaling | *** ## AWS: Fargate with ECS AWS Fargate runs containers without managing servers. Combine it with ECS for orchestration. ### Step 1: Create Dockerfile ```dockerfile theme={null} FROM node:20-alpine WORKDIR /app COPY package*.json ./ RUN npm ci --only=production COPY . . EXPOSE 3000 CMD ["node", "dist/server.js"] ``` ### Step 2: Build and Push to ECR ```bash theme={null} # Authenticate with ECR aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin .dkr.ecr.us-east-1.amazonaws.com # Build image docker build -t my-mcp . # Tag and push docker tag my-mcp:latest .dkr.ecr.us-east-1.amazonaws.com/my-mcp:latest docker push .dkr.ecr.us-east-1.amazonaws.com/my-mcp:latest ``` ### Step 3: Create ECS Service ```bash theme={null} # Create task definition (task-definition.json) aws ecs register-task-definition --cli-input-json file://task-definition.json # Create service aws ecs create-service \ --cluster my-cluster \ --service-name my-mcp-service \ --task-definition my-mcp:1 \ --desired-count 2 \ --launch-type FARGATE ``` *** ## GCP: Cloud Run Cloud Run is GCP's fully managed container platform. It's simpler than Fargate for basic deployments. ### Step 1: Build with Cloud Build ```bash theme={null} # Build and push to Artifact Registry gcloud builds submit --tag gcr.io/PROJECT_ID/my-mcp ``` ### Step 2: Deploy to Cloud Run ```bash theme={null} gcloud run deploy my-mcp \ --image gcr.io/PROJECT_ID/my-mcp \ --platform managed \ --region us-central1 \ --allow-unauthenticated \ --memory 512Mi \ --timeout 300 ``` Cloud Run supports up to 60 minutes timeout on paid plans, much better than Lambda/Vercel. *** ## Azure: Container Apps Azure Container Apps is Microsoft's serverless container platform. ### Step 1: Create Container Registry ```bash theme={null} az acr create --resource-group myResourceGroup --name myregistry --sku Basic ``` ### Step 2: Build and Push ```bash theme={null} az acr build --registry myregistry --image my-mcp:v1 . ``` ### Step 3: Deploy Container App ```bash theme={null} az containerapp create \ --name my-mcp \ --resource-group myResourceGroup \ --environment myEnvironment \ --image myregistry.azurecr.io/my-mcp:v1 \ --target-port 3000 \ --ingress external ``` *** ## Adding Monitoring Cloud providers require you to configure monitoring yourself: ### AWS CloudWatch ```typescript theme={null} import { CloudWatchClient, PutMetricDataCommand } from "@aws-sdk/client-cloudwatch"; const cloudwatch = new CloudWatchClient({}); async function logToolCall(toolName: string, duration: number) { await cloudwatch.send(new PutMetricDataCommand({ Namespace: "MCP/Tools", MetricData: [{ MetricName: "Duration", Dimensions: [{ Name: "Tool", Value: toolName }], Value: duration, Unit: "Milliseconds" }] })); } ``` ### GCP Cloud Monitoring ```typescript theme={null} const monitoring = require('@google-cloud/monitoring'); const client = new monitoring.MetricServiceClient(); // Similar setup for custom metrics ``` *** ## Enterprise Option: LeanMCP on Your Cloud For enterprise customers who need to stay on AWS/GCP/Azure for compliance or existing infrastructure reasons, we offer a managed deployment option. We deploy our platform on **your cloud account** with our infrastructure code. You get: * Battle-tested infrastructure * Full monitoring and observability * No DevOps overhead on your team * Your data stays in your cloud Contact us at [founders@leanmcp.com](mailto:founders@leanmcp.com) for enterprise deployments. *** ## Comparison | Approach | Setup Time | DevOps Required | Monitoring | | ------------------------- | ------------- | --------------- | ------------------ | | **DIY on Cloud** | 500-600 hours | 6-7 engineers | Configure yourself | | **LeanMCP Platform** | Minutes | None | Built-in | | **LeanMCP on Your Cloud** | Days | Our team | Built-in | *** ## Recommendation If you're already deep in AWS/GCP/Azure and have DevOps resources, these guides help you get started. But for most teams, [LeanMCP Platform](/deploy/leanmcp-platform) is faster and cheaper than building infrastructure from scratch. Deploy in minutes, not months LeanMCP on your cloud # Deployment Overview Source: https://docs.leanmcp.com/deploy/introduction Deploy your MCP servers to production LeanMCP SDK doesn't lock you into any specific platform. You can deploy your MCPs anywhere you want — LeanMCP Platform, Vercel, AWS, GCP, Azure, or your own infrastructure. That said, different platforms have different trade-offs. This guide helps you choose the right one. ## Deployment Options | Platform | Best For | Limitations | | -------------------- | ------------------------------------------ | ---------------------- | | **LeanMCP Platform** | Production MCPs, monitoring, observability | None for MCPs | | **Vercel** | Simple MCPs, agent frontends | Timeout limits (5-30s) | | **AWS/GCP/Azure** | Enterprise, existing cloud infrastructure | DevOps overhead | ## Recommended: LeanMCP Platform If you're building MCPs, LeanMCP Platform is the easiest choice. It's built specifically for MCPs — monitoring, observability, and auth are built-in. No DevOps required. Deploy in three commands: ```bash theme={null} leanmcp login leanmcp create-project leanmcp deploy ``` ## Other Platforms The LeanMCP SDK works on any Node.js hosting platform. If you need to deploy on Vercel, AWS, or other cloud providers, we have guides for each: Easiest way to deploy MCPs For simple MCPs and agent frontends Enterprise cloud deployments # Deploy on LeanMCP Platform Source: https://docs.leanmcp.com/deploy/leanmcp-platform The easiest way to build, deploy, and monitor MCPs LeanMCP Platform is built specifically for MCPs. It's the easiest way to deploy, monitor, and update your MCP servers in production. You get: * **Zero-config deployment** — no DevOps, no infrastructure setup * **Built-in monitoring** — see tool calls, errors, latency in real-time * **Observability** — logs, traces, and metrics out of the box * **Auth integration** — connect Clerk, Auth0, or other providers directly on the platform * **GitHub CI/CD** — push to GitHub, automatically deploy to production *** ## Option 1: GitHub Integration (Recommended) Connect your GitHub repository and deploy automatically on every push. ### Step 1: Create a Project Go to [leanmcp.com](https://leanmcp.com) and create a new project. ### Step 2: Connect GitHub Link your GitHub repository to the project. LeanMCP will automatically detect your `leanmcp.config.js` or `package.json`. ### Step 3: Push and Deploy Every push to your main branch triggers a deployment: ```bash theme={null} git add . git commit -m "Update MCP tools" git push origin main ``` Your MCP is live within minutes at `https://your-project.leanmcp.link`. *** ## Option 2: LeanMCP CLI Deploy directly from your terminal using the LeanMCP CLI. ### Step 1: Install and Login ```bash theme={null} npm install -g @leanmcp/cli leanmcp login ``` ### Step 2: Create a Project ```bash theme={null} leanmcp create my-mcp-server --install cd my-mcp-server ``` ### Step 3: Deploy ```bash theme={null} leanmcp deploy . ``` That's it. Your MCP is live. ### Full CLI Workflow ```bash theme={null} # Install CLI npm install -g @leanmcp/cli # Authenticate leanmcp login # Create new project with dependencies leanmcp create my-mcp-server --install cd my-mcp-server # Test locally leanmcp dev # Deploy to production leanmcp deploy . # List your projects leanmcp projects list # Get project details leanmcp projects get ``` *** ## Monitoring and Observability Once deployed, the LeanMCP dashboard shows you: * **Tool call analytics** — which tools are called, how often, success rates * **Error tracking** — see errors with full stack traces * **Latency metrics** — p50, p95, p99 response times * **Real-time logs** — stream logs from your production MCP No additional setup required. It's all built-in. *** ## Auth on the Platform If your MCP needs authentication, you can configure it directly on LeanMCP Platform: 1. Go to your project settings 2. Select your auth provider (Clerk, Auth0, Cognito, etc.) 3. Add your credentials 4. Enable auth for your MCP Your tools automatically receive the authenticated user context. No code changes needed. *** ## Why LeanMCP Platform? | Feature | LeanMCP Platform | DIY Deployment | | -------------- | ---------------- | ----------------------- | | **Setup time** | Minutes | Hours to days | | **Monitoring** | Built-in | Configure yourself | | **Auth** | One-click setup | 600+ lines of code | | **Scaling** | Automatic | Manual configuration | | **Cost** | Pay per use | Infrastructure + DevOps | Go to LeanMCP Platform CLI documentation # Deploy on Vercel Source: https://docs.leanmcp.com/deploy/vercel Deploy simple MCPs on Vercel with important caveats Vercel is a great platform for frontend applications and simple APIs. You can deploy MCPs on Vercel, but there are important limitations to understand. The LeanMCP SDK does not block you from deploying to Vercel or any other platform. However, Vercel's architecture introduces constraints that may not work for all MCPs. *** ## When Vercel Works Well Vercel is a good choice if: * Your MCP is **simple** — basic API wrappers, quick tool calls * You're building an **agent frontend** and the MCP is just one part of your application * Your tool calls complete in **under 30 seconds** (or under 5 seconds on Edge) * You don't have **long-running network requests** or complex orchestration *** ## When Vercel Doesn't Work Vercel runs on AWS Lambda under the hood. This means you inherit Lambda's limitations: ### Timeout Limits | Vercel Runtime | Timeout | | -------------------- | ----------------------- | | Edge Functions | 5 seconds | | Serverless Functions | 30 seconds (Pro plan) | | Serverless Functions | 10 seconds (Hobby plan) | If your MCP does sophisticated things — like summarizing API responses to reduce tokens, orchestrating multiple backend calls, or processing large datasets — you'll hit these timeouts. ### Cold Starts Lambda functions have cold starts. The first request after a period of inactivity takes longer. For MCPs that need consistent low latency, this can be problematic. ### No Built-in MCP Features Vercel doesn't have an MCP SDK. You need to: * Build your own HTTP transport layer * Handle authentication yourself * Set up your own monitoring and logging * Manage observability separately *** ## Deploying to Vercel If your use case fits, here's how to deploy: ### Step 1: Create API Route ```typescript theme={null} // app/api/mcp/route.ts import { LeanMCP } from '@leanmcp/sdk'; const mcp = new LeanMCP({ name: 'my-vercel-mcp', version: '1.0.0' }); mcp.addTool({ name: 'quick_lookup', description: 'Look up data quickly', inputSchema: { type: 'object', properties: { query: { type: 'string' } } }, handler: async (args) => { // Keep this fast - under 30 seconds! const result = await quickLookup(args.query); return result; } }); export async function POST(request: Request) { return mcp.handleRequest(request); } ``` ### Step 2: Configure Vercel ```json theme={null} // vercel.json { "functions": { "app/api/mcp/route.ts": { "maxDuration": 30 } } } ``` ### Step 3: Deploy ```bash theme={null} vercel deploy --prod ``` Or connect your GitHub repository for automatic deployments. *** ## Adding Monitoring Vercel doesn't include MCP-specific monitoring. You'll need to add your own: ```typescript theme={null} // Add logging manually mcp.addTool({ name: 'my_tool', handler: async (args) => { const start = Date.now(); try { const result = await doSomething(args); console.log(`Tool completed in ${Date.now() - start}ms`); return result; } catch (error) { console.error(`Tool failed: ${error.message}`); throw error; } } }); ``` Consider integrating with external monitoring services like Datadog, New Relic, or Sentry. *** ## Comparison: Vercel vs LeanMCP Platform | Feature | Vercel | LeanMCP Platform | | --------------- | ---------------------- | ---------------- | | **Timeout** | 5-30 seconds | No limit | | **MCP SDK** | DIY | Built-in | | **Monitoring** | External services | Built-in | | **Auth** | DIY | One-click setup | | **Cold starts** | Yes | Optimized | | **Best for** | Simple MCPs, frontends | Production MCPs | *** ## Recommendation If you're building an agent application where the frontend is on Vercel and the MCP is a small part — Vercel works fine. If you're building production MCPs with complex tool calls, long-running operations, or need monitoring and auth — use [LeanMCP Platform](/deploy/leanmcp-platform) instead. No timeout limits, built-in monitoring AWS, GCP, Azure deployment # Advanced Examples Source: https://docs.leanmcp.com/examples/advanced Authentication and Elicitation examples # Advanced Examples Advanced LeanMCP examples demonstrating authentication and user input collection. Protect tools with JWT authentication Collect user input before tool execution *** ## OAuth Authentication Protect your MCP tools with JWT authentication using `@leanmcp/auth`. ### Supported Providers | Provider | Package | Best For | | --------------- | --------------- | ------------------------ | | **Clerk** | `@leanmcp/auth` | Quick start, modern apps | | **AWS Cognito** | `@leanmcp/auth` | AWS ecosystem | | **Auth0** | `@leanmcp/auth` | Enterprise apps | ### Code Example ```typescript theme={null} import { Tool } from "@leanmcp/core"; import { AuthProvider, Authenticated } from "@leanmcp/auth"; // Initialize auth provider const authProvider = new AuthProvider('clerk', { frontendApi: process.env.CLERK_FRONTEND_API, secretKey: process.env.CLERK_SECRET_KEY }); await authProvider.init(); export class SecureService { // Public - no auth required @Tool({ description: "Public endpoint" }) async getPublicInfo() { return { message: "Anyone can access this" }; } // Protected - requires valid JWT @Tool({ description: "Get user profile" }) @Authenticated(authProvider) async getProfile() { // authUser is automatically injected! return { userId: authUser.sub, email: authUser.email }; } } ``` ### How It Works ``` 1. Client sends token in _meta.authorization 2. @Authenticated validates JWT with provider 3. authUser injected with decoded payload 4. Tool executes with user context ``` OAuth Basic Example *** ## Elicitation Forms Collect structured user input before tool execution using `@leanmcp/elicitation`. ### How Elicitation Works ``` 1. Client calls tool (missing required fields) 2. @Elicitation returns form schema 3. Client displays form to user 4. Client calls tool again (complete data) 5. Tool executes normally ``` ### Code Example ```typescript theme={null} import { Tool } from "@leanmcp/core"; import { Elicitation } from "@leanmcp/elicitation"; export class ContactService { @Tool({ description: "Submit contact form" }) @Elicitation({ title: "Contact Us", description: "Please fill out the form", fields: [ { name: "name", label: "Your Name", type: "text", required: true }, { name: "email", label: "Email", type: "email", required: true }, { name: "subject", label: "Subject", type: "select", options: [ { label: "General", value: "general" }, { label: "Support", value: "support" }, { label: "Sales", value: "sales" } ] }, { name: "message", label: "Message", type: "textarea", required: true, validation: { minLength: 10, maxLength: 1000 } } ] }) async submitContact(args: { name: string; email: string; subject: string; message: string; }) { return { success: true, ticketId: `TICKET-${Date.now()}` }; } } ``` ### Field Types | Type | Description | | ------------- | --------------------- | | `text` | Single line text | | `textarea` | Multi-line text | | `email` | Email with validation | | `number` | Numeric input | | `boolean` | Checkbox | | `select` | Dropdown | | `multiselect` | Multiple selection | | `date` | Date picker | ### Multi-Step Forms ```typescript theme={null} @Elicitation({ strategy: "multi-step", builder: () => [ { title: "Step 1: Account", fields: [ { name: "email", label: "Email", type: "email", required: true } ] }, { title: "Step 2: Details", condition: (prev) => prev.accountType === "business", fields: [ { name: "company", label: "Company", type: "text", required: true } ] } ] }) ``` Elicitation Example *** ## Run the Examples ```bash theme={null} # Clone the repository git clone https://github.com/Leanmcp-Community/sdk-examples.git cd sdk-examples # Run auth example cd auth-examples/oauth-basic npm install && npm run dev # Run elicitation example cd elicitation-examples/basic-elicitation npm install && npm run dev ``` ## Next Steps Tools, Resources, Prompts Learn the fundamentals # Basic Examples Source: https://docs.leanmcp.com/examples/basic Core MCP examples - Tools, Resources, and Prompts # Basic Examples Get started with LeanMCP using these foundational examples demonstrating the three core MCP primitives. Actions AI can execute on your behalf Read-only data for AI context Reusable conversation templates ## Resources Example Dynamic system configuration exposed as MCP resources. ```typescript theme={null} import { Resource } from "@leanmcp/core"; export class ConfigService { @Resource({ description: "Current server status", mimeType: "application/json" }) serverStatus() { return { status: "healthy", uptime: process.uptime(), memory: process.memoryUsage() }; } @Resource({ description: "Feature flags configuration", mimeType: "application/json" }) featureFlags() { return { darkMode: true, betaFeatures: false, maxUploadSize: "10MB" }; } } ``` System Config Resource Example ## Prompts Example Customer support assistant with specialized prompts. ```typescript theme={null} import { Prompt } from "@leanmcp/core"; export class AssistantService { @Prompt({ description: "Customer support conversation" }) customerSupport(input: { customerName: string; issue: string }) { return { messages: [{ role: "user", content: { type: "text", text: `You are a helpful customer support agent. Customer: ${input.customerName} Issue: ${input.issue} Respond professionally and helpfully.` } }] }; } @Prompt({ description: "Code review assistant" }) codeReviewer() { return { messages: [ { role: "user", content: { type: "text", text: "You are a senior code reviewer." } }, { role: "assistant", content: { type: "text", text: "I'll review for bugs, style, and best practices." } } ] }; } } ``` Customer Assistant Prompts Example ## Run the Examples ```bash theme={null} # Clone the repository git clone https://github.com/Leanmcp-Community/sdk-examples.git cd sdk-examples # Run resources example cd core-examples/resources-example/system-config-resource npm install && npm run dev # Run prompts example cd core-examples/prompts-example/customer-assistant npm install && npm run dev ``` ## Next Steps Auth and Elicitation examples Learn the fundamentals # AI Gateway Source: https://docs.leanmcp.com/guides/ai-gateway Unified API proxy for LLM providers with authentication and observability # AI Gateway The LeanMCP AI Gateway provides a unified API proxy for multiple LLM providers with built-in authentication, token tracking, and observability. ## Features OpenAI, Anthropic, xAI (Grok), Fireworks, ElevenLabs Use official SDKs or LangChain with minimal code changes Firebase JWT or API key authentication Track requests across sessions for observability ## Supported Providers | Provider | Endpoint | Features | | -------------- | ------------------ | -------------------------------------------- | | **OpenAI** | `/v1/openai/*` | Chat, Vision, DALL-E, TTS, Structured Output | | **Anthropic** | `/v1/anthropic/*` | Messages, Vision, Structured Output | | **xAI (Grok)** | `/v1/xai/*` | Chat with Web Search | | **Fireworks** | `/v1/fireworks/*` | Open-source models (Llama, etc.) | | **ElevenLabs** | `/v1/elevenlabs/*` | Text-to-Speech | *** ## Authentication All requests require authentication via the `Authorization` header: ```bash theme={null} Authorization: Bearer ``` **Supported token types:** * **Firebase JWT**: Standard Firebase ID token from your app * **API Key**: LeanMCP API keys (prefixed with `leanmcp_`) *** ## Usage Examples ### OpenAI SDK Use the OpenAI SDK by simply changing the `baseURL`: ```typescript theme={null} import OpenAI from 'openai'; const client = new OpenAI({ baseURL: 'https://aigateway.leanmcp.com/v1/openai/v1', apiKey: 'your-leanmcp-token', // Firebase JWT or API key }); // Streaming chat completion const stream = await client.chat.completions.create({ model: 'gpt-5.2', messages: [ { role: 'user', content: 'Write a haiku about APIs.' } ], stream: true, }); for await (const chunk of stream) { const content = chunk.choices[0]?.delta?.content || ''; process.stdout.write(content); } ``` ### Anthropic SDK ```typescript theme={null} import Anthropic from '@anthropic-ai/sdk'; const client = new Anthropic({ baseURL: 'https://aigateway.leanmcp.com/v1/anthropic', apiKey: 'your-leanmcp-token', }); const stream = await client.messages.stream({ model: 'claude-sonnet-4-5', max_tokens: 300, messages: [ { role: 'user', content: 'Write a haiku about cloud computing.' } ], }); for await (const event of stream) { if (event.type === 'content_block_delta' && event.delta.type === 'text_delta') { process.stdout.write(event.delta.text); } } ``` ### LangChain (Python) LangChain provides a powerful abstraction for building LLM applications. The AI Gateway works seamlessly with LangChain by simply changing the `base_url` parameter. ```python theme={null} from langchain_openai import ChatOpenAI from langchain_core.messages import HumanMessage # Create ChatOpenAI with gateway configuration llm = ChatOpenAI( model="gpt-5.2", base_url="https://aigateway.leanmcp.com/v1/openai/v1", api_key="your-leanmcp-token", # Firebase JWT or API key temperature=0.7, ) # Basic chat completion response = llm.invoke([ HumanMessage(content="Write a haiku about APIs.") ]) print(response.content) ``` ```python theme={null} from langchain_anthropic import ChatAnthropic from langchain_core.messages import HumanMessage # Create ChatAnthropic with gateway configuration llm = ChatAnthropic( model="claude-sonnet-4-5", base_url="https://aigateway.leanmcp.com/v1/anthropic", api_key="your-leanmcp-token", temperature=0.7, max_tokens=300, ) response = llm.invoke([ HumanMessage(content="Explain quantum computing briefly.") ]) print(response.content) ``` #### Streaming with LangChain ```python theme={null} from langchain_openai import ChatOpenAI from langchain_core.messages import HumanMessage llm = ChatOpenAI( model="gpt-5.2", base_url="https://aigateway.leanmcp.com/v1/openai/v1", api_key="your-leanmcp-token", streaming=True, ) # Token-by-token streaming for chunk in llm.stream([ HumanMessage(content="Explain quantum computing in 3 sentences.") ]): print(chunk.content, end="", flush=True) ``` #### Structured Output with Pydantic ```python theme={null} from typing import List from pydantic import BaseModel, Field from langchain_openai import ChatOpenAI from langchain_core.messages import HumanMessage # Define Pydantic models class Step(BaseModel): step_number: int = Field(description="The step number") title: str = Field(description="Brief title of the step") description: str = Field(description="Detailed description") class Recipe(BaseModel): name: str = Field(description="Name of the recipe") cuisine: str = Field(description="Type of cuisine") prep_time_minutes: int = Field(description="Prep time in minutes") ingredients: List[str] = Field(description="List of ingredients") steps: List[Step] = Field(description="Cooking steps") llm = ChatOpenAI( model="gpt-5.2", base_url="https://aigateway.leanmcp.com/v1/openai/v1", api_key="your-leanmcp-token", temperature=0, ) # Get structured output structured_llm = llm.with_structured_output(Recipe) recipe: Recipe = structured_llm.invoke([ HumanMessage(content="Give me a recipe for pasta carbonara.") ]) print(f"Recipe: {recipe.name}") print(f"Ingredients: {recipe.ingredients}") ``` #### Tool Calling with LangChain ```python theme={null} from langchain_openai import ChatOpenAI from langchain_core.messages import HumanMessage from langchain_core.tools import tool @tool def get_weather(city: str, unit: str = "celsius") -> str: """Get the current weather for a city.""" # Your weather API logic here return f"Weather in {city}: 22C, Sunny" @tool def calculate(expression: str) -> str: """Calculate a mathematical expression.""" return f"Result: {eval(expression)}" llm = ChatOpenAI( model="gpt-5.2", base_url="https://aigateway.leanmcp.com/v1/openai/v1", api_key="your-leanmcp-token", temperature=0, ) # Bind tools to the model tools = [get_weather, calculate] llm_with_tools = llm.bind_tools(tools) response = llm_with_tools.invoke([ HumanMessage(content="What's the weather in London and calculate 25 * 4?") ]) # Process tool calls for tool_call in response.tool_calls: print(f"Tool: {tool_call['name']}, Args: {tool_call['args']}") ``` #### Multi-Model Chains Orchestrate multiple providers in a single LangChain workflow: ```python theme={null} from langchain_openai import ChatOpenAI from langchain_anthropic import ChatAnthropic from langchain_core.prompts import ChatPromptTemplate from langchain_core.output_parsers import StrOutputParser # Create both LLMs openai_llm = ChatOpenAI( model="gpt-5.2", base_url="https://aigateway.leanmcp.com/v1/openai/v1", api_key="your-leanmcp-token", ) anthropic_llm = ChatAnthropic( model="claude-sonnet-4-5", base_url="https://aigateway.leanmcp.com/v1/anthropic", api_key="your-leanmcp-token", max_tokens=300, ) # Step 1: OpenAI generates content generator_prompt = ChatPromptTemplate.from_messages([ ("system", "Generate a short story premise (2-3 sentences)."), ("human", "Topic: {topic}"), ]) # Step 2: Anthropic critiques critic_prompt = ChatPromptTemplate.from_messages([ ("system", "Review and suggest one improvement for this story premise."), ("human", "Premise: {premise}"), ]) output_parser = StrOutputParser() # Build chains generator_chain = generator_prompt | openai_llm | output_parser critic_chain = critic_prompt | anthropic_llm | output_parser # Execute multi-model workflow premise = generator_chain.invoke({"topic": "a robot learning to dream"}) critique = critic_chain.invoke({"premise": premise}) print(f"Generated: {premise}") print(f"Critique: {critique}") ``` **Requirements**: Install LangChain packages: ```bash theme={null} pip install langchain-openai langchain-anthropic langchain-core ``` *** ## curl Examples ### OpenAI Streaming ```bash theme={null} curl -N -X POST "https://aigateway.leanmcp.com/v1/openai/v1/chat/completions" \ -H "Authorization: Bearer $AUTH_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "model": "gpt-5.2", "messages": [{"role": "user", "content": "Hello!"}], "stream": true }' ``` ### Anthropic Messages ```bash theme={null} curl -X POST "https://aigateway.leanmcp.com/v1/anthropic/v1/messages" \ -H "Authorization: Bearer $AUTH_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "model": "claude-sonnet-4-5", "max_tokens": 300, "messages": [{"role": "user", "content": "Hello!"}] }' ``` ### xAI (Grok) with Web Search ```bash theme={null} curl -N -X POST "https://aigateway.leanmcp.com/v1/xai/v1/chat/completions" \ -H "Authorization: Bearer $AUTH_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "model": "grok-2-latest", "messages": [{"role": "user", "content": "What are the top tech news today?"}], "stream": true, "search_parameters": {"mode": "auto"} }' ``` ### Fireworks (Llama) ```bash theme={null} curl -N -X POST "https://aigateway.leanmcp.com/v1/fireworks/inference/v1/chat/completions" \ -H "Authorization: Bearer $AUTH_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "model": "accounts/fireworks/models/llama-v3p1-8b-instruct", "messages": [{"role": "user", "content": "Explain quantum computing."}], "stream": true, "max_tokens": 100 }' ``` ### ElevenLabs TTS ```bash theme={null} curl -X POST "https://aigateway.leanmcp.com/v1/elevenlabs/v1/text-to-speech/21m00Tcm4TlvDq8ikWAM" \ -H "Authorization: Bearer $AUTH_TOKEN" \ -H "Content-Type: application/json" \ -o "output.mp3" \ -d '{ "text": "Hello! This is a test.", "model_id": "eleven_monolingual_v1", "voice_settings": {"stability": 0.5, "similarity_boost": 0.5} }' ``` *** ## Advanced Features ### Structured Output (OpenAI) ```typescript theme={null} const response = await fetch('https://aigateway.leanmcp.com/v1/openai/v1/chat/completions', { method: 'POST', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ model: 'gpt-5.2', messages: [ { role: 'user', content: 'Recommend 3 sci-fi movies from the 2010s.' } ], response_format: { type: 'json_schema', json_schema: { name: 'movie_recommendations', strict: true, schema: { type: 'object', properties: { recommendations: { type: 'array', items: { type: 'object', properties: { title: { type: 'string' }, year: { type: 'number' }, genre: { type: 'string' }, reason: { type: 'string' }, }, required: ['title', 'year', 'genre', 'reason'], }, }, }, required: ['recommendations'], }, }, }, }), }); ``` ### Session Tracking Include a session ID to track requests across a conversation: ```bash theme={null} curl -X POST "https://aigateway.leanmcp.com/v1/openai/v1/chat/completions" \ -H "Authorization: Bearer $AUTH_TOKEN" \ -H "Content-Type: application/json" \ -H "leanmcp-session-id: my-session-123" \ -d '{"model": "gpt-5.2", "messages": [...]}' ``` ### Bring Your Own API Key To use your own provider API key instead of platform keys: ```bash theme={null} curl -X POST "https://aigateway.leanmcp.com/v1/anthropic/v1/messages" \ -H "Authorization: Bearer $AUTH_TOKEN" \ -H "x-provider-api-key: sk-ant-your-key" \ -H "Content-Type: application/json" \ -d '{...}' ``` *** ## Related * [Authentication Overview](/sdk/auth) - Server-side authentication with `@Authenticated` * [OAuth Server & Proxy](/sdk/auth-oauth-server) - Build OAuth authorization servers * [GPT Apps Guide](/sdk/ui-gpt-apps) - Build apps for ChatGPT # Converting APIs to MCPs Source: https://docs.leanmcp.com/guides/api-to-mcp How to efficiently convert your existing APIs into MCP servers Converting APIs to MCPs looks trivial. Many tools exist that auto-convert OpenAPI specs directly into MCPs. But **direct conversion is problematic** — what works for developers often fails miserably for AI agents. *** ## The Problem with Direct Conversion APIs are designed to return comprehensive data. MCPs need to return **minimal, relevant data**. ### Example: Web Scraper API Take an API like Apify's web scraper. When you search, it returns: * All search result links * Full HTML of each page * Metadata, timestamps, pagination info * 10-20 results per request Now imagine feeding this to an LLM. You're dumping **entire HTML pages** into the context window. The LLM drowns in irrelevant content and either: * Forgets earlier context * Hits token limits * Produces poor results ### Same Problem Everywhere | Data Source | API Returns | MCP Should Return | | --------------- | -------------------- | ----------------------- | | **Web scraper** | Full HTML pages | Extracted text snippets | | **Database** | All matching rows | Top N relevant results | | **HubSpot/CRM** | Full contact records | Key fields only | | **Search APIs** | Paginated results | Summarized highlights | **Direct API → MCP conversion ignores this fundamental difference.** *** ## Solution 1: Summarize Before Returning Don't return raw API responses. Process them first. ### Option A: Pre-computed Summaries Store summaries alongside your data: ```typescript theme={null} // Your database already has summaries const results = await db.query(` SELECT id, title, summary_slug, key_metrics FROM articles WHERE topic = ? LIMIT 5 `, [input.topic]); // Return only the pre-computed summary fields return results.map(r => ({ id: r.id, title: r.title, summary: r.summary_slug // Already computed, stored in DB })); ``` ### Option B: On-the-fly Summarization Use a small/nano LLM to summarize before returning: ```typescript theme={null} import OpenAI from 'openai'; const openai = new OpenAI(); @Tool({ description: "Search articles and return summaries" }) async searchArticles(input: { query: string }) { // Fetch from your API const rawResults = await api.search(input.query); // Summarize each result with a fast, cheap model const summaries = await Promise.all( rawResults.slice(0, 5).map(async (item) => { const summary = await openai.chat.completions.create({ model: "gpt-5.2", // Fast, cheap nano model messages: [{ role: "user", content: `Summarize in 2 sentences: ${item.content.slice(0, 2000)}` }], max_tokens: 100 }); return { id: item.id, title: item.title, summary: summary.choices[0].message.content }; }) ); return summaries; } ``` **Nano models to consider:** * `gpt-5.2` — Fast, cheap, good quality * `claude-haiku-4-5-20251001` — Anthropic's fastest model * `gemini-1.5-flash` — Google's speed-optimized model * Local models via Ollama for zero-cost summarization *** ## Solution 2: Build a Layer on Top Don't modify your existing API. Build an MCP-optimized layer on top: ```mermaid theme={null} flowchart TD A[Your MCP
Optimized for AI] --> B[Summarization Layer
Processes responses] B --> C[Your Existing API
Unchanged] ``` Your existing API continues serving developers. Your MCP layer: * Calls the same API * Processes/summarizes responses * Returns minimal, relevant data *** ## Authentication: Use Your Existing Auth **Don't create a separate auth system for MCPs.** Use the same OAuth server that authenticates your existing users. ### What You Need | Component | Description | | ----------------------- | ---------------------------------------- | | **OAuth Client ID** | Your application's client identifier | | **OAuth Client Secret** | Your application's secret (keep secure!) | | **OAuth Server URL** | Your auth provider's token endpoint | | **Redirect URI** | Where to redirect after authentication | | **Scopes** | Permissions the MCP needs | ### Same Auth, Same Data ```mermaid theme={null} flowchart TD A[Your OAuth Server] --> B[Web App] A --> C[MCP Server] B -.-> D((Same users, same data, same permissions)) C -.-> D ``` ### Implementation with @leanmcp/auth ```typescript theme={null} import { Service, Tool } from 'leanmcp'; import { OAuth, Protected, AuthUser } from '@leanmcp/auth'; @Service() @OAuth({ provider: 'custom', clientId: process.env.OAUTH_CLIENT_ID, clientSecret: process.env.OAUTH_CLIENT_SECRET, authorizationUrl: 'https://your-auth-server.com/authorize', tokenUrl: 'https://your-auth-server.com/token', scopes: ['read:articles', 'write:comments'] }) export class ArticleService { @Tool({ description: "Get user's saved articles" }) @Protected() async getSavedArticles(@AuthUser() user: any) { // User is authenticated with your existing OAuth // Same token that works with your web app return await api.getArticles({ userId: user.sub }); } } ``` ### Defining Scopes Define scopes based on what the MCP actually needs: ```typescript theme={null} // Don't request all scopes scopes: ['read', 'write', 'delete', 'admin'] // ❌ Too broad // Request only what's needed scopes: ['read:articles', 'read:profile'] // ✅ Minimal ``` *** ## Complete Example: HubSpot Integration ```typescript theme={null} import { Service, Tool } from 'leanmcp'; import { OAuth, Protected, AuthUser } from '@leanmcp/auth'; @Service() @OAuth({ provider: 'custom', clientId: process.env.HUBSPOT_CLIENT_ID, clientSecret: process.env.HUBSPOT_CLIENT_SECRET, authorizationUrl: 'https://app.hubspot.com/oauth/authorize', tokenUrl: 'https://api.hubapi.com/oauth/v1/token', scopes: ['crm.objects.contacts.read'] }) export class HubSpotMCP { @Tool({ description: "Search contacts" }) @Protected() async searchContacts( @AuthUser() user: any, input: { query: string } ) { // Fetch from HubSpot API const contacts = await hubspot.crm.contacts.searchApi.doSearch({ query: input.query, limit: 10, properties: ['firstname', 'lastname', 'email', 'company'] }); // Return only essential fields (not full contact records) return contacts.results.map(c => ({ id: c.id, name: `${c.properties.firstname} ${c.properties.lastname}`, email: c.properties.email, company: c.properties.company })); } } ``` *** ## Summary | Don't | Do | | --------------------------- | --------------------------------- | | Auto-convert OpenAPI to MCP | Build an optimized MCP layer | | Return raw API responses | Summarize/filter before returning | | Create separate MCP auth | Use your existing OAuth server | | Request all scopes | Request minimal scopes needed | | Return full records | Return essential fields only | More on optimizing MCP responses See authentication in action # Adding Auth and Payment Source: https://docs.leanmcp.com/guides/auth-and-payment Implementing authentication and payment in your MCP servers ## Core Philosophy ### When Auth Matters Authentication in MCPs shines when you **already have**: * An existing API with user accounts * A database with access control * Scopes and permissions defined * A working SaaS with authenticated users The goal: **expose the same access control to MCP users** that your existing app users have. Same scopes, same permissions, same data boundaries. Don't create a separate auth system for MCPs. Use your **existing OAuth provider** — same client ID, same tenant, same everything. ### The Architecture ```mermaid theme={null} flowchart TD A[Your OAuth Provider
Clerk / Cognito / Auth0] --> B[Web App Users] A --> C[MCP Users] B --> D[Same Database
Same Permissions
Same Scopes] C --> D ``` *** ## Setting Up Authentication Install `@leanmcp/auth`: ```bash theme={null} npm install @leanmcp/auth @leanmcp/core ``` ### Provider-Specific Dependencies ```bash theme={null} npm install axios jsonwebtoken jwk-to-pem ``` ```bash theme={null} npm install @aws-sdk/client-cognito-identity-provider axios jsonwebtoken jwk-to-pem ``` ```bash theme={null} npm install axios jsonwebtoken jwk-to-pem ``` *** ## Provider Setup ### Clerk (Recommended) Clerk is the easiest option, especially if you plan to add payments later. ```typescript theme={null} import { AuthProvider, Authenticated } from "@leanmcp/auth"; const authProvider = new AuthProvider('clerk', { frontendApi: process.env.CLERK_FRONTEND_API, // e.g., 'xxx.clerk.accounts.dev' secretKey: process.env.CLERK_SECRET_KEY // e.g., 'sk_test_xxx' }); await authProvider.init(); ``` **Environment variables:** ```bash theme={null} CLERK_FRONTEND_API=your-app.clerk.accounts.dev CLERK_SECRET_KEY=sk_test_... ``` ### AWS Cognito If you're using Amazon Amplify, use the **same client ID and user pool**: ```typescript theme={null} const authProvider = new AuthProvider('cognito', { region: process.env.AWS_REGION, userPoolId: process.env.COGNITO_USER_POOL_ID, clientId: process.env.COGNITO_CLIENT_ID // Same as your web app! }); await authProvider.init(); ``` ### Auth0 ```typescript theme={null} const authProvider = new AuthProvider('auth0', { domain: process.env.AUTH0_DOMAIN, clientId: process.env.AUTH0_CLIENT_ID, clientSecret: process.env.AUTH0_CLIENT_SECRET, audience: process.env.AUTH0_AUDIENCE }); await authProvider.init(); ``` *** ## Protecting Tools ### Method-Level Protection ```typescript theme={null} import { Tool } from "@leanmcp/core"; import { Authenticated } from "@leanmcp/auth"; export class UserService { // Protected - requires authentication @Tool({ description: "Get user's private data" }) @Authenticated(authProvider) async getPrivateData(input: { dataId: string }) { // authUser is automatically injected console.log('User ID:', authUser.sub); console.log('Email:', authUser.email); return await db.getData({ userId: authUser.sub, dataId: input.dataId }); } // Public - no authentication @Tool({ description: "Get public info" }) async getPublicInfo() { return { status: "online" }; } } ``` ### Class-Level Protection Protect all methods in a service: ```typescript theme={null} @Authenticated(authProvider) export class SecureService { @Tool({ description: "Tool 1" }) async tool1(input: { data: string }) { // authUser available return { userId: authUser.sub }; } @Tool({ description: "Tool 2" }) async tool2(input: { data: string }) { // authUser available here too return { email: authUser.email }; } } ``` *** ## The authUser Object When using `@Authenticated`, a global `authUser` variable is injected containing the decoded JWT: ```typescript theme={null} { sub: 'user_2abc123xyz', userId: 'user_2abc123xyz', email: 'user@example.com', firstName: 'John', lastName: 'Doe', imageUrl: 'https://img.clerk.com/...' } ``` ```typescript theme={null} { sub: 'user-uuid', email: 'user@example.com', email_verified: true, 'cognito:username': 'username', 'cognito:groups': ['admin', 'users'] } ``` ```typescript theme={null} { sub: 'auth0|507f1f77bcf86cd799439011', email: 'user@example.com', email_verified: true, name: 'John Doe' } ``` *** ## Client-Side: Passing Tokens Clients pass tokens via `_meta.authorization`: ```typescript theme={null} await mcpClient.callTool({ name: "getPrivateData", arguments: { dataId: "123" }, _meta: { authorization: { type: "bearer", token: "eyJhbGciOiJIUzI1NiIs..." // JWT from your auth provider } } }); ``` **Raw MCP request:** ```json theme={null} { "method": "tools/call", "params": { "name": "getPrivateData", "arguments": { "dataId": "123" }, "_meta": { "authorization": { "type": "bearer", "token": "your-jwt-token" } } } } ``` *** ## Adding Payments ### The Challenge Previously, you'd pass Stripe session data to your frontend via API. With MCPs, you need to: 1. Create a payment session 2. Return the payment URL via MCP 3. Let the agent show it to the user ### Using Elicitation for Payments Trigger payment flows with elicitation: ```typescript theme={null} import { Tool } from "@leanmcp/core"; import { Elicitation } from "@leanmcp/elicitation"; import { Authenticated } from "@leanmcp/auth"; import Stripe from 'stripe'; const stripe = new Stripe(process.env.STRIPE_SECRET_KEY); export class PaymentService { @Tool({ description: "Upgrade to premium" }) @Authenticated(authProvider) @Elicitation({ title: "Upgrade to Premium", fields: [ { name: "plan", type: "select", label: "Select Plan", options: [ { value: "monthly", label: "Monthly - $9.99/mo" }, { value: "yearly", label: "Yearly - $99/yr (save 17%)" } ], required: true }, { name: "confirm", type: "boolean", label: "I agree to the terms of service", required: true } ] }) async upgradeToPremium(input: { plan: string; confirm: boolean }) { if (!input.confirm) { return { error: "Please agree to terms" }; } const price = input.plan === 'yearly' ? process.env.STRIPE_YEARLY_PRICE_ID : process.env.STRIPE_MONTHLY_PRICE_ID; // Create Stripe checkout session const session = await stripe.checkout.sessions.create({ customer_email: authUser.email, line_items: [{ price, quantity: 1 }], mode: 'subscription', success_url: `${process.env.APP_URL}/success?session_id={CHECKOUT_SESSION_ID}`, cancel_url: `${process.env.APP_URL}/cancelled`, metadata: { userId: authUser.sub } }); return { message: "Click the link below to complete payment", paymentUrl: session.url, sessionId: session.id }; } } ``` ### Handling Webhooks **Webhooks remain unchanged.** Your existing Stripe webhook handler works the same: ```typescript theme={null} // Your existing webhook - no changes needed app.post('/webhook/stripe', async (req, res) => { const event = stripe.webhooks.constructEvent( req.body, req.headers['stripe-signature'], process.env.STRIPE_WEBHOOK_SECRET ); switch (event.type) { case 'checkout.session.completed': const session = event.data.object; await upgradeUser(session.metadata.userId); break; // ... other events } res.json({ received: true }); }); ``` ### Checking Subscription Status ```typescript theme={null} @Tool({ description: "Check subscription status" }) @Authenticated(authProvider) async checkSubscription() { const user = await db.getUser(authUser.sub); return { plan: user.subscription?.plan || 'free', status: user.subscription?.status || 'none', expiresAt: user.subscription?.expiresAt, canUpgrade: !user.subscription || user.subscription.plan === 'free' }; } ``` *** ## Complete Example ```typescript theme={null} import { Service, Tool, MCPServer, createHTTPServer } from "@leanmcp/core"; import { AuthProvider, Authenticated } from "@leanmcp/auth"; import { Elicitation } from "@leanmcp/elicitation"; import Stripe from 'stripe'; // Initialize auth (use your existing provider!) const authProvider = new AuthProvider('clerk', { frontendApi: process.env.CLERK_FRONTEND_API, secretKey: process.env.CLERK_SECRET_KEY }); await authProvider.init(); const stripe = new Stripe(process.env.STRIPE_SECRET_KEY); @Service() @Authenticated(authProvider) export class PremiumService { @Tool({ description: "Get user profile and subscription" }) async getProfile() { const user = await db.getUser(authUser.sub); return { email: authUser.email, name: `${authUser.firstName} ${authUser.lastName}`, plan: user.subscription?.plan || 'free' }; } @Tool({ description: "Access premium feature" }) async premiumFeature(input: { data: string }) { const user = await db.getUser(authUser.sub); if (user.subscription?.plan !== 'premium') { return { error: "Premium subscription required", upgradeAvailable: true }; } // Premium feature logic return { result: "Premium data processed" }; } @Tool({ description: "Upgrade to premium" }) @Elicitation({ title: "Upgrade to Premium", fields: [ { name: "plan", type: "select", label: "Plan", options: [ { value: "monthly", label: "$9.99/month" }, { value: "yearly", label: "$99/year" } ] } ] }) async upgrade(input: { plan: string }) { const session = await stripe.checkout.sessions.create({ customer_email: authUser.email, line_items: [{ price: input.plan === 'yearly' ? process.env.STRIPE_YEARLY_PRICE : process.env.STRIPE_MONTHLY_PRICE, quantity: 1 }], mode: 'subscription', success_url: `${process.env.APP_URL}/success`, cancel_url: `${process.env.APP_URL}/cancel`, metadata: { userId: authUser.sub } }); return { message: "Complete payment to upgrade", paymentUrl: session.url }; } } // Start server const serverFactory = () => { const server = new MCPServer({ name: "premium-service", version: "1.0.0" }); server.registerService(new PremiumService()); return server.getServer(); }; await createHTTPServer(serverFactory, { port: 3000 }); ``` *** ## Error Handling ```typescript theme={null} import { AuthenticationError } from "@leanmcp/auth"; // Error codes | Code | When | Action | |------|------|--------| | `MISSING_TOKEN` | No token in request | Prompt user to authenticate | | `INVALID_TOKEN` | Token expired/invalid | Refresh token or re-authenticate | | `VERIFICATION_FAILED` | Token verification error | Check provider configuration | ``` *** ## Summary | Aspect | Recommendation | | ----------------- | ----------------------------------------------- | | **Auth Provider** | Use your existing OAuth (Clerk, Cognito, Auth0) | | **Client ID** | Same as your web app | | **Scopes** | Same as your web app | | **Payments** | Use elicitation → Stripe checkout URL | | **Webhooks** | No changes needed | | **Token Passing** | `_meta.authorization.token` | See working auth examples Learn about elicitation Browser-based OAuth flows with PKCE Build authorization servers with provider proxy # LeanMCP vs MCP SDK Source: https://docs.leanmcp.com/guides/leanmcp-vs-mcp-sdk How LeanMCP SDK differs from the official MCP SDK The official MCP SDK gives you the bare minimum to build an MCP server. LeanMCP builds on top of it — the same way Next.js builds on React. You take the underlying protocol, build a framework on top, and make it production-ready. Building an MCP locally is easy. The official SDK works fine for that. But here's the thing — when you want to add the **real features** that actually matter for production MCPs, it becomes really tough: * **Authentication** — connecting with Clerk, Auth0, Cognito, Firebase * **Elicitation** — collecting user input during tool execution * **MCP UI & Apps** — rendering components in clients * **Remote deployment** — running your MCP on a server These are the features that separate a toy from a production service. And the official SDK leaves you completely on your own for all of them. ## What LeanMCP Does LeanMCP abstracts the hard parts. Instead of writing **600-700 lines of code** just to set up authentication, you write **20-30 lines** and run a few CLI commands. That's it. We integrate with the auth providers you already use — Clerk, Auth0, AWS Cognito, Firebase, Google Cloud. We handle the JWT validation, JWKS fetching, token extraction, scope checking. You just add a decorator. And it's entirely **open source**, just like the official MCP SDK. MIT license. Fork it, extend it, contribute to it. ## Deployment: LeanMCP + LeanMCP Platform Think of it like **Next.js + Vercel**. You can deploy Next.js anywhere — AWS, GCP, your own servers. But Vercel gives you optimized deployment, edge functions, and observability out of the box. Same with LeanMCP. You can deploy to any platform you want — AWS, GCP, Railway, Render — you're not locked in. But if you want optimized deployment with built-in observability, monitoring, and zero-config setup, LeanMCP's platform handles that for you with `leanmcp deploy`. ## Why Opinionated Matters MCP is a developing protocol. People don't realize the places where they can go wrong. One example: developers often use **elicitation to collect authentication tokens**. Seems reasonable, right? But it's a security vulnerability. When you use elicitation, the data passes through the MCP client — both the client and server see the token in plain text. If the client is compromised, the token is stolen. The correct approach is using the protocol's `_meta.authorization.token` field, which is handled by the client and never visible in tool responses. Without guidance, you'd never know this. LeanMCP enforces these best practices by design — so you don't accidentally expose vulnerabilities. *** ## The Relationship ```mermaid theme={null} flowchart TD subgraph Official SDK[@modelcontextprotocol/sdk
Protocol + Transport] end subgraph LeanMCP CORE[@leanmcp/core
Decorators + Services] AUTH[@leanmcp/auth
OAuth Providers] ELIC[@leanmcp/elicitation
User Input] CLI[CLI
Create + Deploy] end SDK --> CORE CORE --> AUTH CORE --> ELIC CORE --> CLI ``` LeanMCP is **built on top of** the official SDK. It doesn't replace it — it extends it. *** ## Why Does This Matter? ### Local Development is Easy Either Way Building a basic MCP on your local machine is straightforward with either SDK: ```typescript theme={null} // Official MCP SDK - works fine for basics const server = new McpServer({ name: "my-server" }); server.tool("hello", "Say hello", { name: z.string() }, async ({ name }) => { return { content: [{ type: "text", text: `Hello, ${name}!` }] }; }); ``` ```typescript theme={null} // LeanMCP - also simple @Tool({ description: "Say hello" }) hello(input: { name: string }) { return `Hello, ${input.name}!`; } ``` **No significant difference for basic tools.** ### The Problem: Production Features When you need **real production features**, the official SDK leaves you on your own: | Feature | Official MCP SDK | LeanMCP | | --------------- | --------------------- | ---------------------------------------- | | Authentication | DIY (\~600-700 lines) | `@leanmcp/auth` (\~20-30 lines) | | Elicitation | Manual implementation | `@leanmcp/elicitation` decorator | | OAuth Providers | Build from scratch | Clerk, Auth0, Cognito, Firebase built-in | | HTTP Transport | Basic | Production-ready with session management | | Deployment | Manual | `leanmcp deploy` | *** ## Code Comparison: Authentication ### Official MCP SDK (\~600-700 lines) ```typescript theme={null} // You have to build everything yourself: // 1. JWT validation // 2. JWKS fetching and caching // 3. Token extraction from requests // 4. Scope validation // 5. Error handling // 6. Refresh token logic // 7. Provider-specific quirks import jwt from 'jsonwebtoken'; import jwksClient from 'jwks-rsa'; const client = jwksClient({ jwksUri: 'https://your-provider/.well-known/jwks.json', cache: true, rateLimit: true, }); function getKey(header, callback) { client.getSigningKey(header.kid, (err, key) => { const signingKey = key?.getPublicKey(); callback(null, signingKey); }); } async function validateToken(token: string) { return new Promise((resolve, reject) => { jwt.verify(token, getKey, { issuer: 'https://your-provider/', audience: 'your-audience', }, (err, decoded) => { if (err) reject(err); else resolve(decoded); }); }); } // ... 500+ more lines for middleware, error handling, // scope checking, provider setup, etc. ``` ### LeanMCP (\~20-30 lines) ```typescript theme={null} import { AuthProvider, Authenticated } from "@leanmcp/auth"; const auth = new AuthProvider('clerk', { secretKey: process.env.CLERK_SECRET_KEY! }); await auth.init(); @Authenticated(auth) export class MyService { @Tool({ description: "Protected tool" }) async getData() { // authUser available automatically return { userId: authUser.sub }; } } ``` **3 CLI commands + 20-30 lines of code** vs **600-700 lines of boilerplate**. *** ## Built-in Provider Support LeanMCP integrates with popular auth providers out of the box: | Provider | Setup | | ---------------- | ----------------------------------------------------- | | **Clerk** | `new AuthProvider('clerk', { secretKey })` | | **Auth0** | `new AuthProvider('auth0', { domain, audience })` | | **AWS Cognito** | `new AuthProvider('cognito', { userPoolId, region })` | | **Firebase** | `new AuthProvider('firebase', { projectId })` | | **Google Cloud** | `new AuthProvider('gcp', { projectId })` | | **Custom** | `new AuthProvider('custom', { jwksUri, issuer })` | No need to learn each provider's quirks — LeanMCP handles it. *** ## Opinionated Best Practices The MCP protocol is evolving. Without guidance, developers make mistakes that create **security vulnerabilities**. ### Example: Auth Tokens in Elicitation A common mistake is using elicitation to collect authentication tokens: ```typescript theme={null} // ❌ DANGEROUS: Don't do this! @Tool({ description: "Login" }) async login() { const token = await elicit({ message: "Enter your API token", schema: { token: { type: "string" } } }); // Now both client AND server have seen the token // If client is compromised, token is exposed } ``` **Why this is wrong:** * Elicitation passes data through the MCP client * Client sees the token in plain text * If client is malicious or compromised, token is stolen **The correct approach:** Authentication tokens should flow through the MCP protocol's `_meta.authorization.token` field — handled by the client, never visible in tool responses. LeanMCP enforces this pattern automatically with `@Authenticated`. *** ## Both Are Open Source | | Official MCP SDK | LeanMCP | | -------------- | ----------------------------------------------------------------------------------------- | -------------------------------------------------------------------- | | **License** | MIT | MIT | | **Source** | [github.com/modelcontextprotocol](https://github.com/modelcontextprotocol/typescript-sdk) | [github.com/Leanmcp-Community](https://github.com/Leanmcp-Community) | | **Foundation** | Protocol implementation | Framework on top | LeanMCP is entirely open source. You can fork it, extend it, or contribute to it. *** ## When to Use Each ### Use Official MCP SDK if: * Building a simple, local-only MCP * You want full control over everything * You're experimenting or learning the protocol * You don't need auth, elicitation, or deployment ### Use LeanMCP if: * Building for production * You need authentication (Clerk, Auth0, Cognito, etc.) * You want elicitation with proper validation * You want to deploy remotely * You prefer convention over configuration * You want security best practices enforced *** ## Summary | Aspect | Official MCP SDK | LeanMCP | | ------------------ | ------------------- | ------------------------------- | | **Philosophy** | Minimal, DIY | Opinionated, batteries-included | | **Auth** | Build yourself | Built-in providers | | **Elicitation** | Manual | Decorators | | **Deployment** | Manual | `leanmcp deploy` | | **Code for auth** | \~600-700 lines | \~20-30 lines | | **Best practices** | Your responsibility | Enforced by framework | **LeanMCP is to MCP SDK what Next.js is to React** — same foundation, more structure, faster to production. Build your first MCP with LeanMCP Set up authentication # LeanMCP vs Workato & Others Source: https://docs.leanmcp.com/guides/leanmcp-vs-others How LeanMCP compares to Workato and other integration platforms ## LeanMCP vs Workato Workato was massively anti-MCP initially. They had advertising saying MCP is "AI noise." Now they've pivoted and actually provide a way to host MCPs — but it's very basic. Workato is a closed-source platform built for enterprise integrations. They've been in the space for a long time, but their approach has always been proprietary. They wanted to go against open platforms. Now that MCP is becoming the standard, they're playing catch-up. LeanMCP provides a full-fledged platform — logging, automatic observability, MCP-based optimization of infrastructure. It's not just hosting; it's end-to-end from framework to deployment. And it's open source. Workato gives you basic MCP hosting. LeanMCP gives you the SDK to build, the CLI to manage, and the platform to deploy with production-grade observability. *** ## LeanMCP vs Vercel Vercel is one of the best ways to deploy frontend code, especially if you're using Next.js. It's tightly coupled with Next.js (which Vercel also created), and if you're building a minimalist web app, Vercel shines. For MCPs, it's a different story. Vercel doesn't have an MCP SDK — you have to build your own or use a generic Express server. And because Vercel runs on AWS Lambda under the hood, you inherit Lambda's limitations. The main problem is **timeouts**. Lambda has a 5-second limit on edge functions and 30 seconds otherwise. If your MCP does anything sophisticated — like summarizing API responses to reduce tokens — it might take longer than that. You'll hit timeouts and your requests will fail. Vercel works fine for minimalist MCPs that just wrap APIs and return responses in under 2-3 seconds. But for complex MCPs with token-efficient summarization, it's not the right choice. LeanMCP runs on a virtualization layer deployed on bare metal — not AWS Lambda. No arbitrary timeouts. Seamless scaling. And an SDK built specifically for MCPs, so you can get from idea to deployed production server really fast. | Aspect | Vercel | LeanMCP | | ------------------ | ---------------------- | ------------------------- | | **Best for** | Frontend, Next.js apps | MCP servers | | **Timeout limits** | 5-30 seconds | None | | **MCP SDK** | None (DIY) | Built-in | | **Infrastructure** | AWS Lambda | Bare metal virtualization | *** ## LeanMCP vs AWS Directly If your SaaS is already deployed on AWS, you might think "why not just deploy MCPs there too?" You can. AWS gives you all the features to scale and build — but the learning curve is brutal. To deploy MCPs properly on AWS, you need to figure out logging, observability, access controls, scaling, networking, security groups, IAM roles. Even experienced teams need **6-7 DevOps engineers** and around **500-600 DevOps hours** to get everything right. That's months of work before you even start building MCP features. LeanMCP abstracts this away. For most teams, `leanmcp deploy` handles everything. For enterprise customers who need to stay on AWS for compliance or existing infrastructure reasons, we can deploy our platform on your AWS account with our infrastructure code. You get a battle-tested platform without the DevOps overhead. | Approach | Time to Deploy | DevOps Required | | ----------------------- | -------------------- | ------------------- | | **AWS (DIY)** | 500-600 hours | 6-7 engineers | | **LeanMCP** | Minutes | None | | **LeanMCP on your AWS** | Days (with our help) | Our team handles it | *** ## Summary | Platform | Strengths | Weaknesses for MCPs | | ------------------- | -------------------------- | --------------------------------------- | | **Workato** | Enterprise integrations | Closed source, basic MCP support | | **Vercel** | Frontend deployment | Lambda timeouts, no MCP SDK | | **AWS, GCP, Azure** | Full control, scalability | Massive learning curve, DevOps overhead | | **LeanMCP** | Built for MCPs, end-to-end | | LeanMCP is purpose-built for MCPs. The others can host MCPs, but it's not what they were designed for. Framework comparison Build your first MCP # MCP vs Claude Skills & Code Mode Source: https://docs.leanmcp.com/guides/mcp-vs-claude-skills Understanding when to use Model Context Protocol vs Claude's built-in code execution Anthropic introduced the **Model Context Protocol (MCP)** in November 2024 as an open standard for connecting AI assistants to external tools and data sources. More recently, Anthropic also released **Claude Code Mode** — a sandboxed code execution environment that allows Claude to write and execute code directly. Both approaches have their place. This guide breaks down the differences based on reliability, token usage, user experience, and practical use cases, drawing from empirical data and internal benchmarks. *** ## What is MCP? MCP's primary purpose is to connect tools with LLMs securely. It enables tool builders to expose their capabilities without revealing internal implementation details. Though MCP started as an internal Anthropic project for Claude, it quickly gained widespread adoption across the AI ecosystem. Today, MCP is supported by: * **Workflow builders** like n8n and Gumloop * **OpenAI** (adopted MCP for their platform) * **IDE integrations** like Cursor and Windsurf * **Enterprise platforms** building AI-powered applications At its core, MCP started as a **schema + API wrapper**, but it has evolved into something much more powerful — supporting authentication, elicitation (user input forms), UI components, and more. ## What is Claude Code Mode? Claude Code Mode is Anthropic's approach to executing tool calls **inside a sandbox** rather than through an external MCP server. Instead of calling a predefined API, the agent generates code on-the-fly and executes it within an isolated environment. *** ## Key Differences ### 1. Reliability **More Reliable** You define the schema, the exact API, and exactly how it works. The LLM follows your specification precisely. **Variable Reliability** The agent generates tool calls as code inside the sandbox. Results depend on what Claude has been trained on. **MCP is more reliable** because you control the contract. You define the schema, arguments, and behavior. The LLM simply calls your API as specified. **Claude Code Mode** generates code dynamically. This works well for tasks Claude has seen during training, but can fail miserably for new platforms, APIs, or custom tools that Claude hasn't learned. If MCP was released in November 2024, Claude only got good at generating MCP-compatible code around late 2025 with Claude Opus 4.5. Until then, Claude-generated MCPs were unreliable unless you provided extensive documentation. If you don't want to wait a year for model updates, use MCP. *** ### 2. Token Usage One of MCP's biggest challenges has been **token usage**. Every tool call includes descriptions, arguments, and schemas — which consume tokens. This problem is largely **self-inflicted**: > Most MCPs were auto-generated from OpenAPI specs, which contain verbose documentation meant for humans — paragraphs of explanations, detailed parameter descriptions, etc. **Agents don't need this.** They have knowledge baked into their weights. You just need to provide hints about what the tool does. The schema itself is often sufficient. **Best Practice:** Keep your MCP tool descriptions concise. Agents are smart — they don't need documentation-style explanations. A brief description + clear schema is enough. Claude Code Mode avoids this overhead by generating code directly, but trades it for the reliability issues mentioned above. *** ### 3. Custom Tools & New Platforms **New companies, custom tools, proprietary APIs** If you're building something new, expose it as an MCP. Don't expect Claude Code Mode to magically know how to use your platform. **Standard libraries, common tasks, calculations** Simple math, data processing, and well-known libraries work great in Code Mode. If you're a **startup or building new tools**, MCP is the clear choice. Claude Code Mode only works well for things it was trained on. Your custom API? It will likely fail. *** ### 4. Authentication When your backend requires authentication, MCP provides a structured approach: * **Token storage** handled securely on the client * **OAuth flows** with `@leanmcp/auth` * **Session management** built into the protocol Claude Code Mode has no native auth handling. You'd need to pass credentials into the sandbox, which raises security concerns. **For authenticated APIs, use MCP.** *** ### 5. UI Consistency If you want to display UI components inside a chat interface (ChatGPT, Claude, etc.): | Approach | Result | | --------------- | --------------------------------------------------------------- | | **MCP** | Consistent, predefined UI components | | **Claude Code** | Generated HTML/JS that may look inconsistent or expose raw tags | Users prefer consistency. They don't want to see random HTML or JavaScript tags in their chat. MCP gives you control over the presentation layer. *** ## When Claude Code Mode Shines Claude Code Mode isn't bad — it has legitimate use cases: ### Simple Calculations You don't need an MCP for: * Running a calculator * Solving a differential equation * Summing up a list of bills * Basic budgeting calculations LLMs are notoriously bad at math. Running these in a sandbox makes perfect sense. Building an MCP calculator is overkill. ### Number Crunching Tasks For quantitative finance, data analysis, or repeated execution of small functions, Claude Code Mode works well: ```python theme={null} # Claude can generate and execute this directly def compound_interest(principal, rate, years): return principal * (1 + rate) ** years ``` ### Well-Known Libraries If Claude knows the library (pandas, numpy, requests to public APIs), Code Mode works fine. *** ## When MCP Wins | Use Case | Why MCP | | --------------------- | ----------------------------------- | | **Custom APIs** | Claude doesn't know your API | | **New platforms** | Not in training data | | **Authentication** | Secure token handling | | **Consistent UI** | No random HTML in chat | | **Schema validation** | Structured input/output | | **Elicitation** | Collect user input before execution | | **Production apps** | Reliability matters | *** ## You Can Use Both Here's the good news: **you don't have to choose**. You can enable MCPs for your custom tools AND allow Claude to use Code Mode for simple tasks. Let Claude decide: * Complex authenticated API call? → MCP * Quick calculation? → Code Mode * Custom platform integration? → MCP * Data transformation? → Code Mode **For SaaS developers:** Always expose your tools as MCPs. Don't rely on Claude Code Mode understanding your platform — it won't. *** ## Summary | Factor | MCP | Claude Code Mode | | ------------------- | ------------------------------- | --------------------------------- | | **Reliability** | ✅ High (you control the schema) | ⚠️ Variable (depends on training) | | **Token Usage** | ⚠️ Can be high (but fixable) | ✅ Lower | | **Custom Tools** | ✅ Required | ❌ Will likely fail | | **Authentication** | ✅ Built-in support | ❌ No native handling | | **UI Consistency** | ✅ Controlled | ❌ Random HTML/JS | | **Simple Math** | ❌ Overkill | ✅ Perfect fit | | **Known Libraries** | ❌ Unnecessary | ✅ Works well | **Bottom line:** Use MCP for anything custom, authenticated, or production-critical. Use Claude Code Mode for simple calculations and well-known libraries. Or use both and let Claude decide. *** ## Next Steps Get started with LeanMCP Protect your MCP tools # Using Prompts and Resources Efficiently Source: https://docs.leanmcp.com/guides/prompts-and-resources Best practices for leveraging prompts and resources in MCP servers MCP has three primitives: **Tools**, **Resources**, and **Prompts**. While Tools get all the attention, Resources and Prompts serve distinct purposes that are often misunderstood. *** ## The Three Primitives ```mermaid theme={null} flowchart LR subgraph Agent-Driven T[Tools] end subgraph User-Driven R[Resources] P[Prompts] end T --> LLM[LLM Provider
Anthropic / OpenAI] R --> UI[Client UI
Cursor / Windsurf] P --> UI ``` | Primitive | Control | Purpose | Client Support | | ------------- | ------------- | -------------------------- | ------------------ | | **Tools** | Agent-driven | Actions with side effects | ✅ Widely supported | | **Resources** | User-driven | Read-only data attachments | ⚠️ Limited support | | **Prompts** | User or Agent | Instructions/templates | ⚠️ Limited support | **Resources and Prompts require client-side support.** Not all MCP clients implement them. Tools are universally supported because they're handled by LLM providers (Anthropic, OpenAI). *** ## Resources: User-Controlled Data ### What Are Resources? Resources are **read-only data** that users can attach to their input. Key points: * **User decides** — not the agent * **Agent cannot access resources directly** — it must use Tools * **Tools control access** — authentication, scopes, permissions ```mermaid theme={null} flowchart TD U[User] -->|Attaches| R[Resource] R -->|Added to| I[Input Context] I --> A[Agent/LLM] A -->|Wants more data?| T[Tool] T -->|Checks auth & scopes| D[Database] ``` ### Real-World Examples The best example is the **@ command** in Cursor, Windsurf, and Android Studio: | Client | @ Command | What It Attaches | | ------------------ | --------------- | ------------------- | | **Cursor** | `@file.ts` | File contents | | **Windsurf** | `@PR #123` | Pull request diff | | **Android Studio** | `@build.gradle` | Build configuration | When you type `@file.ts`, you're adding a **resource** to your prompt. The user explicitly chooses what context to include. ### When to Use Resources ```typescript theme={null} import { Resource } from 'leanmcp'; @Resource({ description: "Current user's profile" }) async userProfile() { return { name: "John Doe", role: "Developer", preferences: { theme: "dark", language: "en" } }; } @Resource({ description: "Project configuration" }) async projectConfig() { return { name: "my-app", version: "1.0.0", dependencies: { ... } }; } ``` **Use resources for:** * Configuration files * User profiles * Project metadata * Any read-only context users might want to attach Resources are **not** for data the agent should fetch on its own. That's what Tools are for. *** ## Prompts: Instructions for Agents ### What Are Prompts? Prompts are **instructions** that tell the agent how to behave. They can be: * **User-added** — Explicitly attached by the user * **Agent-picked** — Agent selects relevant prompts when needed ```mermaid theme={null} flowchart TD subgraph User-Added U[User] -->|Selects| P1[Prompt Template] end subgraph Agent-Picked A[Agent] -->|Chooses relevant| P2[Prompt Template] end P1 --> C[Combined Instructions] P2 --> C C --> L[LLM Behavior] ``` ### Real-World Examples The best example is **Guidelines** in Cursor and Windsurf: | Client | Feature | Purpose | | ------------ | ---------------- | ---------------------------------- | | **Cursor** | `.cursorrules` | Project-specific coding guidelines | | **Windsurf** | `.windsurfrules` | Project-specific behavior | | **Both** | Guidelines panel | Agent instructions | These are **prompts** — instructions that shape how the agent generates code. ### When to Use Prompts ```typescript theme={null} import { Prompt } from 'leanmcp'; @Prompt({ description: "Code review guidelines" }) codeReviewPrompt() { return { messages: [{ role: "user", content: { type: "text", text: `You are a senior code reviewer. Follow these rules: 1. Check for security vulnerabilities 2. Ensure proper error handling 3. Verify edge cases are covered 4. Suggest performance improvements 5. Keep feedback constructive` } }] }; } @Prompt({ description: "API documentation writer" }) apiDocPrompt() { return { messages: [{ role: "user", content: { type: "text", text: `Generate API documentation with: - Clear endpoint descriptions - Request/response examples - Error codes and meanings - Authentication requirements` } }] }; } ``` **Use prompts for:** * Coding standards * Review guidelines * Documentation templates * Any reusable instructions *** ## Tools vs Resources vs Prompts ```mermaid theme={null} flowchart TD subgraph Tools T1[Agent decides when to call] T2[Can have side effects] T3[Controlled by LLM provider] T4[Auth & scopes enforced] end subgraph Resources R1[User decides when to attach] R2[Read-only data] R3[Controlled by client UI] R4[No auth needed - user explicit] end subgraph Prompts P1[User or agent selects] P2[Instructions only] P3[Controlled by client UI] P4[Shapes agent behavior] end ``` | Question | Answer | | ---------------------- | ----------------------------------------------------------------------- | | Who decides to use it? | **Tools**: Agent / **Resources**: User / **Prompts**: Both | | Can it modify data? | **Tools**: Yes / **Resources**: No / **Prompts**: No | | Needs client support? | **Tools**: No (LLM handles) / **Resources**: Yes / **Prompts**: Yes | | Purpose? | **Tools**: Actions / **Resources**: Context / **Prompts**: Instructions | *** ## Client Support Reality **The hard truth:** Most MCP clients only support Tools. | Client | Tools | Resources | Prompts | | ------------------ | ----- | ----------- | --------- | | **Claude Desktop** | ✅ | ✅ | ✅ | | **Cursor** | ✅ | ✅ (@ files) | ✅ (rules) | | **Windsurf** | ✅ | ✅ (@ files) | ✅ (rules) | | **ChatGPT** | ✅ | ❌ | ❌ | | **Custom clients** | ✅ | Varies | Varies | If your MCP needs broad client support, **focus on Tools**. Use Resources and Prompts as progressive enhancements for clients that support them. *** ## Best Practices ### For Resources 1. **Keep them read-only** — Resources should never modify state 2. **Make them user-relevant** — Only expose what users would want to attach 3. **Provide fallbacks** — If resources aren't supported, expose similar data via Tools ```typescript theme={null} // Resource for clients that support it @Resource({ description: "Project config" }) async projectConfig() { return await this.getConfig(); } // Tool fallback for clients that don't @Tool({ description: "Get project config" }) async getProjectConfig() { return await this.getConfig(); } ``` ### For Prompts 1. **Keep them focused** — One prompt per behavior/task 2. **Make them reusable** — Generic enough for multiple contexts 3. **Don't duplicate** — If it's in the prompt, don't repeat in tool descriptions ```typescript theme={null} // ❌ BAD: Overly specific @Prompt({ description: "Review React TypeScript code on Monday" }) // ✅ GOOD: Reusable @Prompt({ description: "Code review guidelines" }) ``` *** ## Summary | Primitive | Control | Best For | Client Support | | ------------- | ------- | -------------------------- | -------------- | | **Tools** | Agent | Actions, data access, auth | ✅ Universal | | **Resources** | User | Config, profiles, context | ⚠️ Limited | | **Prompts** | Both | Instructions, guidelines | ⚠️ Limited | **Key insight:** Tools are LLM-focused (agent-driven). Resources and Prompts are UI-focused (user-driven). Build for Tools first, enhance with Resources/Prompts for supporting clients. Learn about MCP tools Understanding resources # Reducing Tokens in MCPs Source: https://docs.leanmcp.com/guides/reducing-tokens Best practices for minimizing token usage in your MCP servers ## How MCP Tool Definitions Work When you add a tool to your MCP, here's what happens: ```mermaid theme={null} flowchart LR T[Your Tools] -->|Descriptions + Schemas| LLM[LLM] LLM -->|Decision| TC{Call tool?} TC -->|Yes| E[Execute] TC -->|No| R[Respond] ``` Every tool you define sends its **description** and **input schema** to the LLM as part of the prompt. The LLM reads these definitions to decide whether to call a tool and which one. **The problem:** Every tool you add increases your input token count. This has two costs: 1. **Context space** — LLMs have finite context windows. More tokens in tool definitions = fewer tokens for conversation history and responses. 2. **Money** — Input tokens cost money. Every request pays for all your tool definitions, whether they're used or not. An MCP with 50 tools and verbose descriptions can easily consume **2,000-5,000 tokens per request** — before the user even says anything. This guide covers practical strategies to minimize token usage and keep your MCPs lean. *** ## The Problem: APIs vs MCPs | REST APIs | MCPs | | -------------------------------------- | --------------------------------- | | Consumed by **developers** | Consumed by **AI agents** | | Need detailed documentation | Need minimal hints | | Stateless, hundreds of endpoints | Focused, purpose-built tools | | Verbose descriptions prevent confusion | Verbose descriptions waste tokens | Most MCPs are auto-generated from OpenAPI specs with paragraphs of explanations designed for humans. **Agents don't need this.** They have knowledge in their weights. A brief description + clear schema is enough. Auto-generated MCPs from OpenAPI specs are typically **pathetic for agents**. Don't just wrap your API — optimize it for AI. *** ## Strategy 1: Expose Only What You Need Your API might have hundreds of endpoints, but your MCP shouldn't. ```typescript theme={null} // BAD: 50+ tools for every endpoint // GOOD: Only what agents actually use @Tool({ description: "Search products" }) async searchProducts(input: { query: string }) { ... } @Tool({ description: "Add to cart" }) async addToCart(input: { productId: string }) { ... } ``` *** ## Strategy 2: Optimize Output Size APIs return paginated results with hundreds of items. MCPs should return **minimal useful responses**. ```typescript theme={null} // BAD: Returns 100 results async search(query: string) { return await api.search(query, { pageSize: 100 }); } // GOOD: Top 5 relevant results only async search(query: string) { const results = await api.search(query); const sorted = rankByRelevance(results, query); return sorted.slice(0, 5).map(r => ({ id: r.id, title: r.title, snippet: r.snippet.slice(0, 200) })); } ``` **Use cursors for large datasets** — never return unbounded queries. The [Exa AI MCP](https://github.com/exa-labs/exa-mcp-server) uses NLP to extract only relevant chunks instead of full HTML pages. *** ## Strategy 3: Design for Minimal Tool Calls Every tool call is a round trip: AI generates → server responds → AI processes → repeat. Chain of calls = chain of tokens. **BAD: Flight booking with 6 tool calls** ```mermaid theme={null} flowchart LR A[searchRoutes] --> B[getFlights] B --> C[checkAvailability] C --> D[getSeatMap] D --> E[calculatePrice] E --> F[bookFlight] ``` Each step: \~200-500 tokens for request + response. **6 calls = 2,000+ tokens** just for the conversation flow. **GOOD: One tool that handles the workflow** ```typescript theme={null} class BookFlightInput { @SchemaConstraint({ description: "Departure airport code (e.g., SFO)" }) from!: string; @SchemaConstraint({ description: "Arrival airport code (e.g., SIN)" }) to!: string; @SchemaConstraint({ description: "Departure date (YYYY-MM-DD)" }) date!: string; @Optional() @SchemaConstraint({ description: "Preferred class", enum: ["economy", "business", "first"] }) class?: string; @Optional() @SchemaConstraint({ description: "Max budget in USD" }) maxPrice?: number; } @Tool({ description: "Search and book flights. Returns top options with prices.", inputClass: BookFlightInput }) async bookFlight(input: BookFlightInput) { // Server handles: route lookup → availability → pricing → filtering const flights = await this.searchFlights(input); return { flights: flights.slice(0, 5).map(f => ({ airline: f.airline, departure: f.departure, arrival: f.arrival, price: f.price, duration: f.duration })), message: "Reply with flight number to book" }; } ``` **Result:** 1 call instead of 6. \~300 tokens instead of 2,000+. **Rule of thumb:** If your workflow requires the AI to call tools in sequence, combine them into one tool. Let your server handle the orchestration. *** ## Strategy 4: Keep Descriptions Concise ```typescript theme={null} // BAD: Documentation-style @Tool({ description: "This tool allows you to search for products in our catalog. You can search by name, category, or SKU..." }) // GOOD: Minimal @Tool({ description: "Search products by name" }) ``` *** ## Strategy 5: Use Token Caching MCP tool definitions are sent with every request. Cache them! | Provider | Cost Reduction | | --------- | -------------- | | Anthropic | \~90% cheaper | | OpenAI | \~50% cheaper | | Fireworks | \~90% cheaper | Once cached, definitions cost **1/10th to 1/100th** of normal tokens. *** ## Strategy 6: Don't MCP Everything Use sandbox/code execution for: * Simple calculations (no calculator MCP!) * File conversions (PNG to JPG) * Math operations * Data transformations MCPs are for: authentication, custom APIs, schema validation, consistent UI. *** ## Summary | Action | Token Savings | | ---------------------------- | ------------- | | Remove unused tools | Proportional | | Limit response size | \~90%+ | | Combine multi-step workflows | \~80-90% | | Shorten descriptions | \~50-80% | | Enable caching | \~90% | | Use sandbox for math | 100% | # Mitigating React Server Component CVE Source: https://docs.leanmcp.com/guides/security-rsc-cve Security deep dive - protecting MCP apps from React Server Component vulnerabilities On December 3, 2025, Meta disclosed [CVE-2025-55182](https://www.cve.org/CVERecord?id=CVE-2025-55182) — a critical unauthenticated remote code execution vulnerability in React Server Components, commonly known as **React2Shell**. It's rated CVSS 10.0, the highest severity possible. An attacker can craft malicious HTTP requests to any Server Function endpoint that, when deserialized by React, achieves remote code execution on the server. Within hours of disclosure, China-nexus threat groups including Earth Lamia and Jackpot Panda were actively exploiting this vulnerability in the wild. CISA added it to their Known Exploited Vulnerabilities list on December 5, 2025. *** ## Are MCPs Affected? **Most MCPs are not affected.** Here's why: The React2Shell vulnerability targets React Server Components (RSC) — a frontend technology. Traditional MCP servers are backend services that expose tools, resources, and prompts over the MCP protocol. They don't run React, don't use RSC, and don't have the vulnerable packages installed. If your MCP is a standard tool server built with LeanMCP, the official MCP SDK, or any backend-only framework — you're safe. No action required. *** ## When MCPs Are Affected MCPs become vulnerable when they include a **frontend component** that uses React Server Components. This specifically applies to: * **MCP-UI implementations** — if you're rendering interactive UI components using Next.js App Router * **MCP Apps** — the new extension for interactive user interfaces in MCP, especially if built with Next.js * **ChatGPT Apps built with Next.js** — as described in the [Vercel blog on running Next.js inside ChatGPT](https://vercel.com/blog/running-next-js-inside-chatgpt) If your MCP uses Next.js 15.x, 16.x, or Next.js 14.3.0-canary.77+ with React Server Components — you need to patch immediately. ### Affected Packages The vulnerability exists in these React packages: * `react-server-dom-webpack` * `react-server-dom-parcel` * `react-server-dom-turbopack` And these frameworks that depend on them: * **Next.js** (15.x, 16.x, 14.3.0-canary.77+) * **React Router** (unstable RSC APIs) * **Waku** * **Expo** (RSC features) * **@vitejs/plugin-rsc** *** ## How to Check If You're Vulnerable Run an audit on your project: ```bash theme={null} npm audit ``` Or check your dependencies directly: ```bash theme={null} npm ls react-server-dom-webpack react-server-dom-parcel react-server-dom-turbopack ``` If you see any of these packages in versions 19.0, 19.1.0, 19.1.1, or 19.2.0 — you're vulnerable. For Next.js projects, Vercel provides an interactive fix tool: ```bash theme={null} npx fix-react2shell-next ``` This checks your versions and performs the necessary upgrades automatically. *** ## How to Patch ### Next.js Upgrade to the patched version for your release line: ```bash theme={null} npm install next@15.0.5 # for 15.0.x npm install next@15.1.9 # for 15.1.x npm install next@15.2.6 # for 15.2.x npm install next@15.3.6 # for 15.3.x npm install next@15.4.8 # for 15.4.x npm install next@15.5.7 # for 15.5.x npm install next@16.0.7 # for 16.0.x ``` If you're on Next.js 14.3.0-canary.77 or later canary releases, downgrade to stable 14.x: ```bash theme={null} npm install next@14 ``` ### React Router (unstable RSC APIs) ```bash theme={null} npm install react@latest react-dom@latest react-server-dom-parcel@latest react-server-dom-webpack@latest @vitejs/plugin-rsc@latest ``` ### Other Frameworks ```bash theme={null} # Waku npm install react@latest react-dom@latest react-server-dom-webpack@latest waku@latest # Vite RSC plugin npm install react@latest react-dom@latest @vitejs/plugin-rsc@latest # Direct RSC packages npm install react@latest react-dom@latest react-server-dom-webpack@latest ``` ### After Patching Rotate any environment variables or secrets that may have been exposed if you were running a vulnerable version in production. *** ## Summary | MCP Type | Affected? | Action Required | | ---------------------------------------- | --------- | ----------------- | | Standard tool servers (LeanMCP, MCP SDK) | No | None | | Backend-only MCPs | No | None | | MCP-UI with Next.js App Router | Yes | Patch immediately | | MCP Apps with Next.js | Yes | Patch immediately | | ChatGPT Apps with Next.js | Yes | Patch immediately | There is no workaround for this vulnerability. Upgrading to a patched version is the only fix. ## References * [CVE-2025-55182 (React)](https://www.cve.org/CVERecord?id=CVE-2025-55182) * [CVE-2025-66478 (Next.js)](https://nextjs.org/blog/CVE-2025-66478) * [React Security Advisory](https://react.dev/blog/2025/12/03/critical-security-vulnerability-in-react-server-components) * [Vercel: Running Next.js inside ChatGPT](https://vercel.com/blog/running-next-js-inside-chatgpt) Building interactive UIs Authentication best practices # Where to Use MCPs Source: https://docs.leanmcp.com/guides/where-to-use-mcps Understanding when and where to use MCP servers effectively MCPs aren't just for SaaS developers. They're a flexible foundation for building AI-powered applications across different contexts. Here's where MCPs truly shine. *** ## 1. SaaS Developers: Expose Your Platform to AI If you have an existing SaaS with APIs and a database, MCPs let you **expose your platform to AI agents** without building everything from scratch. ### The Problem Your competitors are building AI agents. You could: * Build your own agent from scratch (expensive, time-consuming) * Let users export data to other tools (lose control, security risks) * Do nothing (fall behind) ### The MCP Solution Build an MCP that wraps your existing APIs. Now: * **Your data stays yours** — no exports needed * **Users get AI agent support** — through your MCP * **You control access** — auth, scopes, permissions built-in ```mermaid theme={null} flowchart LR U[User] --> A[AI Agent] A -->|Tool calls| M[Your MCP] M -->|Auth + Scopes| D[Your Database] M --> A A --> U ``` ### Building an Agent with MCPs The agent pattern is simple — it's just a loop: ```mermaid theme={null} flowchart TD U[User Prompt] --> A[Agent / LLM] A -->|Decision: Need data?| TC{Tool Call?} TC -->|Yes| M[MCP Tool] M -->|Response| A TC -->|No| R[Final Response] R --> U ``` You can build this with OpenAI or Anthropic in **10-20 minutes**: ```typescript theme={null} import Anthropic from '@anthropic-ai/sdk'; import { MCPClient } from '@leanmcp/client'; const anthropic = new Anthropic(); const mcp = new MCPClient('http://your-mcp-server.com'); // Get tools from your MCP const tools = await mcp.listTools(); async function runAgent(userMessage: string) { const messages = [{ role: 'user', content: userMessage }]; // Agent loop while (true) { const response = await anthropic.messages.create({ model: 'claude-sonnet-4-5-20250929', max_tokens: 1024, tools: tools, // Your MCP tools messages }); // Check for tool calls const toolCalls = response.content.filter(c => c.type === 'tool_use'); if (toolCalls.length === 0) { // No more tool calls - return final response return response.content.find(c => c.type === 'text')?.text; } // Execute tool calls via MCP for (const call of toolCalls) { const result = await mcp.callTool(call.name, call.input); messages.push({ role: 'tool', tool_use_id: call.id, content: JSON.stringify(result) }); } } } ``` ### Why MCP Over Custom Tool Calls? | Custom Tool Calls | MCP | | ------------------------ | --------------------------- | | Auth per tool | Auth built into protocol | | Scope management manual | Scopes via `@leanmcp/auth` | | Users locked to your app | Users can use MCP elsewhere | | Rebuild for each LLM | Works with any LLM | **Key advantage:** If users want to use their data in other tools (Cursor, Claude Desktop, custom apps), they can connect your MCP directly. No data export needed. *** ## 2. AI Agent Startups: Build MVPs Fast If you're building an AI agent startup, MCPs are the **fastest path to an MVP**. ### The Traditional Approach 1. Build tool call handlers 2. Wire up OpenAI/Anthropic 3. Build your agent loop 4. Create test infrastructure 5. Deploy and iterate ### The MCP Approach 1. Build an MCP with your tools, APIs, resources 2. Add prompts for different behaviors (A/B testing) 3. Test in Claude Desktop immediately 4. Deploy when ready ```mermaid theme={null} flowchart TD subgraph Build T[Tools] --> M[MCP Server] R[Resources] --> M P[Prompts A/B/C/D] --> M end subgraph Test M --> CD[Claude Desktop] M --> CU[Cursor] M --> WS[Windsurf] end subgraph Deploy M --> PROD[Production Agent] end ``` ### A/B Testing Prompts Add multiple prompts to your MCP for testing different behaviors: ```typescript theme={null} @Prompt({ description: "Prompt A - Concise responses" }) promptA() { return { messages: [{ role: "user", content: { type: "text", text: "Be concise. One sentence answers." } }] }; } @Prompt({ description: "Prompt B - Detailed explanations" }) promptB() { return { messages: [{ role: "user", content: { type: "text", text: "Provide detailed explanations with examples." } }] }; } @Prompt({ description: "Prompt C - Step by step" }) promptC() { return { messages: [{ role: "user", content: { type: "text", text: "Break down responses into numbered steps." } }] }; } ``` Test each prompt in Claude Desktop and see which performs best — **no code changes needed**. ### Why MCP for MVPs? | Benefit | How | | --------------------- | ---------------------------------- | | **Fast iteration** | Change prompts without redeploying | | **Test anywhere** | Claude Desktop, Cursor, Windsurf | | **Production-ready** | Same MCP works in production | | **No vendor lock-in** | Switch LLMs easily | *** ## 3. Enterprise: Internal Tooling & Agents For large enterprises with internal agents, MCPs provide the **security, access control, and auditability** you need. ### The Enterprise Challenge * Different teams need different data access * SSO integration required * Scope management per user/team * Audit trail for compliance * Works with enterprise LLM providers ### MCP + Enterprise Auth ```mermaid theme={null} flowchart TD U[Employee] -->|SSO Login| IDP[Internal IdP
Okta / Azure AD] IDP -->|JWT Token| A[AI Agent] A -->|Token in _meta| M[MCP Server] M -->|Validate| IDP M -->|Check Scopes| SC[Scope Rules] SC -->|Finance team?| FD[Finance Data] SC -->|Engineering team?| ED[Engineering Data] SC -->|HR team?| HD[HR Data] ``` ### Implementation ```typescript theme={null} import { AuthProvider, Authenticated } from "@leanmcp/auth"; // Connect to your internal SSO const authProvider = new AuthProvider('custom', { jwksUri: 'https://your-sso.company.com/.well-known/jwks.json', issuer: 'https://your-sso.company.com', audience: 'internal-mcp' }); await authProvider.init(); @Authenticated(authProvider) export class InternalDataService { @Tool({ description: "Get team data" }) async getTeamData(input: { dataType: string }) { // authUser contains SSO claims including groups/scopes const userTeam = authUser['groups']?.[0]; const allowedScopes = authUser['scopes'] || []; // Check if user has access to requested data if (!allowedScopes.includes(`read:${input.dataType}`)) { return { error: "Access denied", requiredScope: `read:${input.dataType}` }; } // Fetch data based on team membership return await internalDb.getData({ team: userTeam, type: input.dataType, requestedBy: authUser.sub // Audit trail }); } } ``` ### Works with Enterprise LLM Providers | Provider | Integration | | ------------------------ | ------------------------------ | | **OpenAI Enterprise** | Same MCP, enterprise API keys | | **Anthropic Enterprise** | Same MCP, enterprise agreement | | **AWS Bedrock** | Same MCP, Claude on AWS | | **Azure OpenAI** | Same MCP, Azure endpoints | **Key benefit:** You don't rebuild your agent for each LLM provider. The MCP stays the same — only the LLM connection changes. *** ## Summary: When to Use MCPs | Use Case | Why MCP | | ----------------------- | ------------------------------------------------------- | | **SaaS Developer** | Expose platform to AI, keep data control, auth built-in | | **AI Agent Startup** | Fast MVPs, test in existing tools, no vendor lock-in | | **Enterprise Internal** | SSO integration, scope management, audit trails | ```mermaid theme={null} flowchart LR subgraph Your MCP T[Tools] R[Resources] P[Prompts] A[Auth] end subgraph Use Cases S[SaaS Platform] M[MVP Testing] E[Enterprise Agent] end subgraph Clients CD[Claude Desktop] CU[Cursor] WS[Windsurf] CA[Custom Agent] end Your MCP --> Use Cases Use Cases --> Clients ``` **Bottom line:** If you're building anything that connects AI to data or actions, MCPs give you auth, scopes, flexibility, and portability — all built into the protocol. Secure your MCP Get started now # Introduction Source: https://docs.leanmcp.com/index LeanMCP - The fastest way to build and deploy MCP Servers # Welcome to LeanMCP **LeanMCP is the fastest way to build, deploy, and maintain production-ready, enterprise-grade MCP servers.** It comes with an SDK so you can build MCP servers fast, and a deployment platform so you can ship them globally without getting stuck on infra and protocol gotchas. Most teams spend weeks debugging these issues. LeanMCP solves them out of the box. *** ## What are you here for? **Observability & AI Gateway** Already have an account? Start here. Connect your AI clients (Cursor, Windsurf, Claude, etc.) to LeanMCP and get unified logs, usage tracking, and cost visibility. **Build & Deploy** Use the LeanMCP SDK to build and ship a production-grade MCP Server. Get your first tool call running in 5 minutes. *** ## Production-Grade MCP Infrastructure Deploy MCP servers that are **scalable at the edge** for all your users across all regions. We support every MCP protocol and work seamlessly with all major MCP clients. We handle the hard parts: * **Auto-scaling** - Automatically scales based on demand, no configuration needed * **Fault-tolerant** - Automatic failover, your MCPs stay online even when things go wrong * **No CPU hogging** - Long-running tools run in isolation, never blocking other requests * **Low latency** - Edge deployment across 30+ regions means fast responses globally * **Multi-client support** - Works with Claude, Cursor, Windsurf, and any MCP-compatible client ## Full MCP Protocol Support Unlike partial implementations, LeanMCP supports the **complete MCP specification**: * **Authentication** - Built-in auth flows for secure tool access * **Elicitation** - Interactive prompts for user input during tool execution * **MCP Apps** - Full application lifecycle support * **All transports** - HTTP, SSE, and WebSocket support out of the box * **Sampling** - Let your MCP request LLM completions from the client ## How It Works LeanMCP provides two main components: 1. **Open Source SDK** - Build your MCP servers with TypeScript decorators 2. **Managed Platform** - Deploy directly to our edge infrastructure ```typescript theme={null} import { MCPServer, createHTTPServer, Tool, Service } from "@leanmcp/core"; @Service() class WeatherService { @Tool("Get weather for a city") async getWeather(city: string) { return { temperature: 72, condition: "sunny" }; } } const server = new MCPServer({ name: "my-mcp", version: "1.0.0" }); await createHTTPServer(() => server.getServer(), { port: 3001 }); ``` ## Documentation Roadmap If you’re new here, follow the docs in this order: * **Getting started** (CLI + SDK) * Start with the [Quickstart](/quickstart) * Install and use the CLI: [CLI Installation](/cli/installation) * Then jump into the SDK overview: [SDK Introduction](/building/introduction) * **Ready to deploy** * Read the deployment overview: [Deployment Introduction](/deploy/introduction) * Deploy on the managed platform: [LeanMCP Platform](/deploy/leanmcp-platform) * Or deploy on your own infra: [Cloud Providers](/deploy/cloud-providers) * **Auth + additional features** * Authentication and billing: [Auth & Payment](/guides/auth-and-payment) * UI + apps in chat: [Prompts](/core-concepts/prompts) * Where to use MCPs (clients + platforms): [Where to use MCPs](/guides/where-to-use-mcps) * **Debugging** * Test and debug your server: [Debugging MCP Servers](/debugging) *** ## Get Started with the CLI The fastest way to start is with our CLI: ```bash theme={null} # Install the CLI npm install -g @leanmcp/cli # Login to your account leanmcp login # Create a new project leanmcp init my-mcp-server # Deploy to production leanmcp deploy ``` Set up the LeanMCP CLI Build your first MCP in 5 minutes Join our community! Get help, share your projects, and connect with other builders on [Discord](https://discord.com/invite/DsRcA3GwPy). ## Learn More Build MCPs with TypeScript decorators Tools, Resources, and Prompts ## Deployment Learn about deployment options Deploy to our managed edge infrastructure Deploy as serverless functions AWS, GCP, Azure deployment guides ## Best Practices Optimize token usage in your MCPs Convert existing APIs to MCPs Implement authentication and billing Security best practices ## Advanced Features Secure your MCPs with built-in auth flows Build interactive UIs in chat interfaces Deploy MCPs as ChatGPT plugins Deploy anywhere - Vercel, AWS, GCP, Azure ## API Reference HTTP API for programmatic access If you need enterprise features like **SLA**, **support**, **SSO**, or **custom auth integrations**, email us at [founders@leanmcp.com](mailto:founders@leanmcp.com). # Documentation Plan Source: https://docs.leanmcp.com/plan LeanMCP Documentation Structure and Goals # LeanMCP Documentation Plan ## Main Goal Transform the documentation to focus entirely on **LeanMCP** - the fastest way to build and deploy MCP Servers. ## Content Strategy ### Key Message * **LeanMCP** = Fast MCP building and deployment * **MCPs** = UI for AI Agents (every tool call is like a click) * **Simple words** - No jargon or complex explanations ### Core Topics to Cover 1. **What is LeanMCP** - The fastest way to build MCPs 2. **Why MCPs Matter** - They're the UI for AI agents 3. **Building Good MCPs** - Art of minimizing wrong tool calls 4. **Prototyping Workflows** - Test AI workflows quickly 5. **From Prototype to Production** - Easy deployment ### Content to Remove * All Mintlify generic content * Documentation setup instructions * Branding/customization guides * API reference examples not related to LeanMCP ## Page Structure Plan ### Introduction Page (index.mdx) * What is LeanMCP * Why MCPs are important * How LeanMCP helps you build better MCPs ### Core Documentation Pages * **Getting Started** - Building your first MCP * **MCP Best Practices** - Art of good MCP design * **Testing & Prototyping** - How to test AI workflows * **Deployment** - From prototype to production * **API Reference** - LeanMCP specific APIs ## Writing Guidelines * **Simple words only** - No technical jargon * **Short sentences** - Easy to understand * **Clear examples** - Show, don't just tell * **Focus on LeanMCP** - Everything must relate to our product ## Implementation Approach 1. Create plan (this document) ✅ 2. Update introduction page with new content 3. Review all existing pages 4. Remove unrelated content 5. Add LeanMCP-focused content to each page 6. Ensure consistency across all pages ## Status * **Planning Phase** - Documenting the approach * **Next Step** - Update introduction page * **Waiting for** - User approval to implement changes # Quick Start Source: https://docs.leanmcp.com/quickstart Build your first MCP server in 5 minutes # Quick Start In this guide, you'll install the LeanMCP CLI, create a new project, and build a real image generation service using Gemini's Nano Banana API — complete with tools, resources, and schema validation that AI agents can discover and use. ## Prerequisites * Node.js >= 18 * npm >= 9 ## Step 1: Install the CLI ```bash theme={null} npm i -g @leanmcp/cli ``` Verify the installation: ```bash theme={null} leanmcp --version ``` ## Step 2: Create Your Project ```bash theme={null} leanmcp create my-mcp-server ``` The CLI will ask: * **Install dependencies?** → Yes * **Start dev server?** → Yes Project structure: ``` my-mcp-server/ ├── main.ts # Entry point ├── package.json ├── tsconfig.json └── mcp/ └── example/ └── index.ts # Example service ``` ## Step 3: Server is Running After creation, the server starts automatically: ``` Server running on http://localhost:3001 MCP endpoint: http://localhost:3001/mcp Health check: http://localhost:3001/health ``` To restart later: ```bash theme={null} cd my-mcp-server npm run dev ``` ## Step 4: Test Your MCP Use the MCP Inspector: ```bash theme={null} npx @modelcontextprotocol/inspector http://localhost:3001/mcp ``` Or test with curl: ```bash theme={null} curl http://localhost:3001/mcp \ -X POST \ -H "Content-Type: application/json" \ -H "Accept: application/json, text/event-stream" \ -d '{ "jsonrpc": "2.0", "id": 1, "method": "initialize", "params": { "protocolVersion": "2024-11-05", "capabilities": {}, "clientInfo": { "name": "test", "version": "1.0.0" } } }' ``` ## Step 5: Add Gemini Image Generation Let's build a real example: image generation with Gemini (Nano Banana). Create `mcp/gemini/index.ts`: ```typescript theme={null} import { Tool, Resource, SchemaConstraint, Optional } from "@leanmcp/core"; import fs from "fs"; import path from "path"; // --- Input Schema --- class GenerateImageInput { @SchemaConstraint({ description: "Text description of the image to generate", minLength: 1 }) prompt!: string; @Optional() @SchemaConstraint({ description: "Model: nano-banana (fast) or nano-banana-pro (advanced)", enum: ["nano-banana", "nano-banana-pro"], default: "nano-banana" }) model?: "nano-banana" | "nano-banana-pro"; @Optional() @SchemaConstraint({ description: "Aspect ratio", enum: ["1:1", "16:9", "9:16", "4:3"], default: "1:1" }) aspectRatio?: string; } // --- Service --- export class GeminiImageService { private apiKey = process.env.GEMINI_API_KEY || ""; private baseUrl = "https://generativelanguage.googleapis.com/v1beta/models"; private outputDir = path.join(process.cwd(), "generated-images"); private modelMap = { "nano-banana": "gemini-2.5-flash-image", "nano-banana-pro": "gemini-3-pro-image-preview" }; constructor() { if (!fs.existsSync(this.outputDir)) { fs.mkdirSync(this.outputDir, { recursive: true }); } } @Tool({ description: "Generate an image from text using Gemini (Nano Banana)", inputClass: GenerateImageInput }) async generateImage(input: GenerateImageInput) { const modelKey = input.model || "nano-banana"; const modelName = this.modelMap[modelKey]; const response = await fetch( `${this.baseUrl}/${modelName}:generateContent?key=${this.apiKey}`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ contents: [{ parts: [{ text: input.prompt }] }], generationConfig: { responseModalities: ["IMAGE"], imageConfig: { aspectRatio: input.aspectRatio || "1:1" } } }) } ); const result = await response.json(); const imageData = result.candidates?.[0]?.content?.parts?.[0]?.inlineData?.data; // Save to disk const filename = `gemini_${Date.now()}.png`; const filepath = path.join(this.outputDir, filename); fs.writeFileSync(filepath, Buffer.from(imageData, "base64")); return { success: true, savedTo: filepath, filename }; } @Resource({ description: "Available Gemini models", mimeType: "application/json" }) getModels() { return { contents: [{ uri: "gemini://models", mimeType: "application/json", text: JSON.stringify({ "nano-banana": "Fast, 1K resolution", "nano-banana-pro": "Advanced, up to 4K" }) }] }; } } ``` Add your API key to `.env`: ```bash theme={null} GEMINI_API_KEY=your-key-here ``` Get a free Gemini API key at [aistudio.google.com/apikey](https://aistudio.google.com/apikey) ## Step 6: Build for Production ```bash theme={null} npm run build npm run start ``` Or set a custom port: ```bash theme={null} PORT=4000 npm run start ``` ## What You Built You now have a working MCP server with: * **Tool**: `generateImage` - Generate images from text prompts * **Resource**: `getModels` - Lists available Gemini models * **Auto-discovery** - Services in `mcp/` are automatically registered ## Next Steps Create more tools for AI to execute Expose data to AI agents Template prompts for AI Secure your MCP server Deploy to production All CLI commands # Roadmap Source: https://docs.leanmcp.com/roadmap What we are building next for LeanMCP # LeanMCP Roadmap * Authentication and RBAC * Build and deploy optimizations * Custom domains *** **Stay Updated**: Follow us on [X/Twitter](https://x.com/leanmcp) for the latest updates and announcements. # @leanmcp/auth Source: https://docs.leanmcp.com/sdk/auth Token-based authentication decorators and multi-provider support for MCP tools # @leanmcp/auth Authentication module for LeanMCP providing token-based authentication decorators and multi-provider support for protecting MCP tools, prompts, and resources. ## Features Protect tools, prompts, and resources with a simple decorator AWS Cognito, Clerk, Auth0, and LeanMCP providers Decoded user info injected as global `authUser` variable Uses AsyncLocalStorage for request-isolated context ## Installation ```bash theme={null} npm install @leanmcp/auth @leanmcp/core ``` ### Provider Dependencies ```bash theme={null} npm install @aws-sdk/client-cognito-identity-provider axios jsonwebtoken jwk-to-pem ``` ```bash theme={null} npm install axios jsonwebtoken jwk-to-pem ``` ```bash theme={null} npm install axios jsonwebtoken jwk-to-pem ``` ## Quick Start ### 1. Initialize Auth Provider ```typescript theme={null} import { AuthProvider } from "@leanmcp/auth"; const authProvider = new AuthProvider('cognito', { region: 'us-east-1', userPoolId: 'us-east-1_XXXXXXXXX', clientId: 'your-client-id' }); await authProvider.init(); ``` ### 2. Protect Methods with @Authenticated ```typescript theme={null} import { Tool } from "@leanmcp/core"; import { Authenticated } from "@leanmcp/auth"; export class SentimentService { @Tool({ description: 'Analyze sentiment (requires auth)' }) @Authenticated(authProvider) async analyzeSentiment(input: { text: string }) { // authUser is automatically available with user info console.log('User ID:', authUser.sub); console.log('Email:', authUser.email); return { sentiment: 'positive', score: 0.8, analyzedBy: authUser.sub }; } // Public method - no authentication @Tool({ description: 'Get categories (public)' }) async getCategories() { return { categories: ['positive', 'negative', 'neutral'] }; } } ``` ### 3. Protect Entire Service ```typescript theme={null} // All methods in this class require authentication @Authenticated(authProvider) export class SecureService { @Tool({ description: 'Protected tool' }) async protectedTool(input: { data: string }) { // authUser is available in all methods return { data: input.data, userId: authUser.sub }; } } ``` ## The authUser Variable When using `@Authenticated`, a global `authUser` variable is automatically injected containing the decoded JWT payload: ```typescript theme={null} @Tool({ description: 'Create post' }) @Authenticated(authProvider) async createPost(input: { title: string, content: string }) { // authUser is automatically available return { id: generateId(), title: input.title, content: input.content, authorId: authUser.sub, authorEmail: authUser.email }; } ``` ### Provider-Specific User Data ```typescript theme={null} { sub: 'user-uuid', email: 'user@example.com', email_verified: true, 'cognito:username': 'username', 'cognito:groups': ['admin', 'users'] } ``` ```typescript theme={null} { sub: 'user_2abc123xyz', userId: 'user_2abc123xyz', email: 'user@example.com', firstName: 'John', lastName: 'Doe', imageUrl: 'https://img.clerk.com/...' } ``` ```typescript theme={null} { sub: 'auth0|507f1f77bcf86cd799439011', email: 'user@example.com', email_verified: true, name: 'John Doe', picture: 'https://s.gravatar.com/avatar/...' } ``` ### Controlling User Fetch ```typescript theme={null} // Fetch user info (default) @Authenticated(authProvider, { getUser: true }) async withUserInfo(input: any) { console.log(authUser); // User data available } // Only verify token, skip user fetch (faster) @Authenticated(authProvider, { getUser: false }) async tokenOnlyValidation(input: any) { // authUser is undefined } ``` ## Supported Providers ### AWS Cognito ```typescript theme={null} const authProvider = new AuthProvider('cognito', { region: 'us-east-1', userPoolId: 'us-east-1_XXXXXXXXX', clientId: 'your-client-id' }); await authProvider.init(); ``` **Environment Variables:** ```bash theme={null} AWS_REGION=us-east-1 COGNITO_USER_POOL_ID=us-east-1_XXXXXXXXX COGNITO_CLIENT_ID=your-client-id ``` ### Clerk ```typescript theme={null} // Session Mode (default) const authProvider = new AuthProvider('clerk', { frontendApi: 'your-frontend-api.clerk.accounts.dev', secretKey: 'sk_test_...' }); // OAuth Mode (with refresh tokens) const authProvider = new AuthProvider('clerk', { frontendApi: 'your-frontend-api.clerk.accounts.dev', secretKey: 'sk_test_...', clientId: 'your-oauth-client-id', clientSecret: 'your-oauth-client-secret', redirectUri: 'https://yourapp.com/callback' }); await authProvider.init(); ``` ### Auth0 ```typescript theme={null} const authProvider = new AuthProvider('auth0', { domain: 'your-tenant.auth0.com', clientId: 'your-client-id', clientSecret: 'your-client-secret', audience: 'https://your-api-identifier' }); await authProvider.init(); ``` ### LeanMCP For LeanMCP platform deployments with user secrets support: ```typescript theme={null} const authProvider = new AuthProvider('leanmcp', { apiKey: 'your-leanmcp-api-key' }); await authProvider.init(); ``` ## Client Usage Authentication tokens are passed via the `_meta` field following MCP protocol standards: ```typescript theme={null} await mcpClient.callTool({ name: "analyzeSentiment", arguments: { text: "Hello world" }, _meta: { authorization: { type: "bearer", token: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." } } }); ``` ## Error Handling ```typescript theme={null} import { AuthenticationError } from "@leanmcp/auth"; try { await service.protectedMethod({ text: "test" }); } catch (error) { if (error instanceof AuthenticationError) { switch (error.code) { case 'MISSING_TOKEN': console.log('No token provided'); break; case 'INVALID_TOKEN': console.log('Token is invalid or expired'); break; case 'VERIFICATION_FAILED': console.log('Verification failed:', error.message); break; } } } ``` ## API Reference ### AuthProvider ```typescript theme={null} class AuthProvider { constructor(provider: string, config: any); async init(config?: any): Promise; async verifyToken(token: string): Promise; async refreshToken(refreshToken: string): Promise; async getUser(token: string): Promise; getProviderType(): string; } ``` ### @Authenticated Decorator ```typescript theme={null} function Authenticated( authProvider: AuthProvider, options?: AuthenticatedOptions ): ClassDecorator | MethodDecorator; interface AuthenticatedOptions { getUser?: boolean; // Default: true projectId?: string; // For LeanMCP user secrets } ``` ### AuthenticationError ```typescript theme={null} class AuthenticationError extends Error { code: 'MISSING_TOKEN' | 'INVALID_TOKEN' | 'VERIFICATION_FAILED'; constructor(message: string, code: string); } ``` ### Helper Functions ```typescript theme={null} // Check if authentication is required function isAuthenticationRequired(target: any): boolean; // Get auth provider for method/class function getAuthProvider(target: any): AuthProviderBase | undefined; // Get current authenticated user function getAuthUser(): any; ``` ## Best Practices * Always use HTTPS in production * Store tokens securely (keychain, encrypted storage) * Implement token refresh before expiration * Add rate limiting to protect against brute force * Use environment variables for credentials * Never hardcode secrets in code * Use `_meta` for auth, not business arguments * Use `getUser: false` when you only need token validation * JWKS keys are cached automatically for performance ## OAuth 2.1 Support Beyond server-side token verification, `@leanmcp/auth` provides complete OAuth 2.1 infrastructure: Browser-based OAuth flows with PKCE, token storage, and automatic refresh Build authorization servers with external provider proxy support ### Submodule Imports ```typescript theme={null} // Server-side token verification (this page) import { AuthProvider, Authenticated } from '@leanmcp/auth'; // OAuth client for browser-based flows import { OAuthClient } from '@leanmcp/auth/client'; // Token storage backends import { MemoryStorage, FileStorage, KeychainStorage } from '@leanmcp/auth/storage'; // OAuth proxy for external providers import { OAuthProxy, googleProvider, githubProvider } from '@leanmcp/auth/proxy'; // OAuth authorization server import { OAuthAuthorizationServer } from '@leanmcp/auth/server'; ``` ## Related Packages * [@leanmcp/core](/sdk/core) - Core decorators and server functionality * [@leanmcp/env-injection](/sdk/env-injection) - Environment variable injection for user secrets * [OAuth Client](/sdk/auth-oauth-client) - Client-side OAuth with PKCE and token storage * [OAuth Server & Proxy](/sdk/auth-oauth-server) - Authorization servers with external provider support ## Links * [GitHub Repository](https://github.com/LeanMCP/leanmcp-sdk) * [NPM Package](https://www.npmjs.com/package/@leanmcp/auth) # OAuth Client Source: https://docs.leanmcp.com/sdk/auth-oauth-client Browser-based OAuth 2.1 client with PKCE, token storage, and automatic refresh # OAuth Client The `@leanmcp/auth/client` module provides a complete OAuth 2.1 client implementation for MCP applications. It handles browser-based authentication flows with PKCE, secure token storage, and automatic token refresh. ## Features Secure authorization code flow with Proof Key for Code Exchange Pluggable storage backends: memory, file, or OS keychain Automatic token refresh before expiration RFC 7591 Dynamic Client Registration support ## Installation ```bash theme={null} npm install @leanmcp/auth ``` For file-based storage with encryption: ```bash theme={null} npm install @leanmcp/auth ``` For OS keychain storage: ```bash theme={null} npm install @leanmcp/auth keytar ``` ## Quick Start ```typescript theme={null} import { OAuthClient } from '@leanmcp/auth/client'; import { FileStorage } from '@leanmcp/auth/storage'; // Create client with file-based token storage const client = new OAuthClient({ serverUrl: 'https://api.example.com', authorizationEndpoint: 'https://api.example.com/oauth/authorize', tokenEndpoint: 'https://api.example.com/oauth/token', clientId: 'my-app', scopes: ['openid', 'profile'], storage: new FileStorage({ filePath: '~/.myapp/tokens.json', prettyPrint: true, }), pkceEnabled: true, autoRefresh: true, }); // Authenticate (opens browser) const tokens = await client.authenticate(); console.log('Authenticated!', tokens.access_token); // Get valid token (auto-refreshes if needed) const token = await client.getValidToken(); // Use token in API calls const response = await fetch('https://api.example.com/data', { headers: { Authorization: `Bearer ${token}` }, }); ``` *** ## OAuthClient The main client class for OAuth 2.1 flows. ### Constructor Options ```typescript theme={null} interface OAuthClientOptions { /** Base URL of the OAuth server */ serverUrl: string; /** Authorization endpoint URL */ authorizationEndpoint: string; /** Token endpoint URL */ tokenEndpoint: string; /** Token storage backend */ storage: TokenStorage; /** Client ID (required if not using dynamic registration) */ clientId?: string; /** Client secret (for confidential clients) */ clientSecret?: string; /** OAuth scopes to request */ scopes?: string[]; /** Enable PKCE (default: true) */ pkceEnabled?: boolean; /** Automatically refresh tokens before expiry (default: false) */ autoRefresh?: boolean; /** Callback URL for browser flow (default: http://localhost:PORT/callback) */ redirectUri?: string; /** Port for local callback server (default: random available port) */ callbackPort?: number; } ``` ### Methods #### authenticate() Initiates the OAuth flow by opening a browser window for user authentication. ```typescript theme={null} const tokens = await client.authenticate(); // Returns: { access_token, token_type, expires_in, refresh_token?, scope? } ``` **Flow:** 1. Generates PKCE code verifier and challenge (if enabled) 2. Opens browser to authorization endpoint 3. Starts local HTTP server to receive callback 4. Exchanges authorization code for tokens 5. Stores tokens in configured storage #### getValidToken() Returns a valid access token, refreshing if necessary. ```typescript theme={null} const token = await client.getValidToken(); // Returns: string (access token) ``` If the current token is expired and a refresh token is available, it will automatically refresh. Throws if no valid token is available. #### getTokens() Returns the current stored tokens without refreshing. ```typescript theme={null} const tokens = await client.getTokens(); // Returns: TokenSet | null ``` #### logout() Clears stored tokens. ```typescript theme={null} await client.logout(); ``` *** ## Token Storage The `@leanmcp/auth/storage` module provides pluggable storage backends for tokens. ### MemoryStorage Stores tokens in memory. Tokens are lost when the process exits. ```typescript theme={null} import { MemoryStorage } from '@leanmcp/auth/storage'; const storage = new MemoryStorage(); const client = new OAuthClient({ // ... storage, }); ``` **Use cases:** * Development and testing * Short-lived CLI commands * Serverless functions (tokens passed externally) ### FileStorage Stores tokens in a JSON file with optional encryption. ```typescript theme={null} import { FileStorage } from '@leanmcp/auth/storage'; const storage = new FileStorage({ /** Path to token file (supports ~ for home directory) */ filePath: '~/.myapp/tokens.json', /** Encryption key (optional, enables AES-256-GCM encryption) */ encryptionKey?: string, /** Pretty-print JSON (default: false) */ prettyPrint?: boolean, }); ``` **Example with encryption:** ```typescript theme={null} const storage = new FileStorage({ filePath: '~/.myapp/tokens.json', encryptionKey: process.env.TOKEN_ENCRYPTION_KEY, }); ``` If using encryption, store the encryption key securely (e.g., environment variable). Losing the key means losing access to stored tokens. ### KeychainStorage Stores tokens in the OS secure keychain (macOS Keychain, Windows Credential Manager, Linux Secret Service). ```typescript theme={null} import { KeychainStorage } from '@leanmcp/auth/storage'; const storage = new KeychainStorage({ /** Service name in keychain */ service: 'my-app', /** Account name in keychain */ account: 'oauth-tokens', }); ``` Requires the `keytar` package: `npm install keytar` **Use cases:** * Desktop CLI applications * Developer tools * Any application where OS-level security is preferred ### Custom Storage Implement the `TokenStorage` interface for custom backends: ```typescript theme={null} import type { TokenStorage, TokenSet } from '@leanmcp/auth/storage'; class RedisStorage implements TokenStorage { constructor(private redis: RedisClient, private key: string) {} async get(): Promise { const data = await this.redis.get(this.key); return data ? JSON.parse(data) : null; } async set(tokens: TokenSet): Promise { await this.redis.set(this.key, JSON.stringify(tokens)); } async clear(): Promise { await this.redis.del(this.key); } } ``` *** ## PKCE Flow PKCE (Proof Key for Code Exchange) is enabled by default and required by the MCP OAuth specification. The client automatically: 1. Generates a cryptographically random `code_verifier` 2. Creates the `code_challenge` using SHA-256 3. Sends the challenge with the authorization request 4. Sends the verifier with the token exchange ```typescript theme={null} // PKCE is enabled by default const client = new OAuthClient({ serverUrl: 'https://api.example.com', // ... pkceEnabled: true, // default }); ``` *** ## Token Refresh ### Automatic Refresh When `autoRefresh` is enabled, `getValidToken()` automatically refreshes expired tokens: ```typescript theme={null} const client = new OAuthClient({ // ... autoRefresh: true, }); // Always returns a valid token const token = await client.getValidToken(); ``` ### Manual Refresh You can also manually refresh tokens: ```typescript theme={null} const newTokens = await client.refreshTokens(); ``` *** ## Complete Example Here's a complete CLI application that authenticates with an OAuth server: ```typescript theme={null} import { OAuthClient } from '@leanmcp/auth/client'; import { FileStorage } from '@leanmcp/auth/storage'; const SERVER_URL = 'https://api.example.com'; async function main() { const storage = new FileStorage({ filePath: '~/.myapp/tokens.json', prettyPrint: true, }); const client = new OAuthClient({ serverUrl: SERVER_URL, authorizationEndpoint: `${SERVER_URL}/oauth/authorize`, tokenEndpoint: `${SERVER_URL}/oauth/token`, storage, clientId: 'my-cli-app', scopes: ['openid', 'profile', 'read:data'], pkceEnabled: true, autoRefresh: true, }); // Check for existing tokens const existing = await client.getTokens(); if (existing?.access_token) { console.log('Using existing session'); } else { console.log('Opening browser for authentication...'); await client.authenticate(); console.log('Authenticated!'); } // Make authenticated API call const token = await client.getValidToken(); const response = await fetch(`${SERVER_URL}/api/user`, { headers: { Authorization: `Bearer ${token}` }, }); const user = await response.json(); console.log('User:', user); } main().catch(console.error); ``` *** ## API Reference ### TokenSet ```typescript theme={null} interface TokenSet { access_token: string; token_type: string; expires_in?: number; refresh_token?: string; scope?: string; expires_at?: number; // Unix timestamp } ``` ### TokenStorage Interface ```typescript theme={null} interface TokenStorage { get(): Promise; set(tokens: TokenSet): Promise; clear(): Promise; } ``` *** ## Related * [Authentication Overview](/sdk/auth) - Server-side authentication with `@Authenticated` decorator * [OAuth Server](/sdk/auth-oauth-server) - Build OAuth authorization servers with proxy support * [GPT Apps Authentication](/sdk/ui-gpt-apps#authentication) - Client-side auth in ChatGPT Apps # OAuth Server & Proxy Source: https://docs.leanmcp.com/sdk/auth-oauth-server Build MCP-compliant OAuth authorization servers with external provider proxy support # OAuth Server & Proxy The `@leanmcp/auth/proxy` and `@leanmcp/auth/server` modules enable you to build OAuth 2.1 authorization servers for your MCP applications. Use them to proxy authentication to external identity providers (Google, GitHub, etc.) while issuing your own tokens. ## Features Authenticate users via Google, GitHub, Azure, and more Standard OAuth authorization server metadata Dynamic Client Registration for MCP clients Enforces PKCE per MCP security requirements ## Installation ```bash theme={null} npm install @leanmcp/auth express ``` *** ## Architecture Overview ```mermaid theme={null} sequenceDiagram participant Client as MCP Client (Claude) participant Server as Your MCP Server (OAuth Proxy) participant IdP as External IdP (Google/GitHub) Client->>Server: 1. Auth Request Server->>IdP: 2. Redirect to IdP IdP-->>Server: 3. IdP Token Server-->>Client: 4. Your Token ``` The OAuth Proxy: 1. Receives authorization requests from MCP clients 2. Redirects users to the external identity provider 3. Exchanges the IdP's code for tokens 4. Maps external tokens/user info to your internal tokens 5. Returns your tokens to the MCP client *** ## OAuth Proxy The `OAuthProxy` class handles the complete OAuth flow with external providers. ### Basic Setup ```typescript theme={null} import express from 'express'; import { OAuthProxy, googleProvider, githubProvider } from '@leanmcp/auth/proxy'; const app = express(); app.use(express.json()); app.use(express.urlencoded({ extended: true })); // Configure providers const providers = [ googleProvider({ clientId: process.env.GOOGLE_CLIENT_ID!, clientSecret: process.env.GOOGLE_CLIENT_SECRET!, }), githubProvider({ clientId: process.env.GITHUB_CLIENT_ID!, clientSecret: process.env.GITHUB_CLIENT_SECRET!, }), ]; // Create proxy const proxy = new OAuthProxy({ baseUrl: 'https://api.example.com/auth', sessionSecret: process.env.SESSION_SECRET!, providers, tokenMapper: async (externalTokens, userInfo, provider) => { // Map external tokens to your internal tokens return { access_token: generateMyToken(userInfo), token_type: 'Bearer', expires_in: 3600, }; }, }); // Mount routes const middleware = proxy.createMiddleware(); app.get('/auth/authorize', middleware.authorize); app.get('/auth/callback', middleware.callback); app.post('/auth/token', middleware.token); app.listen(3000); ``` ### Configuration ```typescript theme={null} interface OAuthProxyConfig { /** Base URL for OAuth endpoints (e.g., https://api.example.com/auth) */ baseUrl: string; /** Secret for signing session/state tokens */ sessionSecret: string; /** Array of configured OAuth providers */ providers: OAuthProviderConfig[]; /** Function to map external tokens to your tokens */ tokenMapper: TokenMapperFunction; /** Token TTL in seconds (default: 3600) */ tokenTTL?: number; } ``` ### Token Mapper The `tokenMapper` function is called after successful external authentication. Use it to create your internal tokens: ```typescript theme={null} const proxy = new OAuthProxy({ // ... tokenMapper: async (externalTokens, userInfo, provider) => { // externalTokens: { access_token, refresh_token, ... } from provider // userInfo: { sub, email, name, ... } from provider's userinfo endpoint // provider: { id, name, ... } the provider that authenticated // Look up or create user in your database let user = await db.users.findByEmail(userInfo.email); if (!user) { user = await db.users.create({ email: userInfo.email, name: userInfo.name, provider: provider.id, }); } // Generate your own token const token = jwt.sign( { sub: user.id, email: user.email }, process.env.JWT_SECRET!, { expiresIn: '1h' } ); return { access_token: token, token_type: 'Bearer', expires_in: 3600, user_id: user.id, }; }, }); ``` *** ## Pre-configured Providers Import ready-to-use provider configurations: ### Google ```typescript theme={null} import { googleProvider } from '@leanmcp/auth/proxy'; googleProvider({ clientId: process.env.GOOGLE_CLIENT_ID!, clientSecret: process.env.GOOGLE_CLIENT_SECRET!, scopes: ['openid', 'profile', 'email'], // optional, these are defaults }); ``` ### GitHub ```typescript theme={null} import { githubProvider } from '@leanmcp/auth/proxy'; githubProvider({ clientId: process.env.GITHUB_CLIENT_ID!, clientSecret: process.env.GITHUB_CLIENT_SECRET!, scopes: ['read:user', 'user:email'], // optional }); ``` ### Azure AD ```typescript theme={null} import { azureProvider } from '@leanmcp/auth/proxy'; azureProvider({ clientId: process.env.AZURE_CLIENT_ID!, clientSecret: process.env.AZURE_CLIENT_SECRET!, tenant: process.env.AZURE_TENANT_ID!, // or 'common' for multi-tenant }); ``` ### GitLab ```typescript theme={null} import { gitlabProvider } from '@leanmcp/auth/proxy'; gitlabProvider({ clientId: process.env.GITLAB_CLIENT_ID!, clientSecret: process.env.GITLAB_CLIENT_SECRET!, baseUrl: 'https://gitlab.com', // or self-hosted URL }); ``` ### Slack ```typescript theme={null} import { slackProvider } from '@leanmcp/auth/proxy'; slackProvider({ clientId: process.env.SLACK_CLIENT_ID!, clientSecret: process.env.SLACK_CLIENT_SECRET!, }); ``` ### Discord ```typescript theme={null} import { discordProvider } from '@leanmcp/auth/proxy'; discordProvider({ clientId: process.env.DISCORD_CLIENT_ID!, clientSecret: process.env.DISCORD_CLIENT_SECRET!, }); ``` *** ## Custom Providers Use `customProvider` for any OAuth 2.0 compatible identity provider: ```typescript theme={null} import { customProvider } from '@leanmcp/auth/proxy'; const myProvider = customProvider({ id: 'my-idp', name: 'My Identity Provider', authorizationEndpoint: 'https://idp.example.com/oauth/authorize', tokenEndpoint: 'https://idp.example.com/oauth/token', userInfoEndpoint: 'https://idp.example.com/oauth/userinfo', clientId: process.env.MY_IDP_CLIENT_ID!, clientSecret: process.env.MY_IDP_CLIENT_SECRET!, scopes: ['openid', 'profile', 'email'], supportsPkce: true, }); ``` ### Provider Configuration ```typescript theme={null} interface OAuthProviderConfig { /** Unique identifier for this provider */ id: string; /** Display name */ name: string; /** OAuth authorization endpoint */ authorizationEndpoint: string; /** OAuth token endpoint */ tokenEndpoint: string; /** OpenID Connect userinfo endpoint (optional) */ userInfoEndpoint?: string; /** Client ID */ clientId: string; /** Client secret */ clientSecret: string; /** Scopes to request */ scopes?: string[]; /** Whether provider supports PKCE */ supportsPkce?: boolean; } ``` *** ## OAuth Authorization Server For full MCP OAuth compliance, use `OAuthAuthorizationServer` which adds RFC 8414 metadata and RFC 7591 Dynamic Client Registration: ```typescript theme={null} import express from 'express'; import { OAuthAuthorizationServer } from '@leanmcp/auth/server'; import { googleProvider } from '@leanmcp/auth/proxy'; const app = express(); const authServer = new OAuthAuthorizationServer({ issuer: 'https://api.example.com', // Upstream provider for authentication upstreamProvider: googleProvider({ clientId: process.env.GOOGLE_CLIENT_ID!, clientSecret: process.env.GOOGLE_CLIENT_SECRET!, }), // JWT signing secret signingSecret: process.env.JWT_SECRET!, // Enable Dynamic Client Registration enableDCR: true, // Token TTL in seconds tokenTTL: 3600, // Optional: Custom token mapper tokenMapper: async (upstreamTokens, userInfo) => { return { sub: userInfo.sub, email: userInfo.email, name: userInfo.name, }; }, }); // Mount OAuth routes app.use(authServer.createRouter()); // Serves: // GET /.well-known/oauth-authorization-server (RFC 8414 metadata) // POST /oauth/register (RFC 7591 DCR) // GET /oauth/authorize (Authorization endpoint) // GET /oauth/callback (Provider callback) // POST /oauth/token (Token endpoint) app.listen(3000); ``` ### Server Metadata (RFC 8414) The server automatically exposes OAuth metadata at `/.well-known/oauth-authorization-server`: ```json theme={null} { "issuer": "https://api.example.com", "authorization_endpoint": "https://api.example.com/oauth/authorize", "token_endpoint": "https://api.example.com/oauth/token", "registration_endpoint": "https://api.example.com/oauth/register", "scopes_supported": ["openid", "profile", "email"], "response_types_supported": ["code"], "grant_types_supported": ["authorization_code"], "code_challenge_methods_supported": ["S256"] } ``` ### Dynamic Client Registration (RFC 7591) MCP clients can register dynamically: ```bash theme={null} curl -X POST https://api.example.com/oauth/register \ -H "Content-Type: application/json" \ -d '{ "client_name": "My MCP Client", "redirect_uris": ["http://localhost:3001/callback"] }' ``` Response: ```json theme={null} { "client_id": "abc123", "client_secret": "secret456", "client_name": "My MCP Client", "redirect_uris": ["http://localhost:3001/callback"] } ``` *** ## MCP Auth Error Responses Use `createAuthError` from `@leanmcp/core` to return MCP-compliant authentication errors that trigger ChatGPT's OAuth linking UI: ```typescript theme={null} import { Tool, createAuthError } from '@leanmcp/core'; export class MyService { @Tool({ description: 'Get private data' }) async getPrivateData(args: any, meta?: any) { const token = meta?.authorization?.token; if (!token) { return createAuthError('Authentication required', { resourceMetadataUrl: `${process.env.PUBLIC_URL}/.well-known/oauth-protected-resource`, error: 'invalid_token', errorDescription: 'No access token provided', }); } // ... proceed with authenticated request } } ``` The `createAuthError` function returns a response with `_meta["mcp/www_authenticate"]` that signals to MCP clients (including ChatGPT) to initiate OAuth authentication. *** ## Complete Example Here's a complete OAuth proxy server: ```typescript theme={null} import 'dotenv/config'; import express from 'express'; import { OAuthProxy, googleProvider, githubProvider } from '@leanmcp/auth/proxy'; import jwt from 'jsonwebtoken'; const app = express(); app.use(express.json()); app.use(express.urlencoded({ extended: true })); const PORT = 3000; const BASE_URL = `http://localhost:${PORT}`; // In-memory user database (use a real database in production) const users = new Map(); const proxy = new OAuthProxy({ baseUrl: `${BASE_URL}/auth`, sessionSecret: process.env.SESSION_SECRET!, providers: [ googleProvider({ clientId: process.env.GOOGLE_CLIENT_ID!, clientSecret: process.env.GOOGLE_CLIENT_SECRET!, }), githubProvider({ clientId: process.env.GITHUB_CLIENT_ID!, clientSecret: process.env.GITHUB_CLIENT_SECRET!, }), ], tokenMapper: async (externalTokens, userInfo, provider) => { // Find or create user const key = `${provider.id}:${userInfo.email}`; let user = users.get(key); if (!user) { user = { id: crypto.randomUUID(), email: userInfo.email, name: userInfo.name, provider: provider.id, createdAt: new Date(), }; users.set(key, user); console.log('Created user:', user.email); } // Generate JWT const token = jwt.sign( { sub: user.id, email: user.email, name: user.name }, process.env.JWT_SECRET!, { expiresIn: '1h' } ); return { access_token: token, token_type: 'Bearer', expires_in: 3600, }; }, }); // OAuth routes const middleware = proxy.createMiddleware(); app.get('/auth/authorize', middleware.authorize); app.get('/auth/callback', middleware.callback); app.post('/auth/token', middleware.token); // List available providers app.get('/auth/providers', (req, res) => { res.json({ providers: [ { id: 'google', name: 'Google' }, { id: 'github', name: 'GitHub' }, ], }); }); // Protected API endpoint app.get('/api/me', (req, res) => { const auth = req.headers.authorization; if (!auth?.startsWith('Bearer ')) { return res.status(401).json({ error: 'Unauthorized' }); } try { const payload = jwt.verify(auth.slice(7), process.env.JWT_SECRET!); res.json({ user: payload }); } catch { res.status(401).json({ error: 'Invalid token' }); } }); app.listen(PORT, () => { console.log(`OAuth server running at ${BASE_URL}`); }); ``` *** ## Related * [OAuth Client](/sdk/auth-oauth-client) - Client-side OAuth with PKCE and token storage * [Authentication Overview](/sdk/auth) - Server-side authentication with `@Authenticated` decorator * [Auth & Payment Guide](/guides/auth-and-payment) - Complete authentication integration guide # @leanmcp/cli Source: https://docs.leanmcp.com/sdk/cli Command-line tool for creating, developing, and deploying LeanMCP projects # @leanmcp/cli Command-line tool for creating, developing, and deploying MCP servers to LeanMCP Cloud. ## Features Create production-ready MCP servers in seconds `leanmcp dev` with UI component hot-reload Deploy to LeanMCP Cloud with custom subdomains List, view, and delete cloud projects Manage Lambda environment variables from CLI ## Installation ```bash theme={null} npm install -g @leanmcp/cli ``` Or run without installing: ```bash theme={null} npx @leanmcp/cli create my-mcp-server ``` ## Commands Overview ```bash theme={null} leanmcp create # Create a new project leanmcp add # Add a service to existing project leanmcp dev # Start development server with hot-reload leanmcp build # Build for production leanmcp start # Start production server # Cloud commands leanmcp login # Authenticate with LeanMCP Cloud leanmcp logout # Remove API key leanmcp whoami # Show login status leanmcp deploy # Deploy to LeanMCP Cloud leanmcp projects list # List your cloud projects leanmcp projects get # Get project details leanmcp projects delete # Delete a project # Environment variables leanmcp env list # List environment variables leanmcp env set KEY=val # Set environment variable leanmcp env get KEY # Get environment variable leanmcp env remove KEY # Remove environment variable leanmcp env pull # Pull to .env file leanmcp env push # Push from .env file ``` *** ## Local Development ### create Create a new MCP server project: ```bash theme={null} leanmcp create my-sentiment-tool ``` Interactive prompts will guide you through: 1. Creating the project structure 2. Installing dependencies (optional) 3. Starting the dev server (optional) **Generated structure:** ``` my-mcp-server/ ├── main.ts # Entry point with HTTP server ├── package.json # Dependencies and scripts ├── tsconfig.json # TypeScript configuration └── mcp/ # Services directory └── example.ts # Example service with tools ``` ### add Add a new service to an existing project: ```bash theme={null} cd my-mcp-server leanmcp add weather ``` This: * Creates `mcp/weather.ts` with example Tool, Prompt, and Resource * Automatically registers the service in `main.ts` * Includes `@SchemaConstraint` validation examples ### dev Start the development server with hot-reload: ```bash theme={null} leanmcp dev ``` This command: * Scans for `@UIApp` components and builds them * Starts the HTTP server with `tsx watch` * Watches `mcp/` directory for changes * Automatically rebuilds UI components when modified * Hot-reloads when adding/removing `@UIApp` decorators ```bash theme={null} $ leanmcp dev LeanMCP Development Server ℹ Found 2 @UIApp component(s) ℹ UI components built Starting development server... [HTTP][INFO] Server running on http://localhost:3001 [HTTP][INFO] MCP endpoint: http://localhost:3001/mcp ``` ### build Build the project for production: ```bash theme={null} leanmcp build ``` Compiles TypeScript and bundles UI components. ### start Start the production server: ```bash theme={null} leanmcp start ``` Runs the compiled production build. *** ## Cloud Commands ### login Authenticate with LeanMCP Cloud: ```bash theme={null} leanmcp login ``` Steps: 1. Go to [leanmcp.com/api-keys](https://leanmcp.com/api-keys) 2. Create an API key with "BUILD\_AND\_DEPLOY" scope 3. Enter the key when prompted ```bash theme={null} $ leanmcp login LeanMCP Login To authenticate, you need an API key from LeanMCP. Steps: 1. Go to: https://leanmcp.com/api-keys 2. Create a new API key with "BUILD_AND_DEPLOY" scope 3. Copy the API key and paste it below ? Enter your API key: airtrain_xxxxx... ✔ API key validated and saved Login successful! Config saved to: ~/.leanmcp/config.json ``` ### logout Remove your API key: ```bash theme={null} leanmcp logout ``` ### whoami Check your current login status: ```bash theme={null} leanmcp whoami ``` ### deploy Deploy your MCP server to LeanMCP Cloud: ```bash theme={null} leanmcp deploy . # Or specify a folder leanmcp deploy ./my-project ``` Deployment process: 1. Creates project (or updates existing) 2. Packages and uploads code 3. Builds container image 4. Deploys to serverless Lambda 5. Configures custom subdomain ```bash theme={null} $ leanmcp deploy . LeanMCP Deploy Generated project name: swift-coral-sunset Path: /path/to/my-project ? Subdomain for your deployment: my-api ✔ Subdomain 'my-api' is available Deployment Details: Project: swift-coral-sunset Subdomain: my-api URL: https://my-api.leanmcp.dev ? Proceed with deployment? Yes ✔ Project created: 7f4a3b2c... ✔ Project uploaded ✔ Build complete (45s) ✔ Deployed ✔ Subdomain configured ============================================================ DEPLOYMENT SUCCESSFUL! ============================================================ Your MCP server is now live: URL: https://my-api.leanmcp.dev Test endpoints: curl https://my-api.leanmcp.dev/health curl https://my-api.leanmcp.dev/mcp ``` ### projects Manage your cloud projects: ```bash theme={null} # List all projects leanmcp projects list # Get project details leanmcp projects get # Delete a project leanmcp projects delete leanmcp projects delete --force # Skip confirmation ``` ### env Manage environment variables on your deployed Lambda functions: ```bash theme={null} # List environment variables leanmcp env list leanmcp env list --reveal # Show actual values # Set variables leanmcp env set API_KEY=sk-123 DEBUG=true # Get a specific variable leanmcp env get API_KEY --reveal # Remove a variable leanmcp env remove OLD_KEY # Pull to local .env file leanmcp env pull # Push from local .env file leanmcp env push leanmcp env push --replace # Replace all variables ``` Changes to environment variables are applied immediately and will take effect after a cold start. See the [Environment Variables](/cli/env-vars) guide for detailed documentation. *** ## NPM Scripts Generated projects include: ```bash theme={null} npm run dev # Start with hot reload (tsx watch) npm run build # Build for production npm run start # Run production build npm run clean # Remove build artifacts ``` ## Testing Your Server ```bash theme={null} # List available tools curl http://localhost:3001/mcp \ -X POST \ -H "Content-Type: application/json" \ -d '{ "jsonrpc": "2.0", "id": 1, "method": "tools/list" }' # Call a tool curl http://localhost:3001/mcp \ -X POST \ -H "Content-Type: application/json" \ -d '{ "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "calculate", "arguments": { "a": 10, "b": 5, "operation": "add" } } }' ``` ## Configuration ### Port ```bash theme={null} PORT=4000 npm run dev # Or in .env file PORT=4000 ``` ### LeanMCP Config Stored in `~/.leanmcp/config.json`: ```json theme={null} { "apiKey": "airtrain_...", "apiUrl": "https://api.leanmcp.com", "lastUpdated": "2024-01-15T10:30:00.000Z" } ``` ## Troubleshooting Change the port in `.env`: ```bash theme={null} PORT=3002 ``` Ensure dependencies are installed: ```bash theme={null} npm install ``` Ensure your `tsconfig.json` has: ```json theme={null} { "compilerOptions": { "experimentalDecorators": true, "emitDecoratorMetadata": true } } ``` Run `leanmcp login` first to authenticate with your API key. Choose a different subdomain when prompted. ## Requirements * Node.js >= 18.0.0 * npm >= 9.0.0 ## Related Packages * [@leanmcp/core](/sdk/core) - Core MCP server functionality * [@leanmcp/auth](/sdk/auth) - Authentication decorators * [@leanmcp/ui](/sdk/ui) - MCP App UI components ## Links * [GitHub Repository](https://github.com/LeanMCP/leanmcp-sdk) * [NPM Package](https://www.npmjs.com/package/@leanmcp/cli) * [LeanMCP Dashboard](https://leanmcp.com) # @leanmcp/core Source: https://docs.leanmcp.com/sdk/core Core library for building MCP servers with TypeScript decorators # @leanmcp/core Core library for building Model Context Protocol (MCP) servers with TypeScript decorators and declarative schema definition. ## Features `@Tool`, `@Prompt`, `@Resource` with full TypeScript support Zero-config service discovery from `./mcp` directory Declarative JSON Schema with `@SchemaConstraint` decorators Production-ready HTTP server with session management ## Installation ```bash theme={null} npm install @leanmcp/core ``` For HTTP server support: ```bash theme={null} npm install express cors ``` ## Quick Start ### Zero-Config (Recommended) The simplest way to create an MCP server with auto-discovery: ```typescript theme={null} import { createHTTPServer } from "@leanmcp/core"; await createHTTPServer({ name: "my-mcp-server", version: "1.0.0", port: 3001, cors: true, logging: true }); // Services are automatically discovered from ./mcp directory ``` **Directory Structure:** ``` your-project/ ├── main.ts └── mcp/ ├── sentiment/ │ └── index.ts # export class SentimentService ├── weather/ │ └── index.ts # export class WeatherService └── config.ts # Optional: shared dependencies ``` ### Define a Service ```typescript theme={null} // mcp/sentiment/index.ts import { Tool, SchemaConstraint, Optional } from "@leanmcp/core"; class AnalyzeSentimentInput { @SchemaConstraint({ description: 'Text to analyze', minLength: 1 }) text!: string; @Optional() @SchemaConstraint({ description: 'Language code', enum: ['en', 'es', 'fr'], default: 'en' }) language?: string; } export class SentimentService { @Tool({ description: 'Analyze sentiment of text', inputClass: AnalyzeSentimentInput }) async analyzeSentiment(input: AnalyzeSentimentInput) { return { sentiment: 'positive', score: 0.8 }; } } ``` *** ## Decorators ### @Tool Marks a method as a callable MCP tool. ```typescript theme={null} class CalculateInput { @SchemaConstraint({ description: 'First number' }) a!: number; @SchemaConstraint({ description: 'Second number' }) b!: number; } @Tool({ description: 'Calculate sum of two numbers', inputClass: CalculateInput }) async calculate(input: CalculateInput) { return { result: input.a + input.b }; } ``` **Options:** | Option | Type | Description | | ------------- | -------- | --------------------------- | | `description` | `string` | Tool description for the AI | | `inputClass` | `Class` | Class defining input schema | ### @Prompt Marks a method as a reusable prompt template. ```typescript theme={null} class CodeReviewInput { @SchemaConstraint({ description: 'Code to review' }) code!: string; @SchemaConstraint({ description: 'Programming language' }) language!: string; } @Prompt({ description: 'Generate code review prompt' }) codeReview(input: CodeReviewInput) { return { messages: [{ role: "user", content: { type: "text", text: `Review this ${input.language} code:\n\n${input.code}` } }] }; } ``` ### @Resource Marks a method as an MCP resource (data source). ```typescript theme={null} @Resource({ description: 'Get system configuration', mimeType: 'application/json' }) async getConfig() { return { version: "1.0.0", environment: process.env.NODE_ENV }; } ``` ### @SchemaConstraint Add validation constraints to class properties. ```typescript theme={null} class UserInput { @SchemaConstraint({ description: 'User email', format: 'email', minLength: 5, maxLength: 100 }) email!: string; @SchemaConstraint({ description: 'User age', minimum: 18, maximum: 120 }) age!: number; @Optional() @SchemaConstraint({ description: 'User role', enum: ['admin', 'user', 'guest'], default: 'user' }) role?: string; } ``` **Common constraints:** * `description`, `default` - Documentation * `minLength`, `maxLength` - String length * `minimum`, `maximum` - Number range * `enum` - Allowed values * `format` - String format (`email`, `uri`, `date`, etc.) * `pattern` - Regex pattern ### @Optional Marks a property as optional in the schema. ```typescript theme={null} class SearchInput { @SchemaConstraint({ description: 'Search query' }) query!: string; @Optional() @SchemaConstraint({ description: 'Max results', default: 10 }) limit?: number; } ``` *** ## API Reference ### createHTTPServer Create and start an HTTP server with auto-discovery. **Simplified API (Recommended):** ```typescript theme={null} await createHTTPServer({ name: string; // Server name (required) version: string; // Server version (required) port?: number; // Port (default: 3001) cors?: boolean | object; // Enable CORS (default: false) logging?: boolean; // Enable logging (default: false) debug?: boolean; // Verbose debug logs (default: false) autoDiscover?: boolean; // Auto-discover services (default: true) mcpDir?: string; // Custom mcp directory path sessionTimeout?: number; // Session timeout in ms stateless?: boolean; // Stateless mode for Lambda/serverless (default: true) dashboard?: boolean; // Serve dashboard UI at / (default: true) }); ``` **Factory Pattern (Advanced):** ```typescript theme={null} const serverFactory = async () => { const server = new MCPServer({ name: "my-server", version: "1.0.0", autoDiscover: false // Disable for manual registration }); server.registerService(new MyService()); return server.getServer(); }; await createHTTPServer(serverFactory, { port: 3001, cors: true }); ``` ### MCPServer Main server class for registering services. ```typescript theme={null} const server = new MCPServer({ name: string; // Server name version: string; // Server version logging?: boolean; // Enable logging (default: false) debug?: boolean; // Verbose debug logs (default: false) autoDiscover?: boolean; // Auto-discover from ./mcp (default: true) mcpDir?: string; // Custom mcp directory path }); server.registerService(instance); // Manual registration server.getServer(); // Get underlying MCP SDK server ``` *** ## Auto-Discovery Services are automatically discovered from the `./mcp` directory: 1. Recursively scans for `index.ts` or `index.js` files 2. Dynamically imports each file 3. Looks for exported classes 4. Instantiates with no-args constructors 5. Registers all decorated methods ### Shared Dependencies For services needing shared configuration (auth, database, etc.), create a `config.ts`: ```typescript theme={null} // mcp/config.ts import { AuthProvider } from "@leanmcp/auth"; export const authProvider = new AuthProvider('cognito', { region: process.env.AWS_REGION, userPoolId: process.env.COGNITO_USER_POOL_ID, clientId: process.env.COGNITO_CLIENT_ID }); await authProvider.init(); ``` Then import in your services: ```typescript theme={null} // mcp/slack/index.ts import { Tool } from "@leanmcp/core"; import { Authenticated } from "@leanmcp/auth"; import { authProvider } from "../config.js"; @Authenticated(authProvider) export class SlackService { @Tool({ description: 'Send a message' }) async sendMessage(args: { channel: string; message: string }) { // Implementation } } ``` *** ## HTTP Endpoints | Endpoint | Method | Description | | --------- | ------ | ------------------------------------ | | `/mcp` | POST | MCP protocol endpoint (JSON-RPC 2.0) | | `/health` | GET | Health check | | `/` | GET | Welcome message | ## Error Handling Errors are automatically caught and returned in MCP format: ```typescript theme={null} @Tool({ description: 'Divide numbers', inputClass: DivideInput }) async divide(input: DivideInput) { if (input.b === 0) { throw new Error("Division by zero"); } return { result: input.a / input.b }; } ``` Returns: ```json theme={null} { "content": [{"type": "text", "text": "Error: Division by zero"}], "isError": true } ``` ## Environment Variables ```bash theme={null} PORT=3001 # Server port NODE_ENV=production # Environment ``` ## TypeScript Support **Key Points:** * Input schema is defined via `inputClass` in the decorator * Output type is inferred from the return type * For tools with no input, omit `inputClass` * Use `@SchemaConstraint` for validation and documentation ```typescript theme={null} class MyInput { @SchemaConstraint({ description: 'Input field' }) field!: string; } @Tool({ description: 'My tool', inputClass: MyInput }) async myTool(input: MyInput): Promise<{ result: string }> { return { result: input.field.toUpperCase() }; } ``` ## Related Packages * [@leanmcp/cli](/sdk/cli) - CLI tool for project creation * [@leanmcp/auth](/sdk/auth) - Authentication decorators * [@leanmcp/ui](/sdk/ui) - MCP App UI components * [@leanmcp/elicitation](/sdk/elicitation) - Structured user input ## Links * [GitHub Repository](https://github.com/LeanMCP/leanmcp-sdk) * [NPM Package](https://www.npmjs.com/package/@leanmcp/core) * [MCP Specification](https://modelcontextprotocol.io/specification/2025-11-25) # @leanmcp/elicitation Source: https://docs.leanmcp.com/sdk/elicitation Structured user input collection using MCP elicitation protocol # @leanmcp/elicitation Structured user input collection for LeanMCP tools using the MCP elicitation protocol. The `@Elicitation` decorator automatically intercepts tool calls to request missing required parameters from users. ## Features Automatically collect missing user inputs before tool execution Programmatic form creation with `ElicitationFormBuilder` Form and multi-step elicitation strategies min/max, pattern matching, custom validators ## Installation ```bash theme={null} npm install @leanmcp/elicitation @leanmcp/core ``` ## Quick Start ### Simple Form Elicitation ```typescript theme={null} import { Tool } from "@leanmcp/core"; import { Elicitation } from "@leanmcp/elicitation"; class SlackService { @Tool({ description: "Create a new Slack channel" }) @Elicitation({ title: "Create Channel", description: "Please provide channel details", fields: [ { name: "channelName", label: "Channel Name", type: "text", required: true, validation: { pattern: "^[a-z0-9-]+$", errorMessage: "Must be lowercase alphanumeric with hyphens" } }, { name: "isPrivate", label: "Private Channel", type: "boolean", defaultValue: false } ] }) async createChannel(args: { channelName: string; isPrivate: boolean }) { return { success: true, channelName: args.channelName }; } } ``` ### How It Works 1. **Client calls tool** with missing required fields 2. **Decorator intercepts** and checks for missing fields 3. **Elicitation request returned** with form definition 4. **Client displays form** to collect user input 5. **Client calls tool again** with complete arguments 6. **Method executes** normally *** ## Fluent Builder API For more complex forms, use `ElicitationFormBuilder`: ```typescript theme={null} import { Tool } from "@leanmcp/core"; import { Elicitation, ElicitationFormBuilder, validation } from "@leanmcp/elicitation"; class UserService { @Tool({ description: "Create user account" }) @Elicitation({ builder: () => new ElicitationFormBuilder() .title("User Registration") .description("Create a new user account") .addEmailField("email", "Email Address", { required: true }) .addTextField("username", "Username", { required: true, validation: validation() .minLength(3) .maxLength(20) .pattern("^[a-zA-Z0-9_]+$") .build() }) .addSelectField("role", "Role", [ { label: "Admin", value: "admin" }, { label: "User", value: "user" } ]) .build() }) async createUser(args: any) { return { success: true, email: args.email }; } } ``` ### Builder Methods | Method | Description | | -------------------------------------------------- | ----------------------------- | | `title(string)` | Set form title | | `description(string)` | Set form description | | `condition(fn)` | Set condition for elicitation | | `addTextField(name, label, opts?)` | Add text input | | `addTextAreaField(name, label, opts?)` | Add textarea | | `addNumberField(name, label, opts?)` | Add number input | | `addBooleanField(name, label, opts?)` | Add checkbox | | `addSelectField(name, label, options, opts?)` | Add dropdown | | `addMultiSelectField(name, label, options, opts?)` | Add multi-select | | `addEmailField(name, label, opts?)` | Add email input | | `addUrlField(name, label, opts?)` | Add URL input | | `addDateField(name, label, opts?)` | Add date picker | | `addCustomField(field)` | Add custom field | | `build()` | Build final config | *** ## Conditional Elicitation Only ask for inputs when needed: ```typescript theme={null} @Tool({ description: "Send message to Slack" }) @Elicitation({ condition: (args) => !args.channelId, title: "Select Channel", fields: [ { name: "channelId", label: "Channel", type: "select", required: true, options: [ { label: "#general", value: "C12345" }, { label: "#random", value: "C67890" } ] } ] }) async sendMessage(args: { channelId?: string; message: string }) { // Only elicits if channelId is missing } ``` *** ## Multi-Step Elicitation Break input collection into sequential steps: ```typescript theme={null} @Tool({ description: "Deploy application" }) @Elicitation({ strategy: "multi-step", builder: () => [ { title: "Step 1: Environment", fields: [ { name: "environment", label: "Environment", type: "select", required: true, options: [ { label: "Production", value: "prod" }, { label: "Staging", value: "staging" } ] } ] }, { title: "Step 2: Configuration", fields: [ { name: "replicas", label: "Replicas", type: "number", defaultValue: 3 } ], condition: (prev) => prev.environment === "prod" } ] }) async deployApp(args: any) { // Implementation } ``` *** ## Field Types | Type | Description | | ------------- | ------------------------ | | `text` | Single-line text input | | `textarea` | Multi-line text area | | `number` | Numeric input | | `boolean` | Checkbox | | `select` | Dropdown (single choice) | | `multiselect` | Multi-select | | `email` | Email input | | `url` | URL input | | `date` | Date picker | *** ## Validation ### Built-in Validation ```typescript theme={null} { name: "username", label: "Username", type: "text", validation: { minLength: 3, maxLength: 20, pattern: "^[a-zA-Z0-9_]+$", errorMessage: "Username must be 3-20 alphanumeric characters" } } ``` ### Using ValidationBuilder ```typescript theme={null} import { validation } from "@leanmcp/elicitation"; validation() .minLength(8) .maxLength(100) .pattern("^[a-zA-Z0-9]+$") .customValidator((value) => value !== "admin") .errorMessage("Invalid input") .build() ``` *** ## Elicitation Flow ### Request/Response Cycle **First Call (Missing Fields):** ```json theme={null} // Request { "method": "tools/call", "params": { "name": "createChannel", "arguments": {} } } // Response (Elicitation Request) { "type": "elicitation", "title": "Create Channel", "fields": [ { "name": "channelName", "label": "Channel Name", "type": "text", "required": true } ] } ``` **Second Call (Complete Fields):** ```json theme={null} // Request { "method": "tools/call", "params": { "name": "createChannel", "arguments": { "channelName": "my-channel", "isPrivate": false } } } // Response (Tool Result) { "content": [{"type": "text", "text": "{\"success\": true}"}] } ``` *** ## API Reference ### ElicitationConfig ```typescript theme={null} interface ElicitationConfig { strategy?: 'form' | 'multi-step'; title?: string; description?: string; fields?: ElicitationField[]; condition?: (args: any) => boolean; builder?: (context: ElicitationContext) => ElicitationRequest | ElicitationStep[]; } ``` ### ElicitationField ```typescript theme={null} interface ElicitationField { name: string; label: string; type: 'text' | 'number' | 'boolean' | 'select' | 'multiselect' | 'date' | 'email' | 'url' | 'textarea'; description?: string; required?: boolean; defaultValue?: any; options?: Array<{ label: string; value: any }>; validation?: FieldValidation; placeholder?: string; helpText?: string; } ``` ### FieldValidation ```typescript theme={null} interface FieldValidation { min?: number; max?: number; minLength?: number; maxLength?: number; pattern?: string; customValidator?: (value: any) => boolean | string; errorMessage?: string; } ``` *** ## Best Practices Only ask when truly needed using the `condition` option: ```typescript theme={null} @Elicitation({ condition: (args) => !args.channelId, // ... }) ``` Reduce user input burden with `defaultValue`: ```typescript theme={null} { name: "priority", type: "select", defaultValue: "normal", options: [...] } ``` The fluent API is more maintainable: ```typescript theme={null} builder: () => new ElicitationFormBuilder() .addTextField("name", "Name", { required: true }) .addSelectField("role", "Role", [...]) .build() ``` Use `helpText` and `placeholder` to guide users: ```typescript theme={null} { name: "email", type: "email", placeholder: "user@example.com", helpText: "We'll send confirmation here" } ``` ## Related Packages * [@leanmcp/core](/sdk/core) - Core decorators and server functionality * [@leanmcp/auth](/sdk/auth) - Authentication decorators and providers * [@leanmcp/cli](/sdk/cli) - CLI tool for creating new projects ## Links * [GitHub Repository](https://github.com/LeanMCP/leanmcp-sdk) * [MCP Specification](https://modelcontextprotocol.io/specification/2025-11-25) * [NPM Package](https://www.npmjs.com/package/@leanmcp/elicitation) # @leanmcp/env-injection Source: https://docs.leanmcp.com/sdk/env-injection Request-scoped user secrets injection for MCP tools # @leanmcp/env-injection Request-scoped environment variable injection for LeanMCP tools. Enables user-specific secrets (API keys, tokens) to be securely fetched and accessed within MCP tool methods. This package **only works with the LeanMCP auth provider** and requires a `projectId` to be configured. Users manage their own secrets through the LeanMCP dashboard. ## Features * **Request-scoped isolation** - Each user's secrets are isolated using `AsyncLocalStorage` * **@RequireEnv decorator** - Validate required secrets exist before method execution * **getEnv() / getAllEnv()** - Access user-specific secrets in your tool code * **Concurrency safe** - Each request has its own isolated context ## Installation ```bash theme={null} npm install @leanmcp/env-injection @leanmcp/auth @leanmcp/core ``` ## Quick Start ### 1. Configure Auth Provider with projectId ```typescript theme={null} import { AuthProvider } from "@leanmcp/auth"; const projectId = process.env.LEANMCP_PROJECT_ID; const authProvider = new AuthProvider('leanmcp', { apiKey: process.env.LEANMCP_API_KEY }); await authProvider.init(); ``` ### 2. Use @RequireEnv and getEnv() ```typescript theme={null} import { Tool } from "@leanmcp/core"; import { Authenticated } from "@leanmcp/auth"; import { RequireEnv, getEnv } from "@leanmcp/env-injection"; @Authenticated(authProvider, { projectId }) export class SlackService { @Tool({ description: 'Send a message to Slack' }) @RequireEnv(["SLACK_TOKEN", "SLACK_CHANNEL"]) async sendMessage(args: { message: string }) { // getEnv() returns THIS USER's secret, not a global env var const token = getEnv("SLACK_TOKEN")!; const channel = getEnv("SLACK_CHANNEL")!; await slackApi.postMessage(channel, args.message, token); return { success: true, channel }; } } ``` ## How It Works ``` Request → @Authenticated(projectId) → Fetch Secrets → runWithEnv() → @RequireEnv → Method → Cleanup ↓ ↓ ↓ Verify token Store in ALS getEnv() works ``` 1. User makes request with auth token 2. `@Authenticated` verifies token and fetches user's secrets from LeanMCP API 3. Secrets are stored in `AsyncLocalStorage` for this request only 4. `@RequireEnv` validates required secrets exist 5. `getEnv()` accesses secrets during method execution 6. Context is automatically cleaned up after request completes ## API Reference ### @RequireEnv(keys) Decorator to validate required environment variables exist before method execution. ```typescript theme={null} import { RequireEnv, getEnv } from "@leanmcp/env-injection"; @RequireEnv(["SLACK_TOKEN", "SLACK_CHANNEL"]) async sendMessage(args: { message: string }) { // Method only executes if BOTH keys exist const token = getEnv("SLACK_TOKEN")!; // Safe to use ! const channel = getEnv("SLACK_CHANNEL")!; } ``` **Requirements:** * Must be used with `@Authenticated(authProvider, { projectId })` * Throws clear error if `projectId` is not configured * Throws if required keys are missing *** ### getEnv(key) Get a single environment variable from the current request context. ```typescript theme={null} import { getEnv } from "@leanmcp/env-injection"; const token = getEnv("SLACK_TOKEN"); // Returns undefined if key doesn't exist // Throws if called outside env context ``` *** ### getAllEnv() Get all environment variables from the current request context. ```typescript theme={null} import { getAllEnv } from "@leanmcp/env-injection"; const env = getAllEnv(); // { SLACK_TOKEN: "xoxb-...", SLACK_CHANNEL: "#general" } ``` *** ### hasEnvContext() Check if currently inside an env context. ```typescript theme={null} import { hasEnvContext, getEnv } from "@leanmcp/env-injection"; if (hasEnvContext()) { const token = getEnv("API_KEY"); // Safe to call } ``` *** ### runWithEnv(env, fn) Run a function with environment variables in scope. Used internally by `@Authenticated`. ```typescript theme={null} import { runWithEnv, getEnv } from "@leanmcp/env-injection"; await runWithEnv({ API_KEY: "secret123" }, async () => { console.log(getEnv("API_KEY")); // "secret123" }); ``` ## Error Messages ### Missing projectId Configuration ``` Environment injection not configured for SlackService.sendMessage(). To use @RequireEnv, you must configure 'projectId' in your @Authenticated decorator: @Authenticated(authProvider, { projectId: 'your-project-id' }) ``` ### Missing Required Variables ``` Missing required environment variables: SLACK_TOKEN, SLACK_CHANNEL. Please configure these secrets in your LeanMCP dashboard for this project. ``` ### Called Outside Context ``` getEnv("SLACK_TOKEN") called outside of env context. To use getEnv(), you must configure 'projectId' in your @Authenticated decorator: @Authenticated(authProvider, { projectId: 'your-project-id' }) ``` ## Environment Variables | Variable | Description | | -------------------- | ------------------------------------- | | `LEANMCP_API_KEY` | Your LeanMCP API key (with SDK scope) | | `LEANMCP_PROJECT_ID` | Project ID to scope secrets to | ## Best Practices Environment injection requires the `projectId` option to know which project's secrets to fetch. ```typescript theme={null} @Authenticated(authProvider, { projectId: 'my-project' }) ``` Fails fast with clear error messages if secrets are missing. ```typescript theme={null} @RequireEnv(["API_KEY", "SECRET"]) ``` After `@RequireEnv` validates, secrets are guaranteed to exist. ```typescript theme={null} @RequireEnv(["API_KEY"]) async method() { const key = getEnv("API_KEY")!; // Safe to use ! } ``` They're request-scoped for security. Always call `getEnv()` when needed. ## Related Packages * [@leanmcp/auth](/sdk/auth) - Authentication decorators (required) * [@leanmcp/core](/sdk/core) - Core MCP server functionality ## Links * [GitHub Repository](https://github.com/LeanMCP/leanmcp-sdk) * [NPM Package](https://www.npmjs.com/package/@leanmcp/env-injection) # Examples Source: https://docs.leanmcp.com/sdk/examples Complete examples of LeanMCP SDK usage # Examples Complete, production-ready examples demonstrating various features of the LeanMCP SDK. ## Basic MCP Server A minimal MCP server with a simple tool. ```typescript theme={null} import { createHTTPServer, MCPServer, Tool, SchemaConstraint } from "@leanmcp/core"; // Define input schema class CalculateInput { @SchemaConstraint({ description: 'First number' }) a!: number; @SchemaConstraint({ description: 'Second number' }) b!: number; @SchemaConstraint({ description: 'Operation', enum: ['add', 'subtract', 'multiply', 'divide'] }) operation!: string; } // Service with tools export class CalculatorService { @Tool({ description: 'Perform arithmetic operations', inputClass: CalculateInput }) async calculate(input: CalculateInput) { let result: number; switch (input.operation) { case 'add': result = input.a + input.b; break; case 'subtract': result = input.a - input.b; break; case 'multiply': result = input.a * input.b; break; case 'divide': if (input.b === 0) throw new Error('Division by zero'); result = input.a / input.b; break; default: throw new Error('Invalid operation'); } return { result }; } } // Create and start server const serverFactory = () => { const server = new MCPServer({ name: "calculator-server", version: "1.0.0", logging: true }); server.registerService(new CalculatorService()); return server.getServer(); }; await createHTTPServer(serverFactory, { port: 3000, cors: true }); ``` ## Weather Service MCP server that fetches weather data from an external API. ```typescript theme={null} import { Tool, SchemaConstraint, Optional } from "@leanmcp/core"; import { retry, formatResponse } from "@leanmcp/utils"; class GetWeatherInput { @SchemaConstraint({ description: 'City name', minLength: 1 }) city!: string; @Optional() @SchemaConstraint({ description: 'Country code (ISO 3166)', pattern: '^[A-Z]{2}$' }) country?: string; @Optional() @SchemaConstraint({ description: 'Temperature unit', enum: ['celsius', 'fahrenheit'], default: 'celsius' }) unit?: string; } export class WeatherService { private apiKey = process.env.WEATHER_API_KEY!; @Tool({ description: 'Get current weather for a city', inputClass: GetWeatherInput }) async getWeather(input: GetWeatherInput) { try { const location = input.country ? `${input.city},${input.country}` : input.city; const units = input.unit === 'fahrenheit' ? 'imperial' : 'metric'; // Fetch with retry logic const data = await retry( async () => { const response = await fetch( `https://api.openweathermap.org/data/2.5/weather?q=${location}&units=${units}&appid=${this.apiKey}` ); if (!response.ok) { throw new Error(`Weather API error: ${response.statusText}`); } return response.json(); }, { maxRetries: 3, delayMs: 1000 } ); const temp = Math.round(data.main.temp); const unit = input.unit === 'fahrenheit' ? '°F' : '°C'; return { content: [{ type: "text", text: `Weather in ${data.name}: ${data.weather[0].description}, ${temp}${unit}` }] }; } catch (error) { return { content: [{ type: "text", text: `Failed to fetch weather: ${error.message}` }], isError: true }; } } } ``` ## Authenticated Service with AWS Cognito MCP server with AWS Cognito authentication for protected operations. ```typescript theme={null} import { Tool, SchemaConstraint, Optional } from "@leanmcp/core"; import { Authenticated, AuthProvider } from "@leanmcp/auth"; // Initialize auth provider const authProvider = new AuthProvider('cognito', { region: process.env.AWS_REGION, userPoolId: process.env.COGNITO_USER_POOL_ID, clientId: process.env.COGNITO_CLIENT_ID }); await authProvider.init(); // Input schemas class CreatePostInput { @SchemaConstraint({ description: 'Post title', minLength: 1, maxLength: 200 }) title!: string; @SchemaConstraint({ description: 'Post content', minLength: 1 }) content!: string; @Optional() @SchemaConstraint({ description: 'Post tags' }) tags?: string[]; } class GetPostsInput { @Optional() @SchemaConstraint({ description: 'Author user ID' }) authorId?: string; @Optional() @SchemaConstraint({ description: 'Maximum number of posts', minimum: 1, maximum: 100, default: 10 }) limit?: number; } // Blog service with authentication export class BlogService { private posts: any[] = []; @Tool({ description: 'Create a new blog post (requires authentication)' }) @Authenticated(authProvider) async createPost(input: CreatePostInput) { // Token is validated via _meta.authorization.token const post = { id: `post-${Date.now()}`, title: input.title, content: input.content, tags: input.tags || [], createdAt: new Date().toISOString() }; this.posts.push(post); return { success: true, post }; } @Tool({ description: 'Get blog posts (public)' }) async getPosts(input: GetPostsInput) { let filtered = this.posts; if (input.authorId) { filtered = filtered.filter(p => p.authorId === input.authorId); } const limit = input.limit || 10; const posts = filtered.slice(0, limit); return { posts, total: filtered.length }; } @Tool({ description: 'Delete a blog post (requires authentication)' }) @Authenticated(authProvider) async deletePost(input: { postId: string }) { const postIndex = this.posts.findIndex(p => p.id === input.postId); if (postIndex === -1) { throw new Error('Post not found'); } this.posts.splice(postIndex, 1); return { success: true, message: 'Post deleted successfully' }; } } ``` ## Service with Elicitation MCP server using elicitation to collect input from users. ```typescript theme={null} import { Tool, SchemaConstraint, Optional } from "@leanmcp/core"; import { Elicitation } from "@leanmcp/elicitation"; class SlackService { @Tool({ description: "Create a new Slack channel" }) @Elicitation({ title: "Create Channel", description: "Please provide channel details", fields: [ { name: "channelName", label: "Channel Name", type: "text", required: true, validation: { pattern: "^[a-z0-9-]+$", errorMessage: "Must be lowercase alphanumeric with hyphens" } }, { name: "isPrivate", label: "Private Channel", type: "boolean", defaultValue: false } ] }) async createChannel(args: { channelName: string; isPrivate: boolean }) { return { success: true, channelId: `C${Date.now()}`, channelName: args.channelName }; } } ``` ## Database Service with Resources MCP server providing both tools and resources. ```typescript theme={null} import { Tool, Resource, Prompt, SchemaConstraint } from "@leanmcp/core"; interface User { id: string; name: string; email: string; createdAt: string; } export class DatabaseService { private users: User[] = [ { id: '1', name: 'John Doe', email: 'john@example.com', createdAt: '2024-01-01' }, { id: '2', name: 'Jane Smith', email: 'jane@example.com', createdAt: '2024-01-02' } ]; // Tool: Query users @Tool({ description: 'Search users by name or email', inputClass: class { @SchemaConstraint({ description: 'Search query' }) query!: string; } }) async searchUsers(input: { query: string }) { const query = input.query.toLowerCase(); const results = this.users.filter(u => u.name.toLowerCase().includes(query) || u.email.toLowerCase().includes(query) ); return { users: results, count: results.length }; } // Resource: Get all users @Resource({ description: 'Get all users in the database', mimeType: 'application/json' }) async getAllUsers() { return { users: this.users, total: this.users.length, lastUpdated: new Date().toISOString() }; } // Resource: Get database stats @Resource({ description: 'Get database statistics', mimeType: 'application/json' }) async getDatabaseStats() { return { totalUsers: this.users.length, oldestUser: this.users[0]?.createdAt, newestUser: this.users[this.users.length - 1]?.createdAt }; } // Prompt: Generate user report @Prompt({ description: 'Generate a user report prompt' }) userReport(input: { userId: string }) { const user = this.users.find(u => u.id === input.userId); if (!user) { throw new Error('User not found'); } return { messages: [{ role: "user", content: { type: "text", text: `Generate a detailed report for user: ${user.name} (${user.email}). Include account age and activity summary.` } }] }; } } ``` ## Multi-Service Server Complete server with multiple services. ```typescript theme={null} import { createHTTPServer, MCPServer } from "@leanmcp/core"; import { CalculatorService } from "./services/calculator"; import { WeatherService } from "./services/weather"; import { BlogService } from "./services/blog"; import { DatabaseService } from "./services/database"; const serverFactory = () => { const server = new MCPServer({ name: "multi-service-mcp", version: "1.0.0", logging: true }); // Register all services server.registerService(new CalculatorService()); server.registerService(new WeatherService()); server.registerService(new BlogService()); server.registerService(new DatabaseService()); return server.getServer(); }; // Start server const PORT = process.env.PORT || 3000; await createHTTPServer(serverFactory, { port: PORT, cors: true, logging: true }); console.log(`🚀 MCP Server running on http://localhost:${PORT}`); ``` ## Testing Your Server ### Using cURL ```bash theme={null} # List all tools curl -X POST http://localhost:3000/mcp \ -H "Content-Type: application/json" \ -d '{ "jsonrpc": "2.0", "method": "tools/list", "id": 1 }' # Call a tool curl -X POST http://localhost:3000/mcp \ -H "Content-Type: application/json" \ -d '{ "jsonrpc": "2.0", "method": "tools/call", "params": { "name": "calculate", "arguments": { "a": 10, "b": 5, "operation": "add" } }, "id": 2 }' # Call authenticated tool (with _meta) curl -X POST http://localhost:3000/mcp \ -H "Content-Type: application/json" \ -d '{ "jsonrpc": "2.0", "method": "tools/call", "params": { "name": "createPost", "arguments": { "title": "My First Post", "content": "Hello, World!" }, "_meta": { "authorization": { "type": "bearer", "token": "YOUR_COGNITO_TOKEN" } } }, "id": 3 }' ``` ### Using TypeScript Client ```typescript theme={null} async function callMCPTool(toolName: string, args: any, token?: string) { const params: any = { name: toolName, arguments: args }; // Add authentication if token provided if (token) { params._meta = { authorization: { type: "bearer", token: token } }; } const response = await fetch('http://localhost:3000/mcp', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ jsonrpc: '2.0', method: 'tools/call', params, id: Date.now() }) }); return response.json(); } // Use it const result = await callMCPTool('calculate', { a: 10, b: 5, operation: 'multiply' }); console.log(result); ``` ## Next Steps Learn more about decorators and schemas Add authentication to your server Scaffold projects quickly More examples on GitHub # LeanMCP SDK Source: https://docs.leanmcp.com/sdk/introduction Build production-ready MCP servers with TypeScript decorators # LeanMCP SDK A TypeScript SDK for building Model Context Protocol (MCP) servers with decorators, authentication, and production-ready features. ## Overview LeanMCP SDK provides a modern, decorator-based approach to building MCP servers. It handles the complexity of the MCP protocol while letting you focus on building great tools. ## Key Features Use `@Tool`, `@Prompt`, `@Resource` decorators with full TypeScript support Token-based auth with support for Firebase, Supabase, and custom providers Automatic validation using JSON Schema and AJV HTTP server, session management, error handling, and logging out of the box ## Quick Example ```typescript theme={null} import { Tool, SchemaConstraint } from "@leanmcp/core"; class AnalyzeSentimentInput { @SchemaConstraint({ description: 'Text to analyze', minLength: 1 }) text!: string; } export class SentimentService { @Tool({ description: 'Analyze sentiment of text', inputClass: AnalyzeSentimentInput }) async analyzeSentiment(input: AnalyzeSentimentInput) { return { sentiment: 'positive', score: 0.8 }; } } ``` ## Packages The SDK is organized into several packages: * **[@leanmcp/core](/sdk/core)** - Core decorators and server functionality * **[@leanmcp/auth](/sdk/auth)** - Authentication decorators and providers * **[@leanmcp/env-injection](/sdk/env-injection)** - Request-scoped user secrets injection * **[@leanmcp/elicitation](/sdk/elicitation)** - Structured user input collection * **[@leanmcp/ui](/sdk/ui)** - MCP-native React components and hooks for MCP Apps * **[@leanmcp/cli](/sdk/cli)** - Command-line tool for project scaffolding * **[@leanmcp/utils](/sdk/utils)** - Utility functions and helpers ## Installation Install the core package to get started: ```bash theme={null} npm install @leanmcp/core ``` Or use the CLI to create a new project: ```bash theme={null} npx @leanmcp/cli create my-mcp-server ``` ## Next Steps Learn about decorators and schema definition Add authentication to your MCP server Scaffold projects with the CLI View complete examples ## Resources * [GitHub Repository](https://github.com/LeanMCP/leanmcp-sdk) * [MCP Specification](https://modelcontextprotocol.io/specification/2025-11-25) * [NPM Package](https://www.npmjs.com/package/@leanmcp/core) # @leanmcp/ui Source: https://docs.leanmcp.com/sdk/ui MCP-native React components and hooks for building MCP Apps # @leanmcp/ui Build rich, interactive MCP Apps with React components designed for the Model Context Protocol. Features first-class tool integration, streaming support, and automatic host theming. ## Features ToolButton, ToolDataGrid, ToolForm - components that directly integrate with MCP tools Works with ext-apps hosts (Claude Desktop) and ChatGPT GPT Actions Automatic theme sync with host application (light/dark mode) MockAppProvider for unit testing MCP App components ## Installation ```bash theme={null} npm install @leanmcp/ui ``` Import the styles in your app entry point: ```tsx theme={null} import '@leanmcp/ui/styles.css'; ``` ## Two App Paradigms `@leanmcp/ui` supports two different host environments: ### ext-apps (Claude Desktop, MCP Hosts) Uses the `@modelcontextprotocol/ext-apps` protocol for iframe-based communication. ```tsx theme={null} import { AppProvider, ToolButton } from '@leanmcp/ui'; import '@leanmcp/ui/styles.css'; function MyApp() { return ( Refresh Data ); } ``` ### ChatGPT GPT Actions Uses ChatGPT's native `window.openai` SDK. ```tsx theme={null} import { GPTAppProvider, useGptTool } from '@leanmcp/ui'; import '@leanmcp/ui/styles.css'; function MyGPTApp() { return ( ); } function DataDisplay() { const { call, result, loading } = useGptTool('get-data'); return ( ); } ``` ## Quick Start Example Here's a complete example showing a tool-linked UI component: ```tsx theme={null} import { AppProvider, ToolDataGrid, RequireConnection } from '@leanmcp/ui'; import '@leanmcp/ui/styles.css'; function UsersApp() { return ( ({ rows: result.users, total: result.total })} rowActions={[ { label: 'Edit', tool: 'edit-user' }, { label: 'Delete', tool: 'delete-user', variant: 'destructive' } ]} pagination /> ); } ``` ## Server-Side Integration Link UI components to tools using the `@UIApp` decorator (for ext-apps) or `@GPTApp` decorator (for ChatGPT): ```typescript theme={null} import { Tool } from '@leanmcp/core'; import { UIApp } from '@leanmcp/ui'; class WeatherService { @Tool({ description: 'Get weather for a city' }) @UIApp({ component: './WeatherCard' }) async getWeather(args: { city: string }) { return { city: args.city, temperature: 22, condition: 'Sunny' }; } } ``` When the tool is called, the host will render the linked UI component with the tool result. ## Testing Use `MockAppProvider` for unit testing: ```tsx theme={null} import { MockAppProvider } from '@leanmcp/ui/testing'; import { render, screen } from '@testing-library/react'; test('WeatherCard displays temperature', () => { render( ); expect(screen.getByText('20°C')).toBeInTheDocument(); }); ``` ## What's Next ToolButton, ToolDataGrid, ToolForm, and more useTool, useResource, useMessage, useHostContext ChatGPT integration, @UIApp, @GPTApp decorators Complete working examples ## Links * [GitHub Repository](https://github.com/LeanMCP/leanmcp-sdk) * [NPM Package](https://www.npmjs.com/package/@leanmcp/ui) * [MCP Specification](https://modelcontextprotocol.io/specification/2025-11-25) # UI Components Source: https://docs.leanmcp.com/sdk/ui-components MCP-native React components for building MCP Apps # MCP Components React components designed for seamless integration with MCP tools. Each component provides built-in loading states, error handling, and host theming support. ## ToolButton Button that executes an MCP tool on click with optional confirmation, loading states, and result display. ### Basic Usage ```tsx theme={null} Refresh ``` ### With Result Display ```tsx theme={null} console.log('Created:', order.id)} > Place Order ``` ### With Confirmation Dialog ```tsx theme={null} Delete ``` ### Props Reference | Prop | Type | Description | | --------------- | ---------------------------------------------------- | ---------------------------------- | | `tool` | `string \| ToolBinding` | Tool name or binding config | | `args` | `Record` | Arguments to pass to the tool | | `resultDisplay` | `'inline' \| 'toast' \| 'modal' \| 'none'` | How to display results | | `confirm` | `boolean \| ConfirmConfig` | Show confirmation before executing | | `variant` | `'default' \| 'destructive' \| 'outline' \| 'ghost'` | Button style variant | | `loadingText` | `string` | Text to show while loading | | `onToolSuccess` | `(result) => void` | Called on successful execution | | `onToolError` | `(error) => void` | Called on error | *** ## ToolDataGrid Data grid with server-side pagination, sorting, and row actions - all powered by MCP tools. ### Basic Usage ```tsx theme={null} ({ rows: result.users, total: result.total })} /> ``` ### With Row Actions ```tsx theme={null} ({ id: row.id }) }, { label: 'Delete', tool: 'delete-order', variant: 'destructive', confirm: true } ]} pagination defaultPageSize={25} /> ``` ### Props Reference | Prop | Type | Description | | ----------------- | ----------------------------- | ---------------------------------- | | `dataTool` | `string \| ToolBinding` | Tool to fetch data from | | `columns` | `ToolDataGridColumn[]` | Column definitions | | `transformData` | `(result) => { rows, total }` | Transform tool result to grid data | | `rowActions` | `ToolDataGridRowAction[]` | Actions for each row | | `pagination` | `boolean` | Enable pagination (default: true) | | `defaultPageSize` | `number` | Initial page size | | `refreshInterval` | `number` | Auto-refresh interval in ms | | `onRowClick` | `(row, index) => void` | Row click handler | ### Column Definition ```typescript theme={null} interface ToolDataGridColumn { key: string; // Field key (dot notation supported) header: string; // Column header text sortable?: boolean; // Enable sorting render?: (value, row, index) => ReactNode; // Custom renderer width?: string; // CSS width align?: 'left' | 'center' | 'right'; } ``` *** ## ToolForm Form component that submits data to an MCP tool with built-in field types and validation. ### Basic Usage ```tsx theme={null} console.log('Created:', user)} /> ``` ### Field Types | Type | Description | | ---------- | ----------------------------- | | `text` | Standard text input (default) | | `number` | Numeric input with min/max | | `email` | Email input with validation | | `password` | Password input | | `textarea` | Multi-line text | | `select` | Dropdown selection | | `checkbox` | Boolean checkbox | | `switch` | Toggle switch | | `slider` | Range slider | ### Props Reference | Prop | Type | Description | | ------------------ | ---------------------------- | ------------------------ | | `toolName` | `string` | Tool to call on submit | | `fields` | `ToolFormField[]` | Field definitions | | `submitText` | `string` | Submit button text | | `showSuccessToast` | `boolean` | Show toast on success | | `resetOnSuccess` | `boolean` | Reset form after success | | `onSuccess` | `(result) => void` | Success callback | | `onError` | `(error) => void` | Error callback | | `layout` | `'vertical' \| 'horizontal'` | Form layout | *** ## ToolSelect Select component that can fetch options from a tool and/or trigger a tool on selection. ### Fetch Options from Tool ```tsx theme={null} result.categories.map(c => ({ value: c.id, label: c.name }))} placeholder="Select category" /> ``` ### Call Tool on Selection ```tsx theme={null} ``` ### Props Reference | Prop | Type | Description | | ------------------ | ----------------------- | -------------------------------- | | `optionsTool` | `string \| ToolBinding` | Tool to fetch options from | | `transformOptions` | `(result) => Option[]` | Transform result to options | | `onSelectTool` | `string \| ToolBinding` | Tool to call on selection | | `argName` | `string` | Argument name for selected value | | `options` | `ToolSelectOption[]` | Static options | | `value` | `string` | Controlled value | | `onValueChange` | `(value) => void` | Change handler | *** ## ToolInput Text input with debounced tool calls for search/autocomplete scenarios. ```tsx theme={null} setSearchResults(items)} /> ``` *** ## ResourceView Display an MCP resource with auto-refresh support. ```tsx theme={null} (
{JSON.stringify(data, null, 2)}
)} /> ``` *** ## Utility Components ### RequireConnection Guard wrapper that shows loading/error states until MCP connection is established. ```tsx theme={null} } errorContent={(error) => {error.message}} > ``` ### ToolErrorBoundary Error boundary with retry functionality for tool-related errors. ```tsx theme={null} refetch()}> ``` ### StreamingContent Render streaming/partial tool data as it arrives. ```tsx theme={null} } /> ``` # Decorators & GPT Apps Source: https://docs.leanmcp.com/sdk/ui-gpt-apps Server-side decorators and ChatGPT-specific integration # Decorators & GPT Apps This page covers server-side decorators for linking tools to UI components, and ChatGPT-specific providers and hooks. ## @UIApp Decorator Links an MCP tool to a React UI component for ext-apps hosts (Claude Desktop, MCP-compatible hosts). When a tool decorated with `@UIApp` is called, the host will: 1. Execute the tool and get the result 2. Fetch the linked UI component as an HTML resource 3. Render the UI in an iframe with the tool result ### Basic Usage ```typescript theme={null} import { Tool } from '@leanmcp/core'; import { UIApp } from '@leanmcp/ui'; class WeatherService { @Tool({ description: 'Get weather for a city' }) @UIApp({ component: './WeatherCard' }) async getWeather(args: { city: string }) { const weather = await fetchWeatherAPI(args.city); return { city: args.city, temperature: weather.temp, condition: weather.condition }; } } ``` Use a **path string** (e.g., `'./WeatherCard'`) for the component to avoid importing browser code in your server bundle. The CLI will resolve and build the component separately. ### With Custom URI ```typescript theme={null} @Tool({ description: 'Show dashboard' }) @UIApp({ component: './DashboardView', uri: 'ui://analytics/dashboard', // Custom resource URI title: 'Analytics Dashboard' // HTML document title }) async getDashboard() { return { /* dashboard data */ }; } ``` ### Options | Option | Type | Description | | ----------- | ------------------------------- | ---------------------------------------------------- | | `component` | `React.ComponentType \| string` | Component or path to component file | | `uri` | `string` | Custom resource URI (auto-generated if not provided) | | `title` | `string` | HTML document title | | `styles` | `string` | Additional CSS styles | ### The UI Component Your UI component receives the tool result via `useToolResult`: ```tsx theme={null} // WeatherCard.tsx import { AppProvider, useToolResult, Card, RequireConnection } from '@leanmcp/ui'; import '@leanmcp/ui/styles.css'; interface WeatherData { city: string; temperature: number; condition: string; } function WeatherCardContent() { const { result, loading } = useToolResult(); if (loading || !result) return
Loading...
; return (

{result.city}

{result.temperature}°C

{result.condition}

); } export default function WeatherCard() { return ( ); } ``` *** ## @GPTApp Decorator Links an MCP tool to a React UI component specifically for ChatGPT GPT Actions. ```typescript theme={null} import { Tool } from '@leanmcp/core'; import { GPTApp } from '@leanmcp/ui'; class AnalyticsService { @Tool({ description: 'Show analytics dashboard' }) @GPTApp({ component: './AnalyticsDashboard' }) async getAnalytics(args: { period: string }) { return { /* analytics data */ }; } } ``` ### Options | Option | Type | Description | | ----------- | ------------------------------- | ----------------------------------- | | `component` | `React.ComponentType \| string` | Component or path to component file | | `uri` | `string` | Custom resource URI | | `title` | `string` | HTML document title | *** ## GPTAppProvider Context provider for ChatGPT Apps using the native `window.openai` SDK. ### Basic Setup ```tsx theme={null} import { GPTAppProvider } from '@leanmcp/ui'; import '@leanmcp/ui/styles.css'; function MyGPTApp() { return ( ); } ``` ### Props | Prop | Type | Description | | ---------- | ----------- | --------------------------- | | `appName` | `string` | App name for identification | | `children` | `ReactNode` | App content | *** ## useGptApp Access the GPT App context including connection state and theme. ```tsx theme={null} import { useGptApp } from '@leanmcp/ui'; function MyComponent() { const { isConnected, // Whether connected to ChatGPT theme, // 'light' | 'dark' displayMode, // Display mode locale, // User locale maxHeight, // Max height from host callTool, // Call a server tool error // Connection error } = useGptApp(); if (!isConnected) { return
Connecting to ChatGPT...
; } return
Connected! Theme: {theme}
; } ``` *** ## useGptTool Call MCP tools via ChatGPT's SDK with loading and error states. ### Basic Usage ```tsx theme={null} import { useGptTool } from '@leanmcp/ui'; function DataViewer() { const { call, result, loading, error } = useGptTool('get-data'); useEffect(() => { call({ id: '123' }); }, []); if (loading) return
Loading...
; if (error) return
Error: {error.message}
; return
{JSON.stringify(result, null, 2)}
; } ``` ### Return Value | Property | Type | Description | | ------------- | ------------------------- | ---------------- | | `call` | `(args?) => Promise` | Execute the tool | | `result` | `any` | Tool result | | `loading` | `boolean` | Loading state | | `error` | `Error \| null` | Error if any | | `isConnected` | `boolean` | Connection state | *** ## useAuth Handle OAuth authentication flows in GPT Apps. This hook integrates with ChatGPT's native OAuth linking UI. ### Basic Usage ```tsx theme={null} import { useAuth } from '@leanmcp/ui'; function ProtectedContent() { const { isAuthenticated, // Whether user is authenticated isLoading, // Auth check in progress user, // User info (if authenticated) error, // Auth error (if any) login, // Trigger OAuth flow logout, // Clear auth state } = useAuth(); if (isLoading) { return
Checking authentication...
; } if (!isAuthenticated) { return (

Please sign in to continue

); } return (

Welcome, {user?.name}!

); } ``` ### How It Works 1. When a tool returns `_meta["mcp/www_authenticate"]`, ChatGPT displays an OAuth linking prompt 2. The `useAuth` hook detects this and updates `isAuthenticated` state 3. Calling `login()` triggers the OAuth flow by calling an auth-required tool 4. After successful OAuth, ChatGPT automatically retries the tool call ### Return Value | Property | Type | Description | | ----------------- | ------------------ | ----------------------------- | | `isAuthenticated` | `boolean` | Whether user is authenticated | | `isLoading` | `boolean` | Auth check in progress | | `user` | `AuthUser \| null` | User info if authenticated | | `error` | `Error \| null` | Auth error if any | | `login` | `() => void` | Trigger OAuth authentication | | `logout` | `() => void` | Clear auth state | ### AuthUser Type ```typescript theme={null} interface AuthUser { sub?: string; // User ID email?: string; // Email address name?: string; // Display name picture?: string; // Avatar URL } ``` ### With Protected Tools Combine `useAuth` with tools that require authentication: ```tsx theme={null} import { useAuth, useGptTool, Button, Card } from '@leanmcp/ui'; function PrivateData() { const { isAuthenticated, user, login } = useAuth(); const { call, result, loading } = useGptTool('get-private-data'); if (!isAuthenticated) { return (

This feature requires authentication

); } return (

Logged in as {user?.email}

{result &&
{JSON.stringify(result, null, 2)}
}
); } ``` ### Server-Side Auth Errors For the auth flow to work, your server tool should return MCP-compliant auth errors: ```typescript theme={null} import { Tool, createAuthError } from '@leanmcp/core'; export class DataService { @Tool({ description: 'Get private user data' }) async getPrivateData(args: any, meta?: any) { const token = meta?.authorization?.token; if (!token) { return createAuthError('Authentication required', { resourceMetadataUrl: `${process.env.PUBLIC_URL}/.well-known/oauth-protected-resource`, error: 'invalid_token', }); } // Proceed with authenticated request... } } ``` *** ## Environment Differences ### ext-apps vs ChatGPT | Feature | ext-apps (AppProvider) | ChatGPT (GPTAppProvider) | | ------------- | ------------------------- | ------------------------ | | Transport | PostMessage iframe | window\.openai SDK | | Theme sync | Full CSS variables | Basic light/dark | | Tool calls | `useTool`, `callTool` | `useGptTool` | | Resources | `useResource` | Not supported | | Messages | `useMessage` | Not supported | | Display modes | Inline, modal, fullscreen | Inline only | ### Shared Features Both environments support: * Tool execution * Theme awareness (light/dark) * Auto-applied styles via `@leanmcp/ui/styles.css` * Testing with `MockAppProvider` *** ## Complete GPT App Example ```tsx theme={null} import { GPTAppProvider, useGptTool, Card, Button } from '@leanmcp/ui'; import '@leanmcp/ui/styles.css'; function StockDashboard() { const { call, result, loading, error } = useGptTool('get-stock-price'); return (
{error && (
Error: {error.message}
)} {result && (

{result.symbol}

${result.price}

0 ? 'text-green-500' : 'text-red-500'}> {result.change > 0 ? '+' : ''}{result.change}%

)}
); } export default function App() { return ( ); } ``` *** ## Related * [@leanmcp/ui Overview](/sdk/ui) - Introduction to MCP-native React components * [UI Components](/sdk/ui-components) - Pre-built MCP components * [UI Hooks](/sdk/ui-hooks) - All available React hooks * [Authentication Overview](/sdk/auth) - Server-side authentication with `@Authenticated` * [OAuth Server & Proxy](/sdk/auth-oauth-server) - Build OAuth authorization servers * [Auth & Payment Guide](/guides/auth-and-payment) - Complete authentication integration guide # UI Hooks Source: https://docs.leanmcp.com/sdk/ui-hooks React hooks for MCP tool calls, resources, and host communication # MCP Hooks React hooks for interacting with MCP servers from within your App components. All hooks work within an `AppProvider` or `GPTAppProvider` context. ## useTool The primary hook for calling MCP tools with full state management, retries, and result transformation. ### Basic Usage ```tsx theme={null} function RefreshButton() { const { call, loading, result, error } = useTool('refresh-data'); return ( ); } ``` ### With Arguments ```tsx theme={null} function WeatherWidget({ city }: { city: string }) { const { call, result, loading } = useTool('get-weather'); useEffect(() => { call({ city }); }, [city]); if (loading) return ; return
{result?.temperature}°C
; } ``` ### With Options ```tsx theme={null} const { call, result, error, retry, reset } = useTool('create-item', { defaultArgs: { type: 'note' }, transform: (result) => result.structuredContent?.item, retry: { count: 3, delay: 1000 }, onStart: () => console.log('Starting...'), onSuccess: (item) => toast.success(`Created: ${item.name}`), onError: (error) => toast.error(error.message), onComplete: () => console.log('Done'), }); ``` ### Return Value | Property | Type | Description | | --------- | --------------------------------------------- | ------------------------- | | `call` | `(args?) => Promise` | Execute the tool | | `mutate` | `(args) => Promise` | Alias for call (semantic) | | `loading` | `boolean` | Whether tool is executing | | `state` | `'idle' \| 'loading' \| 'success' \| 'error'` | Current state | | `result` | `T \| null` | Last successful result | | `error` | `Error \| null` | Last error | | `reset` | `() => void` | Reset to initial state | | `retry` | `() => Promise` | Retry last call | | `abort` | `() => void` | Abort current call | ### Options | Option | Type | Description | | ------------- | ---------------------------- | --------------------------------------- | | `defaultArgs` | `Record` | Default arguments merged with call args | | `transform` | `(result) => T` | Transform the raw tool result | | `retry` | `number \| { count, delay }` | Retry configuration | | `onStart` | `() => void` | Called when tool starts | | `onSuccess` | `(result) => void` | Called on success | | `onError` | `(error) => void` | Called on error | | `onComplete` | `() => void` | Called after success or error | *** ## useResource Read MCP server resources with auto-refresh and subscription support. ### Basic Usage ```tsx theme={null} function UserProfile() { const { data, loading, error, refresh } = useResource('user://profile'); if (loading) return ; if (error) return {error.message}; return (

{data?.name}

); } ``` ### With Auto-Refresh ```tsx theme={null} const { data } = useResource('metrics://dashboard', { refreshInterval: 5000, // Refresh every 5 seconds transform: (raw) => raw.metrics, }); ``` ### Return Value | Property | Type | Description | | ------------- | ------------------ | --------------------- | | `data` | `T \| null` | Resource data | | `loading` | `boolean` | Whether loading | | `error` | `Error \| null` | Error if any | | `refresh` | `() => Promise` | Manual refresh | | `lastUpdated` | `Date \| null` | Last update timestamp | ### Options | Option | Type | Description | | ----------------- | ------------- | ------------------------------------ | | `refreshInterval` | `number` | Auto-refresh interval in ms | | `subscribe` | `boolean` | Enable subscription (when supported) | | `transform` | `(data) => T` | Transform resource data | | `skip` | `boolean` | Skip initial fetch | *** ## useMessage Send messages to the host chat interface. ```tsx theme={null} function FeedbackButton() { const { sendMessage } = useMessage(); const handleClick = async () => { await sendMessage('User requested help with the dashboard'); }; return ; } ``` ### Return Value | Property | Type | Description | | ------------- | --------------------------------- | -------------------- | | `sendMessage` | `(text: string) => Promise` | Send message to host | *** ## useHostContext Access host environment information like theme, viewport, and display mode. ```tsx theme={null} function ThemedComponent() { const { theme, displayMode, viewport } = useHostContext(); return (

Display mode: {displayMode}

Viewport: {viewport?.width}x{viewport?.height}

); } ``` ### Return Value | Property | Type | Description | | ------------- | ------------------- | -------------------- | | `theme` | `'light' \| 'dark'` | Current theme | | `displayMode` | `string` | Current display mode | | `viewport` | `{ width, height }` | Viewport dimensions | | `styles` | `object` | Host style variables | *** ## useToolResult Access the tool result passed from the host when rendering a tool-linked UI. ```tsx theme={null} function WeatherCard() { const { result, loading, error } = useToolResult(); if (loading) return ; if (error) return {error.message}; if (!result) return null; return (

{result.city}

{result.temperature}°C - {result.condition}

); } ``` *** ## useToolInput Access the tool input arguments when the host is streaming input to your app. ```tsx theme={null} function LivePreview() { const { input } = useToolInput<{ query: string }>(); return (

Current query: {input?.query ?? 'None'}

); } ``` *** ## useToolStream Handle streaming tool responses with partial updates. ```tsx theme={null} function StreamingResponse() { const { partial, isStreaming } = useToolStream<{ text: string }>(); return (
{isStreaming && } {partial?.text ?? ''}
); } ``` *** ## useMcpApp Low-level hook to access the full MCP App context. Most use cases are covered by the specialized hooks above. ```tsx theme={null} function AdvancedComponent() { const { app, // Raw ext-apps App instance isConnected, // Connection state error, // Connection error callTool, // Call any tool sendMessage, // Send chat message sendLog, // Send log to host openLink, // Open URL in host requestDisplayMode, // Request display mode change } = useMcpApp(); const handleFullscreen = async () => { await requestDisplayMode('fullscreen'); }; return ( ); } ``` # @leanmcp/utils Source: https://docs.leanmcp.com/sdk/utils Utility functions and helpers for LeanMCP SDK # @leanmcp/utils Utility functions and helpers for LeanMCP SDK. ## Features * **Retry logic** - Exponential backoff for resilient operations * **Response formatting** - Format data as JSON, Markdown, HTML, or tables * **Object utilities** - Deep merge, validation, and manipulation * **Async helpers** - Sleep, timeout, and promise utilities ## Installation ```bash theme={null} npm install @leanmcp/utils ``` ## API Reference ### Response Formatting #### formatResponse(data, format) Format data based on specified format type. ````typescript theme={null} import { formatResponse } from "@leanmcp/utils"; // JSON formatting const json = formatResponse({ hello: "world" }, "json"); // Output: '{\n "hello": "world"\n}' // Markdown formatting const md = formatResponse({ hello: "world" }, "markdown"); // Output: '```json\n{\n "hello": "world"\n}\n```' // HTML formatting const html = formatResponse({ hello: "world" }, "html"); // Output: '
{\n  "hello": "world"\n}
' // Table formatting (for arrays) const table = formatResponse([ { name: "Alice", age: 30 }, { name: "Bob", age: 25 } ], "table"); // Output: Markdown table format ```` **Supported formats:** * `json` - Pretty-printed JSON * `markdown` - JSON wrapped in markdown code block * `html` - JSON wrapped in HTML pre tag * `table` - Markdown table (for arrays of objects) * Default - String conversion #### formatAsTable(data) Format array of objects as a Markdown table. ```typescript theme={null} import { formatAsTable } from "@leanmcp/utils"; const data = [ { name: "Alice", age: 30, city: "NYC" }, { name: "Bob", age: 25, city: "LA" } ]; const table = formatAsTable(data); console.log(table); // | name | age | city | // | --- | --- | --- | // | Alice | 30 | NYC | // | Bob | 25 | LA | ``` ### Object Utilities #### deepMerge(target, ...sources) Deep merge multiple objects. ```typescript theme={null} import { deepMerge } from "@leanmcp/utils"; const target = { a: 1, b: { c: 2 } }; const source1 = { b: { d: 3 } }; const source2 = { e: 4 }; const result = deepMerge(target, source1, source2); // { a: 1, b: { c: 2, d: 3 }, e: 4 } ``` #### isObject(item) Check if value is a plain object. ```typescript theme={null} import { isObject } from "@leanmcp/utils"; isObject({}); // true isObject([]); // false isObject(null); // false isObject("string"); // false ``` ### Async Utilities #### retry(fn, options) Retry a function with exponential backoff. ```typescript theme={null} import { retry } from "@leanmcp/utils"; // Retry API call up to 3 times const result = await retry( async () => { const response = await fetch('https://api.example.com/data'); if (!response.ok) throw new Error('API error'); return response.json(); }, { maxRetries: 3, // Maximum number of retries delayMs: 1000, // Initial delay in milliseconds backoff: 2 // Backoff multiplier (2^n) } ); ``` **Retry logic:** * Attempt 1: Immediate * Attempt 2: Wait 1000ms * Attempt 3: Wait 2000ms * Attempt 4: Wait 4000ms #### sleep(ms) Async sleep function. ```typescript theme={null} import { sleep } from "@leanmcp/utils"; await sleep(1000); // Wait 1 second console.log("1 second later"); ``` #### timeout(promise, ms) Add timeout to a promise. ```typescript theme={null} import { timeout } from "@leanmcp/utils"; try { const result = await timeout( fetch('https://slow-api.example.com'), 5000 // 5 second timeout ); } catch (error) { console.log('Request timed out'); } ``` ## Usage Examples ### Formatting API Responses ```typescript theme={null} import { formatResponse } from "@leanmcp/utils"; class DataService { @Tool({ description: 'Get user data' }) async getUsers() { const users = await fetchUsers(); // Return as formatted table return { content: [{ type: "text", text: formatResponse(users, "table") }] }; } } ``` ### Resilient API Calls ```typescript theme={null} import { retry } from "@leanmcp/utils"; class ExternalService { @Tool({ description: 'Fetch external data' }) async fetchData(input: { url: string }) { // Automatically retry failed requests const data = await retry( () => fetch(input.url).then(r => r.json()), { maxRetries: 3, delayMs: 1000 } ); return { data }; } } ``` ### Deep Configuration Merging ```typescript theme={null} import { deepMerge } from "@leanmcp/utils"; const defaultConfig = { server: { port: 3000, host: 'localhost' }, logging: { level: 'info' } }; const userConfig = { server: { port: 4000 }, features: { auth: true } }; const config = deepMerge(defaultConfig, userConfig); // { // server: { port: 4000, host: 'localhost' }, // logging: { level: 'info' }, // features: { auth: true } // } ``` ## Type Definitions All functions are fully typed with TypeScript: ```typescript theme={null} export function formatResponse(data: any, format: string): string; export function formatAsTable(data: any[]): string; export function deepMerge>(target: T, ...sources: Partial[]): T; export function isObject(item: any): boolean; export function retry(fn: () => Promise, options?: RetryOptions): Promise; export function sleep(ms: number): Promise; export function timeout(promise: Promise, ms: number): Promise; interface RetryOptions { maxRetries?: number; delayMs?: number; backoff?: number; } ``` ## Related Packages * [@leanmcp/core](/sdk/core) - Core MCP server functionality * [@leanmcp/auth](/sdk/auth) - Authentication decorators * [@leanmcp/cli](/sdk/cli) - CLI tool for creating new projects ## Links * [GitHub Repository](https://github.com/LeanMCP/leanmcp-sdk) * [NPM Package](https://www.npmjs.com/package/@leanmcp/utils)