Let Claude, ChatGPT, or any MCP-compatible AI agent read sensors and control devices on your jettyd fleet via OAuth 2.0.
The jettyd MCP server exposes your IoT fleet as a set of tools consumable by large language models. It runs at https://mcp.jettyd.com and implements the Model Context Protocol over HTTP with Server-Sent Events (SSE). Authentication uses OAuth 2.0 with PKCE — no client secrets required.
https://mcp.jettyd.com/mcphttps://auth.jettyd.comhttps://mcp.jettyd.com/.well-known/oauth-authorization-servermcp.jettyd.com connects to your jettyd account via OAuth. If you need a locally-running server for Claude Desktop with an API key instead of OAuth, see the @jettyd/mcp npm package.
The hosted MCP server uses OAuth 2.0 Authorization Code flow with PKCE (Proof Key for Code Exchange). No client secret is needed — PKCE provides the security guarantee instead.
Fetch the authorization server metadata to get all endpoint URLs:
curl https://mcp.jettyd.com/.well-known/oauth-authorization-server
The server returns a JSON document with the endpoints your client needs:
{
"issuer": "https://auth.jettyd.com",
"authorization_endpoint": "https://auth.jettyd.com/oauth/authorize",
"token_endpoint": "https://auth.jettyd.com/oauth/token",
"introspection_endpoint": "https://auth.jettyd.com/oauth/introspect",
"scopes_supported": [
"devices:read", "devices:write",
"telemetry:read", "commands:write",
"rules:read", "rules:write"
],
"response_types_supported": ["code"],
"code_challenge_methods_supported": ["S256"],
"grant_types_supported": ["authorization_code", "refresh_token"]
}
Generate a PKCE code verifier and challenge, then redirect the user to the authorization endpoint:
# Generate a high-entropy code verifier (43-128 chars, URL-safe)
CODE_VERIFIER=$(openssl rand -base64 48 | tr -d '+/=\n' | head -c 64)
# Derive the S256 code challenge
CODE_CHALLENGE=$(printf '%s' "$CODE_VERIFIER" \
| openssl dgst -sha256 -binary \
| openssl base64 \
| tr -d '=' | tr '+/' '-_')
# Open the authorization URL in the user's browser
open "https://auth.jettyd.com/oauth/authorize?\
response_type=code\
&client_id=YOUR_CLIENT_ID\
&redirect_uri=http%3A%2F%2Flocalhost%3A8080%2Fcallback\
&scope=devices%3Aread+telemetry%3Aread+commands%3Awrite\
&code_challenge=${CODE_CHALLENGE}\
&code_challenge_method=S256\
&state=$(openssl rand -hex 16)"
state value and verify it matches when the authorization server redirects back to your redirect_uri. This prevents CSRF attacks.
After the user approves access, the authorization server redirects to your redirect_uri with a short-lived code parameter:
http://localhost:8080/callback?code=AUTH_CODE_HERE&state=YOUR_STATE
Exchange the authorization code for an access token. Include the original code_verifier — the server verifies it against the challenge from Step 2:
curl -X POST https://auth.jettyd.com/oauth/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=authorization_code" \
-d "code=AUTH_CODE_HERE" \
-d "redirect_uri=http%3A%2F%2Flocalhost%3A8080%2Fcallback" \
-d "client_id=YOUR_CLIENT_ID" \
-d "code_verifier=${CODE_VERIFIER}"
On success the server returns:
{
"access_token": "at_...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "rt_...",
"scope": "devices:read telemetry:read commands:write"
}
refresh_token with grant_type=refresh_token at the token endpoint to obtain a new access token without requiring user interaction again.
Add the jettyd MCP server to your Claude Desktop configuration file. On macOS the config is at ~/Library/Application Support/Claude/claude_desktop_config.json; on Windows at %APPDATA%\Claude\claude_desktop_config.json.
Option A — Hosted OAuth server (recommended for shared / multi-user setups):
{
"mcpServers": {
"jettyd": {
"type": "sse",
"url": "https://mcp.jettyd.com/mcp"
}
}
}
Claude Desktop will open a browser window for OAuth consent on first use and store the resulting token automatically.
Option B — Local npx server with API key (faster for single-user dev):
{
"mcpServers": {
"jettyd": {
"command": "npx",
"args": ["-y", "@jettyd/mcp"],
"env": {
"JETTYD_ACCESS_TOKEN": "at_YOUR_TOKEN_HERE",
"JETTYD_API_URL": "https://api.jettyd.com"
}
}
}
}
Claude.ai supports adding remote MCP servers directly in the browser. No local installation required.
https://mcp.jettyd.com/mcpcommands:write prominently signals that the integration can actuate devices.
Any client that supports the MCP specification can connect to the jettyd hosted server. Use the SSE endpoint with Bearer token authentication:
# Test connectivity with curl after completing OAuth
curl -H "Authorization: Bearer at_YOUR_TOKEN_HERE" \
-H "Accept: text/event-stream" \
https://mcp.jettyd.com/mcp
For clients that require a static URL with the token embedded (non-standard, use with care):
https://mcp.jettyd.com/mcp?access_token=at_YOUR_TOKEN_HERE
Authorization: Bearer header whenever possible.
The MCP server exposes eight tools. Each tool call is validated server-side; requests that exceed the token's granted scopes are rejected with 403 Forbidden.
| Tool | Required scope(s) | Description |
|---|---|---|
list_devices |
devices:read | Return all devices in the tenant fleet, including online/offline status and last-seen timestamp. |
get_device |
devices:read | Fetch full metadata for a single device by ID: firmware version, shadow state, assigned tags, and connection info. |
get_fleet_status |
devices:read | Aggregate fleet health summary: total devices, online count, devices with active alerts, and last-update time. |
read_sensor |
telemetry:read | Read the latest telemetry value for a specific sensor key on a device. Returns value, unit, and timestamp. |
get_history |
telemetry:read | Query historical telemetry for a device and sensor key over a time range. Returns a time-series array. |
send_command |
commands:write | Send an actuator command to a device (e.g., toggle relay, set setpoint). Command is delivered via MQTT and acknowledged. |
configure_device |
devices:write | Update device shadow configuration (reporting interval, thresholds, tags). Change is persisted and synced to the device. |
list_alerts |
rules:read | List all alert rules for the tenant, including trigger conditions, notification channels, and current fired/resolved state. |
list_devices — no arguments required:
{}
read_sensor — read temperature from a specific device:
{
"device_id": "dev_01hx3k9j2m4v5p6q7r8s",
"sensor_key": "temperature"
}
get_history — fetch the last 24 hours of humidity readings:
{
"device_id": "dev_01hx3k9j2m4v5p6q7r8s",
"sensor_key": "humidity",
"from": "2025-05-19T00:00:00Z",
"to": "2025-05-20T00:00:00Z",
"resolution": "1h"
}
send_command — turn on a relay:
{
"device_id": "dev_01hx3k9j2m4v5p6q7r8s",
"command": "set_relay",
"params": {
"channel": 1,
"state": "on"
}
}
configure_device — change reporting interval to 60 seconds:
{
"device_id": "dev_01hx3k9j2m4v5p6q7r8s",
"config": {
"telemetry_interval_s": 60
}
}
Request only the scopes your integration requires. Users see the scope list on the consent screen; narrower scope requests build more trust.
| Scope | Grants access to |
|---|---|
devices:read |
Read device metadata, online status, shadow state, firmware version, and tags. Required by list_devices, get_device, and get_fleet_status. |
devices:write |
Update device shadow configuration, reporting intervals, and tags. Required by configure_device. Does not grant ability to send actuator commands. |
telemetry:read |
Read current and historical sensor values. Required by read_sensor and get_history. Scoped to devices the user owns or has been shared access to. |
commands:write |
Send actuator commands to devices (relay, setpoint, motor, etc.). Required by send_command. All commands are logged with the token's subject for audit purposes. |
rules:read |
Read alert rules, trigger conditions, and notification channel configuration. Required by list_alerts. |
rules:write |
Create, update, and delete alert rules and notification channels. No MCP tool currently requires this scope — it is reserved for future rule-management tools. |
The MCP server returns standard HTTP status codes alongside a JSON error body on all failure paths.
Returned when the request cannot be authenticated. Two distinct causes:
Missing token — no Authorization header or access_token query parameter was provided:
{
"error": "unauthorized",
"error_description": "Bearer token required. Authenticate via OAuth before calling MCP tools."
}
Invalid or expired token — the token is malformed, has been revoked, or has expired:
{
"error": "invalid_token",
"error_description": "The access token is invalid or has expired. Obtain a new token via the OAuth flow."
}
Insufficient scope — the token is valid but does not include the scope required by the called tool (RFC 6750 §3.1):
{
"error": "insufficient_scope",
"error_description": "This tool requires the 'commands:write' scope. Re-authorize with the additional scope.",
"scope": "commands:write"
}
Returned when the token introspection endpoint is unreachable and the server cannot verify the token. The MCP server fails closed — it does not grant access on introspection failure:
{
"error": "introspection_unavailable",
"error_description": "Token introspection is temporarily unavailable. Please retry in a moment."
}
introspection_unavailable is returned, the server has rejected your request. This is intentional — jettyd never grants access on an ambiguous auth result. Retry after a short backoff; if the error persists, check the status page.