Skip to content

About

Optional Kotlin example for caller-controlled preflight before owned Koog MCP tool calls

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

2 Commits

Folders and files

Repository files navigation

Agent Guild for Koog

An optional Kotlin/JVM example that applies a caller's endpoint policy immediately before an owned Koog MCP tool invocation. It uses Koog 1.2.0 and MCP integration 1.2.0-beta. Author: AgentTanuki. Apache-2.0.

The public API owns unauthenticated HTTPS MCP connections and a fixed native GraphAIAgent. It accepts your existing Koog PromptExecutor and AIAgentConfig. A tool-name collision uses Koog's first-registry-wins behavior; the guard checks the exact tool object that this registry selects.

Free /preflight is unsigned, bounded observation. It does not verify identity, a passport signature, protocol conformance, task completion or payment. Signature presence and card retrieval are not verification. The caller decides which failed and unknown checks are acceptable. do_not_delegate always denies.

Install from source

Requires JDK 17 and Maven. From this directory:

mvn clean install

This builds/tests the project and installs version 0.1.0 into your local Maven repository. These coordinates are not advertised as published on Maven Central:

<dependency>
  <groupId>io.github.agenttanuki</groupId>
  <artifactId>koog-agentguild</artifactId>
  <version>0.1.0</version>
</dependency>

Koog runtime dependencies are published artifacts. This project also uses Ktor/OkHttp for connections and Jackson's streaming parser for duplicate-key and depth checks. Test-only dependencies include Koog's mock executor and the official Kotlin MCP SDK server 0.11.1.

Use with your Koog executor

executor and config below are the PromptExecutor and AIAgentConfig supplied by your application. Set iteration/provider limits in that config. The endpoint is an operator-selected public MCP URL, never model-generated input.

import agentguild.GuardedMcpAgent
import agentguild.PreflightPolicy
import agentguild.PublicMcpEndpoint

val endpoint = PublicMcpEndpoint.of(
    endpointUrl,
    discloseEndpointToAgentGuild = true,
)
val guarded = GuardedMcpAgent.create(
    promptExecutor = executor,
    config = config,
    endpoints = listOf(endpoint),
    policy = PreflightPolicy(
        requiredProven = setOf("endpoint_reachable", "protocol_handshake"),
        allowedVerdicts = setOf("no_failed_checks", "delegate_with_caution"),
        allowedUnknowns = setOf("agent_card_signed", "payment_claim_holds", "independent_evidence"),
        allowedFailedChecks = setOf("agent_card_resolves", "agent_card_signed"),
    ),
    onObservation = { evidence ->
        // Operator audit destination only. Do not insert remote prose into prompts.
        audit(evidence)
    },
)
try {
    val answer = guarded.run(task)
} finally {
    guarded.close()
}

The policy above is an explicit example: it tolerates missing/unsigned cards and unavailable history/payment evidence while requiring a reachability and protocol-handshake observation. Default policy is stricter: every failed or unknown check denies. An authenticated or unrecognized endpoint may have an unknown handshake; a caller may explicitly remove that requirement and allow that named unknown, but its status stays unknown. MCP authentication itself is outside this version.

close() closes each owned official MCP client/transport and its HTTP connection and invokes Koog cleanup of the supplied prompt executor. Give this handle its own executor; do not share an executor whose lifetime belongs to another agent. Concurrent calls on one handle are serialized, while native tool calls within a turn may execute in parallel.

Disclosure and boundaries

Construction resolves/screens DNS and initializes/discovers MCP tools. That traffic precedes the invocation guard. Each subsequent protected tool call makes a fresh request to https://agent-guild-5d5r.onrender.com/preflight?url=.... Only the explicitly disclosed endpoint URL is sent: no task content, tool arguments, authentication headers or model messages. Agent Guild actively probes that endpoint as part of the observation; disclosure authorizes that additional service traffic. Never supply an endpoint containing an opaque credential in its path: URL syntax cannot reliably recognize such secrets. No account, registration, paid /check, payment or passport operation is performed.

Only public HTTPS endpoints with plain normalized paths are supported. URL credentials, query strings, fragments, private/reserved IP addresses and local DNS results are refused. The owned MCP client pins screened addresses and disables redirects and proxies. No authentication headers or externally supplied clients are accepted.

The preflight request has a five-second total response deadline and 64 KiB body limit, rejects redirects/non-200 responses/compressed bodies/cached indications, validates UTF-8 and duplicate JSON keys, and requires the six checks and consistent summaries/verdict of the pinned free contract. Failures produce fixed error codes; they never retry into approval. External cancellation remains cancellation. There is no local observation cache.

PreflightObservation preserves the actual service origin, exact target, local request/receipt timestamps, returned cache headers and full response in untrustedResponseJson. The timestamps describe this client's request window, not a signed service timestamp. Guild probes from its own network and DNS resolution; this report does not bind its observation to the same IP address or TLS session used by the owned MCP client. This is still an unsigned remote report. Unknown statuses remain present. Remote prose is not appended to the model's prompt, tool result or denial message; the optional operator audit callback receives it as untrusted data.

The graph, execution registry, pipeline and feature ordering stay private. No custom strategies, arbitrary features, externally supplied registries, local tools or A2A calls are supported. This does not mediate independent network requests made by your application or provider implementation. It is not a sandbox against hostile in-process code or reflection. Internal test hooks are not supported public API.

Run entirely offline

This command uses Koog's actual mock executor, an official Kotlin MCP SDK server bound to loopback, and a synthetic local preflight fixture:

mvn test-compile exec:exec -Dexec.classpathScope=test

It runs in a separate JVM, prints a synthetic-observation label and asserts exactly one native SDK tool invocation. It calls no model provider or production Guild service. Test-only loopback access is explicit and cannot be passed to the public factory.

mvn test

Tests cover official SDK allow/deny, same-name binding, registry/context copies, environment replacement and registry mutation rejection, prior denial, native parallel calls, cancellation, changing evidence, response contract/bounds and URL policy. The fixture validates routing/denial behavior; it does not measure adoption or certify endpoint safety. A consumer check is provided under consumer/ after local installation.

Source pins

  • Koog release 1.2.0: 8973a0a488514d61bdae2d7b2b2fc2049639d9c9.
  • Kotlin MCP SDK 0.11.1: f02f5d3d10694c8fffd6a25b60f384a16b0441b9.
  • The free Agent Guild response contract is pinned to preflight.py at commit 8dae8d2, SHA-256 bc7e8cdce5395fbf225d704ad82f88819e51863afc5c1aeecd184c9edf03d7c1. New check names or changed summary semantics fail closed until reviewed.

This is a standalone optional example. It does not change Koog's dependencies or claim upstream acceptance.

About

Optional Kotlin example for caller-controlled preflight before owned Koog MCP tool calls

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages