Ecosystem Bridges
Turn absorbs the existing ecosystem without asking you to migrate everything at once. Three native primitives give you a migration path from any developer context: REST APIs, GraphQL schemas, gRPC services, FHIR endpoints, existing MCP servers, and legacy CLI scripts.
Compile-Time Schema Adapters
The Problem They Solve
Every API ecosystem ships its own SDK. Integrating Stripe, GitHub, a hospital's FHIR endpoint, or a gRPC microservice traditionally requires installing a language-specific package, reading the documentation, writing wrapper code, and then exposing that wrapper to your LLM tool list. This creates SDK bloat, stale bindings, and hidden runtime costs.
Turn inverts the model. There is no SDK. The compiler reads the schema once at build time and generates native Turn closures. At runtime the LLM calls plain Turn functions with no awareness of the underlying protocol.
How It Works
A schema adapter is a Rust crate compiled to wasm32-unknown-unknown: a sandboxed WebAssembly module with no filesystem, no network, and no host access. At compile time the Turn compiler:
- Fetches the remote schema URL.
- Loads the adapter in a Wasmtime sandbox.
- Calls
expand_schema()and receives a JSON array of Turn AST nodes. - Inlines those nodes into the program as if they were hand-written Turn code.
Zero runtime overhead. The schema is resolved once. The LLM never pays for it.
Supported Adapters
| Keyword | Schema format | Generated output |
|---|---|---|
use schema::openapi("url") | OpenAPI 3.x JSON | One tool closure per operation |
use schema::graphql("url") | GraphQL Introspection JSON | StructDef per type; closures per Query and Mutation |
use schema::swagger("url") | Swagger v2 JSON | One tool closure per path and method |
use schema::grpc("url") | Protobuf .proto text | StructDef per message; closure per RPC |
use schema::fhir("url") | FHIR Conformance Statement JSON | StructDef per resource; CRUD closures |
use schema::soap("url") | SOAP WSDL XML | One tool closure per exposed SOAP action |
use schema::odata("url") | OData Metadata XML/JSON | StructDef per Entity; closure per Action/Function |
Example
// GraphQL: fetches introspection schema at compile time.
// At runtime, gh is a map of native Turn tools.
let gh = use schema::graphql("https://api.github.com/graphql");
// Stripe REST: every endpoint becomes a callable Turn function.
let stripe = use schema::swagger("https://api.stripe.com/openapi.json");
// The LLM calls these natively. No HTTP. No JSON parsing.
let invoice = infer StripeInvoice with [stripe.createInvoice] {
"Create an invoice for Alice for $49.99.";
};The mcp() Bridge
The Model Context Protocol (MCP) represents a large existing investment across thousands of servers and tools. Turn does not displace MCP. It absorbs it as a subprocess.
// Spawns a stdio JSON-RPC MCP server as an OS subprocess.
// Returns: McpServer { pid: 12345, status: "active" }
let legacy = mcp("stdio://npx @modelcontextprotocol/server-stripe");When the Turn VM executes mcp(), it validates the stdio:// URL scheme, extracts the binary and arguments, and spawns the process using std::process::Command. No shell is involved. The subprocess lifecycle is tied to the owning agent.
Migration path: mcp() is a bridge, not a destination. Once a team rewrites their MCP server logic in Turn, the mcp() call is replaced with use schema::openapi(...) and the subprocess overhead disappears entirely.
CLI Domestication (sys_exec)
LLMs composing bash -c strings or injecting shell metacharacters is a critical security failure mode. Turn eliminates it structurally.
// Each token is a separate, typed Str in a Turn map.
// No shell is invoked. Shell injection is structurally impossible.
let output = call("sys_exec", {
"bin": "python3",
"arg1": "process_data.py",
"arg2": user_provided_input // Str: a positional arg, not a shell string
});The sys_exec handler in the Turn ToolRegistry enforces:
- The argument must be a
Map. A raw string causes an immediate typed error. - Every map value must be a
Str. Numbers or nested structures cause an immediate typed error. - The OS call is
Command::new(binary).args(args)with no shell and no interpolation. - Returns
Str(stdout) on success, or a typed error string on non-zero exit.
Design Summary
| Mechanism | When to use | Runtime overhead |
|---|---|---|
use schema::* | Stable API with a public schema URL | Zero. Resolved at compile time. |
mcp("stdio://...") | Existing MCP server you operate | 1 OS subprocess per agent |
sys_exec | Legacy script or binary that does one thing | 1 child process per call |
Next Steps
- Design Mandate The philosophy and constraints behind every language decision.
- Implementation The compiler pipeline, bytecode instruction set, and VM internals.