43 KiB
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
.protoservice 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):
-
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?
-
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)
-
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
-
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
-
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"?
-
Webhooks / events — Note but don't deep-dive.
- Available webhook events (for future reference)
- Delivery format
-
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):
- If OpenAPI spec exists, download it and parse programmatically
- Extract all paths + methods into a spreadsheet/list
- Group by URL prefix (e.g.,
/contacts/*,/deals/*,/invoices/*) - Count endpoints per group
- Read the 2-3 most important endpoints per group in detail
- 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.
# {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.*