Track 1 — Context Engineering¶
You run a maritime shipping company called Shipyard. Ships, crews, voyages, cargo — all scattered across REST APIs. What you want is an AI agent that can actually use them. What you don't want is to wait for every backend team to finish their work before you start.
Good news: you don't have to. This track opens with a mock. One YAML file, no backend, and the agent already works. Then — step by step — we wire the real registry, lock the doors (both of them), shape the output, and plug in a legacy system that still speaks XML.
No code. Just a spec. Contract-first.
⚓ Prerequisites. A running Ikanos engine (see Installation). All capability files for this track live in
ikanos-docs/tutorial/in the public Ikanos repository.
Step 1 — Mock First¶
File: step-1-shipyard-mock.yml
You don't have a backend yet. Define what the agent sees first, fill it
with mock data, worry about the wire later. One expose block, one tool, no
consumes, no call. Just value:.
ikanos: "1.0.0-alpha3"
capability:
exposes:
- type: mcp
port: 3001
namespace: shipyard-tools
description: "Shipyard MCP tools for fleet management"
tools: # alpha3 — keyed map by tool name
get-ship:
description: "Retrieve a ship's details by IMO number"
inputParameters:
imo: { type: string, required: true, description: "IMO number" }
outputParameters:
- { name: imo, type: string, value: "{{imo}}" }
- { name: name, type: string, value: "Northern Star" }
- { name: type, type: string, value: "cargo" }
- { name: flag, type: string, value: "NO" }
- { name: status, type: string, value: "active" }
outputParameters is a flat list — same shape as inputParameters. Two
flavors of value:: static strings ("Northern Star") returned as-is, and
a Mustache template ("{{imo}}") referencing the tool's input. A mock
can do that — no consumes required. Enough to lock the contract.
Run it¶
# Download the file
curl -L -o step-1-shipyard-mock.yml \
https://raw.githubusercontent.com/naftiko/ikanos/main/ikanos-docs/tutorial/step-1-shipyard-mock.yml
# Validate, then serve
ikanos validate step-1-shipyard-mock.yml
ikanos serve step-1-shipyard-mock.yml
Connect an MCP client and call get-ship(imo: "IMO-9321483"):
Expected output:
{ "imo": "IMO-9321483", "name": "Northern Star", "type": "cargo", "flag": "NO", "status": "active" }
🧭 What you learned: contract-first, flat
outputParameters,value:(static + Mustache), MCP expose withoutconsumes.
Step 2 — Wiring to a real API¶
File: step-2-shipyard-wiring.yml
The Maritime Registry is live. GET /ships/{imo_number} returns real data.
Time to honor the contract from Step 1 — same tool signature, same output
shape, real data behind it.
Three surgical changes: add a consumes block, add call: on the tool, swap
every value: for mapping:.
capability:
consumes:
- namespace: registry
type: http
baseUri: "https://mocks.naftiko.net/rest/naftiko-shipyard-maritime-registry-api/1.0.0-alpha3"
resources: # alpha3 — keyed map by resource name
ship:
path: "/ships/{{imo_number}}"
operations: # alpha3 — keyed map by operation name
get-ship:
method: GET
inputParameters:
imo_number: { in: path, required: true }
exposes:
- type: mcp
port: 3001
namespace: shipyard-tools
tools:
get-ship:
inputParameters:
imo: { type: string, required: true }
call: registry.get-ship
with:
imo_number: shipyard-tools.imo
outputParameters:
- type: object
properties:
imo: { type: string, mapping: "$.imo_number" }
name: { type: string, mapping: "$.vessel_name" }
type: { type: string, mapping: "$.vessel_type" }
flag: { type: string, mapping: "$.flag_code" }
status: { type: string, mapping: "$.operational_status" }
The with: block is the bridge: the agent speaks imo, the registry speaks
imo_number.
The mock is gone. The contract survived.
Run it¶
curl -L -o step-2-shipyard-wiring.yml \
https://raw.githubusercontent.com/naftiko/ikanos/main/ikanos-docs/tutorial/step-2-shipyard-wiring.yml
ikanos validate step-2-shipyard-wiring.yml
ikanos serve step-2-shipyard-wiring.yml
Same tool, same call (get-ship(imo: "IMO-9321483")) — but the bytes now come
from the live Maritime Registry.
🧭 What you learned:
consumes,call,with,mapping, path vs query parameters, contract preservation.
Step 3 — Auth & Binds — both doors¶
File: step-3-shipyard-auth.yml
The registry's public tier returns 5 fields per ship. Specs, dimensions,
certifications, crew assignments — all of that sits behind a bearer token.
While we're handling tokens, lock the MCP server too — right now anyone on
the network can call get-ship.
Same mechanism, two directions. binds declares what's secret.
authentication plugs it in — once on the back door (consumes), once on
the front door (MCP expose).
binds:
- namespace: registry-env
location: "file:///./shared/secrets.yaml"
keys:
REGISTRY_TOKEN: "registry-bearer-token"
MCP_SERVER_TOKEN: "mcp-server-token"
capability:
consumes:
- namespace: registry
type: http
baseUri: "..."
authentication:
type: bearer
token: "REGISTRY_TOKEN"
exposes:
- type: mcp
port: 3001
namespace: shipyard-tools
authentication:
type: bearer
token: "MCP_SERVER_TOKEN"
# ...tools unchanged
In production you swap the location: for a vault or omit it for env-var
injection. Tool signatures and output shapes don't change — but unauthenticated
clients now get a 401.
Run it¶
curl -L -o step-3-shipyard-auth.yml \
https://raw.githubusercontent.com/naftiko/ikanos/main/ikanos-docs/tutorial/step-3-shipyard-auth.yml
# Provide the two tokens referenced by binds (one-time setup)
mkdir -p shared
cat > shared/secrets.yaml <<'EOF'
registry-bearer-token: "demo-registry-token"
mcp-server-token: "demo-mcp-token"
EOF
ikanos validate step-3-shipyard-auth.yml
ikanos serve step-3-shipyard-auth.yml
Try get-ship from MCP Inspector without an Authorization: Bearer …
header — you'll get a 401. Add Authorization: Bearer demo-mcp-token and the
call succeeds.
🧭 What you learned:
binds,authenticationfor consumes and exposes, externalized secrets.
Steps 4–7 — Output shaping, legacy systems, writes, lookups¶
The remaining steps in this track are summarized below. Each has its own YAML
file in ikanos-docs/tutorial/.
| Step | File | Adds |
|---|---|---|
| 4 — Right-size the payload | step-4-shipyard-output-shaping.yml |
Selective mapping: to trim a 30-field response down to what the agent needs |
| 5 — Plug a legacy XML system | step-5-shipyard-multi-source.yml |
A second consumes adapter; XML-to-JSON conversion via the format adapter |
| 6 — Teach the agent to write | step-6-shipyard-write-operations.yml |
POST and PUT operations; safer than POST-everywhere by default |
| 7 — Server-side lookups | step-7-shipyard-orchestrated-lookup.yml |
steps: with type: lookup to join ship + crew into one response |
Run any of Steps 4–7¶
The pattern is identical — download, validate, serve:
# Replace STEP with the filename from the table above, e.g. step-4-shipyard-output-shaping.yml
STEP=step-4-shipyard-output-shaping.yml
curl -L -o "$STEP" \
"https://raw.githubusercontent.com/naftiko/ikanos/main/ikanos-docs/tutorial/$STEP"
ikanos validate "$STEP"
ikanos serve "$STEP"
Step 7 also pulls in shared/step7-registry-consumes.yml — download it alongside:
mkdir -p shared
curl -L -o shared/step7-registry-consumes.yml \
https://raw.githubusercontent.com/naftiko/ikanos/main/ikanos-docs/tutorial/shared/step7-registry-consumes.yml
When you're ready for skill groups, aggregates, and a parallel REST adapter, move on to Track 2 — API Reusability.
Recap¶
You started with a mock and ended with a contract-first capability that:
- Talks to a real authenticated API
- Locks down the MCP server with bearer auth
- Returns exactly the fields the agent needs
- Handles legacy XML payloads and write operations
- Joins data across multiple endpoints server-side
All in YAML. All deterministic. All deployable as a single Docker container.