Getting Started
This guide walks you from a fresh checkout to your first coordination session. By the end, you will have the runtime running locally and will have completed a full Decision Mode session through the gRPC API.
For protocol concepts like sessions, modes, and the two-plane model, see the protocol documentation.
Prerequisites
You need a Rust stable toolchain (1.75 or later) and the Protocol Buffers compiler.
# macOS
brew install protobuf
# Ubuntu / Debian
sudo apt-get install -y protobuf-compiler
# Verify
protoc --version
rustc --versionBuild and run
Clone the repository and build:
git clone https://github.com/multiagentcoordinationprotocol/runtime.git
cd runtime
cargo buildStarting a development server
For local development, disable TLS and skip configuring tokens. With no auth resolvers configured, the runtime falls back to dev-mode auth:
export MACP_ALLOW_INSECURE=1
cargo runThe server listens on 127.0.0.1:50051 and treats any Authorization: Bearer <value> header as authenticating the caller with sender identity <value>.
Starting a production server
In production, the runtime requires TLS and token-based authentication:
export MACP_TLS_CERT_PATH=/path/to/server.crt
export MACP_TLS_KEY_PATH=/path/to/server.key
export MACP_AUTH_TOKENS_FILE=/path/to/tokens.json
cargo runSee the Deployment Guide for the full environment variable reference.
Your first session
A coordination session has four steps: negotiate the protocol version, create a session, exchange mode-specific messages, and bind the terminal outcome with a commitment.
Step 1: Initialize
The client sends its supported protocol versions and the runtime selects one. This also exchanges capability information so the client knows which features are available.
-> InitializeRequest {
supported_protocol_versions: ["1.0"],
client_info: { name: "my-agent", version: "0.1.0" }
}
<- InitializeResponse {
selected_protocol_version: "1.0",
runtime_info: { name: "macp-runtime", version: "0.4.0" },
supported_modes: [
"macp.mode.decision.v1",
"macp.mode.proposal.v1",
"macp.mode.task.v1",
"macp.mode.handoff.v1",
"macp.mode.quorum.v1",
"ext.multi_round.v1"
]
}Step 2: Create a session
Send a SessionStart envelope to create a Decision Mode session with two participants. Every standards-track session requires four fields in the payload: participants, mode_version, configuration_version, and a positive ttl_ms.
-> Send(Envelope {
macp_version: "1.0",
mode: "macp.mode.decision.v1",
message_type: "SessionStart",
message_id: "msg-001",
session_id: "550e8400-e29b-41d4-a716-446655440000",
sender: "",
timestamp_unix_ms: 1712500000000,
payload: SessionStartPayload {
intent: "Decide whether to deploy v2.0",
participants: ["agent://analyst", "agent://reviewer"],
mode_version: "1.0.0",
configuration_version: "config.default",
policy_version: "",
ttl_ms: 60000
}
})
<- Ack { ok: true, session_state: OPEN }The sender field is left empty because the runtime overrides it with the authenticated identity. The empty policy_version resolves to the built-in policy.default, which imposes no governance constraints beyond the mode's own rules.
Session IDs must be either UUID v4/v7 in hyphenated lowercase form or base64url tokens of at least 22 characters. Short or human-readable IDs like "my-session" are rejected.
Step 3: Exchange messages
In Decision Mode, participants propose options, evaluate them, and vote. The session initiator and declared participants can all send proposals. Evaluations and votes reference proposals by ID.
-> Send(Envelope { message_type: "Proposal", payload: ProposalPayload {
proposal_id: "p1",
option: "deploy-v2",
rationale: "All tests passing, metrics stable"
}})
<- Ack { ok: true }
-> Send(Envelope { sender: "agent://analyst", message_type: "Vote", payload: VotePayload {
proposal_id: "p1",
vote: "APPROVE",
reason: "Risk assessment passed"
}})
<- Ack { ok: true }Step 4: Commit
The session initiator binds the terminal outcome. The commitment payload must echo the session's bound mode_version and configuration_version -- the runtime rejects mismatches.
-> Send(Envelope { message_type: "Commitment", payload: CommitmentPayload {
commitment_id: "c1",
action: "decision.selected",
authority_scope: "deployment",
reason: "Unanimous approval for deploy-v2",
mode_version: "1.0.0",
configuration_version: "config.default",
policy_version: "policy.default",
outcome_positive: true
}})
<- Ack { ok: true, session_state: RESOLVED }The session is now terminal. Any subsequent messages targeting it are rejected with SESSION_NOT_OPEN.
Authentication
Development mode
In development mode (no auth resolvers configured), clients send their sender identity as a bearer token:
metadata: { "authorization": "Bearer agent://my-agent" }The runtime accepts any bearer value as the sender identity. This fallback is only active while neither MACP_AUTH_TOKENS_* nor MACP_AUTH_ISSUER is set.
Production mode
Create a tokens.json file that maps bearer tokens to agent identities and capabilities:
[
{
"token": "secret-token-for-analyst",
"sender": "agent://analyst",
"allowed_modes": ["macp.mode.decision.v1", "macp.mode.task.v1"],
"can_start_sessions": true,
"max_open_sessions": 10,
"can_manage_mode_registry": false,
"is_observer": false
},
{
"token": "secret-token-for-reviewer",
"sender": "agent://reviewer",
"allowed_modes": [],
"can_start_sessions": true,
"can_manage_mode_registry": false
},
{
"token": "secret-token-for-auditor",
"sender": "agent://auditor",
"can_start_sessions": false,
"is_observer": true
}
]Setting allowed_modes to an empty array (or omitting it) grants access to all modes. The runtime derives the sender identity from the token, so agents cannot spoof their identity. is_observer allows passive-subscribe access to any session, even when the identity is not a declared participant -- useful for monitoring and audit agents. Clients authenticate by sending Authorization: Bearer <token> (or the alternate x-macp-token: <token> header) in the gRPC metadata.
JWT mode
The runtime accepts JWT bearer tokens when MACP_AUTH_ISSUER is set. Configure a JWKS source (MACP_AUTH_JWKS_JSON inline, or MACP_AUTH_JWKS_URL fetched + cached) and optionally override MACP_AUTH_AUDIENCE (default macp-runtime) and MACP_AUTH_JWKS_TTL_SECS (default 300). Supported signature algorithms are RS256, ES256, and HS256.
export MACP_AUTH_ISSUER=https://issuer.example.com
export MACP_AUTH_AUDIENCE=macp-runtime
export MACP_AUTH_JWKS_URL=https://issuer.example.com/.well-known/jwks.json
cargo runThe JWT's sub claim becomes the sender. An optional macp_scopes claim carries capability fields that mirror the static token config:
{
"sub": "agent://analyst",
"iss": "https://issuer.example.com",
"aud": "macp-runtime",
"exp": 1767225600,
"macp_scopes": {
"allowed_modes": ["macp.mode.decision.v1", "macp.mode.task.v1"],
"can_start_sessions": true,
"max_open_sessions": 10,
"can_manage_mode_registry": false,
"is_observer": false
}
}When macp_scopes is omitted, the identity defaults to permissive: any mode allowed, sessions can be started, and admin/observer flags off. The is_observer capability is required for passive-subscribe access to sessions where the caller is neither the initiator nor a declared participant.
Resolver order
If both JWT and static bearer tokens are configured, the runtime runs the JWT resolver first and then the static resolver. JWT-shaped tokens (containing dots) are only considered by the JWT resolver; opaque tokens are only considered by the static resolver. Dev-mode fallback activates only when neither MACP_AUTH_ISSUER nor MACP_AUTH_TOKENS_* is configured.
Running the example clients
The repository includes example clients in src/bin that demonstrate each mode. Start the development server in one terminal, then run any example in another:
# Terminal 1: start the server
export MACP_ALLOW_INSECURE=1 && cargo run
# Terminal 2: run examples
cargo run --bin client # Decision mode
cargo run --bin proposal_client # Proposal mode
cargo run --bin task_client # Task mode
cargo run --bin handoff_client # Handoff mode
cargo run --bin quorum_client # Quorum mode
cargo run --bin multi_round_client # Multi-round extension
cargo run --bin fuzz_client # Error path testingCommon errors
| Error | Cause | Fix |
|---|---|---|
UNAUTHENTICATED | No valid credential provided | In dev mode send Authorization: Bearer <sender>; in prod send a configured static bearer or a valid JWT |
INVALID_ENVELOPE | Missing required SessionStart fields | Ensure participants, mode_version, configuration_version, and ttl_ms > 0 are all present |
SESSION_NOT_OPEN | Session already resolved or expired | Use GetSession to check state; start a new session |
INVALID_SESSION_ID | Session ID format not accepted | Use UUID v4/v7 or base64url (22+ characters) |
FORBIDDEN | Sender not authorized for this message | Check the mode's authority rules; ensure the sender is in the participants list |
RATE_LIMITED | Too many requests per minute | Wait for the rate window to expire, or increase the limit via environment variables |
Next steps
- Examples for worked examples of every mode
- API Reference for the full gRPC surface
- Modes for runtime implementation details
- Deployment Guide for production setup