Skip to content

Protos — the group repo contract

A group repo is any git repo with this layout (contract §1; live example: ramen-demo-mcp-group):

mcp/
  requirements.txt                        pip requirements installed by the worker
  tools/<name>/<name>.py                  callable code
  tools/<name>/<name>.json                proto
  tools/<name>/utils/<name>_utils.py      optional; utils/ is importable
  resources/<name>/<name>.py + .json      same shape, plus uri and mime_type
  prompts/<name>/<name>.json              + SKILL.md (agent-skills template) + settings.json

Rules: folder name == json name == file stem; type must match the folder; type ∈ {tool, resource, prompt}; callable must exist in <name>.py. A package that breaks a rule is reported in the deploy job's errors list and the rest still load.

Tool

{
  "type": "tool",
  "name": "demo_calculator_tool",
  "description": "Add, subtract, multiply or divide two numbers.",
  "callable": "calculator_func",
  "input": {
    "var1": {"type": "number", "description": "first operand"},
    "var2": {"type": "number", "description": "second operand"},
    "func": {"enum": ["add", "subtract", "multiply", "divide"], "description": "operation"}
  },
  "output": {"type": "number"},
  "error": {"type": "string"}
}
# mcp/tools/demo_calculator_tool/demo_calculator_tool.py
from demo_calculator_tool_utils import apply   # utils/ is on sys.path

def calculator_func(var1, var2, func):
    return apply(func, var1, var2)
input becomes the MCP inputSchema (number | string | integer | boolean, enum → string enum; all params required, no extras). Return values that are not strings are JSON-encoded. Exceptions become isError: true with ExceptionType: message and no traceback.

Resource

Adds "uri": "demo://readme" and "mime_type": "text/markdown"; the callable returns the text.

Prompt

No callable. "skill": "SKILL.md", "settings": "settings.json"; input params become prompt arguments and {{param}} in SKILL.md is substituted. Front matter is stripped; settings.json is appended as a ## Settings block and exposed as _meta.settings in prompts/list.

Secrets in code

Any string argument containing {{$<group>.<NAME>}} is replaced by the runtime before the call from RAMEN_SECRET_<GROUP>__<NAME>; code can also call ramen_runtime.secrets.resolve(text). Values are redacted from errors and never logged. See Secrets.

Transport: JSON-RPC 2.0 over gRPC

How a call reaches a package is a separate contract (§11, proto/ramen/v1/mcp.proto): the MCP messages your tool sees are the standard ones, but since 0.3.1 they travel as the bytes body of one ramen.v1.Mcp/Call per request (a notification returns an empty body) rather than over HTTP.

service Mcp {
  rpc Call(JsonRpc) returns (JsonRpc);                 // one JSON-RPC 2.0 message in, its response out
  rpc Session(stream JsonRpc) returns (stream JsonRpc); // reserved; may return UNIMPLEMENTED
}
message JsonRpc { bytes body = 1; }                    // UTF-8 JSON-RPC 2.0 request or response
Metadata Meaning
authorization: Bearer rmk_… the group's MCP key; missing/wrong → gRPC UNAUTHENTICATED (empty key set = deny all)
ramen-group, ramen-zone routing at the load balancer; not an auth signal
(peer address / x-forwarded-for with RAMEN_TRUST_PROXY=1) must match RAMEN_ALLOWED_CIDRS → else PERMISSION_DENIED

Transport failures are gRPC statuses; protocol failures stay JSON-RPC errors in the body (-32601 for a blocked or unknown tool, -32602 for bad arguments, isError: true for an exception in your code). Messages are capped at 4 MiB; RAMEN_MAX_INFLIGHT overflows answer RESOURCE_EXHAUSTED. grpc.health.v1.Health/Check on the same port is SERVING once your packages loaded. Standard MCP clients do not see any of this: ramen-mcp-bridge turns the worker into an ordinary stdio server (local quickstart).

Validate before you push

tests/fixtures/proto.schema.json in the Ramen repo is the JSON Schema for <name>.json; the runtime's tests load tests/fixtures/broken_group to show every rejection case.