MCS Driver Contract – Version 0.6
This document defines the minimal contract all MCS-compatible drivers must implement.
See Minimal Driver Contract for detailed descriptions of each method, the stateless design rationale, and the DriverResponse semantics.
The syntax is language-agnostic and intended to guide implementations across all runtimes.
struct Binding {
capability: string // what the driver does: "csv", "rest", "filesystem", "pdf"
adapter: string // which backend: "localfs", "http", "smb", "s3" ("*" = any)
spec_format: string // "OpenAPI", "JSON-Schema", "Custom", etc.
}
struct DriverMeta {
id: string // UUID for unique identification
name: string // Human-readable name
version: string // Semantic version, e.g. "1.0.0"
bindings: array[Binding] // Subject matter + adapter combinations
target_llms: array[string] // "*" or specific models like "claude-4", None for ToolDrivers
capabilities: array[string] // optional contracts the driver *class* satisfies:
// "standalone", "orchestratable", "healthcheck",
// "native_tools", "streaming", "autostart"
}
struct ToolCallRecord {
name: string // which tool ran
arguments: dict // with which arguments
result: any = null // its output on success
error: string? = null // the reason on failure
tool_call_id: string? = null // provider id, when the LLM supplied one
}
struct DriverResponse {
call_executed: boolean = false // true once the driver executed the call(s)
call_failed: boolean = false // true when any processed call could not be parsed/executed
call_pending: boolean = false // true while a call is still forming in a stream (nothing executed)
retry_prompt: string? = null // driver-authored prompt hint for the client to append on retry
messages: array[Message]? = null // pre-formatted conversation entries the client appends to its history
executed_calls: array[ToolCallRecord]? = null // per-call report for client observability / UX
}
abstract class MCSDriver {
meta: DriverMeta
abstract get_function_description(model_name?: string) -> string
abstract get_driver_system_message(model_name?: string) -> string
abstract process_llm_response(llm_response: string | dict) -> DriverResponse
}
ToolDriver (for Orchestration)
(see Section 4)
struct ToolParameter {
name: string
description: string
required: boolean = false
schema?: dict // e.g. {"type": "string", "enum": [...]}
}
struct Tool {
name: string // machine identifier (OpenAPI operationId / MCP name)
title?: string = null // optional short human-readable label (OpenAPI summary / MCP title)
description?: string = null // detailed text forwarded to the LLM (may contain prompt-engineering instructions)
parameters: array[ToolParameter] = []
// Invariant: at least one of title or description must be non-empty.
// When only title is provided, description is auto-filled from title.
}
abstract class MCSToolDriver {
meta: DriverMeta // target_llms = null, as it's orchestrator-facing
abstract list_tools() -> array[Tool] // List available tools
abstract execute_tool(tool_name: string, arguments: dict) -> any // Execute and return result
}
Orchestrator Example
An Orchestrator is a MCS Driver itself, so it is transparent to the client.
class BasicOrchestrator extends MCSDriver {
drivers: array[MCSToolDriver]
constructor(drivers: array[MCSToolDriver], name?: string)
private collect_tools() -> array[Tool] // Aggregate from drivers
get_function_description(model_name?: string) -> string // Format tools
get_driver_system_message(model_name?: string) -> string // Build prompt
process_llm_response(llm_response: string | dict) -> DriverResponse // Parse, find tool, execute
}
For the client, it is irrelevant whether it interacts with a single / multiple MCS Driver(s) or Orchestrator(s), as both adhere to the same interface. This allows mixing and matching components arbitrarily without requiring adjustments to the client's logic.
It is also suggested to let the orchestrator implement the ToolDriver interface, so it can be used as a ToolDriver in a higher level orchestrator.
Mixins for Capabilities
abstract class SupportsHealthcheck {
abstract healthcheck() -> dict // e.g. {"status": "OK"}
}
abstract class SupportsAutostart {
abstract autostart(kwargs: dict) -> void // Launch container, etc.
}
struct NativeToolContext {
system_message: string // behavioral prompt (without inlined tool descriptions)
tools: array[dict] // tools in native LLM provider format (e.g. OpenAI function schema)
}
abstract class SupportsNativeTools {
abstract get_native_tool_context(model_name?: string) -> NativeToolContext
}
// Streaming: the client reassembles chunks in a StreamBuffer and hands the buffer
// itself to process_llm_response. The *type* is the signal -- there is no flag.
// See Section 8 and ADR-0001.
abstract class SupportsStreaming {
abstract process_llm_response(llm_response: string | dict | StreamBuffer) -> DriverResponse
abstract extraction_strategies() -> array[ExtractionStrategy]
new_stream_buffer(model_name?: string) -> StreamBuffer // convenience: seeded with the chain
}
// Cross-cutting concerns (auth, permission, hooks) intercept tool execution
// from *inside* the driver -- see Section 6.
abstract class SupportsToolMiddleware {
abstract add_middleware(middleware: ToolMiddleware) -> void
}
Drivers extend with mixins as needed, e.g.:
class ConcreteDriver extends MCSDriver, SupportsHealthcheck {
// Implement methods
}
Clients feature-detect before invoking: ask the object (isinstance(driver, SupportsHealthcheck)) when they hold it, or read meta.capabilities when they only have metadata — see Section 8. Mixins should follow a convention like prefixing with "Supports" followed by the capability name and providing a method of the same name, but deviations are allowed for flexibility. The standard itself makes no prescriptions; SDKs should define these to standardize common capabilities.