Free unsigned Agent Guild preflight immediately before factory-owned native Rig MCP tool execution. The guard follows the actual captured MCP client, session and wire tool name. It supports normal and streaming AgentRunner execution while preserving native rich results, errors and invocation metadata.
This standalone source library/example pins Rig/core/agent 0.42.0, RMCP 2.2.0 and Rust 1.94.0. It does not use the unreleased rig-rmcp package. It is not published on crates.io; use this reviewed repository/source archive. The local demo needs no model service, Guild connection, account or payment.
With the pinned Rust toolchain available:
cargo test --locked --all-targets --features fixtures
cargo run --locked --features fixtures --example offlineThe example uses Rig's deterministic native CompletionModel, an official RMCP StreamableHttpService/LocalSessionManager server and a controlled unsigned preflight response, all on ephemeral loopback ports. Expected output confirms one preflight and one native MCP tools/call. This is a real native integration test, not a complete MCP conformance suite. Once dependencies are cached, both commands also run with --offline.
Once the v0.1.0 tag is available in the public repository, add the pinned Git dependency:
[dependencies]
rig-agentguild = { git = "https://github.com/AgentTanuki/rig-agentguild", tag = "v0.1.0" }Before that release tag is available, or when using the reviewed source archive, place your application next to this source checkout and use a path dependency:
[dependencies]
rig-agentguild = { path = "../rig-agentguild" }Keep the reviewed tag or source revision/archive pinned in deployment. No registry-install claim is made. Public source: AgentTanuki/rig-agentguild.
Call the production constructor with your canonical public HTTPS MCP endpoint, an optional wire-name-to-model-alias map, local policy and explicit active-probe acknowledgement:
use rig_agentguild::{ActiveProbeConsent, GuardedMcpSession, Options, Policy};
use std::collections::BTreeMap;
// Inside your async application; replace with your public MCP endpoint.
let mut session = GuardedMcpSession::connect(
"https://your-public-endpoint.example/mcp",
BTreeMap::new(),
Policy::default(),
Options::default(),
ActiveProbeConsent::Acknowledged,
).await?;
let tools = session.tools();
// Supply `tools` to your existing AgentBuilder.dynamic_tools(tools).
// Keep `session` alive until its agent runs have finished.
// Then: session.close().await?;The constructor performs MCP initialization and initial tool discovery before the preflight guard. Acknowledgement authorizes disclosure of the endpoint URL and Guild's active probe; it does not make this initial MCP discovery guarded. Only subsequent calls through the returned tools are guarded.
Aliases map discovered wire tool names to model names. An alias never becomes the wire name. Unknown mapping keys, duplicate discovered names and duplicate aliases within one session are refused. Up to 64 discovered tools are accepted after discovery. The raw MCP client and private native tool registry are never exposed. Caller-created raw tools, another tool registry, direct MCP access and tools installed by other code are outside this guard. Cross-session alias collisions retain Rig's native last-registration-wins behavior; the selected closure still checks its own endpoint. Use distinct aliases to avoid surprising model-facing collisions.
The default policy requires endpoint_reachable and protocol_handshake to be proven, rejects caution, and refuses unavailable or invalid responses. Unknowns among the other four supported checks are retained and do not independently block under this default. This is not a policy that requires all six checks to be known or proven.
An otherwise usable MCP endpoint can receive delegate_with_caution because it lacks a resolvable or signed A2A card. A caller willing to accept that specific limitation can explicitly permit caution while retaining proven reachability and handshake:
use rig_agentguild::{Check, Policy};
let policy = Policy::new(
[Check::EndpointReachable, Check::ProtocolHandshake],
false, // required unknowns remain refused
true, // explicitly permit nonblocking caution
);Policy::new accepts typed checks and real booleans. do_not_delegate is always refused, including with an empty required set, tolerated unknowns and permitted caution. A required failed check is never tolerated. Choosing to tolerate a required unknown leaves its status Unknown; it does not invent evidence. Missing required contract fields cannot be tolerated.
The parser requires exact target equality, all six supported checks, unique check names and object keys, valid status/verdict values, and exact consistency among checks, failed, unknowns, scored and the computed verdict. It rejects malformed UTF-8, trailing JSON, deep nesting and contradictory data. New valid check identifiers may be accepted for consistency checking, but only their total count is retained. The public observation contains the six known statuses, verdict, target/origin and local request/receipt times. It intentionally excludes remote prose, full raw JSON and unrecognized identifiers.
Every completed preflight decision is available through session.decisions() (last 64 in completion order), and the per-call Decision is added to native result context alongside untouched MCP metadata. Those decisions describe preflight policy; they do not assert the later MCP call succeeded. Cancelled unfinished attempts and local closed-session refusals have no completed preflight observation. Model-facing preflight summaries contain only locally defined reasons. Native MCP result text and rich content retain their native semantics and should be treated as untrusted tool output.
- Production uses only
GET https://agent-guild-5d5r.onrender.com/preflight?url=.... The endpoint URL is disclosed and Guild actively probes it. This free response is unsigned and is not a safety guarantee, identity/ownership proof, passport verification, reputation purchase or settlement decision. No/check, deep-preflight, registration, payment, credentials or paid operation is called. - Only the URL is sent to Guild. Tool arguments, tool names and native invocation metadata are not included in the preflight request. Native invocation metadata still goes to the intended MCP provider, as it normally would. Supply a public URL: userinfo, query strings, fragments, whitespace and noncanonical URL spellings are refused. Opaque credentials hidden in URL paths cannot be recognized reliably; never supply a secret-bearing path. Rejected input and transport errors are not echoed in preflight summaries.
- The library owns both HTTP clients. Production endpoints require public HTTPS. All DNS answers must pass conservative public-unicast screening; mixed private/public answers are refused. The resulting addresses are pinned into the owned client, with normal HTTPS certificate/hostname verification. IPv4-mapped/translation/transition IPv6 addresses, special-use ranges and local addresses are refused. No caller-supplied clients, credential headers, proxy, redirect, cookie store, decompression or automatic payment/authentication flow is configured.
- Reqwest retry policy is explicitly
never; RMCP expired-session request replay and SSE response-stream reconnection are explicitly disabled. A broken stream requires a new caller-authorized connection. SSE resumption concerns delivery of a response and is distinct from replaying a tool action; both automatic behaviors are disabled here. Native model retries or repeated tool attempts are separate invocations and receive new preflight requests. - Each call makes a fresh preflight request with no local cache and no-cache/no-store headers. Visible cache-hit or positive-age/cache-lifetime responses are refused. This does not prove an intermediary cannot return unmarked stale data.
requested_atandreceived_atare local client times, not authenticated server observation timestamps. - The default asynchronous preflight network deadline is 5s, covering DNS resolution, connection, response headers and the complete body; it is configurable up to 15s. JSON parsing follows and is separately bounded by the body cap (at most 64KiB), JSON parser recursion protection and the 16-level accepted depth. Task/runtime starvation is outside a wall-clock guarantee. A timed-out or abandoned preflight future closes its local connection; a running system DNS worker may finish with its discarded result, without initiating a later connection. Guild's already-started remote probe may continue even after the local request is dropped.
- MCP initialization/discovery has its own setup deadline (default 15s), and native
tools/callhas a separate per-call timeout (default 30s). Initial discovery and MCP payload parsing retain native SDK semantics; the 64-tool limit is checked after discovery and is not an MCP response-byte limit. Automatic catalog refresh/list-change reconciliation is unsupported. Rebuild a session to discover a changed catalog. The guarded session binds the exact URL and initially screened addresses; Guild probes from a separate network vantage and does not prove it inspected the same IP/TLS session as the owned MCP client. - Published Rig 0.42.0 sends best-effort cooperative MCP cancellation when its explicit tool timeout fires while the invocation future is being polled. If the caller aborts after dispatch, dropping that native future can also drop its timeout path: our unwrapped/wrapped reproduction sees no cancellation after 250ms despite a 150ms configured timeout. Do not promise remote execution stops when a caller abandons a run or stream. There is no shared-client-close workaround. MCP cancellation cannot roll back prior effects.
- Closing the owned session immediately refuses new guarded calls and waits up to 2s for native cleanup. A timeout/join failure is returned as an error and remains an error on subsequent closes. Drop requests cancellation but does not await cleanup. Finish/close deliberately; never use session shutdown as per-call cancellation for shared work.
- The
fixturesfeature is an explicit testing/demo path. Both its MCP endpoint and preflight origin must be canonicalhttp://127.0.0.1:<port>/...URLs, and the origin path must be/. It cannot override the production constructor. Do not enable this feature for production. Stdio, standalone SSE transport, A2A actions, resources/prompts, arbitrary clients, wasm and other Rig versions are outside this artifact.
The suite covers actual normal/streaming agent loops, two official MCP servers with same-name tools, aliases/registry snapshots, policy denials and prior hooks, native retries and parallel calls, metadata/rich/error preservation, fresh observations, protocol/contract failures, no automatic RPC replay, actual runner cancellation before dispatch, the retained post-dispatch native limit, pinned address use, delayed resolver disposal, header/body timeout and peer closure, oversized responses and wire-attempt counts. Tests use controlled local servers; they do not prove production availability or external adoption. Linux CI is configured to run these tests, the local demo and the same checks from the packaged source outside the checkout.
Small reproducible issues and focused tests are welcome. Preserve the native binding, uncertainty, disclosure and cancellation boundaries. This project is authored by AgentTanuki, licensed Apache-2.0, and is independent of Rig/RMCP maintainers. An invited catalog submission is distribution work, not evidence of users or endorsement.