Skip to main content
Glama
waggle-sensor

Sage MCP Server

Official

Sage MCP Server

A Model Context Protocol server for the Sage Grande Testbed — cyberinfrastructure for AI@Edge. It exposes ~30 MCP tools, resources, and prompts for querying sensor data, submitting edge-computing jobs, discovering plugins, and browsing Sage documentation from any MCP-compatible client (Claude Desktop, Cursor, custom agents, etc.).

Built on FastMCP 2.10+ and the MCP SDK 1.12+; supports stdio, sse, and streamable-http transports.


Choose your setup

There are three ways to run this — pick the one that fits.

#

Path

Who it's for

You need

1

Hosted (mcp.sagecontinuum.org)

Everyone. Zero install.

An MCP client + a Sage token.

2

Local — stdio

IDE users who want to run the server themselves.

Python 3.11+ and this repo.

3

Local — HTTP

Devs, custom agents, curl testing.

Python 3.11+ or Docker.

All three expose the exact same tools. The only difference between hosted and local is how much data you can reach: without Sage credentials, every tool still runs but falls back to public data only. See Authentication & credentials for how to add your token.


Related MCP server: Jetson MCP Server

1. Hosted (mcp.sagecontinuum.org)

The server is already running at https://mcp.sagecontinuum.org/mcp. Point any MCP client at it — no install needed.

Cursor (~/.cursor/mcp.json) or Claude Desktop (~/Library/Application Support/Claude/claude_desktop_config.json on macOS):

{
  "mcpServers": {
    "sage": {
      "url": "https://mcp.sagecontinuum.org/mcp",
      "headers": {
        "Authorization": "Bearer YOUR_USERNAME:YOUR_ACCESS_TOKEN"
      }
    }
  }
}

Get YOUR_ACCESS_TOKEN from https://portal.sagecontinuum.org/account/access. The Bearer value must be in username:token form for anything beyond public data.

Restart your client, then ask natural questions:

"Show me temperature readings from node W023 in the last hour." "Find nodes in Chicago with recent camera images." "What's the highest temperature recorded today across all nodes?"


2. Local — stdio (for IDE clients)

Run the server as a subprocess of your IDE. Nothing binds to a network port. This is the most private mode — data and credentials never leave your machine.

Install:

git clone https://github.com/waggle-sensor/sage-mcp.git
cd sage-mcp
pip install -r requirements.txt
cp .env.example .env       # edit and add SAGE_USER / SAGE_PASS if you have them

Wire it into Cursor/Claude Desktop:

{
  "mcpServers": {
    "sage-local": {
      "command": "/absolute/path/to/sage-mcp/scripts/run-local-stdio.sh",
      "env": {
        "SAGE_USER": "your-sage-username",
        "SAGE_PASS": "your-sage-access-token"
      }
    }
  }
}

env: values here override anything in .env — useful when you want different tokens per IDE profile. Omit them entirely to run against public data only.


3. Local — HTTP (for development or non-IDE clients)

Bind the server to a local port. Useful for curl, custom agents, browser extensions, or anything that connects to a URL rather than spawning a subprocess.

Option A — bare Python

pip install -r requirements.txt
cp .env.example .env       # optional
./scripts/run-local-http.sh

Server is now on http://127.0.0.1:8000/mcp. Quick sanity check:

curl http://127.0.0.1:8000/health
# {"status":"ok"}

Option B — Docker

cp .env.example .env       # optional
docker compose up          # http://127.0.0.1:8000/mcp — laptop-safe default

The default docker compose up publishes only to 127.0.0.1. To expose it on all interfaces (cloud deployment), use the cloud profile:

docker compose --profile cloud up -d

For older MCP clients that only speak SSE:

docker compose --profile sse up   # http://127.0.0.1:8001/sse

Point a client at your local server

{
  "mcpServers": {
    "sage-local-http": {
      "url": "http://127.0.0.1:8000/mcp"
    }
  }
}

Authentication & credentials

The server never requires a Sage token — it just does less without one.

Scenario

What works

What doesn't

No token

Public sensor data, node listings, docs, plugin discovery, image proxy for public images

Protected datasets, /proxy/image for restricted content, job submission (sesctl still needs a real token)

username:token provided

Everything the token's Sage account has access to

Anything not shared with your account

How to provide a token, in order of preference:

  1. .env file (SAGE_USER=… + SAGE_PASS=…) — picked up by both ./scripts/run-local-http.sh and ./scripts/run-local-stdio.sh and by docker compose.

  2. Per-request HTTP header — for the hosted server or any HTTP transport:

    • Authorization: Bearer username:token (recommended)

    • Authorization: Basic <base64(username:token)>

    • X-SAGE-Token: username:token

  3. Query parameter?token=username:token as a last resort (shows up in server logs, so avoid for anything sensitive).

Get your token from https://portal.sagecontinuum.org/account/access. Access to protected data also requires signing the Sage Data Use Agreement.


Configuration reference

Every option is an environment variable — see .env.example for the full list with comments.

Variable

Default

Purpose

MCP_TRANSPORT

streamable-http

stdio / sse / streamable-http / http (alias)

MCP_HOST

0.0.0.0

Bind address (network transports only)

MCP_PORT

8000

Bind port (network transports only)

MCP_PATH

/mcp

HTTP path (streamable-http only)

LOG_LEVEL

INFO

Python logging level

SAGE_USER, SAGE_PASS

Basic auth for /proxy/image

SAGE_PROXY_BASE_URL

http://localhost:8000

Public URL prefix in generated proxy links

ADMIN_API_KEY

Required to hit /analytics/*

SAGE_MCP_SKIP_REGISTRY_REFRESH

Skip ECR call on startup (offline/CI)


HTTP endpoints (non-MCP)

Path

Auth

Purpose

GET /health

none

Liveness / readiness probe

GET /proxy/image?url=…

SAGE_USER/SAGE_PASS or per-request token

Authenticated image proxy to storage.sagecontinuum.org

GET /analytics/summary

admin key

Aggregate usage counts

GET /analytics/users

admin key

Per-user stats

GET /analytics/tools

admin key

Per-tool stats

GET /analytics/user/{id}

admin key

One user + their tool usage

GET /analytics/activity

admin key

Recent activity feed (?limit=)

Admin API key is accepted via X-Admin-API-Key header, Authorization: Bearer <key>, or ?api_key=<key>.


Tools, resources & prompts

29 tools, 2 resources, 7 prompts. Highlights:

  • Sensor data: get_node_all_data, get_node_iio_data, get_environmental_summary, list_available_nodes, search_measurements, get_node_temperature, get_temperature_summary

  • Node metadata: get_node_info, list_all_nodes, get_sensor_details

  • Jobs: submit_sage_job, submit_plugin_job, submit_multi_plugin_job, check_job_status, query_job_data, force_remove_job, suspend_job

  • Geography: get_nodes_by_location, get_measurement_stat_by_location

  • Plugins: find_plugins_for_task, get_plugin_data, query_plugin_data_nl, create_plugin

  • Images: get_cloud_images, get_image_data, get_image_proxy_url

  • Docs: ask_sage_docs, sage_faq, search_sage_docs

  • Resources: query://{plugin}, stats://temperature

  • Prompts: getting_started_guide, plugin_development_guide, data_analysis_guide, troubleshooting_guide, and three "suggest" prompts.

More docs in the docs/ folder — Getting Started, Authentication, Custom Functions, Docker Deployment.


Testing

pip install -r requirements.txt
python -m pytest tests/          # 61 tests, no network required

The test suite mocks sage_data_client and the ECR registry — nothing hits Sage's servers during CI. Legacy interactive smoke scripts (test_auth.py, test_server.py, test_image_proxy.py, ...) still work as manual tests; they're excluded from pytest collection via pytest.ini.


Extending

Add a tool by decorating a function in one of the modules under sage_mcp_server/tools/:

# sage_mcp_server/tools/sensor_tools.py

@mcp.tool
def my_custom_analysis(data_query: str, analysis_type: str = "basic") -> str:
    """Perform a custom analysis on Sage data."""
    df = data_service.query_data(...)
    return f"Analysis results: ..."

See Custom Functions Guide for the full workflow (fork → add → deploy).


Project layout

sage_mcp.py                          # 30-line entrypoint; exposes `mcp`
sage_mcp_server/                     # FastMCP factory + services
├── server.py                        # build_server(), main()
├── auth.py                          # HTTP request auth extraction
├── analytics_service.py             # in-memory analytics
├── data_service.py                  # sage-data-client wrapper
├── docs_helper.py                   # docs search + FAQ
├── job_service.py                   # sesctl wrapper
├── job_templates.py                 # pre-baked plugin job templates
├── models.py                        # pydantic v2 domain models
├── plugin_generator.py              # cookiecutter-style plugin scaffolding
├── plugin_metadata.py               # ECR plugin registry
├── plugin_query_service.py          # NL plugin query
├── plugin_registry.py               # measurement/plugin catalog
├── utils.py                         # time parsing, timestamp formatting
└── tools/                           # MCP tools split by concern
    ├── sensor_tools.py
    ├── job_tools.py
    ├── geo_tools.py
    ├── plugin_tools.py
    ├── docs_tools.py
    ├── prompts.py
    └── http_routes.py               # /health, /analytics/*, /proxy/image

License

MIT. See LICENSE.

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    F
    maintenance
    The Model Context Protocol (MCP) server provides a conversational interface for the exploration and analysis of RDF Turtle Knowledge Graph in Local File mode or SPARQL Endpoint.
    54
    MIT
  • F
    license
    Not graded
    quality
    D
    maintenance
    A Model Context Protocol server that enables monitoring and remote control of Nvidia Jetson boards using natural language commands over a network connection.
    11
    -
  • F
    license
    Not graded
    quality
    D
    maintenance
    A Model Context Protocol server that enables natural language querying of MySQL databases, supporting both HTTP-based communication and MCP stdio protocol.
    -