The bridge protocol
darkray-mcp and darkray_bridge both speak one small protocol to DarkRay, the bridge.
This page is for writing a client in another language; from Python, use
the Python client instead.
Find DarkRay: bridge.json
Section titled “Find DarkRay: bridge.json”While DarkRay runs, it keeps a discovery file at ~/.darkray/bridge.json (on Windows,
C:\Users\<you>\.darkray\bridge.json). With MCP on:
{"protocol": 1, "enabled": true, "pid": 4120, "app_version": "0.9.0", "port": 51234, "token": "<64 hex characters>"}With MCP off, there is no port or token:
{"protocol": 1, "enabled": false, "pid": 4120, "app_version": "0.9.0"}| Key | Holds |
|---|---|
protocol |
The highest bridge protocol this DarkRay speaks |
enabled |
Whether MCP is on |
port |
The TCP port on 127.0.0.1 |
token |
The secret the handshake must present; new each time MCP turns on |
pid |
DarkRay’s process id |
app_version |
DarkRay’s version |
Read it fresh before each connection. No file means DarkRay isn’t running; a file whose pid
is no longer running was left by a DarkRay that quit. DarkRay writes the file atomically
(a temporary file renamed over it), readable only by you on macOS and Linux, and removes it
when it quits. The DARKRAY_BRIDGE_FILE environment variable points the Python client and
darkray-mcp at another file.
Connect and frame messages
Section titled “Connect and frame messages”Open a TCP connection to 127.0.0.1 on port. Messages in both directions are
JSON-RPC 2.0, one per line: compact UTF-8 JSON
ending in \n, the same framing as MCP’s stdio transport. A message over 64 MB closes the
connection.
Say hello
Section titled “Say hello”The first request must be bridge.hello, with the token:
{"jsonrpc": "2.0", "id": 1, "method": "bridge.hello", "params": {"token": "…", "protocol": 1, "client": "my-tool 0.1"}}{"jsonrpc": "2.0", "id": 1, "result": {"protocol": 1, "app_version": "0.9.0"}}client names your program on the Console’s MCP card and in the In use tooltip. Any
other first request, or a wrong token, gets error -32001 and the connection closes. A
protocol that isn’t a whole number of 1 or more gets error -32600 and the connection closes.
Tokens are compared in constant time.
Send requests
Section titled “Send requests”Each MCP tool has a bridge method; the MCP tool reference lists it
under each tool (“Bridge method”), for example search_photos is photos.search. A
request’s params are the tool’s arguments, unchanged:
{"jsonrpc": "2.0", "id": 2, "method": "photos.search", "params": {"query": "rating:>=4", "limit": 2}}{"jsonrpc": "2.0", "id": 2, "result": {"total": 318, "photos": [{"id": 5120, "filename": "DSCF2210.RAF", "…": "…"}]}}- Several requests can be in flight on one connection. Replies may come out of order (a
preview finishes after a later quick request), so match them by
id. - DarkRay checks every request against its method’s schema (unknown and missing parameters, types, ranges, choices, list limits) before running it, and fills in defaults.
- Protocol 1 has no notifications. DarkRay ignores any you send, and sends none: follow jobs
by polling
jobs.list. - A preview’s
jpegis the JPEG file in base64. - Several clients may connect at once; their requests take turns.
Errors
Section titled “Errors”An error reply carries code, message (a sentence saying what to do next) and sometimes
data:
| Code | Name | When | data |
|---|---|---|---|
| -32700 | parse error | Not JSON | |
| -32600 | invalid request | Not JSON-RPC 2.0, or no protocol in common | |
| -32601 | unknown method | Not a method of this DarkRay | method |
| -32602 | invalid params | A missing, unknown or mistyped parameter | param |
| -32603 | internal error | Treated as failed by the Python client | |
| -32001 | unauthorized | No hello, or a wrong token | |
| -32002 | not found | A catalog, photo, collection, preset, look or job | kind, ids |
| -32003 | out of range | A value outside its range, or an unknown choice | field with min and max, or choices |
| -32004 | too many | Past a limit: 1,000 photos per call, 500 results per page | limit, param |
| -32005 | not allowed | Can’t run now or here | reason |
| -32006 | failed | The work started and failed | reason |
A call over many photos checks every id and value before changing anything; when it fails, nothing changed. The Python client turns each code into an exception (see Errors and timeouts).
Versions
Section titled “Versions”protocol is a whole number. Adding a method, an optional parameter or a result field
doesn’t change it; removing something or changing what it means does. DarkRay answers the
hello with the lower of its protocol and yours, and that is the protocol for the connection.
A method this DarkRay doesn’t have yet gets -32601.
Technical details: how darkray-mcp maps MCP onto the bridge
darkray-mcp lists the MCP tools with tools/list, from the same table as the Python client
(darkray_bridge/methods.py), and turns each tools/call into one bridge request, passing
arguments as params. A get_preview result becomes MCP image content (image/jpeg) plus
a text item with its size, region and preparation; every other result is one text item
holding the JSON. Bridge errors become tool results with isError: true holding the
message; only an unknown tool or a malformed request is a JSON-RPC error.
notifications/cancelled stops waiting for a call.
It accepts MCP’s legacy handshake (initialize, versions 2024-11-05, 2025-03-26, 2025-06-18
and 2025-11-25) and the 2026-07-28 version, which sends the protocol version and client
capabilities in each request’s _meta and may begin with server/discover. An unsupported
version gets error -32022 with the supported list.