SmartPot Model Context Protocol (MCP) server: enables MCP-compatible AI assistants to inspect crops, read telemetry, request AI diagnostics, and—when authorized by the user—operate actuators, always on behalf of the connected account.
The server does not have its own account. Each user creates a connection token (spt_…) in the PWA under Profile › Connections with a defined scope:
| Scope | Capabilities |
|---|---|
| Read-only | Query crops, readings, diagnostics, overview dashboard, and notifications |
| Control | Additionally create, edit, and delete crops, trigger actuators, and mark alerts |
The MCP client sends that token on every request (Authorization: Bearer spt_…) and the server forwards it to SmartPot-API, which enforces isolation: it only sees that account's crops and applies its scope. No connection can change the password, manage other connections, configure notification channels, or view device credentials. Revoking a connection in the PWA takes effect within the 30-second positive/negative validation cache; the API stores only the SHA-256 digest of the token and records its last use. The MCP edge calls GET /api/v1/users/me with the connection token, caches the verdict by SHA-256 for 30 seconds (MCP_TOKEN_CACHE_SECONDS), and rejects fake or revoked tokens with 401 on every route, including tools/list.
The HTTP transport is stateless across requests: each request is served in isolation using the token it carries.
flowchart LR
subgraph clients["AI Assistants & MCP Clients"]
claude["Claude Code / Desktop"]
inspector["MCP Inspector"]
agent["Autonomous AI Agents"]
end
subgraph proxy["Reverse Proxy & Edge Transport"]
nginx["Nginx Host Gateway<br/>https://mcp.smartpot.app<br/>Port 8092 · TLS 443 Termination"]
end
subgraph mcp_server["SmartPot-MCP Container (FastMCP · Python 3.13)"]
direction TB
host_guard["TransportSecurityMiddleware<br/>Host & Origin Header Validation<br/>Mitigates HTTP 421 Misdirected Requests"]
token_auth["ConnectionTokenMiddleware<br/>Bearer Authentication with spt_... Tokens<br/>Cached validation with 5-minute TTL"]
fastmcp["FastMCP Protocol Server (Pydantic 2.14.3)<br/>Exposes 25 Tools, 5 Resources, 3 Prompts<br/>Scopes: READ and CONTROL"]
host_guard --> token_auth --> fastmcp
end
subgraph api_backend["Application Engine"]
api["SmartPot-API (Go 1.27.3)<br/>GET /api/v1/auth/tokens/verify<br/>Validates token hash & permissions"]
end
claude & inspector & agent -->|"HTTPS Stream / SSE / POST /mcp"| nginx
nginx --> host_guard
token_auth <-->|"Token Verification"| api
fastmcp -->|"REST Calls with Scoped Token"| api
classDef core fill:#067A52,stroke:#0B3D2B,color:#FFFFFF
classDef leaf fill:#DDF5EA,stroke:#067A52,color:#17261F
classDef water fill:#E3F2FB,stroke:#1F6FA0,color:#17261F
class claude,inspector,agent water
class nginx,api core
class host_guard,token_auth,fastmcp leaf
The image is public: ghcr.io/smartpottech/smartpot-mcp. It is included in the demo environment, which starts without cloning the code:
curl -fsSL https://raw.githubusercontent.com/SmartPotTech/.github/main/docker/demo/compose.yaml | docker compose -f - up -d --waitIn the local demo, the MCP server listens at http://localhost:8002/mcp (health check at http://localhost:8002/health). In production, it is deployed at https://mcp.smartpot.app/mcp (internal port 8092). Sign in to the PWA at http://localhost:5173 with demo@smartpot.app / SmartPot2026, create a connection in Profile › Connections, and copy the displayed configuration.
To run only this service pointing to an existing API:
docker run -d --name smartpot-mcp -p 127.0.0.1:8002:8002 --read-only --cap-drop ALL \
-e SMARTPOT_API_URL=http://host.docker.internal:8091 \
ghcr.io/smartpottech/smartpot-mcp:latest{
"mcpServers": {
"smartpot": {
"type": "http",
"url": "http://localhost:8002/mcp",
"headers": { "Authorization": "Bearer spt_…" }
}
}
}{
"mcpServers": {
"smartpot": {
"command": "uv",
"args": ["--directory", "<SmartPot-MCP directory>", "run", "python", "-m", "app.main"],
"env": { "SMARTPOT_API_URL": "http://localhost:8091", "SMARTPOT_API_TOKEN": "spt_…" }
}
}
}| Group | Tools |
|---|---|
| Account | get_account |
| Crops | list_crops, get_crop, create_crop, update_crop, delete_crop, set_crop_automation, get_crop_weather |
| Readings | get_latest_reading, get_readings_history, get_readings_summary, record_reading |
| Actuators | list_actuators, send_actuator_command, send_bulk_command, list_commands |
| AI | get_crop_insights, get_crop_profiles, get_learning_status |
| Overview | get_overview, get_metric_series, get_fleet |
| Alerts | list_notifications, get_unread_count, mark_notification_read |
Each tool declares whether it is read-only, idempotent, or destructive so the client prompts for confirmation before operating hardware or deleting data. When creating a physical crop, device credentials are never returned over MCP.
smartpot://crops/{crop_id},smartpot://crops/{crop_id}/latest, andsmartpot://crops/{crop_id}/insights.smartpot://profilesandsmartpot://health(public).
agronomic_diagnosis, emergency_triage, and fleet_daily_summary.
Requires uv.
uv sync --frozen
uv run python -m app.main # stdio (uses SMARTPOT_API_TOKEN)
TRANSPORT=streamable-http uv run python -m app.main # HTTP on port 8002
uv run ruff check . && uv run ruff format --check .
uv run pytestTests mock the API and verify that each request arrives with the caller's token, that two accounts never mix, that missing tokens or session JWTs never reach the API, that a fabricated well-formed spt_ token is rejected on tools/list, that the 30-second cache avoids repeated validation calls, that read-only connections cannot trigger actuators, that deceptive Host/Origin values are rejected, and that device credentials are never exposed.
Unprivileged user (1000:1000), read-only filesystem, all capabilities dropped, strict Host/Origin parsing (MCP_ALLOWED_HOSTS and MCP_ALLOWED_ORIGINS; lowercase, exact authority, ports 1..65535, IPv6), and an explicit CORS allowlist instead of *. /health stays public for probes.
The interactive diagrams follow one tool call from the Host/Origin check to SmartPot-API, in English and Spanish, with links to the exact source lines.
MIT.
Servidor de Model Context Protocol (MCP) de SmartPot: permite que un asistente de IA compatible con MCP consulte los cultivos, lea la telemetría, pida el diagnóstico de la IA y, si la persona lo autoriza, accione los actuadores, siempre a nombre de la cuenta de quien se conecta.
El servidor no tiene cuenta propia. Cada persona crea en la PWA, en Perfil › Conexiones, un token de conexión
(spt_…) con un alcance:
| Alcance | Qué permite |
|---|---|
| Solo lectura | Consultar cultivos, lecturas, diagnósticos, panel general y avisos |
| Control | Además, crear, editar y borrar cultivos, accionar actuadores y marcar avisos |
El cliente MCP envía ese token en cada petición (Authorization: Bearer spt_…) y el servidor lo reenvía a
SmartPot-API, que decide: solo ve los cultivos de esa cuenta y aplica su alcance. Ninguna conexión puede cambiar la
contraseña, administrar otras conexiones, configurar canales de aviso ni ver las credenciales de un dispositivo.
Revocar la conexión en la PWA surte efecto dentro de la caché de validación de 30 segundos (positiva o negativa); la API guarda solo el SHA-256 del token y registra su último uso. El borde MCP llama GET /api/v1/users/me con el token de conexión, cachea el veredicto por SHA-256 durante 30 segundos (MCP_TOKEN_CACHE_SECONDS) y rechaza tokens falsos o revocados con 401 en todas las rutas, incluido tools/list.
El transporte HTTP no guarda estado entre peticiones: cada una se atiende de forma aislada con el token que trae.
flowchart LR
subgraph clients["AI Assistants & MCP Clients"]
claude["Claude Code / Desktop"]
inspector["MCP Inspector"]
agent["Autonomous AI Agents"]
end
subgraph proxy["Reverse Proxy & Edge Transport"]
nginx["Nginx Host Gateway<br/>https://mcp.smartpot.app<br/>Port 8092 · TLS 443 Termination"]
end
subgraph mcp_server["SmartPot-MCP Container (FastMCP · Python 3.13)"]
direction TB
host_guard["TransportSecurityMiddleware<br/>Host & Origin Header Validation<br/>Mitigates HTTP 421 Misdirected Requests"]
token_auth["ConnectionTokenMiddleware<br/>Bearer Authentication with spt_... Tokens<br/>Cached validation with 5-minute TTL"]
fastmcp["FastMCP Protocol Server (Pydantic 2.14.3)<br/>Exposes 25 Tools, 5 Resources, 3 Prompts<br/>Scopes: READ and CONTROL"]
host_guard --> token_auth --> fastmcp
end
subgraph api_backend["Application Engine"]
api["SmartPot-API (Go 1.27.3)<br/>GET /api/v1/auth/tokens/verify<br/>Validates token hash & permissions"]
end
claude & inspector & agent -->|"HTTPS Stream / SSE / POST /mcp"| nginx
nginx --> host_guard
token_auth <-->|"Token Verification"| api
fastmcp -->|"REST Calls with Scoped Token"| api
classDef core fill:#067A52,stroke:#0B3D2B,color:#FFFFFF
classDef leaf fill:#DDF5EA,stroke:#067A52,color:#17261F
classDef water fill:#E3F2FB,stroke:#1F6FA0,color:#17261F
class claude,inspector,agent water
class nginx,api core
class host_guard,token_auth,fastmcp leaf
La imagen es pública: ghcr.io/smartpottech/smartpot-mcp. Viene incluida en la demo local y en producción, y se levanta sin
descargar el código:
curl -fsSL https://raw.githubusercontent.com/SmartPotTech/.github/main/docker/demo/compose.yaml | docker compose -f - up -d --waitEn la demo local, el servidor MCP queda en http://localhost:8002/mcp (salud en http://localhost:8002/health). En producción, se despliega en https://mcp.smartpot.app/mcp (puerto interno 8092). Ingresa a la PWA en
http://localhost:5173 con demo@smartpot.app / SmartPot2026, crea una conexión en Perfil › Conexiones y copia
la configuración que muestra.
Solo este servicio, apuntando a una API existente:
docker run -d --name smartpot-mcp -p 127.0.0.1:8002:8002 --read-only --cap-drop ALL \
-e SMARTPOT_API_URL=http://host.docker.internal:8091 \
ghcr.io/smartpottech/smartpot-mcp:latest{
"mcpServers": {
"smartpot": {
"type": "http",
"url": "http://localhost:8002/mcp",
"headers": { "Authorization": "Bearer spt_…" }
}
}
}{
"mcpServers": {
"smartpot": {
"command": "uv",
"args": ["--directory", "<carpeta de SmartPot-MCP>", "run", "python", "-m", "app.main"],
"env": { "SMARTPOT_API_URL": "http://localhost:8091", "SMARTPOT_API_TOKEN": "spt_…" }
}
}
}| Grupo | Herramientas |
|---|---|
| Cuenta | get_account |
| Cultivos | list_crops, get_crop, create_crop, update_crop, delete_crop, set_crop_automation, get_crop_weather |
| Lecturas | get_latest_reading, get_readings_history, get_readings_summary, record_reading |
| Actuadores | list_actuators, send_actuator_command, send_bulk_command, list_commands |
| IA | get_crop_insights, get_crop_profiles, get_learning_status |
| Panel general | get_overview, get_metric_series, get_fleet |
| Avisos | list_notifications, get_unread_count, mark_notification_read |
Cada herramienta declara si es de solo lectura, idempotente o destructiva, para que el cliente pida confirmación antes de mover hardware o borrar datos. Al crear un cultivo real, las credenciales del dispositivo nunca se devuelven por MCP.
smartpot://crops/{crop_id},smartpot://crops/{crop_id}/latestysmartpot://crops/{crop_id}/insights.smartpot://profilesysmartpot://health(públicos).
agronomic_diagnosis, emergency_triage y fleet_daily_summary.
Requiere uv.
uv sync --frozen
uv run python -m app.main # stdio (usa SMARTPOT_API_TOKEN)
TRANSPORT=streamable-http uv run python -m app.main # HTTP en el puerto 8002
uv run ruff check . && uv run ruff format --check .
uv run pytestLas pruebas simulan la API y comprueban que cada petición llega con el token de quien llama, que dos cuentas no se
mezclan, que sin token o con un JWT de sesión no se llega a la API, que un token spt_ bien formado pero falso se
rechaza en tools/list, que la caché de 30 segundos evita validaciones repetidas, que una conexión de solo lectura no
acciona, que valores engañosos de Host/Origin se rechazan y que
las credenciales del dispositivo no se exponen.
Usuario sin privilegios (1000:1000), sistema de archivos de solo lectura, sin capacidades, análisis estricto de
Host/Origin (MCP_ALLOWED_HOSTS y MCP_ALLOWED_ORIGINS: minúsculas, autoridad exacta, puertos 1..65535, IPv6)
y lista CORS explícita en vez de *. /health sigue siendo público para los probes.
Los diagramas interactivos siguen una llamada a una herramienta desde la validación de Host/Origin hasta SmartPot-API, en español e inglés, con enlaces a las líneas exactas del código.
MIT.