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 (like404
or500
) 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 */ ]
}
Field | Description |
---|---|
protocolVersion | The MSP version. Must be 2 . |
moduleVersion | The semantic version of your module (for display only). |
moduleName | A unique, human-readable name for your module. |
description | A 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:
Field | Description |
---|---|
name | Unique identifier for the action (e.g., createOrder ). Must match ^[a-zA-Z0-9_]+$ . |
description | A clear explanation of what the action does, for AI consumption. |
route | The API endpoint for this action (e.g., /action/createOrder ). |
riskLevel | The required approval workflow. Must be one of: "safe" , "machineApprovalRequired" , "humanApprovalRequired" , or "forbidden" . |
input | A JSON Schema object defining the parameters for this action. |
Data Endpoint Schema
Each object in the data
array is defined by this schema:
Field | Description |
---|---|
name | Unique identifier for the data source (e.g., dailySalesReport ). Must match ^[a-zA-Z0-9_]+$ . |
description | A clear explanation of the data provided. |
route | The API endpoint for retrieving the CSV data (e.g., /data/salesReport ). |
input | A 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:
Header | Example Value | Description |
---|---|---|
X-SynthGrid-Request-Id | 123e4567-... | A unique UUIDv4 for this specific HTTP request. Useful for logging and tracing. |
X-SynthGrid-Task-Id | a1b2c3d4-... | The unique ID of the overall task that triggered this action. |
X-SynthGrid-Synth-Id | f0e9d8c7-... | The unique ID of the Synth executing the action. |
X-SynthGrid-User-Emails | alice@co.com | Comma-separated list of user emails participating in the task. |
Custom Headers | my-secret-token | Your administrator can configure any number of custom headers (e.g., for an API key). |
Action Endpoints
- Request:
POST
to the definedroute
with a JSON body pre-validated by SynthGrid against yourinput
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 definedroute
with a pre-validated JSON body. - Success Response: An HTTP
200 OK
with aContent-Type
oftext/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 aContent-Type
ofapplication/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 anull
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. ThemoduleVersion
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”).