AI agent tools · Contract testing · JSON Schema

Contract Testing for AI Agent Tools

Test agent tool schemas, transport behavior, authorization, retries, and compatibility with valid, invalid, and adversarial requests.

Published
Updated
Code examples
Illustrative
Reading time
5 minutes

An agent-tool contract test proves more than “the JSON parsed.” It verifies that advertised schemas accept intended requests and reject invalid ones, that results match their declared shape, that errors are stable, and that policy middleware blocks unauthorized effects. Test the contract from both producer and agent-runtime perspectives before measuring model behavior.

Define the complete contract

For each tool, version these surfaces:

SurfaceContract questions
DiscoveryIs the name unique and description accurate?
InputRequired fields, types, bounds, formats, unknown properties?
OutputSuccess and error variants, nullability, provenance?
SemanticsPreconditions, postconditions, idempotency, side effects?
PolicyWho may call it, for which tenant and resources?
OperationsDeadline, rate limit, retry and cancellation behavior?

JSON Schema Draft 2020-12 defines structural and validation vocabularies, but implementations can differ in supported vocabularies and format behavior. Declare $schema, choose a conforming validator, and test the validator configuration itself. JSON Schema specification

The schema is not the whole contract. It cannot prove that an account belongs to the caller or that a refund is authorized.

Start with a restrictive schema

This illustrative schema makes accidental inputs easier to reject:

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://example.test/tools/get-order-input.schema.json",
  "type": "object",
  "properties": {
    "orderId": {
      "type": "string",
      "pattern": "^ord_[A-Za-z0-9]{12}$"
    }
  },
  "required": ["orderId"],
  "additionalProperties": false
}

Test the schema against its meta-schema during build. Compile it using the same dialect and options as production. If format validation matters, configure and test it explicitly rather than assuming every validator treats formats as assertions.

Create positive, boundary, and negative examples

Every input contract needs at least:

valid: smallest intended request
valid: every optional field present
boundary: minimum and maximum values
invalid: each required property missing
invalid: wrong primitive type and null
invalid: empty, oversized, Unicode, and malformed identifiers
invalid: unknown property
invalid: mutually inconsistent fields
adversarial: path traversal, URL abuse, control characters, injection-shaped text

Generate some cases from the schema, but hand-write semantic boundaries. Schema-derived generation will not know that endDate must follow startDate unless you encode or separately test that rule. JSON Schema’s official use cases explicitly include contract, property-based, fuzzing, and invalid-instance generation. JSON Schema use cases

Do not feed unbounded generated values to live infrastructure. Run fuzzing against an isolated implementation with time, memory, request-size, and effect limits.

Test outputs as aggressively as inputs

Validate every success response before returning it to the model. Test errors as discriminated, machine-readable results:

type OrderResult =
  | { readonly ok: true; readonly order: { readonly id: string; readonly status: string } }
  | { readonly ok: false; readonly code: "NOT_FOUND" | "FORBIDDEN" | "UNAVAILABLE"; readonly retryable: boolean };

Check that:

  • absent resources do not leak whether another tenant owns them;
  • secret fields never appear, including in errors;
  • partial responses are represented explicitly;
  • retryability matches the failure class;
  • errors do not contain stack traces or hostile upstream content; and
  • output provenance is retained when the agent must cite or validate it.

Treat upstream text as untrusted. An indirect prompt injection returned by a search or MCP tool is data, not a new instruction. OWASP’s agent guidance recommends structured validation, least privilege, and adversarial tests for tool misuse and exfiltration. OWASP AI Agent Security Cheat Sheet

Put authorization behind the contract boundary

Test the broker that executes a tool, not only the handler:

principal + tenant + requested capability + arguments
  → authenticate
  → authorize exact resource and operation
  → validate input
  → enforce deadline and idempotency
  → execute in constrained environment
  → validate and redact output
  → append audit event

Build a permission matrix containing allowed and denied cases for roles, tenants, resource ownership, expired credentials, revoked capabilities, and approval state. For a destructive tool, assert the database or sandbox remains unchanged after every denial.

Model behavior is not a security control. Even if the system prompt says “never access another tenant,” the executor must enforce it.

Verify transport and failure semantics

Inject failures at each boundary:

  • connection refusal and DNS failure;
  • timeout before and after the server accepts work;
  • malformed or truncated response;
  • duplicate delivery;
  • cancellation;
  • rate-limit response with and without retry guidance;
  • server restart; and
  • success response arriving after the caller’s deadline.

For writes, distinguish retry-safe transport failures from unknown outcomes. Use an idempotency key bound to principal, operation, and normalized arguments when the service supports it. A retry policy must never assume that a timed-out write did not happen.

Test concurrency: two requests using the same idempotency key, conflicting updates, stale versions, and cancellation racing with completion.

Check producer-consumer compatibility

Store contract fixtures from both sides:

  • provider tests prove the current implementation satisfies advertised schemas and semantics;
  • consumer tests prove the agent runtime can discover, call, parse, and classify the tool; and
  • compatibility tests run old consumers against the candidate provider and the candidate consumer against supported providers.

OpenAPI 3.1 bases its Schema Object on JSON Schema Draft 2020-12 while defining an OpenAPI dialect. Validate the full OpenAPI document and use the correct dialect; “valid JSON Schema” alone does not validate an API description. OpenAPI 3.1.1 specification, JSON Schema guidance for OpenAPI 3.1

Classify changes:

ChangeTypical compatibility risk
Add optional inputolder provider may reject it; older consumer unaffected
Add required inputbreaking for existing callers
Remove enum valuebreaking if callers send it
Add output fieldbreaks consumers that incorrectly reject unknown fields
Change side effect or retryabilitysemantic break even if schema is unchanged

Version behavior, not just shapes. A description change that makes the model choose a tool differently also deserves an agent evaluation.

Keep contract tests distinct from agent evals

Contract tests answer whether the tool boundary behaves as declared. Agent evals answer whether a stochastic system chooses and uses it correctly. Run contracts first; a broken tool makes agent scores uninterpretable. Then add selection, argument grounding, and recovery cases to the agent eval suite.

Contract-test checklist

  • Declare and validate the schema dialect.
  • Test valid, boundary, invalid, adversarial, and oversized instances.
  • Validate success and error outputs before model exposure.
  • Enforce authentication, tenant isolation, authorization, and approvals in code.
  • Assert denied calls have no side effects.
  • Inject timeout, duplicate, cancellation, partial, and restart failures.
  • Define idempotency and unknown-outcome behavior for writes.
  • Test supported producer-consumer version combinations.
  • Treat description and semantic changes as versioned changes.
  • Run all destructive tests in isolated fixtures.