Module Service Protocol - Full Specification

Version History

  • *2 (2025-07-22) - Removed output schema requirement, added CSV endpoints. Backwards compatible with V1.
  • 1 (2024-10-16) - Initial release.

Overview

This document outlines the Protocol specification for creating a service that implements the Module Service Protocol, compatible with SynthGrid. By implementing this protocol, your internal services become powerful tools that Synths can use to perform real work.

Your service remains entirely your own IP and is completely independent of SynthGrid. MindFront never needs to view, introspect, or have access to your code or logic.

Protocol Philosophy

  • This is an RPC-style protocol over HTTP. HTTP is used only as a transport layer.
  • The outcome of an operation is determined exclusively by the status field within the JSON response body.
  • Your service should return an HTTP 200 OK for all protocol-level responses, including logical errors (failure, invalidInput). A non-200 status code (like 404 or 500) should only be used for transport-level errors where a JSON response body cannot be formed.

High-level MSP overview

flowchart LR
  subgraph YM["Your Module Service"]
    direction LR
      meta["/meta"]
      actions["/action/*"]
      data["/data/*"]
    end
      
  SynthGrid --Reads--> meta
  SynthGrid --Calls--> actions
  SynthGrid --Calls--> data

  actions --> YI["Your Data/Services"]
  data --> YI

Module Service Protocol Sequence

sequenceDiagram
    participant SG as SynthGrid
    participant CM as Custom Module

    Note over SG,CM: Module Registration
    SG->>CM: GET /meta
    CM-->>SG: Provide metadata
    
    Note over SG,CM: Action Execution (Optional)
    SG->>CM: POST /action/yourAction
    activate CM
    CM-->>SG: Return JSON result
    deactivate CM

    Note over SG,CM: Data Retrieval (Optional)
    SG->>CM: POST /data/yourReport
    activate CM
    CM-->>SG: Return CSV data or JSON error
    deactivate CM
Base URL

You may choose any base URL for your service. We highly encourage using a direct WireGuard connection for security. Alternatively, use HTTPS with a robust authentication mechanism via custom headers.


Meta Endpoint

A GET request to /meta must return a JSON object that declares the module’s capabilities. SynthGrid polls this endpoint approximately every 5 seconds to detect changes in real-time.

Response Schema
{
  "protocolVersion": 2,
  "moduleVersion": "x.y.z",
  "moduleName": "string",
  "description": "string",
  "actions": [ /* Array of Action Schema objects */ ],
  "data": [ /* Array of Data Endpoint Schema objects */ ]
}
FieldDescription
protocolVersionThe MSP version. Must be 2.
moduleVersionThe semantic version of your module (for display only).
moduleNameA unique, human-readable name for your module.
descriptionA brief explanation of the module’s purpose.
actions(Optional) An array of action objects. Can be omitted or be an empty array ([]).
data(Optional) An array of data endpoint objects. Can be omitted or be an empty array ([]).

Action Schema

Each object in the actions array is defined by this schema:

FieldDescription
nameUnique identifier for the action (e.g., createOrder). Must match ^[a-zA-Z0-9_]+$.
descriptionA clear explanation of what the action does, for AI consumption.
routeThe API endpoint for this action (e.g., /action/createOrder).
riskLevelThe required approval workflow. Must be one of: "safe", "machineApprovalRequired", "humanApprovalRequired", or "forbidden".
inputA JSON Schema object defining the parameters for this action.

Data Endpoint Schema

Each object in the data array is defined by this schema:

FieldDescription
nameUnique identifier for the data source (e.g., dailySalesReport). Must match ^[a-zA-Z0-9_]+$.
descriptionA clear explanation of the data provided.
routeThe API endpoint for retrieving the CSV data (e.g., /data/salesReport).
inputA JSON Schema object defining filtering parameters. Data endpoints do not have a riskLevel.

Risk Level Evaluation

This flowchart illustrates how SynthGrid handles the riskLevel for actions.

flowchart TB
    A[Start: Action Request] --> B{Check riskLevel}
    B -->|safe| C[Execute Action]
    B -->|machineApprovalRequired| D{Automated Check}
    B -->|humanApprovalRequired| E{Human Review}
    B -->|forbidden| F[Reject Action]
    D -->|Pass| C; D -->|Fail| F
    E -->|Approved| C; E -->|Rejected| F
    C --> G[Return Result]; F --> H[Return Error]
    G --> I[End]; H --> I[End]

Action & Data Endpoints

Request Headers from SynthGrid

SynthGrid sends the following headers with every POST request to your action and data endpoints:

HeaderExample ValueDescription
X-SynthGrid-Request-Id123e4567-...A unique UUIDv4 for this specific HTTP request. Useful for logging and tracing.
X-SynthGrid-Task-Ida1b2c3d4-...The unique ID of the overall task that triggered this action.
X-SynthGrid-Synth-Idf0e9d8c7-...The unique ID of the Synth executing the action.
X-SynthGrid-User-Emailsalice@co.comComma-separated list of user emails participating in the task.
Custom Headersmy-secret-tokenYour administrator can configure any number of custom headers (e.g., for an API key).

Action Endpoints

  • Request: POST to the defined route with a JSON body pre-validated by SynthGrid against your input schema.
  • Response: An HTTP 200 OK with a JSON body indicating the outcome.

Success Response:

{
  "status": "success",
  "data": { /* A non-null JSON object representing the result of the action */ }
}

Error Response:

{
  "status": "failure" | "invalidInput",
  "error": { "message": "A clear, user-facing explanation of the error." }
}
  • invalidInput: Use when the input is schema-valid but fails a business logic check (e.g., an ID does not exist).
  • failure: Use for all other operational errors (e.g., a downstream service is unavailable).

Data Endpoints

  • Request: POST to the defined route with a pre-validated JSON body.
  • Success Response: An HTTP 200 OK with a Content-Type of text/csv and a raw CSV body. The first row must be headers. If no records are found, return only the header row.
  • Failure Response: An HTTP 200 OK with a Content-Type of application/json and a standard JSON error body.

Complete Example

This runnable Python script implements a service with one action and one data endpoint.

#!/usr/bin/env python3
import json
from http.server import HTTPServer, BaseHTTPRequestHandler

METADATA = {
    "protocolVersion": 2,
    "moduleVersion": "1.0.1",
    "moduleName": "SimpleCRM",
    "description": "A basic CRM with actions for customers.",
    "actions": [{
        "name": "getCustomer", "description": "Retrieves details for a single customer by ID.",
        "route": "/action/getCustomer", "riskLevel": "safe",
        "input": {
            "type": "object", "properties": {"customerId": {"type": "string"}}, "required": ["customerId"]
        }
    }],
    "data": [{
        "name": "getAllCustomers", "description": "Exports a list of all customers.",
        "route": "/data/getAllCustomers", "input": {"type": "object", "properties": {}}
    }]
}

# Dummy in-memory data store
CUSTOMERS = { "CUST-001": {"name": "Alice Corp", "status": "active"}, "CUST-002": {"name": "Bob Inc", "status": "inactive"} }

class Handler(BaseHTTPRequestHandler):
    def send_json(self, data, status_code=200):
        self.send_response(status_code)
        self.send_header("Content-type", "application/json")
        self.end_headers()
        self.wfile.write(json.dumps(data).encode())

    def do_GET(self):
        if self.path == "/meta": self.send_json(METADATA)
        else: self.send_error(404)

    def do_POST(self):
        content_length = int(self.headers.get('Content-Length', 0))
        input_data = json.loads(self.rfile.read(content_length))

        if self.path == "/action/getCustomer":
            customer_id = input_data.get("customerId")
            if customer_id in CUSTOMERS:
                self.send_json({"status": "success", "data": {"id": customer_id, **CUSTOMERS[customer_id]}})
            else:
                self.send_json({"status": "invalidInput", "error": {"message": f"Customer ID '{customer_id}' not found."}})
        
        elif self.path == "/data/getAllCustomers":
            self.send_response(200)
            self.send_header("Content-type", "text/csv")
            self.end_headers()
            headers = "ID,Name,Status\n"
            rows = [f"{id},{data['name']},{data['status']}" for id, data in CUSTOMERS.items()]
            csv_data = headers + "\n".join(rows)
            self.wfile.write(csv_data.encode())
        
        else:
            self.send_error(404)

if __name__ == "__main__":
    HTTPServer(("", 8080), Handler).serve_forever()

Best Practices & FAQs

Data Handling

  • Return Meaningful Data: An action’s data payload should always contain the result of the operation (e.g., the full record that was updated). This provides confirmation and enables action chaining.
  • Handling null vs. Missing Keys: Your service can assign different semantic meaning to a key being absent versus a key being present with a null value (e.g., null could mean “explicitly unset this optional field”).
  • Payload Sizes: Keep JSON data payloads to a reasonable size. Use data endpoints for exporting large datasets (>100KB).

Security & Networking

  • Authentication: The recommended method is a direct WireGuard connection. For other setups, use custom headers to pass API keys or tokens; your administrator will configure these in SynthGrid.
  • Idempotency: While SynthGrid does not repeat requests, network retries can occur. Use the X-SynthGrid-Request-Id header in your logs to trace requests and in your application logic to de-duplicate operations if necessary.

Development Lifecycle

  • Environments: Manage dev/staging/prod by running separate instances of your MSP service and having your SynthGrid administrator point different Synths to the appropriate base URLs.
  • Updating: Simply deploy your new service code and restart it. SynthGrid will detect changes via the /meta poll within seconds. The moduleVersion is for display only and does not control updates.

Common Questions

  • How do I enrich the UI for parameters? Use standard JSON Schema keywords in your input schema. Try to avoid nesting, etc.
  • What if my action is slow? The user will see a “processing” state in the SynthGrid UI for the duration of the request (up to the 100s timeout). Design your actions to be as fast as possible. For long-running reports, consider an action that triggers the report and another that retrieves it by ID.
  • Can my service create tasks in SynthGrid? Not in MSP v2. This is a planned future capability.
  • Can my service receive events from SynthGrid? Indirectly. An administrator can configure a Synth to call one of your actions in response to a SynthGrid event (e.g., “when a task is completed, call the logTaskCompletion action”).