Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .env.public
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ COUCHDB_USERNAME=admin
COUCHDB_PASSWORD=password
IOT_DBNAME=iot
WO_DBNAME=workorder
VIBRATION_DBNAME=vibration

# ── IBM WatsonX (plan-execute runner) ────────────────────────────────────────
WATSONX_APIKEY=
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,9 @@ benchmark/cods_track2/.env.local
CLAUDE.md
mcp/couchdb/sample_data/bulk_docs.json
.env

# Local MCP client config — .mcp.json.example is the committed template.
.mcp.json
mcp/servers/tsfm/artifacts/tsfm_models/
src/tmp/

Expand Down
35 changes: 35 additions & 0 deletions .mcp.json.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"_comment": "Copy to .mcp.json (gitignored) so any MCP client (e.g. MCP Inspector) can launch the six servers as stdio subprocesses. Expects `uv sync` to have installed the console scripts under .venv/bin/. Credentials are loaded from .env by python-dotenv when the servers start — keep secrets out of this file.",
"mcpServers": {
"utilities": {
"type": "stdio",
"command": ".venv/bin/utilities-mcp-server",
"args": []
},
"fmsr": {
"type": "stdio",
"command": ".venv/bin/fmsr-mcp-server",
"args": []
},
"iot": {
"type": "stdio",
"command": ".venv/bin/iot-mcp-server",
"args": []
},
"wo": {
"type": "stdio",
"command": ".venv/bin/wo-mcp-server",
"args": []
},
"vibration": {
"type": "stdio",
"command": ".venv/bin/vibration-mcp-server",
"args": []
},
"tsfm": {
"type": "stdio",
"command": ".venv/bin/tsfm-mcp-server",
"args": []
}
}
}
24 changes: 23 additions & 1 deletion docs/mcp-servers.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ Synthetic motor vibration data (`asset_id: Motor_01`, from `motor_01.json`) ship

**Path:** `src/servers/wo/main.py`
**Requires:** CouchDB (`COUCHDB_URL`, `COUCHDB_USERNAME`, `COUCHDB_PASSWORD`, `WO_DBNAME`)
**Data init:** Handled automatically by `docker compose -f src/couchdb/docker-compose.yaml up` (runs `src/couchdb/init_wo.py` inside the CouchDB container on every start — database is dropped and reloaded each time)
**Data init:** Handled automatically by `docker compose -f src/couchdb/docker-compose.yaml up` (runs `src/couchdb/init_wo.py` inside the CouchDB container on every start — database is dropped and reloaded each time). If a tool returns `{"error": "..._not_available"}`, the seeding step didn't run — restart the CouchDB container.

| Tool | Arguments | Description |
| ----------------------------- | ----------------------------------------------------- | ---------------------------------------------------------------------------------------- |
Expand Down Expand Up @@ -118,3 +118,25 @@ uv run vibration-mcp-server
```

They speak MCP over stdio, so they're idle until a client connects on stdin.

## Inspecting a server directly

To list the tools / resources / prompts a server exposes (and try a tool call) without writing client code:

**Option 1 — the MCP Inspector UI:**

```bash
npx @modelcontextprotocol/inspector uv run iot-mcp-server
```

**Option 2 — Claude Code or another MCP client:** copy `.mcp.json.example` at the repo root to `.mcp.json` and the client will launch all six servers as stdio subprocesses. The example file points to the `<name>-mcp-server` console scripts installed by `uv sync`.

**Option 3 — raw stdio JSON-RPC** (useful in scripts/CI):

```bash
printf '%s\n' \
'{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"probe","version":"0"}}}' \
'{"jsonrpc":"2.0","method":"notifications/initialized"}' \
'{"jsonrpc":"2.0","id":2,"method":"tools/list"}' \
| uv run iot-mcp-server
```
39 changes: 26 additions & 13 deletions src/servers/iot/main.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import os
import logging
from datetime import datetime
from functools import lru_cache
from typing import Any, Dict, List, Optional, Union
from mcp.server.fastmcp import FastMCP
from pydantic import BaseModel
Expand All @@ -24,20 +23,34 @@
COUCHDB_USERNAME = os.environ.get("COUCHDB_USERNAME")
COUCHDB_PASSWORD = os.environ.get("COUCHDB_PASSWORD")

# Initialize CouchDB
try:
db = couchdb3.Database(
COUCHDB_DBNAME,
url=COUCHDB_URL,
user=COUCHDB_USERNAME,
password=COUCHDB_PASSWORD,
# Initialize CouchDB. Guard before construction: couchdb3.Database raises inside
# __init__ when COUCHDB_URL is None, leaving a partially-constructed instance
# whose __del__ then raises AttributeError on `self.session.close()`. Skipping
# construction keeps the MCP server startable for tool discovery even when
# CouchDB env vars are unset.
db = None
if COUCHDB_URL and COUCHDB_DBNAME:
try:
db = couchdb3.Database(
COUCHDB_DBNAME,
url=COUCHDB_URL,
user=COUCHDB_USERNAME,
password=COUCHDB_PASSWORD,
)
logger.info(f"Connected to CouchDB: {COUCHDB_DBNAME}")
except Exception as e:
logger.error(f"Failed to connect to CouchDB: {e}")
db = None
else:
logger.warning(
"CouchDB env vars not set (COUCHDB_URL, IOT_DBNAME); "
"tool calls that need CouchDB will return an error."
)
logger.info(f"Connected to CouchDB: {COUCHDB_DBNAME}")
except Exception as e:
logger.error(f"Failed to connect to CouchDB: {e}")
db = None

mcp = FastMCP("iot", instructions="IoT sensor data: browse sites, assets, sensors, and query historical readings from CouchDB.")
mcp = FastMCP(
"iot",
instructions="IoT sensor data: browse sites, assets, sensors, and query historical readings from CouchDB.",
)

# Static site as per original requirement
SITES = ["MAIN"]
Expand Down