Skip to content

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"):

npx @modelcontextprotocol/inspector
# then point it at http://localhost:3001

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 without consumes.


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, authentication for 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.