Add 11 MCP agent skills to repo — 550KB of encoded pipeline knowledge
Skills added: - mcp-api-analyzer (43KB) — Phase 1: API analysis - mcp-server-builder (88KB) — Phase 2: Server build - mcp-server-development (31KB) — TS MCP patterns - mcp-app-designer (85KB) — Phase 3: Visual apps - mcp-apps-integration (20KB) — structuredContent UI - mcp-apps-official (48KB) — MCP Apps SDK - mcp-apps-merged (39KB) — Combined apps reference - mcp-localbosses-integrator (61KB) — Phase 4: LocalBosses wiring - mcp-qa-tester (113KB) — Phase 5: Full QA framework - mcp-deployment (17KB) — Phase 6: Production deploy - mcp-skill (exa integration) These skills are the encoded knowledge that lets agents build production-quality MCP servers autonomously through the pipeline.
This commit is contained in:
parent
f3c4cd817b
commit
8d65417afe
45
skills/README.md
Normal file
45
skills/README.md
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
# MCPEngine Skills
|
||||||
|
|
||||||
|
These are agent skills (SKILL.md files) that guide AI agents through the MCP development pipeline. Each skill encodes the exact process, patterns, and standards for a specific phase of MCP server/app development.
|
||||||
|
|
||||||
|
**These are the "secret sauce" — the encoded knowledge that lets agents build production-quality MCP servers autonomously.**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Pipeline Skills (in order)
|
||||||
|
|
||||||
|
| # | Skill | Size | Purpose |
|
||||||
|
|---|-------|------|---------|
|
||||||
|
| 1 | **mcp-api-analyzer** | 43KB | Analyze API docs → structured analysis doc. Always the FIRST step. |
|
||||||
|
| 2 | **mcp-server-builder** | 88KB | Build a complete MCP server from the analysis doc. Every pattern and template. |
|
||||||
|
| 3 | **mcp-server-development** | 31KB | TypeScript MCP server patterns, best practices, error handling. |
|
||||||
|
| 4 | **mcp-app-designer** | 85KB | Design and build visual HTML apps for each MCP server. |
|
||||||
|
| 5 | **mcp-apps-integration** | 20KB | Add rich UI (structuredContent) to MCP tool results. |
|
||||||
|
| 6 | **mcp-apps-official** | 48KB | Official MCP Apps SDK patterns and host integration. |
|
||||||
|
| 7 | **mcp-apps-merged** | 39KB | Combined/merged MCP Apps reference. |
|
||||||
|
| 8 | **mcp-localbosses-integrator** | 61KB | Wire MCP servers + apps into LocalBosses Next.js app. |
|
||||||
|
| 9 | **mcp-qa-tester** | 113KB | Full QA framework — protocol, visual, functional, live API testing. |
|
||||||
|
| 10 | **mcp-deployment** | 17KB | Package and deploy — Docker, Railway, GitHub, production. |
|
||||||
|
|
||||||
|
## Utility Skills
|
||||||
|
|
||||||
|
| Skill | Purpose |
|
||||||
|
|-------|---------|
|
||||||
|
| **mcp-skill** | Exa MCP integration (web search, deep research) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## How agents use these
|
||||||
|
|
||||||
|
1. Agent receives task (e.g., "build a Stripe MCP server")
|
||||||
|
2. Agent reads `mcp-api-analyzer/SKILL.md` → produces analysis doc
|
||||||
|
3. Agent reads `mcp-server-builder/SKILL.md` → builds the server
|
||||||
|
4. Agent reads `mcp-app-designer/SKILL.md` → builds UI apps
|
||||||
|
5. Agent reads `mcp-qa-tester/SKILL.md` → runs full test suite
|
||||||
|
6. Agent reads `mcp-deployment/SKILL.md` → packages for production
|
||||||
|
|
||||||
|
Each skill is self-contained — an agent can pick up any step independently.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Total encoded knowledge: ~550KB of structured agent instructions
|
||||||
869
skills/mcp-api-analyzer/SKILL.md
Normal file
869
skills/mcp-api-analyzer/SKILL.md
Normal file
@ -0,0 +1,869 @@
|
|||||||
|
# MCP API Analyzer — Phase 1: API Discovery & Analysis
|
||||||
|
|
||||||
|
**When to use this skill:** You have API documentation (URLs, OpenAPI specs, user guides) for a service and need to produce a structured analysis document that feeds into the MCP Factory pipeline. This is always the FIRST step before building anything.
|
||||||
|
|
||||||
|
**What this covers:** Reading API docs efficiently, cataloging endpoints, designing tool groups, naming tools, identifying app candidates, documenting auth flows and rate limits. Output is a single `{service}-api-analysis.md` file.
|
||||||
|
|
||||||
|
**Pipeline position:** Phase 1 of 6 → Output feeds into `mcp-server-builder` (Phase 2) and `mcp-app-designer` (Phase 3)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Inputs
|
||||||
|
|
||||||
|
| Input | Required | Description |
|
||||||
|
|-------|----------|-------------|
|
||||||
|
| API documentation URL(s) | **Yes** | Primary reference docs |
|
||||||
|
| OpenAPI/Swagger spec | Preferred | Machine-readable endpoint catalog |
|
||||||
|
| User guides / tutorials | Nice-to-have | Helps understand real-world usage |
|
||||||
|
| Marketing / pricing page | Nice-to-have | Tier limits, feature gates |
|
||||||
|
| Existing SDK examples | Nice-to-have | Reveals common patterns |
|
||||||
|
|
||||||
|
## 2. Output
|
||||||
|
|
||||||
|
A single file: **`{service}-api-analysis.md`**
|
||||||
|
|
||||||
|
Place it in the workspace root or alongside the future server directory:
|
||||||
|
```
|
||||||
|
~/.clawdbot/workspace/{service}-api-analysis.md
|
||||||
|
```
|
||||||
|
|
||||||
|
This file is the sole input for Phase 2 (server build) and Phase 3 (app design).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. How to Read API Docs Efficiently
|
||||||
|
|
||||||
|
### Step 0: API Style Detection
|
||||||
|
|
||||||
|
**Identify the API style FIRST.** This determines how you read the docs and how tools are designed.
|
||||||
|
|
||||||
|
| Style | Detection Signals | Tool Mapping |
|
||||||
|
|-------|-------------------|--------------|
|
||||||
|
| **REST** | Multiple URL paths, standard HTTP verbs (GET/POST/PUT/DELETE), resource-oriented URLs | 1 endpoint → 1 tool (standard) |
|
||||||
|
| **GraphQL** | Single `/graphql` endpoint, `query`/`mutation` in request body, schema introspection | Queries → read tools, Mutations → write tools, Subscriptions → skip (note for future) |
|
||||||
|
| **SOAP/XML** | WSDL file, XML request/response, `Content-Type: text/xml`, `.asmx` endpoints | Each WSDL operation → 1 tool, note XML→JSON transform needed |
|
||||||
|
| **gRPC** | `.proto` files, binary protocol, service/method definitions | Each RPC method → 1 tool, note HTTP/gRPC gateway if available |
|
||||||
|
| **WebSocket** | `ws://` or `wss://` URLs, persistent connections, event-based messaging | Message types → tools, note connection lifecycle management |
|
||||||
|
|
||||||
|
**Adaptation notes for non-REST APIs:**
|
||||||
|
|
||||||
|
- **GraphQL:** Download the schema (`{ __schema { types { name fields { name } } } }`). Group by query vs mutation. Each meaningful query/mutation becomes a tool. Combine related queries if they share variables. The server's API client sends POST requests with `{ query, variables }` — document the query string per tool.
|
||||||
|
- **SOAP:** Locate the WSDL. Each `<operation>` maps to a tool. Note the SOAPAction header. The server must transform XML responses to JSON — document the response mapping per tool.
|
||||||
|
- **gRPC:** Check for an HTTP/JSON gateway (many gRPC services expose one). If available, treat as REST. If not, the server needs a gRPC client — document the `.proto` service and method names.
|
||||||
|
- **WebSocket:** These are usually event-driven, not request/response. Map "send message" events to write tools. For incoming events, note them for future resource/subscription support. The server must manage a persistent connection.
|
||||||
|
|
||||||
|
### What to READ (priority order):
|
||||||
|
|
||||||
|
1. **Authentication page** — Read FIRST, completely. Auth determines everything.
|
||||||
|
- What type? (OAuth2, API key, JWT, session token, basic auth)
|
||||||
|
- Where does the token go? (Authorization header, query param, cookie)
|
||||||
|
- Token refresh flow? (Expiry, refresh tokens, re-auth)
|
||||||
|
- Scopes/permissions model?
|
||||||
|
|
||||||
|
2. **Rate limits page** — Read SECOND. This constrains tool design.
|
||||||
|
- Requests per minute/hour/day?
|
||||||
|
- Per-endpoint limits vs global limits?
|
||||||
|
- Burst allowance?
|
||||||
|
- Rate limit headers? (X-RateLimit-Remaining, Retry-After)
|
||||||
|
|
||||||
|
3. **API overview / getting started** — Skim for architecture patterns.
|
||||||
|
- REST vs GraphQL vs RPC?
|
||||||
|
- Base URL pattern (versioned? regional?)
|
||||||
|
- Common response envelope (data wrapper, pagination shape)
|
||||||
|
- Error response format
|
||||||
|
|
||||||
|
4. **Endpoint reference** — Systematic scan, don't deep-dive yet.
|
||||||
|
- Group endpoints by resource/domain (contacts, deals, invoices, etc.)
|
||||||
|
- Note HTTP methods per endpoint (GET=read, POST=create, PUT=update, DELETE=delete)
|
||||||
|
- Flag endpoints with complex input (nested objects, file uploads, webhooks)
|
||||||
|
- Count total endpoints per group
|
||||||
|
|
||||||
|
5. **Pagination docs** — Find the pagination pattern.
|
||||||
|
- Cursor-based vs offset-based vs page-based?
|
||||||
|
- What params? (page, limit, offset, cursor, startAfter)
|
||||||
|
- Max page size?
|
||||||
|
- How to detect "no more pages"?
|
||||||
|
|
||||||
|
6. **Webhooks / events** — Note but don't deep-dive.
|
||||||
|
- Available webhook events (for future reference)
|
||||||
|
- Delivery format
|
||||||
|
|
||||||
|
7. **Version & deprecation info** — Check for sunset timelines.
|
||||||
|
- Current stable version
|
||||||
|
- Any deprecated endpoints still in use
|
||||||
|
- Version header requirements (e.g., `API-Version: 2024-01-01`)
|
||||||
|
- Breaking changes in recent versions
|
||||||
|
|
||||||
|
### What to SKIP (or skim very lightly):
|
||||||
|
|
||||||
|
- SDK-specific guides (Python, Ruby, etc.) — we build our own client
|
||||||
|
- UI/dashboard tutorials — we only care about the API
|
||||||
|
- Community forums / blog posts — too noisy
|
||||||
|
- Deprecated endpoints — unless no replacement exists
|
||||||
|
- Webhook setup instructions — we consume the API, not webhooks (usually)
|
||||||
|
|
||||||
|
### Speed technique for large APIs (50+ endpoints):
|
||||||
|
|
||||||
|
1. If OpenAPI spec exists, download it and parse programmatically
|
||||||
|
2. Extract all paths + methods into a spreadsheet/list
|
||||||
|
3. Group by URL prefix (e.g., `/contacts/*`, `/deals/*`, `/invoices/*`)
|
||||||
|
4. Count endpoints per group
|
||||||
|
5. Read the 2-3 most important endpoints per group in detail
|
||||||
|
6. Note the pattern — most groups follow identical CRUD patterns
|
||||||
|
|
||||||
|
### Pagination Pattern Catalog
|
||||||
|
|
||||||
|
Different APIs use different pagination strategies. Identify which pattern(s) the API uses and document per the table below.
|
||||||
|
|
||||||
|
| Pattern | How It Works | Request Next Page | Detect Last Page | Total Count | Example APIs |
|
||||||
|
|---------|-------------|-------------------|------------------|-------------|-------------|
|
||||||
|
| **Offset/Limit** | Skip N records, return M | `?offset=25&limit=25` | Results < limit, or offset ≥ total | Usually available | Most REST APIs |
|
||||||
|
| **Page Number** | Request page N of size M | `?page=2&pageSize=25` | Empty results, or page ≥ totalPages | Usually available | GHL, HubSpot |
|
||||||
|
| **Cursor (opaque)** | Server returns an opaque cursor string | `?cursor=abc123&limit=25` | Cursor is null/absent in response | Rarely available | Slack, Facebook |
|
||||||
|
| **Keyset (Stripe-style)** | Use last item's ID as boundary | `?starting_after=obj_xxx&limit=25` | `has_more: false` in response | Rarely available | Stripe, Intercom |
|
||||||
|
| **Link Header** | Server returns `Link: <url>; rel="next"` in headers | Follow the `rel="next"` URL directly | No `rel="next"` link in response | Sometimes via `rel="last"` | GitHub, many REST APIs |
|
||||||
|
| **Scroll/Search-After** | Server returns a sort-value array to continue from | `?search_after=[timestamp, id]` | Empty results | Via separate count query | Elasticsearch |
|
||||||
|
| **Composite Cursor** | Base64-encoded JSON with multiple sort fields | `?cursor=eyJpZCI6MTIzLCJ...}` | Decoded cursor has `done: true`, or results empty | Rarely available | Internal APIs, GraphQL relay |
|
||||||
|
| **Token-Based (AWS-style)** | Server returns a `NextToken` / `NextContinuationToken` | Pass `NextToken` in next request body/params | `NextToken` is absent in response | Sometimes via separate field | AWS (S3, DynamoDB, SQS) |
|
||||||
|
|
||||||
|
**For each pattern, document:**
|
||||||
|
- How to request the next page
|
||||||
|
- How to detect the last page (no more data)
|
||||||
|
- Whether total count is available
|
||||||
|
- Whether backwards pagination is supported
|
||||||
|
- Max page size allowed
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Analysis Document Template
|
||||||
|
|
||||||
|
Use this EXACT template. Every section is required.
|
||||||
|
|
||||||
|
````markdown
|
||||||
|
# {Service Name} — MCP API Analysis
|
||||||
|
|
||||||
|
**Date:** {YYYY-MM-DD}
|
||||||
|
**API Version:** {version}
|
||||||
|
**Base URL:** `{base_url}`
|
||||||
|
**Documentation:** {docs_url}
|
||||||
|
**OpenAPI Spec:** {spec_url or "Not available"}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Service Overview
|
||||||
|
|
||||||
|
**What it does:** {1-2 sentence description}
|
||||||
|
**Target users:** {Who uses this product}
|
||||||
|
**Pricing tiers:** {Free / Starter / Pro / Enterprise — note API access level per tier}
|
||||||
|
**API access:** {Which tiers include API access, any costs per call}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Authentication
|
||||||
|
|
||||||
|
**Method:** {OAuth2 / API Key / JWT / Basic Auth / Custom}
|
||||||
|
|
||||||
|
### Auth Flow:
|
||||||
|
```
|
||||||
|
{Step-by-step auth flow}
|
||||||
|
1. {First step}
|
||||||
|
2. {Second step}
|
||||||
|
3. {How to get/refresh token}
|
||||||
|
```
|
||||||
|
|
||||||
|
### OAuth2 Details (if applicable):
|
||||||
|
- **Grant type:** {authorization_code / client_credentials / PKCE / device_code}
|
||||||
|
- **Authorization URL:** `{url}`
|
||||||
|
- **Token URL:** `{url}`
|
||||||
|
- **Redirect URI requirements:** {localhost allowed? specific paths?}
|
||||||
|
- **Scopes required:** {list scopes and what they grant}
|
||||||
|
- **PKCE required?** {yes/no — required for public clients}
|
||||||
|
|
||||||
|
### Headers:
|
||||||
|
```
|
||||||
|
Authorization: {Bearer {token} / Basic {base64} / X-API-Key: {key}}
|
||||||
|
Content-Type: application/json
|
||||||
|
{Any other required headers, e.g., X-Account-ID}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Environment Variables Needed:
|
||||||
|
```bash
|
||||||
|
{SERVICE}_API_KEY=
|
||||||
|
{SERVICE}_API_SECRET= # If OAuth2
|
||||||
|
{SERVICE}_BASE_URL= # If configurable/sandbox
|
||||||
|
{SERVICE}_ACCOUNT_ID= # If multi-tenant
|
||||||
|
```
|
||||||
|
|
||||||
|
### Token Lifecycle:
|
||||||
|
- **Token type:** {access token / API key / JWT}
|
||||||
|
- **Expiry:** {duration or "never" for API keys}
|
||||||
|
- **Refresh mechanism:** {refresh token endpoint / re-auth / N/A}
|
||||||
|
- **Refresh token expiry:** {duration or "never"}
|
||||||
|
- **Caching strategy:** {Cache token, refresh 5 min before expiry}
|
||||||
|
- **Storage for long-running server:** {Token stored in memory, refresh before expiry. For OAuth2 auth code flow: initial token obtained via browser flow, server stores refresh token and auto-refreshes.}
|
||||||
|
|
||||||
|
### Key Rotation / Compromise:
|
||||||
|
- **Rotation procedure:** {How to generate new keys/secrets}
|
||||||
|
- **Revocation endpoint:** {URL to revoke compromised tokens, or "manual via dashboard"}
|
||||||
|
- **Grace period:** {Does old key continue working after rotation? For how long?}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. API Patterns
|
||||||
|
|
||||||
|
**Style:** {REST / GraphQL / SOAP / gRPC / WebSocket}
|
||||||
|
**Non-REST adaptation notes:** {If non-REST, note how tools map — see API Style Detection above}
|
||||||
|
**Response envelope:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"data": [...],
|
||||||
|
"meta": { "total": 100, "page": 1, "pageSize": 25 }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Pagination:**
|
||||||
|
- **Type:** {cursor / offset / page-based / keyset / link-header / token-based}
|
||||||
|
- **Parameters:** {page, pageSize / limit, offset / cursor, limit / starting_after}
|
||||||
|
- **Max page size:** {number}
|
||||||
|
- **End detection:** {empty array / hasMore field / next cursor is null / no Link rel="next"}
|
||||||
|
- **Total count available:** {yes — in meta.total / no / separate count endpoint}
|
||||||
|
- **Backwards pagination:** {supported / not supported}
|
||||||
|
|
||||||
|
**Error format:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"error": { "code": "NOT_FOUND", "message": "Resource not found" }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Rate limits:**
|
||||||
|
- **Global:** {X requests per Y}
|
||||||
|
- **Per-endpoint:** {Any specific limits}
|
||||||
|
- **Burst allowance:** {Token bucket / leaky bucket / simple counter}
|
||||||
|
- **Rate limit scope:** {per-key / per-endpoint / per-user}
|
||||||
|
- **Exceeded penalty:** {429 response / temporary ban / throttled response}
|
||||||
|
- **Headers:** {X-RateLimit-Remaining, Retry-After}
|
||||||
|
- **Strategy:** {Exponential backoff / fixed delay / queue}
|
||||||
|
|
||||||
|
**Sandbox / Test Environment:**
|
||||||
|
- **Available:** {yes / no}
|
||||||
|
- **Sandbox base URL:** `{sandbox_url or "N/A"}`
|
||||||
|
- **How to access:** {Separate API key / toggle in dashboard / different subdomain}
|
||||||
|
- **Limitations:** {Rate limits differ? Data resets? Feature parity with production?}
|
||||||
|
- **QA impact:** {Can QA use sandbox for live API testing? Any endpoints unavailable in sandbox?}
|
||||||
|
|
||||||
|
> **Why this matters:** If a sandbox exists, QA testing (Phase 5) can run against it safely without affecting production data. If no sandbox, QA must use mocks or test carefully with real data. Document this early — it directly affects the testing strategy.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Version & Deprecation
|
||||||
|
|
||||||
|
- **Current stable version:** {e.g., v2, 2024-01-01}
|
||||||
|
- **Version mechanism:** {URL path (/v2/), header (API-Version: 2024-01-01), query param}
|
||||||
|
- **Version header requirements:** {Required header name and format, if any}
|
||||||
|
- **Deprecation timeline:** {Any endpoints or versions being sunset — with dates}
|
||||||
|
- **Breaking changes in recent versions:** {Notable changes that affect tool design}
|
||||||
|
- **Changelog URL:** {Link to changelog/migration guide for reference}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Endpoint Catalog
|
||||||
|
|
||||||
|
### Group: {Domain Name} ({count} endpoints)
|
||||||
|
|
||||||
|
| Method | Path | Description | Notes |
|
||||||
|
|--------|------|-------------|-------|
|
||||||
|
| GET | `/resource` | List resources | Paginated, filterable |
|
||||||
|
| GET | `/resource/{id}` | Get single resource | |
|
||||||
|
| POST | `/resource` | Create resource | Required: name, email |
|
||||||
|
| PUT | `/resource/{id}` | Update resource | Partial update supported |
|
||||||
|
| DELETE | `/resource/{id}` | Delete resource | Soft delete |
|
||||||
|
|
||||||
|
{Repeat for each domain group}
|
||||||
|
|
||||||
|
### Group: {Next Domain} ({count} endpoints)
|
||||||
|
...
|
||||||
|
|
||||||
|
**Total endpoints:** {count}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Tool Groups (for Lazy Loading)
|
||||||
|
|
||||||
|
Tools are organized into groups that load on-demand. Each group maps to a domain.
|
||||||
|
|
||||||
|
| Group Name | Tools | Load Trigger | Description |
|
||||||
|
|------------|-------|--------------|-------------|
|
||||||
|
| `contacts` | {count} | User asks about contacts | Contact CRUD, search, tags |
|
||||||
|
| `deals` | {count} | User asks about deals/pipeline | Deal management, stages |
|
||||||
|
| `invoicing` | {count} | User asks about invoices/payments | Invoice CRUD, payments |
|
||||||
|
| `calendar` | {count} | User asks about scheduling | Appointments, availability |
|
||||||
|
| `analytics` | {count} | User asks for reports/metrics | Dashboards, KPIs |
|
||||||
|
| `admin` | {count} | User asks about settings/config | Users, permissions, webhooks |
|
||||||
|
|
||||||
|
**Target:** 5-15 groups, 3-15 tools per group. No group should exceed 20 tools.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Tool Inventory
|
||||||
|
|
||||||
|
### Group: {group_name}
|
||||||
|
|
||||||
|
#### `list_{resources}`
|
||||||
|
- **Title:** List {Resources}
|
||||||
|
- **Icon:** `{service-cdn-url}/list-icon.svg` *(or omit if no suitable icon — SVG preferred)*
|
||||||
|
- **Description:** List {resources} with optional filters and pagination. Returns `{key_field_1, key_field_2, key_field_3, status}` for each {resource}. Use when the user wants to browse, filter, or get an overview of multiple {resources}. Do NOT use when searching by specific keyword (use `search_{resources}` instead) or for getting full details of one {resource} (use `get_{resource}` instead).
|
||||||
|
- **HTTP:** GET `/resource`
|
||||||
|
- **Annotations:** `readOnlyHint: true`, `destructiveHint: false`, `idempotentHint: true`, `openWorldHint: false`
|
||||||
|
- **Parameters:**
|
||||||
|
| Param | Type | Required | Description |
|
||||||
|
|-------|------|----------|-------------|
|
||||||
|
| page | number | No | Page number (default 1) |
|
||||||
|
| pageSize | number | No | Results per page (default 25, max 100) |
|
||||||
|
| query | string | No | Search by name, email, or phone |
|
||||||
|
| status | string | No | Filter: active, inactive, all |
|
||||||
|
| sortBy | string | No | Sort field: created, updated, name |
|
||||||
|
- **Output Schema:** `{ data: Resource[], meta: { total: number, page: number, pageSize: number } }`
|
||||||
|
- **Content Annotations:** `audience: ["user", "assistant"]`, `priority: 0.7`
|
||||||
|
- **Response shape:** `{ data: Resource[], meta: { total, page, pageSize } }`
|
||||||
|
|
||||||
|
#### `get_{resource}`
|
||||||
|
- **Title:** Get {Resource} Details
|
||||||
|
- **Icon:** `{service-cdn-url}/detail-icon.svg` *(optional)*
|
||||||
|
- **Description:** Get complete details for a single {resource} by ID. Returns all fields including `{notable_field_1, notable_field_2, notable_field_3}`. Use when the user references a specific {resource} by name/ID or needs detailed information about one {resource}. Do NOT use when the user wants to browse multiple {resources} (use `list_{resources}` instead).
|
||||||
|
- **HTTP:** GET `/resource/{id}`
|
||||||
|
- **Annotations:** `readOnlyHint: true`, `destructiveHint: false`, `idempotentHint: true`, `openWorldHint: false`
|
||||||
|
- **Parameters:**
|
||||||
|
| Param | Type | Required | Description |
|
||||||
|
|-------|------|----------|-------------|
|
||||||
|
| {resource}_id | string | **Yes** | {Resource} ID |
|
||||||
|
- **Output Schema:** `Resource` (full object with all fields)
|
||||||
|
- **Content Annotations:** `audience: ["user"]`, `priority: 0.8`
|
||||||
|
- **Response shape:** `Resource`
|
||||||
|
|
||||||
|
#### `create_{resource}`
|
||||||
|
- **Title:** Create New {Resource}
|
||||||
|
- **Icon:** `{service-cdn-url}/create-icon.svg` *(optional)*
|
||||||
|
- **Description:** Create a new {resource}. Returns the created {resource} with its assigned ID. Use when the user wants to add, create, or set up a new {resource}. Do NOT use when updating an existing {resource} (use `update_{resource}` instead). Side effect: creates a permanent record in the system.
|
||||||
|
- **HTTP:** POST `/resource`
|
||||||
|
- **Annotations:** `readOnlyHint: false`, `destructiveHint: false`, `idempotentHint: false`, `openWorldHint: false`
|
||||||
|
- **Parameters:**
|
||||||
|
| Param | Type | Required | Description |
|
||||||
|
|-------|------|----------|-------------|
|
||||||
|
| name | string | **Yes** | {Resource} name |
|
||||||
|
| email | string | No | Email address |
|
||||||
|
| {etc.} | | | |
|
||||||
|
- **Output Schema:** `Resource` (created object with ID)
|
||||||
|
- **Content Annotations:** `audience: ["user"]`, `priority: 0.9`
|
||||||
|
- **Response shape:** `Resource`
|
||||||
|
|
||||||
|
#### `update_{resource}`
|
||||||
|
- **Title:** Update {Resource}
|
||||||
|
- **Icon:** `{service-cdn-url}/edit-icon.svg` *(optional)*
|
||||||
|
- **Description:** Update an existing {resource}. Only include fields to change — omitted fields remain unchanged. Returns the updated {resource}. Use when the user wants to modify, change, or edit a {resource}. Do NOT use when creating a new {resource} (use `create_{resource}` instead). Side effect: modifies the existing record.
|
||||||
|
- **HTTP:** PUT `/resource/{id}`
|
||||||
|
- **Annotations:** `readOnlyHint: false`, `destructiveHint: false`, `idempotentHint: true`, `openWorldHint: false`
|
||||||
|
- **Parameters:**
|
||||||
|
| Param | Type | Required | Description |
|
||||||
|
|-------|------|----------|-------------|
|
||||||
|
| {resource}_id | string | **Yes** | {Resource} ID |
|
||||||
|
| {fields...} | | No | Fields to update |
|
||||||
|
- **Output Schema:** `Resource` (updated object)
|
||||||
|
- **Content Annotations:** `audience: ["user"]`, `priority: 0.9`
|
||||||
|
- **Response shape:** `Resource`
|
||||||
|
|
||||||
|
#### `delete_{resource}`
|
||||||
|
- **Title:** Delete {Resource}
|
||||||
|
- **Icon:** `{service-cdn-url}/delete-icon.svg` *(optional)*
|
||||||
|
- **Description:** Delete a {resource} permanently. This cannot be undone. Use only when the user explicitly asks to delete or remove a {resource}. Do NOT use for archiving, deactivating, or hiding (use `update_{resource}` with status change instead, if available). Side effect: permanently removes the record.
|
||||||
|
- **HTTP:** DELETE `/resource/{id}`
|
||||||
|
- **Annotations:** `readOnlyHint: false`, `destructiveHint: true`, `idempotentHint: true`, `openWorldHint: false`
|
||||||
|
- **Parameters:**
|
||||||
|
| Param | Type | Required | Description |
|
||||||
|
|-------|------|----------|-------------|
|
||||||
|
| {resource}_id | string | **Yes** | {Resource} ID |
|
||||||
|
- **Output Schema:** `{ success: boolean }`
|
||||||
|
- **Content Annotations:** `audience: ["user"]`, `priority: 1.0`
|
||||||
|
- **Response shape:** `{ success: true }`
|
||||||
|
|
||||||
|
{Repeat for each tool in each group}
|
||||||
|
|
||||||
|
### Disambiguation Table (per group)
|
||||||
|
|
||||||
|
For each tool group, produce a disambiguation matrix to guide tool routing:
|
||||||
|
|
||||||
|
| User says... | Correct tool | Why not others |
|
||||||
|
|---|---|---|
|
||||||
|
| "Show me all {resources}" | `list_{resources}` | Not `search_` (no keyword), not `get_` (not one specific item) |
|
||||||
|
| "Find {name}" | `search_{resources}` | Not `list_` (specific name = search), not `get_` (no ID provided) |
|
||||||
|
| "What's {name}'s email?" | `get_{resource}` | Not `list_`/`search_` (asking about a specific known {resource}) |
|
||||||
|
| "Add a new {resource}" | `create_{resource}` | Not `update_` (new, not existing) |
|
||||||
|
| "Change {name}'s phone number" | `update_{resource}` | Not `create_` (modifying existing) |
|
||||||
|
| "Remove {name}" | `delete_{resource}` | Not `update_` (user said remove/delete, not deactivate) |
|
||||||
|
|
||||||
|
### Common User Intent Clustering
|
||||||
|
|
||||||
|
For each disambiguation entry, consider **diverse phrasings** real users would type. Cluster by intent to ensure the tool description handles all variants:
|
||||||
|
|
||||||
|
| Intent | Common Phrasings | Target Tool |
|
||||||
|
|--------|-----------------|-------------|
|
||||||
|
| Browse/overview | "show me", "list", "what are my", "pull up", "let me see", "give me all" | `list_{resources}` |
|
||||||
|
| Search/find | "find", "search for", "look up", "where is", "do I have a" | `search_{resources}` |
|
||||||
|
| Detail/inspect | "tell me about", "what's the status of", "show me details for", "more info on" | `get_{resource}` |
|
||||||
|
| Create/add | "add", "create", "new", "set up", "register", "make a" | `create_{resource}` |
|
||||||
|
| Modify/edit | "change", "update", "edit", "modify", "fix", "set X to Y" | `update_{resource}` |
|
||||||
|
| Remove/delete | "delete", "remove", "get rid of", "cancel", "drop" | `delete_{resource}` |
|
||||||
|
|
||||||
|
> **Tip:** When writing tool descriptions, ensure the "When to use" clause covers the most common phrasings for that intent. The "When NOT to use" clause should address the top misrouting risk (e.g., `list_` vs `search_` is the most common confusion).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. App Candidates
|
||||||
|
|
||||||
|
### Dashboard Apps
|
||||||
|
| App ID | Name | Data Source Tools | Description |
|
||||||
|
|--------|------|-------------------|-------------|
|
||||||
|
| `{svc}-dashboard` | {Service} Dashboard | `get_analytics`, `list_*` | Overview KPIs, recent activity |
|
||||||
|
|
||||||
|
### Data Grid Apps
|
||||||
|
| App ID | Name | Data Source Tools | Description |
|
||||||
|
|--------|------|-------------------|-------------|
|
||||||
|
| `{svc}-contact-grid` | Contacts | `list_contacts`, `search_contacts` | Searchable contact list |
|
||||||
|
|
||||||
|
### Detail Card Apps
|
||||||
|
| App ID | Name | Data Source Tools | Description |
|
||||||
|
|--------|------|-------------------|-------------|
|
||||||
|
| `{svc}-contact-card` | Contact Card | `get_contact` | Single contact deep-dive |
|
||||||
|
|
||||||
|
### Form/Wizard Apps
|
||||||
|
| App ID | Name | Data Source Tools | Description |
|
||||||
|
|--------|------|-------------------|-------------|
|
||||||
|
| `{svc}-contact-creator` | New Contact | `create_contact` | Contact creation form |
|
||||||
|
|
||||||
|
### Specialized Apps
|
||||||
|
| App ID | Name | Type | Data Source Tools | Description |
|
||||||
|
|--------|------|------|-------------------|-------------|
|
||||||
|
| `{svc}-calendar` | Calendar | calendar | `list_appointments` | Appointment calendar |
|
||||||
|
| `{svc}-pipeline` | Pipeline | funnel | `list_deals` | Deal pipeline kanban |
|
||||||
|
| `{svc}-timeline` | Activity | timeline | `get_activity` | Activity feed |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Elicitation Candidates
|
||||||
|
|
||||||
|
Identify flows where the MCP server should request user input mid-operation using the MCP Elicitation capability (`elicitation/create`). These are interactions where the server needs information or confirmation from the user before proceeding.
|
||||||
|
|
||||||
|
### When to flag a flow for elicitation:
|
||||||
|
|
||||||
|
- **OAuth account selection** — API supports multiple connected accounts; server needs user to choose which one
|
||||||
|
- **Destructive operation confirmation** — DELETE or irreversible actions should confirm before executing
|
||||||
|
- **Ambiguous input resolution** — User says "delete the contact" but there are 3 matches; server asks which one
|
||||||
|
- **Multi-step wizards** — Creating a complex resource that requires sequential input (e.g., create event → pick calendar → set time → invite attendees)
|
||||||
|
- **Scope/permission escalation** — Action requires additional OAuth scopes the user hasn't granted
|
||||||
|
- **Payment/billing actions** — Any action that costs money should confirm amount and target
|
||||||
|
|
||||||
|
### Elicitation Candidate Template:
|
||||||
|
|
||||||
|
| Flow | Trigger | Elicitation Type | User Input Needed | Fallback (if elicitation unsupported) |
|
||||||
|
|------|---------|-----------------|--------------------|-----------------------------------------|
|
||||||
|
| Delete {resource} | `delete_{resource}` called | Confirmation | "Confirm delete {name}? (yes/no)" | Return warning text, require second call |
|
||||||
|
| Connect account | First API call with OAuth | Selection | "Which account? (list options)" | Use default/first account |
|
||||||
|
| Bulk action | `bulk_update` with >10 items | Confirmation | "Update {N} records? (yes/no)" | Cap at 10, warn about limit |
|
||||||
|
| {Describe flow} | {What triggers it} | {Confirmation / Selection / Form} | {What the user sees} | {What happens if client doesn't support elicitation} |
|
||||||
|
|
||||||
|
**Important:** Always plan a fallback for clients that don't support elicitation. The server should still function — it just may require the user to provide the information in their original message or via a follow-up tool call.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. Task Candidates (Async Operations)
|
||||||
|
|
||||||
|
Identify tools where the operation may take >10 seconds and should be executed asynchronously using MCP Tasks (spec 2025-11-25, experimental SEP-1686).
|
||||||
|
|
||||||
|
### When to flag a tool for async/task support:
|
||||||
|
- **Report generation** — compiling analytics, PDFs, exports
|
||||||
|
- **Bulk operations** — updating 100+ records, mass imports
|
||||||
|
- **External processing** — waiting on third-party webhooks, payment processing
|
||||||
|
- **Data migration** — moving large datasets between systems
|
||||||
|
- **File generation** — creating CSVs, spreadsheets, archives
|
||||||
|
|
||||||
|
### Task Candidate Template:
|
||||||
|
|
||||||
|
| Tool | Typical Duration | Task Support | Recommended Polling Interval |
|
||||||
|
|------|-----------------|-------------|------------------------------|
|
||||||
|
| `export_report` | 30-120s | required | 5000ms |
|
||||||
|
| `bulk_update` | 10-60s | optional | 3000ms |
|
||||||
|
| `generate_invoice_pdf` | 5-15s | optional | 2000ms |
|
||||||
|
| `{tool_name}` | {duration} | {required/optional/forbidden} | {interval} |
|
||||||
|
|
||||||
|
> **Note:** Most tools should be `forbidden` for task support — only flag tools that genuinely need async execution. If the operation completes in <5 seconds, don't use tasks.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. Data Shape Contracts
|
||||||
|
|
||||||
|
For each app candidate, define the exact mapping from tool `outputSchema` to what the app's `render()` function expects. This contract prevents silent data shape mismatches.
|
||||||
|
|
||||||
|
### Contract Template:
|
||||||
|
|
||||||
|
| App | Source Tool | Tool outputSchema Key Fields | App Expected Fields | Transform Notes |
|
||||||
|
|-----|------------|------------------------------|---------------------|-----------------|
|
||||||
|
| `{svc}-contact-grid` | `list_contacts` | `data[].{name,email,status}`, `meta.{total,page,pageSize}` | `data[].{name,email,status}`, `meta.{total,page,pageSize}` | Direct pass-through |
|
||||||
|
| `{svc}-dashboard` | `get_analytics` | `{revenue,contacts,deals}` | `metrics.{revenue,contacts,deals}`, `recent[]` | LLM restructures into metrics + recent |
|
||||||
|
| `{svc}-{type}` | `{tool}` | `{fields}` | `{fields}` | `{notes}` |
|
||||||
|
|
||||||
|
### Contract Rules:
|
||||||
|
1. **Direct pass-through** — When tool output matches app input exactly. Preferred.
|
||||||
|
2. **LLM transform** — When the LLM must restructure data (via APP_DATA). Document the mapping explicitly so system prompts can reference it.
|
||||||
|
3. **Aggregation** — When an app needs data from multiple tools. List all source tools and how their outputs combine.
|
||||||
|
|
||||||
|
### Validation:
|
||||||
|
- The builder should set `outputSchema` to match the contract
|
||||||
|
- The designer should set `validateData()` to check for the contracted fields
|
||||||
|
- The integrator's `systemPromptAddon` should reference these contracts for APP_DATA generation
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 12. Naming Conventions
|
||||||
|
|
||||||
|
### Tool names: `{verb}_{noun}`
|
||||||
|
- `list_contacts`, `get_contact`, `create_contact`, `update_contact`, `delete_contact`
|
||||||
|
- `search_contacts` (if separate from list)
|
||||||
|
- `send_message`, `schedule_appointment`, `export_report`
|
||||||
|
|
||||||
|
### Semantic Clustering — Verb Prefix Conventions
|
||||||
|
|
||||||
|
Use consistent verb prefixes to signal intent. This helps the LLM distinguish between tools with related names and reduces misrouting.
|
||||||
|
|
||||||
|
| Prefix | Intent | Maps to HTTP | Examples |
|
||||||
|
|--------|--------|-------------|----------|
|
||||||
|
| `browse_` or `list_` | List/overview of multiple items | GET (collection) | `list_contacts`, `browse_invoices` |
|
||||||
|
| `inspect_` or `get_` | Deep-dive into a single item | GET (single) | `get_contact`, `inspect_deal` |
|
||||||
|
| `modify_` or `create_` / `update_` | Create or change a resource | POST / PUT | `create_contact`, `update_deal` |
|
||||||
|
| `remove_` or `delete_` | Delete a resource | DELETE | `delete_contact`, `remove_tag` |
|
||||||
|
| `search_` | Full-text or keyword search | GET (with query) | `search_contacts` |
|
||||||
|
| `send_` | Dispatch a message/notification | POST (side effect) | `send_email`, `send_sms` |
|
||||||
|
| `export_` | Generate a report/file | GET or POST | `export_report` |
|
||||||
|
|
||||||
|
**Guidelines:**
|
||||||
|
- Pick ONE prefix style per server and be consistent (either `list_`/`get_` or `browse_`/`inspect_`, not both)
|
||||||
|
- The standard `list_`/`get_`/`create_`/`update_`/`delete_` is recommended for most APIs
|
||||||
|
- Use `browse_`/`inspect_`/`modify_`/`remove_` only if you need to avoid ambiguity with existing tool names or if the API's language uses these verbs naturally
|
||||||
|
- For mutually exclusive tools, add "INSTEAD OF" notes in descriptions (e.g., "Use `search_contacts` INSTEAD OF `list_contacts` when the user provides a keyword")
|
||||||
|
|
||||||
|
### App IDs: `{service}-{type}-{optional-qualifier}`
|
||||||
|
- `{svc}-dashboard`, `{svc}-contact-grid`, `{svc}-contact-card`
|
||||||
|
- `{svc}-pipeline-kanban`, `{svc}-calendar-view`, `{svc}-activity-timeline`
|
||||||
|
|
||||||
|
### Tool group names: lowercase, domain-based
|
||||||
|
- `contacts`, `deals`, `invoicing`, `calendar`, `analytics`, `admin`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 13. Quirks & Gotchas
|
||||||
|
|
||||||
|
{List any API-specific issues discovered during analysis}
|
||||||
|
|
||||||
|
- {e.g., "Delete endpoint returns 200 with empty body, not 204"}
|
||||||
|
- {e.g., "Pagination starts at 0, not 1"}
|
||||||
|
- {e.g., "Date fields use Unix timestamps, not ISO 8601"}
|
||||||
|
- {e.g., "Rate limit resets at midnight UTC, not rolling window"}
|
||||||
|
- {e.g., "Sandbox environment has different base URL"}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 14. Implementation Priority
|
||||||
|
|
||||||
|
### Phase 1 (Core — build first):
|
||||||
|
1. {most-used-group} — {why}
|
||||||
|
2. {second-group} — {why}
|
||||||
|
|
||||||
|
### Phase 2 (Important — build second):
|
||||||
|
3. {third-group} — {why}
|
||||||
|
4. {fourth-group} — {why}
|
||||||
|
|
||||||
|
### Phase 3 (Nice-to-have — build if time):
|
||||||
|
5. {remaining-groups}
|
||||||
|
|
||||||
|
### App Priority:
|
||||||
|
1. {svc}-dashboard — Always build the dashboard first
|
||||||
|
2. {svc}-{most-used-grid} — Most common data view
|
||||||
|
3. {svc}-{most-used-detail} — Detail for most common entity
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Tool Description Best Practices
|
||||||
|
|
||||||
|
Tool descriptions are the #1 factor in whether an LLM correctly routes to the right tool. Follow these rules:
|
||||||
|
|
||||||
|
### The Description Formula (6-part):
|
||||||
|
|
||||||
|
```
|
||||||
|
{What it does}. {What it returns — include 2-3 key field names}.
|
||||||
|
{When to use it — specific user intents}. {When NOT to use it — disambiguation}.
|
||||||
|
{Side effects — if any}.
|
||||||
|
```
|
||||||
|
|
||||||
|
Every tool description MUST include the "When NOT to use" clause. Research shows this single addition reduces tool misrouting by ~30%.
|
||||||
|
|
||||||
|
### Before/After Example:
|
||||||
|
|
||||||
|
**❌ BEFORE (too vague, no disambiguation):**
|
||||||
|
```
|
||||||
|
"List contacts with optional filters. Returns paginated results including name, email, phone,
|
||||||
|
and status. Use when the user wants to see, search, or browse their contact list."
|
||||||
|
```
|
||||||
|
|
||||||
|
**✅ AFTER (specific, disambiguated, actionable):**
|
||||||
|
```
|
||||||
|
"List contacts with optional filters and pagination. Returns {name, email, phone, status,
|
||||||
|
created_date} for each contact, plus {total, page, pageSize} metadata. Use when the user
|
||||||
|
wants to browse, filter, or get an overview of multiple contacts. Do NOT use when searching
|
||||||
|
by specific keyword (use search_contacts instead) or for getting full details of one contact
|
||||||
|
(use get_contact instead). Read-only, no side effects."
|
||||||
|
```
|
||||||
|
|
||||||
|
### For similar tools, differentiate clearly:
|
||||||
|
```
|
||||||
|
list_contacts: "...browse, filter, or get an overview of multiple contacts.
|
||||||
|
Do NOT use when searching by keyword (use search_contacts) or looking up one contact (use get_contact)."
|
||||||
|
search_contacts: "...full-text search across all contact fields by keyword.
|
||||||
|
Do NOT use when browsing without a search term (use list_contacts) or when the user has a specific ID (use get_contact)."
|
||||||
|
get_contact: "...get complete details for one contact by ID.
|
||||||
|
Do NOT use when the user wants multiple contacts (use list_contacts) or is searching by name (use search_contacts)."
|
||||||
|
```
|
||||||
|
|
||||||
|
### Token Budget Awareness
|
||||||
|
|
||||||
|
Tool descriptions consume context window tokens. Every tool definition averages 50-200 tokens depending on schema complexity. With 50+ tools, this is 10,000+ tokens before any work begins.
|
||||||
|
|
||||||
|
**Targets:**
|
||||||
|
- **Total tool definition tokens per server:** Under 5,000 tokens
|
||||||
|
- **Per-tool target:** ~200 tokens (description + schema combined)
|
||||||
|
- **Active tools per interaction:** Cap at 15-20 via lazy loading
|
||||||
|
|
||||||
|
**Optimization techniques:**
|
||||||
|
- Be concise — every word must earn its place
|
||||||
|
- Eliminate redundant descriptions between the tool description and parameter descriptions
|
||||||
|
- Use field name lists (`{name, email, phone}`) instead of prose descriptions of return values
|
||||||
|
- Combine overlapping tools when the distinction is minor (e.g., `list_contacts` with optional `query` param instead of separate `list_contacts` + `search_contacts`)
|
||||||
|
|
||||||
|
### Tool Count Optimization
|
||||||
|
|
||||||
|
If a tool group exceeds 15 tools, consider combining:
|
||||||
|
|
||||||
|
| Instead of... | Combine into... | How |
|
||||||
|
|---------------|-----------------|-----|
|
||||||
|
| `list_contacts` + `search_contacts` | `list_contacts` with optional `query` param | Add `query` as optional filter |
|
||||||
|
| `get_contact_email` + `get_contact_phone` + `get_contact_address` | `get_contact` (returns all fields) | Single tool, all fields returned |
|
||||||
|
| `create_contact` + `create_lead` + `create_prospect` | `create_contact` with `type` param | Use enum parameter for type |
|
||||||
|
| `get_report_daily` + `get_report_weekly` + `get_report_monthly` | `get_report` with `period` param | Use enum parameter for period |
|
||||||
|
|
||||||
|
**Rule of thumb:** If two tools share >80% of their parameters and the same endpoint pattern, they should be one tool with a distinguishing parameter.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. MCP Annotation Rules
|
||||||
|
|
||||||
|
Every tool MUST have annotations. Use this decision tree:
|
||||||
|
|
||||||
|
```
|
||||||
|
Is it a GET/read operation?
|
||||||
|
→ readOnlyHint: true, destructiveHint: false
|
||||||
|
|
||||||
|
Is it a DELETE operation?
|
||||||
|
→ readOnlyHint: false, destructiveHint: true
|
||||||
|
|
||||||
|
Is it a POST/create operation?
|
||||||
|
→ readOnlyHint: false, destructiveHint: false, idempotentHint: false
|
||||||
|
|
||||||
|
Is it a PUT/upsert operation?
|
||||||
|
→ readOnlyHint: false, destructiveHint: false, idempotentHint: true
|
||||||
|
|
||||||
|
Does it affect external systems outside this API?
|
||||||
|
→ openWorldHint: true (rare — most API tools are openWorldHint: false)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Content Annotations Planning
|
||||||
|
|
||||||
|
MCP content blocks can carry `audience` and `priority` annotations that control how tool outputs are routed. Plan these during analysis — they feed directly into the server builder.
|
||||||
|
|
||||||
|
### Audience Annotation:
|
||||||
|
- `["user"]` — Output is for the end user (show in UI/app, don't feed back to LLM for reasoning)
|
||||||
|
- `["assistant"]` — Output is for the LLM (feed into context for multi-step reasoning, don't show to user)
|
||||||
|
- `["user", "assistant"]` — Both (show to user AND available for LLM reasoning — the default)
|
||||||
|
|
||||||
|
### Priority Annotation (0.0 to 1.0):
|
||||||
|
- `1.0` — Critical, always show prominently (destructive operation results, errors, confirmations)
|
||||||
|
- `0.7-0.9` — Important, show normally (most tool results)
|
||||||
|
- `0.3-0.6` — Supplementary, can be collapsed/summarized (metadata, pagination info)
|
||||||
|
- `0.0-0.2` — Low priority, assistant-only (debug info, internal state)
|
||||||
|
|
||||||
|
### Planning Guidelines:
|
||||||
|
|
||||||
|
| Tool Type | Audience | Priority | Rationale |
|
||||||
|
|-----------|----------|----------|-----------|
|
||||||
|
| `list_*` | `["user", "assistant"]` | 0.7 | User sees data, LLM may use for follow-up |
|
||||||
|
| `get_*` | `["user"]` | 0.8 | Primarily for user display |
|
||||||
|
| `create_*` / `update_*` | `["user"]` | 0.9 | User needs confirmation of changes |
|
||||||
|
| `delete_*` | `["user"]` | 1.0 | Critical — user must see result |
|
||||||
|
| `search_*` | `["user", "assistant"]` | 0.7 | User sees results, LLM may refine |
|
||||||
|
| Analytics/aggregation | `["user"]` | 0.8 | Dashboard-type data, primarily visual |
|
||||||
|
| Internal/helper tools | `["assistant"]` | 0.3 | LLM uses for reasoning, user doesn't need to see |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. App Candidate Selection Criteria
|
||||||
|
|
||||||
|
Not every endpoint deserves an app. Use this checklist:
|
||||||
|
|
||||||
|
### BUILD an app when:
|
||||||
|
- ✅ The data is a **list** that benefits from search/filter UI (data grid)
|
||||||
|
- ✅ The data is **complex** with many fields (detail card)
|
||||||
|
- ✅ There are **aggregate metrics** or KPIs (dashboard)
|
||||||
|
- ✅ The data is **date-based** and benefits from calendar layout (calendar)
|
||||||
|
- ✅ The data has **stages/phases** (funnel/kanban)
|
||||||
|
- ✅ The data is **chronological events** (timeline)
|
||||||
|
- ✅ There's a **multi-step creation flow** (form/wizard)
|
||||||
|
|
||||||
|
### SKIP an app when:
|
||||||
|
- ❌ It's a simple CRUD with 2-3 fields (just use the tool directly)
|
||||||
|
- ❌ The response is a simple success/fail (no visual benefit)
|
||||||
|
- ❌ It's a settings/config endpoint (rarely needed in UI)
|
||||||
|
- ❌ It's a batch/background operation (status check is enough)
|
||||||
|
|
||||||
|
### App count targets:
|
||||||
|
- **Small API (10-20 endpoints):** 3-5 apps
|
||||||
|
- **Medium API (20-50 endpoints):** 5-10 apps
|
||||||
|
- **Large API (50+ endpoints):** 10-20 apps
|
||||||
|
- **Never exceed 25 apps** for a single service — diminishing returns
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Quality Gate Checklist
|
||||||
|
|
||||||
|
Before passing the analysis doc to Phase 2, verify:
|
||||||
|
|
||||||
|
### Core Completeness:
|
||||||
|
- [ ] **API style identified** — REST/GraphQL/SOAP/gRPC/WebSocket documented with adaptation notes if non-REST
|
||||||
|
- [ ] **Every endpoint is cataloged** — no missing endpoints from the API reference
|
||||||
|
- [ ] **Tool groups are balanced** — no group with 50+ tools, aim for 3-15 per group
|
||||||
|
- [ ] **Active tool count is manageable** — total tools ≤ 60, each lazy-loaded group ≤ 20, active per interaction ≤ 15-20
|
||||||
|
|
||||||
|
### Tool Quality:
|
||||||
|
- [ ] **Tool descriptions follow 6-part formula** — What / Returns (field names) / When to use / When NOT to use / Side effects
|
||||||
|
- [ ] **Every tool has a `title` field** — Human-readable display name separate from machine name
|
||||||
|
- [ ] **Every tool has an `outputSchema` planned** — Expected response structure documented
|
||||||
|
- [ ] **Every tool has annotations planned** — readOnlyHint, destructiveHint, idempotentHint, openWorldHint
|
||||||
|
- [ ] **Content annotations planned** — audience and priority assigned per tool type
|
||||||
|
- [ ] **Disambiguation tables exist** — For each tool group with similar tools, "User says X → Correct tool → Why not others"
|
||||||
|
- [ ] **Semantic verb prefixes are consistent** — list_/get_/create_/update_/delete_ (or chosen alternative) used uniformly
|
||||||
|
|
||||||
|
### Auth & Infrastructure:
|
||||||
|
- [ ] **Auth flow is complete** — Step-by-step, env vars listed, refresh strategy documented
|
||||||
|
- [ ] **OAuth2 subtype identified** — If OAuth2: grant type, PKCE, scopes, token lifetime documented
|
||||||
|
- [ ] **Token lifecycle documented** — Expiry, refresh, storage strategy for long-running server, key rotation procedure
|
||||||
|
- [ ] **Pagination pattern identified** — Type, params, max size, end detection, total count availability
|
||||||
|
- [ ] **Rate limits are documented** — Global + per-endpoint, burst behavior, scope, penalty
|
||||||
|
|
||||||
|
### Planning:
|
||||||
|
- [ ] **Version & deprecation documented** — Current version, sunset timelines, version header requirements
|
||||||
|
- [ ] **App candidates have clear data sources** — Each app maps to specific tool(s)
|
||||||
|
- [ ] **Data shape contracts defined** — Tool outputSchema → app expected input mapped per app candidate
|
||||||
|
- [ ] **Elicitation candidates identified** — Destructive operations, ambiguous inputs, multi-step flows, account selection
|
||||||
|
- [ ] **Task candidates identified** — Long-running operations flagged with polling intervals
|
||||||
|
- [ ] **Icon planning noted per tool** — SVG preferred, at least noted even if deferred
|
||||||
|
- [ ] **Sandbox/test environment documented** — Availability, URL, QA impact
|
||||||
|
- [ ] **Error format is documented** — Response shape, common error codes
|
||||||
|
- [ ] **Naming follows conventions** — verb_noun tools, service-type app IDs, consistent verb prefixes
|
||||||
|
- [ ] **User intent clustering done** — Diverse phrasings per disambiguation entry
|
||||||
|
- [ ] **Quirks & gotchas captured** — API-specific oddities that affect implementation
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. Example: Completed Analysis (abbreviated)
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
# Calendly — MCP API Analysis
|
||||||
|
|
||||||
|
**Date:** 2026-02-04
|
||||||
|
**API Version:** v2
|
||||||
|
**Base URL:** `https://api.calendly.com`
|
||||||
|
**Documentation:** https://developer.calendly.com/api-docs
|
||||||
|
|
||||||
|
## 1. Service Overview
|
||||||
|
**What it does:** Scheduling automation platform
|
||||||
|
**API Style:** REST
|
||||||
|
|
||||||
|
## 2. Authentication
|
||||||
|
**Method:** OAuth2 (Personal Access Token also available)
|
||||||
|
**OAuth2 Grant Type:** authorization_code (PKCE recommended for public clients)
|
||||||
|
**Token Expiry:** 2 hours (refresh token: 30 days)
|
||||||
|
Headers: `Authorization: Bearer {token}`
|
||||||
|
|
||||||
|
## 4. Version & Deprecation
|
||||||
|
**Current Version:** v2 (v1 sunset: 2024-06-01)
|
||||||
|
**Version Mechanism:** URL path (/api/v2/)
|
||||||
|
|
||||||
|
## 6. Tool Groups
|
||||||
|
| Group | Tools | Description |
|
||||||
|
|-------|-------|-------------|
|
||||||
|
| `scheduling` | 8 | Event types, scheduling links |
|
||||||
|
| `events` | 6 | Scheduled events, invitees |
|
||||||
|
| `users` | 4 | User profiles, org membership |
|
||||||
|
| `webhooks` | 3 | Webhook subscriptions |
|
||||||
|
|
||||||
|
## 7. Tool Inventory (example tool)
|
||||||
|
### `list_events`
|
||||||
|
- **Title:** List Scheduled Events
|
||||||
|
- **Description:** List scheduled events with date range and status filters. Returns {name, start_time, end_time, status, invitee_count} per event. Use when user wants to see upcoming or past events. Do NOT use for event type management (use list_event_types) or single event details (use get_event). Read-only.
|
||||||
|
- **Output Schema:** `{ collection: Event[], pagination: { count, next_page_token } }`
|
||||||
|
- **Content Annotations:** `audience: ["user", "assistant"]`, `priority: 0.7`
|
||||||
|
|
||||||
|
## 8. App Candidates
|
||||||
|
- calendly-dashboard (Dashboard) — event counts, upcoming schedule
|
||||||
|
- calendly-event-grid (Data Grid) — list scheduled events
|
||||||
|
- calendly-event-detail (Detail Card) — single event with invitee info
|
||||||
|
- calendly-calendar (Calendar) — visual calendar of events
|
||||||
|
- calendly-availability (Form) — set availability preferences
|
||||||
|
|
||||||
|
## 9. Elicitation Candidates
|
||||||
|
| Flow | Trigger | Type | User Input | Fallback |
|
||||||
|
|------|---------|------|------------|----------|
|
||||||
|
| Cancel event | `cancel_event` | Confirmation | "Cancel event with {invitee}?" | Require explicit confirmation in message |
|
||||||
|
| Connect calendar | Initial setup | Selection | "Which calendar provider?" | Default to primary calendar |
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. Execution Workflow
|
||||||
|
|
||||||
|
```
|
||||||
|
1. Receive API docs URL(s) from user
|
||||||
|
2. Identify API style (REST/GraphQL/SOAP/gRPC/WebSocket)
|
||||||
|
3. Read auth page → Document auth flow (including OAuth2 subtype, token lifecycle, key rotation)
|
||||||
|
4. Read rate limits → Document constraints (including burst, scope, penalty)
|
||||||
|
5. Check sandbox/test environment → Document availability, URL, and QA impact
|
||||||
|
6. Check version/deprecation → Document current version and sunset timelines
|
||||||
|
7. Scan all endpoints → Build endpoint catalog
|
||||||
|
8. Group endpoints by domain → Define tool groups (cap at 15-20 active per interaction)
|
||||||
|
9. Name each tool → Write 6-part descriptions with annotations, title, outputSchema, content annotations, icon
|
||||||
|
10. Build disambiguation tables with user intent clustering for each tool group
|
||||||
|
11. Identify elicitation candidates (destructive ops, ambiguous inputs, multi-step flows)
|
||||||
|
12. Identify task candidates (long-running operations >10s)
|
||||||
|
13. Identify app candidates → Map to data source tools
|
||||||
|
14. Define data shape contracts (tool outputSchema → app expected input)
|
||||||
|
15. Document quirks/gotchas
|
||||||
|
16. Set implementation priority
|
||||||
|
17. Run quality gate checklist
|
||||||
|
18. Output: {service}-api-analysis.md
|
||||||
|
```
|
||||||
|
|
||||||
|
**Estimated time:** 30-60 minutes for small APIs, 1-2 hours for large APIs (50+ endpoints)
|
||||||
|
|
||||||
|
**Agent model recommendation:** Opus — requires deep reading comprehension and strategic judgment for tool grouping and app candidate selection.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*This skill is Phase 1 of the MCP Factory pipeline. The analysis document it produces is the single source of truth for all subsequent phases.*
|
||||||
2170
skills/mcp-app-designer/SKILL.md
Normal file
2170
skills/mcp-app-designer/SKILL.md
Normal file
File diff suppressed because it is too large
Load Diff
772
skills/mcp-apps-integration/SKILL.md
Normal file
772
skills/mcp-apps-integration/SKILL.md
Normal file
@ -0,0 +1,772 @@
|
|||||||
|
# MCP Apps Integration — Building Servers with Rich UI
|
||||||
|
|
||||||
|
**When to use this skill:** Adding rich UI components (structuredContent) to MCP servers. Use when tool results benefit from visual presentation beyond plain text/JSON.
|
||||||
|
|
||||||
|
**What this covers:** Integrating MCP Apps with server tools, based on 11 production GHL apps (Contact Grid, Pipeline Board, Calendar View, Invoice Preview, etc.).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. What Are MCP Apps?
|
||||||
|
|
||||||
|
**MCP Apps = Tools that return `structuredContent`** (HTML-based UI components that render in Claude Desktop)
|
||||||
|
|
||||||
|
**Use cases:**
|
||||||
|
- **Data grids:** Contact lists, search results
|
||||||
|
- **Dashboards:** Stats, metrics, KPIs
|
||||||
|
- **Cards:** Opportunity cards, invoice previews
|
||||||
|
- **Timelines:** Activity feeds, history
|
||||||
|
- **Forms:** Quick actions embedded in UI
|
||||||
|
- **Visualizations:** Charts, graphs, calendars
|
||||||
|
|
||||||
|
**When to use apps vs regular tools:**
|
||||||
|
- ✅ Use apps: Visual data (grids, cards, timelines)
|
||||||
|
- ❌ Skip apps: Simple CRUD operations, plain JSON responses
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Architecture Pattern
|
||||||
|
|
||||||
|
### Server + Apps Integration
|
||||||
|
```
|
||||||
|
mcp-server-myservice/
|
||||||
|
├── src/
|
||||||
|
│ ├── index.ts # Main server (or server.ts)
|
||||||
|
│ ├── clients/
|
||||||
|
│ │ └── api-client.ts # API client
|
||||||
|
│ ├── apps/
|
||||||
|
│ │ └── index.ts # Apps manager + tool definitions
|
||||||
|
│ ├── ui/
|
||||||
|
│ │ ├── contact-grid.html
|
||||||
|
│ │ ├── dashboard.html
|
||||||
|
│ │ └── ...
|
||||||
|
│ └── types/
|
||||||
|
│ └── index.ts # Shared TypeScript types
|
||||||
|
├── dist/
|
||||||
|
│ ├── index.js # Compiled server
|
||||||
|
│ ├── apps/
|
||||||
|
│ ├── app-ui/ # Compiled HTML files (copied during build)
|
||||||
|
│ └── ...
|
||||||
|
├── package.json
|
||||||
|
├── tsconfig.json
|
||||||
|
└── README.md
|
||||||
|
```
|
||||||
|
|
||||||
|
**Key points:**
|
||||||
|
- Apps manager lives in `src/apps/index.ts`
|
||||||
|
- HTML UI files live in `src/ui/` or `app-ui/`
|
||||||
|
- Compiled UI files must be accessible at runtime (copy during build)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Apps Manager Pattern
|
||||||
|
|
||||||
|
### Basic MCPAppsManager Class
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { Tool } from '@modelcontextprotocol/sdk/types.js';
|
||||||
|
import { MyAPIClient } from '../clients/api-client.js';
|
||||||
|
import * as fs from 'fs';
|
||||||
|
import * as path from 'path';
|
||||||
|
import { fileURLToPath } from 'url';
|
||||||
|
|
||||||
|
export interface AppToolResult {
|
||||||
|
content: Array<{ type: 'text'; text: string }>;
|
||||||
|
structuredContent?: Record<string, unknown>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AppResourceHandler {
|
||||||
|
uri: string;
|
||||||
|
mimeType: string;
|
||||||
|
getContent: () => string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ESM __dirname equivalent
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = path.dirname(__filename);
|
||||||
|
|
||||||
|
function getUIBuildPath(): string {
|
||||||
|
// When compiled, this file is at dist/apps/index.js
|
||||||
|
// UI files are at dist/app-ui/
|
||||||
|
const fromDist = path.resolve(__dirname, '..', 'app-ui');
|
||||||
|
if (fs.existsSync(fromDist)) {
|
||||||
|
return fromDist;
|
||||||
|
}
|
||||||
|
// Fallback
|
||||||
|
return fromDist;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class MCPAppsManager {
|
||||||
|
private apiClient: MyAPIClient;
|
||||||
|
private resourceHandlers: Map<string, AppResourceHandler> = new Map();
|
||||||
|
private uiBuildPath: string;
|
||||||
|
|
||||||
|
constructor(apiClient: MyAPIClient) {
|
||||||
|
this.apiClient = apiClient;
|
||||||
|
this.uiBuildPath = getUIBuildPath();
|
||||||
|
this.registerResourceHandlers();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register all UI resource handlers
|
||||||
|
*/
|
||||||
|
private registerResourceHandlers(): void {
|
||||||
|
const resources: Array<{ uri: string; file: string }> = [
|
||||||
|
{ uri: 'ui://myservice/contact-grid', file: 'contact-grid.html' },
|
||||||
|
{ uri: 'ui://myservice/dashboard', file: 'dashboard.html' },
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const resource of resources) {
|
||||||
|
this.resourceHandlers.set(resource.uri, {
|
||||||
|
uri: resource.uri,
|
||||||
|
mimeType: 'text/html;profile=mcp-app',
|
||||||
|
getContent: () => this.loadUIResource(resource.file),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load UI resource from build directory
|
||||||
|
*/
|
||||||
|
private loadUIResource(filename: string): string {
|
||||||
|
const filePath = path.join(this.uiBuildPath, filename);
|
||||||
|
try {
|
||||||
|
return fs.readFileSync(filePath, 'utf-8');
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`UI resource not found: ${filePath}`);
|
||||||
|
return this.getFallbackHTML(filename);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate fallback HTML when UI resource is not built
|
||||||
|
*/
|
||||||
|
private getFallbackHTML(filename: string): string {
|
||||||
|
const componentName = filename.replace('.html', '');
|
||||||
|
return `
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>${componentName}</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div style="text-align: center; padding: 20px; color: #666;">
|
||||||
|
<p>UI component "${componentName}" is loading...</p>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get tool definitions for all app tools
|
||||||
|
*/
|
||||||
|
getToolDefinitions(): Tool[] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
name: 'view_contact_grid',
|
||||||
|
description: 'Display contact search results in a data grid. Returns a visual UI component.',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
query: { type: 'string', description: 'Search query' },
|
||||||
|
limit: { type: 'number', description: 'Max results (default: 25)' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// ... more app tools
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get resource handlers (for server registration)
|
||||||
|
*/
|
||||||
|
getResourceHandlers(): Map<string, AppResourceHandler> {
|
||||||
|
return this.resourceHandlers;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle app tool calls
|
||||||
|
*/
|
||||||
|
async handleAppTool(name: string, args: Record<string, unknown>): Promise<AppToolResult> {
|
||||||
|
switch (name) {
|
||||||
|
case 'view_contact_grid':
|
||||||
|
return this.viewContactGrid(args);
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new Error(`Unknown app tool: ${name}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Example: Contact Grid App
|
||||||
|
*/
|
||||||
|
private async viewContactGrid(args: Record<string, unknown>): Promise<AppToolResult> {
|
||||||
|
const { query = '', limit = 25 } = args;
|
||||||
|
|
||||||
|
// Call API to get data
|
||||||
|
const contacts = await this.apiClient.searchContacts({ query, limit: Number(limit) });
|
||||||
|
|
||||||
|
// Return structuredContent pointing to UI resource
|
||||||
|
return {
|
||||||
|
content: [{ type: 'text', text: `Found ${contacts.length} contacts` }],
|
||||||
|
structuredContent: {
|
||||||
|
type: 'ui',
|
||||||
|
uri: 'ui://myservice/contact-grid',
|
||||||
|
data: {
|
||||||
|
contacts,
|
||||||
|
query,
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Server Integration
|
||||||
|
|
||||||
|
### In `src/index.ts` or `src/server.ts`
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
||||||
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
||||||
|
import {
|
||||||
|
CallToolRequestSchema,
|
||||||
|
ListToolsRequestSchema,
|
||||||
|
ListResourcesRequestSchema,
|
||||||
|
ReadResourceRequestSchema,
|
||||||
|
} from "@modelcontextprotocol/sdk/types.js";
|
||||||
|
import { MyAPIClient } from './clients/api-client.js';
|
||||||
|
import { MCPAppsManager } from './apps/index.js';
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
// Initialize API client
|
||||||
|
const apiClient = new MyAPIClient(process.env.API_KEY!);
|
||||||
|
|
||||||
|
// Initialize apps manager
|
||||||
|
const appsManager = new MCPAppsManager(apiClient);
|
||||||
|
|
||||||
|
// Create MCP server
|
||||||
|
const server = new Server(
|
||||||
|
{ name: 'myservice-mcp', version: '1.0.0' },
|
||||||
|
{ capabilities: { tools: {}, resources: {} } } // ✅ Enable resources
|
||||||
|
);
|
||||||
|
|
||||||
|
// List tools (regular tools + app tools)
|
||||||
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
||||||
|
const regularTools = [
|
||||||
|
// ... your regular tools
|
||||||
|
];
|
||||||
|
const appTools = appsManager.getToolDefinitions();
|
||||||
|
|
||||||
|
return {
|
||||||
|
tools: [...regularTools, ...appTools],
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle tool calls
|
||||||
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
||||||
|
const { name, arguments: args } = request.params;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Check if it's an app tool
|
||||||
|
const appTools = appsManager.getToolDefinitions().map(t => t.name);
|
||||||
|
if (appTools.includes(name)) {
|
||||||
|
return await appsManager.handleAppTool(name, args || {});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle regular tools
|
||||||
|
const result = await handleRegularTool(apiClient, name, args || {});
|
||||||
|
return {
|
||||||
|
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
const message = error instanceof Error ? error.message : String(error);
|
||||||
|
return {
|
||||||
|
content: [{ type: 'text', text: `Error: ${message}` }],
|
||||||
|
isError: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// List resources (UI files)
|
||||||
|
server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
||||||
|
const handlers = appsManager.getResourceHandlers();
|
||||||
|
const resources = Array.from(handlers.values()).map(h => ({
|
||||||
|
uri: h.uri,
|
||||||
|
mimeType: h.mimeType,
|
||||||
|
name: h.uri.split('/').pop() || h.uri,
|
||||||
|
}));
|
||||||
|
return { resources };
|
||||||
|
});
|
||||||
|
|
||||||
|
// Read resources (serve UI HTML)
|
||||||
|
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
||||||
|
const { uri } = request.params;
|
||||||
|
const handler = appsManager.getResourceHandlers().get(uri);
|
||||||
|
|
||||||
|
if (!handler) {
|
||||||
|
throw new Error(`Resource not found: ${uri}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
contents: [{
|
||||||
|
uri,
|
||||||
|
mimeType: handler.mimeType,
|
||||||
|
text: handler.getContent(),
|
||||||
|
}],
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// Start server
|
||||||
|
const transport = new StdioServerTransport();
|
||||||
|
await server.connect(transport);
|
||||||
|
console.error('MyService MCP server with apps running on stdio');
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch(console.error);
|
||||||
|
```
|
||||||
|
|
||||||
|
**Key additions for apps:**
|
||||||
|
1. `capabilities: { tools: {}, resources: {} }` — Enable resources
|
||||||
|
2. `ListResourcesRequestSchema` handler — List UI files
|
||||||
|
3. `ReadResourceRequestSchema` handler — Serve UI HTML
|
||||||
|
4. Check if tool is an app tool before routing
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. HTML UI Component Template
|
||||||
|
|
||||||
|
### Example: Contact Grid (`src/ui/contact-grid.html`)
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Contact Grid</title>
|
||||||
|
<style>
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||||
|
padding: 20px;
|
||||||
|
background: #f5f5f5;
|
||||||
|
}
|
||||||
|
.grid-container {
|
||||||
|
background: white;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 20px;
|
||||||
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
.grid-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
padding-bottom: 15px;
|
||||||
|
border-bottom: 2px solid #e0e0e0;
|
||||||
|
}
|
||||||
|
.grid-title {
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
.grid-count {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
.contacts-table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
.contacts-table th {
|
||||||
|
text-align: left;
|
||||||
|
padding: 12px;
|
||||||
|
background: #f8f9fa;
|
||||||
|
color: #555;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 13px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
}
|
||||||
|
.contacts-table td {
|
||||||
|
padding: 12px;
|
||||||
|
border-bottom: 1px solid #e0e0e0;
|
||||||
|
}
|
||||||
|
.contacts-table tr:hover {
|
||||||
|
background: #f8f9fa;
|
||||||
|
}
|
||||||
|
.contact-name {
|
||||||
|
font-weight: 600;
|
||||||
|
color: #2563eb;
|
||||||
|
}
|
||||||
|
.contact-email {
|
||||||
|
color: #666;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
.contact-status {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 4px 12px;
|
||||||
|
border-radius: 12px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
.status-active {
|
||||||
|
background: #d1fae5;
|
||||||
|
color: #065f46;
|
||||||
|
}
|
||||||
|
.status-inactive {
|
||||||
|
background: #fee2e2;
|
||||||
|
color: #991b1b;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="grid-container">
|
||||||
|
<div class="grid-header">
|
||||||
|
<div class="grid-title">Contacts</div>
|
||||||
|
<div class="grid-count" id="count"></div>
|
||||||
|
</div>
|
||||||
|
<table class="contacts-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Email</th>
|
||||||
|
<th>Phone</th>
|
||||||
|
<th>Status</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="contacts-tbody">
|
||||||
|
<!-- Populated by JavaScript -->
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Listen for data from MCP
|
||||||
|
window.addEventListener('message', (event) => {
|
||||||
|
if (event.data?.type === 'mcp-app-init') {
|
||||||
|
const data = event.data.data;
|
||||||
|
renderContacts(data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function renderContacts(data) {
|
||||||
|
const { contacts, query } = data;
|
||||||
|
|
||||||
|
// Update count
|
||||||
|
document.getElementById('count').textContent =
|
||||||
|
`${contacts.length} result${contacts.length !== 1 ? 's' : ''}`;
|
||||||
|
|
||||||
|
// Render table rows
|
||||||
|
const tbody = document.getElementById('contacts-tbody');
|
||||||
|
tbody.innerHTML = contacts.map(contact => `
|
||||||
|
<tr>
|
||||||
|
<td class="contact-name">${escapeHtml(contact.name)}</td>
|
||||||
|
<td class="contact-email">${escapeHtml(contact.email || 'N/A')}</td>
|
||||||
|
<td>${escapeHtml(contact.phone || 'N/A')}</td>
|
||||||
|
<td>
|
||||||
|
<span class="contact-status status-${contact.status || 'active'}">
|
||||||
|
${escapeHtml(contact.status || 'Active')}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
`).join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
function escapeHtml(text) {
|
||||||
|
const div = document.createElement('div');
|
||||||
|
div.textContent = text;
|
||||||
|
return div.innerHTML;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Key patterns:**
|
||||||
|
- Self-contained (all CSS/JS inline)
|
||||||
|
- `window.addEventListener('message', ...)` to receive data
|
||||||
|
- `event.data.type === 'mcp-app-init'` to detect init
|
||||||
|
- `event.data.data` contains the structuredContent.data object
|
||||||
|
- Escape HTML to prevent XSS
|
||||||
|
- Clean, modern styling
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Common UI Patterns
|
||||||
|
|
||||||
|
### 1. Data Grid (List View)
|
||||||
|
**Use for:** Contact lists, search results, transaction history
|
||||||
|
**Components:** Table, sorting, pagination indicators
|
||||||
|
**Example apps:** Contact Grid, Pipeline Board
|
||||||
|
|
||||||
|
### 2. Card View (Detail View)
|
||||||
|
**Use for:** Single item details, opportunity cards, invoices
|
||||||
|
**Components:** Card container, labeled fields, actions
|
||||||
|
**Example apps:** Opportunity Card, Invoice Preview
|
||||||
|
|
||||||
|
### 3. Dashboard (Stats/Metrics)
|
||||||
|
**Use for:** Analytics, KPIs, performance metrics
|
||||||
|
**Components:** Stat cards, charts (use Chart.js), progress bars
|
||||||
|
**Example apps:** Campaign Stats, Agent Stats
|
||||||
|
|
||||||
|
### 4. Timeline (Activity Feed)
|
||||||
|
**Use for:** History, activity logs, event streams
|
||||||
|
**Components:** Timeline with timestamps, event types, icons
|
||||||
|
**Example apps:** Contact Timeline, Workflow Status
|
||||||
|
|
||||||
|
### 5. Calendar View
|
||||||
|
**Use for:** Appointments, events, schedules
|
||||||
|
**Components:** Calendar grid, event markers, time slots
|
||||||
|
**Example apps:** Calendar View
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Build Configuration
|
||||||
|
|
||||||
|
### package.json Scripts
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"scripts": {
|
||||||
|
"build": "npm run build:ts && npm run build:ui",
|
||||||
|
"build:ts": "tsc",
|
||||||
|
"build:ui": "node scripts/copy-ui.js",
|
||||||
|
"dev": "tsx src/index.ts",
|
||||||
|
"start": "node dist/index.js"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### scripts/copy-ui.js
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
import fs from 'fs-extra';
|
||||||
|
import path from 'path';
|
||||||
|
|
||||||
|
const uiSource = path.join(process.cwd(), 'src', 'ui');
|
||||||
|
const uiDest = path.join(process.cwd(), 'dist', 'app-ui');
|
||||||
|
|
||||||
|
console.log('Copying UI files...');
|
||||||
|
console.log(`From: ${uiSource}`);
|
||||||
|
console.log(`To: ${uiDest}`);
|
||||||
|
|
||||||
|
// Ensure dist/app-ui exists
|
||||||
|
fs.ensureDirSync(uiDest);
|
||||||
|
|
||||||
|
// Copy all HTML files from src/ui to dist/app-ui
|
||||||
|
fs.copySync(uiSource, uiDest, { overwrite: true });
|
||||||
|
|
||||||
|
console.log('✅ UI files copied successfully');
|
||||||
|
```
|
||||||
|
|
||||||
|
**Install fs-extra:**
|
||||||
|
```bash
|
||||||
|
npm install --save-dev fs-extra @types/fs-extra
|
||||||
|
```
|
||||||
|
|
||||||
|
### tsconfig.json
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2022",
|
||||||
|
"module": "ES2022",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"outDir": "./dist",
|
||||||
|
"rootDir": "./src",
|
||||||
|
"strict": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"forceConsistentCasingInFileNames": true
|
||||||
|
},
|
||||||
|
"include": ["src/**/*"],
|
||||||
|
"exclude": ["node_modules", "dist", "src/ui"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note:** Exclude `src/ui` from TypeScript compilation (HTML files don't need compiling)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Testing Apps
|
||||||
|
|
||||||
|
### 1. Build the server
|
||||||
|
```bash
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Add to Claude Desktop config
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"mcpServers": {
|
||||||
|
"myservice": {
|
||||||
|
"command": "node",
|
||||||
|
"args": ["/absolute/path/to/dist/index.js"],
|
||||||
|
"env": {
|
||||||
|
"API_KEY": "your_key_here"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Restart Claude Desktop
|
||||||
|
|
||||||
|
### 4. Call an app tool
|
||||||
|
```
|
||||||
|
Can you show me the contact grid for "john"?
|
||||||
|
```
|
||||||
|
|
||||||
|
Claude will call `view_contact_grid` → Server returns `structuredContent` → UI renders in Claude Desktop
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. When to Use Apps vs Regular Tools
|
||||||
|
|
||||||
|
| Scenario | Use App | Use Regular Tool |
|
||||||
|
|----------|---------|------------------|
|
||||||
|
| Display contact list | ✅ Grid UI | ❌ JSON dump |
|
||||||
|
| Show dashboard stats | ✅ Dashboard UI | ❌ Plain numbers |
|
||||||
|
| Get single contact by ID | ❌ Overkill | ✅ JSON response |
|
||||||
|
| Create a new record | ❌ No UI needed | ✅ POST + return result |
|
||||||
|
| Search + display results | ✅ Grid UI | Maybe (depends on result size) |
|
||||||
|
| Calendar of appointments | ✅ Calendar UI | ❌ JSON dates hard to parse |
|
||||||
|
| Invoice details | ✅ Card UI | Maybe |
|
||||||
|
|
||||||
|
**Rule of thumb:** If the result benefits from visual formatting, use an app. If it's pure data/CRUD, use a regular tool.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. Common Pitfalls
|
||||||
|
|
||||||
|
### ❌ UI files not copied to dist/
|
||||||
|
**Solution:** Add `build:ui` script that copies HTML from `src/ui/` to `dist/app-ui/`
|
||||||
|
|
||||||
|
### ❌ UI path resolution fails
|
||||||
|
**Solution:** Use `fileURLToPath` for ESM `__dirname` equivalent + check `fs.existsSync()`
|
||||||
|
|
||||||
|
### ❌ Data not showing in UI
|
||||||
|
**Solution:** Check `event.data.type === 'mcp-app-init'` and log `event.data.data` to console
|
||||||
|
|
||||||
|
### ❌ Resources not registered
|
||||||
|
**Solution:** Add `capabilities: { resources: {} }` and implement `ListResourcesRequestSchema` + `ReadResourceRequestSchema`
|
||||||
|
|
||||||
|
### ❌ HTML escaping issues
|
||||||
|
**Solution:** Always escape user data with `escapeHtml()` function
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. App Tool Naming Convention
|
||||||
|
|
||||||
|
**Pattern:** `view_` or `show_` prefix for app tools
|
||||||
|
|
||||||
|
- `view_contact_grid` → Display contact grid
|
||||||
|
- `show_dashboard` → Display dashboard
|
||||||
|
- `view_opportunity_card` → Display opportunity card
|
||||||
|
- `show_calendar` → Display calendar
|
||||||
|
|
||||||
|
**Why:**
|
||||||
|
- Differentiates app tools from regular tools
|
||||||
|
- Signals to Claude that result is visual
|
||||||
|
- Clear intent (viewing vs fetching)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 12. Example: Complete App Tool
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
{
|
||||||
|
name: 'view_pipeline_board',
|
||||||
|
description: 'Display sales pipeline board with opportunities grouped by stage. Returns an interactive visual component.',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
pipelineId: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Pipeline ID (optional, defaults to main pipeline)'
|
||||||
|
},
|
||||||
|
includeWon: {
|
||||||
|
type: 'boolean',
|
||||||
|
description: 'Include won deals (default: false)'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
private async viewPipelineBoard(args: Record<string, unknown>): Promise<AppToolResult> {
|
||||||
|
const { pipelineId, includeWon = false } = args;
|
||||||
|
|
||||||
|
// Fetch pipeline data from API
|
||||||
|
const pipeline = await this.apiClient.getPipeline(pipelineId);
|
||||||
|
const opportunities = await this.apiClient.getOpportunities({
|
||||||
|
pipelineId,
|
||||||
|
status: includeWon ? 'all' : 'active',
|
||||||
|
});
|
||||||
|
|
||||||
|
// Group by stage
|
||||||
|
const groupedByStage = opportunities.reduce((acc, opp) => {
|
||||||
|
if (!acc[opp.stageId]) acc[opp.stageId] = [];
|
||||||
|
acc[opp.stageId].push(opp);
|
||||||
|
return acc;
|
||||||
|
}, {} as Record<string, any[]>);
|
||||||
|
|
||||||
|
return {
|
||||||
|
content: [{
|
||||||
|
type: 'text',
|
||||||
|
text: `Pipeline Board: ${pipeline.name} (${opportunities.length} opportunities)`,
|
||||||
|
}],
|
||||||
|
structuredContent: {
|
||||||
|
type: 'ui',
|
||||||
|
uri: 'ui://myservice/pipeline-board',
|
||||||
|
data: {
|
||||||
|
pipeline,
|
||||||
|
opportunities,
|
||||||
|
groupedByStage,
|
||||||
|
includeWon,
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 13. Resources
|
||||||
|
|
||||||
|
- **MCP Apps Docs:** https://modelcontextprotocol.io/docs/apps
|
||||||
|
- **Example Apps:** `/Users/jakeshore/.clawdbot/workspace/mcp-diagrams/ghl-mcp-apps-only/`
|
||||||
|
- **GHL MCP Server:** `/Users/jakeshore/.clawdbot/workspace/mcp-diagrams/GoHighLevel-MCP/`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
**To add apps to an MCP server:**
|
||||||
|
1. Create `MCPAppsManager` class in `src/apps/index.ts`
|
||||||
|
2. Build HTML UI components in `src/ui/`
|
||||||
|
3. Register resource handlers in apps manager
|
||||||
|
4. Add `capabilities: { resources: {} }` to server
|
||||||
|
5. Implement `ListResourcesRequestSchema` and `ReadResourceRequestSchema`
|
||||||
|
6. Return `structuredContent` from app tool handlers
|
||||||
|
7. Copy UI files to `dist/app-ui/` during build
|
||||||
|
|
||||||
|
**Benefits:**
|
||||||
|
- Rich visual presentation of data
|
||||||
|
- Better UX in Claude Desktop
|
||||||
|
- Interactive components (grids, cards, dashboards)
|
||||||
|
- Clear separation of regular tools vs visual tools
|
||||||
|
|
||||||
|
Follow this pattern and your apps will integrate seamlessly with your MCP server.
|
||||||
86
skills/mcp-apps-merged/CHANGELOG.md
Normal file
86
skills/mcp-apps-merged/CHANGELOG.md
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
# MCP Apps Merged Skill — Changelog
|
||||||
|
|
||||||
|
## Merge Date: 2026-02-03
|
||||||
|
|
||||||
|
### Added from Official ext-apps Skill
|
||||||
|
|
||||||
|
These sections/content were **new** from the official skill and did not exist in the custom skill:
|
||||||
|
|
||||||
|
| Addition | Location in Merged Doc |
|
||||||
|
|----------|----------------------|
|
||||||
|
| YAML frontmatter with trigger phrases | Top of file |
|
||||||
|
| Framework selection table (Vue/Svelte/Preact/Solid) | Quick Start Decision Tree |
|
||||||
|
| Project context section (adding to existing vs new) | Quick Start Decision Tree |
|
||||||
|
| Full framework templates table (6 frameworks) | Get Reference Code → Framework Templates |
|
||||||
|
| API Reference source files table | Get Reference Code → API Reference |
|
||||||
|
| Additional advanced examples (video-resource, sheet-music, threejs, transcript, basic-host) | Get Reference Code → Advanced Examples |
|
||||||
|
| `npm install` vs manual versions warning | Required Packages + MUST DO table |
|
||||||
|
| `tsx` vs `bun` compatibility note | Required Packages |
|
||||||
|
| `ontoolinputpartial` streaming section with use cases table and code patterns | New section: Streaming Partial Input |
|
||||||
|
| Visibility-based resource management (IntersectionObserver) | New section: Visibility-Based Resource Management |
|
||||||
|
| Fullscreen mode (`requestDisplayMode()`) with CSS patterns | New section: Fullscreen Mode |
|
||||||
|
| Safe area insets handling (`safeAreaInsets`) | New section: Safe Area Insets |
|
||||||
|
| `sendLog` for debug logging to host | Client-Side + Debugging Checklist |
|
||||||
|
| Testing with basic-host example | Testing section |
|
||||||
|
| Host styling split into Applying / Using subsections | Host CSS Variables |
|
||||||
|
| `line-height` CSS variable example | Host CSS Variables |
|
||||||
|
|
||||||
|
### Kept from Custom Battle-Tested Skill
|
||||||
|
|
||||||
|
Everything from the custom skill was preserved as the structural base:
|
||||||
|
|
||||||
|
| Content | Status |
|
||||||
|
|---------|--------|
|
||||||
|
| "What MCP Apps Actually Are" explanation | Kept verbatim |
|
||||||
|
| "How The Data Flows" numbered diagram | Kept verbatim |
|
||||||
|
| Quick Reference pattern | Kept verbatim |
|
||||||
|
| Required Packages table | Kept, enhanced with official notes |
|
||||||
|
| Project Structure tree | Kept verbatim |
|
||||||
|
| Complete Server Template (annotated) | Kept verbatim |
|
||||||
|
| Tool Registration quick reference | Kept verbatim |
|
||||||
|
| Resource Registration snippet | Kept verbatim |
|
||||||
|
| Tool Visibility Options | Kept (merged — both had same content) |
|
||||||
|
| Server-Side Action Tools (view vs action) | Kept verbatim |
|
||||||
|
| Complete HTML Template | Kept verbatim |
|
||||||
|
| Complete UI Logic template | Kept verbatim |
|
||||||
|
| Vanilla JS Full Lifecycle pattern | Kept, enhanced with `ontoolinputpartial` + safe area |
|
||||||
|
| React Pattern | Kept verbatim |
|
||||||
|
| Calling Server Tools snippet | Kept verbatim |
|
||||||
|
| Updating Model Context snippet | Kept verbatim |
|
||||||
|
| App Lifecycle Handlers table | Kept, added `ontoolinputpartial` row |
|
||||||
|
| Host CSS Variables section | Kept, merged with official's richer version |
|
||||||
|
| Build Configuration (vite, tsconfig, package.json) | Kept verbatim |
|
||||||
|
| **Module Scope Issue** (critical gotcha) | Kept verbatim |
|
||||||
|
| **Drag-and-Drop Pattern** (complete) | Kept verbatim |
|
||||||
|
| **Edit Modal Pattern** (complete) | Kept verbatim |
|
||||||
|
| CSS for Drag-and-Drop & Modals | Kept verbatim |
|
||||||
|
| Key Lessons Learned (6 items) | Kept verbatim |
|
||||||
|
| Data Handling Gotchas (array validation, escaping) | Kept verbatim |
|
||||||
|
| MUST DO table | Kept, added 4 rows from official |
|
||||||
|
| MUST NOT table | Kept, added 3 rows from official |
|
||||||
|
| Host-Specific Notes (Goose, Claude Desktop) | Kept verbatim |
|
||||||
|
| JSONRPC pipe testing commands | Kept verbatim |
|
||||||
|
| Debugging Checklist | Kept, added sendLog step |
|
||||||
|
| Common Errors table (10+ entries) | Kept, added 2 rows from official |
|
||||||
|
| Summary: The Golden Path | Kept, expanded with new steps |
|
||||||
|
| `git clone` reference code command | Kept verbatim |
|
||||||
|
|
||||||
|
### Reorganization
|
||||||
|
|
||||||
|
| Change | Rationale |
|
||||||
|
|--------|-----------|
|
||||||
|
| Moved "Get Reference Code" earlier (after Project Structure) | Better flow — see examples before diving into implementation |
|
||||||
|
| Merged framework templates + advanced examples into one "Get Reference Code" section | Eliminates duplicate reference code sections |
|
||||||
|
| Added dedicated sections for Streaming, Visibility, Fullscreen, Safe Areas | These were inline in the official skill; standalone sections are more scannable |
|
||||||
|
| Split Host CSS Variables into "Applying" and "Using" subsections | Clearer separation of setup vs usage |
|
||||||
|
| Enhanced MUST DO/MUST NOT tables with official skill's requirements | Single authoritative rules table |
|
||||||
|
| Added `sendLog` to both client-side section and debugging checklist | Available in two contexts where you'd need it |
|
||||||
|
| Expanded Golden Path summary from 6 to 10 steps | Covers all new features (streaming, fullscreen, visibility, styling) |
|
||||||
|
|
||||||
|
### Deduplication
|
||||||
|
|
||||||
|
Only literally identical content was deduplicated:
|
||||||
|
- Tool visibility options (identical in both) → kept once
|
||||||
|
- `git clone` command (identical) → kept once
|
||||||
|
- CSS variable examples (nearly identical) → merged into richer version with `line-height`
|
||||||
|
- Handler registration order warning (same in both) → kept custom's more detailed version
|
||||||
1294
skills/mcp-apps-merged/SKILL.md
Normal file
1294
skills/mcp-apps-merged/SKILL.md
Normal file
File diff suppressed because it is too large
Load Diff
1136
skills/mcp-apps-official/SKILL.md
Normal file
1136
skills/mcp-apps-official/SKILL.md
Normal file
File diff suppressed because it is too large
Load Diff
885
skills/mcp-deployment/SKILL.md
Normal file
885
skills/mcp-deployment/SKILL.md
Normal file
@ -0,0 +1,885 @@
|
|||||||
|
# MCP Deployment & Distribution
|
||||||
|
|
||||||
|
**When to use this skill:** Packaging and distributing MCP servers. Use when preparing servers for production, Docker containers, Railway deployment, or GitHub publishing.
|
||||||
|
|
||||||
|
**What this covers:** Deployment patterns from 30+ production MCP servers including Docker, Railway, npm publishing, and GitHub repository setup.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Deployment Overview
|
||||||
|
|
||||||
|
### Common Deployment Targets
|
||||||
|
|
||||||
|
1. **Local (Claude Desktop)** — Development + personal use
|
||||||
|
2. **Docker Container** — Portable, isolated environment
|
||||||
|
3. **Railway.app** — Hosted deployment (for web-accessible MCPs)
|
||||||
|
4. **npm Registry** — Public distribution
|
||||||
|
5. **GitHub** — Source code + documentation
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Local Deployment (Claude Desktop)
|
||||||
|
|
||||||
|
### Standard Configuration
|
||||||
|
|
||||||
|
**Location:** `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS)
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"mcpServers": {
|
||||||
|
"myservice": {
|
||||||
|
"command": "node",
|
||||||
|
"args": [
|
||||||
|
"/absolute/path/to/mcp-server-myservice/dist/index.js"
|
||||||
|
],
|
||||||
|
"env": {
|
||||||
|
"MY_SERVICE_API_KEY": "your_api_key_here",
|
||||||
|
"MY_SERVICE_API_SECRET": "your_secret_here"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Key points:**
|
||||||
|
- Use absolute paths for `args`
|
||||||
|
- Environment variables in `env` object
|
||||||
|
- Server name (`myservice`) appears in Claude Desktop
|
||||||
|
- Restart Claude Desktop after config changes
|
||||||
|
|
||||||
|
### Alternative: npx Installation
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"mcpServers": {
|
||||||
|
"myservice": {
|
||||||
|
"command": "npx",
|
||||||
|
"args": ["-y", "mcp-server-myservice"],
|
||||||
|
"env": {
|
||||||
|
"MY_SERVICE_API_KEY": "your_api_key_here"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Requires:**
|
||||||
|
- Package published to npm
|
||||||
|
- `bin` field in package.json pointing to executable
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Docker Containerization
|
||||||
|
|
||||||
|
### Dockerfile Template
|
||||||
|
|
||||||
|
```dockerfile
|
||||||
|
# Multi-stage build for smaller final image
|
||||||
|
FROM node:20-alpine AS builder
|
||||||
|
|
||||||
|
# Set working directory
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy package files
|
||||||
|
COPY package*.json ./
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
|
RUN npm ci
|
||||||
|
|
||||||
|
# Copy source code
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Build TypeScript
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
# Production stage
|
||||||
|
FROM node:20-alpine
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy package files
|
||||||
|
COPY package*.json ./
|
||||||
|
|
||||||
|
# Install production dependencies only
|
||||||
|
RUN npm ci --production
|
||||||
|
|
||||||
|
# Copy built files from builder
|
||||||
|
COPY --from=builder /app/dist ./dist
|
||||||
|
|
||||||
|
# Copy UI files (if using MCP Apps)
|
||||||
|
COPY --from=builder /app/dist/app-ui ./dist/app-ui
|
||||||
|
|
||||||
|
# Expose port (if using HTTP transport)
|
||||||
|
# EXPOSE 3000
|
||||||
|
|
||||||
|
# Set environment variable defaults
|
||||||
|
ENV NODE_ENV=production
|
||||||
|
|
||||||
|
# Run the MCP server
|
||||||
|
CMD ["node", "dist/index.js"]
|
||||||
|
```
|
||||||
|
|
||||||
|
**Key features:**
|
||||||
|
- Multi-stage build → Smaller final image
|
||||||
|
- `npm ci` → Faster, more reliable than `npm install`
|
||||||
|
- `--production` → Excludes devDependencies
|
||||||
|
- `node:20-alpine` → Lightweight base image
|
||||||
|
|
||||||
|
### .dockerignore
|
||||||
|
|
||||||
|
```
|
||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
.env
|
||||||
|
.git
|
||||||
|
.gitignore
|
||||||
|
*.md
|
||||||
|
npm-debug.log
|
||||||
|
```
|
||||||
|
|
||||||
|
**Why:** Prevents unnecessary files from being copied into image
|
||||||
|
|
||||||
|
### Build & Run
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Build image
|
||||||
|
docker build -t mcp-server-myservice .
|
||||||
|
|
||||||
|
# Run container
|
||||||
|
docker run -it --rm \
|
||||||
|
-e MY_SERVICE_API_KEY=your_key \
|
||||||
|
-e MY_SERVICE_API_SECRET=your_secret \
|
||||||
|
mcp-server-myservice
|
||||||
|
|
||||||
|
# Run with env file
|
||||||
|
docker run -it --rm \
|
||||||
|
--env-file .env \
|
||||||
|
mcp-server-myservice
|
||||||
|
```
|
||||||
|
|
||||||
|
### Docker Compose (Optional)
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# docker-compose.yml
|
||||||
|
version: '3.8'
|
||||||
|
|
||||||
|
services:
|
||||||
|
mcp-server:
|
||||||
|
build: .
|
||||||
|
environment:
|
||||||
|
- MY_SERVICE_API_KEY=${MY_SERVICE_API_KEY}
|
||||||
|
- MY_SERVICE_API_SECRET=${MY_SERVICE_API_SECRET}
|
||||||
|
restart: unless-stopped
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Run with docker-compose
|
||||||
|
docker-compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Railway Deployment
|
||||||
|
|
||||||
|
### railway.json
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"$schema": "https://railway.app/railway.schema.json",
|
||||||
|
"build": {
|
||||||
|
"builder": "NIXPACKS",
|
||||||
|
"buildCommand": "npm run build"
|
||||||
|
},
|
||||||
|
"deploy": {
|
||||||
|
"startCommand": "node dist/index.js",
|
||||||
|
"restartPolicyType": "ON_FAILURE",
|
||||||
|
"restartPolicyMaxRetries": 10
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Key fields:**
|
||||||
|
- `buildCommand` → Compile TypeScript
|
||||||
|
- `startCommand` → Run compiled server
|
||||||
|
- `restartPolicyType` → Auto-restart on failure
|
||||||
|
|
||||||
|
### Environment Variables
|
||||||
|
|
||||||
|
**In Railway Dashboard:**
|
||||||
|
1. Go to project → Variables
|
||||||
|
2. Add all required environment variables:
|
||||||
|
- `MY_SERVICE_API_KEY`
|
||||||
|
- `MY_SERVICE_API_SECRET`
|
||||||
|
- `NODE_ENV=production`
|
||||||
|
|
||||||
|
### Deployment Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Install Railway CLI
|
||||||
|
npm install -g @railway/cli
|
||||||
|
|
||||||
|
# Login
|
||||||
|
railway login
|
||||||
|
|
||||||
|
# Link to project
|
||||||
|
railway link
|
||||||
|
|
||||||
|
# Deploy
|
||||||
|
railway up
|
||||||
|
|
||||||
|
# View logs
|
||||||
|
railway logs
|
||||||
|
```
|
||||||
|
|
||||||
|
### railway.toml (Alternative)
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[build]
|
||||||
|
builder = "NIXPACKS"
|
||||||
|
buildCommand = "npm ci && npm run build"
|
||||||
|
|
||||||
|
[deploy]
|
||||||
|
startCommand = "node dist/index.js"
|
||||||
|
restartPolicyType = "ON_FAILURE"
|
||||||
|
restartPolicyMaxRetries = 10
|
||||||
|
|
||||||
|
[env]
|
||||||
|
NODE_ENV = "production"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. npm Publishing
|
||||||
|
|
||||||
|
### package.json Configuration
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "mcp-server-myservice",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "MCP server for MyService integration",
|
||||||
|
"type": "module",
|
||||||
|
"main": "dist/index.js",
|
||||||
|
"bin": {
|
||||||
|
"mcp-server-myservice": "dist/index.js"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"dist",
|
||||||
|
"README.md",
|
||||||
|
"LICENSE"
|
||||||
|
],
|
||||||
|
"keywords": [
|
||||||
|
"mcp",
|
||||||
|
"mcp-server",
|
||||||
|
"model-context-protocol",
|
||||||
|
"myservice",
|
||||||
|
"claude-desktop"
|
||||||
|
],
|
||||||
|
"author": "Your Name <your.email@example.com>",
|
||||||
|
"license": "MIT",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/yourusername/mcp-server-myservice.git"
|
||||||
|
},
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/yourusername/mcp-server-myservice/issues"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/yourusername/mcp-server-myservice#readme"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Key fields:**
|
||||||
|
- `bin` → Makes package executable via `npx`
|
||||||
|
- `files` → Only include necessary files in package
|
||||||
|
- `keywords` → Helps with npm search
|
||||||
|
- `repository` → Links to GitHub
|
||||||
|
|
||||||
|
### .npmignore
|
||||||
|
|
||||||
|
```
|
||||||
|
src
|
||||||
|
*.ts
|
||||||
|
tsconfig.json
|
||||||
|
.env
|
||||||
|
.env.example
|
||||||
|
node_modules
|
||||||
|
.git
|
||||||
|
.DS_Store
|
||||||
|
```
|
||||||
|
|
||||||
|
**Why:** Prevents source files from being published (only `dist/` is needed)
|
||||||
|
|
||||||
|
### Publishing Workflow
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Ensure you're logged in to npm
|
||||||
|
npm login
|
||||||
|
|
||||||
|
# 2. Build the project
|
||||||
|
npm run build
|
||||||
|
|
||||||
|
# 3. Test locally before publishing
|
||||||
|
npm pack
|
||||||
|
# This creates a .tgz file - inspect it to verify contents
|
||||||
|
|
||||||
|
# 4. Publish to npm
|
||||||
|
npm publish
|
||||||
|
|
||||||
|
# For scoped packages (e.g., @yourorg/mcp-server-myservice)
|
||||||
|
npm publish --access public
|
||||||
|
```
|
||||||
|
|
||||||
|
### Versioning
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Patch release (1.0.0 -> 1.0.1)
|
||||||
|
npm version patch
|
||||||
|
|
||||||
|
# Minor release (1.0.0 -> 1.1.0)
|
||||||
|
npm version minor
|
||||||
|
|
||||||
|
# Major release (1.0.0 -> 2.0.0)
|
||||||
|
npm version major
|
||||||
|
|
||||||
|
# Then publish
|
||||||
|
npm publish
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. GitHub Repository Setup
|
||||||
|
|
||||||
|
### File Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
mcp-server-myservice/
|
||||||
|
├── .github/
|
||||||
|
│ └── workflows/
|
||||||
|
│ ├── build.yml # CI/CD
|
||||||
|
│ └── publish.yml # npm publish automation
|
||||||
|
├── src/
|
||||||
|
│ └── index.ts
|
||||||
|
├── dist/ # gitignored
|
||||||
|
├── .env.example # Template for env vars
|
||||||
|
├── .gitignore
|
||||||
|
├── .npmignore
|
||||||
|
├── Dockerfile
|
||||||
|
├── docker-compose.yml
|
||||||
|
├── railway.json
|
||||||
|
├── package.json
|
||||||
|
├── tsconfig.json
|
||||||
|
├── README.md
|
||||||
|
├── LICENSE
|
||||||
|
└── CHANGELOG.md
|
||||||
|
```
|
||||||
|
|
||||||
|
### .gitignore
|
||||||
|
|
||||||
|
```
|
||||||
|
# Dependencies
|
||||||
|
node_modules/
|
||||||
|
|
||||||
|
# Build output
|
||||||
|
dist/
|
||||||
|
|
||||||
|
# Environment variables
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
|
||||||
|
# OS files
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
|
||||||
|
# Misc
|
||||||
|
.cache/
|
||||||
|
```
|
||||||
|
|
||||||
|
### README.md Template
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
# MCP Server for MyService
|
||||||
|
|
||||||
|
MCP (Model Context Protocol) server integration for MyService. Enables Claude Desktop to interact with MyService API.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- ✅ List and search contacts
|
||||||
|
- ✅ Get contact details
|
||||||
|
- ✅ Create and update contacts
|
||||||
|
- ✅ View dashboard metrics
|
||||||
|
- ✅ Rich UI components (contact grid, dashboard)
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
### Claude Desktop
|
||||||
|
|
||||||
|
Add to your `claude_desktop_config.json`:
|
||||||
|
|
||||||
|
\`\`\`json
|
||||||
|
{
|
||||||
|
"mcpServers": {
|
||||||
|
"myservice": {
|
||||||
|
"command": "npx",
|
||||||
|
"args": ["-y", "mcp-server-myservice"],
|
||||||
|
"env": {
|
||||||
|
"MY_SERVICE_API_KEY": "your_api_key_here"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
### Manual Installation
|
||||||
|
|
||||||
|
\`\`\`bash
|
||||||
|
git clone https://github.com/yourusername/mcp-server-myservice.git
|
||||||
|
cd mcp-server-myservice
|
||||||
|
npm install
|
||||||
|
npm run build
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
Add to `claude_desktop_config.json`:
|
||||||
|
|
||||||
|
\`\`\`json
|
||||||
|
{
|
||||||
|
"mcpServers": {
|
||||||
|
"myservice": {
|
||||||
|
"command": "node",
|
||||||
|
"args": ["/absolute/path/to/mcp-server-myservice/dist/index.js"],
|
||||||
|
"env": {
|
||||||
|
"MY_SERVICE_API_KEY": "your_api_key_here"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
### Required Environment Variables
|
||||||
|
|
||||||
|
- `MY_SERVICE_API_KEY` — Your MyService API key ([get one here](https://myservice.com/api-keys))
|
||||||
|
- `MY_SERVICE_API_SECRET` — Your MyService API secret (optional)
|
||||||
|
|
||||||
|
### Optional Environment Variables
|
||||||
|
|
||||||
|
- `MY_SERVICE_BASE_URL` — Override API base URL (default: `https://api.myservice.com`)
|
||||||
|
- `LOG_LEVEL` — Logging level: `debug`, `info`, `warn`, `error` (default: `info`)
|
||||||
|
|
||||||
|
## Available Tools
|
||||||
|
|
||||||
|
### Core Tools
|
||||||
|
|
||||||
|
- `list_contacts` — List contacts with pagination and filters
|
||||||
|
- `get_contact` — Get detailed contact information
|
||||||
|
- `create_contact` — Create a new contact
|
||||||
|
- `update_contact` — Update existing contact
|
||||||
|
- `delete_contact` — Delete a contact
|
||||||
|
|
||||||
|
### App Tools (Rich UI)
|
||||||
|
|
||||||
|
- `view_contact_grid` — Display contact search results in a data grid
|
||||||
|
- `show_dashboard` — Display dashboard with metrics and KPIs
|
||||||
|
|
||||||
|
## Usage Examples
|
||||||
|
|
||||||
|
### List contacts
|
||||||
|
|
||||||
|
\`\`\`
|
||||||
|
Can you show me all active contacts?
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
### Search and display
|
||||||
|
|
||||||
|
\`\`\`
|
||||||
|
Search for contacts with "john" in their name and show me the grid
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
### Create contact
|
||||||
|
|
||||||
|
\`\`\`
|
||||||
|
Create a new contact:
|
||||||
|
Name: Jane Smith
|
||||||
|
Email: jane@example.com
|
||||||
|
Phone: 555-1234
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
\`\`\`bash
|
||||||
|
# Install dependencies
|
||||||
|
npm install
|
||||||
|
|
||||||
|
# Run in development mode
|
||||||
|
npm run dev
|
||||||
|
|
||||||
|
# Build for production
|
||||||
|
npm run build
|
||||||
|
|
||||||
|
# Run tests
|
||||||
|
npm test
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
## Docker
|
||||||
|
|
||||||
|
\`\`\`bash
|
||||||
|
# Build image
|
||||||
|
docker build -t mcp-server-myservice .
|
||||||
|
|
||||||
|
# Run container
|
||||||
|
docker run -it --rm \
|
||||||
|
-e MY_SERVICE_API_KEY=your_key \
|
||||||
|
mcp-server-myservice
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
## Railway Deployment
|
||||||
|
|
||||||
|
1. Fork this repository
|
||||||
|
2. Connect to Railway
|
||||||
|
3. Add environment variables in Railway dashboard
|
||||||
|
4. Deploy
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
Pull requests are welcome! Please read [CONTRIBUTING.md](CONTRIBUTING.md) for details.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT License - see [LICENSE](LICENSE) file for details.
|
||||||
|
|
||||||
|
## Support
|
||||||
|
|
||||||
|
- [Open an issue](https://github.com/yourusername/mcp-server-myservice/issues)
|
||||||
|
- [MyService API Documentation](https://myservice.com/docs)
|
||||||
|
- [MCP Documentation](https://modelcontextprotocol.io)
|
||||||
|
```
|
||||||
|
|
||||||
|
### LICENSE (MIT Template)
|
||||||
|
|
||||||
|
```
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2026 Your Name
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. GitHub Actions CI/CD
|
||||||
|
|
||||||
|
### .github/workflows/build.yml
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
name: Build and Test
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ main, develop ]
|
||||||
|
pull_request:
|
||||||
|
branches: [ main ]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
node-version: [18.x, 20.x]
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Use Node.js ${{ matrix.node-version }}
|
||||||
|
uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
node-version: ${{ matrix.node-version }}
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: npm ci
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: npm run build
|
||||||
|
|
||||||
|
- name: Run tests
|
||||||
|
run: npm test
|
||||||
|
if: ${{ hashFiles('**/*.test.ts') != '' }}
|
||||||
|
|
||||||
|
- name: Verify dist exists
|
||||||
|
run: test -d dist && test -f dist/index.js
|
||||||
|
```
|
||||||
|
|
||||||
|
### .github/workflows/publish.yml
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
name: Publish to npm
|
||||||
|
|
||||||
|
on:
|
||||||
|
release:
|
||||||
|
types: [created]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
publish:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Use Node.js
|
||||||
|
uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
node-version: '20.x'
|
||||||
|
registry-url: 'https://registry.npmjs.org'
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: npm ci
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: npm run build
|
||||||
|
|
||||||
|
- name: Publish to npm
|
||||||
|
run: npm publish
|
||||||
|
env:
|
||||||
|
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Setup:**
|
||||||
|
1. Go to npmjs.com → Account Settings → Access Tokens
|
||||||
|
2. Create new token (Automation or Publish)
|
||||||
|
3. Add to GitHub repo → Settings → Secrets → `NPM_TOKEN`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Distribution Checklist
|
||||||
|
|
||||||
|
Before publishing/deploying:
|
||||||
|
|
||||||
|
### Code Quality
|
||||||
|
- [ ] All TypeScript compiles without errors
|
||||||
|
- [ ] No console.logs in production code (use proper logging)
|
||||||
|
- [ ] Error handling implemented for all tools
|
||||||
|
- [ ] Environment variables validated on startup
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
- [ ] README.md with installation instructions
|
||||||
|
- [ ] .env.example with all required variables
|
||||||
|
- [ ] Tool descriptions are clear and helpful
|
||||||
|
- [ ] Examples provided in README
|
||||||
|
|
||||||
|
### Package Configuration
|
||||||
|
- [ ] `package.json` has correct `name`, `version`, `description`
|
||||||
|
- [ ] `files` field only includes necessary files
|
||||||
|
- [ ] `keywords` added for npm search
|
||||||
|
- [ ] `repository`, `bugs`, `homepage` URLs set
|
||||||
|
- [ ] License file included
|
||||||
|
|
||||||
|
### Testing
|
||||||
|
- [ ] Tested locally in Claude Desktop
|
||||||
|
- [ ] All tools work as expected
|
||||||
|
- [ ] Apps render correctly (if applicable)
|
||||||
|
- [ ] Error cases handled gracefully
|
||||||
|
|
||||||
|
### Security
|
||||||
|
- [ ] No API keys hardcoded
|
||||||
|
- [ ] `.env` in `.gitignore`
|
||||||
|
- [ ] Sensitive data not logged
|
||||||
|
- [ ] Dependencies up to date (`npm audit`)
|
||||||
|
|
||||||
|
### Deployment
|
||||||
|
- [ ] Dockerfile builds successfully
|
||||||
|
- [ ] Docker container runs without errors
|
||||||
|
- [ ] Railway deployment works (if applicable)
|
||||||
|
- [ ] npm package installs and runs via `npx`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Version Management
|
||||||
|
|
||||||
|
### Semantic Versioning
|
||||||
|
|
||||||
|
- **Patch (1.0.0 → 1.0.1):** Bug fixes, no API changes
|
||||||
|
- **Minor (1.0.0 → 1.1.0):** New features, backward compatible
|
||||||
|
- **Major (1.0.0 → 2.0.0):** Breaking changes
|
||||||
|
|
||||||
|
### CHANGELOG.md
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
# Changelog
|
||||||
|
|
||||||
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
|
The format is based on [Keep a Changelog](https://keepachangelog.com/),
|
||||||
|
and this project adheres to [Semantic Versioning](https://semver.org/).
|
||||||
|
|
||||||
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- New `search_contacts` tool with full-text search
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Improved error messages for API failures
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Fixed pagination issue in `list_contacts`
|
||||||
|
|
||||||
|
## [1.1.0] - 2026-02-03
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- Contact grid MCP app
|
||||||
|
- Dashboard MCP app
|
||||||
|
- Docker support
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Updated dependencies to latest versions
|
||||||
|
|
||||||
|
## [1.0.0] - 2026-01-15
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- Initial release
|
||||||
|
- Basic CRUD tools for contacts
|
||||||
|
- MyService API integration
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. Multi-Platform Distribution
|
||||||
|
|
||||||
|
### npm + Docker + GitHub
|
||||||
|
|
||||||
|
**Best practice:** Offer multiple installation methods
|
||||||
|
|
||||||
|
**README.md section:**
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
## Installation Methods
|
||||||
|
|
||||||
|
### 1. npx (Easiest)
|
||||||
|
|
||||||
|
\`\`\`bash
|
||||||
|
# Add to claude_desktop_config.json
|
||||||
|
{
|
||||||
|
"mcpServers": {
|
||||||
|
"myservice": {
|
||||||
|
"command": "npx",
|
||||||
|
"args": ["-y", "mcp-server-myservice"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
### 2. npm Global Install
|
||||||
|
|
||||||
|
\`\`\`bash
|
||||||
|
npm install -g mcp-server-myservice
|
||||||
|
|
||||||
|
# Then reference in claude_desktop_config.json
|
||||||
|
{
|
||||||
|
"mcpServers": {
|
||||||
|
"myservice": {
|
||||||
|
"command": "mcp-server-myservice"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
### 3. Docker
|
||||||
|
|
||||||
|
\`\`\`bash
|
||||||
|
docker run -it --rm \
|
||||||
|
-e MY_SERVICE_API_KEY=your_key \
|
||||||
|
ghcr.io/yourusername/mcp-server-myservice:latest
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
### 4. From Source
|
||||||
|
|
||||||
|
\`\`\`bash
|
||||||
|
git clone https://github.com/yourusername/mcp-server-myservice.git
|
||||||
|
cd mcp-server-myservice
|
||||||
|
npm install && npm run build
|
||||||
|
|
||||||
|
# Reference dist/index.js in claude_desktop_config.json
|
||||||
|
\`\`\`
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. Common Deployment Issues
|
||||||
|
|
||||||
|
### Issue: "Cannot find module"
|
||||||
|
**Cause:** Missing dependencies or incorrect path
|
||||||
|
**Fix:** Run `npm ci` and use absolute paths in config
|
||||||
|
|
||||||
|
### Issue: "Environment variable not set"
|
||||||
|
**Cause:** Missing env vars
|
||||||
|
**Fix:** Add to `env` object in Claude Desktop config or `.env` file
|
||||||
|
|
||||||
|
### Issue: "UI files not found"
|
||||||
|
**Cause:** `dist/app-ui/` not copied during build
|
||||||
|
**Fix:** Add `build:ui` script to copy HTML files
|
||||||
|
|
||||||
|
### Issue: "ENOENT: no such file or directory"
|
||||||
|
**Cause:** Path resolution fails in compiled code
|
||||||
|
**Fix:** Use `fileURLToPath` for ESM `__dirname` equivalent
|
||||||
|
|
||||||
|
### Issue: Docker build fails
|
||||||
|
**Cause:** Missing build step or dependencies
|
||||||
|
**Fix:** Ensure `npm run build` runs in Dockerfile and all deps installed
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 12. Resources
|
||||||
|
|
||||||
|
- **MCP Deployment Guide:** https://modelcontextprotocol.io/docs/deployment
|
||||||
|
- **Railway Docs:** https://docs.railway.app
|
||||||
|
- **npm Publishing Guide:** https://docs.npmjs.com/creating-and-publishing-scoped-public-packages
|
||||||
|
- **Docker Best Practices:** https://docs.docker.com/develop/dev-best-practices
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
**Distribution workflow:**
|
||||||
|
1. Build: `npm run build`
|
||||||
|
2. Test locally in Claude Desktop
|
||||||
|
3. Create README.md with installation instructions
|
||||||
|
4. Add Dockerfile + railway.json (if deploying)
|
||||||
|
5. Publish to npm: `npm publish`
|
||||||
|
6. Push to GitHub with proper README
|
||||||
|
7. Tag releases for versioning
|
||||||
|
8. Automate with GitHub Actions
|
||||||
|
|
||||||
|
**Key files:**
|
||||||
|
- `package.json` → npm distribution
|
||||||
|
- `Dockerfile` → Docker containerization
|
||||||
|
- `railway.json` → Railway deployment
|
||||||
|
- `README.md` → User documentation
|
||||||
|
- `.env.example` → Configuration template
|
||||||
|
- `.github/workflows/` → CI/CD automation
|
||||||
|
|
||||||
|
Follow these patterns and your MCP servers will be production-ready and easy to distribute.
|
||||||
1543
skills/mcp-localbosses-integrator/SKILL.md
Normal file
1543
skills/mcp-localbosses-integrator/SKILL.md
Normal file
File diff suppressed because it is too large
Load Diff
3388
skills/mcp-qa-tester/SKILL.md
Normal file
3388
skills/mcp-qa-tester/SKILL.md
Normal file
File diff suppressed because it is too large
Load Diff
2609
skills/mcp-server-builder/SKILL.md
Normal file
2609
skills/mcp-server-builder/SKILL.md
Normal file
File diff suppressed because it is too large
Load Diff
723
skills/mcp-server-development/2026-BLUEPRINT.md
Normal file
723
skills/mcp-server-development/2026-BLUEPRINT.md
Normal file
@ -0,0 +1,723 @@
|
|||||||
|
# MCP Server Blueprint — February 2026
|
||||||
|
|
||||||
|
**This is the definitive template for building production-ready MCP servers in 2026.**
|
||||||
|
|
||||||
|
Use this checklist for EVERY new MCP server. No skipping steps. These patterns ensure your server is:
|
||||||
|
- ✅ Usable (not just functional)
|
||||||
|
- ✅ Fast (lazy loading, efficient queries)
|
||||||
|
- ✅ Discoverable (labels, descriptions)
|
||||||
|
- ✅ Interactive (MCP Apps where appropriate)
|
||||||
|
- ✅ Debuggable (logging, progress)
|
||||||
|
- ✅ Production-ready (error handling, deployment)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 1: Planning (Before Writing Code)
|
||||||
|
|
||||||
|
### 1.1 Define Server Scope
|
||||||
|
- [ ] What API/service are you integrating?
|
||||||
|
- [ ] What are the 5-10 most important operations?
|
||||||
|
- [ ] Who is the target user? (developers, business users, etc.)
|
||||||
|
- [ ] What data is most frequently accessed?
|
||||||
|
|
||||||
|
### 1.2 Identify Tool Categories
|
||||||
|
Label your tools by category. Common patterns:
|
||||||
|
- [ ] **CRUD operations** (create, read, update, delete)
|
||||||
|
- [ ] **Search/Filter** (find data with queries)
|
||||||
|
- [ ] **Analytics/Reporting** (stats, dashboards, summaries)
|
||||||
|
- [ ] **Workflows** (multi-step operations)
|
||||||
|
- [ ] **Admin** (configuration, settings)
|
||||||
|
|
||||||
|
### 1.3 Identify UI Opportunities
|
||||||
|
Which operations benefit from visual display?
|
||||||
|
- [ ] **Data grids** — Contact lists, search results, tables
|
||||||
|
- [ ] **Dashboards** — Metrics, KPIs, analytics
|
||||||
|
- [ ] **Cards** — Detail views (invoices, opportunities, profiles)
|
||||||
|
- [ ] **Timelines** — Activity feeds, history
|
||||||
|
- [ ] **Forms** — Quick actions (booking, creating records)
|
||||||
|
- [ ] **Kanban** — Pipeline views, project boards
|
||||||
|
|
||||||
|
If you have 3+ UI opportunities, plan for MCP Apps.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 2: Core Server Setup
|
||||||
|
|
||||||
|
### 2.1 Project Structure
|
||||||
|
```bash
|
||||||
|
mkdir mcp-server-myservice
|
||||||
|
cd mcp-server-myservice
|
||||||
|
npm init -y
|
||||||
|
npm install @modelcontextprotocol/sdk
|
||||||
|
npm install -D typescript @types/node tsx fs-extra @types/fs-extra
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.2 File Structure
|
||||||
|
```
|
||||||
|
mcp-server-myservice/
|
||||||
|
├── src/
|
||||||
|
│ ├── index.ts # Main server (or server.ts)
|
||||||
|
│ ├── clients/
|
||||||
|
│ │ └── api-client.ts # API client
|
||||||
|
│ ├── apps/ # If using MCP Apps
|
||||||
|
│ │ └── index.ts # Apps manager
|
||||||
|
│ ├── ui/ # If using MCP Apps
|
||||||
|
│ │ ├── contact-grid.html
|
||||||
|
│ │ └── dashboard.html
|
||||||
|
│ └── types/
|
||||||
|
│ └── index.ts # Shared types
|
||||||
|
├── dist/ # Build output
|
||||||
|
├── scripts/
|
||||||
|
│ └── copy-ui.js # UI build script
|
||||||
|
├── package.json
|
||||||
|
├── tsconfig.json
|
||||||
|
├── .env.example
|
||||||
|
├── .gitignore
|
||||||
|
├── .npmignore
|
||||||
|
├── Dockerfile
|
||||||
|
├── railway.json
|
||||||
|
└── README.md
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.3 Package Configuration
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "mcp-server-myservice",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"type": "module",
|
||||||
|
"main": "dist/index.js",
|
||||||
|
"bin": {
|
||||||
|
"mcp-server-myservice": "dist/index.js"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"build": "npm run build:ts && npm run build:ui",
|
||||||
|
"build:ts": "tsc",
|
||||||
|
"build:ui": "node scripts/copy-ui.js",
|
||||||
|
"dev": "tsx src/index.ts",
|
||||||
|
"start": "node dist/index.js"
|
||||||
|
},
|
||||||
|
"files": ["dist", "README.md", "LICENSE"],
|
||||||
|
"keywords": ["mcp", "mcp-server", "model-context-protocol", "myservice"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 3: Tool Design (The Most Important Phase)
|
||||||
|
|
||||||
|
### 3.1 Tool Naming Convention
|
||||||
|
✅ **Use:** `verb_noun` (snake_case)
|
||||||
|
❌ **Avoid:** camelCase, PascalCase, kebab-case
|
||||||
|
|
||||||
|
**CRUD patterns:**
|
||||||
|
- `list_contacts` (with pagination + filters)
|
||||||
|
- `get_contact` (by ID)
|
||||||
|
- `create_contact` (returns created object)
|
||||||
|
- `update_contact` (partial updates)
|
||||||
|
- `delete_contact` (confirm before delete)
|
||||||
|
- `search_contacts` (full-text search if different from list)
|
||||||
|
|
||||||
|
**Other patterns:**
|
||||||
|
- `send_email`, `schedule_appointment`, `export_report`, `analyze_pipeline`
|
||||||
|
|
||||||
|
### 3.2 Tool Metadata & Labels ⭐ CRITICAL
|
||||||
|
Every tool MUST have `_meta` with labels:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
{
|
||||||
|
name: "search_contacts",
|
||||||
|
description: "Search contacts with filters. Returns paginated results.",
|
||||||
|
inputSchema: { /* ... */ },
|
||||||
|
_meta: {
|
||||||
|
labels: {
|
||||||
|
category: "contacts", // Group by feature
|
||||||
|
access: "read", // read | write | delete
|
||||||
|
complexity: "simple", // simple | complex | batch
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Label categories to use:**
|
||||||
|
- `category`: contacts, deals, analytics, calendar, email, admin, workflows
|
||||||
|
- `access`: read, write, delete
|
||||||
|
- `complexity`: simple (1 API call), complex (multiple calls), batch (loops)
|
||||||
|
- `sensitivity`: public, internal, confidential (optional)
|
||||||
|
|
||||||
|
### 3.3 Input Schemas — Best Practices
|
||||||
|
```typescript
|
||||||
|
inputSchema: {
|
||||||
|
type: "object" as const,
|
||||||
|
properties: {
|
||||||
|
// Always describe parameters
|
||||||
|
page: {
|
||||||
|
type: "number",
|
||||||
|
description: "Page number (default 1, starts at 1)"
|
||||||
|
},
|
||||||
|
pageSize: {
|
||||||
|
type: "number",
|
||||||
|
description: "Results per page (default 50, max 100)"
|
||||||
|
},
|
||||||
|
// Use enums for fixed options
|
||||||
|
status: {
|
||||||
|
type: "string",
|
||||||
|
description: "Filter by status",
|
||||||
|
enum: ["active", "inactive", "pending"],
|
||||||
|
},
|
||||||
|
// ISO 8601 for dates
|
||||||
|
createdAfter: {
|
||||||
|
type: "string",
|
||||||
|
description: "Filter created after (ISO 8601: 2026-02-03T14:00:00Z)"
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// Mark required fields explicitly
|
||||||
|
required: ["contactId"],
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.4 Pagination (Mandatory for List Operations)
|
||||||
|
Every `list_` or `search_` tool MUST support pagination:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
{
|
||||||
|
name: "list_contacts",
|
||||||
|
description: "List contacts with pagination and filters",
|
||||||
|
inputSchema: {
|
||||||
|
type: "object" as const,
|
||||||
|
properties: {
|
||||||
|
page: { type: "number", description: "Page number (default 1)" },
|
||||||
|
pageSize: { type: "number", description: "Results per page (default 50, max 100)" },
|
||||||
|
query: { type: "string", description: "Search query (optional)" },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
_meta: {
|
||||||
|
labels: { category: "contacts", access: "read", complexity: "simple" },
|
||||||
|
},
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**In handler:**
|
||||||
|
```typescript
|
||||||
|
case "list_contacts": {
|
||||||
|
const { page = 1, pageSize = 50, query } = args;
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append("page", String(page));
|
||||||
|
params.append("pageSize", String(Math.min(Number(pageSize), 100))); // Cap at API max
|
||||||
|
if (query) params.append("query", query);
|
||||||
|
|
||||||
|
return await client.get(`/contacts?${params}`);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 4: Lazy-Loaded Resources ⭐ NEW
|
||||||
|
|
||||||
|
### 4.1 When to Use Resources vs Tools
|
||||||
|
**Use resources for:**
|
||||||
|
- Large datasets (contact lists, transaction history)
|
||||||
|
- Frequently changing data (real-time dashboards)
|
||||||
|
- Reference data (documentation, schemas)
|
||||||
|
- User-specific data (per-user settings, dashboards)
|
||||||
|
|
||||||
|
**Use tools for:**
|
||||||
|
- Operations with parameters (search, filter, create)
|
||||||
|
- One-time fetches
|
||||||
|
- Mutations (create, update, delete)
|
||||||
|
|
||||||
|
### 4.2 Resource Setup
|
||||||
|
```typescript
|
||||||
|
import { ListResourcesRequestSchema, ReadResourceRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
||||||
|
|
||||||
|
// Declare resources capability
|
||||||
|
const server = new Server(
|
||||||
|
{ name: "myservice-mcp", version: "1.0.0" },
|
||||||
|
{ capabilities: { tools: {}, resources: {} } } // ✅ Enable resources
|
||||||
|
);
|
||||||
|
|
||||||
|
// List available resources (metadata only)
|
||||||
|
server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
||||||
|
return {
|
||||||
|
resources: [
|
||||||
|
{
|
||||||
|
uri: "myservice://contacts/all",
|
||||||
|
name: "All Contacts",
|
||||||
|
description: "Complete contact database (lazy-loaded)",
|
||||||
|
mimeType: "application/json",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
uri: "myservice://analytics/dashboard",
|
||||||
|
name: "Analytics Dashboard",
|
||||||
|
description: "Real-time analytics data",
|
||||||
|
mimeType: "application/json",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// Fetch resource content on-demand
|
||||||
|
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
||||||
|
const { uri } = request.params;
|
||||||
|
|
||||||
|
switch (uri) {
|
||||||
|
case "myservice://contacts/all": {
|
||||||
|
const contacts = await client.get("/contacts?limit=1000"); // Fetch when requested
|
||||||
|
return {
|
||||||
|
contents: [{
|
||||||
|
uri,
|
||||||
|
mimeType: "application/json",
|
||||||
|
text: JSON.stringify(contacts, null, 2),
|
||||||
|
}],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
case "myservice://analytics/dashboard": {
|
||||||
|
const analytics = await client.get("/analytics/dashboard");
|
||||||
|
return {
|
||||||
|
contents: [{
|
||||||
|
uri,
|
||||||
|
mimeType: "application/json",
|
||||||
|
text: JSON.stringify(analytics, null, 2),
|
||||||
|
}],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new Error(`Unknown resource: ${uri}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.3 Resource Templates (Dynamic URIs)
|
||||||
|
```typescript
|
||||||
|
server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
||||||
|
return {
|
||||||
|
resourceTemplates: [
|
||||||
|
{
|
||||||
|
uriTemplate: "myservice://contact/{id}",
|
||||||
|
name: "Contact Details",
|
||||||
|
description: "Full contact record by ID",
|
||||||
|
mimeType: "application/json",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
||||||
|
const { uri } = request.params;
|
||||||
|
|
||||||
|
const contactMatch = uri.match(/^myservice:\/\/contact\/(.+)$/);
|
||||||
|
if (contactMatch) {
|
||||||
|
const contactId = contactMatch[1];
|
||||||
|
const contact = await client.get(`/contacts/${contactId}`);
|
||||||
|
return {
|
||||||
|
contents: [{
|
||||||
|
uri,
|
||||||
|
mimeType: "application/json",
|
||||||
|
text: JSON.stringify(contact, null, 2),
|
||||||
|
}],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(`Unknown resource: ${uri}`);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 5: MCP Apps (If Applicable)
|
||||||
|
|
||||||
|
### 5.1 Should You Build Apps?
|
||||||
|
Build MCP Apps if you have:
|
||||||
|
- ✅ Visual data (grids, cards, dashboards)
|
||||||
|
- ✅ 3+ UI opportunities identified in Phase 1
|
||||||
|
- ✅ Complex data relationships (better shown than described)
|
||||||
|
- ✅ Interactive workflows (drag-drop, forms)
|
||||||
|
|
||||||
|
Skip apps if:
|
||||||
|
- ❌ Simple CRUD operations only
|
||||||
|
- ❌ All operations return small JSON objects
|
||||||
|
- ❌ No visual benefit
|
||||||
|
|
||||||
|
### 5.2 App Architecture
|
||||||
|
See `mcp-apps-integration` skill for full details. Quick checklist:
|
||||||
|
|
||||||
|
- [ ] Create `src/apps/index.ts` — MCPAppsManager class
|
||||||
|
- [ ] Create `src/ui/` directory — HTML components
|
||||||
|
- [ ] Register resource handlers for UI files
|
||||||
|
- [ ] Add app tools with `_meta.ui.resourceUri`
|
||||||
|
- [ ] Implement `ListResourcesRequestSchema` handler
|
||||||
|
- [ ] Implement `ReadResourceRequestSchema` handler
|
||||||
|
- [ ] Add `build:ui` script to copy HTML to `dist/app-ui/`
|
||||||
|
|
||||||
|
### 5.3 App Tool Naming
|
||||||
|
**Pattern:** `view_` or `show_` prefix
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
{
|
||||||
|
name: "view_contact_grid",
|
||||||
|
description: "Display contact search results in a data grid (visual UI component)",
|
||||||
|
inputSchema: { /* ... */ },
|
||||||
|
_meta: {
|
||||||
|
labels: { category: "contacts", access: "read", complexity: "simple" },
|
||||||
|
ui: { resourceUri: "ui://myservice/contact-grid" },
|
||||||
|
},
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.4 Common App Patterns
|
||||||
|
- **Contact Grid** — Search results table
|
||||||
|
- **Dashboard** — Multi-widget analytics view
|
||||||
|
- **Pipeline Board** — Kanban with drag-drop
|
||||||
|
- **Opportunity Card** — Detail view for single record
|
||||||
|
- **Calendar View** — Appointment/event calendar
|
||||||
|
- **Timeline** — Activity feed
|
||||||
|
|
||||||
|
Reference: 11 production GHL apps in `/Users/jakeshore/.clawdbot/workspace/mcp-diagrams/ghl-mcp-apps-only/`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 6: Progress & Logging
|
||||||
|
|
||||||
|
### 6.1 Progress Notifications (For Long Operations)
|
||||||
|
Any operation taking >5 seconds MUST send progress updates:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
if (name === "import_contacts") {
|
||||||
|
const progressToken = request.params._meta?.progressToken;
|
||||||
|
|
||||||
|
if (progressToken) {
|
||||||
|
await server.notification({
|
||||||
|
method: "notifications/progress",
|
||||||
|
params: {
|
||||||
|
progressToken,
|
||||||
|
progress: 0.3, // 30%
|
||||||
|
total: 1.0,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ... do work
|
||||||
|
|
||||||
|
if (progressToken) {
|
||||||
|
await server.notification({
|
||||||
|
method: "notifications/progress",
|
||||||
|
params: { progressToken, progress: 1.0, total: 1.0 },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.2 Structured Logging
|
||||||
|
Log important operations for debugging:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { LoggingLevel } from "@modelcontextprotocol/sdk/types.js";
|
||||||
|
|
||||||
|
await server.notification({
|
||||||
|
method: "notifications/message",
|
||||||
|
params: {
|
||||||
|
level: LoggingLevel.Info,
|
||||||
|
logger: "myservice",
|
||||||
|
data: {
|
||||||
|
operation: "create_contact",
|
||||||
|
contactId: newContact.id,
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
**When to log:**
|
||||||
|
- Info: Successful operations (create, update, delete)
|
||||||
|
- Warning: Rate limits, retries, fallbacks
|
||||||
|
- Error: API failures, validation errors
|
||||||
|
- Debug: Detailed request/response data (dev only)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 7: Error Handling (Production-Ready)
|
||||||
|
|
||||||
|
### 7.1 Tool Handler Error Wrapping
|
||||||
|
```typescript
|
||||||
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
||||||
|
const { name, arguments: args } = request.params;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await handleTool(client, name, args || {});
|
||||||
|
return {
|
||||||
|
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
const message = error instanceof Error ? error.message : String(error);
|
||||||
|
|
||||||
|
// Log the error
|
||||||
|
await server.notification({
|
||||||
|
method: "notifications/message",
|
||||||
|
params: {
|
||||||
|
level: LoggingLevel.Error,
|
||||||
|
logger: "myservice",
|
||||||
|
data: { tool: name, error: message },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
content: [{ type: "text", text: `Error: ${message}` }],
|
||||||
|
isError: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.2 API Client Error Handling
|
||||||
|
```typescript
|
||||||
|
async request(endpoint: string, options: RequestInit = {}) {
|
||||||
|
const response = await fetch(url, options);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorText = await response.text();
|
||||||
|
|
||||||
|
// Parse API error if JSON
|
||||||
|
try {
|
||||||
|
const errorJson = JSON.parse(errorText);
|
||||||
|
throw new Error(
|
||||||
|
`API error: ${response.status} - ${errorJson.message || errorJson.error || errorText}`
|
||||||
|
);
|
||||||
|
} catch {
|
||||||
|
throw new Error(
|
||||||
|
`API error: ${response.status} ${response.statusText} - ${errorText}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.json();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 8: Prompts (Optional but Recommended)
|
||||||
|
|
||||||
|
### 8.1 When to Add Prompts
|
||||||
|
Add prompts for:
|
||||||
|
- Common analysis workflows (e.g., "Analyze pipeline health")
|
||||||
|
- Report generation (e.g., "Generate contact summary")
|
||||||
|
- Quick actions (e.g., "Find overdue tasks")
|
||||||
|
- Data exploration (e.g., "Show top performers")
|
||||||
|
|
||||||
|
### 8.2 Prompt Implementation
|
||||||
|
```typescript
|
||||||
|
import { ListPromptsRequestSchema, GetPromptRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
||||||
|
|
||||||
|
server.setRequestHandler(ListPromptsRequestSchema, async () => {
|
||||||
|
return {
|
||||||
|
prompts: [
|
||||||
|
{
|
||||||
|
name: "contact_summary",
|
||||||
|
description: "Generate comprehensive contact summary with recent activity",
|
||||||
|
arguments: [
|
||||||
|
{ name: "contactId", description: "Contact ID", required: true },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
server.setRequestHandler(GetPromptRequestSchema, async (request) => {
|
||||||
|
const { name, arguments: args } = request.params;
|
||||||
|
|
||||||
|
if (name === "contact_summary") {
|
||||||
|
const { contactId } = args;
|
||||||
|
const contact = await client.get(`/contacts/${contactId}`);
|
||||||
|
const activities = await client.get(`/contacts/${contactId}/activities`);
|
||||||
|
|
||||||
|
return {
|
||||||
|
description: `Summary for ${contact.name}`,
|
||||||
|
messages: [{
|
||||||
|
role: "user",
|
||||||
|
content: {
|
||||||
|
type: "text",
|
||||||
|
text: `Generate a comprehensive summary:\n\n${JSON.stringify({ contact, activities }, null, 2)}`,
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(`Unknown prompt: ${name}`);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 9: Testing Checklist
|
||||||
|
|
||||||
|
### 9.1 Local Testing
|
||||||
|
- [ ] All tools compile without errors (`npm run build`)
|
||||||
|
- [ ] Server starts successfully (`npm start`)
|
||||||
|
- [ ] Environment variables validated on startup
|
||||||
|
- [ ] Test each tool in Claude Desktop
|
||||||
|
- [ ] Test pagination (page 1, page 2)
|
||||||
|
- [ ] Test error cases (invalid IDs, missing params)
|
||||||
|
- [ ] Test apps render correctly (if applicable)
|
||||||
|
- [ ] Check logs in Claude Desktop console
|
||||||
|
|
||||||
|
### 9.2 Performance Testing
|
||||||
|
- [ ] List operations return in <2 seconds
|
||||||
|
- [ ] Lazy-loaded resources only fetch when requested
|
||||||
|
- [ ] No unnecessary API calls
|
||||||
|
- [ ] Pagination caps at API maximum
|
||||||
|
- [ ] Progress notifications for operations >5 seconds
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 10: Documentation
|
||||||
|
|
||||||
|
### 10.1 README.md Structure
|
||||||
|
```markdown
|
||||||
|
# MCP Server for MyService
|
||||||
|
|
||||||
|
MCP integration for MyService. Enables Claude Desktop to [core value prop].
|
||||||
|
|
||||||
|
## Features
|
||||||
|
- ✅ List/search/CRUD contacts
|
||||||
|
- ✅ Analytics dashboard (MCP App)
|
||||||
|
- ✅ Pipeline visualization (MCP App)
|
||||||
|
- ✅ Progress tracking for imports
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
[npx / manual / docker options]
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
[Environment variables with .env.example]
|
||||||
|
|
||||||
|
## Available Tools
|
||||||
|
[List of tools with descriptions]
|
||||||
|
|
||||||
|
## MCP Apps (Rich UI)
|
||||||
|
[List of app tools with screenshots]
|
||||||
|
|
||||||
|
## Development
|
||||||
|
[Build/dev instructions]
|
||||||
|
```
|
||||||
|
|
||||||
|
### 10.2 .env.example
|
||||||
|
```bash
|
||||||
|
# MyService API Credentials
|
||||||
|
MY_SERVICE_API_KEY=your_api_key_here
|
||||||
|
MY_SERVICE_API_SECRET=your_secret_here
|
||||||
|
|
||||||
|
# Optional: Override base URL
|
||||||
|
# MY_SERVICE_BASE_URL=https://sandbox.api.myservice.com
|
||||||
|
|
||||||
|
# Optional: Logging
|
||||||
|
# LOG_LEVEL=debug
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 11: Deployment
|
||||||
|
|
||||||
|
### 11.1 Docker
|
||||||
|
- [ ] Multi-stage Dockerfile
|
||||||
|
- [ ] .dockerignore file
|
||||||
|
- [ ] Test build locally
|
||||||
|
- [ ] Test run locally
|
||||||
|
|
||||||
|
### 11.2 Railway
|
||||||
|
- [ ] railway.json with build + start commands
|
||||||
|
- [ ] Environment variables documented
|
||||||
|
- [ ] Test deployment
|
||||||
|
|
||||||
|
### 11.3 npm Publishing
|
||||||
|
- [ ] `bin` field in package.json
|
||||||
|
- [ ] `files` field includes only dist/
|
||||||
|
- [ ] .npmignore excludes src/, .env
|
||||||
|
- [ ] Keywords for discoverability
|
||||||
|
- [ ] Test `npx` installation locally
|
||||||
|
|
||||||
|
### 11.4 GitHub
|
||||||
|
- [ ] README.md complete
|
||||||
|
- [ ] LICENSE file
|
||||||
|
- [ ] .gitignore excludes node_modules, dist, .env
|
||||||
|
- [ ] GitHub Actions for CI/CD (optional)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Production Checklist (Final Review)
|
||||||
|
|
||||||
|
### Code Quality
|
||||||
|
- [ ] All tools have `_meta.labels`
|
||||||
|
- [ ] All parameters have descriptions
|
||||||
|
- [ ] Required fields marked explicitly
|
||||||
|
- [ ] Pagination implemented for list operations
|
||||||
|
- [ ] Error handling in all tool handlers
|
||||||
|
- [ ] No hardcoded API keys or secrets
|
||||||
|
- [ ] Logging for important operations
|
||||||
|
|
||||||
|
### Features
|
||||||
|
- [ ] Lazy-loaded resources for large datasets
|
||||||
|
- [ ] Progress notifications for long operations (>5s)
|
||||||
|
- [ ] MCP Apps for visual data (if applicable)
|
||||||
|
- [ ] Prompts for common workflows (if applicable)
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
- [ ] README with installation instructions
|
||||||
|
- [ ] .env.example with all required variables
|
||||||
|
- [ ] Tool descriptions clear and helpful
|
||||||
|
- [ ] Examples in README
|
||||||
|
|
||||||
|
### Deployment
|
||||||
|
- [ ] Compiles without errors
|
||||||
|
- [ ] Runs in Claude Desktop
|
||||||
|
- [ ] Docker image builds (if using Docker)
|
||||||
|
- [ ] Railway deploys successfully (if using Railway)
|
||||||
|
- [ ] npm package installs via npx (if publishing)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Anti-Patterns to Avoid
|
||||||
|
|
||||||
|
❌ **No labels on tools** — Always add `_meta.labels`
|
||||||
|
❌ **Loading all data upfront** — Use lazy-loaded resources
|
||||||
|
❌ **No pagination** — Every list operation needs page/pageSize
|
||||||
|
❌ **Silent failures** — Always log errors and return clear messages
|
||||||
|
❌ **No progress for slow ops** — Add progress notifications for >5s operations
|
||||||
|
❌ **Building apps when not needed** — Only build apps if visually beneficial
|
||||||
|
❌ **Missing descriptions** — Every parameter needs a description
|
||||||
|
❌ **No environment validation** — Check env vars on startup
|
||||||
|
❌ **Skipping error handling** — Wrap all tool handlers in try-catch
|
||||||
|
❌ **Generic error messages** — Be specific ("Contact not found" not "Error")
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Reference Materials
|
||||||
|
|
||||||
|
- **Skills:**
|
||||||
|
- `mcp-server-development` — Full TypeScript patterns
|
||||||
|
- `mcp-apps-integration` — MCP Apps guide
|
||||||
|
- `mcp-deployment` — Docker/Railway/npm
|
||||||
|
|
||||||
|
- **Example Servers:**
|
||||||
|
- `/Users/jakeshore/.clawdbot/workspace/mcp-diagrams/mcp-servers/`
|
||||||
|
- 30 production servers with all patterns
|
||||||
|
|
||||||
|
- **Example Apps:**
|
||||||
|
- `/Users/jakeshore/.clawdbot/workspace/mcp-diagrams/ghl-mcp-apps-only/`
|
||||||
|
- 11 production apps with UI components
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## TL;DR — The Golden Rules
|
||||||
|
|
||||||
|
1. **Labels on every tool** — category, access, complexity
|
||||||
|
2. **Lazy-load large datasets** — Use resources, not tools
|
||||||
|
3. **Paginate everything** — page/pageSize on all lists
|
||||||
|
4. **Progress for slow ops** — >5 seconds = progress notifications
|
||||||
|
5. **Apps for visual data** — Grids, dashboards, cards, timelines
|
||||||
|
6. **Log important operations** — Info, Warning, Error levels
|
||||||
|
7. **Handle errors gracefully** — Clear messages, no silent failures
|
||||||
|
8. **Document thoroughly** — README, .env.example, descriptions
|
||||||
|
9. **Test before shipping** — All tools work in Claude Desktop
|
||||||
|
10. **Deploy with confidence** — Docker, Railway, npm ready to go
|
||||||
|
|
||||||
|
**Follow this blueprint and your MCP servers will be production-ready, usable, and optimized for February 2026.**
|
||||||
1242
skills/mcp-server-development/SKILL.md
Normal file
1242
skills/mcp-server-development/SKILL.md
Normal file
File diff suppressed because it is too large
Load Diff
7
skills/mcp-skill/.clawdhub/origin.json
Normal file
7
skills/mcp-skill/.clawdhub/origin.json
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"registry": "https://clawhub.ai",
|
||||||
|
"slug": "mcp-skill",
|
||||||
|
"installedVersion": "1.0.0",
|
||||||
|
"installedAt": 1770110462685
|
||||||
|
}
|
||||||
1
skills/mcp-skill/README.md
Normal file
1
skills/mcp-skill/README.md
Normal file
@ -0,0 +1 @@
|
|||||||
|
MCP Skill
|
||||||
14
skills/mcp-skill/SKILL.md
Normal file
14
skills/mcp-skill/SKILL.md
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
# MCP Skill
|
||||||
|
|
||||||
|
This skill wraps the MCP at https://mcp.exa.ai/mcp for various tools such as web search, deep research, and more.
|
||||||
|
|
||||||
|
## Tools Included
|
||||||
|
- web_search_exa
|
||||||
|
- web_search_advanced_exa
|
||||||
|
- get_code_context_exa
|
||||||
|
- deep_search_exa
|
||||||
|
- crawling_exa
|
||||||
|
- company_research_exa
|
||||||
|
- linkedin_search_exa
|
||||||
|
- deep_researcher_start
|
||||||
|
- deep_researcher_check
|
||||||
Loading…
x
Reference in New Issue
Block a user