ExoSense MCP Server
Enables interaction with ExoSense IoT devices, sensors, groups, and assets via GraphQL, supporting device management and data queries through a Model Context Protocol interface.
README
ExoSense MCP Server (Python)
A Model Context Protocol (MCP) server for interacting with the ExoSense platform using GraphQL, implemented in Python.
Overview
Invoked by: python3 -m exosense_mcp.server
This MCP server provides tools to interact with ExoSense devices, sensors, groups, assets, and data through a standardized GraphQL interface. It features a modular architecture with session-based authentication and supports comprehensive IoT device management operations.
The server is implemented using aiohttp and follows the JSON-RPC 2.0 protocol for MCP communication. Tools are loaded dynamically from a configuration file, making it easy to add or modify tools without changing the core server code.
Features
- GraphQL API: All interactions with ExoSense use GraphQL for efficient data fetching
- Session-based Authentication: Token/OAuth authentication managed at the session level
- Modular Tool Architecture: 18 specialized tools for different ExoSense operations
- Dynamic Tool Loading: Tools are loaded from
config.ymlat startup - Type-Safe: Full Python type hints with Pydantic validation
- Pre-built Queries: Common GraphQL queries and mutations included
- Error Handling: Comprehensive error handling and validation
- HTTP Streaming: Built on aiohttp with HTTP streaming support
Installation
Use a virtual environment (recommended, and required on systems with externally-managed Python such as Debian/Ubuntu):
python3 -m venv .venv
source .venv/bin/activate # On Windows: .venv\Scripts\activate
pip install -r requirements.txt
Or install as a package:
pip install -e .
If you see an "externally-managed-environment" error from pip, create and use a venv as above; do not use --break-system-packages.
Configuration
The server can be configured using environment variables. You can set them in one of two ways:
-
Using a
.envfile (recommended for local development):- Create a
.envfile in thepython/directory - Add the following variables:
EXOSENSE_API_URL=https://api.exosense.com EXOSENSE_ORIGIN=https://exosense.com EXOSENSE_AUTH_TOKEN=your-token-here PORT=9000 HTTP_STREAMING=Private - The
.envfile is automatically loaded when the server starts
- Create a
-
Using system environment variables:
- Set environment variables directly in your shell or system configuration
Environment Variables
EXOSENSE_API_URL: GraphQL endpoint for ExoSense (default:https://api.exosense.com)EXOSENSE_ORIGIN: Host name of the solution being referenced (default:https://exosense.com)EXOSENSE_AUTH_TOKEN: Default authentication token (optional, required for private mode)PORT: Server port (default:9000)HTTP_STREAMING: Set to"Private"for backward compatibility (optional, no longer required for auth mode)DIODE_JOIN_ADDRESS: Diode join address for private networks (optional; omit for public publish). Set in.envper deployment to avoid config merge conflicts.DIODE_CLIENT: Omit orembedded(default) — run Diode in this process. Set tocontaineronly when a sidecar publishes Diode (see Diode below).LISTEN_HOST: Bind address for the HTTP MCP server (default:127.0.0.1).LOG_LEVEL: Python logging level (default:INFO). UseWARNINGin production to cut log I/O overhead.
Performance: The server reuses a single HTTP connection pool to ExoSense (no new TCP/TLS per GraphQL call). GET /health, POST /mcp, and /.well-known/mcp-authentication are logged at DEBUG only so health probes and MCP traffic do not spam INFO. JSON-RPC responses use compact JSON (no extra whitespace). initialize avoids copying all headers into a dict when authenticating. ToolContext and inspect are not recreated on every tool call. For lowest latency on probes, set LOG_LEVEL=WARNING.
Hybrid Authentication Mode
The server uses a hybrid authentication approach that supports both single-tenant and multi-tenant scenarios:
-
Client-Provided Auth (Multi-Tenant): If a client provides authentication headers:
- The server will use the client's credentials from headers
- Supports
x-automation-token+x-originORAuthorization: Automation <token>+origin - Each client session can have different credentials
- Perfect for SaaS platforms or multi-organization deployments
-
Environment Fallback (Single-Tenant Default): If no auth headers are provided:
- The server falls back to
EXOSENSE_ORIGINandEXOSENSE_AUTH_TOKENfrom.env - IT can set a default API key without exposing it to clients
- Clients can still override by providing their own headers
- Perfect for internal tools or when you want a default tenant
- The server falls back to
Priority: Headers (client-provided) → .env variables (IT-provided default)
This allows IT to configure a default API key in .env for convenience, while still allowing pipelines and clients to use their own credentials when needed.
Usage
Starting the Server
python3 -m exosense_mcp.server
.venv/bin/python -m exosense_mcp.server
Or using the installed script:
exosense-mcp-server
The server will:
- Test the connection to ExoSense (if credentials are provided)
- Load all tools from
config.yml - Start the HTTP server on the configured port (default: 9000; set
PORTin.envto override) - Listen for MCP requests at
http://localhost:9000/mcp(or whatever port you set)
Running as a systemd service
To run the server under systemd with the project’s virtual environment (good for VMs with many projects). The unit file is set up for projects under /root/projects/ (e.g. /root/projects/exosense-pymcp).
-
Install the project and create a venv in
/root/projects/exosense-pymcp:mkdir -p /root/projects/exosense-pymcp cp -r . /root/projects/exosense-pymcp/ # or clone/git pull cd /root/projects/exosense-pymcp python3 -m venv .venv .venv/bin/pip install -r requirements.txt -
Configure a
.envfile in that directory (see Configuration above). -
Install the unit file and start the service:
sudo cp contrib/exosense-pymcp.service /etc/systemd/system/ sudo systemctl daemon-reload sudo systemctl enable --now exosense-pymcp -
Check status and logs:
sudo systemctl status exosense-pymcp journalctl -u exosense-pymcp -f
Each project on the VM can have its own unit file; copy and edit the template to point at its path under /root/projects/.
Pipeline Authentication
For pipelines that want to use their own ExoSense credentials (instead of the server's .env default), see PIPELINE_AUTHENTICATION.md for detailed integration examples including:
- cURL examples
- Python/Node.js code samples
- GitHub Actions and GitLab CI configurations
- Security best practices
MCP Client Configuration
To connect an MCP client to the ExoSense MCP server, configure your client with:
{
"mcpServers": {
"exosense": {
"type": "http",
"url": "http://localhost:9000/mcp",
"headers": {
"x-automation-token": "<YOUR_EXOSENSE_API_TOKEN>",
"origin": "https://your-exosense-instance.com"
}
}
}
}
Note: If HTTP_STREAMING=Private is set in the server's .env file, headers are optional as the server will use .env authentication.
Diode (optional – publish MCP over the internet)
You can run the server’s embedded Diode client so the MCP endpoint is reachable over Diode without coordinating an external client, or run in container mode when another process in the stack publishes Diode for you.
- Install the Diode CLI from https://diode.io/download#cli so the
diodebinary is on your PATH (or in the project directory). Not required whenDIODE_CLIENT=containerif publish is handled elsewhere. - In
config.yml, setauto-start-diode: truewhen you want Diode publish enabled (embedded path) or to document intent with container Diode. .env: By default the server uses the embedded Diode client (no variable needed). SetDIODE_CLIENT=containeronly when this process must not start Diode and a container/sidecar handles publish. OptionalDIODE_CLIENT=embeddedis the same as omitting the variable.- Start the server as usual:
python3 -m exosense_mcp.server
With auto-start-diode: true and embedded mode (default), the server spawns the Diode CLI on startup, creates a local client database under diode_client/, and prints the public MCP URL (e.g. https://<client>.diode.link:9000/mcp). The Diode process is stopped automatically when the server exits.
To use a private Diode network instead of public publish, set DIODE_JOIN_ADDRESS in your .env file (or environment) to your join address. Keeping it in .env avoids merge conflicts when deploying to different environments.
Diode deploy MCP (Cursor)
To deploy packaged releases via Diode’s diode_deploy tool, add the Diode MCP server with the deploy preset. This repo includes .cursor/mcp.json, which passes deploy settings using Cursor’s env interpolation: values like ${env:DIODE_MCP_DEPLOY_TARGET} are filled from the operating-system environment of the Cursor process—not by reading .env automatically.
- Add
DIODE_MCP_DEPLOY_TARGETandDIODE_MCP_DEPLOY_UUIDto your.env(see.env.example) so they stay out of git and match your deployment. - Make sure those variables are exported into the environment Cursor inherits, for example:
- Launch Cursor from a terminal after:
set -a && source .env && set +a && cursor .(adjust for your shell), or - Use direnv so the project directory exports them when you
cdinto it, or - Define the same variables in your shell profile / OS user environment.
- Launch Cursor from a terminal after:
Semantics: DIODE_MCP_DEPLOY_TARGET is the diode://… URL for the deploy files listener. DIODE_MCP_DEPLOY_UUID is the deploy token; when set, the tool can rename tarballs to {UUID}.tar.gz and you can omit deploy_token in tool calls (see Diode CLI docs/mcp-spec.md).
Python container: main.py at the repo root imports and runs the MCP server (required by the deploy platform). deploy.manifest.json sets start_command to python3 main.py (avoid python3 -m … here — the ingest validator rejects those characters). The server still binds to 127.0.0.1 by default. requirements.txt is installed before start.
Do not put .env in the deploy tarball — it is too sensitive. Use scripts/diode_deploy_bundle.sh, which excludes .env. Anything you rely on locally from .env (e.g. EXOSENSE_API_URL, EXOSENSE_ORIGIN, EXOSENSE_AUTH_TOKEN, PORT, DIODE_CLIENT, LISTEN_HOST, DIODE_JOIN_ADDRESS, LOG_LEVEL, HTTP_STREAMING) must be configured on the Diode Deploy project so the running container receives the same variables as process environment. See .env.example for the full list of knobs; mirror those into the deploy UI or project env settings.
Adjust command in mcp.json if your Diode binary is not at /Users/hr/opt/diode/diode (e.g. "diode" when it is on PATH).
Development
Project Structure
python/
├── exosense_mcp/
│ ├── __init__.py
│ ├── server.py # Main MCP server (aiohttp-based)
│ ├── auth.py # Authentication handler
│ ├── exosense_client.py # GraphQL client for ExoSense
│ ├── utils.py # Utility functions
│ ├── tools/ # Modular tool architecture
│ │ ├── __init__.py
│ │ ├── types.py # Tool context and types
│ │ ├── _helpers.py # Helper functions for tools
│ │ └── *.py # Individual tool modules (18 tools)
│ ├── graphql/ # GraphQL query builders
│ │ ├── __init__.py
│ │ ├── assets.py
│ │ ├── groups.py
│ │ ├── devices_products.py
│ │ ├── insight_modules.py
│ │ ├── logs.py
│ │ ├── reports.py
│ │ ├── work_instructions.py
│ │ └── condition_policies.py
│ ├── types/ # Type definitions
│ │ ├── __init__.py
│ │ ├── auth.py # Authentication types
│ │ └── graphql.py # GraphQL types
│ ├── resources/ # MCP resources (documentation links)
│ │ └── index.py
│ └── prompts/ # MCP prompts
│ └── *.py
├── config.yml # Tool configuration (lists all tools)
├── pyproject.toml
├── requirements.txt
└── README.md
Tool Architecture
Each tool follows a consistent pattern:
- Pydantic Model: Defines the tool's parameters with validation
- Execute Function:
async def execute(arguments: Dict[str, Any], context: ToolContext) -> Dict[str, Any] - TOOL_METADATA: Dictionary containing tool name, description, and JSON schema
Example tool structure:
from typing import Dict, Any
from pydantic import BaseModel, Field, ValidationError
from .types import ToolContext
from ._helpers import pydantic_to_json_schema, format_success_response, format_error_response
class MyToolParams(BaseModel):
param1: str = Field(..., description="Description of param1")
param2: int = Field(10, ge=1, description="Description of param2")
async def execute(arguments: Dict[str, Any], context: ToolContext) -> Dict[str, Any]:
try:
# Validate arguments
try:
args = MyToolParams(**arguments)
except ValidationError as e:
return format_error_response(Exception(f"Invalid arguments: {e}"))
# Get authenticated client
auth = context.session.get("authorization") if context.session else None
import exosense_mcp.server as server_module
client = server_module.get_exosense_client(auth)
# Tool logic here
result = await client.query(...)
return format_success_response(result, "Success message")
except Exception as error:
return format_error_response(error)
# Tool metadata
schema = pydantic_to_json_schema(MyToolParams)
TOOL_METADATA = {
"name": "exosense-my-tool",
"description": "Description of what the tool does",
"inputSchema": schema
}
Adding New Tools
To add a new tool:
- Create a new tool file in
exosense_mcp/tools/(e.g.,my_new_tool.py) - Follow the tool architecture pattern above
- Add the tool to
config.yml:tools: - file: exosense_mcp/tools/my_new_tool.py name: exosense-my-new-tool - Restart the server - tools are loaded dynamically at startup
Available Tools
The server provides 18 specialized MCP tools for ExoSense operations:
exosense-current-user- Get current user informationexosense-get-root-group- Get root group informationexosense-get-groups- Query groups with filtering and include optionsexosense-get-products- Get all IoT Connectors (products)exosense-get-devices- Query devices with filtering and include optionsexosense-get-assets- Query assets with filtering and include options (returns summary only)exosense-get-asset-details- Get high-level statistics for a specific asset by ID or nameexosense-find-asset- Find assets by fuzzy name matching (e.g., "my battery" → "Battery Bank")exosense-get-asset-statuses- Get status information for specific assetsexosense-get-insight-modules- Get all available internal insight modulesexosense-get-insight-module- Get detailed information about a specific insight moduleexosense-get-asset-historical-data- Generate and retrieve historical data reportsexosense-get-work-instructions- Get work instructionsexosense-get-conditions- Get conditionsexosense-get-condition-comments- Get condition commentsexosense-get-event-logs- Get event logs
All tools use session-based authentication and provide comprehensive error handling.
Dependencies
pydantic>=2.0.0- Data validation and settings managementhttpx>=0.25.0- Async HTTP client for GraphQL requestspython-dotenv>=1.0.0- Environment variable managementaiohttp>=3.9.0- Async HTTP server for MCP protocolpyyaml>=6.0.0- YAML configuration file parsing
Testing
The server includes connection testing on startup. When you start the server, it will:
- Test the connection to ExoSense (if credentials are provided)
- Display connection status and configuration
- Load and register all tools from
config.yml - Start the HTTP server
Contributing
- Follow the existing modular tool architecture
- Create new tools in separate files under
exosense_mcp/tools/ - Use Pydantic models for parameter validation
- Add proper Python type hints for all new functionality
- Include comprehensive error handling using
format_error_response - Add tools to
config.ymlfor dynamic loading - Follow the tool pattern: Pydantic model +
execute()function +TOOL_METADATA
License
MIT
Recommended Servers
playwright-mcp
A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.
Magic Component Platform (MCP)
An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.
Audiense Insights MCP Server
Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.
VeyraX MCP
Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.
graphlit-mcp-server
The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.
Kagi MCP Server
An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.
E2B
Using MCP to run code via e2b.
Neon Database
MCP server for interacting with Neon Management API and databases
Exa Search
A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.
Qdrant Server
This repository is an example of how to create a MCP server for Qdrant, a vector search engine.