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.

F
license - not found
-
quality - not tested
C
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Latest Blog Posts

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/waggle-sensor/sage-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server