Releasing Ikanos with Ikanos - Turning a script into an agentic capability Ikanos, an open source framework for Spec-Driven Integration, has automated its own release process by modeling it as a capability, turning a script into an agentic set of MCP tools that an agent can drive. The next version of Ikanos (1.0.0-beta3) will ship using this capability, demonstrating the product's ability to create custom, use-case-shaped MCP servers. The capability wraps GitHub APIs and a Slack webhook, exposing one tool per release step, such as checking open PRs, tagging repos, polling workflow status, merging PRs, dispatching workflows, and posting announcements. What happens when you use your own product for the thing you dread doing most? Every release at our company follows the same ritual, some predefined steps that we have to monitor in case of drift. Although none of it is hard. It’s just repetitive, easy to get out of order under time pressure, and the kind of thing that quietly consumes an afternoon every single cycle without ever feeling like “real work.”. A fair amount of the steps are stable which is exactly what made them start to feel less like a process and more like a spec waiting to be written down. So on a fairly ordinary day, I got this idea: what if each of these steps existed as its own tool, in a catalogue, the way you’d expose any other capability https://shipyard.naftiko.io/ikanos/1.0.0-beta3/concepts/ ? for context a capability is an artifact aka a self-contained YAML document declaring what APIs are consumed and what tools, skills, or resources are exposed This capability would not be a script that hard-codes the sequence, but a set of well-described MCP tools one per step that an agent could pick up and drive on command. Tell it what a release looks like in a SKILL.md , once, and let it call the tools in the right order instead of a person doing it by hand every time. It turns out to be a nice internal dogfooding case for the product itself, the release process was already sitting there, real and repetitive, waiting for exactly the kind of tool catalogue the product is built to create. Why this is a ‘drink your own champagne’ moment? The neatest part isn’t that we automated our own release it’s that the next version of Ikanos will ship using a capability. Ikanos releasing Ikanos. Ikanos https://shipyard.naftiko.io/ikanos/1.0.0-beta3/ is the open source framework for Spec-Driven Integration. It lets you build the MCP server your agent needs, not use the one your vendor ships . Custom, use-case-shaped MCP surfaces integrated from as many public and private APIs as the context requires without the generic-server tax. The idea: describe the release process as a capability Instead of writing another one-off internal script, we modeled the release process as a single Ikanos capability. That means two things happen at once, from one description: Consumes : the capability wraps the handful of systems a release actually touches: a source-control API pull requests, tags, branches, CI runs, releases and a team chat webhook for announcements. Exposes : it re-publishes the individual release steps as a small MCP server, with one tool per checklist item: checking for open PRs, tagging a repo, polling a workflow’s status, merging a specific PR, dispatching a workflow, checking whether a release entry already exists, creating or updating that release’s notes, and posting freeze/lift announcements. Each exposed tool is a thin wrapper around one or two underlying API calls no bespoke automation code, just declarative mappings from a tool’s inputs to the calls it makes and the fields it returns. The capability format also lets you tag each tool as read-only or mutating, and idempotent or not, which turns out to matter a lot once these tools are in the hands of something other than a person clicking carefully. ikanos: "1.0.0-beta3" binds: - namespace: "github-env" description: "GitHub API credentials for the org release automation." location: "file:///./shared/secrets.yaml" keys: GITHUB TOKEN: "github-release-bot-token" RELEASE MCP TOKEN: "release-mcp-server-token" - namespace: "slack-env" description: "Slack Incoming Webhook for canal release announcements." location: "file:///./shared/secrets.yaml" keys: SLACK WEBHOOK PATH: "slack-canal-webhook-path" capability: consumes: - namespace: github type: http baseUri: "https://api.github.com" authentication: type: bearer token: "" resources: pulls: display: "Pull requests" operations: list-open-prs: method: GET inputParameters: repo: { in: path, required: true, type: string } state: { in: query, required: false, type: string } outputParameters: - { name: prs, type: array, value: "$ " } ...refs, commits, actions, releases follow the same pattern - namespace: slack type: http baseUri: "https://hooks.slack.com/services/XXXXXXXXXXX" resources: webhook: display: " canal incoming webhook" path: "" operations: post-message: method: POST inputParameters: text: { in: body, required: true, type: string } body: '{"text":""}' exposes: - type: mcp namespace: release-tools description: "Tools that automate the fixed, deterministic steps of the release process." tools: check-open-prs: description: "List open PRs on a given repo used to verify no PR is left open before release." inputParameters: repo: { type: string, required: true } call: github.list-open-prs with: repo: release-tools.repo state: "open" outputParameters: - type: array mapping: "$.prs" hints: readOnly: true idempotent: true tag-repo: description: "Create and push a git tag vX.Y.Z on a repo's current main HEAD." inputParameters: repo: { type: string, required: true } version: { type: string, required: true } steps: get-sha: type: call call: github.get-branch-head-sha with: { repo: "", branch: "main" } create-tag: type: call call: github.create-tag-ref with: { repo: "", tag name: "v", sha: "$.get-sha.sha" } hints: readOnly: false idempotent: false announce-freeze: description: "Announce on canal that a release is starting and merge freeze on main is enforced across all repos. Use at the start of the release process." inputParameters: version: { type: string, required: true } steps: post: type: call call: slack.post-message with: text: ":lock: Release v starting merge freeze on main is now enforced across all org repos. Please hold merges until the all-clear." hints: readOnly: false idempotent: false announce-freeze-lifted: description: "Announce on canal that the release is complete and merge freeze is lifted. Use at the end of the release process." inputParameters: version: { type: string, required: true } steps: post: type: call call: slack.post-message with: text: ":unlock: Release v complete merge freeze is lifted, main is open again across all org repos." hints: readOnly: false idempotent: false What it looks like from the outside Loading a capability like this into an MCP inspector surfaces every tool immediately its description, its input schema, and those safety hints, all without writing a client. The read-only checks has this PR list changed, has this workflow finished sit clearly apart from the ones that actually move state cutting a tag, merging a PR, publishing a release . That separation is small, but it’s the difference between “safe to call speculatively while figuring out what to do” and “call this exactly once, on purpose.” Talking an agent through a release The plan is to connect the release tools to an agent and simply narrate what a release looks like, the way you’d brief a teammate: - Announce the freeze internally - do this - .. - do that - Announce that the freeze is lifted Rather than scripting this as one fixed sequence, the agent would chain the tools itself, making the branching decision create vs. update based on what the “does a release already exist” check returns, and looping the workflow-status check until it sees a terminal result. No glue code, no bash script babysitting a CI run, no one refreshing a browser tab waiting for green. Can an agent be trusted to chain these steps on its own? Fair question a script is predictable because it’s fixed, so why hand the sequencing to something that improvises? The honest answer is that the sequencing isn’t actually the risky part; the individual actions are, and that’s exactly what the read-only/mutating and idempotent hints are for. An agent can call every read-only tool as many times as it wants while it figures out what to do next there’s nothing to protect against there. The tools that actually change something tagging, merging, publishing are the ones that matter, and each is scoped narrowly enough that a mistaken call does one specific, visible thing - an extra tag, a duplicate release update - rather than something silently destructive. Idempotent tools can safely be retried if a call fails partway through; non-idempotent ones, like merging a PR, are the ones worth a human glancing over the transcript for, at least while this is new. So the trust model isn’t “the agent won’t make a mistake.” It’s “the tool catalogue is designed so that the mistakes it could plausibly make are cheap, visible, and reversible” a property of how the capability is described, not of how well-behaved the agent happens to be on a given day. We’ll likely follow this up with a deeper post on how we think about that boundary for higher-stakes capabilities. There’s also a subtler advantage that only shows up once something goes wrong mid-release. A script that hits an unexpected state, a workflow that failed instead of succeeded, a release that already exists when it wasn’t supposed to, a PR that can’t be merged because main moved, either crashes or, worse, silently takes the wrong branch because nobody anticipated that exact case. Someone has to notice, go read logs, figure out what state things are actually in, and either patch the script or finish the step by hand. An agent working from the same tool catalogue can do a version of that investigation itself: re-run the read-only checks to see what’s actually true right now, reason about why the expected path didn’t hold, and either retry, take a different documented path, or stop and explain exactly what it’s unsure about instead of failing opaquely. It’s not magic and it’s still bounded by which tools exist and what they’re allowed to do but the recovery path goes from “someone digging in and reconstructing what happened” to “ask it what it saw and what it tried.” The general pattern If you’re maintaining any repetitive, multi-system process releases, deploy checklists, incident runbooks, or onboarding steps, this is a reasonable template: wrap the systems you already call as inputs to a capability, expose the discrete steps as tools with honest safety hints, and let an agent or a person, via an inspector drive the sequence instead of a script that only handles the happy path. The smallest version of this is one tool around one API call you already make by hand. Start there. Build your first capability with Ikanos https://shipyard.naftiko.io/ikanos/1.0.0-beta3/?utm source=naftiko-blog&utm medium=blog&utm campaign=releasing-ikanos-with-ikanos&utm content=releasing-ikanos-with-ikanos-blog