First Version
This commit is contained in:
@@ -0,0 +1,20 @@
|
||||
# MCP (Model Context Protocol)
|
||||
|
||||
External tool integration via Model Context Protocol servers.
|
||||
|
||||
## Files
|
||||
|
||||
- [mcp.md](../mcp.md) — McpManager, transports (stdio/HTTP), protocol version + `MCP-Protocol-Version` header, `tools/list` pagination, structured tool results, media tool results (image/audio/resource → `data/mcp_media/` + `/api/mcp-media/`), cancellation (`notifications/cancelled`) + Tasks (block-and-poll `tasks/get`/`result`/`cancel`), enable/disable, tool registration
|
||||
- **Specification reference** (in `specs/` subdirectory) — one file per official MCP spec revision, as background for future Skald MCP work. See [specs/index.md](specs/index.md) for the comparative overview:
|
||||
- [specs/2026-07-28-draft.md](specs/2026-07-28-draft.md) — Draft / RC: stateless redesign, per-request negotiation, extensions framework
|
||||
- [specs/2025-11-25.md](specs/2025-11-25.md) — Latest Stable: OIDC Discovery, icons, URL-mode elicitation, experimental Tasks
|
||||
- [specs/2025-06-18.md](specs/2025-06-18.md) — Stable: Streamable HTTP, Elicitation, structured tool output
|
||||
- [specs/2024-11-05.md](specs/2024-11-05.md) — Legacy: first public release
|
||||
- **Servers** (in `servers/` subdirectory):
|
||||
- [servers/gmail.md](servers/gmail.md) — Gmail read+modify+send MCP server (custom Python)
|
||||
- [servers/gcal.md](servers/gcal.md) — Google Calendar read+write MCP server (custom Python)
|
||||
- [servers/gmaps.md](servers/gmaps.md) — Google Maps transit/directions MCP server (custom Python)
|
||||
- [servers/serpapi_flights.md](servers/serpapi_flights.md) — SerpAPI Google Flights search MCP server (custom Python)
|
||||
- [servers/whatsapp.md](servers/whatsapp.md) — WhatsApp read+send MCP server (custom Node.js)
|
||||
|
||||
See [../index.md#mcp-model-context-protocol](../index.md#mcp-model-context-protocol) for navigation.
|
||||
@@ -0,0 +1,213 @@
|
||||
# Google Calendar MCP Server (gcal)
|
||||
|
||||
## Overview
|
||||
|
||||
A Python MCP server providing **read + write** access to Google Calendar via the Google Calendar API v3.
|
||||
|
||||
**Server name:** `gcal`
|
||||
**Transport:** `stdio` (spawns `python3 scripts/gcal_mcp_server.py`)
|
||||
**Location:** `scripts/gcal_mcp_server.py`
|
||||
|
||||
The server also emits push notifications (`event/new_calendar_event`) to the agent when new events are created on the primary calendar (polled every 5 min).
|
||||
|
||||
---
|
||||
|
||||
## Tools
|
||||
|
||||
All tools are callable as `mcp__gcal__<tool>`; the table lists the bare `<tool>` names.
|
||||
|
||||
| Tool | Required params | Optional params | Description |
|
||||
|------|-----------------|-----------------|-------------|
|
||||
| `status` | *(none)* | *(none)* | Self-check: verifies credentials load, the token refreshes, and the Calendar API responds. Call first when another gcal tool fails. |
|
||||
| `list_calendars` | *(none)* | *(none)* | List calendars accessible to the user (surfaces `calendar_id` values). |
|
||||
| `list_events` | *(none)* | `calendar_id`, `time_min`, `time_max`, `max_results`, `full_text`, `time_zone` | Chronological event listing. `time_min` defaults to NOW. |
|
||||
| `get_event` | `event_id` | `calendar_id` | Read a single event by ID, including attendees + RSVP status. |
|
||||
| `create_event` | `summary`, `start`, `end` | `description`, `location`, `attendees`, `recurrence`, `time_zone`, `calendar_id`, `reminders` | Create an event; returns ID + HTML link. |
|
||||
| `update_event` | `event_id` | `summary`, `start`, `end`, `description`, `location`, `attendees`, `time_zone`, `calendar_id`, `reminders` | Patch fields of an existing event; omitted fields are preserved. |
|
||||
| `delete_event` | `event_id` | `calendar_id` | Permanently delete an event. Irreversible. |
|
||||
| `respond_to_event` | `event_id`, `response` | `calendar_id` | Set RSVP (`accepted`, `declined`, `tentative`, `needsAction`). |
|
||||
|
||||
### `reminders` format
|
||||
|
||||
`reminders` (on `create_event` / `update_event`) accepts a JSON array whose items are **either** integers (minutes before the event, popup reminder) **or** objects `{"method": "popup"|"email", "minutes": <int>}`. Overrides calendar defaults. Examples: `[10, 30, 60]` or `[{"method": "email", "minutes": 60}, {"method": "popup", "minutes": 10}]`.
|
||||
|
||||
---
|
||||
|
||||
## Authentication
|
||||
|
||||
### Credentials File
|
||||
|
||||
OAuth 2.0 user credentials are stored in:
|
||||
- **Default path:** `./secrets/google_creds.json` (relative to project root)
|
||||
- **Override:** `GOOGLE_CREDS_PATH` env var
|
||||
|
||||
Required OAuth scope (granted by the setup script):
|
||||
```
|
||||
https://www.googleapis.com/auth/calendar
|
||||
```
|
||||
|
||||
### Token Refresh
|
||||
|
||||
The access token expires after ~1 hour. The server **refreshes it automatically** in two places:
|
||||
1. At startup, if the cached token is expired.
|
||||
2. **Mid-session**, on the first 401 / `RefreshError` from any tool — `_call()` refreshes once, persists the new token to `secrets/google_creds.json`, and retries the call.
|
||||
|
||||
If the refresh token itself has been revoked or expired, `status` and every tool return an actionable `Error:` instructing to re-run `scripts/gcal_oauth_setup.py`.
|
||||
|
||||
### Git Safety
|
||||
|
||||
`./secrets/` is in `.gitignore` — credentials are never committed.
|
||||
|
||||
---
|
||||
|
||||
## Setup
|
||||
|
||||
### 1. Create OAuth credentials
|
||||
|
||||
1. Go to [Google Cloud Console → Credentials](https://console.cloud.google.com/apis/credentials).
|
||||
2. Create an **OAuth client ID** of type *Desktop app*.
|
||||
3. Put `client_id` and `client_secret` in `secrets/google_oauth_client.json`:
|
||||
```json
|
||||
{ "client_id": "....apps.googleusercontent.com", "client_secret": "GOCSPX-..." }
|
||||
```
|
||||
4. Enable the **Google Calendar API** under APIs & Services.
|
||||
|
||||
### 2. Run the OAuth flow
|
||||
|
||||
```bash
|
||||
.venv/bin/python scripts/gcal_oauth_setup.py
|
||||
```
|
||||
|
||||
Opens a browser, asks for the Calendar scope, and saves the token to `secrets/google_creds.json`.
|
||||
|
||||
### 3. Install dependencies
|
||||
|
||||
`google-auth`, `google-auth-oauthlib`, `google-api-python-client` are already listed in `requirements.txt`. Re-running `./run.sh` installs them automatically; or:
|
||||
|
||||
```bash
|
||||
uv pip install -r requirements.txt
|
||||
```
|
||||
|
||||
### 4. Register the server with the agent
|
||||
|
||||
```
|
||||
register_mcp(
|
||||
name="gcal",
|
||||
transport="stdio",
|
||||
command="python3",
|
||||
args=["scripts/gcal_mcp_server.py"]
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Self-check: is Calendar working?
|
||||
|
||||
```
|
||||
mcp__gcal__status()
|
||||
```
|
||||
Returns `Status: READY ✅ (ok)` if credentials load, the token refreshes, and the Calendar API responds; otherwise a `Status: … ❌/⚠️ (…)` report with a `What to do:` list. Call this first whenever another gcal tool fails.
|
||||
|
||||
### List events in the next 7 days
|
||||
|
||||
```
|
||||
mcp__gcal__list_events(
|
||||
calendar_id="primary",
|
||||
time_min="2026-06-22T00:00:00+02:00",
|
||||
time_max="2026-06-29T00:00:00+02:00",
|
||||
max_results=50,
|
||||
time_zone="Europe/Rome"
|
||||
)
|
||||
```
|
||||
`time_min` / `time_max` are also accepted as `start_time` / `end_time` for backward compatibility. If `time_min` is omitted it defaults to NOW.
|
||||
|
||||
### Search events
|
||||
|
||||
```
|
||||
mcp__gcal__list_events(
|
||||
full_text="dentist",
|
||||
time_min="2026-01-01T00:00:00+01:00",
|
||||
time_max="2026-12-31T00:00:00+01:00"
|
||||
)
|
||||
```
|
||||
|
||||
### Get / create / RSVP
|
||||
|
||||
```
|
||||
mcp__gcal__get_event(event_id="<id>")
|
||||
|
||||
mcp__gcal__create_event(
|
||||
summary="Dentist",
|
||||
start="2026-07-01T09:00:00",
|
||||
end="2026-07-01T10:00:00",
|
||||
reminders=[30, {"method": "email", "minutes": 120}]
|
||||
)
|
||||
|
||||
mcp__gcal__respond_to_event(event_id="<id>", response="accepted")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Enable / Disable
|
||||
|
||||
```
|
||||
toggle_item(kind="mcp", id="gcal", enabled=false) # disable
|
||||
toggle_item(kind="mcp", id="gcal", enabled=true) # re-enable
|
||||
restart # required for changes to take effect
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Dependencies
|
||||
|
||||
| Package | Version | Purpose |
|
||||
|---------|---------|---------|
|
||||
| `google-api-python-client` | 2.197.0 | Google API Python client (Calendar v3) |
|
||||
| `google-auth` | 2.55.0 | Auth / token refresh |
|
||||
| `google-auth-oauthlib` | 1.4.0 | Local OAuth flow (setup script) |
|
||||
|
||||
---
|
||||
|
||||
## Error Handling
|
||||
|
||||
Every Google API exception is mapped to an actionable `Error:` string (flagged with `isError: true`):
|
||||
|
||||
| Condition | Response |
|
||||
|-----------|----------|
|
||||
| Credentials file missing | `"Error: Credentials file not found at …. Run scripts/gcal_oauth_setup.py…"` |
|
||||
| Token refresh failed / revoked | `"Error: Calendar API token refresh failed …. Re-run scripts/gcal_oauth_setup.py…"` |
|
||||
| HTTP 401 | `"Error: Calendar API rejected the access token (401)…. Re-run scripts/gcal_oauth_setup.py…"` |
|
||||
| HTTP 403 | `"Error: Calendar API returned 403 Forbidden. The OAuth scopes … are insufficient, or the Calendar API is disabled in the Google Cloud Console…"` |
|
||||
| HTTP 404 | `"Error: Calendar API returned 404 Not Found. Check the event/calendar ID…"` |
|
||||
| HTTP 429 | `"Error: Calendar API rate limit exceeded (429). Wait a moment and retry."` |
|
||||
| HTTP 400 | `"Error: Calendar API rejected the request as invalid (400). Check the parameters. Detail: …"` |
|
||||
| HTTP 5xx | `"Error: Calendar API returned a server error (HTTP …). Retry in a moment."` |
|
||||
| Missing required param | `"Error: Missing required parameter '<name>'"` |
|
||||
| Unknown tool | `"Error: Unknown tool: <name>"` |
|
||||
|
||||
All errors are logged to stderr with the `[gcal_mcp]` prefix.
|
||||
|
||||
---
|
||||
|
||||
## Protocol
|
||||
|
||||
Implements JSON-RPC 2.0 over stdio (same as gmail / gmaps / whatsapp servers):
|
||||
|
||||
- **Requests:** read from stdin, one JSON object per line
|
||||
- **Responses:** written to stdout
|
||||
- **Notifications:** server-initiated (`event/new_calendar_event`), no `id`
|
||||
- **Logs:** stderr only, prefixed `[gcal_mcp]`
|
||||
|
||||
Supported methods: `initialize`, `notifications/initialized`, `tools/list`, `tools/call`.
|
||||
|
||||
---
|
||||
|
||||
## When to Update This File
|
||||
|
||||
- A tool is added, removed, or renamed
|
||||
- Auth mechanism changes
|
||||
- Credential path or scope changes
|
||||
- New error cases are mapped
|
||||
- Protocol version changes
|
||||
@@ -0,0 +1,283 @@
|
||||
# Gmail MCP Server (gmail)
|
||||
|
||||
## Overview
|
||||
|
||||
A Python MCP server providing **read, modify, and send** access to Gmail via the Gmail API v1.
|
||||
|
||||
**Server name:** `gmail`
|
||||
**Transport:** `stdio` (spawns `python3 scripts/gmail_mcp_server.py`)
|
||||
**Location:** `scripts/gmail_mcp_server.py`
|
||||
|
||||
The server also emits push notifications (`event/new_email`) to the agent when new mail lands in the INBOX (polled every 60 s via the Gmail History API).
|
||||
|
||||
### Permissions (safe by design)
|
||||
|
||||
| Capability | Yes/No |
|
||||
|------------|--------|
|
||||
| Read messages & threads | ✅ |
|
||||
| Search messages | ✅ |
|
||||
| List labels | ✅ |
|
||||
| Modify labels (mark read, star, archive) | ✅ |
|
||||
| Send email | ✅ |
|
||||
| Create labels | ✅ |
|
||||
| Download attachments | ✅ |
|
||||
| Trash message (reversible) | ❌ *removed from tools* |
|
||||
| Untrash message | ❌ *removed from tools* |
|
||||
| Permanently delete | ❌ *scope not granted* |
|
||||
|
||||
The server uses the `gmail.modify` + `gmail.labels` scopes, which allow all operations **except permanent deletion**.
|
||||
|
||||
---
|
||||
|
||||
## Tools
|
||||
|
||||
All tools are callable as `mcp__gmail__<tool>`; the table lists the bare `<tool>` names.
|
||||
|
||||
| Tool | Required params | Optional params | Description |
|
||||
|------|-----------------|-----------------|-------------|
|
||||
| `status` | *(none)* | *(none)* | Self-check: verifies credentials load, the token refreshes, and the Gmail API responds. Call first when another gmail tool fails. |
|
||||
| `list_messages` | *(none)* | `query`, `max_results`, `label_ids`, `page_token` | List messages with optional Gmail search query and label filter. Returns subject, sender, date, message/thread IDs. Pass `page_token` (from a previous response) to fetch the next page. |
|
||||
| `get_message` | `message_id` | `include_body` | Read a single message by ID (body included by default, truncated at 10000 chars). HTML-only emails are converted to readable text; attachment filenames are listed. |
|
||||
| `get_thread` | `thread_id` | *(none)* | Read all messages in a thread, newest last. |
|
||||
| `list_labels` | *(none)* | *(none)* | List labels with total + unread counts (resolves label IDs). |
|
||||
| `modify_message` | `message_id` | `add_labels`, `remove_labels` | Add/remove labels (mark read, archive, star). Each label arg accepts a string or array. |
|
||||
| `send_message` | `to`, `subject`, `body` | `cc`, `bcc`, `in_reply_to`, `thread_id`, `attachments` | Send an email; supports in-thread replies and file attachments. |
|
||||
| `get_profile` | *(none)* | *(none)* | Account email, total message/thread count, history ID. |
|
||||
| `create_label` | `name` | `label_list_visibility`, `message_list_visibility` | Create a label; returns the new label ID. |
|
||||
| `download_attachments` | `message_id` | `folder` | Save all attachments from a message. Defaults to `data/gmail_attachments/`. |
|
||||
|
||||
> **Removed:** `search_messages` (a literal alias of `list_messages` — use `list_messages` with the `query` parameter instead, which supports full Gmail search syntax).
|
||||
|
||||
### Gmail Search Syntax
|
||||
|
||||
The `query` parameter on `list_messages` supports Gmail's native syntax:
|
||||
|
||||
| Example | Meaning |
|
||||
|---------|---------|
|
||||
| `from:john@example.com` | Messages from a sender |
|
||||
| `to:maria@example.com` | Messages to a recipient |
|
||||
| `subject:meeting` | Messages with "meeting" in subject |
|
||||
| `is:unread` / `is:starred` | Unread / starred messages |
|
||||
| `in:inbox` / `in:sent` | Messages in Inbox / Sent |
|
||||
| `after:2024/01/01` / `before:2024/06/01` | Date range |
|
||||
| `has:attachment` | Messages with attachments |
|
||||
| `label:MY_LABEL` | Messages with a custom label |
|
||||
|
||||
Combine with spaces: `from:john is:unread after:2024/06/01`.
|
||||
|
||||
---
|
||||
|
||||
## Authentication
|
||||
|
||||
### Credentials File
|
||||
|
||||
OAuth 2.0 user credentials are stored in:
|
||||
- **Default path:** `./secrets/gmail_creds.json` (relative to project root)
|
||||
- **Override:** `GMAIL_CREDS_PATH` env var
|
||||
|
||||
Required OAuth scopes (granted by the setup script):
|
||||
```
|
||||
https://www.googleapis.com/auth/gmail.modify
|
||||
https://www.googleapis.com/auth/gmail.labels
|
||||
```
|
||||
|
||||
### Token Refresh
|
||||
|
||||
The access token expires after ~1 hour. The server **refreshes it automatically** in two places:
|
||||
1. At startup, if the cached token is expired.
|
||||
2. **Mid-session**, on the first 401 / `RefreshError` from any tool — `_call()` refreshes once, persists the new token to `secrets/gmail_creds.json`, and retries the call.
|
||||
|
||||
If the refresh token itself has been revoked or expired, `status` and every tool return an actionable `Error:` instructing to re-run `scripts/gmail_oauth_setup.py`.
|
||||
|
||||
### Git Safety
|
||||
|
||||
`./secrets/` is in `.gitignore` — credentials are never committed.
|
||||
|
||||
---
|
||||
|
||||
## Setup
|
||||
|
||||
### 1. Create OAuth credentials
|
||||
|
||||
1. Go to [Google Cloud Console → Credentials](https://console.cloud.google.com/apis/credentials).
|
||||
2. Create an **OAuth client ID** of type *Desktop app*.
|
||||
3. Put `client_id` and `client_secret` in `secrets/google_oauth_client.json`:
|
||||
```json
|
||||
{ "client_id": "....apps.googleusercontent.com", "client_secret": "GOCSPX-..." }
|
||||
```
|
||||
4. Enable the **Gmail API** under APIs & Services.
|
||||
|
||||
(The OAuth client file is shared with gcal; a single Desktop client works for both.)
|
||||
|
||||
### 2. Run the OAuth flow
|
||||
|
||||
```bash
|
||||
.venv/bin/python scripts/gmail_oauth_setup.py
|
||||
```
|
||||
|
||||
Opens a browser, asks for the Gmail scopes, and saves the token to `secrets/gmail_creds.json`.
|
||||
|
||||
### 3. Install dependencies
|
||||
|
||||
`google-auth`, `google-auth-oauthlib`, `google-api-python-client` are already listed in `requirements.txt`. Re-running `./run.sh` installs them automatically; or:
|
||||
|
||||
```bash
|
||||
uv pip install -r requirements.txt
|
||||
```
|
||||
|
||||
### 4. Register the server with the agent
|
||||
|
||||
```
|
||||
register_mcp(
|
||||
name="gmail",
|
||||
transport="stdio",
|
||||
command="python3",
|
||||
args=["scripts/gmail_mcp_server.py"]
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Self-check: is Gmail working?
|
||||
|
||||
```
|
||||
mcp__gmail__status()
|
||||
```
|
||||
Returns `Status: READY ✅ (ok)` with the account email if credentials load, the token refreshes, and the Gmail API responds; otherwise a `Status: … ❌/⚠️ (…)` report with a `What to do:` list. Call this first whenever another gmail tool fails.
|
||||
|
||||
### List unread messages
|
||||
|
||||
```
|
||||
mcp__gmail__list_messages(query="is:unread", max_results=10)
|
||||
```
|
||||
|
||||
When more results exist, the response ends with a `More results available. Use page_token='...'` line. Pass that token back to page forward:
|
||||
|
||||
```
|
||||
mcp__gmail__list_messages(query="is:unread", max_results=10, page_token="09876...")
|
||||
```
|
||||
|
||||
### Read a message
|
||||
|
||||
```
|
||||
mcp__gmail__get_message(message_id="190abc123...", include_body=true)
|
||||
```
|
||||
|
||||
The body is the `text/plain` part when present; for HTML-only emails it is converted to readable text and labelled `--- Body (converted from HTML) ---`. If the message has attachments, their filenames are listed on an `Attachments:` line — download them with `download_attachments` (which only needs the `message_id`).
|
||||
|
||||
### Mark as read / archive
|
||||
|
||||
```
|
||||
mcp__gmail__modify_message(message_id="190abc123...", remove_labels=["UNREAD"])
|
||||
mcp__gmail__modify_message(message_id="190abc123...", remove_labels="INBOX") # archive
|
||||
```
|
||||
|
||||
### Send / reply in-thread
|
||||
|
||||
```
|
||||
mcp__gmail__send_message(
|
||||
to="friend@example.com",
|
||||
subject="Hello!",
|
||||
body="How are you?"
|
||||
)
|
||||
|
||||
mcp__gmail__send_message(
|
||||
to="sender@example.com",
|
||||
subject="Re: Original subject",
|
||||
body="This is my reply.",
|
||||
in_reply_to="190abc123...",
|
||||
thread_id="190thread456..."
|
||||
)
|
||||
```
|
||||
|
||||
`in_reply_to` adds RFC 2822 threading headers; `thread_id` attaches the message to the correct Gmail thread. For a proper reply visible in all clients, pass both.
|
||||
|
||||
### Send with attachments
|
||||
|
||||
```
|
||||
mcp__gmail__send_message(
|
||||
to="friend@example.com",
|
||||
subject="Documents attached",
|
||||
body="See the two files attached.",
|
||||
attachments=[
|
||||
"data/gmail_attachments/PREVENTIVO DI SPESA.pdf",
|
||||
"uploads/gmail_attachments/00035.pdf"
|
||||
]
|
||||
)
|
||||
```
|
||||
|
||||
`attachments` is an array of local file paths. Each path is either absolute or relative to the **project root**; the server reads every file from disk, guesses its MIME type, and adds it to a `multipart/mixed` message. **If any path does not exist the email is NOT sent** — the tool returns `Error: attachment not found: <path>` so the agent can correct it. Total attachment size is limited to ~25 MB (the Gmail `messages.send` request embeds the message as base64).
|
||||
|
||||
### Download attachments
|
||||
|
||||
```
|
||||
mcp__gmail__download_attachments(message_id="190abc123...")
|
||||
```
|
||||
Saves into `data/gmail_attachments/` (served via `/data/gmail_attachments/...` in the frontend). Override with `folder`.
|
||||
|
||||
---
|
||||
|
||||
## Enable / Disable
|
||||
|
||||
```
|
||||
toggle_item(kind="mcp", id="gmail", enabled=false) # disable
|
||||
toggle_item(kind="mcp", id="gmail", enabled=true) # re-enable
|
||||
restart # required for changes to take effect
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Dependencies
|
||||
|
||||
| Package | Version | Purpose |
|
||||
|---------|---------|---------|
|
||||
| `google-api-python-client` | 2.197.0 | Google API Python client (Gmail v1) |
|
||||
| `google-auth` | 2.55.0 | Auth / token refresh |
|
||||
| `google-auth-oauthlib` | 1.4.0 | Local OAuth flow (setup script) |
|
||||
|
||||
---
|
||||
|
||||
## Error Handling
|
||||
|
||||
Every Google API exception is mapped to an actionable `Error:` string (flagged with `isError: true`):
|
||||
|
||||
| Condition | Response |
|
||||
|-----------|----------|
|
||||
| Credentials file missing | `"Error: Credentials file not found at …. Run scripts/gmail_oauth_setup.py…"` |
|
||||
| Credentials invalid & no refresh token | `"Error: Credentials invalid and cannot be refreshed. Re-run scripts/gmail_oauth_setup.py."` |
|
||||
| Token refresh failed / revoked | `"Error: Gmail API token refresh failed …. Re-run scripts/gmail_oauth_setup.py…"` |
|
||||
| HTTP 401 | `"Error: Gmail API rejected the access token (401)…. Re-run scripts/gmail_oauth_setup.py…"` |
|
||||
| HTTP 403 | `"Error: Gmail API returned 403 Forbidden. The OAuth scopes … are insufficient, or the Gmail API is disabled in the Google Cloud Console…"` |
|
||||
| HTTP 404 | `"Error: Gmail API returned 404 Not Found. Check the message/thread/attachment ID."` |
|
||||
| HTTP 429 | `"Error: Gmail API rate limit exceeded (429). Wait a moment and retry."` |
|
||||
| HTTP 400 | `"Error: Gmail API rejected the request as invalid (400). Check the parameters. Detail: …"` |
|
||||
| HTTP 5xx | `"Error: Gmail API returned a server error (HTTP …). Retry in a moment."` |
|
||||
| Missing required param | `"Error: Missing required parameter '<name>'"` |
|
||||
| Unknown tool | `"Error: Unknown tool: <name>"` |
|
||||
|
||||
All errors are logged to stderr with the `[gmail_mcp]` prefix.
|
||||
|
||||
---
|
||||
|
||||
## Protocol
|
||||
|
||||
Implements JSON-RPC 2.0 over stdio (same as gcal / gmaps / whatsapp servers):
|
||||
|
||||
- **Requests:** read from stdin, one JSON object per line
|
||||
- **Responses:** written to stdout
|
||||
- **Notifications:** server-initiated (`event/new_email`), no `id`
|
||||
- **Logs:** stderr only, prefixed `[gmail_mcp]`
|
||||
|
||||
Supported methods: `initialize`, `notifications/initialized`, `tools/list`, `tools/call`.
|
||||
|
||||
---
|
||||
|
||||
## When to Update This File
|
||||
|
||||
- A tool is added, removed, or renamed
|
||||
- Auth mechanism or scopes change
|
||||
- Credential path changes
|
||||
- New error cases are mapped
|
||||
- Protocol version changes
|
||||
@@ -0,0 +1,224 @@
|
||||
# Google Maps MCP Server (gmaps)
|
||||
|
||||
## Overview
|
||||
|
||||
A Python MCP server providing **public-transit & mapping** capabilities via the Google Maps Platform APIs.
|
||||
|
||||
**Server name:** `gmaps`
|
||||
**Transport:** `stdio` (spawns `python3 scripts/gmaps_mcp_server.py`)
|
||||
**Location:** `scripts/gmaps_mcp_server.py`
|
||||
|
||||
---
|
||||
|
||||
## Tools
|
||||
|
||||
All tools are callable as `mcp__gmaps__<tool>`; the table lists the bare `<tool>` names.
|
||||
|
||||
| Tool | Required params | Optional params | Description |
|
||||
|------|-----------------|-----------------|-------------|
|
||||
| `status` | *(none)* | *(none)* | Self-check: verifies the API key is valid and the Geocoding API responds. Call first when another Maps tool fails. |
|
||||
| `directions` | `origin`, `destination` | `mode`, `departure_time`, `transit_mode`, `transit_routing_preference`, `alternatives`, `language` | Step-by-step directions (transit, driving, walking, bicycling) |
|
||||
| `geocode` | `address` | `language`, `region` | Address / place name → coordinates + place_id |
|
||||
| `reverse_geocode` | `lat`, `lng` | `language` | Coordinates → formatted address |
|
||||
| `search_places` | *(at least one of `query`/`location`)* | `radius`, `type`, `language` | Find nearby stations, stops, POIs |
|
||||
| `distance_matrix` | `origins`, `destinations` | `mode`, `language` | Travel time & distance between multiple points |
|
||||
|
||||
---
|
||||
|
||||
## Authentication
|
||||
|
||||
### API Key
|
||||
|
||||
Unlike Gmail/Calendar (OAuth), Google Maps uses a **plain API key**.
|
||||
|
||||
**Priority order:**
|
||||
|
||||
1. Environment variable `GOOGLE_MAPS_API_KEY`
|
||||
2. File `secrets/gmaps_api_key.txt` (first non-empty line)
|
||||
|
||||
The `secrets/` directory is in `.gitignore` — the key will not be committed.
|
||||
|
||||
### Required Google Cloud APIs
|
||||
|
||||
Enable all four in the [Google Cloud Console](https://console.cloud.google.com/apis/library):
|
||||
|
||||
| API | Used by |
|
||||
|-----|---------|
|
||||
| **Directions API** | `directions` |
|
||||
| **Geocoding API** | `geocode`, `reverse_geocode` |
|
||||
| **Places API** | `search_places` |
|
||||
| **Distance Matrix API** | `distance_matrix` |
|
||||
|
||||
---
|
||||
|
||||
## Setup
|
||||
|
||||
### 1. Create API Key
|
||||
|
||||
1. Go to [Google Cloud Console → Credentials](https://console.cloud.google.com/apis/credentials)
|
||||
2. Click **Create credentials → API key**
|
||||
3. (Recommended) Restrict the key to the four APIs above
|
||||
|
||||
### 2. Save the key
|
||||
|
||||
```bash
|
||||
echo "YOUR_API_KEY_HERE" > secrets/gmaps_api_key.txt
|
||||
```
|
||||
|
||||
Or set the environment variable in your shell/`run.sh`:
|
||||
|
||||
```bash
|
||||
export GOOGLE_MAPS_API_KEY=AIza...
|
||||
```
|
||||
|
||||
### 3. Install the Python dependency
|
||||
|
||||
```bash
|
||||
.venv/bin/pip install googlemaps
|
||||
# or, if you re-run run.sh, it installs requirements.txt automatically
|
||||
```
|
||||
|
||||
`googlemaps` is already listed in `requirements.txt`.
|
||||
|
||||
### 4. Register the server with the agent
|
||||
|
||||
Ask the agent:
|
||||
```
|
||||
register_mcp(
|
||||
name="gmaps",
|
||||
transport="stdio",
|
||||
command="python3",
|
||||
args=["scripts/gmaps_mcp_server.py"]
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Self-check: is Maps working?
|
||||
|
||||
```
|
||||
mcp__gmaps__status()
|
||||
```
|
||||
Returns `OK: …` if the API key is present, valid, and the Geocoding API responds; otherwise an `Error:` string explaining what to fix. Call this first whenever another Maps tool fails.
|
||||
|
||||
### Transit directions home (now)
|
||||
|
||||
```
|
||||
mcp__gmaps__directions(
|
||||
origin="Piazza del Duomo, Milano",
|
||||
destination="casa mia", ← or the real address saved in agent memory
|
||||
mode="transit"
|
||||
)
|
||||
```
|
||||
|
||||
### Prefer train, fewer transfers
|
||||
|
||||
```
|
||||
mcp__gmaps__directions(
|
||||
origin="current location",
|
||||
destination="Via Roma 1, Torino",
|
||||
mode="transit",
|
||||
transit_mode="train",
|
||||
transit_routing_preference="fewer_transfers"
|
||||
)
|
||||
```
|
||||
|
||||
### Find the nearest metro station
|
||||
|
||||
```
|
||||
mcp__gmaps__search_places(
|
||||
query="metro",
|
||||
location="Piazza Garibaldi, Napoli",
|
||||
radius=500,
|
||||
type="subway_station"
|
||||
)
|
||||
```
|
||||
|
||||
### How long does it take from A to B?
|
||||
|
||||
```
|
||||
mcp__gmaps__distance_matrix(
|
||||
origins="Stazione Centrale, Milano",
|
||||
destinations="Aeroporto Malpensa",
|
||||
mode="transit"
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Enable / Disable
|
||||
|
||||
```
|
||||
toggle_item(kind="mcp", id="gmaps", enabled=false) # disable
|
||||
toggle_item(kind="mcp", id="gmaps", enabled=true) # re-enable
|
||||
restart # required for changes to take effect
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Dependencies
|
||||
|
||||
| Package | Version | Purpose |
|
||||
|---------|---------|---------|
|
||||
| `googlemaps` | latest | Google Maps Platform Python client |
|
||||
|
||||
---
|
||||
|
||||
## Parameter notes
|
||||
|
||||
### Coordinates format
|
||||
|
||||
Whenever a parameter accepts coordinates, pass them as a **`"latitude,longitude"` decimal string with no spaces**, e.g. `"45.4654,9.1866"`. Never pass an array or separate fields.
|
||||
|
||||
### `departure_time`
|
||||
|
||||
Must be the **literal string `"now"`** or an **ISO 8601 datetime string with timezone offset**, e.g. `"2025-06-15T08:30:00+02:00"`. Never pass a Unix timestamp integer — the tool now rejects non-string values with an explicit error.
|
||||
|
||||
### `transit_mode`
|
||||
|
||||
Restricts results to a specific vehicle: `"train"` = intercity/regional rail, `"subway"` = metro, `"tram"` = trams, `"bus"` = buses, `"rail"` = any rail. Omit to allow any vehicle.
|
||||
|
||||
---
|
||||
|
||||
## Error Handling
|
||||
|
||||
| Error | Response |
|
||||
|-------|----------|
|
||||
| API key not found | `"Error: Google Maps API key not found. Set GOOGLE_MAPS_API_KEY…"` |
|
||||
| `googlemaps` not installed | `"Error: Missing dependency: No module named 'googlemaps'. Run: pip install googlemaps"` |
|
||||
| `OVER_QUERY_LIMIT` | `"Error: <API> API quota exceeded (OVER_QUERY_LIMIT). Check usage and billing in the Google Cloud Console."` |
|
||||
| `REQUEST_DENIED` | `"Error: <API> API request denied (REQUEST_DENIED). Verify that the API key … is valid and that the <API> API is enabled …"` |
|
||||
| `INVALID_REQUEST` | `"Error: <API> API rejected the request as invalid (INVALID_REQUEST). Check that the addresses, coordinates, and parameters are well-formed."` |
|
||||
| `MAX_ELEMENTS_EXCEEDED` | `"Error: <API> API returned MAX_ELEMENTS_EXCEEDED — too many origins×destinations at once. …"` |
|
||||
| `NOT_FOUND` | `"Error: <API> API could not geocode one of the supplied places. …"` |
|
||||
| Timeout | `"Error: <API> API request timed out. Retry in a moment."` |
|
||||
| No route found | `"No routes found from '…' to '…'."` |
|
||||
| Missing required param | `"Error: Missing required parameter '…'"` |
|
||||
| Invalid `departure_time` | `"Error: 'departure_time' must be 'now' or an ISO 8601 string …"` |
|
||||
|
||||
All error responses are flagged with `isError: true`. The error label `<API>` reflects which Google Maps Platform API was being called (Directions, Geocoding, Places, Distance Matrix).
|
||||
|
||||
All errors are logged to stderr with `[gmaps_mcp]` prefix.
|
||||
|
||||
---
|
||||
|
||||
## Protocol
|
||||
|
||||
Implements JSON-RPC 2.0 over stdio (same as gcal and gmail servers):
|
||||
|
||||
- **Requests:** read from stdin, one JSON object per line
|
||||
- **Responses:** written to stdout
|
||||
- **Logs:** stderr only, prefixed `[gmaps_mcp]`
|
||||
|
||||
Supported methods: `initialize`, `notifications/initialized`, `tools/list`, `tools/call`.
|
||||
|
||||
---
|
||||
|
||||
## When to Update This File
|
||||
|
||||
- New tools added
|
||||
- Auth mechanism changes (e.g. OAuth migration)
|
||||
- New transport option added
|
||||
- Error cases change
|
||||
@@ -0,0 +1,177 @@
|
||||
# SerpAPI Google Flights MCP Server (serpapi_flights)
|
||||
|
||||
## Overview
|
||||
|
||||
A Python MCP server providing **flight search** capabilities via the SerpAPI Google Flights engine.
|
||||
|
||||
**Server name:** `serpapi_flights`
|
||||
**Transport:** `stdio` (spawns `python3 scripts/mcp/serpapi_flights/server.py`)
|
||||
**Location:** `scripts/mcp/serpapi_flights/server.py`
|
||||
|
||||
---
|
||||
|
||||
## Tools
|
||||
|
||||
| Tool | Required params | Optional params | Description |
|
||||
|------|-----------------|-----------------|-------------|
|
||||
| `serpapi_search_flights` | `departure_id`, `arrival_id`, `outbound_date` | `return_date`, `adults`, `children`, `infants_in_seat`, `infants_on_lap`, `stops`, `currency`, `preferred_cabins`, `hl`, `max_results` | One-way or round-trip flight search on Google Flights |
|
||||
|
||||
The previous `serpapi_lookup_airport` tool (hardcoded dictionary of ~100 airports) was removed: the LLM already knows the common IATA codes, and SerpAPI itself accepts both airport codes (`JFK`, `FCO`) and city codes covering all airports of a city (`NYC`, `ROM`, `MIL`, `LON`).
|
||||
|
||||
---
|
||||
|
||||
## Authentication
|
||||
|
||||
### API key
|
||||
|
||||
SerpAPI uses a single API key (no OAuth).
|
||||
|
||||
**Priority order:**
|
||||
|
||||
1. Environment variable `SERPAPI_API_KEY`
|
||||
2. File `secrets/serpapi_api_key.txt` (first non-empty line)
|
||||
|
||||
The `secrets/` directory is in `.gitignore` — the key will not be committed.
|
||||
|
||||
### Get a key
|
||||
|
||||
1. Sign up at [serpapi.com](https://serpapi.com).
|
||||
2. Copy your API key from the dashboard.
|
||||
|
||||
### Save the key
|
||||
|
||||
```bash
|
||||
echo "YOUR_SERPAPI_KEY_HERE" > secrets/serpapi_api_key.txt
|
||||
```
|
||||
|
||||
Or set the environment variable:
|
||||
|
||||
```bash
|
||||
export SERPAPI_API_KEY=your_key_here
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Setup
|
||||
|
||||
### 1. Install the Python dependency
|
||||
|
||||
`httpx` is listed in the project root `requirements.txt` and is installed automatically by `run.sh` into `.venv/`. To install manually:
|
||||
|
||||
```bash
|
||||
uv pip install httpx
|
||||
# or: .venv/bin/pip install httpx
|
||||
```
|
||||
|
||||
The local `scripts/mcp/serpapi_flights/requirements.txt` is kept for standalone use and contains only `httpx>=0.27.0`.
|
||||
|
||||
### 2. Register the server with the agent
|
||||
|
||||
Ask the agent:
|
||||
|
||||
```
|
||||
register_mcp(
|
||||
name="serpapi_flights",
|
||||
transport="stdio",
|
||||
command="python3",
|
||||
args=["scripts/mcp/serpapi_flights/server.py"]
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### One-way, cheapest Milan → Rome next week
|
||||
|
||||
```
|
||||
mcp__serpapi_flights__serpapi_search_flights(
|
||||
departure_id="MIL",
|
||||
arrival_id="ROM",
|
||||
outbound_date="2026-07-01"
|
||||
)
|
||||
```
|
||||
|
||||
### Round-trip London → New York, 2 adults, business class, non-stop only
|
||||
|
||||
```
|
||||
mcp__serpapi_flights__serpapi_search_flights(
|
||||
departure_id="LON",
|
||||
arrival_id="NYC",
|
||||
outbound_date="2026-08-01",
|
||||
return_date="2026-08-15",
|
||||
adults=2,
|
||||
preferred_cabins="business",
|
||||
stops=0,
|
||||
currency="USD"
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Parameter notes
|
||||
|
||||
### Airport vs city codes
|
||||
|
||||
`departure_id` and `arrival_id` must be **exactly 3 ASCII letters**, uppercased automatically. Both forms are accepted:
|
||||
|
||||
- **Airport code** — a specific airport: `JFK`, `FCO` (Rome Fiumicino), `LGW` (London Gatwick).
|
||||
- **City code** — covers all airports of a city: `NYC`, `ROM`, `MIL`, `LON`. **Prefer city codes** when the user does not name a specific airport.
|
||||
|
||||
### `stops`
|
||||
|
||||
Integer enum: `0` = non-stop only, `1` = max one stop, `2` = max two stops. Omit to allow any.
|
||||
|
||||
### `type` (handled automatically)
|
||||
|
||||
The server sends SerpAPI `type=2` for one-way (no `return_date`) and `type=1` for round-trip. The caller never sets `type` directly.
|
||||
|
||||
### `currency`
|
||||
|
||||
ISO 4217 code (3 uppercase letters). Default `EUR`.
|
||||
|
||||
### `hl`
|
||||
|
||||
Language code for SerpAPI result text (e.g. `"en"`, `"it"`). Default `"en"`.
|
||||
|
||||
---
|
||||
|
||||
## Error Handling
|
||||
|
||||
| Error | Response |
|
||||
|-------|----------|
|
||||
| API key missing | `"Error: SerpAPI API key not found. Set SERPAPI_API_KEY env var or create secrets/serpapi_api_key.txt …"` |
|
||||
| HTTP 401 | `"Error: Invalid SerpAPI API key. Check secrets/serpapi_api_key.txt or the SERPAPI_API_KEY env var."` |
|
||||
| HTTP 429 | `"Error: SerpAPI rate limit exceeded. Wait a moment and retry."` |
|
||||
| HTTP 400 | `"Error: Bad request — <body>. Verify airport codes (3-letter IATA) and dates."` |
|
||||
| Timeout (30s) | `"Error: Request to SerpAPI timed out (30s). …"` |
|
||||
| Missing/invalid param | `"Error: 'outbound_date' must be in YYYY-MM-DD format (got '…')."` |
|
||||
| Unknown tool | `"Error: Unknown tool: <name>"` |
|
||||
| SerpAPI error in body | `"Error: SerpAPI returned an error: <message>"` |
|
||||
|
||||
Every error is returned as a tool result with `isError: true` (detected by the `Error:` prefix).
|
||||
|
||||
All errors are logged to stderr with `[serpapi_flights_mcp]` prefix.
|
||||
|
||||
---
|
||||
|
||||
## Protocol
|
||||
|
||||
Implements JSON-RPC 2.0 over stdio (same shape as the `gmaps`, `gcal`, `gmail`, and `whatsapp` servers):
|
||||
|
||||
- **Requests:** read from stdin, one JSON object per line
|
||||
- **Responses:** written to stdout
|
||||
- **Logs:** stderr only, prefixed `[serpapi_flights_mcp]`
|
||||
|
||||
Supported methods: `initialize`, `notifications/initialized`, `tools/list`, `tools/call`.
|
||||
|
||||
The server is fully synchronous: stdin is read line-by-line, each `tools/call` performs a blocking `httpx.Client.get` to SerpAPI and returns the formatted result. No background threads, no FastMCP framework.
|
||||
|
||||
---
|
||||
|
||||
## When to Update This File
|
||||
|
||||
- New tools added
|
||||
- Auth mechanism changes
|
||||
- SerpAPI parameters added or removed
|
||||
- Error cases change
|
||||
@@ -0,0 +1,349 @@
|
||||
# WhatsApp MCP Server (whatsapp)
|
||||
|
||||
## Overview
|
||||
|
||||
A Node.js MCP server that exposes WhatsApp as a set of tools for the LLM, using **whatsapp-web.js** + Puppeteer (headless Chromium).
|
||||
|
||||
**Server name:** `whatsapp`
|
||||
**Transport:** `stdio` (spawns `node scripts/whatsapp_mcp/index.js`)
|
||||
**Location:** `scripts/whatsapp_mcp/index.js`
|
||||
|
||||
### Capabilities
|
||||
|
||||
| Capability | Enabled |
|
||||
|------------|---------|
|
||||
| List chats and groups | ✅ |
|
||||
| Read messages from a chat | ✅ |
|
||||
| Search messages by keyword | ✅ |
|
||||
| Search contacts by name | ✅ |
|
||||
| Send messages | ✅ |
|
||||
| Send media (image/video/audio/document, from file or URL) | ✅ |
|
||||
| Download received media (photos, videos, documents) | ✅ |
|
||||
| Logout / reset session without restart | ✅ |
|
||||
| Address a contact by phone number (no lookup needed) | ✅ |
|
||||
| Edit/delete messages | ❌ not implemented |
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ Important notes on WhatsApp
|
||||
|
||||
- **whatsapp-web.js is unofficial**: it drives WhatsApp Web through Chromium. WhatsApp may ban the number in case of heavy or anomalous use.
|
||||
- **Safe use**: reading your own groups and sending individual messages is in the tolerated grey area. Do not use it for spam or bulk automation.
|
||||
- **Recommended number**: using a secondary number or a parallel WhatsApp Business account reduces the risk.
|
||||
|
||||
---
|
||||
|
||||
## Tools
|
||||
|
||||
All tools are callable as `mcp__whatsapp__<tool>`; the table lists the bare `<tool>` names.
|
||||
|
||||
| Tool | Parameters | Description |
|
||||
|------|------------|-------------|
|
||||
| `status` | *(none)* | Connection status as a plain-language report: the state, what it means, and step-by-step fix instructions when not operational. Cross-checks the live socket (`getState()`) to catch silently dropped sessions |
|
||||
| `get_qr` | *(none)* | QR code to scan with the phone — path/URL to a PNG or HTML page, with ASCII fallback (only when status = QR_READY) |
|
||||
| `logout` | *(none)* | Ends the session, **clears the cached credentials on disk** and re-initializes the client → new QR without restart. Use it when the session has expired/got stuck or to link a different phone |
|
||||
| `list_chats` | `max_chats` (int, default 20, max 50) | List recent chats with name, ID and unread count |
|
||||
| `get_messages` | `chat_id` **or** `number`, `limit` (int, default 20, max 100), `offset` (int, default 0) | Messages from a chat/group with pagination support. Media messages are tagged with their type and a `download id` |
|
||||
| `send_message` | `chat_id` **or** `number`, `message` (required) | Send a text message |
|
||||
| `send_media` | `chat_id` **or** `number`, `source` (required: file path or http(s) URL), `caption`, `as_document` (bool) | Send an image/video/audio/document |
|
||||
| `download_media` | `message_id` (required) | Download the media attached to a message; saves it under `data/whatsapp_media/` and returns the path + `/data/` URL |
|
||||
| `search_messages` | `query` (required), `max_results` (int, default 20, max 50) | Search by keyword across all chats |
|
||||
| `search_contacts` | `query` (required), `max_results` (int, default 20, max 50) | Search saved contacts by name (partial, case-insensitive). Use it to find the ID of a contact not present in recent chats |
|
||||
|
||||
### chat_id format
|
||||
|
||||
- **Contact:** `39xxxxxxxxxx@c.us` (international prefix without `+`, followed by `@c.us`)
|
||||
- **Group:** `xxxxxxxxxx-xxxxxxxxxx@g.us`
|
||||
|
||||
Correct chat_ids are obtained via `list_chats` (recent chats) or `search_contacts` (saved contacts not in recent chats).
|
||||
|
||||
**Shortcut for individual contacts:** `get_messages`, `send_message` and `send_media` also accept a plain `number` (phone number with country code, e.g. `393331234567` or `+39 333 123 4567`) instead of a `chat_id`. The server resolves it via `getNumberId` (which also verifies the number is on WhatsApp), so there is no need to look up the chat_id first. Groups must still be addressed by `chat_id`.
|
||||
|
||||
---
|
||||
|
||||
## Authentication
|
||||
|
||||
### First time (QR scan)
|
||||
|
||||
On the first launch there is no saved session. The client generates a QR code:
|
||||
|
||||
1. The LLM calls `status` → response `QR_READY`
|
||||
2. The LLM calls `get_qr` → returns the QR
|
||||
3. The user scans the QR with WhatsApp → **Settings → Linked Devices → Link a Device**
|
||||
4. The state moves to `AUTHENTICATED`, then `READY`
|
||||
|
||||
The QR is also saved to a file (PNG at `data/whatsapp_qr.png`, HTML at `secrets/whatsapp_qr.html`, ASCII fallback at `secrets/whatsapp_qr.txt`).
|
||||
|
||||
### Subsequent sessions
|
||||
|
||||
The session is persisted in `secrets/whatsapp_session/` (managed by whatsapp-web.js's `LocalAuth`). On server restart the session is restored automatically, with no need to scan the QR again.
|
||||
|
||||
### Browser lifecycle & restart cleanup (self-healing)
|
||||
|
||||
The Puppeteer profile lives at `secrets/whatsapp_session/session` and Chromium takes an **exclusive lock** on it (`SingletonLock`): two browsers cannot open the same profile at once.
|
||||
|
||||
The host (skald) kills MCP servers with **SIGKILL** on shutdown/restart (`kill_on_drop` in the Rust MCP client — see `crates/mcp-client/src/server.rs`). SIGKILL is untrappable, so the node process dies instantly and the headless Chromium it spawned is **orphaned** (reparented to init), keeping the profile locked. The next launch would then fail with *"The browser is already running for <profile>. Use a different `userDataDir` or stop the running browser first."* — and every WhatsApp tool stays dead until the orphan is killed by hand.
|
||||
|
||||
To make this self-healing, on **every startup** (in `initClient()` → `cleanupStaleBrowser()`) the server:
|
||||
|
||||
1. Runs `pgrep -f <profile-dir>` and `SIGKILL`s every process still bound to the profile. Because skald runs a single WhatsApp MCP at a time and SIGKILLs the old node before spawning the new one, any such process is guaranteed to be a leftover orphan.
|
||||
2. Removes the stale `SingletonLock` / `SingletonCookie` / `SingletonSocket` files so a fresh launch is unblocked.
|
||||
|
||||
For the shutdown paths that *are* trappable (clean stdin EOF, `SIGTERM`/`SIGINT` from a container/OS stop), the server closes the browser cleanly (`client.destroy()`, awaited with a 5 s cap) so it releases the lock on the way out. The startup cleanup is the backstop that covers the SIGKILL path. The saved login (under `session/Default/`) is untouched, so no QR re-scan is needed after a restart.
|
||||
|
||||
### Logout / expired session (without restart)
|
||||
|
||||
When the session expires or gets stuck (state `DISCONNECTED`), on restart `LocalAuth` would reload the invalid session from `secrets/whatsapp_session/`, immediately returning to the disconnected state. Previously the only fix was to delete that folder by hand and restart.
|
||||
|
||||
Now `logout` is enough:
|
||||
|
||||
1. Attempts a clean logout (`client.logout()`), tolerating failure if the browser page is already dead;
|
||||
2. As a fallback, closes the browser (`destroy()`) to release the locks on the profile;
|
||||
3. **Force-deletes `secrets/whatsapp_session/`** (the cached token);
|
||||
4. Removes any stale QR files;
|
||||
5. Re-initializes the client → generates a new QR within a few seconds.
|
||||
|
||||
```
|
||||
mcp__whatsapp__logout()
|
||||
# → wait a few seconds
|
||||
mcp__whatsapp__get_qr()
|
||||
# → scan the new QR
|
||||
```
|
||||
|
||||
No server restart required.
|
||||
|
||||
### Token storage
|
||||
|
||||
| File/Directory | Contents |
|
||||
|---|---|
|
||||
| `secrets/whatsapp_session/` | Persistent WhatsApp session (LocalAuth) — deleted by `logout` |
|
||||
| `secrets/whatsapp_qr.html` / `data/whatsapp_qr.png` | Temporary QR code (removed after authentication or a logout) |
|
||||
| `secrets/whatsapp_qr.txt` | ASCII fallback of the QR (when `qrcode` is unavailable) |
|
||||
| `data/whatsapp_media/` | Media downloaded via `download_media`, served at `/data/whatsapp_media/` |
|
||||
|
||||
Everything under `secrets/` is in `.gitignore` via the `secrets/` rule.
|
||||
|
||||
---
|
||||
|
||||
## Setup (one-time)
|
||||
|
||||
### 1. Install the Node.js dependencies
|
||||
|
||||
```bash
|
||||
cd scripts/whatsapp_mcp
|
||||
npm install
|
||||
```
|
||||
|
||||
This installs `whatsapp-web.js`, `puppeteer` (includes Chromium, ~300MB), `qrcode` and `qrcode-terminal`.
|
||||
|
||||
### 2. Register the server (have the agent do it)
|
||||
|
||||
```
|
||||
register_mcp(
|
||||
name="whatsapp",
|
||||
transport="stdio",
|
||||
command="node",
|
||||
args=["scripts/whatsapp_mcp/index.js"]
|
||||
)
|
||||
```
|
||||
|
||||
### 3. First authentication
|
||||
|
||||
```
|
||||
mcp__whatsapp__status()
|
||||
# → QR_READY
|
||||
|
||||
mcp__whatsapp__get_qr()
|
||||
# → shows the QR, scan it with the phone
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Usage examples
|
||||
|
||||
### See recent chats
|
||||
|
||||
```
|
||||
mcp__whatsapp__list_chats(max_chats=10)
|
||||
```
|
||||
|
||||
### Read the latest messages from a group
|
||||
|
||||
```
|
||||
mcp__whatsapp__get_messages(
|
||||
chat_id="1234567890-9876543210@g.us",
|
||||
limit=50
|
||||
)
|
||||
```
|
||||
|
||||
### Page through history (older messages)
|
||||
|
||||
`offset` skips the most recent messages, exposing the preceding window:
|
||||
|
||||
```
|
||||
# Last 20 messages
|
||||
get_messages(chat_id="...", limit=20, offset=0)
|
||||
|
||||
# Messages 21–40 (previous)
|
||||
get_messages(chat_id="...", limit=20, offset=20)
|
||||
|
||||
# Messages 41–60 (even older)
|
||||
get_messages(chat_id="...", limit=20, offset=40)
|
||||
```
|
||||
|
||||
Limit: `limit + offset` cannot exceed 200 in a single call (a `fetchMessages` constraint).
|
||||
|
||||
### Find the contact of someone not in recent chats
|
||||
|
||||
```
|
||||
mcp__whatsapp__search_contacts(query="Luca")
|
||||
# → Luca Rossi [contact] | ID: 393331234567@c.us
|
||||
```
|
||||
|
||||
### Search for what was said about a topic
|
||||
|
||||
```
|
||||
mcp__whatsapp__search_messages(query="Monday meeting")
|
||||
```
|
||||
|
||||
### Send a message
|
||||
|
||||
```
|
||||
# By chat_id (groups, or chats already open)
|
||||
mcp__whatsapp__send_message(
|
||||
chat_id="393331234567@c.us",
|
||||
message="Hi! Are you there?"
|
||||
)
|
||||
|
||||
# Or directly by number — no list_chats/search_contacts needed first
|
||||
mcp__whatsapp__send_message(
|
||||
number="+39 333 123 4567",
|
||||
message="Hi! Are you there?"
|
||||
)
|
||||
```
|
||||
|
||||
### Send media (image, video, document)
|
||||
|
||||
```
|
||||
# From a local file (path relative to the project root, or absolute)
|
||||
mcp__whatsapp__send_media(
|
||||
number="393331234567",
|
||||
source="data/report.pdf",
|
||||
caption="Here is the report",
|
||||
as_document=true
|
||||
)
|
||||
|
||||
# From a URL
|
||||
mcp__whatsapp__send_media(
|
||||
chat_id="1234567890-9876543210@g.us",
|
||||
source="https://example.com/photo.jpg",
|
||||
caption="Look at this"
|
||||
)
|
||||
```
|
||||
|
||||
### Download received media
|
||||
|
||||
`get_messages` tags media messages with a `download id`:
|
||||
|
||||
```
|
||||
mcp__whatsapp__get_messages(number="393331234567", limit=10)
|
||||
# → [2026-06-22 10:01:00] Luca [image, download id="true_39...@c.us_3EB0..."]: invoice photo
|
||||
|
||||
mcp__whatsapp__download_media(message_id="true_39...@c.us_3EB0...")
|
||||
# → saved to data/whatsapp_media/... (also served at /data/whatsapp_media/...)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Connection states
|
||||
|
||||
`status` returns a self-describing report — it states the lifecycle state, explains it in plain language, and lists concrete next steps whenever it is not `READY`. The agent should not need this table; it is here for reference.
|
||||
|
||||
| State | Meaning | What to do |
|
||||
|-------|---------|------------|
|
||||
| `INITIALIZING` | Browser starting up, session loading | Wait a few seconds |
|
||||
| `QR_READY` | QR scan needed | Call `get_qr` and scan |
|
||||
| `AUTHENTICATED` | QR scanned, session being established | Wait (→ READY automatically) |
|
||||
| `READY` | Operational | All tools available |
|
||||
| `DISCONNECTED` | Connection/session lost | Call `logout` to reset and log in again (no restart) |
|
||||
|
||||
### Live socket cross-check
|
||||
|
||||
The lifecycle `state` above is driven by whatsapp-web.js **events**, so it can lag behind a session that drops silently. When `state` is `READY`, `status` also queries the live socket (`client.getState()`, a `WAState`) and reports a mismatch with tailored instructions:
|
||||
|
||||
| Live `WAState` while READY | Reported as | Fix suggested |
|
||||
| --- | --- | --- |
|
||||
| `CONNECTED` | `READY ✅ (ok)` | — |
|
||||
| `UNPAIRED` / `UNPAIRED_IDLE` | `action needed` | Device unlinked from phone → `logout` + re-scan |
|
||||
| `CONFLICT` | `action needed` | WhatsApp Web open elsewhere → close it, or `logout` + re-scan |
|
||||
| `TIMEOUT` | `transient` | May auto-reconnect → wait and re-check; if stuck, `logout` |
|
||||
| `DEPRECATED_VERSION` | `needs maintenance` | Update the `whatsapp-web.js` dependency (developer task) |
|
||||
| `getState()` fails / other | `uncertain` | Browser may have crashed → wait and re-check; if stuck, `logout` |
|
||||
|
||||
---
|
||||
|
||||
## Enable / Disable
|
||||
|
||||
### Disable (when not needed)
|
||||
|
||||
```
|
||||
toggle_item(kind="mcp", id="whatsapp", enabled=false)
|
||||
restart
|
||||
```
|
||||
|
||||
### Re-enable
|
||||
|
||||
```
|
||||
toggle_item(kind="mcp", id="whatsapp", enabled=true)
|
||||
restart
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Dependencies
|
||||
|
||||
| Package | Version | Purpose |
|
||||
|---------|---------|---------|
|
||||
| `whatsapp-web.js` | ^1.34.7 | WhatsApp Web client |
|
||||
| `puppeteer` | ^25.1.0 | Headless Chromium (bundled) |
|
||||
| `qrcode` | ^1.5.4 | Generates the QR as PNG / data-URL (HTML) |
|
||||
| `qrcode-terminal` | ^0.12.0 | ASCII QR fallback |
|
||||
|
||||
**System requirements:**
|
||||
- Node.js ≥ 18
|
||||
- ~500MB of space for Puppeteer/Chromium
|
||||
- A background Chromium process while the server is running
|
||||
|
||||
---
|
||||
|
||||
## Common errors
|
||||
|
||||
| Error | Cause | Fix |
|
||||
|-------|-------|-----|
|
||||
| `whatsapp-web.js not found` | `npm install` not run | `cd scripts/whatsapp_mcp && npm install` |
|
||||
| `WhatsApp not ready (status: INITIALIZING)` | Server just started | Wait 15-30 seconds |
|
||||
| `WhatsApp not ready (status: QR_READY)` | Session expired/missing | Call `get_qr` and scan |
|
||||
| `WhatsApp not ready (status: DISCONNECTED)` | Connection/session lost | Call `logout`, wait, then `get_qr` and scan |
|
||||
| Stays `DISCONNECTED` even after restart | Expired cached token in `secrets/whatsapp_session/` | Call `logout` (clears the cache and regenerates the QR) |
|
||||
| `The browser is already running for …/session` | An orphaned Chromium from a hard-killed (SIGKILL) prior run still holds the profile lock | **Auto-healed on next startup** by `cleanupStaleBrowser()` (kills the orphan + clears the lock). See *Browser lifecycle & restart cleanup* |
|
||||
| Chat ID not found | Wrong ID | Use `list_chats` to get the correct IDs |
|
||||
|
||||
---
|
||||
|
||||
## Protocol
|
||||
|
||||
Implements JSON-RPC 2.0 over stdio (same pattern as gmail and gcal):
|
||||
- **Requests:** JSON on stdin (one per line)
|
||||
- **Responses:** JSON on stdout
|
||||
- **Logs:** stderr with the `[whatsapp_mcp]` prefix
|
||||
|
||||
Supported methods: `initialize`, `notifications/initialized`, `tools/list`, `tools/call`
|
||||
|
||||
---
|
||||
|
||||
## When to update this file
|
||||
|
||||
- New tools added to the server
|
||||
- Changed session/QR paths under `secrets/`
|
||||
- New connection states
|
||||
- Changed dependency versions
|
||||
@@ -0,0 +1,94 @@
|
||||
# MCP Specification — 2024-11-05 (Legacy)
|
||||
|
||||
> **Status:** Legacy (first public release)
|
||||
> **Released:** 2024-11-05 · repo tags `2024-11-05` and `2024-11-05-final`
|
||||
> **Spec site:** https://modelcontextprotocol.io/specification/2024-11-05
|
||||
> **Authoritative schema:** [schema/2024-11-05/schema.ts](https://github.com/modelcontextprotocol/specification/blob/main/schema/2024-11-05/schema.ts)
|
||||
|
||||
This is the **first public release** of the Model Context Protocol specification (November 2024). It establishes the JSON-RPC 2.0 based, stateful, capability-negotiated protocol that all later revisions build upon: the Host/Client/Server architecture, the four server primitives (Resources, Prompts, Tools, Logging) and the two client features (Sampling, Roots), over stdio and the original HTTP+SSE transport. It is retained here as the historical baseline; later revisions (2025-03-26, 2025-06-18) added formal authorization, Streamable HTTP, structured tool output, and Elicitation — none of which exist in this version. The TypeScript schema (`schema.ts`) is the source of truth referenced by BCP 14 keywords (MUST / SHOULD / MAY).
|
||||
|
||||
## At a glance
|
||||
- **Protocol style:** stateful connections
|
||||
- **Capability negotiation:** connection-level (during `initialize`)
|
||||
- **Transports:** stdio; HTTP+SSE (the original two-endpoint SSE transport)
|
||||
- **Server features:** Resources, Prompts, Tools, Logging
|
||||
- **Client features:** Sampling, Roots
|
||||
- **Authorization:** **not part of the core spec** — clients and servers MAY negotiate custom auth; HTTP transports carry transport-level security guidance only
|
||||
|
||||
## Architecture
|
||||
MCP follows a **client-host-server** architecture. A Host process creates and manages multiple Client instances; each Client has a 1:1 stateful session with exactly one Server. The Host enforces security policy, consent, and context aggregation; Servers are intentionally simple, composable, and isolated from one another — a server cannot read the whole conversation nor see into other servers. Servers receive only the contextual information they need, and the full conversation history stays with the Host.
|
||||
|
||||
Design principles: servers should be extremely easy to build, highly composable, mutually isolated, and incrementally extensible through capability negotiation. Servers may be local processes or remote services, and may request LLM access back through the client via Sampling.
|
||||
|
||||
## Base Protocol
|
||||
### Messages
|
||||
All messages **MUST** follow JSON-RPC 2.0. Three types are defined:
|
||||
- **Requests** — **MUST** include a `string | number` `id` and a `method`. Unlike base JSON-RPC, the id **MUST NOT** be `null`, and **MUST NOT** have been reused by the requestor within the same session.
|
||||
- **Responses** — **MUST** echo the request `id`; exactly one of `result` or `error` **MUST** be set (never both); error `code` **MUST** be an integer.
|
||||
- **Notifications** — **MUST NOT** include an `id`.
|
||||
|
||||
### Lifecycle
|
||||
A strict three-phase lifecycle: **Initialization → Operation → Shutdown**.
|
||||
- Initialization **MUST** be the first interaction. The client sends an `initialize` request carrying `protocolVersion` (e.g. `"2024-11-05"`), its `capabilities`, and `clientInfo`. The server responds with its own negotiated `protocolVersion`, `capabilities`, and `serverInfo`. The client then **MUST** send an `notifications/initialized` notification before normal operations begin.
|
||||
- Before the server's `initialize` response, the client **SHOULD NOT** send anything but `ping`; before the `initialized` notification, the server **SHOULD NOT** send anything but `ping` and logging.
|
||||
- **Version negotiation:** the client sends a supported version (SHOULD be its latest); if the server supports it, it responds with the same version, otherwise with another supported version (SHOULD be its latest). If the client cannot accept the server's version, it **SHOULD** disconnect.
|
||||
- **Capability negotiation:** client (`roots`, `sampling`, `experimental`) and server (`prompts`, `resources`, `tools`, `logging`, `experimental`) declare supported features. Sub-capabilities include `listChanged` (prompts/resources/tools) and `subscribe` (resources only). Both parties **SHOULD** respect negotiated capabilities for the whole session.
|
||||
- **Shutdown** has no dedicated message: stdio clients close the server's stdin, then escalate to `SIGTERM` / `SIGKILL`; HTTP shutdown is signalled by closing the connection(s).
|
||||
|
||||
### Transports
|
||||
Two standard transports are defined; clients **SHOULD** support stdio whenever possible.
|
||||
- **stdio** — the client launches the server as a subprocess; messages are newline-delimited on stdin/stdout and **MUST NOT** contain embedded newlines; the server **MUST NOT** write non-MCP data to stdout; `stderr` **MAY** carry UTF-8 logs.
|
||||
- **HTTP with SSE** — the server exposes two endpoints: an SSE endpoint (client connects, receives an `endpoint` event with a POST URI) and an HTTP POST endpoint to which the client sends all subsequent messages; server-to-client messages arrive as SSE `message` events. Security: servers **MUST** validate the `Origin` header, **SHOULD** bind to localhost only, and **SHOULD** require authentication.
|
||||
- **Custom transports** MAY be implemented provided they preserve the JSON-RPC format and lifecycle.
|
||||
|
||||
### Authorization
|
||||
**Authentication and authorization were not part of the core specification in this revision** (the `/basic/authorization` page does not exist for 2024-11-05). The base-protocol overview states auth "is not currently part of the core MCP specification" and that clients and servers **MAY** negotiate their own custom strategies. The only auth-related guidance is the transport-level security requirements above. (The OAuth 2.1 framework was introduced in a later revision, not this one.)
|
||||
|
||||
### Versioning
|
||||
There is no dedicated versioning page in this revision; version behavior is specified within the lifecycle handshake (see above). The negotiated version is a literal string such as `"2024-11-05"`.
|
||||
|
||||
## Server Features
|
||||
Servers advertise any implemented features via capabilities; each listed below **MUST** be declared before use.
|
||||
### Resources
|
||||
Application-driven contextual data, each identified by a [URI](https://datatracker.ietf.org/doc/html/rfc3986). Capability `resources` with optional `subscribe` and `listChanged`. Methods: `resources/list` (paginated), `resources/read` (by URI), `resources/templates/list` (RFC 6570 URI templates, paginated), plus `resources/subscribe` / `resources/unsubscribe` when `subscribe` is supported. List changes emit `notifications/resources/list_changed`; subscribed resource updates emit `notifications/resources/updated`.
|
||||
|
||||
### Prompts
|
||||
User-controlled templated messages/workflows (typically surfaced as slash commands). Capability `prompts` with optional `listChanged`. Methods: `prompts/list` (paginated) and `prompts/get` (with `arguments`). A returned `PromptMessage` carries a `role` (`"user"` / `"assistant"`) and a content item (text or image). List changes emit `notifications/prompts/list_changed`. Arguments may be auto-completed via the completion utility.
|
||||
|
||||
### Tools
|
||||
Model-controlled executable functions. Capability `tools` with optional `listChanged`. Methods: `tools/list` (paginated) and `tools/call`. A tool definition carries `name`, `description`, and an `inputSchema` (JSON Schema). A call response returns a `content` array of typed items (text and image content in this revision) plus a boolean `isError` flag. **There is no structured output** — results are unstructured content arrays. List changes emit `notifications/tools/list_changed`. Hosts **SHOULD** keep a human in the loop and confirm invocations.
|
||||
|
||||
### Utilities / Logging
|
||||
Capability `logging` (declared as `{}`). Servers emit `notifications/message` with a syslog (RFC 5424) severity level (`debug`, `info`, `notice`, `warning`, `error`, `critical`, `alert`, `emergency`), an optional `logger` name, and arbitrary JSON-serializable `data`. Clients **MAY** set the minimum level via `logging/setLevel`. Additional cross-cutting utilities referenced by the spec include pagination (`server/utilities/pagination`), argument completion (`server/utilities/completion`), and `ping` (`basic/utilities/ping`).
|
||||
|
||||
## Client Features
|
||||
### Sampling
|
||||
Server-initiated LLM requests, enabling nested/agentic behaviors. Capability `sampling` (declared as `{}`). The server sends `sampling/createMessage` with `messages`, optional `modelPreferences` (sub-string `hints` plus normalized `costPriority` / `speedPriority` / `intelligencePriority` in 0–1), optional `systemPrompt`, and `maxTokens`. The client (with a human in the loop) approves/edits the prompt and returns `role`, `content`, `model`, and `stopReason`.
|
||||
|
||||
### Roots
|
||||
Filesystem/URI boundaries the server may operate within. Capability `roots` with optional `listChanged`. The server calls `roots/list` and receives `Root` entries; each `uri` **MUST** be a `file://` URI in this revision (plus an optional display `name`). Clients supporting `listChanged` **MUST** emit `notifications/roots/list_changed` when the set changes. Clients **MUST** validate URIs against path traversal and **SHOULD** obtain user consent before exposing roots.
|
||||
|
||||
## Revision history
|
||||
- **2024-11-05** — first public release of the specification (repo tag `2024-11-05`).
|
||||
- **2024-11-05-final** — final revision of the 2024-11-05 spec line (repo tag `2024-11-05-final`).
|
||||
- **2024-10-07** — pre-public revision tag that existed before the public release.
|
||||
|
||||
## Skald relevance
|
||||
Skald's MCP client implementation lives in `crates/mcp-client/`: `McpServer` (stdio) and `McpHttpServer` (HTTP+SSE) both implement the `McpServerClient` trait defined in `crates/mcp-client/src/lib.rs`. The `McpManager` orchestrator in `src/core/mcp/mod.rs` registers and dispatches MCP tools to the agent tool-calling loop. This 2024-11-05 revision is the historical baseline the earliest MCP support targeted (stdio + HTTP+SSE, Resources/Prompts/Tools/Logging, Sampling/Roots); modern Skald also speaks newer revisions and therefore supports capabilities (e.g. Streamable HTTP, structured tool output) that did not exist in this legacy spec.
|
||||
|
||||
## References
|
||||
- [Specification overview](https://modelcontextprotocol.io/specification/2024-11-05)
|
||||
- [Architecture](https://modelcontextprotocol.io/specification/2024-11-05/architecture)
|
||||
- [Base protocol](https://modelcontextprotocol.io/specification/2024-11-05/basic)
|
||||
- [Lifecycle](https://modelcontextprotocol.io/specification/2024-11-05/basic/lifecycle)
|
||||
- [Messages](https://modelcontextprotocol.io/specification/2024-11-05/basic/messages)
|
||||
- [Transports](https://modelcontextprotocol.io/specification/2024-11-05/basic/transports)
|
||||
- [Server features](https://modelcontextprotocol.io/specification/2024-11-05/server)
|
||||
- [Tools](https://modelcontextprotocol.io/specification/2024-11-05/server/tools)
|
||||
- [Resources](https://modelcontextprotocol.io/specification/2024-11-05/server/resources)
|
||||
- [Prompts](https://modelcontextprotocol.io/specification/2024-11-05/server/prompts)
|
||||
- [Logging](https://modelcontextprotocol.io/specification/2024-11-05/server/utilities/logging)
|
||||
- [Client features / Roots](https://modelcontextprotocol.io/specification/2024-11-05/client)
|
||||
- [Sampling](https://modelcontextprotocol.io/specification/2024-11-05/client/sampling)
|
||||
- [Schema (schema.ts)](https://github.com/modelcontextprotocol/specification/blob/main/schema/2024-11-05/schema.ts)
|
||||
- [Release 2024-11-05](https://github.com/modelcontextprotocol/modelcontextprotocol/releases/tag/2024-11-05)
|
||||
@@ -0,0 +1,88 @@
|
||||
# MCP Specification — 2025-06-18 (Stable)
|
||||
|
||||
> **Status:** Stable
|
||||
> **Released:** 2025-06-18
|
||||
> **Spec site:** https://modelcontextprotocol.io/specification/2025-06-18
|
||||
> **Authoritative schema:** [schema/2025-06-18/schema.ts](https://github.com/modelcontextprotocol/specification/blob/main/schema/2025-06-18/schema.ts)
|
||||
|
||||
Revision 2025-06-18 is the "modern stable" baseline of the Model Context Protocol. It supersedes the 2024-11-05 legacy revision by introducing the **Streamable HTTP** transport (single-endpoint, replacing the two-endpoint HTTP+SSE design), the **Elicitation** client feature (`elicitation/create`, server-initiated structured input from the user), and **structured tool output** (typed `structuredContent` validated against an optional `outputSchema`). It also tightens the authorization framework with OAuth 2.1 Resource Indicators (RFC 8707) and OAuth Protected Resource Metadata (RFC 9728), and removes JSON-RPC batching. The authoritative source of truth is the TypeScript schema; the spec text is normative where it restates requirements using BCP 14 keywords (MUST/SHOULD/MAY).
|
||||
|
||||
## At a glance
|
||||
- **Protocol style:** stateful connections
|
||||
- **Capability negotiation:** connection-level (`initialize`)
|
||||
- **Transports:** stdio; **Streamable HTTP** (replaces HTTP+SSE)
|
||||
- **Server features:** Resources, Prompts, Tools, Logging
|
||||
- **Client features:** Sampling, Roots, **Elicitation**
|
||||
- **Authorization:** OAuth 2.1 + Resource Indicators (RFC 8707)
|
||||
|
||||
## Architecture
|
||||
Client-host-server topology built on JSON-RPC. A **host** creates and manages multiple **client** instances; each client holds a 1:1 **stateful session** with one **server** and enforces isolation between servers (a server cannot see the full conversation or other servers). Servers expose primitives (resources/prompts/tools) and MAY request client features (sampling/roots/elicitation). Design principles: servers must be easy to build, highly composable, mutually isolated, and progressively extensible via capability negotiation at `initialize`.
|
||||
|
||||
## Base Protocol
|
||||
|
||||
### Messages
|
||||
All messages **MUST** be [JSON-RPC 2.0](https://www.jsonrpc.org/specification), UTF-8 encoded. Three kinds: **Requests** (with a string/integer `id`; the id **MUST NOT** be `null` and **MUST NOT** be reused within a session), **Responses** (`result` XOR `error`; both **MUST NOT** be set; error codes are integers), and **Notifications** (no `id`; receiver **MUST NOT** reply). The reserved `_meta` property carries extension metadata; key names with a `mcp`/`modelcontextprotocol` prefix are reserved. **JSON-RPC batching is disallowed** (each message is a standalone request/notification/response).
|
||||
|
||||
### Lifecycle
|
||||
Three phases. (1) **Initialization** — client sends `initialize` with its supported `protocolVersion` (SHOULD be its latest), `capabilities`, and `clientInfo`; server responds with the negotiated `protocolVersion`, its `capabilities`, `serverInfo`, and optional `instructions`; client then sends `notifications/initialized`. The server SHOULD NOT send non-ping/logging requests before `initialized`. (2) **Operation** — both parties **MUST** respect the negotiated version and only use negotiated capabilities. (3) **Shutdown** — no dedicated message; the transport signals termination (stdio: close stdin → SIGTERM → SIGKILL; HTTP: `DELETE` the session or close streams). If versions mismatch the client **SHOULD** disconnect.
|
||||
|
||||
### Transports
|
||||
Two standard transports. **stdio** — the client launches the server as a subprocess; newline-delimited JSON-RPC over stdin/stdout; messages **MUST NOT** contain embedded newlines; stderr is for logs only. **Streamable HTTP** — the server exposes a single **MCP endpoint** supporting `POST` and optionally `GET` (e.g. `https://example.com/mcp`), replacing the 2024-11-05 HTTP+SSE two-endpoint design:
|
||||
- Every client JSON-RPC message is a fresh `POST`; the request **MUST** send `Accept: application/json, text/event-stream`. For a *request* the server responds either with `application/json` or by opening an SSE `text/event-stream`; for a *notification/response* the server returns `202 Accepted`.
|
||||
- Server→client traffic (notifications/requests) flows over an SSE stream that the client opens via `GET` (server MAY decline with `405`). The server **MUST NOT** broadcast the same message across multiple concurrent streams.
|
||||
- **Sessions:** the server MAY assign an `Mcp-Session-Id` (cryptographically secure, visible ASCII 0x21–0x7E) on the initialize response; the client **MUST** echo it on all subsequent requests; a `404` forces re-initialization; a `DELETE` terminates the session.
|
||||
- **Resumability:** servers MAY attach SSE event `id`s; the client resumes via the `Last-Event-ID` header on `GET` (per-stream cursor; no cross-stream replay).
|
||||
- Clients using HTTP **MUST** send `MCP-Protocol-Version: <version>` on all post-initialize requests. Servers **MUST** validate the `Origin` header (DNS-rebinding defence) and **SHOULD** bind localhost when local.
|
||||
|
||||
### Authorization
|
||||
Optional, for HTTP transports only (stdio retrieves credentials from the environment). Built on OAuth 2.1 ([draft-ietf-oauth-v2-1-13](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-13)) with PKCE. **Resource Server classification:** the MCP server is an OAuth 2.1 *resource server*; the MCP client is the OAuth 2.1 *client*; tokens are issued by a separate authorization server. Discovery: MCP servers **MUST** implement OAuth 2.0 Protected Resource Metadata ([RFC 9728](https://www.rfc-editor.org/rfc/rfc9728.html)) and signal the metadata URL via `WWW-Authenticate` on `401`; clients **MUST** use Authorization Server Metadata ([RFC 8414](https://datatracker.ietf.org/doc/html/rfc8414)) and **SHOULD** use Dynamic Client Registration ([RFC 7591](https://datatracker.ietf.org/doc/html/rfc7591)). **Resource Indicators (RFC 8707):** the client **MUST** send the `resource` parameter (identifying the target MCP server) in both the authorization and token requests. Token refresh is supported; scopes are server-defined.
|
||||
|
||||
### Versioning
|
||||
Protocol versions are date strings (e.g. `2025-06-18`). Version negotiation happens only in `initialize` (see Lifecycle): the client sends the version it supports; the server returns it if supported, otherwise its latest other version; the client disconnects on an unsupported response. A `CHANGELOG.md` records revisions; SDKs adopt versions at their own pace and rely on negotiation for backwards/forwards compatibility.
|
||||
|
||||
## Server Features
|
||||
Servers advertise implemented primitives in their capabilities. Control model: **Prompts** are user-controlled, **Resources** application-controlled, **Tools** model-controlled.
|
||||
|
||||
### Resources
|
||||
Application-driven context exposed via URIs. Capability `resources` with optional `subscribe` and `listChanged`. Methods: `resources/list`, `resources/read`, `resources/templates/list` (RFC 6570 URI templates), plus `resources/subscribe`/`unsubscribe` and `notifications/resources/*`. **Resource links** — tools and resources can reference related resources; a `resource` content item (with `uri`, optional `mimeType`, and inline `text`/`blob`) embeds a resource, and tool results can emit resource links so clients can pull further context. All content items carry optional `annotations` (`audience`, `priority`, `lastModified`).
|
||||
|
||||
### Prompts
|
||||
User-controlled templated messages. Capability `prompts` (with optional `listChanged`). Methods: `prompts/list`, `prompts/get` (takes `name` + `arguments`). A `PromptMessage` has `role` (`user`/`assistant`) and typed `content` (text/image/audio/embedded-resource). Arguments can be autocompleted via the completion utility.
|
||||
|
||||
### Tools
|
||||
Model-controlled functions. Capability `tools` (with optional `listChanged`). Methods: `tools/list`, `tools/call`. A tool definition carries `name`, optional `title`/`description`, `inputSchema` (JSON Schema), and optional `outputSchema` (JSON Schema) and `annotations` (treated as untrusted). **Structured output:** a `tools/call` result returns a `content[]` array (text/image/audio/resource items) **plus an optional `structuredContent`** JSON object. When `outputSchema` is declared, the server **MUST** return structured results conforming to it and clients **SHOULD** validate; for backwards compatibility a tool returning `structuredContent` **SHOULD** also emit the serialized JSON in a `TextContent` block. Execution failures use `isError: true`; protocol errors use standard JSON-RPC codes.
|
||||
|
||||
### Utilities / Logging
|
||||
Cross-cutting utilities: `ping` (liveness), `notifications/progress` (progress tracking), `notifications/cancelled` (cancellation via request id), `notifications/initialized`, and `completion/complete` (argument autocompletion). **Logging:** servers with the `logging` capability emit `notifications/message` with a level (`debug`..`emergency`) and structured data; clients set level via `logging/setLevel`.
|
||||
|
||||
## Client Features
|
||||
|
||||
### Sampling
|
||||
Server-initiated LLM generation. Client capability `sampling`. Method `sampling/createMessage` — the server sends `messages`, optional `systemPrompt`, `maxTokens`, and `modelPreferences` (`hints` + normalized `intelligencePriority`/`speedPriority`/`costPriority` in 0–1); the client performs human-in-the-loop review, selects the model, and returns `role`/`content`/`model`/`stopReason`. Human approval is expected; the server never supplies API keys.
|
||||
|
||||
### Roots
|
||||
Server-initiated inquiry into client filesystem boundaries. Client capability `roots` (with optional `listChanged`). Method `roots/list` returns `file://` URIs the server may operate within; `notifications/roots/list_changed` signals updates. Clients **MUST** validate URIs (path traversal) and obtain user consent.
|
||||
|
||||
### Elicitation
|
||||
Server-initiated structured input from the end user. Client capability `elicitation`. Method `elicitation/create` — the server sends a human-readable `message` plus a `requestedSchema`, a *restricted* JSON Schema (flat object of primitive properties only: string/number/boolean/enums, no nesting). The client presents a form and responds with `action: "accept"` (+ `content` object), `"decline"`, or `"cancel"`. **Privacy/safety:** servers **MUST NOT** use elicitation to request sensitive information (passwords, API keys, credentials); clients **SHOULD** make the requesting server clear and provide explicit decline/cancel. Because responses may be sensitive, they must not be echoed into logs or persisted beyond need.
|
||||
|
||||
## Changes vs 2024-11-05
|
||||
- **Streamable HTTP** replaces the HTTP+SSE two-endpoint transport (single `POST`/`GET` MCP endpoint, `Mcp-Session-Id` header, SSE resumability via `Last-Event-ID`, `MCP-Protocol-Version` header).
|
||||
- **Elicitation** client feature added (`elicitation/create` with a restricted JSON Schema form; accept/decline/cancel).
|
||||
- **Structured tool output** — tools may return `structuredContent` (typed JSON) plus an optional `outputSchema`; structured results SHOULD also serialize to a `TextContent` block.
|
||||
- **Resource links** — tools/resources can reference related resources (embedded `resource` content items, annotations).
|
||||
- **JSON-RPC batching removed** (was permitted in 2024-11-05; each message is now standalone).
|
||||
- **Authorization strengthened:** OAuth 2.1 Resource Indicators (RFC 8707) with the `resource` parameter; OAuth 2.0 Protected Resource Metadata (RFC 9728) + Resource Server classification; Authorization Server Metadata (RFC 8414); Dynamic Client Registration (RFC 7591) recommended.
|
||||
- **Protocol remains stateful** with connection-level capability negotiation; client features are now Sampling, Roots, and Elicitation.
|
||||
|
||||
## Skald relevance
|
||||
Skald's MCP stack implements this revision. `crates/mcp-client/` provides the transport/runtime primitives — `McpServerClient` trait (`src/lib.rs:67`), stdio server, and `McpHttpServer` (`http_server.rs`) — while `src/core/mcp/mod.rs` (`McpManager`) registers and drives connected servers, and `src/core/elicitation/mod.rs` bridges the new `elicitation/create` server→client request through `ElicitationManager` (surfaced in the Inbox) with secrets never logged or persisted. 2025-06-18 is therefore the baseline that introduced the Elicitation feature Skald implements end-to-end.
|
||||
|
||||
## References
|
||||
- [Specification overview](https://modelcontextprotocol.io/specification/2025-06-18)
|
||||
- [Architecture](https://modelcontextprotocol.io/specification/2025-06-18/architecture)
|
||||
- [Base protocol](https://modelcontextprotocol.io/specification/2025-06-18/basic)
|
||||
- [Server features](https://modelcontextprotocol.io/specification/2025-06-18/server)
|
||||
- [Client features](https://modelcontextprotocol.io/specification/2025-06-18/client)
|
||||
- [Schema (schema.ts)](https://github.com/modelcontextprotocol/specification/blob/main/schema/2025-06-18/schema.ts)
|
||||
- [Release 2025-06-18](https://github.com/modelcontextprotocol/modelcontextprotocol/releases/tag/2025-06-18)
|
||||
@@ -0,0 +1,135 @@
|
||||
# MCP Specification — 2025-11-25 (Latest Stable)
|
||||
|
||||
> **Status:** Latest Stable (current `Latest` release)
|
||||
> **Released:** 2025-11-25 · repo tags `2025-11-25` (stable), `2025-11-25-RC` (pre-release)
|
||||
> **Spec site:** https://modelcontextprotocol.io/specification/2025-11-25
|
||||
> **Authoritative schema:** [schema/2025-11-25/schema.ts](https://github.com/modelcontextprotocol/specification/blob/main/schema/2025-11-25/schema.ts)
|
||||
|
||||
This is the current stable revision of the Model Context Protocol. It keeps the stateful, JSON-RPC 2.0 core of 2025-06-18 and refines authorization (adding OpenID Connect Discovery and OAuth Client ID metadata documents), introduces optional **icons** metadata across server primitives, **URL-mode elicitation**, **incremental scope consent** (step-up authorization), **tool-calling in sampling**, and ships an **experimental Tasks** mechanism for long-running, pollable operations.
|
||||
|
||||
## At a glance
|
||||
- **Protocol style:** stateful connections
|
||||
- **Capability negotiation:** connection-level (`initialize`)
|
||||
- **Transports:** stdio; Streamable HTTP
|
||||
- **Server features:** Resources, Prompts, Tools, Logging
|
||||
- **Client features:** Sampling, Roots, Elicitation
|
||||
- **Authorization:** OAuth 2.1 + Resource Indicators (RFC 8707) + **OIDC Discovery** + Protected Resource Metadata (RFC 9728) + Client ID metadata documents + incremental scope consent
|
||||
- **Experimental:** Tasks
|
||||
|
||||
## Architecture
|
||||
|
||||
Client-host-server topology built on JSON-RPC, focused on context exchange and sampling coordination.
|
||||
|
||||
- **Host:** creates/manages multiple clients, enforces consent and security policy, aggregates context. Each client has a 1:1 relationship with one server and maintains isolation between servers.
|
||||
- **Client:** establishes one stateful session per server, negotiates capabilities, routes messages, owns subscriptions/notifications.
|
||||
- **Server:** exposes resources/tools/prompts, may request sampling/roots/elicitation through client interfaces; can be a local process or remote service.
|
||||
|
||||
Design principles: servers should be easy to build, highly composable, must **not** read the whole conversation or "see into" other servers, and features can be added progressively. **Capability negotiation** is the contract: server features (resources, prompts, tools, logging) and client features (sampling, roots, elicitation, tasks) must be advertised in `capabilities` to be usable.
|
||||
|
||||
## Base Protocol
|
||||
|
||||
### Messages
|
||||
|
||||
All messages **MUST** be [JSON-RPC 2.0](https://www.jsonrpc.org/specification), UTF-8 encoded. Three kinds:
|
||||
|
||||
- **Requests** — `id` (string|integer) **REQUIRED**; **MUST NOT** be `null`; the requestor **MUST NOT** reuse an `id` within a session.
|
||||
- **Responses** — `result` (any object) on success; `error` (`code` integer + `message`) on failure. The `id` **MUST** match the originating request (except when the request `id` was unreadable due to malformation).
|
||||
- **Notifications** — one-way; **MUST NOT** carry an `id`; the receiver **MUST NOT** respond.
|
||||
|
||||
The `_meta` field is reserved for protocol-level and extension metadata; certain keys are reserved by MCP and implementers **MUST NOT** assume meaning for reserved keys. **JSON Schema dialect:** schemas without an explicit `$schema` default to [JSON Schema 2020-12](https://json-schema.org/draft/2020-12/schema); implementations **MUST** support at least 2020-12 and **SHOULD** document any additional dialects. The `icons` property is a standardized optional visual-identifier array (`src`, `mimeType`, `sizes`) usable on implementations/resources/tools/prompts; icon payloads **MAY** contain executable content (e.g. scripted SVG) so consumers **MUST** fetch them without credentials and treat them as untrusted.
|
||||
|
||||
### Lifecycle
|
||||
|
||||
Three phases: **Initialization → Operation → Shutdown**.
|
||||
|
||||
1. Client sends `initialize` with its `protocolVersion` (this **SHOULD** be the latest the client supports), client `capabilities`, and `clientInfo`.
|
||||
2. Server responds with the negotiated `protocolVersion`, its `capabilities`, `serverInfo` (`name`/`title`/`version`/`description`/`icons`/`websiteUrl`), and optional `instructions`.
|
||||
3. Client sends an `notifications/initialized` notification; only then does normal operation begin. Before `initialize` is answered the client **SHOULD NOT** send non-`ping` requests; before `initialized` the server **SHOULD NOT** send non-`ping`/non-logging requests.
|
||||
|
||||
**Version negotiation:** if the server supports the requested version it echoes it; otherwise it returns the latest version *it* supports. If the client cannot accept that version it **SHOULD** disconnect. Over HTTP the client **MUST** send `MCP-Protocol-Version: <version>` on every subsequent request.
|
||||
|
||||
### Transports
|
||||
|
||||
- **stdio** — client launches the server as a subprocess; newline-delimited JSON-RPC on stdin/stdout; messages **MUST NOT** contain embedded newlines; stdout is reserved exclusively for valid MCP messages; `stderr` is for optional logging. Clients **SHOULD** support stdio whenever possible.
|
||||
- **Streamable HTTP** — the server exposes a **single MCP endpoint** supporting POST and GET. The client POSTs each JSON-RPC message with `Accept: application/json, text/event-stream`; the server answers synchronously (`application/json`) or opens an SSE stream. **Session management:** the server **MAY** assign an `Mcp-Session-Id` on the `InitializeResult` response; once issued, the client **MUST** send that header on subsequent requests and the server **MAY** reject mismatches with 404. **Resumability:** servers **MAY** attach SSE `id`s; clients resume after disconnect via HTTP GET with `Last-Event-ID`. Servers **MUST** validate the `Origin` header (403 on mismatch) and **SHOULD** bind to localhost locally to prevent DNS-rebinding. Replaces the deprecated HTTP+SSE transport from 2024-11-05.
|
||||
|
||||
### Authorization
|
||||
|
||||
Authorization is **OPTIONAL**. HTTP-based implementations **SHOULD** conform; stdio **SHOULD NOT** (use environment credentials instead). The model: the MCP server is an OAuth 2.1 **resource server**; the MCP client is the OAuth 2.1 **client**; a separate **authorization server** issues tokens.
|
||||
|
||||
- Built on **OAuth 2.1** (draft-ietf-oauth-v2-1-13) with PKCE and **Resource Indicators (RFC 8707)**.
|
||||
- **Protected Resource Metadata (RFC 9728)** — MCP servers **MUST** implement it; clients **MUST** use it for authorization-server discovery (via `WWW-Authenticate: resource_metadata=…` on 401, or a `.well-known/oauth-protected-resource` URI). Servers **SHOULD** advertise required `scope` in the 401 challenge.
|
||||
- **Authorization-server metadata** — the authorization server **MUST** expose either **OAuth 2.0 Authorization Server Metadata (RFC 8414)** *or* **OpenID Connect Discovery 1.0** (`.well-known/openid-configuration`); clients **MUST** support **both**, trying RFC 8414 first.
|
||||
- **OAuth Client ID Metadata Documents** (draft-ietf-oauth-client-id-metadata-document-00) — clients/servers **SHOULD** support this public-client identification mechanism; RFC 7591 Dynamic Client Registration **MAY** be supported.
|
||||
- **Incremental scope consent** — the step-up / scope-challenge flow: clients start with the minimal `scopes_supported` (or the 401 `scope`) and request additional scopes only when the server returns an insufficient-scope error (`WWW-Authenticate` with a narrowed challenge), satisfying least-privilege progressively.
|
||||
|
||||
### Versioning
|
||||
|
||||
There is no dedicated versioning page in this revision; protocol-version agreement is defined within **Lifecycle** (the `initialize` handshake and the `MCP-Protocol-Version` header). The version string for this revision is `2025-11-25`. The spec site's `/basic/versioning` URL does **not** resolve for 2025-11-25.
|
||||
|
||||
## Server Features
|
||||
|
||||
### Resources
|
||||
|
||||
Application-driven context exposed by URI. Capability `resources` with optional `subscribe` and `listChanged`. Operations: `resources/list` (paginated), `resources/read`, `resources/templates/list` (RFC 6570 URI templates, auto-completable). Resources carry `uri`, `name`, optional `title`/`description`/`mimeType`/`icons`/`annotations` (`audience`, `priority`, `lastModified`). Changes are signalled via `notifications/resources/*`; **resource links** let any `text` content embed typed links to other resources through their URI. Clients **MUST** obtain user consent before exposing resource data.
|
||||
|
||||
### Prompts
|
||||
|
||||
User-controlled templates (e.g. slash commands). Capability `prompts` with optional `listChanged`. Operations: `prompts/list` (paginated), `prompts/get` (with arguments). A prompt has `name`, optional `title`/`description`/`arguments`/`icons`; messages are `role` + typed `content` (text/image/audio/embedded-resource), each supporting `annotations`.
|
||||
|
||||
### Tools
|
||||
|
||||
Model-controlled functions. Capability `tools` with optional `listChanged`. Operations: `tools/list` (paginated) and `tools/call`. A tool defines `name`, optional `title`/`description`/`icons`, `inputSchema` (defaults to JSON Schema 2020-12), optional `outputSchema`, and `annotations` (e.g. `readOnlyHint`, `destructiveHint`, `idempotentHint`, `openWorldHint`). **Structured output:** when `outputSchema` is present, the server **MUST** return conforming results as a JSON object in `structuredContent`, and **SHOULD** also mirror it in a `TextContent` block for backwards compatibility; clients **SHOULD** validate against the schema. Results return `content[]`, an `isError` flag, and the optional `structuredContent`. A human **SHOULD** remain in the loop to authorize invocations.
|
||||
|
||||
### Utilities / Logging
|
||||
|
||||
Cross-cutting utilities: **pagination** (`cursor`/`nextCursor` on list ops), **completion** (`completion/complete` for argument auto-complete), **ping** (`ping`), **cancellation** (`notifications/cancelled`), **progress** (`notifications/progress`), and **logging** — capability `logging`; clients configure level via `logging/setLevel`, servers emit `notifications/message` with severity, logger name, and structured data.
|
||||
|
||||
## Client Features
|
||||
|
||||
### Sampling
|
||||
|
||||
Server-initiated LLM completions via `sampling/createMessage`. Clients **MUST** declare `sampling`; **human-in-the-loop** review of the prompt and result is **RECOMMENDED**. Requests carry `messages`, optional `systemPrompt`, `modelPreferences` (`hints`, `intelligencePriority`/`speedPriority`), `maxTokens`, and (when the client advertises `sampling.tools`) a `tools` array plus optional `toolChoice`. **Tool-calling support:** the client's LLM may emit `tool_use`; the server executes the tool and re-issues `sampling/createMessage` with the tool results, looping until `stopReason: "endTurn"`. The legacy `includeContext: "thisServer"|"allServers"` values (gated behind `sampling.context`) are soft-deprecated.
|
||||
|
||||
### Roots
|
||||
|
||||
Server-initiated discovery of filesystem boundaries. Capability `roots` (optional `listChanged`). Server sends `roots/list`; client returns `uri` (currently **MUST** be a `file://` URI) plus optional `name`. Clients emit `notifications/roots/list_changed` when the set changes. Clients **MUST** validate root URIs against path traversal and **SHOULD** prompt for consent before exposing them.
|
||||
|
||||
### Elicitation
|
||||
|
||||
Server-initiated requests for user information via `elicitation/create`. Capability `elicitation` with sub-modes `form` and `url` (an empty object is equivalent to `form`-only; at least one mode **MUST** be supported). Two modes:
|
||||
|
||||
- **Form mode** — in-band structured input. `requestedSchema` is restricted to a flat object of primitive properties (string/number/integer/boolean/enum, including `oneOf`/multi-select arrays); formats `email`, `uri`, `date`, `date-time`. Response `action` is `accept` (with `content`), `decline`, or `cancel`.
|
||||
- **URL mode** (new in 2025-11-25) — out-of-band interaction for sensitive data (secrets, OAuth, payments) that must **not** transit the client. Parameters: `mode: "url"`, `url`, `elicitationId`, `message`. The client only collects consent and opens the URL; the response is `action: "accept"` with **no** content. Servers **MAY** fire `notifications/elicitation/complete` and **MAY** return error `-32042` (`URLElicitationRequiredError`) carrying required elicitations. Servers **MUST** use URL mode (never form mode) for passwords/API keys/tokens/credentials.
|
||||
|
||||
Privacy: clients **MUST** show which server is asking, provide clear decline/cancel controls, let users review form responses before sending, and never log/retain secrets.
|
||||
|
||||
## Experimental: Tasks
|
||||
|
||||
Introduced in 2025-11-25 as **experimental**. Tasks are durable state machines that wrap a request for pollable, deferred-result execution (expensive computation, batch jobs, external job APIs). Either party may be a **requestor** or **receiver**. A task is created by adding a `task` field (with optional `ttl`) to an augmentable request; the receiver returns a `CreateTaskResult` with a `taskId`, `status` (`working`→`completed`/`failed`/`cancelled`, plus `input_required`), `pollInterval`, and timestamps. The real result is fetched later via `tasks/result`; status via `tasks/get`, optional `notifications/tasks/status`, listing via `tasks/list`, termination via `tasks/cancel`. Capability `tasks` is structured per request category (e.g. `tasks.requests.tools.call` server-side; `tasks.requests.sampling.createMessage` / `tasks.requests.elicitation.create` client-side), with tool-level negotiation through `execution.taskSupport` (`required`/`optional`/`forbidden`). The design may be formalized or modified in future revisions.
|
||||
|
||||
## Changes vs 2025-06-18
|
||||
|
||||
- **OpenID Connect Discovery 1.0** added as a first-class authorization-server metadata mechanism (clients must support it alongside RFC 8414).
|
||||
- **Icons metadata** — optional `icons[]` on implementations, resources, tools, and prompts.
|
||||
- **Incremental scope consent** — step-up authorization via scope challenges (request additional scopes only on insufficient-scope errors).
|
||||
- **URL-mode elicitation** (`mode: "url"`, `URLElicitationRequiredError` `-32042`, `notifications/elicitation/complete`) for sensitive out-of-band interactions.
|
||||
- **Sampling tool-calling** — `sampling.tools` capability; servers may pass `tools`/`toolChoice` and run a multi-turn tool loop.
|
||||
- **OAuth Client ID metadata documents** (draft-ietf-oauth-client-id-metadata-document-00) for public-client identification.
|
||||
- **Experimental Tasks** — durable, pollable, deferred-result request augmentation.
|
||||
- Unchanged: stateful JSON-RPC 2.0 core (batching already removed in 2025-06-18), connection-level capability negotiation, stdio + Streamable HTTP transports, structured tool output (`outputSchema`/`structuredContent`, from 2025-06-18), elicitation form mode, resource links.
|
||||
|
||||
## Skald relevance
|
||||
|
||||
This is the revision Skald's MCP client targets. The client lives in `crates/mcp-client/` (stdio + Streamable HTTP, with an `elicitation` integration test); runtime connection management is `src/core/mcp/mod.rs` (`McpManager`); server-initiated `elicitation/create` is handled by `src/core/elicitation/mod.rs` (`ElicitationManager` + bridge), surfaced in the unified **Inbox**, with secrets never logged or persisted. **Non-text tool-result content** (`image`/`audio`/`resource`/`resource_link`) is now preserved rather than dropped: it is persisted under `data/mcp_media/` and surfaced as a `/api/mcp-media/` URL in a markdown result (see [`../../mcp.md`](../../mcp.md) → *Media tool results*).
|
||||
|
||||
**Cancellation** (`notifications/cancelled`) is implemented: an abandoned `tools/call` (a `/stop` that drops the work future, or the 120 s timeout) now tells the server to stop via a per-transport drop-guard, instead of leaving it working on abandoned output. **Tasks** are polled **synchronously (block-and-poll)**: `call_tool` opts a task-capable tool in with the `task` field, then `poll_task` polls `tasks/get` until terminal and fetches the real result via `tasks/result` — so a task-mode call no longer hits the 120 s wall — sending `tasks/cancel` cooperatively if the call is dropped. Both are documented in [`../../mcp.md`](../../mcp.md) → *Cancellation & Tasks*. The block-and-poll variant holds the session for the task's duration and doesn't survive a self-restart; the **detached/durable** poller (DB-persisted tasks + background delivery via `inject_async_result`/`resume_turn`) is the tracked follow-up. Still unimplemented from this revision: **sampling**, **roots**, **URL-mode elicitation**, mid-task `input_required`, progress/completion/logging utilities, and OAuth authorization (HTTP transport).
|
||||
|
||||
## References
|
||||
- [Specification overview](https://modelcontextprotocol.io/specification/2025-11-25)
|
||||
- [Architecture](https://modelcontextprotocol.io/specification/2025-11-25/architecture)
|
||||
- [Base protocol](https://modelcontextprotocol.io/specification/2025-11-25/basic)
|
||||
- [Server features](https://modelcontextprotocol.io/specification/2025-11-25/server)
|
||||
- [Client features](https://modelcontextprotocol.io/specification/2025-11-25/client)
|
||||
- [Schema (schema.ts)](https://github.com/modelcontextprotocol/specification/blob/main/schema/2025-11-25/schema.ts)
|
||||
- [Release 2025-11-25](https://github.com/modelcontextprotocol/modelcontextprotocol/releases/tag/2025-11-25)
|
||||
@@ -0,0 +1,114 @@
|
||||
# MCP Specification — 2026-07-28 Draft (RC)
|
||||
|
||||
> **Status:** Draft / Release Candidate (pre-release). Subject to change — not final.
|
||||
> **Tag:** `2026-07-28-RC` · rendered at `/specification/draft`
|
||||
> **Spec site:** https://modelcontextprotocol.io/specification/draft
|
||||
> **Authoritative schema:** [schema/draft/schema.ts](https://github.com/modelcontextprotocol/specification/blob/main/schema/draft/schema.ts)
|
||||
|
||||
The `2026-07-28` draft is a **foundational redesign** of the Model Context Protocol, not an incremental revision. It moves from the prior stateful-connection model to a **stateless base protocol with self-contained requests**, replaces connection-level capability negotiation (the `initialize` handshake) with **per-request capability negotiation**, and introduces a formal **extensions framework** for opt-in modular functionality. Several older client/server features are deprecated under a new feature-lifecycle policy. This is the direction future Skald MCP work should anticipate; the per-request shift and the Tasks extension are the most impactful changes to plan for.
|
||||
|
||||
## At a glance
|
||||
- **Protocol style:** stateless, self-contained requests (no session state inferred across requests)
|
||||
- **Capability negotiation:** per-request, via `_meta.io.modelcontextprotocol/clientCapabilities`
|
||||
- **Transports:** **stdio** (newline-delimited JSON-RPC over a client-launched subprocess) and **Streamable HTTP** (single MCP endpoint; `POST` per message, request-scoped SSE for replies). Custom transports **MAY** be defined. (HTTP+SSE was deprecated earlier, in `2025-03-26`.)
|
||||
- **Server features:** Resources, Prompts, Tools
|
||||
- **Client features (core):** **Elicitation only**. (Sampling and Roots are deprecated — see below.)
|
||||
- **Extensions (opt-in):** Tasks (`io.modelcontextprotocol/tasks`), MCP Apps (`io.modelcontextprotocol/ui`), Skills over MCP (in Working Group / experimental), OAuth client-credentials, enterprise-managed authorization, …
|
||||
- **Authorization:** OAuth 2.1 + PKCE, HTTP transports only; stdio uses environment credentials
|
||||
|
||||
## The redesign (why this matters)
|
||||
Three structural changes define this draft:
|
||||
|
||||
1. **Stateless base protocol.** All information needed to process a request is contained in the request itself — protocol version, client identity, and capabilities travel in the `_meta` field on every request. Servers **MUST NOT** rely on prior requests to establish context, and **SHOULD** handle requests associated with multiple tasks/threads/conversations.
|
||||
2. **Per-request capability negotiation.** The connection-level `initialize` handshake is gone for modern clients; capabilities are declared on each request, and the server's capabilities are discoverable via `server/discover`. This replaces session-scoped negotiation entirely.
|
||||
3. **Extensions framework.** Optional, always opt-in, modular capabilities (Tasks, MCP Apps, …) negotiated through the `extensions` field of capabilities. Core client features are trimmed to Elicitation; Sampling, Roots, and Logging move to **deprecated** status under a formal lifecycle policy (not silent removal).
|
||||
|
||||
## Architecture
|
||||
The client-host-server topology is retained. A **host** creates and manages multiple **client** instances; each client has a 1:1 relationship with exactly one **server** and enforces isolation (a server cannot see the whole conversation or other servers). The change is that clients now "attach protocol version and capabilities to every request" rather than establishing a session. Servers expose resources/tools/prompts and **MAY** request client input (sampling/elicitation/roots) by returning an `InputRequiredResult` within a reply. Design principles are unchanged: servers should be easy to build, highly composable, mutually isolated, and progressively extensible.
|
||||
|
||||
## Base Protocol
|
||||
|
||||
### Messages
|
||||
All messages **MUST** be [JSON-RPC 2.0](https://www.jsonrpc.org/specification), UTF-8 encoded. Three kinds: **Requests** (with a string/integer `id`; the id **MUST NOT** be `null` and **MUST NOT** match any in-flight request id), **Responses**, and **Notifications** (no `id`; receiver **MUST NOT** reply). A notable addition is **polymorphic results**: every result **MUST** include a `resultType` field — `"complete"` (final content), `"input_required"` (an `InputRequiredResult`, driving a multi-round-trip request), or extension-defined values; an absent `resultType` **MUST** be treated as `"complete"` for backward compatibility. JSON-RPC's reserved server-error range is now partitioned: `-32000`–`-32019` is legacy (new codes **MUST NOT** be allocated there); `-32020`–`-32099` is reserved for the MCP specification. Defined codes: `-32020` `HeaderMismatch`, `-32021` `MissingRequiredClientCapability`, `-32022` `UnsupportedProtocolVersion`. Three message patterns are supported: **Request/Response**, **Multi Round-Trip Requests (MRTR)**, and **Subscribe/Notify** (`subscriptions/listen`).
|
||||
|
||||
### Lifecycle / Negotiation
|
||||
There is **no negotiation handshake** in the modern protocol. Every request declares its protocol version in `_meta` (also mirrored in the `MCP-Protocol-Version` header on HTTP). If the server does not support the requested version it **MUST** respond `UnsupportedProtocolVersionError` (`-32022`) listing its `supported` versions; the client **SHOULD** pick a mutually supported version and retry. Servers **MUST** implement `server/discover`; clients **MAY** call it first to learn supported versions and capabilities up front, but are free to invoke any RPC inline and handle the error. Capability negotiation is therefore **per-request**: clients advertise capabilities in `_meta.io.modelcontextprotocol/clientCapabilities`; servers expose theirs through `server/discover`. Extensions are advertised the same way, via a `capabilities.extensions` map keyed by extension identifier.
|
||||
|
||||
### Transports
|
||||
A transport is a binding (framing + metadata + cancellation signalling); message semantics are identical on every transport. Two standard bindings:
|
||||
- **stdio** — newline-delimited JSON-RPC over a client-launched subprocess; `notifications/cancelled` abandons an in-flight request. Clients **SHOULD NOT** tie the subprocess lifetime to a single task/thread/conversation.
|
||||
- **Streamable HTTP** — every client message is a `POST` to a single MCP endpoint; a reply arrives as a JSON object or a request-scoped SSE stream. Selected `_meta` fields are mirrored into HTTP headers so intermediaries can route without parsing the body (body remains source of truth). Cancellation closes the response stream.
|
||||
|
||||
Custom transports **MAY** be defined but **MUST** preserve JSON-RPC format, the message patterns, and the per-request metadata model; those over a reliable byte stream **SHOULD** reuse stdio framing.
|
||||
|
||||
### Authorization
|
||||
Optional. For HTTP transports it is built on **OAuth 2.1** with PKCE; stdio **SHOULD NOT** use it and instead retrieves credentials from the environment. The MCP server is an OAuth 2.1 *resource server* and **MUST** implement OAuth 2.0 Protected Resource Metadata ([RFC 9728](https://www.rfc-editor.org/rfc/rfc9728.html)), signalling the metadata URL via `WWW-Authenticate` on `401`. Authorization servers **MUST** provide discovery via OAuth 2.0 Authorization Server Metadata ([RFC 8414](https://datatracker.ietf.org/doc/html/rfc8414)) or OIDC Discovery; clients **MUST** support both. Client registration priority: **Client ID Metadata Documents** (new, preferred) → pre-registered → **Dynamic Client Registration** ([RFC 7591](https://datatracker.ietf.org/doc/html/rfc7591), now **deprecated**). Resource Indicators ([RFC 8707](https://www.rfc-editor.org/rfc/rfc8707.html)) and the `iss` parameter ([RFC 9207](https://datatracker.ietf.org/doc/html/rfc9207)) apply. Servers **SHOULD** advertise required `scope` in the `WWW-Authenticate` challenge; clients follow a least-privilege scope-selection strategy with step-up authorization.
|
||||
|
||||
### Versioning
|
||||
Protocol versions are date strings (e.g. `2026-07-28`). Terminology: **Modern** = per-request metadata (this revision and later); **Legacy** = `initialize`-handshake versions (`2025-11-25` and earlier); **Dual-era** = implementations supporting both. A dual-era server selects behaviour from how the client opens: a request carrying modern `_meta` is served statelessly; an `initialize` request selects legacy semantics. Clients detect a server's era via transport-specific probing (stdio: `server/discover` and fall back on any non-modern error; HTTP: attempt a modern request and inspect a `400` body). The era is a property of the server and **SHOULD** be cached for the process/origin lifetime. A full compatibility matrix (Modern/Legacy/Dual-era × client/server) is specified.
|
||||
|
||||
## Server Features
|
||||
Servers advertise implemented primitives in their capabilities. Control model unchanged: **Prompts** user-controlled, **Resources** application-controlled, **Tools** model-controlled.
|
||||
|
||||
### Resources
|
||||
Context/data exposed by the server, addressed by URI. Discovery and reading are RPC-driven; clients open a `subscriptions/listen` stream to receive `notifications/*` (tagged with a `subscriptionId`) when resources change. The old `-32002` "resource not found" code is replaced by `-32602`; clients **SHOULD** still accept `-32002` from legacy servers.
|
||||
|
||||
### Prompts
|
||||
Templated messages/workflows invoked by user choice (e.g. slash commands, menu options). Listed and fetched on demand; the model does not pick them autonomously.
|
||||
|
||||
### Tools
|
||||
Functions exposed to the LLM (model-controlled). Tool invocation requires the server to declare tool capabilities. Tools **MAY** return structured output, and — via extensions such as MCP Apps — can reference interactive UI resources.
|
||||
|
||||
### Utilities
|
||||
Cross-cutting concerns now consist of **Configuration**, **Progress tracking**, **Cancellation**, and **Error reporting**. **Logging is no longer a core utility: it is deprecated** (SEP-2577). The migration path is to log to `stderr` for stdio transports and to use [OpenTelemetry](https://opentelemetry.io/) for observability.
|
||||
|
||||
## Client Features
|
||||
### Elicitation
|
||||
The **only core client feature**. Servers **MAY** request user input during request processing by returning an `InputRequiredResult` containing an `elicitation/create` request. Two modes:
|
||||
- **Form mode** — in-band structured data with optional JSON-Schema validation. Schemas are restricted to flat objects of primitive properties (string/number/integer/boolean/enum). Servers **MUST NOT** use form mode to request secrets (passwords, API keys, tokens, payment credentials).
|
||||
- **URL mode** — out-of-band interaction via URL navigation; data (other than the URL) is **not** exposed to the client. Servers **MUST** use URL mode for sensitive information.
|
||||
|
||||
Clients supporting elicitation **MUST** declare `_meta.io.modelcontextprotocol/clientCapabilities.elicitation` (with `form` and/or `url` sub-capabilities; empty object ≡ `form` only) on each request. Clients **MUST** make clear which server is asking, and provide decline/cancel options.
|
||||
|
||||
## Extensions (new)
|
||||
### Overview
|
||||
Extensions are optional additions beyond the core protocol — modular, specialized, or experimental. They are identified by `{vendor-prefix}/{extension-name}` (official ones use `io.modelcontextprotocol/`), advertised in the `capabilities.extensions` map (per-request), and **always disabled by default** — requiring explicit opt-in from both sides. If one side supports an extension the other lacks, it **MUST** fall back to core behaviour or reject with an appropriate error. Extensions evolve independently and **SHOULD** use capability flags or in-extension versioning rather than new identifiers for breaking changes. Official extensions live in `ext-*` repositories; experimental ones (incubating in Working/Interest Groups) use the `experimental-ext-` prefix.
|
||||
|
||||
### Tasks
|
||||
Extension `io.modelcontextprotocol/tasks` (repo `ext-tasks`). Lets a server return a durable handle instead of blocking on long-running operations (CI pipelines, batch jobs, human approvals). Flow: the client declares the extension per-request; when a request will be long-running the server returns a `CreateTaskResult` (`resultType: "task"`) carrying a `taskId`, initial status, TTL, and suggested `pollIntervalMs`; the client polls `tasks/get`; if status becomes `input_required`, the response carries `inputRequests` which the client fulfils via `tasks/update`; terminal states are `completed` / `failed` / `cancelled`. The client **MAY** send `tasks/cancel` (cooperative). Servers **MAY** push `notifications/tasks` via `subscriptions/listen`; polling remains the default. Task IDs are durable and survive client disconnect/restart.
|
||||
|
||||
### Skills over MCP
|
||||
An emerging extension (Skills Over MCP Working Group; current direction is SEP-2640, a Resources-based Extensions-Track proposal; reference work in `experimental-ext-skills`). It defines how "agent skills" — rich, structured instructions for agent workflows — are discovered, distributed, and consumed through MCP. **Not yet a finalized official extension** at the time of this draft.
|
||||
|
||||
### MCP Apps
|
||||
Extension `io.modelcontextprotocol/ui` (repo `ext-apps`). Lets a server return interactive HTML (charts, forms, video players, dashboards) rendered inline in the conversation. A tool declares a UI reference via `_meta.ui.resourceUri` pointing at a `ui://` resource; the host fetches and renders the HTML inside a **sandboxed iframe**, isolated from the parent page. Communication uses a JSON-RPC dialect over `postMessage` with a `ui/` method prefix (e.g. `ui/initialize`); `_meta.ui.csp` and `_meta.ui.permissions` control loading and capabilities. Already supported by several clients (Claude, Claude Desktop, VS Code Copilot, Microsoft 365 Copilot, Goose, Postman, and others).
|
||||
|
||||
## Changes vs 2025-11-25
|
||||
- **Stateless base protocol**: requests are self-contained; servers **MUST NOT** rely on connection/session state.
|
||||
- **Per-request capability negotiation** replaces the `initialize` handshake; `server/discover` provides up-front discovery.
|
||||
- **Polymorphic results** (`resultType`: `complete` / `input_required` / extension values) and a formal MRTR pattern via `InputRequiredResult`.
|
||||
- **New spec-reserved error range** (`-32020`–`-32099`) and codes (`HeaderMismatch`, `MissingRequiredClientCapability`, `UnsupportedProtocolVersion`).
|
||||
- **Elicitation is the only core client feature**; **Sampling and Roots are deprecated** (SEP-2577), retained ≥12 months, eligible for removal in the first revision released on/after **2027-07-28**. Migration: Roots → tool parameters/resource URIs/server configuration; Sampling → integrate directly with LLM provider APIs.
|
||||
- **Logging deprecated** as a utility (SEP-2577) → `stderr` / OpenTelemetry.
|
||||
- **Dynamic Client Registration deprecated** (→ Client ID Metadata Documents).
|
||||
- **Extensions framework** introduced; **Tasks** stabilized as an official extension (`io.modelcontextprotocol/tasks`), **MCP Apps** added (`io.modelcontextprotocol/ui`), **Skills over MCP** incubating.
|
||||
|
||||
## Skald relevance
|
||||
Skald's MCP surface lives in `crates/mcp-client/` (the `McpServer` stdio wrapper, `McpHttpServer`, and the `McpServerClient` trait), `src/core/mcp/` (`McpManager`), and `src/core/elicitation/` (`ElicitationManager` + bridge, surfaced in the Inbox). Two draft changes matter most for future planning: (1) the **stateless / per-request** shift means capability and version metadata must be attached to every request rather than assumed from a session, and `server/discover` becomes the discovery primitive; (2) the **Tasks extension** maps closely onto Skald's async/background work and would let MCP servers return durable, pollable handles for long-running operations. Skald's existing elicitation bridge already corresponds to the one remaining core client feature, so that path forward is well aligned. Until the draft finalizes, treat Sampling/Roots/Logging as deprecated-but-present for interop only.
|
||||
|
||||
## References
|
||||
- [Specification (draft)](https://modelcontextprotocol.io/specification/draft)
|
||||
- [Architecture](https://modelcontextprotocol.io/specification/draft/architecture)
|
||||
- [Base protocol](https://modelcontextprotocol.io/specification/draft/basic)
|
||||
- [Versioning and compatibility](https://modelcontextprotocol.io/specification/draft/basic/versioning)
|
||||
- [Transports](https://modelcontextprotocol.io/specification/draft/basic/transports)
|
||||
- [Authorization](https://modelcontextprotocol.io/specification/draft/basic/authorization)
|
||||
- [Server features](https://modelcontextprotocol.io/specification/draft/server)
|
||||
- [Client features — Elicitation](https://modelcontextprotocol.io/specification/draft/client/elicitation)
|
||||
- [Deprecated features registry](https://modelcontextprotocol.io/specification/draft/deprecated)
|
||||
- [Extensions overview](https://modelcontextprotocol.io/extensions/overview)
|
||||
- [Tasks extension](https://modelcontextprotocol.io/extensions/tasks/overview)
|
||||
- [MCP Apps extension](https://modelcontextprotocol.io/extensions/apps/overview)
|
||||
- [Skills over MCP Working Group](https://modelcontextprotocol.io/community/working-groups/skills-over-mcp)
|
||||
- [Schema (schema.ts)](https://github.com/modelcontextprotocol/specification/blob/main/schema/draft/schema.ts)
|
||||
- [Release 2026-07-28-RC](https://github.com/modelcontextprotocol/modelcontextprotocol/releases/tag/2026-07-28-RC)
|
||||
@@ -0,0 +1,74 @@
|
||||
# MCP Specification Reference
|
||||
|
||||
Quick-reference mirrors of each **official Model Context Protocol specification revision**,
|
||||
maintained as background for future Skald MCP work. The authoritative source of truth for
|
||||
every revision is the TypeScript schema (`schema/<version>/schema.ts`) in
|
||||
[`modelcontextprotocol/specification`](https://github.com/modelcontextprotocol/specification);
|
||||
these notes summarize each revision's structure, requirements, and deltas for fast lookup
|
||||
when implementing against `crates/mcp-client/` and `src/core/mcp/`.
|
||||
|
||||
Each file follows the same template — *At a glance → Architecture → Base Protocol
|
||||
(Messages / Lifecycle / Transports / Authorization / Versioning) → Server Features →
|
||||
Client Features → Changes vs previous → Skald relevance → References* — so revisions can be
|
||||
diffed section by section.
|
||||
|
||||
## Revisions
|
||||
|
||||
| Revision | Status | Released | Protocol style | Headline | File |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| **2026-07-28** | **Draft / RC** | ~2026-05 (pre-release) | Stateless, per-request caps | Foundational redesign: stateless base, per-request negotiation, extensions framework; Sampling/Roots/Logging deprecated | [2026-07-28-draft.md](2026-07-28-draft.md) |
|
||||
| **2025-11-25** | **Latest Stable** | 2025-11-25 | Stateful | OIDC Discovery, icons, URL-mode elicitation, sampling tool-calling, experimental Tasks | [2025-11-25.md](2025-11-25.md) |
|
||||
| **2025-06-18** | Stable | 2025-06-18 | Stateful | Streamable HTTP, Elicitation, structured tool output, OAuth Resource Indicators (RFC 8707) | [2025-06-18.md](2025-06-18.md) |
|
||||
| **2024-11-05** | Legacy | 2024-11-05 | Stateful | First public release: stdio + HTTP+SSE, Resources/Prompts/Tools/Logging, Sampling/Roots | [2024-11-05.md](2024-11-05.md) |
|
||||
|
||||
Additional repo tags not given their own file (point-revisions of the spec lines above):
|
||||
|
||||
- `2024-11-05-final` — final revision of the 2024-11-05 line (covered in [2024-11-05.md](2024-11-05.md)).
|
||||
- `2024-10-07` — pre-public preliminary tag, superseded by the public 2024-11-05 release.
|
||||
- `2025-03-26` — intermediate revision (deprecated the HTTP+SSE transport); superseded by 2025-06-18.
|
||||
- `2025-11-25-RC` — release candidate of the 2025-11-25 line.
|
||||
|
||||
## Feature availability across revisions
|
||||
|
||||
| Capability | 2024-11-05 | 2025-06-18 | 2025-11-25 | 2026-07-28 (draft) |
|
||||
| --- | :---: | :---: | :---: | :---: |
|
||||
| **stdio transport** | ✔ | ✔ | ✔ | ✔ |
|
||||
| **HTTP+SSE transport** | ✔ | ✘ (replaced) | ✘ | ✘ (deprecated since 2025-03-26) |
|
||||
| **Streamable HTTP transport** | ✘ | ✔ | ✔ | ✔ |
|
||||
| **Stateful connections** | ✔ | ✔ | ✔ | ✘ (stateless, self-contained requests) |
|
||||
| **Per-request capability negotiation** | ✘ | ✘ | ✘ | ✔ |
|
||||
| **Resources** | ✔ | ✔ | ✔ | ✔ |
|
||||
| **Prompts** | ✔ | ✔ | ✔ | ✔ |
|
||||
| **Tools** (structured output `outputSchema`/`structuredContent`) | ✘ | ✔ | ✔ | ✔ |
|
||||
| **Tools** (unstructured `content[]` only) | ✔ | ✔ | ✔ | ✔ |
|
||||
| **Logging** (server feature / utility) | ✔ | ✔ | ✔ | ✘ deprecated (→ stderr / OpenTelemetry) |
|
||||
| **Icons metadata** | ✘ | ✘ | ✔ | ✔ |
|
||||
| **Sampling** | ✔ | ✔ | ✔ | ✘ deprecated (SEP-2577) |
|
||||
| **Roots** | ✔ | ✔ | ✔ | ✘ deprecated (SEP-2577) |
|
||||
| **Elicitation** (form mode) | ✘ | ✔ | ✔ | ✔ (only core client feature) |
|
||||
| **Elicitation** (URL mode) | ✘ | ✘ | ✔ | ✔ |
|
||||
| **OAuth 2.1 authorization framework** | ✘ (not in core spec) | ✔ (RFC 8707, RFC 9728) | ✔ + OIDC Discovery + Client ID metadata | ✔ (DCR deprecated → Client ID metadata) |
|
||||
| **JSON-RPC batching** | ✔ | ✘ (removed) | ✘ | ✘ |
|
||||
| **Tasks** | ✘ | ✘ | experimental | extension `io.modelcontextprotocol/tasks` |
|
||||
| **Extensions framework** | ✘ | ✘ | ✘ | ✔ |
|
||||
|
||||
## Feature-lifecycle policy (introduced in the 2026-07-28 draft)
|
||||
|
||||
The draft formalizes how features are deprecated and removed ([SEP-2577](https://modelcontextprotocol.io/specification/draft/deprecated)):
|
||||
|
||||
- A deprecated feature is retained for **at least 12 months** and remains usable for interop.
|
||||
- It becomes **eligible for removal** in the first revision released on/after **2027-07-28**.
|
||||
- Currently deprecated: **Sampling**, **Roots**, **Logging**, **Dynamic Client Registration**.
|
||||
|
||||
When implementing against the draft, treat these as present-but-don't-build-new-dependencies.
|
||||
|
||||
## Source of truth
|
||||
|
||||
- **Schema (authoritative):** [`schema/<version>/schema.ts`](https://github.com/modelcontextprotocol/specification/tree/main/schema) — the TypeScript schema is normative; the prose restates it with BCP 14 keywords (MUST / SHOULD / MAY).
|
||||
- **Spec site:** [modelcontextprotocol.io/specification](https://modelcontextprotocol.io/specification) — per-version browsable spec.
|
||||
- **Releases:** [github.com/modelcontextprotocol/modelcontextprotocol/releases](https://github.com/modelcontextprotocol/modelcontextprotocol/releases) — tags, RCs, changelogs.
|
||||
|
||||
## Related Skald docs
|
||||
|
||||
- [../index.md](../index.md) — MCP subsystem overview (servers, transports, integration)
|
||||
- [../../mcp.md](../../mcp.md) — `McpManager`, transports, naming convention, enable/disable
|
||||
Reference in New Issue
Block a user