Skip to main content

15 · Next Steps

Core Standard Enhancements (Define in Spec for Consistency Across SDKs)

  • Finalize JSON-Schema for DriverMeta and capability flags: This ensures metadata is machine-validatable, promoting interoperability. Include schemas for Bindings, Tools, and ToolParameters to standardize serialization/deserialization.

  • Decide if sync/async drivers are needed: Specify optional async semantics in the standard (e.g., for I/O-heavy bridges); define when to use (e.g., streaming responses). SDKs handle language idioms (e.g., async/await in Python).

  • Clear signaling in process_llm_response for call occurrence: Resolved in v0.4 -- process_llm_response now returns a DriverResponse object that carries call_executed, call_failed, call_pending, executed_calls and retry_prompt. The driver itself is fully stateless and thread-safe. See Section 3.

  • Streaming and native tool-call support: Resolved in v0.5 -- process_llm_response accepts str | dict (supporting both raw text and structured native tool-call objects), and SupportsStreaming widens it to an LLMStreamBuffer so the type carries the streaming signal. The DriverResponse gained a messages field that provides pre-formatted conversation entries the client can append directly to its message history. This shifts message formatting responsibility from the client to the driver. See Section 3 and ADR-0001.

  • Exception handling and error reporting vs. return values in error cases: Define what clients expect (e.g., structured error objects with codes/messages for failures, raw results on success); SDKs adapt to language-specific exceptions/logging.

  • Should the output from process_llm_response() really be ANY or a structured object?: Resolved in v0.5 -- DriverResponse now separates executed_calls (the readable per-call report: name, arguments, result or error) from messages (pre-formatted conversation entries for the LLM). The client no longer receives the unchanged LLM input in result; instead, messages provides everything needed for the conversation history, and executed_calls everything needed for display.

  • Driver trust and verification: Running third-party drivers is running third-party code. MCS should provide mechanisms to make driver origin and integrity verifiable -- similar to how Linux distributions use package checksums and signing keys. Concrete steps under discussion:

    • Signed package checksums published alongside drivers on PyPI/npm
    • A machine-readable trust manifest in DriverMeta (author, source repository, signing key reference)
    • Integration with the mcs-pkg discovery index (see Section 12) to surface trust labels (Verified / Reference / Community), security notes, and compatibility matrices
    • Guidelines for client-side verification before dynamic loading (compare checksum before importing a driver discovered at runtime)

    The goal is not to build a PKI from scratch but to leverage existing ecosystem tools (PyPI trusted publishers, npm provenance, GitHub attestations) and expose the results in MCS-specific metadata. SDKs implement the verification mechanics; the spec defines what metadata must be present.

  • Driver versioning, registries, dynamic loading for true Plug & Play and max security: Outline guidelines in the spec (e.g., semver rules, registry discovery protocols, checksum requirements); include autoloading via names/checksums. SDKs implement loading mechanics (e.g., Pip integration).

  • Autostart recommendation (container labels, health endpoints): Expand with virtualization mandates (e.g., Docker params, sandboxing rules) and startup guidelines; define how drivers signal needs (e.g., via metadata). SDKs provide reference frameworks for launching.

  • PromptStrategy as Codec (implemented in Python SDK): The Python SDK now implements a PromptStrategy abstraction that acts as an interchangeable codec: it bundles tool description formatting, call-format examples, and response parsing in a single object. The default JsonPromptStrategy uses JSON; future strategies (XML for Claude, plain text for local models) implement the same interface with their own format. All prompt text -- system templates, retry messages, healing regex rules -- lives in external TOML files, enabling configuration without code changes. Combined with the new BaseDriver class, this eliminates ~400 lines of duplicated LLM-facing logic across drivers and orchestrators. See Section 10 for the conceptual design.

  • Explore a Prompt Provider for dynamic loading: Add informative section on future extensions for external prompt overrides/loading (e.g., via URLs/registries); define override semantics in init. Prototype in SDKs before standardizing.

  • Hybrid driver-orchestrator patterns: Resolved -- Sections 4 and 5 now cover the ToolDriver, hybrid driver, adapter pattern, and orchestrator composition in detail.

  • Multi-connection support within a single driver: Currently, a driver handles one binding with one connection. Multiple connections of the same binding (e.g., three REST APIs) require an Orchestrator. An alternative model: allow a single driver to hold multiple connections natively (e.g., accept a list of URLs). This would reduce the Orchestrator's role to pure multi-binding coordination (REST + Filesystem + ...), simplifying the architecture for the common case of "same protocol, multiple endpoints." Trade-offs: a multi-connection driver is more complex internally but removes an Orchestrator layer for the most frequent use case. This needs prototyping and community feedback before standardizing.

  • User Consent before tool execution: Resolved -- process_llm_response used to be atomic: it parsed the LLM output, detected a tool call and executed it in one step, leaving the client no seam to intervene. That seam now exists inside the driver as a tool middleware: PermissionMiddleware receives the pending call with its arguments, asks the client's consent handler, and either continues the chain or short-circuits it with a structured permission_denied result -- no execution, no exception. This gives the standalone driver the same gate the ToolDriver + Orchestrator split offered, without forcing that architecture and without splitting process_llm_response in two. Cost gating, audit trails and human-in-the-loop confirmation use the same seam; lifecycle observation uses HooksMiddleware alongside it. See ADR-0002.

  • Raw source spec access: Should drivers expose the original unprocessed source specification (e.g. the raw OpenAPI file before any reduction or transformation)? Currently get_function_description returns whatever the driver has prepared, which may be reduced, reformatted, or generated from Tool objects. A capability like "raw_spec" could signal that the original source is available, but no concrete use case has been identified yet. If needed, this could be modeled as a mixin or a method on DriverMeta.

Provider Tool-Call Formats -- resolved as ExtractionStrategy

LLM providers return tool calls in vastly different wire formats:

ProviderFormat
OpenAI Chat Completionstool_calls array with function.name + function.arguments (JSON string)
OpenAI Responsesoutput item list with function_call items
Anthropic Claudetool_use content block with name + input (object)
Google GeminifunctionCall with name + args (object)
Ollama / local modelsPlain-text JSON in the assistant content (no structured event)

Resolved -- each format is an ExtractionStrategy: a format expert that recognises one shape and answers, for that shape, whether a call is forming, whether one is finished, and which part is displayable text. The driver holds an ordered chain of them; the first that recognises a response owns it. Driver authors write no format-specific parsing at all.

One design point is worth recording, because the original proposal here got it backwards: the strategies deliberately do not normalise into a common {"tool": ..., "arguments": ...} dict. Each works in its provider's native shape, and a stream is reassembled back into exactly the message that provider's SDK returns for a non-streaming call. Normalising would mean the driver has to rebuild the native shape again when it writes the tool result into the conversation history -- a lossy round-trip for no gain. See ADR-0001 for the decision and the streaming format reference for the matrix.

Runner Concept (for discussion)

The OpenAI Agents SDK introduces a "Runner" that encapsulates the entire LLM-tool execution loop: LLM call, tool-call detection, execution, history management, and iteration -- all in a single run(input) -> output call. The developer never writes the loop; the Runner does everything.

With MCS v0.5 and the messages field on DriverResponse, a similar convenience layer becomes trivially implementable:

class MCSRunner {
driver: MCSDriver
llm_client: LLMClient

run(user_input: string) -> string {
messages = [system_prompt, user_message]
loop {
llm_output = llm_client.chat(messages)
response = driver.process_llm_response(llm_output)
if response.messages:
messages.extend(response.messages)
if not response.call_executed and not response.call_failed:
return llm_output // final answer
}
}
}

How it relates to existing MCS concepts:

ConceptDirectionWhat it aggregates
OrchestratorHorizontalMultiple ToolDrivers into one MCSDriver
RunnerVerticalMCSDriver + LLM + Loop into one run() call

A Runner is not an Orchestrator in the strict MCS sense. An Orchestrator aggregates tools across drivers and is transparent to the client (it is an MCSDriver). A Runner automates the client-side conversation loop and sits above the driver layer.

However, a Runner could be implemented as an MCSDriver -- its process_llm_response would internally run the full loop and return only the final answer. This makes it chainable but breaks the principle that the client controls the loop. For simple use cases (chatbots, scripts, batch processing), this trade-off is acceptable.

Recommendation: SDKs may offer a Runner as an optional convenience class (similar to BasicOrchestrator). Complex clients that need streaming, human-in-the-loop consent, or custom UI continue to use the manual loop. The Runner is opt-in, not a replacement for the core contract.

Defer to SDKs (Implementation Details, Not Core Spec)

  • Provide language-specific reference interfaces in python-sdk / typescript-sdk: Fully shift to SDKs as the standard is agnostic; use them to bootstrap other languages (e.g., Go, Rust) with code samples.
  • Checksums and autoloading in clients via names alone: Spec recommends security practices (e.g., mandatory verification); SDKs build autoload logic, handling platform-specific package management and resolution.
  • Exception handling nuances and type hints: Spec defines semantics (e.g., what to return/raise); SDKs adapt with language features (e.g., typed errors in TypeScript).
  • Collect community feedback: Ongoing for the standard (e.g., via GitHub issues); SDKs incorporate into best practices/examples, feeding back to spec revisions.