Blog

Releasing Ikanos with Ikanos

Farah Trigui ·August 6, 2026
Table of contents

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 ? (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 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:

  1. 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.
  2. 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.”

The release process modeled as an Ikanos capability

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.

tools check-open-prs.svg

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

Further reading