Skip to main content

Every release, agent callable

Yanib ships a spec compliant Model Context Protocol server. Point Claude Desktop, Cursor, ChatGPT desktop, or your own pipeline at it and agents can query, draft, translate, and publish releases.

Quick start

Two minutes to get an agent talking to your Yanib account.

  1. 1

    Add Yanib to your MCP client

    Drop the config below into claude_desktop_config.json (macOS: ~/Library/Application Support/Claude/claude_desktop_config.json).

    {
      "mcpServers": {
        "yanib": {
          "url": "https://yanib.dev/api/mcp",
          "auth": "oauth"
        }
      }
    }
  2. 2

    Restart the client and sign in

    On first connection, the client redirects you to Yanib's consent screen. Pick the scopes you want to grant (see the catalog below) and approve. Yanib mints an access token and hands it back to the client, you never copy and paste anything.

  3. 3

    Ask the agent about your releases

    Try things like “using yanib, what did we ship last week?” or “draft release v2.4.0 from the unpublished PRs”.

Endpoint

POSThttps://yanib.dev/api/mcp
Transport
Streamable HTTP (2026-07-28 MCP spec), stateless, each request stands alone.
Auth
OAuth 2.1 Bearer token. Discovery per RFC 9728.
Content-Type
application/json
Accept
application/json, text/event-stream
.well-known/oauth-protected-resource

RFC 9728 metadata advertising Yanib as the protected resource.

.well-known/oauth-authorization-server

RFC 8414 metadata, auth endpoint, token endpoint, registration endpoint.

Public API, no account needed

Every published story is also readable with zero credentials, so any coding agent can check what changed before, say, bumping a dependency.

POSThttps://yanib.dev/api/mcp/publicno auth · read only

Four tools over public story data, prefixed yanib_public_ so they never collide with the authenticated set: yanib_public_stories, yanib_public_diff_releases (grouped, breaking changes first), yanib_public_diff_capabilities (the structural half of the same check: which endpoints, data models and SDK elements were removed, renamed or deprecated between two tags), and yanib_public_search.

claude mcp add --transport http yanib-public https://yanib.dev/api/mcp/public
/llms.txt

Plaintext discovery doc: what Yanib is, both MCP endpoints, the REST API, and the per story page markdown convention.

/stories/{slug}/llms.txt

Any public story page as clean markdown, releases newest first, entries grouped by change type.

Teach your agent

Two commands install a Claude Code plugin that knows Yanib's release workflow and wires up this MCP server, so the agent learns the draft → review → approve → publish loop, not just the raw tools.

yanib-release-managerskill + MCP · Claude Code

Add the marketplace, install the plugin, then just ask, “draft our release notes.” OAuth connects on first tool use; nothing to copy and paste.

/plugin marketplace add <org>/agent-skills
/plugin install yanib@yanib-agent-skills
Browse the agent-skills repo

Tool catalog

Each tool, one intent. Names prefixed yanib_ so they don't collide when your agent has multiple MCP servers loaded.

  • yanib_inspectreadreleases:read

    Start here. Takes no arguments. Returns the caller's repos with the exact `repo` values every other yanib_* tool accepts, published release counts, unreleased merged PR counts, and the scopes this token grants. Cheap: one query, no AI. Call it before guessing arguments.

    args: none

  • yanib_list_recent_releasesreadreleases:read

    Return the caller's most recent published releases with their story entries. Use this to answer 'what has this team shipped recently'. Optional `repo` narrows to one repository, `since` to an ISO timestamp, `limit` caps the count. Each release includes tag, publishedAt, summary, and a compact list of entries grouped by change type.

    args: repo?, since?, limit?

  • yanib_search_releasesreadreleases:read

    Search the caller's release history for `query`. Use for questions like 'when did we ship dark mode' or 'find releases about the auth rewrite' — prefer this over listing everything. Optional `repo` narrows to one repository. Returns matching releases with the specific entries that matched.

    args: query, repo?, limit?

  • yanib_get_repo_activityreadreleases:read

    Return merged pull requests in `repo` that have NOT yet been rolled into a published release. This is the raw material a `yanib_draft_release` call would consume. Use for 'what would go into the next release'.

    args: repo, limit?

  • yanib_review_releasewritereleases:read

    Run (or fetch the cached) QA review of the release `releaseId` — its draft story checked against the pull requests it consumed. Returns structured findings (missing entries, overclaims, undocumented breaking changes, wording) plus a suggested semantic version bump and rationale. Use it during the draft→approval gate to catch problems before `yanib_publish_release`. Never edits the release, never publishes, never blocks publishing; the human decides.

    args: releaseId

  • yanib_draft_releasewritereleases:draft

    Create a DRAFT release for `repo` with tag `tagName`, using the merged PRs the repo has accumulated since the last published release. Returns the drafted release id plus its story entries. The draft is persisted but stays private — call `yanib_publish_release` to make it public. Fails if no unpublished activity exists, or if the tag already exists on that repo, in which case look for the existing draft rather than inventing a new tag.

    args: repo, tagName

  • yanib_translate_releasewritetranslations:generate

    Translate the story entries of release `releaseId` into the language `lang`. Translations are cached in the database, so repeating a call with the same `lang` returns the stored translation and costs nothing. Returns the translated entries alongside the original entry metadata (changeType, prNumber).

    args: releaseId, lang

  • yanib_generate_social_postwritesocial:generate

    AI draft a Twitter and/or LinkedIn post announcing the release `releaseId`; optional `platform` picks one channel. Returns the post text only. Does NOT post to any platform, pair with the user's connected integrations for actual delivery.

    args: releaseId, platform?

  • yanib_publish_releasedestructivereleases:publish

    Publish the DRAFT release `releaseId`. This is destructive and irreversible from an agent's perspective: it updates the platform release on GitHub/GitLab, sends subscriber emails (which cannot be recalled), fires connected Slack/Discord/webhook integrations, and makes the release publicly visible on its story page. You MUST present the release contents to the user and receive explicit approval in this conversation before calling this tool with `confirmation: true`. Never set `confirmation` on your own initiative.

    args: releaseId, confirmation

  • yanib_list_pr_reviewsreadreviews:read

    Return whether Yanib's PR impact review is switched on for `repo`, what is still missing if it is not, and the most recent recorded review attempts (pull request number, analysis id, state, whether the GitHub check and review were delivered, timestamps). Use it to answer 'is Yanib reviewing this repo' and 'what has it looked at lately'. Operational metadata only: findings and severity need a fresh per-repository access check, so read one review with `yanib_get_pr_impact`.

    args: repo, limit?

  • yanib_get_pr_impactreadreviews:read

    Return Yanib's current impact review for pull request `pullRequestNumber` on `repo`: highest severity, whether the impact lands in the changed code, elsewhere in the same repository, or in downstream repositories, what could not be checked, and the recommended next actions. Use it for 'will this PR break anything downstream' and 'what did Yanib flag on PR #123'. It re-verifies the PR's commits against GitHub first, so a pull request that moved returns a `changed` error instead of a stale answer; pass the returned `analysisId` back to pin a follow-up read. Never posts to GitHub.

    args: repo, pullRequestNumber, analysisId?

  • yanib_get_capability_inventoryreadsurfaces:read

    Return the capabilities Yanib has detected in `repo` — API routes, SDK elements, database models, events — as an inventory of what the repository exposes. Use for 'what endpoints does this repo expose', 'what is in our public API surface', or 'what changed in the API surface recently'. Optional `surface`, `state`, `q`, `limit` and `cursor` narrow and page it. Read only: it never confirms, dismisses or announces anything. Check `ledger` on the response: `capability` includes removals, renames and deprecations; `legacy` is an additions-only log where the absence of a removal proves nothing. If `detectionEnabled` is false the list is empty because detection is switched off for that repository, not because it exposes nothing.

    args: repo, surface?, state?, q?, limit?, cursor?

  • yanib_get_pending_surface_changesreadsurfaces:read

    Return the surface changes in `repo` that are waiting for a human decision — the Surfaces review queue. Use for 'what surface changes need review' or 'is anything waiting on us'. `limit` and `cursor` page it. Each row carries the id a decision would address: `capabilityChangeId` on the capability ledger, `surfaceEventId` on the legacy one, exactly one of them non-null. Read only: confirming or dismissing is a human action taken in the Yanib dashboard, and this tool cannot do it.

    args: repo, limit?, cursor?

  • yanib_get_surface_configreadsurfaces:read

    Return the surface configuration `repo` is actually running — the surfaces the detector will match on the next push, each one's file patterns, `owners:` references and `notify:` list, plus where those notifications resolve to and which surfaces route to nobody. Use it to explain why something was or was not detected, or to look up the exact surface names `yanib_get_capability_inventory` accepts. Set `includeYaml` to also get the re-serialized config. Read only.

    args: repo, includeYaml?

  • yanib_review_surface_changewritesurfaces:review

    Record a NON-announcing decision on one surface change in `repo`: `action: "dismiss"` (the detection was wrong or not worth tracking) or `action: "accept_no_action"` (it was right and needs no follow-up), with a structured `reason` and an optional `note`. Address the row with exactly one of `capabilityChangeId` (capability ledger) or `surfaceEventId` (legacy ledger) — `yanib_get_pending_surface_changes` returns both keys with one non-null, and sending the wrong one for the repo’s ledger is an argument error. Nothing is announced, nothing is emailed, no issue is opened. Decisions are append-only: this appends a row, and a human can supersede it in the Yanib dashboard. Read the change with `yanib_get_capability_inventory` before deciding. To CONFIRM a change, use `yanib_confirm_surface_change` — this tool cannot.

    args: repo, capabilityChangeId?, surfaceEventId?, action, reason, note?

  • yanib_confirm_surface_changedestructivesurfaces:review

    Confirm one surface change in `repo`, addressed by exactly one of `capabilityChangeId` or `surfaceEventId`. Confirming is what makes the change real to everyone else: it announces the change to the repository's configured destinations (Slack, Discord, webhooks) and may open issues in repositories that declared they consume this one. None of that can be recalled. You MUST first present the change to the user — its identifier, surface, change kind and evidence summary, all of which `yanib_get_capability_inventory` returns — and receive explicit approval in this conversation before calling this tool with `confirmation: true`. Never set `confirmation` on your own initiative. To dismiss or accept without announcing, use `yanib_review_surface_change` instead.

    args: repo, capabilityChangeId?, surfaceEventId?, confirmation

  • yanib_review_pr_findingdestructivereviews:manage

    Record a human review of one PR-impact finding — Yanib’s claim that a pull request in `repo` affects a repository that consumes it. Takes `pullRequestNumber`, `findingId`, and a `decision` object of `edge` (is the dependency edge real), `impact` (breaking / behavior_risk / compatible / indeterminate), `action` (what should happen) and a structured `reason`, plus an optional `note`. Reviews are append-only. Most verdicts only record what you decided; ONE does not — `edge: "confirmed"` + `impact: "breaking"` + `action: "coordinate"` opens an issue in the consuming repository, which is a write into somebody else’s repo and cannot be recalled. That verdict therefore requires `decision.confirmation: true`, and you may only set it after the user has seen the finding and approved the issue being opened. Correcting an existing review is a dashboard action; this tool refuses a finding that already has one.

    args: repo, pullRequestNumber, findingId, decision, note?

  • yanib_askwritereleases:read

    Answer a natural-language `question` from Yanib's own evidence: release history, indexed repository summaries, the capability inventory, and available Reverb PR reviews. Use it for open questions — what a repository does, how a subsystem is put together, why something changed — and use `yanib_search_releases` instead for 'when did we ship X'. Optional `repos` (a single `owner/name`) narrows the search; optional `history` carries prior turns so follow-ups resolve. Returns the answer plus the exact sources it used; show those sources. Costs an AI call, so ask one good question rather than several narrow ones. If it returns no sources, that scope is not indexed — it is not evidence that the thing does not exist.

    args: question, repos?, history?

  • yanib_get_releasereadreleases:read

    Read everything Yanib knows about the release `releaseId` in one call: its story entries, its Release Story, its derived shape (how big the release is and which product areas moved), its web visibility, which audience variants exist, and its QA report. `include` picks the sections — `entries`, `story`, `shape`, `visibility`, `audiences`, `qa`, defaulting to entries, shape, visibility, qa — so ask for fewer when you only need one. `audience` re-reads the entries in an engineers/product/customers voice when that variant exists, and says so when it does not. `format: "markdown"` returns a shareable document instead of JSON. Unlike the list and search tools this reads a release in any status, so it is how you inspect a DRAFT before anyone approves it.

    args: releaseId, include?, audience?, format?

  • yanib_set_release_visibilitywritereleases:draft

    Set the web audience of release `releaseId`: `visibility: "PUBLIC"` puts it on the repo's public story page, `"WORKSPACE"` keeps it inside the team. Allowed while the release is DRAFT, PUBLISHED or FAILED. Nothing is re-sent — no subscriber emails, no Slack/Discord/webhook integrations, no platform release update — and the change is reversible. But turning a PUBLISHED release to WORKSPACE takes down a public story page that people may already link to, so ask the user before changing the visibility of a PUBLISHED release. Returns the stored value and the effective one, which falls back to the repo default when the stored value is null.

    args: releaseId, visibility

Human approval. yanib_publish_release carries the MCP destructiveHint annotation and requires an explicit confirmation: true argument. A well behaved client surfaces a confirmation UI to you before calling it; the server rejects the call otherwise.

How auth works

OAuth 2.1 with PKCE and dynamic client registration. No client secrets, no copying and pasting keys.

  1. 1

    Client hits /api/mcp with no token.

    Server responds 401 with WWW-Authenticate: Bearer resource_metadata=...

  2. 2

    Client fetches /.well-known/oauth-protected-resource.

    Sees authorization_servers: ["https://yanib.dev"] and Yanib's supported scopes.

  3. 3

    Client fetches /.well-known/oauth-authorization-server.

    Gets authorization_endpoint, token_endpoint, registration_endpoint. PKCE S256 is mandatory.

  4. 4

    Client POSTs to /oauth/register with its metadata.

    Public client by default, no client_secret, PKCE only. Yanib returns a client_id.

  5. 5

    Client redirects you to /oauth/authorize.

    You sign in to Yanib (or reuse your session), see the consent screen with requested scopes, approve.

  6. 6

    Yanib redirects to your client's redirect_uri with a code.

    The code is single use, short lived, PKCE bound.

  7. 7

    Client POSTs to /oauth/token with grant_type=authorization_code + code_verifier.

    Yanib returns an access_token (1 hour) + refresh_token (30 days). Refresh rotates on every use.

  8. 8

    Client uses Authorization: Bearer <access_token> for MCP calls.

    Access tokens are scoped, tools without the right scope return an error result.

Scopes

Grant the narrowest scope the agent actually needs. You can revoke access anytime from your Yanib settings.

  • releases:readRead releases, entries, search history, and unpublished repo activity.
  • releases:draftCreate AI drafted releases from unconsumed PRs, and edit release settings such as web visibility.
  • releases:publishPublish a DRAFT release. Sends real emails and fires integrations, destructive.
  • translations:generateTranslate release entries into supported languages.
  • social:generateDraft Twitter / LinkedIn copy from a release.
  • reviews:readRead PR impact review setup and the recorded reviews for a pull request. Read-only, never posts to GitHub.
  • surfaces:readRead a repo's detected capability inventory, the surface changes waiting for review, and its effective .yanib.yml. Read only, decides nothing.
  • surfaces:reviewRecord a decision on a detected surface change. Dismissing and accepting are quiet and append-only; confirming announces the change to the repo's Slack/Discord/webhooks and can open issues in consuming repos, destructive.
  • reviews:manageRecord a review of a PR-impact finding. Append-only; the confirmed-breaking-coordinate verdict opens an issue in the consuming repository, destructive.

Testing without a client

The endpoint speaks JSON-RPC 2.0 over HTTP POST. You can drive it end to end with curl if you're debugging your own integration.

Register a client
curl -s -X POST https://yanib.dev/oauth/register \
  -H "Content-Type: application/json" \
  -d '{
    "client_name": "My agent",
    "redirect_uris": ["https://myagent.example/callback"],
    "scope": "releases:read"
  }'
List tools
curl -s -X POST https://yanib.dev/api/mcp \
  -H "Authorization: Bearer $YANIB_MCP_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
Call a tool
curl -s -X POST https://yanib.dev/api/mcp \
  -H "Authorization: Bearer $YANIB_MCP_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{
    "jsonrpc":"2.0",
    "id":2,
    "method":"tools/call",
    "params":{
      "name":"yanib_list_recent_releases",
      "arguments":{"limit":5}
    }
  }'

Design & security

A few opinions baked into the server that we think matter for anyone building on top.

  • One tool = one user intent.

    No broad tools that take a free form params blob. Every tool has a precise Zod schema. Agents pick correctly more often as a result.

  • PKCE S256 is mandatory.

    The MCP 2026-07-28 spec forbids non PKCE flows and token pass through. Public clients (installed apps) never see a client_secret.

  • Compact JSON responses.

    Every tool call inflates the agent's context window. We paginate, trim PR bodies to 500 chars, and return the smallest useful shape.

  • Stateless.

    No session state on our side. Every client gets identical request/response semantics, no matter the region or which load balanced node answers.

Ready to connect an agent?

Sign in to Yanib, connect a repo, then point Claude Desktop, Cursor, or your own pipeline at https://yanib.dev/api/mcp.