Architecture
Two-Layer Design
The SDK is organized into two layers:
┌─────────────────────────────────────────────┐
│ High-Level Session Helpers │
│ DecisionSession · ProposalSession · ... │
│ ┌─────────────────────────────────────┐ │
│ │ Projections (local state) │ │
│ │ DecisionProjection · TaskProjection│ │
│ └─────────────────────────────────────┘ │
├─────────────────────────────────────────────┤
│ Low-Level Transport │
│ MacpClient · MacpStream · ProtoRegistry │
├─────────────────────────────────────────────┤
│ gRPC / Protobuf │
│ @grpc/grpc-js · protobufjs │
└─────────────────────────────────────────────┘
│
▼
┌─────────────────┐
│ MACP Runtime │
│ (Rust/gRPC) │
└─────────────────┘Layer 1: MacpClient (Transport)
MacpClient provides typed wrappers around the gRPC MACPRuntimeService:
| RPC | Method | Returns |
|---|---|---|
Initialize | client.initialize() | InitializeResult |
Send | client.send(envelope) | Ack |
StreamSession | client.openStream() | MacpStream |
GetSession | client.getSession(id) | SessionMetadata |
CancelSession | client.cancelSession(id, reason) | Ack |
GetManifest | client.getManifest(agentId?) | AgentManifest |
ListModes | client.listModes() | ModeDescriptor[] |
ListRoots | client.listRoots() | Root[] |
ListExtModes | client.listExtModes() | ModeDescriptor[] |
RegisterExtMode | client.registerExtMode(desc) | { ok, error? } |
UnregisterExtMode | client.unregisterExtMode(mode) | { ok, error? } |
PromoteMode | client.promoteMode(mode, name?) | { ok, error?, mode? } |
WatchModeRegistry | via ModeRegistryWatcher | async iterator |
WatchRoots | via RootsWatcher | async iterator |
The client dynamically loads protobuf definitions from the proto/ directory at construction time.
Layer 2: Session Helpers
Each coordination mode has a session class that:
- Holds a
sessionId, version strings, and optional auth - Provides typed methods for each mode-specific message type
- Builds envelopes via
buildEnvelope()+ProtoRegistry.encodeKnownPayload() - Sends via
MacpClient.send()with automatic Ack checking - Applies accepted envelopes to a local projection
// Internal pattern (same for all session classes):
private async sendAndTrack(envelope: Envelope, auth?: AuthConfig): Promise<Ack> {
const ack = await this.client.send(envelope, { auth: auth ?? this.auth });
if (ack.ok) this.projection.applyEnvelope(envelope, this.client.protoRegistry);
return ack;
}Projections
Projections are pure state machines that track session state client-side. They receive envelopes and maintain typed collections:
Envelope → applyEnvelope() → updates internal state
│
├── Maps (proposals, tasks, handoffs, etc.)
├── Arrays (evaluations, updates, etc.)
├── Phase tracking
└── Query helpers (voteTotals, hasQuorum, etc.)Projections only track state for envelopes that were successfully accepted (Ack ok: true). Rejected messages are never applied.
Key property: Projections are deterministic. Given the same sequence of envelopes, they always produce the same state. This makes them easy to test without a running runtime.
ProtoRegistry
The ProtoRegistry is the bridge between TypeScript objects and protobuf wire format:
TypeScript Object → encodeKnownPayload(mode, messageType, value) → Buffer
Buffer → decodeKnownPayload(mode, messageType, payload) → TypeScript ObjectIt maintains two lookup maps:
MODE_MAP: Maps(mode, messageType)to protobuf type names for mode-specific messagesCORE_MAP: MapsmessageTypeto protobuf type names for core messages (SessionStart, Commitment, Signal, Progress)
For extension modes using JSON encoding (like ext.multi_round.v1), it falls back to JSON serialization.
Runtime Boundary
The runtime is the authoritative source of truth. The SDK provides convenience but does not enforce:
| Concern | Handled By |
|---|---|
| Session state (OPEN/RESOLVED/EXPIRED) | Runtime |
| Message ordering (acceptance order) | Runtime |
Message deduplication (message_id) | Runtime |
| TTL enforcement | Runtime |
| Mode-specific validation | Runtime |
| Participant authorization | Runtime |
| Envelope building + encoding | SDK |
| Local state projection | SDK |
| Typed method signatures | SDK |
If the runtime rejects a message (Ack ok: false), the SDK throws MacpAckError and does not apply the envelope to the projection.
File Organization
src/
├── index.ts # Barrel export
├── client.ts # MacpClient + MacpStream
├── auth.ts # Auth factory + gRPC metadata
├── constants.ts # MACP_VERSION, mode identifiers
├── envelope.ts # Envelope + payload builders
├── errors.ts # Error class hierarchy
├── proto-registry.ts # Protobuf encode/decode registry
├── types.ts # TypeScript interfaces
├── watchers.ts # ModeRegistryWatcher, RootsWatcher
├── decision.ts # DecisionSession
├── proposal.ts # ProposalSession
├── task.ts # TaskSession
├── handoff.ts # HandoffSession
├── quorum.ts # QuorumSession
├── projections.ts # Barrel re-export for projections/
└── projections/
├── decision.ts # DecisionProjection
├── proposal.ts # ProposalProjection
├── task.ts # TaskProjection
├── handoff.ts # HandoffProjection
└── quorum.ts # QuorumProjection