FastMCP IoT Sensor Server
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@FastMCP IoT Sensor Servershow me the latest sensor readings"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
FastMCP IoT Sensor Server
A portfolio artifact demonstrating hands-on MCP (Model Context Protocol) server
development using FastMCP. Simulates a network of IoT environmental sensors — the kind of
fleet you'd find monitoring a building or vehicle for emergency-response / edge-monitoring use cases — and exposes
their data and controls as MCP tools that any MCP client (Claude Desktop, fastmcp dev, or a custom client) can
query and act on.
For the layered architecture and a dependency diagram see docs/architecture.md;
for a deeper file-by-file walkthrough (useful for talking through this project in an interview)
see docs/project-walkthrough.md; for the MCP protocol/design
background behind the decisions here see docs/mcp-design-notes.md.
Why this exists
This is a v1 portfolio demo, not a production system: no real hardware, no message broker, everything runs in a single Python process. It's designed to show the shape of a real MCP server — tool design, resource vs. tool distinction, background async work alongside request/response handling, and persistence — without the operational overhead of an actual sensor deployment. It pairs naturally with a follow-up "real edge gateway" project (MQTT + hardware) as a next step.
The MCP server and the simulation are deliberately separate concerns. iot_sensors.server (and everything it
depends on — app.py, domain/, tools/, resources.py, prompts.py) is the thing another project would
actually embed or connect to; it never imports simulation code. iot_sensors.simulation exists purely to
generate the live, changing data that makes the server interesting to demo — it's an optional add-on, off by
default (see Simulating live data below), not part of the server's own runtime.
Related MCP server: MCP Weather Server — Demo
How it works
Two things run as separate OS processes and never talk to each other directly — they communicate only indirectly, through the shared SQLite file. This is deliberate: the MCP server doesn't need to know a simulator exists, and the simulator doesn't need to know anything about MCP.
graph LR
Client["MCP Client<br/>(Claude Desktop, Inspector,<br/>or a custom client)"]
subgraph SP["Server process — fastmcp run -m iot_sensors.server"]
Transport["stdio transport<br/>(JSON-RPC)"]
App["app.py<br/>(FastMCP instance + lifespan)"]
Surface["tools/ · resources.py · prompts.py"]
end
subgraph SIM["Simulator process — python -m iot_sensors.simulation.run"]
Simulator["simulator.py<br/>(asyncio loop, ~every 8s)"]
end
DB[("iot_sensors.db<br/>(SQLite — domain/db.py)")]
Client <-->|"tool calls, resource reads,<br/>prompt requests"| Transport
Transport <--> App
App --> Surface
Surface <-->|"query / write via domain.db"| DB
Simulator -->|"writes readings + alerts<br/>via domain.db"| DB
style DB fill:#f2e9d8,stroke:#a67c3a
style SP fill:#e8eef7,stroke:#3a5a8c
style SIM fill:#f7f0e8,stroke:#a67c3aHow a single tool call actually flows through the MCP layer, e.g. a client calling
get_active_alerts:
sequenceDiagram
participant C as MCP Client
participant T as stdio transport
participant S as FastMCP (app.py)
participant Tool as tools/alerts.py
participant DB as domain/db.py (SQLite)
C->>T: call_tool("get_active_alerts", {})
T->>S: dispatch to registered tool
S->>Tool: get_active_alerts()
Tool->>DB: SELECT * FROM alerts WHERE acknowledged = 0
DB-->>Tool: rows
Tool-->>S: list[AlertRecord] (Pydantic)
S-->>T: serialized structured result
T-->>C: JSON-RPC responseFastMCP handles the schema generation, dispatch, and serialization steps (S above) — the tool
function itself only ever deals with plain domain objects, never the wire format. See
docs/architecture.md for the static import/dependency diagram (which
files depend on which), as opposed to this runtime data-flow view.
Architecture
The package is organized in layers rather than one flat pile of files: a domain layer
(domain/) that describes sensors/readings/alerts and how they're persisted, with no knowledge of
MCP or the simulator; an MCP layer (app.py, server.py, tools/, resources.py,
prompts.py) that exposes the domain layer over the MCP protocol; and a simulation layer
(simulation/) that also depends on the domain layer, but only to generate demo data — it has no
relationship to the MCP layer. Dependencies point one direction, inward: both the MCP layer and
the simulation layer import from domain/, but domain/ imports from neither.
fastmcp.json Declarative FastMCP config: entrypoint, transport, deployment env
pyproject.toml Package metadata; src-layout package discovery
src/iot_sensors/
├── app.py Creates the shared FastMCP() instance + lifespan (DB init + seed; simulator only if opted in)
├── server.py Entrypoint: imports tool/resource/prompt modules for registration, then mcp.run()
├── domain/ sensors/readings/alerts + persistence — knows nothing about MCP or simulation
│ ├── db.py SQLite schema (stdlib sqlite3, no ORM) + query helpers; DB path resolved next to this file
│ ├── models.py Pydantic models for MCP tool/resource I/O, plus domain dataclasses
│ └── seed_data.py Fleet identity: 9 sensors across 3 zones + default alert thresholds
├── resources.py sensors://fleet resource
├── prompts.py diagnose_alert prompt
├── tools/
│ ├── sensors.py list_sensors, get_sensor_reading, get_sensor_history
│ ├── alerts.py set_alert_threshold, get_active_alerts, acknowledge_alert, bulk_acknowledge_alerts
│ └── zone_health.py summarize_zone_health (sampling)
└── simulation/ optional: not imported by server.py/app.py unless opted in
├── profiles.py per-sensor drift/anomaly parameters — simulation-only, domain layer never reads this
├── simulator.py asyncio background loop: readings with drift + occasional anomaly spikes
└── run.py standalone script: run this in a separate process for a live demo
tests/ pytest + pytest-asyncio tests that call tools/resources/prompts through an in-memory FastMCP Client
docs/ architecture diagram, project walkthrough, MCP design notesThe MCP-layer structure follows the standard FastMCP src-layout convention: a thin server.py
entrypoint that just imports the tool/resource/prompt modules for their @mcp.tool/
@mcp.resource/@mcp.prompt registration side effects, with the actual FastMCP() instance
factored into its own app.py so those modules can import it without circular imports. Tools are
grouped into tools/<domain-area>.py files rather than piling every tool into one file — the
natural next step if the tool count kept growing would be splitting each domain area into its own
mounted sub-server (see docs/mcp-design-notes.md).
Design rationale:
SQLite over an ORM — the schema is small and stable; raw
sqlite3keeps the data layer legible without an abstraction that isn't earning its keep.DB path resolved relative to the package, not the process's working directory —
domain/db.pyderives the default path from its own file location (Path(__file__).resolve().parent), so the database always lands in the same place no matter wherefastmcp runhappens to be launched from. Override withIOT_SENSORS_DB_PATHif needed.Simulation kept out of the server's own runtime — the server's lifespan only initializes the schema and seeds the fleet; it starts the background simulator only if
IOT_SENSORS_ENABLE_SIMULATORis set. A project embeddingiot_sensors.servergets a clean server with zero simulation code in its default startup path; the simulator is there to demo the server working, not to be a dependency of using it. This split runs down to the data too:domain/seed_data.py(core, always loaded) only holds a sensor's identity — id/name/type/zone/unit — and its default alert thresholds;simulation/profiles.py(simulation-only) holds the drift/anomaly parameters that only the simulator needs. Nothing outsideiot_sensors.simulationever readsprofiles.py.Tools vs. resources — the eight tools are the actionable surface (query specific sensors, configure thresholds, acknowledge alerts, summarize zone health). The
sensors://fleetresource demonstrates the complementary read-only, subscribable surface MCP offers for "give me the current state of the world" access.
Sensor Fleet
Zone | Sensors |
Zone A | Temperature, Humidity, CO2 |
Zone B | Temperature, CO2, Smoke/particulate index |
Zone C | Temperature, Humidity, Smoke/particulate index |
When the simulator is running (see below), each sensor emits a new reading every ~8 seconds with small randomized drift, plus a small chance per tick of an injected anomaly spike (useful for demoing the alerting path without waiting for a real incident).
Setup
Requires Python 3.11+.
python3 -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"Quick start
Run the tests (fast, deterministic, doesn't need the simulator):
pytest tests/ -vStart the server in one terminal:
FASTMCP_SHOW_SERVER_BANNER=false fastmcp run -m iot_sensors.serverStart the simulator in a second terminal, so there's live data to query — the server alone only seeds the static fleet, it doesn't generate readings itself (see Simulating live data):
python -m iot_sensors.simulation.runInteract with it via the Inspector (instead of step 2 — the Inspector launches the server for you), Claude Desktop (see below), or any other MCP client.
Run the server standalone (stdio transport)
fastmcp run # picks up fastmcp.json in the current directoryor, without the config file:
FASTMCP_SHOW_SERVER_BANNER=false fastmcp run -m iot_sensors.server(iot_sensors/server.py uses relative imports, so it must be run as an installed module — -m iot_sensors.server — rather than as a bare file path.)
Interactive inspector
FASTMCP_SHOW_SERVER_BANNER=false npx @modelcontextprotocol/inspector@latest .venv/bin/fastmcp run -m iot_sensors.serverUse Inspector v2+ (@latest) — v1 doesn't implement a UI for elicitation requests, which
bulk_acknowledge_alerts relies on. Suppressing the startup banner matters for stdio transport:
any extra text on stdout besides JSON-RPC corrupts the message framing and the Inspector reports
the connection as failed.
Simulating live data
The server on its own only initializes the schema and seeds the static fleet — every sensor's last_reading stays
None and there's nothing to query in get_active_alerts() until something writes readings. To see it behave like
a live system, either:
Run the simulator in a separate process (recommended — keeps the server itself simulation-free):
python -m iot_sensors.simulation.runOr opt the server's own lifespan into starting it:
IOT_SENSORS_ENABLE_SIMULATOR=true FASTMCP_SHOW_SERVER_BANNER=false fastmcp run -m iot_sensors.serverConnect from Claude Desktop
Add to your Claude Desktop MCP config (claude_desktop_config.json):
{
"mcpServers": {
"iot-sensors": {
"command": "/absolute/path/to/.venv/bin/fastmcp",
"args": ["run", "-m", "iot_sensors.server"],
"env": { "FASTMCP_SHOW_SERVER_BANNER": "false" }
}
}
}Restart Claude Desktop and the iot-sensors server should appear in the MCP tool list.
Run tests
pytest tests/ -vMCP Tools
Tool | Description |
| All sensors with current status (id, name, type, zone, last value, last updated) |
| Latest reading for one sensor |
| Time-series of past readings |
| Configure alert bounds for a sensor |
| Unacknowledged alerts, most recent first |
| Mark an alert as handled |
| Acknowledge multiple alerts at once; elicits the user for scope (all/critical-only/none) when there's more than one active alert |
| Gathers a zone's sensors/alerts, then samples the client's LLM for a natural-language health summary |
MCP Resources
Resource | Description |
| Live listing of all sensors and their current values |
MCP Prompts
Prompt | Description |
| Structures an alert investigation: pulls the alert and its sensor's past-hour history into a template asking the model to assess cause and recommend next steps |
Example prompts (for Claude Desktop)
"List all sensors and their current status."
"What's the CO2 level in Zone B right now?"
"Show me the temperature trend for sensor 1 over the last hour."
"Are there any active alerts? Summarize their severity."
"Set the smoke alert threshold for sensor 6 to 0–40, then check for new alerts in a minute."
Out of scope for v1
Real MQTT/hardware integration — natural next step, ties into a separate edge-gateway project
Authentication / multi-tenant access
Web dashboard UI
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Servers
Alicense-qualityBmaintenanceMCP server providing tools for AI-agents to interact with Enapter EMS.Last updatedApache 2.0- Alicense-qualityDmaintenanceDemo MCP server that provides weather data for cities, with tools to get weather and list available cities.Last updatedMIT
- AlicenseAqualityBmaintenanceMCP server for Smart Compost platform that lets LLM agents list composting processes/devices and pull measurements as read-only tools.Last updated633Apache 2.0
- Flicense-qualityBmaintenanceMCP server that bridges MQTT sensor data to MCP, allowing clients to query cached sensor readings and send commands via tools like list_topics, read_topic, and publish.Last updated
Related MCP Connectors
Hosted MCP endpoint with realistic fake data for prototyping agents. 12 tools, no setup.
MCP server exposing the Backtest360 engine API as tools for AI agents.
MCP server for Mireye Earth — federal-source-cited geospatial data for any MCP-aware agent.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/csaad-ai-engineer/fastmcp_iot_sensors'
If you have feedback or need assistance with the MCP directory API, please join our Discord server