agentguild

package module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 8, 2026 License: Apache-2.0 Imports: 20 Imported by: 0

README

Eino + Agent Guild preflight

An optional Go integration that checks a disclosed MCP endpoint immediately before its native tool call. It owns the official MCP SDK client and installs the guard on that exact session. Eino aliases, same-name collisions and WithToolList reuse of these tools therefore keep the selected endpoint's policy.

This is an independent AgentTanuki project, licensed under Apache-2.0. It does not modify Eino or install a global hook.

Connect requires an explicit endpoint, policy and DiscloseEndpointAndAllowProbing: true. It connects to the MCP endpoint and discovers its tools. Before each subsequent tools/call, it sends only the full endpoint URL to GET https://agent-guild-5d5r.onrender.com/preflight?url=.... Guild actively probes that endpoint, including its public discovery surface. The guard sends no prompts, tool arguments, credentials or payment data to Guild. Raw tool names are available only in the optional local audit callback.

Use only an endpoint you are authorized to disclose and permit Guild to probe. Production targets must be public HTTPS URLs, without user information, query strings or fragments. Never provide credentials hidden in the URL path: opaque path credentials cannot be recognized by URL syntax alone. Private addresses and invalid configurations are rejected before connection/disclosure. No OAuth, API keys, caller transports or proxy settings are accepted.

The free response is an unsigned, bounded observation from Guild's network vantage. This application's MCP connection has a separate network vantage and pins its own screened DNS result. They can reach different machines behind DNS/CDNs, and the endpoint may change behavior between observation and execution. Exact local session binding does not prove remote ownership or identity. Card retrieval, signature presence, or a payment challenge does not prove signature validity, protocol conformance, safety, successful tasks or paid execution. This integration never calls /check, /preflight/deep, registration, passport verification, billing or payment endpoints.

Supported runtime

The module pins Eino v0.9.19, its officialmcp adapter v0.1.1, and the official MCP Go SDK v1.6.1. The adapter and SDK require Go 1.25 or later. Local validation used Go 1.26.8 on macOS arm64; CI is configured for Go 1.26.8 and 1.27.1 on Linux. A configured CI matrix is not a claim that an unpublished run has passed.

Build the source with:

go mod download
go test -race -count=1 ./...
go vet ./...
go build ./...
go mod verify

Native use

Once the release is available, install the pinned version:

go get github.com/AgentTanuki/eino-agentguild@v0.1.0

Before release availability, evaluate this source using a local Go module replacement. The import path is github.com/AgentTanuki/eino-agentguild; use a reviewed published release or commit for deployed applications.

policy := agentguild.StrictPolicy()
// Explicit operator decision: these two missing observations are tolerable
// for this application. They remain unknown in the retained evidence.
policy.AllowedUnknown = []string{"payment_claim_holds", "independent_evidence"}

connection, err := agentguild.Connect(ctx, agentguild.Config{
    EndpointURL: operatorApprovedEndpoint,
    DiscloseEndpointAndAllowProbing: true,
    Policy: policy,
    OnDecision: func(d agentguild.Decision) {
        auditLocalDecision(d) // concurrency-safe; must return promptly
    },
})
if err != nil { return err }
defer connection.Close()

toolsNode, err := compose.NewToolNode(ctx, &compose.ToolsNodeConfig{
    Tools: connection.Tools(),
})

Use the resulting native ToolsNode in your existing Eino graph/agent, or invoke it with an operator-selected tool call. The operator example compiles this API and makes zero calls without explicit flags. It performs discovery only unless a tool is named. It uses strict policy plus any explicitly listed tolerated unknowns; applications needing other policies should configure the library directly.

StrictPolicy() accepts no_failed_checks, requires endpoint_reachable and protocol_handshake to be proven, and allows no failed or unknown checks. Many ordinary MCP endpoints lack an A2A card or independent evidence, so this default intentionally denies them. For a justified application policy, select AcceptedVerdicts, RequiredProven, AllowedUnknown and AllowedFailed explicitly. For example, a caller may omit protocol_handshake from RequiredProven and list it in AllowedUnknown when a bounded unauthenticated probe cannot establish the handshake. This preserves an unknown as unknown. do_not_delegate is always denied, irrespective of other settings. Required-proven checks take precedence over tolerance lists; no configuration automatically expands itself.

Unavailable, timed-out, malformed, conflicting, stale-indicated or wrong-target responses deny the call. The parser requires all six current contract checks, unique names, valid statuses, exact failed/unknown/scored summaries and a consistent verdict. Duplicate JSON keys, excessive nesting, missing fields and oversized bodies are rejected. New cache-result fields require review. Additional check names are preserved and evaluated by the same policy rules.

Evidence and network behavior

OnDecision receives the actual service origin and target, local request/receipt times, raw bounded JSON, parsed verdict/checks/failed/unknowns, response cache headers and a fixture marker. Remote headline, detail, method, limits and extension data remain untrusted evidence. They are never interpreted as instructions or injected into model messages by this package. Policy errors expose fixed reason codes, not transport URLs or remote prose. Changing a callback report cannot change the already computed decision; the callback is synchronous and should not block.

Each tool attempt gets a fresh preflight GET. There is no client cache; requests ask for no caching, and positive/invalid Age headers are rejected. This does not authenticate a remote freshness claim. Preflight defaults to an 8-second total deadline (configurable up to 15 seconds), including DNS, connection, headers and a maximum 64 KiB body. It follows no redirects and uses no environment proxy. Its HTTP/1 transport disables connection reuse to prevent Go's reused-connection retry behavior.

The owned MCP transport also disables HTTP connection reuse, redirects and proxies. It screens every resolved address and pins the connection to one allowed address; the URL hostname remains the TLS verification name. Setup/discovery has a 10-second total deadline and each MCP HTTP request a 5-second deadline. The native session has no OAuth, automatic session reconnection, SDK retry or standalone SSE. HTTP socket reuse is separate from the logical MCP session. A failed session requires a deliberate new Connect, with the same explicit endpoint/policy requirements. Outer application retries get a fresh preflight; the guard cannot undo work already dispatched.

Boundary limitations

  • Initialization, notifications and tools/list discovery happen before preflight; initial server metadata remains untrusted. This guard covers subsequent tools/call, not discovery, resources, prompts, A2A, arbitrary application network calls or direct clients created elsewhere.
  • Only tools returned by this factory are guarded. WithToolList can intentionally replace them with arbitrary unguarded tools; those tools are outside coverage. Local tools remain usable under native Eino behavior. Eino's duplicate-name selection is last-wins; use ToolNamePrefix for clarity, but enforcement never trusts the exposed name as proof of origin.
  • Invoke, Eino's native invokable-to-stream conversion, parallel calls and outer middleware retries retain the actual session guard. Eino does not automatically cancel sibling tools on another tool's denial. Existing earlier caller denial prevents the observation. Outer code can hide returned errors; it cannot make this denied native call dispatch.
  • Native officialmcp result formatting and error conversion remain unchanged. The guard does not validate or sanitize tool content. Metadata mutation does not move the session or raw wire tool name; do not mutate tool metadata concurrently with agent execution.
  • Close is idempotent and cancels pending preflight before closing the native session. Stop agent runs before closing. Cancellation cannot reverse completed or already dispatched work.

Loopback demo

go run -tags agentguild_fixture ./cmd/offline-demo

The demo starts an official SDK MCP server and a synthetic preflight HTTP fixture on exact 127.0.0.1 origins. It uses the real native Eino ToolsNode, allows one call, then denies another and asserts the protected tool ran only once. It uses no model/provider, public Guild request, account or payment. Unknowns remain visible. ConnectLoopbackFixture exists only with the explicit agentguild_fixture build tag and rejects empty or non-loopback origins. Do not use that build tag for production binaries.

Source contract

These tests demonstrate synthetic native integration behavior. They are not evidence of external agents, adoption, passports, revenue, paid tasks or public endpoint compatibility.

Documentation

Overview

Copyright 2026 AgentTanuki. Licensed under the Apache License, Version 2.0.

Copyright 2026 AgentTanuki. Licensed under the Apache License, Version 2.0.

Copyright 2026 AgentTanuki. Licensed under the Apache License, Version 2.0.

Copyright 2026 AgentTanuki. Licensed under the Apache License, Version 2.0.

Index

Constants

View Source
const GuildOrigin = "https://agent-guild-5d5r.onrender.com"

Variables

This section is empty.

Functions

This section is empty.

Types

type Check

type Check struct{ Name, Status, Detail string }

Check describes unsigned remote evidence. Detail is untrusted prose.

type Config

type Config struct {
	EndpointURL string
	// Acknowledges sending the full endpoint URL to Guild and its active probes.
	DiscloseEndpointAndAllowProbing bool
	Policy                          Policy
	ToolNamePrefix                  string
	// Zero selects 8 seconds; maximum is 15 seconds. One fresh GET per tools/call.
	PreflightTimeout time.Duration
	// OnDecision is an optional synchronous local audit callback. No arguments or
	// prompts are included. It must return promptly and be concurrency-safe.
	// Mutating the report cannot change the already computed policy decision.
	OnDecision func(Decision)
}

Config is per connection and never automatically installed as a global hook.

type Connection

type Connection struct {
	// contains filtered or unexported fields
}

Connection owns the real MCP session, screened transport and preflight client. Tools exposes native Eino tools, not the session/client/transport.

func Connect

func Connect(ctx context.Context, cfg Config) (*Connection, error)

Connect initializes and lists tools before any preflight. Each subsequent tools/call is checked at its owned SDK sending middleware boundary.

func (*Connection) Close

func (c *Connection) Close() error

Close cancels pending preflight observations and closes the native session. It is idempotent. Stop agent runs before closing; already dispatched work may have happened and cannot be rolled back by cancellation or this guard.

func (*Connection) Tools

func (c *Connection) Tools() []tool.BaseTool

Tools returns a new slice of the same guarded native tools. Their endpoint binding survives native aliasing, metadata renaming, and WithToolList reuse. Arbitrary tools constructed elsewhere are outside this connection's guard.

type Decision

type Decision struct {
	Allowed     bool
	Reason      string
	RawTool     string
	Observation *Observation
}

type DeniedError

type DeniedError struct{ Reason string }

DeniedError is intentionally redacted: it does not include endpoint URLs, remote prose, tool arguments, or the underlying transport error.

func (*DeniedError) Error

func (e *DeniedError) Error() string

type Evidence

type Evidence struct {
	Target, Verdict, Headline, Method, Limits string
	Checks                                    []Check
	Failed, Unknowns, Scored                  []string
}

Evidence retains the service's facts and prose separately from local policy. It is not a credential, signature verification, or execution guarantee.

type Observation

type Observation struct {
	ServiceOrigin                                   string
	Target                                          string
	RequestedAt, ReceivedAt                         time.Time
	ClientNoCache                                   bool
	Synthetic                                       bool
	ResponseAge, ResponseDate, ResponseCacheControl string
	RawJSON                                         json.RawMessage
	Evidence                                        *Evidence
}

Observation records the actual observation origin and local request interval. RawJSON and Evidence contain untrusted remote data; do not inject them into a prompt as instructions. ClientNoCache describes only this client's behavior.

type Policy

type Policy struct {
	AcceptedVerdicts []string
	RequiredProven   []string
	AllowedUnknown   []string
	AllowedFailed    []string
}

Policy is explicit caller policy. Unknown and failed checks need individual allow entries; allowing them does not change their observed status.

func StrictPolicy

func StrictPolicy() Policy

StrictPolicy requires both blocking checks and tolerates no failures/unknowns. Many legitimate MCP services will be denied until the operator deliberately chooses which missing evidence their application can tolerate.

Directories

Path Synopsis
cmd

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL