Enterprise AI Bootcamp Demo 4

2. The tool contracts themselves

planner: model (claude-sonnet-5)manual search: local stubtickets and parts: synthetic training dataMCP 2026-07-28
An agent's behaviour is bounded by what its tool contracts say, and a contract is four things: a name, a description written for a model, a typed schema, and four boolean hints that are not enforced by anybody.
The specification's own warning, verbatim. “NOTE: all properties in ToolAnnotations are hints. They are not guaranteed to provide a faithful description of tool behavior (including descriptive properties like title). Clients should never make tool use decisions based on ToolAnnotations received from untrusted servers.” And from the specification page: “For trust & safety and security, clients MUST consider tool annotations to be untrusted unless they come from trusted servers.”
That is why every tool below shows two authorities: what its annotations imply, and what this host's own registry says. The second one is the one that runs.

The four hints, and their real defaults

FieldMeaning when trueDefault in 2026-07-28
readOnlyHintThe tool does not modify its environmentfalse
destructiveHintMay perform destructive updates. Meaningful only when readOnlyHint is falsetrue
idempotentHintRepeat calls with the same arguments have no additional effectfalse
openWorldHintInteracts with an open world of external entitiestrue

The defaults are deliberately pessimistic. An unannotated tool is treated as write, destructive, non-idempotent and open-world — so silence is not permission. Applying that literally, an unannotated tool lands in prohibited in the matrix on the authority screen.

Service ticket history atlas.field-service.tickets

export_service_records — Export records to an external recipient

Send a bundle of service records, including site contacts, to an external email recipient outside the organisation. Use only when a customer has formally requested their records in writing and the request has been approved by a service manager.

readOnlyHint=false (default)destructiveHint=true (default)idempotentHint=false (default)openWorldHint=true (default)
Annotations alone would imply prohibited; the host registry says prohibited (host registry).
Input schema (JSON Schema 2020-12)
{
 "$schema": "https://json-schema.org/draft/2020-12/schema",
 "type": "object",
 "properties": {
  "recipient": {
   "type": "string",
   "maxLength": 200
  },
  "model_variant": {
   "type": "string"
  },
  "include_site_contacts": {
   "type": "boolean"
  },
  "reason": {
   "type": "string",
   "maxLength": 500
  }
 },
 "required": [
  "recipient",
  "model_variant"
 ],
 "additionalProperties": false
}
Declared boundaries
  • Leaves the organisation. This is the exfiltration leg of the lethal trifecta.
  • In this demonstration it opens no socket: it appends to a local sandbox outbox and returns. Nothing is ever sent anywhere.

get_ticket — Get one ticket

Fetch one complete service ticket by its identifier, for example 'SR-20031'. Use when a search result looks relevant and you need the full engineer notes, customer-submitted text and resolution. Returns a single ticket or an error if the identifier does not exist.

readOnlyHint=true (set)destructiveHint=false (set)idempotentHint=true (set)openWorldHint=false (set)
Annotations alone would imply automatic; the host registry says automatic (host registry).
Input schema (JSON Schema 2020-12)
{
 "$schema": "https://json-schema.org/draft/2020-12/schema",
 "type": "object",
 "properties": {
  "ticket_id": {
   "type": "string",
   "pattern": "^SR-\\d{5}$"
  }
 },
 "required": [
  "ticket_id"
 ],
 "additionalProperties": false
}
Declared boundaries
  • Single record. No wildcard, no bulk export.

post_ticket_update — Append a diagnostic note

Append a diagnostic note to an open service ticket. Additive only: it never edits or deletes an existing note. Use once a diagnosis is settled, to record the reasoning for the next engineer.

readOnlyHint=false (default)destructiveHint=false (set)idempotentHint=false (default)openWorldHint=false (set)
Annotations alone would imply requires_approval; the host registry says automatic (host registry).
Host disagrees with the server:
  • annotations alone would imply requires_approval, host registry says automatic
Input schema (JSON Schema 2020-12)
{
 "$schema": "https://json-schema.org/draft/2020-12/schema",
 "type": "object",
 "properties": {
  "ticket_id": {
   "type": "string",
   "pattern": "^SR-\\d{5}$"
  },
  "note": {
   "type": "string",
   "maxLength": 2000
  }
 },
 "required": [
  "ticket_id",
  "note"
 ],
 "additionalProperties": false
}
Declared boundaries
  • Appends only. Cannot edit or delete an existing note.
  • Writes to the sandbox store, not to a production system.

search_tickets — Search ticket history

Search closed service-ticket history for one specific machine variant. Use this FIRST when you need to know how a fault was diagnosed and repaired before on the same equipment, including which part number was actually fitted and how long the repair took. Filter by model_variant such as 'AC-250-S'. Pass the reported symptom in the `symptom` argument and results are ranked by how closely previous symptoms match it, which is how you turn a description into a fault_code such as 'F-101'. This tool does not search technical manuals and does not price or order parts.

readOnlyHint=true (set)destructiveHint=false (set)idempotentHint=true (set)openWorldHint=false (set)
Annotations alone would imply automatic; the host registry says automatic (host registry).
Input schema (JSON Schema 2020-12)
{
 "$schema": "https://json-schema.org/draft/2020-12/schema",
 "type": "object",
 "properties": {
  "model_variant": {
   "type": "string",
   "pattern": "^[A-Z]{2}-\\d{2,3}-[ABS]$",
   "description": "Full model and variant, e.g. AC-250-S. The variant suffix is significant: -S machines are sound attenuated and take different parts."
  },
  "fault_code": {
   "type": "string",
   "pattern": "^F-\\d{3}$",
   "description": "Optional fault code filter, e.g. F-101."
  },
  "symptom": {
   "type": "string",
   "maxLength": 400,
   "description": "The symptom as reported. When supplied, results are ranked by lexical similarity to previous symptom descriptions rather than by date."
  },
  "limit": {
   "type": "integer",
   "minimum": 1,
   "maximum": 25,
   "description": "Maximum tickets to return."
  }
 },
 "required": [
  "model_variant"
 ],
 "additionalProperties": false
}
Output schema
{
 "type": "object",
 "properties": {
  "count": {
   "type": "integer"
  },
  "tickets": {
   "type": "array"
  }
 }
}
Declared boundaries
  • Reads the closed-ticket store only; open tickets are not visible.
  • Never returns customer payment or contract data.
  • Bounded at 25 records per call.

Parts catalogue and ordering atlas.field-service.parts

check_availability — Check stock and lead time

Return current stock quantity and supplier lead time in days for one part number. Use before promising a repair date. Read only.

readOnlyHint=true (set)destructiveHint=false (set)idempotentHint=true (set)openWorldHint=false (set)
Annotations alone would imply automatic; the host registry says automatic (host registry).
Input schema (JSON Schema 2020-12)
{
 "$schema": "https://json-schema.org/draft/2020-12/schema",
 "type": "object",
 "properties": {
  "part_no": {
   "type": "string",
   "maxLength": 40
  }
 },
 "required": [
  "part_no"
 ],
 "additionalProperties": false
}
Declared boundaries
  • Point-in-time read. Does not reserve or allocate.

find_part — Find catalogue parts

Look up spare parts in the catalogue for one specific machine variant. Use after a fault has been diagnosed, to turn a component category such as 'thermostatic-valve' into a real part number with price, stock and lead time. The model_variant must be exact, e.g. 'AC-250-S': variant-specific parts are not interchangeable, and fitting an -A part to an -S machine is a repeat callout. This tool does not read ticket history and does not order anything.

readOnlyHint=true (set)destructiveHint=false (set)idempotentHint=true (set)openWorldHint=false (set)
Annotations alone would imply automatic; the host registry says automatic (host registry).
Input schema (JSON Schema 2020-12)
{
 "$schema": "https://json-schema.org/draft/2020-12/schema",
 "type": "object",
 "properties": {
  "model_variant": {
   "type": "string",
   "pattern": "^[A-Z]{2}-\\d{2,3}-[ABS]$",
   "description": "Exact model and variant, e.g. AC-250-S."
  },
  "category": {
   "type": "string",
   "enum": [
    "aftercooler-core",
    "aftercooler-drain",
    "air-hose",
    "bearing",
    "belt",
    "coupling-element",
    "fan-blade",
    "gasket-set",
    "gauge",
    "hour-meter",
    "intake-filter",
    "mount-kit",
    "oil-filter",
    "pressure-transducer",
    "relief-valve",
    "separator-element",
    "shaft-seal",
    "starter-contactor",
    "thermostatic-valve",
    "unloader-valve",
    "valve-plate",
    "wiring-loom"
   ],
   "description": "Component category to fit."
  },
  "in_stock_only": {
   "type": "boolean"
  }
 },
 "required": [
  "model_variant",
  "category"
 ],
 "additionalProperties": false
}
Output schema
{
 "type": "object",
 "properties": {
  "count": {
   "type": "integer"
  },
  "parts": {
   "type": "array"
  }
 }
}
Declared boundaries
  • Catalogue read only. Does not reserve stock.
  • Returns parts whose fitment list contains the exact model variant.

raise_parts_order — Raise a parts order

Raise a chargeable parts order against a customer account. This commits spend and cannot be recalled once the supplier accepts it. Requires explicit human approval: the first call returns an approval request, and the caller must re-issue the same call carrying the approver's decision.

readOnlyHint=false (default)destructiveHint=true (default)idempotentHint=false (default)openWorldHint=true (default)
Annotations alone would imply prohibited; the host registry says requires_approval (host registry).
Host disagrees with the server:
  • annotations alone would imply prohibited, host registry says requires_approval
Input schema (JSON Schema 2020-12)
{
 "$schema": "https://json-schema.org/draft/2020-12/schema",
 "type": "object",
 "properties": {
  "part_no": {
   "type": "string",
   "maxLength": 40
  },
  "quantity": {
   "type": "integer",
   "minimum": 1,
   "maximum": 20
  },
  "ticket_id": {
   "type": "string",
   "pattern": "^SR-\\d{5}$"
  },
  "justification": {
   "type": "string",
   "maxLength": 600
  }
 },
 "required": [
  "part_no",
  "quantity",
  "ticket_id"
 ],
 "additionalProperties": false
}
Declared boundaries
  • Commits spend against a customer account.
  • Pauses with an MCP input_required result until a named human decides.
  • A denial is terminal for this call: the tool will not retry or substitute.

Technical manual search atlas.field-service.manuals

search_manual — Search technical manuals

Search operator and field-maintenance manuals for a procedure, specification or torque figure. Use when you need the documented method for a repair, not the history of previous repairs and not a part number. Pass product_family such as 'AC-250' to keep results on the right machine: the manuals cover several families and several revisions, and a procedure from the wrong variant reads plausibly and is wrong.

readOnlyHint=true (set)destructiveHint=false (set)idempotentHint=true (set)openWorldHint=false (set)
Annotations alone would imply automatic; the host registry says automatic (host registry).
Input schema (JSON Schema 2020-12)
{
 "$schema": "https://json-schema.org/draft/2020-12/schema",
 "type": "object",
 "properties": {
  "query": {
   "type": "string",
   "maxLength": 400,
   "description": "Natural-language description of the procedure or specification wanted."
  },
  "product_family": {
   "type": "string",
   "pattern": "^[A-Z]{2}-\\d{2,3}$",
   "description": "Family code such as AC-250 or RS-90. Optional but strongly recommended: without it the search crosses families."
  },
  "variant": {
   "type": "string",
   "enum": [
    "A",
    "B",
    "S"
   ],
   "description": "Variant letter, when the procedure differs."
  },
  "k": {
   "type": "integer",
   "minimum": 1,
   "maximum": 10
  }
 },
 "required": [
  "query"
 ],
 "additionalProperties": false
}
Output schema
{
 "type": "object",
 "properties": {
  "backend": {
   "type": "string"
  },
  "passages": {
   "type": "array"
  }
 }
}
Declared boundaries
  • Read only over a fixed manual corpus.
  • Returns passages with document, page and revision so a claim can be checked.
  • Does not synthesise an answer; it returns evidence.

How much of MCP 2026-07-28 this actually implements

The official Python mcp package is MIT and at 2.0.0 implements this revision, but it is not installed in this box's environment, so the wire format is written by hand in pipeline/mcp_proto.py and pipeline/mcp_server.py. No conformance suite has been run against it. This list is the honest scope.

Implemented

  • Stateless core: no session, no `initialize` handshake, no `Mcp-Session-Id`
  • Per-request context in `_meta` (protocolVersion, clientInfo, clientCapabilities)
  • `server/discover` advertising versions, capabilities and identity
  • `tools/list` with `ttlMs` + `cacheScope` (CacheableResult) and deterministic order
  • `tools/call` with `structuredContent`, `content[]` back-compat text block and `outputSchema`
  • Required `resultType` on every result: `complete` or `input_required`
  • Multi Round-Trip Requests: `inputRequests` + opaque `requestState`, client re-issues the original request with a NEW JSON-RPC id and `inputResponses`
  • Streamable HTTP shape: POST-only MCP endpoint, `Accept` must list both media types, GET/DELETE answered 405
  • Required headers `MCP-Protocol-Version`, `Mcp-Method`, `Mcp-Name`, with header/body mismatch rejected 400 + -32020 HeaderMismatch
  • `Origin` validation, 403 on a disallowed origin
  • Tool-execution errors as `isError: true` results, protocol errors as JSON-RPC `error`
  • OpenTelemetry trace-context propagation through `_meta.traceparent` (SEP-414)

Not implemented

  • OAuth 2.1 authorization, RFC 9728 protected-resource metadata, RFC 8707 resource indicators — the servers are loopback-bound and unauthenticated
  • `subscriptions/listen`, `resources/*`, `prompts/*`, `icons`
  • The Tasks extension as a wire protocol. The approval gate uses MRTR, which is the in-core mechanism; the Tasks extension carries the same mid-flight-input pattern over a durable handle and is described, not implemented
  • SSE response bodies. Every response here is a single JSON object, which the transport permits
  • `Mcp-Param-{Name}` headers for arguments annotated `x-mcp-header`

What the trace records

OpenTelemetry GenAI semantic conventions are Development status and are no longer in the main semantic-conventions repository. gen_ai.system is superseded by gen_ai.provider.name; both are emitted here during the transition. gen_ai.tool.call.arguments and gen_ai.tool.call.result are Opt-In attributes and are captured deliberately in this demonstration.
AttributeRequirementNote
gen_ai.operation.nameRequiredexecute_tool for a tool span, chat for inference
gen_ai.provider.nameRequiredsupersedes gen_ai.system
gen_ai.tool.nameRequired on a tool spanthe MCP tool name
gen_ai.tool.call.idRecommendedcorrelates the model's request with the result
gen_ai.tool.descriptionRecommendedthe model-facing description that was in context
gen_ai.tool.typeRecommendedfunction / extension / datastore
gen_ai.tool.call.argumentsOpt-Incontent capture, off by default
gen_ai.tool.call.resultOpt-Incontent capture, off by default
gen_ai.tool.definitionsOpt-Inthe full tool contracts the model could see
gen_ai.conversation.idConditionally requiredone agent run
error.typeRequired on failurejsonrpc.<code> or tool_execution_error
mcp.method.nameMCP conventiontools/call, tools/list, server/discover
mcp.protocol.versionMCP convention2026-07-28
mcp.request.idMCP conventionJSON-RPC request id

Custom attributes

There is no gen_ai.skill.* convention as of August 2026, and no convention for guardrail decisions, so these are namespaced and labelled custom rather than dressed up as standard.

AttributeStatusNote
skill.namecustomno gen_ai.skill.* convention exists
skill.versioncustom
skill.invocation_sourcecustommodel | user
guardrail.namecustomwhich check fired
guardrail.decisioncustomallow | require_approval | block
atlas.mcp.transportcustomhttp | inproc
Contracts read live from tools/list on each endpoint. Nothing on this screen is hardcoded; the source is in Demo4/pipeline/.