Track 2 — API Reusability¶
By the end of Track 1, your capability could list, inspect, plan, and enrich. Five tools, two APIs, one contract that survived the journey from mock to production.
But the Shipyard doesn't live alone. Partner agents want a catalog they can discover. Logic keeps getting copy-pasted between tools. The operations dashboard speaks REST, not MCP. This track adds three layers on top of Track 1 without touching the core tools:
| Step | Adds | Why |
|---|---|---|
| 8 — Skill groups | type: skill + SkillTool.from |
Discoverable grouping of MCP tools |
| 9 — Aggregates & ref | aggregates: + ref: |
Share logic across MCP, Skill, and REST |
| 10 — REST adapter | type: rest |
Same logic, HTTP-shaped door for non-AI clients |
Same YAML. Still no code.
⚓ Before you start. Complete Track 1, Steps 1–7. Files live in
ikanos-docs/tutorial/in the public Ikanos repository.
Step 8 — Skill Groups¶
File: step-8-shipyard-skill-groups.yml
Five tools today. Twenty tomorrow. Fifty next quarter. An agent landing on your MCP endpoint sees a flat list and has to figure out on its own which tools matter for planning a voyage versus auditing the fleet. That's noise.
Agent Skills are the table of contents for your toolbox. They group
related tools under a business-facing label (fleet-ops, voyage-ops).
- type: skill
port: 3002
namespace: shipyard-skills
description: "Shipyard skill groups for structured agent discovery"
skills: # alpha3 — keyed map by skill name
fleet-ops:
description: "Fleet management — list, search, and inspect ships"
tools: # alpha3 — keyed map by tool name
list-ships:
from: { sourceNamespace: shipyard-tools, action: list-ships }
get-ship:
from: { sourceNamespace: shipyard-tools, action: get-ship }
voyage-ops:
description: "Voyage planning — create and manage voyages"
tools:
create-voyage:
from: { sourceNamespace: shipyard-tools, action: create-voyage }
No logic duplication — each skill just references existing MCP tools via
from.sourceNamespace + action. Purely additive: your MCP tools keep working
exactly as before.
The SKILL adapter also auto-exposes a read-only HTTP API on port 3002 for
remote discovery (GET /skills, GET /skills/{name}, GET /skills/{name}/download).
Run it¶
curl -L -o step-8-shipyard-skill-groups.yml \
https://raw.githubusercontent.com/naftiko/ikanos/main/ikanos-docs/tutorial/step-8-shipyard-skill-groups.yml
ikanos validate step-8-shipyard-skill-groups.yml
ikanos serve step-8-shipyard-skill-groups.yml
Probe the Skill discovery endpoint:
🧭 What you learned:
type: skill,SkillTool.from, SKILL HTTP API for remote discovery.
Step 9 — Aggregates & Ref¶
File: step-9-shipyard-aggregates.yml
Step 7 defined a three-step chain inside get-ship-with-crew. Step 11 will
chain seven steps for the Fleet Manifest. And the REST adapter (Step 10)
wants to expose get-ship-with-crew too. That's the same logic in three
places.
Aggregates are named, reusable computation units. flows inside
them define the steps. Tools and REST operations reference them with ref:
instead of inlining logic. Each flow declares semantics (safe,
idempotent, cacheable) and the framework derives MCP hints automatically.
aggregates: # alpha3 — keyed map (under capability)
crew-resolver:
display: "Crew Resolver"
flows: # alpha3 — keyed map (was `functions`)
resolve-crew-for-ship:
semantics:
safe: true
idempotent: true
cacheable: true
inputParameters:
imo: { type: string, required: true }
steps: # alpha3 — keyed map
get-ship:
type: call
call: registry.get-ship
with: { imo_number: crew-resolver.imo }
list-crew:
type: call
call: registry.list-crew
resolve-crew:
type: lookup
index: list-crew
match: crewId
lookupValue: "$.get-ship.assignedCrew"
outputParameters: ["fullName", "role"]
mappings:
- { target: imo, value: "$.get-ship.imo_number" }
- { target: name, value: "$.get-ship.vessel_name" }
- { target: crew, value: "$.resolve-crew" }
Tools and REST ops now point to the aggregate:
tools:
get-ship-with-crew:
inputParameters:
imo: { type: string, required: true }
ref: crew-resolver.resolve-crew-for-ship
with:
imo: shipyard-tools.imo
No hints: block. The engine sees safe: true → derives
readOnly: true, destructive: false. idempotent: true → carried through to
MCP ToolAnnotations. cacheable → unlocks response caching.
The output is byte-for-byte identical to Step 7. The spec got dramatically cleaner.
Run it¶
curl -L -o step-9-shipyard-aggregates.yml \
https://raw.githubusercontent.com/naftiko/ikanos/main/ikanos-docs/tutorial/step-9-shipyard-aggregates.yml
ikanos validate step-9-shipyard-aggregates.yml
ikanos serve step-9-shipyard-aggregates.yml
Then, in MCP Inspector at http://localhost:3001, call
get-ship-with-crew(imo: "IMO-9321483") and confirm the response matches
Step 7 byte-for-byte.
🧭 What you learned:
aggregates,flows,semantics,ref, automatic semantics→hints derivation.
Step 10 — REST adapter¶
File: step-10-shipyard-rest-adapter.yml
Not every consumer is an AI agent. The ops dashboard is a plain React app.
The partner logistics company speaks OpenAPI. type: rest exposes HTTP
endpoints mapped to the same consumed operations — or, better, the same
aggregate functions from Step 9.
- type: rest
port: 3003
namespace: shipyard-api
resources:
- name: ships
path: "/ships"
operations:
- { name: list-ships, method: GET, call: registry.list-ships }
- name: get-ship
method: GET
path: "/ships/{imo}"
inputParameters:
- { name: imo, in: path, required: true }
call: registry.get-ship
with: { imo_number: shipyard-api.imo }
- name: get-ship-with-crew
method: GET
path: "/ships/{imo}/crew"
inputParameters:
- { name: imo, in: path, required: true }
ref: crew-resolver.resolve-crew-for-ship
with: { imo: shipyard-api.imo }
Two things to notice:
- REST operations declare HTTP-level parameter placement (
in: path,in: query,in: body) that MCP tools don't need. get-ship-with-crewusesref: crew-resolver.resolve-crew-for-ship— the exact same aggregate the MCP tool uses.
Three expose blocks (MCP, Skill, REST) now coexist in the same capability, all backed by the same consumes and aggregates.
Run it¶
curl -L -o step-10-shipyard-rest-adapter.yml \
https://raw.githubusercontent.com/naftiko/ikanos/main/ikanos-docs/tutorial/step-10-shipyard-rest-adapter.yml
ikanos validate step-10-shipyard-rest-adapter.yml
ikanos serve step-10-shipyard-rest-adapter.yml
Three doors, three smoke tests:
# MCP (port 3001) — via MCP Inspector
npx @modelcontextprotocol/inspector
# Skill catalog (port 3002)
curl http://localhost:3002/skills
# REST adapter (port 3003)
curl http://localhost:3003/ships
curl http://localhost:3003/ships/IMO-9321483
curl http://localhost:3003/ships/IMO-9321483/crew
🧭 What you learned:
type: rest, HTTP verbs and paths,inputParameterswithin: path|query|body,refon REST operations, MCP ↔ Skill ↔ REST coexistence.
Recap¶
You now have one capability that serves three classes of consumer:
- AI agents via MCP
- Skill-aware agents via the SKILL HTTP catalog
- Web/mobile/partner systems via REST
…all from the same consumes adapters and aggregates — no logic duplication.
Move on to Track 3 — Agent Orchestration for the Fleet Manifest capstone.