ExoSense MCP Server

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.

Category
Visit Server

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.yml at 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:

  1. Using a .env file (recommended for local development):

    • Create a .env file in the python/ 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 .env file is automatically loaded when the server starts
  2. 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 .env per deployment to avoid config merge conflicts.
  • DIODE_CLIENT: Omit or embedded (default) — run Diode in this process. Set to container only 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). Use WARNING in 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:

  1. 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-origin OR Authorization: Automation <token> + origin
    • Each client session can have different credentials
    • Perfect for SaaS platforms or multi-organization deployments
  2. Environment Fallback (Single-Tenant Default): If no auth headers are provided:

    • The server falls back to EXOSENSE_ORIGIN and EXOSENSE_AUTH_TOKEN from .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

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:

  1. Test the connection to ExoSense (if credentials are provided)
  2. Load all tools from config.yml
  3. Start the HTTP server on the configured port (default: 9000; set PORT in .env to override)
  4. 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).

  1. 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
    
  2. Configure a .env file in that directory (see Configuration above).

  3. 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
    
  4. 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.

  1. Install the Diode CLI from https://diode.io/download#cli so the diode binary is on your PATH (or in the project directory). Not required when DIODE_CLIENT=container if publish is handled elsewhere.
  2. In config.yml, set auto-start-diode: true when you want Diode publish enabled (embedded path) or to document intent with container Diode.
  3. .env: By default the server uses the embedded Diode client (no variable needed). Set DIODE_CLIENT=container only when this process must not start Diode and a container/sidecar handles publish. Optional DIODE_CLIENT=embedded is the same as omitting the variable.
  4. 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.

  1. Add DIODE_MCP_DEPLOY_TARGET and DIODE_MCP_DEPLOY_UUID to your .env (see .env.example) so they stay out of git and match your deployment.
  2. 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 cd into it, or
    • Define the same variables in your shell profile / OS user environment.

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:

  1. Pydantic Model: Defines the tool's parameters with validation
  2. Execute Function: async def execute(arguments: Dict[str, Any], context: ToolContext) -> Dict[str, Any]
  3. 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:

  1. Create a new tool file in exosense_mcp/tools/ (e.g., my_new_tool.py)
  2. Follow the tool architecture pattern above
  3. Add the tool to config.yml:
    tools:
      - file: exosense_mcp/tools/my_new_tool.py
        name: exosense-my-new-tool
    
  4. Restart the server - tools are loaded dynamically at startup

Available Tools

The server provides 18 specialized MCP tools for ExoSense operations:

  1. exosense-current-user - Get current user information
  2. exosense-get-root-group - Get root group information
  3. exosense-get-groups - Query groups with filtering and include options
  4. exosense-get-products - Get all IoT Connectors (products)
  5. exosense-get-devices - Query devices with filtering and include options
  6. exosense-get-assets - Query assets with filtering and include options (returns summary only)
  7. exosense-get-asset-details - Get high-level statistics for a specific asset by ID or name
  8. exosense-find-asset - Find assets by fuzzy name matching (e.g., "my battery" → "Battery Bank")
  9. exosense-get-asset-statuses - Get status information for specific assets
  10. exosense-get-insight-modules - Get all available internal insight modules
  11. exosense-get-insight-module - Get detailed information about a specific insight module
  12. exosense-get-asset-historical-data - Generate and retrieve historical data reports
  13. exosense-get-work-instructions - Get work instructions
  14. exosense-get-conditions - Get conditions
  15. exosense-get-condition-comments - Get condition comments
  16. exosense-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 management
  • httpx>=0.25.0 - Async HTTP client for GraphQL requests
  • python-dotenv>=1.0.0 - Environment variable management
  • aiohttp>=3.9.0 - Async HTTP server for MCP protocol
  • pyyaml>=6.0.0 - YAML configuration file parsing

Testing

The server includes connection testing on startup. When you start the server, it will:

  1. Test the connection to ExoSense (if credentials are provided)
  2. Display connection status and configuration
  3. Load and register all tools from config.yml
  4. Start the HTTP server

Contributing

  1. Follow the existing modular tool architecture
  2. Create new tools in separate files under exosense_mcp/tools/
  3. Use Pydantic models for parameter validation
  4. Add proper Python type hints for all new functionality
  5. Include comprehensive error handling using format_error_response
  6. Add tools to config.yml for dynamic loading
  7. Follow the tool pattern: Pydantic model + execute() function + TOOL_METADATA

License

MIT

Recommended Servers

playwright-mcp

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.

Official
Featured
TypeScript
Magic Component Platform (MCP)

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.

Official
Featured
Local
TypeScript
Audiense Insights MCP Server

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.

Official
Featured
Local
TypeScript
VeyraX MCP

VeyraX MCP

Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.

Official
Featured
Local
graphlit-mcp-server

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.

Official
Featured
TypeScript
Kagi MCP Server

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.

Official
Featured
Python
E2B

E2B

Using MCP to run code via e2b.

Official
Featured
Neon Database

Neon Database

MCP server for interacting with Neon Management API and databases

Official
Featured
Exa Search

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.

Official
Featured
Qdrant Server

Qdrant Server

This repository is an example of how to create a MCP server for Qdrant, a vector search engine.

Official
Featured