Skip to main content
Glama

Server Details

Shared graph-based task tracker for agents, agents’ agents and people. Source-available, free cloud.

Ownership verified
Status
Healthy
Uptime
80.0% over the last 24 hours
Last Tested
Transport
Streamable HTTP · MCP 2025-11-25
URL
Repository
tryskein/skein
GitHub Stars
0
ChatGPT
–
Claude
–

TDQS

A3.9/5.0

Scored across 42 tools

Disambiguation4/5

Most tools have crisp, distinct purposes (claim vs close vs accept vs update vs unclaim are carefully delineated, and list/ready/search/prime carry explicit "when to use this one" guidance). Some friction remains from the many-variants (close/close_many, update/update_many, dep_add/dep_add_many) and from action-multiplexed tools (skein_attachments, skein_component, skein_watchers, skein_user, skein_theme) whose sub-operations overlap conceptually with sibling tools like skein_project_create.

Naming Consistency4/5

Dominant pattern is skein_<verb>_<noun> or skein_<verb>: create/update/close/claim/unclaim/show/list/search/ready/reparent all fit. Deviations are minor but real — noun-only names for action-multiplexed tools (skein_comment, skein_component, skein_attachments, skein_watchers, skein_notifications), plus a stray get_ prefix in skein_get_instructions and skein_get_monitor_script.

Tool Count2/5

42 tools is well past the 25+ threshold; the surface spans issues, deps, comments, commits, projects, members, components, notifications, reminders, attachments, themes and admin users. Many of these (theme, user admin, project_members, attachments) are peripheral to the core issue-tracking purpose but still each expand the selection space an agent must scan.

Completeness4/5

Coverage is genuinely deep: full issue lifecycle (create/update/close/claim/unclaim/accept/reparent), comment CRUD, dependency add/remove/batch, project and component management, notifications, watchers, reminders, and batched variants for bulk work. Minor gaps remain — no issue hard-delete or project delete (archive only), and the server itself notes the full project description is unreachable through any MCP tool.

Publisher details

Operator
SEBO - FZCO · Publisher source
Operator website
Not available
Vendor relationship
First-party · Publisher source
Documentation
Not available
Trust center
Not available
Restrictions
Not applicable

Available Tools

42 tools
skein_acceptA
Idempotent
Inspect

Accept an issue assigned to you — explicit acknowledgement that you've seen the issue, understood its scope and are taking it. Sets accepted_at = now() and accepted_by = caller. The issue's creator and watchers receive a notification. ANSWERS WITH AN ACKNOWLEDGEMENT by default — the id, the applied state and the new version, no issue card and no prose — so the expectedVersion cycle continues without a re-read; pass verbose for the full IssueDetail.

When an abort survives retries while expectedVersion still matches, serialization_retry means no acceptance was written; retry unchanged.

Use this when the issue lands in your inbox (assigned to you, accepted_at IS NULL). Without acceptance, the creator has no guarantee you've actually seen the issue — Skein's inbox model exists precisely to provide that signal.

Only the current assignee accepts — owner rights and the admin role do not. Idempotent: calling on an already-accepted issue is a silent no-op (no duplicate event, no version bump).

Optional expectedVersion enforces optimistic concurrency same as skein_update / skein_close.

Inputs: id (required), verbose, expectedVersion.

skein_attachmentsA
Destructive
Inspect

Files attached to an issue or to one of its comments. Uploading takes two calls — action="upload_url", then action="confirm" once the file has landed in storage; an unconfirmed attachment is invisible to list. Presigned URLs expire after 10 minutes. Read-only actions: list, metadata, get_url.

Inputs: action (required), issueId, filename, commentId, +3 more.

skein_claimAInspect

Take locks for the caller's MCP session using exactly one of id or ids. A batch contains 1..100 unique issue ids and is atomic: every issue is claimed or the whole transaction rolls back; the successful response preserves caller order. Claim transitions status to in_progress atomically. open/deferred → in_progress in the same transaction; started_at is stamped on first entry. Re-claiming the same in_progress issue from the same session is idempotent (no version bump, no event). Locked by another session → already_claimed. Closed issues cannot be claimed — they return cannot_claim_closed; reopen via skein_update first. Standing tasks return cannot_claim_standing — spawn a child (skein_create parentId=) and claim that. already_claimed always reflects a real other holder; a claim that loses a database serialization race returns serialization_retry instead — nothing was written, so retry the call verbatim. A successful claim emits issue.claimed plus issue.status_changed (reason="claim") when status transitioned, and acknowledges with the post-state: id, status=in_progress, lockedBy, the claim stamp and the version (see verbose for the full issue). A BATCH acknowledges with {results:[…one row per input id, in caller order…], summary:{requested, succeeded, failed}} — the same form skein_close_many uses. That envelope is NOT an invitation to partial success: a batch claim is still all-or-nothing, so failed is 0 on every response you will ever see and a failing batch is a tool error with nothing written. Admins may pass assignee to claim on behalf of another user; non-admins setting assignee to anyone other than themselves get forbidden. ANSWERS WITH AN ACKNOWLEDGEMENT by default — the id, the applied state and the new version, no issue card and no prose — so the expectedVersion cycle continues without a re-read; pass verbose for the full IssueDetail.

Inputs: id, ids, verbose, assignee.

skein_closeAInspect

Close an issue: status → closed, locked_by cleared, closed_at + closed_reason recorded, issue.closed (+ issue.unclaimed when it was locked) emitted. reason is required (non-empty after trim) — free-form context for a human reader; progress counts use closeOutcome, never the reason. Caller must hold the lock OR pass force=true (force_unlock: project owner, space owner, instance admin); expectedVersion is required; pinned issues require force=true. Optionally attach the git commits that close the issue via commits[]. ANSWERS WITH AN ACKNOWLEDGEMENT by default — the id, the applied state and the new version, no issue card and no prose — so the expectedVersion cycle continues without a re-read; pass verbose for the full IssueDetail.

If a retried transaction aborts while the declared version still matches, serialization_retry means the close rolled back; retry unchanged.

closeOutcome is required when transitioning to closed; repeating a close without it preserves the previous outcome. Target and outcome edits: skein_get_instructions, "Close outcome".

Re-close is NOT idempotent: every call overwrites closed_reason / closed_at and bumps version. To add commit hashes to an already-closed issue without losing the original reason use skein_commits_attach.

Retiring a standing task is rejected with standing_has_open_children (+ childIds) while any child is not closed; force does not bypass it.

Close-time warnings ride on the response's TOP level in both modes, under the same names: warning (fires when the issue has open children, or when it is closed with no assignee — both reasons joined when both apply) and openChildList (the open children themselves). Do not confuse openChildList with the nested epic.openChildren counter — different things, deliberately different names.

Inputs: id (required), reason (required), expectedVersion (required), force, +4 more.

skein_close_manyAInspect

Close many issues in one round-trip. Each item is processed independently in its own SERIALIZABLE transaction; on row-level failure (version_conflict, locked_by_other, pinned_no_force, ...) the rest of the batch still runs. Inspect results[i].ok per row to find the failures and results[i].error.code to branch on the cause. Result rows preserve input order. A row returning serialization_retry rolled back its close without a version change; resend that row unchanged.

Caps at 100 items per call (matches the REST bulk endpoint). For >100 issues, split client-side. force=true needs force_unlock on each row's project and bypasses lock + version + pinned predicates uniformly across all rows. Per-row reason is required (non-empty after trim) — agents typically pass the same string in every item. Each item may carry an optional commits[] (max 10). Re-close is NOT idempotent — it overwrites closed_reason / closed_at per item; to add commits to already-closed issues use skein_commits_attach per issue. closeOutcome / closeTarget are per item, as on skein_close. closeOutcome is required when transitioning to closed; repeating a close without it preserves the previous outcome. Each row answers closeOutcome, plus closeLink when it named a target.

Inputs: items (required), force.

skein_commentAInspect

Add a comment to an existing issue. The comment is recorded with authorType="agent" so the timeline distinguishes agent-authored notes from human ones. body must be non-empty after trim and at most 100k characters.

The kind parameter (REQUIRED for agent callers) determines the resulting comment's authorKind: kind="directed" → authorKind="agent_directed" (the human user told the agent to post this comment, e.g. "reply to demo-42 with my answer below"); kind="autonomous" → authorKind="agent_autonomous" (the agent posted on its own initiative — handoff note, progress update, side-quest finding). The Humans/Agents filter chips on the comment thread fold human + agent_directed into "Humans" and surface agent_autonomous separately as "Agents". authorType (transport) stays agent regardless of kind.

The response is the created comment MINUS body, plus bodyOmitted:true — every other field of the comment is returned untouched, so this is an omission of one key, not an allowlist. You just supplied the text, and an echoed copy would sit in your context for the rest of the session and be re-sent on every later turn. What that leaves you includes id (for a follow-up skein_comment_update / skein_comment_delete) and version (the expectedVersion token). To read a body back — yours or anyone's — use skein_comment_get (by id) or skein_show (the thread). A write that lost a database serialization race (nothing written) returns serialization_retry — retry the call verbatim.

Inputs: body (required), issueId (required), kind.

skein_comment_deleteA
DestructiveIdempotent
Inspect

Soft-delete a comment. The comment author may always delete their own comment; otherwise the caller must be an owner/admin of the comment's project (a plain member who is not the author gets forbidden). The comment body is retained server-side for audit but blanked on read — the row stays in the thread as a tombstone ("comment deleted"). Idempotent: deleting an already-deleted comment is a no-op success. Optional reason is recorded on the comment.deleted event. A write that lost a database serialization race (nothing written) returns serialization_retry — retry the call verbatim.

Inputs: commentId (required), reason.

skein_comment_getA
Read-onlyIdempotent
Inspect

Fetch one comment by UUID as a full CommentDTO (id, issueId, author, authorUserId, authorType, authorKind, body, createdAt, updatedAt, editedAt, deletedAt, version, mentions). Mentions are hydrated like ListComments (always a non-nil array). Soft-deleted comments return as tombstones: body is "" and mentions is []. Use this after a monitor poll surfaces a truncated bodyPreview (or when you already hold a commentId) instead of re-fetching the whole thread via skein_show.

Errors: invalid UUID → validation_error; missing id OR comment whose project is not visible → comment_not_found (same code — no existence leak).

Inputs: commentId (required).

skein_comment_updateAInspect

Edit the body of a comment you authored. AUTHOR-ONLY — only the user who posted the comment may edit it (a different user, even a project owner, gets forbidden). expectedVersion is the optimistic-concurrency token: pass the version you last read on the comment; if it was edited by another writer in between you get version_conflict with the live currentVersion so you can re-read and retry. A write that instead lost a database serialization race (nothing written, no version moved) returns serialization_retry — retry the call verbatim, do not re-read first. Editing a deleted comment fails with a validation error. Authorship is never rewritten — the edit is recorded only in the comment.edited event actor. @mentions newly introduced by the edit fire the usual mention notification + auto-watch; mentions removed by the edit are left alone.

kind mirrors skein_comment (REQUIRED for agent callers): "directed" = the human told you to make this edit; "autonomous" = your own initiative. It does not change the comment's authorKind — it only classifies the mention fan-out for newly-introduced @mentions.

The response is the edited comment MINUS body, plus bodyOmitted:true — one key omitted, everything else returned untouched (same contract as skein_comment). You just supplied the text, and an echoed copy would stay in your context for the rest of the session. The post-edit version comes back, so a further edit chains straight off it. Read the body back with skein_comment_get or skein_show.

Inputs: body (required), commentId (required), expectedVersion (required), kind.

skein_commits_attachA
Idempotent
Inspect

Attach git commit refs to an existing issue without touching status, closed_reason, closed_at, version or locked_by. Use this — NOT a second skein_close — when the original close forgot commits[] or a closing commit landed afterwards: re-closing silently overwrites the original closed_reason, this tool is the additive path. ANSWERS WITH AN ACKNOWLEDGEMENT by default — the id, the applied state and the new version, no issue card and no prose — so the expectedVersion cycle continues without a re-read; pass verbose for the full IssueDetail.

Issue may be in any status. Re-attaching the same hash is a silent no-op; the issue.commits_attached event is still emitted for the audit trail. Caller must hold the lock OR pass force=true (force_unlock: project owner, space owner, instance admin) — unlocked issues are open to any agent. No expectedVersion: the attach does not bump version.

Inputs: commits (required), issueId (required), force, verbose.

skein_commits_detachA
DestructiveIdempotent
Inspect

Remove git commit refs from an issue without touching status, closed_reason, closed_at, version or locked_by. The counterpart of skein_commits_attach, and the only way back from a hash that should not have been linked — a hash is accepted on its FORM alone (lowercase hex, 7..64), so a fabricated one passes validation and, before this tool, stayed linked forever. ANSWERS WITH AN ACKNOWLEDGEMENT by default — the id, the applied state and the new version, no issue card and no prose — so the expectedVersion cycle continues without a re-read; pass verbose for the full IssueDetail.

Matches on HASH, not on (repo, hash): every row of this issue carrying the hash is removed, whichever repo it was filed under, so a ref attached to the WRONG repo is removable too. Issue may be in any status. Caller must hold the lock OR pass force=true (force_unlock: project owner, space owner, instance admin) — unlocked issues are open to any agent.

The response carries detached (the rows actually removed, each with the repoId you never named) and notFound (the hashes that had no row on this issue) at the TOP LEVEL in both lean and verbose mode. Read them: a typo in the hash produces the same success as a real removal, and only notFound tells the two apart.

Inputs: hashes (required), issueId (required), force, verbose.

skein_componentAInspect

Components of a project — sub-units such as a microservice, a module or an area of code, which issues are attached to so search and reports can group work. Call action="list" before skein_create to pick the right componentIds. Writing (create / update / archive) is for the project owner, space owner or instance admin; archiving is soft and reversible with archived=false. Read-only actions: list.

Inputs: action (required), project (required), id, name, +3 more.

skein_createAInspect

⚠ DEPENDENCIES: use the dependencies array to wire typed edges (blocks / waits-for / related / discovered-from / supersedes / duplicates / parent-child) to existing issues atomically with the create. Do NOT write "depends on X" / "blocks Y" / "requires X" in description, acceptance or notes — prose refs are invisible to the dependency graph, skein_ready gating, the UI Dependencies section and close-time warnings. For a hierarchical child prefer the parentId parameter on this same call; for edges on an EXISTING issue use skein_dep_add. ANSWERS WITH AN ACKNOWLEDGEMENT by default — the id, the applied state and the new version, no issue card and no prose — so the expectedVersion cycle continues without a re-read; pass verbose for the full IssueDetail.

Create a new issue. Two-layer duplicate detection (pg_trgm title similarity + semantic vector rerank) runs natively — do NOT preflight with skein_search or skein_prime to 'check for duplicates'. On a match returns {created:false, duplicates:[...], decisionRequired:true}: decide yourself — retry with force:true if it is distinct, or call again with duplicateOf:"demo-42" (links a duplicates edge). degraded semantics: skein_get_instructions ("Duplicate detection").

kind (REQUIRED for agent callers) sets the issue's source column — see the parameter. The optional sourceContext annotates the trigger further. parentId switches id-generation to hierarchical (parent.N) regardless of project default. id is honoured only when idMode=custom.

Inline dependencies[] (max 50) are created in the SAME SERIALIZABLE tx as the issue insert — any failed edge (cycle, target invisible, target missing, duplicate edge) rolls back the entire create and returns a validation_error / dependency_* code naming the failing index. Cross-project edges are allowed; the caller must already see both projects. The new issue is on the FROM side of each edge.

Assignee default: without assignee, the issue is assigned to YOU (the API key's owner) and accepted, if your personal setting "Assign issues my agent creates to me" is on (the default; switch it in the web UI notification settings). Not for standing tasks, and not under someone else's parent: when the nearest assigned ancestor is another person, or one you cannot see, the issue stays unassigned. Pass unassigned: true when the user asks for an unassigned issue; to drop the assignee later, clear it with skein_update (assignee=""). Being the assignee is not a lock: skein_claim is still needed to start work.

⚠ Field bleed: tool-call wrapper literals (<invoke, <parameter name=, </parameter>) in the prose fields (description / acceptanceCriteria / notes / design) are rejected with validation_error (code: tool_call_syntax_leaked) — a hit means you closed a parameter block with the wrong tag. Closed set + the look-alikes that are NOT rejected: skein_get_instructions ("Text fields — forbidden literals").

Inputs: type (required), title (required), project (required), id, +19 more.

skein_dep_addAInspect

Add a typed dependency edge between two issues (blocks, waits-for, related, discovered-from, supersedes, duplicates, parent-child). The PRIMARY way to express ANY relationship between issues — always this or skein_create({dependencies:[...]}), never a free-text ref ("depends on X", "see also Y", "requires Z") in description / acceptance / notes / design: prose is invisible to the dependency graph, skein_ready gating, the UI Dependencies section and close-time warnings.

parent-child only ADDS a parent (parent_already_set if one exists); prefer parentId on skein_create for new children, skein_reparent to move an existing issue, skein_dep_remove to drop an edge.

gate is accepted only on type=waits-for, optional there; it is stored but not evaluated — readiness waits for to itself to close, not for its children. Cross-project edges are allowed within one space if you can see both projects; a target of another space or of a project you cannot see answers issue_not_found, exactly as a nonexistent one; a cycle returns dependency_cycle. Type table and direction semantics: skein_get_instructions ("Dependency edge types").

Inputs: toId (required), type (required), fromId (required), gate.

skein_dep_add_manyAInspect

Add many typed dependency edges in one round-trip. Each item is independent — direction + type semantics match skein_dep_add. Cycle detection runs per-edge against the live graph state, so a batch of [A→B, B→A] of type=blocks persists A→B and fails B→A with dependency_cycle: order in items[] matters. Caps at 100 edges per call; result rows preserve input order. Per-row error codes: validation_error, issue_not_found (also for a target of another space or of a project you cannot see), dependency_already_exists, dependency_cycle, parent_already_set, serialization_retry (retryable), dependency_target_not_visible_yet (retryable), batch_deadline_exceeded / request_cancelled (the row did not report a commit — resend it; an edge that landed answers dependency_already_exists), internal_error.

parent-child only ADDS a parent (parent_already_set if one exists); prefer parentId on skein_create for new children, skein_reparent to move an existing issue, skein_dep_remove to drop an edge.

Inputs: items (required).

skein_dep_removeA
Destructive
Inspect

Remove a typed dependency edge — the inverse of skein_dep_add and the cleanup path when an edge was added by mistake or a relationship no longer holds. Identifies the edge by the composite (fromId, toId, type) and writes a dependency.deleted audit row.

To MOVE a parent-child edge (re-parent an issue) use skein_reparent instead — it atomically replaces the parent. skein_dep_remove only deletes.

Returns dependency_not_found when no edge matches the tuple (not a silent success — a stale dep list is surfaced so the caller re-reads).

Refuses with close_outcome_link_required (nothing removed) when the edge is the LAST one behind a close outcome: the last duplicates edge FROM an issue closed as duplicate, or the last supersedes edge TO an issue closed as superseded. The error names that issue only when you can see it. Change that issue's closeOutcome or reopen it first; with another edge of the same type and direction left, the removal goes through.

Inputs: toId (required), type (required), fromId (required).

skein_drop_delegatedA
Idempotent
Inspect

Drop this issue from your Delegated tab: you stop seeing it there AND stop receiving bell notifications. Use it when you have handed off the work and don't need to track it anymore. ANSWERS WITH AN ACKNOWLEDGEMENT by default — the id, the applied state and the new version, no issue card and no prose — so the expectedVersion cycle continues without a re-read; pass verbose for the full IssueDetail.

Effect: appends your user id to the issue's dismissed_by array (sticky — persists across re-assignments unless you become the current assignee again, in which case it's cleared automatically). Also unsubscribes you from the issue exactly like skein_watchers(action="unwatch"): IF you are subscribed to it, the subscription is deleted and an opt-out stops the automatic paths (assignment, reopen) from re-subscribing you; if you are NOT subscribed (for example, auto-unsubscribed when the issue closed), no opt-out is recorded, so a later reopen or assignment can subscribe you again. Either way the issue's card leaves your Watching column.

Idempotent: a second call on an issue you've already dropped is a silent no-op — no duplicate event, no version bump. The acknowledgement carries dismissed — YOUR membership in dismissed_by after the call (true here) — because neither status nor version moves, so it is the only proof the write applied.

To resume, call skein_resume_delegated. To resume bell notifications only, call skein_watchers(action="watch") (the two intents are decoupled — undismissing the delegation does NOT re-arm notifications).

Inputs: id (required), verbose.

skein_epic_createAInspect

Atomically create an epic and 0..50 child issues under it in a single SERIALIZABLE transaction. Use skein_epic_create when decomposing a feature, story, or chunk of work into 3+ sub-tasks that should be tracked together. Children are created in input order; each child's parentId is automatically set to the newly-allocated epic id, so child ids land as epicId.1, epicId.2, … regardless of the project's default id-generation mode. ANSWERS WITH AN ACKNOWLEDGEMENT by default — the id, the applied state and the new version, no issue card and no prose — so the expectedVersion cycle continues without a re-read; pass verbose for the full IssueDetail.

For an EMPTY epic (no children to plan up front), use the plain skein_create with type="epic" instead — this tool's value is the atomic decomposition, not the epic itself.

Atomicity: any child failure (validation, FK, race) rolls back the ENTIRE batch — the epic vanishes too. There is never a half-built decomposition on disk. A batch that lost a database serialization race after every retry (nothing written) returns serialization_retry — retry the call verbatim. On success the response is an ACKNOWLEDGEMENT, not a set of issue cards: epic and one children entry per child in input order, each carrying the identifier, the applied state and the version — enough to continue an expectedVersion cycle without re-reading — and no issue prose at all. Pass verbose for the full IssueDetail of the epic and of every child, or read one of them later with skein_show.

Children skip the layer-1 / layer-2 duplicate gate that skein_create runs by default — siblings under one epic often have legitimately similar titles ("write tests for X", "write tests for Y") and decision_required mid-decomposition would defeat the batch. The epic itself is NOT duplicate-checked either; callers wanting a duplicate-aware epic should fall back to skein_create + per-child skein_create calls.

kind is REQUIRED for agent callers (same semantics as skein_create): "directed" = user-requested, "autonomous" = agent side-quest. The value maps to source="agent_directed" / source="agent_autonomous" and propagates to every child (so the audit trail is uniform across the decomposition). Web UI callers force source=human and may omit kind.

Assignee default: without assignee, the issue is assigned to YOU (the API key's owner) and accepted, if your personal setting "Assign issues my agent creates to me" is on (the default; switch it in the web UI notification settings). Not for standing tasks, and not under someone else's parent: when the nearest assigned ancestor is another person, or one you cannot see, the issue stays unassigned. Pass unassigned: true when the user asks for an unassigned issue; to drop the assignee later, clear it with skein_update (assignee=""). Being the assignee is not a lock: skein_claim is still needed to start work. The assignee of the epic and of each child is decided separately, by its own assignee / unassigned; the epic's values do not propagate to children, and the epic created by this call counts as their parent for the someone-else's-parent rule.

Inputs: title (required), project (required), kind, notes, +13 more.

skein_get_instructionsA
Read-onlyIdempotent
Inspect

Returns the long-form Skein agent guide (~15 KB markdown). Call once at session start, cache for the session — content does not change between calls. Pass project (alias or UUID) to receive a project-tagged copy.

Inputs: project.

skein_get_monitor_scriptAInspect

Set up a live notification monitor on an issue for a headless agent. One call: (1) subscribes you as a watcher of the issue (subtree=true also covers every current and future subtask); (2) mints a scoped, read-only skm_ monitor token (it can only poll) tied to your calling API key — it stays valid while the runner keeps polling, lapses after 6h without a successful check (the renewal window, configurable on the server) and never lives past 12h after minting; (3) returns monitorId (the handle to stop this monitor) and a ready-to-run bootstrap command that streams new notifications (new comments AND task closure by default) into your context near-real-time via an autonomous Python runner. The runner keeps retrying through server or network trouble and stops by itself when the issue is closed.

SECURITY: the token is returned in the token field, NOT in the bootstrap command. Write token to the 0600 file named by tokenFilePath (use the Write tool) and run bootstrap as-is — the runner reads the token from that file. Do NOT paste the token into a shell command (it would leak into argv / shell history).

Inputs: id (required), subtree, eventTypes.

skein_listA
Read-onlyIdempotent
Inspect

Paginated list of issues. BY DEFAULT a page is 25 rows (ceiling 100), and each row is the named lean set — id, title, status, type, priority, assignee, labels, isBlocked, parentIdCurrent, version, updatedAt, childrenCount, unclosedChildrenCount — NOT the issue card: no description, design, acceptance or notes. projectId joins that set in cross-project mode only. Named means named: keys outside the set are ABSENT, componentIds, createdBy, createdAt, acceptedAt, pinned and epicProgress among them — ask for those explicitly with fields (an unknown name is REFUSED and the error names it, so a typo cannot silently widen or empty the row); read one issue whole with skein_show.

Two modes:

  • Project-scoped (default): pass project — paginated list of issues in that one project, with status / priority / assignee / labels / parent / source / text / pinned filters.

  • Cross-project: pass crossProject: true (omit project) — paginated list of every issue visible to the caller across every project they can see. Supports an FTS q over title + description + acceptance + design + notes (with optional semantic rerank when the embedder is available), plus the status / type / origin / assignee / createdBy / acceptedAt filters from the project-scoped path. Returns total (exact count) on every page so the caller knows the visible horizon. The cursor field from the response is opaque; echo it back unchanged on the next call.

Inputs: q, text, type, limit, +17 more.

skein_moveA
Read-onlyIdempotent
Inspect

There is no in-place project move in Skein. This tool is a documented signpost: it does NOT mutate any state, it returns a pre-filled kit for the canonical copy+supersedes+close workflow. Inputs: issueId (source) + targetProject (handle). The output has one shape, and it is this by default and by design: the source card WITHOUT the fields the prepared call already carries (title / description / type / priority and, when set, assignee / acceptanceCriteria / design / notes / labels — each of the source's texts appears in the response exactly once, in nextSteps[0].args; read the whole card with skein_show), the target project's components with naive name-match hints against the source title/description, and two ready-to-call nextSteps — skein_create in target (with a supersedes dependency edge on source) and skein_close on source. An unassigned source is copied with unassigned: true, so the copy stays unassigned whatever your «assign to me» setting says; an assigned source carries its assignee as an explicit one. The agent picks a componentId from targetComponents (or leaves the array empty for a project-wide task), fills in the close reason and closeTarget placeholders with the id returned by skein_create, then makes the two calls (the close answers closeLink:"existed" — step 1 made the edge). The new issue gets a fresh hierarchical id; the source closes and the supersedes edge is the audit trail. Why no in-place move: the hierarchical id is embedded in git commits, chats, messages between agents, and other external refs — renumbering would break them, and swapping project_id while keeping the id creates visual incongruity in the target's listing. Copy+supersedes is the honest mechanism that preserves provenance without breaking external pointers.

Inputs: issueId (required), targetProject (required).

skein_notificationsA
Idempotent
Inspect

Your notification inbox — the bell feed of what happened on the issues and projects you watch. Everything here is per-user: you only ever see and change your own rows. By default a row is a summary, not the change itself: long old/new values are cut to fit the row budget and marked valuesTruncated when they were (see below for where the full value lives).

action="inbox" reads the feed (scope unread | all | archived, cursor-paginated); its first page of unread / all also lists your pending space invitations (no token or link — a person accepts them in the web UI). Only rows of projects you can access now are shown. Four actions clear unread state at four widths — mark_read for one row, mark_read_issue for everything on one issue including its comments, mark_read_card for everything on one Watching card (the issue's own notifications plus its descendants' when you watch it with its subtree — inside and outside the Watching window; answers {"marked": N}), mark_read_all for the whole backlog (optionally scoped to one project). archive moves one row out of the inbox into the archive view; read it back with inbox + scope="archived". Read-only actions: inbox.

Retention: notifications are kept NOTIFICATIONS_RETENTION_DAYS days (90 by default). A background sweep, at least once an hour, deletes a row that is not a mention and is older than that window, read or not, archived or not — EXCEPT a row whose web push is still owed (queued or being delivered): it stays, and is visible in the feed, until the delivery is over. A mention is never deleted, whatever its age.

What an inbox row does NOT carry in full: on a field-change notification the oldValue / newValue pair is cut to fit the row's size budget, and a row whose values were actually cut carries valuesTruncated: true. A row without that key was not cut, so the marker means «there is more», never «this might be all». The two halves of the pair then differ, and the difference decides whether reading on is possible at all. newValue is the entity's CURRENT state, so the route is the entity the row points at: read the issue with skein_show, or, for a comment notification, that comment with skein_comment_get(commentId). oldValue has NO route: the previous value is not on the card (it was overwritten) and no MCP tool returns it — skein_show(includeEvents:true) names the field that changed and who changed it, not what it held before. On that half the truncated preview is all there is; when you need the whole of a long old value, open the issue's activity in the web UI. The row's summary also has the entity reference removed where the row already names the entity, so the same id is not carried twice.

Inputs: action (required), limit, scope, cursor, +3 more.

skein_primeA
Read-onlyIdempotent
Inspect

Session priming: returns caller identity (user + agent), the caller's in-progress issues, top ready (unblocked) issues assigned to or available for the caller, currently blocked issues, and recent activity events the caller is involved in. Call this ONCE at the start of a working session before any other Skein tool — the response is the agent's orientation snapshot. By default the three issue lists carry the LEAN row described below and never the issue card, and the scan spans every project you can see; optional project narrows every section to one.

What the rows are, because this tool loads on every session and it is worth knowing before you build on it: all three issue lists carry the same LEAN row skein_list returns, here in cross-project form — id, title, status, type, priority, assignee, labels, isBlocked, parentIdCurrent, version, updatedAt, childrenCount, unclosedChildrenCount, projectId — and never the issue card. Keys outside that named set are absent on all three lists, componentIds among them. There is no fields here and no verbose mode: read one issue whole with skein_show, or take the wider row from skein_list, whose fields can name the columns this tool never carries.

One documented exception on top of that, and it is an ABSENCE rather than an empty value: rows in readyForMe carry NO labels key at all. That list comes from the readiness query, which does not load labels (the join would make it N+1). Returning an empty array would assert the issue HAS no labels — a different and usually false statement — so the key is absent instead, and «not supplied here» stays distinguishable from «none». Read labels with skein_show on the issue, or with skein_list, which does load them. Everything else on that row is present, isBlocked included: it is always false there, because unblocked is what the query selected on.

Inputs: limit, project, component.

skein_project_createAInspect

Create a new project. The calling user becomes the initial owner. idPrefix is the per-project token used to namespace issue ids (e.g. "demo" gives ids like "demo-42"); when omitted it's auto-derived from the name. defaultIdMode selects the id-generation strategy for issues (counter|hash|custom; default counter). Optional aliases and gitRepos are inserted atomically with the project — a collision (alias already taken, name/id_prefix already taken) rolls back the whole create. The project goes into a space: space names it (a short id from skein_whoami); when omitted, the one space you can create projects in is used; if there are several, the call is refused with space_required listing them. A space you can see only as the instance admin is refused with not_space_member: add yourself as a member first.

Inputs: name (required), space, aliases, gitRepos, +4 more.

skein_project_listA
Read-onlyIdempotent
Inspect

List projects visible to the calling user. BY DEFAULT a page is 50 rows (ceiling 100), and each row's description is cut to the first 200 characters. When a description was actually cut the row carries descriptionTruncated: true; a short or empty description carries no such key, so the marker means «there is more», never «this might be all». The full text is not reachable through any MCP tool today — this list is the only MCP surface that renders it — so treat the preview as the orientation aid it is and open the project in the web UI when you need the whole thing.

Useful for orientation when an agent doesn't yet know the project handle. Pair with skein_project_resolve when the user types a fuzzy name. Honours archived to include archived projects in the result. When q is set, items are ordered by relevance score (FTS + optional semantic rerank) instead of recency.

Inputs: q, limit, cursor, archived.

skein_project_membersA
Destructive
Inspect

Who is on a project and with which role ('owner' | 'member'). Reading the roster is open to any member of the project; adding and removing require a project owner (the space's owner and the instance admin included), except that a member may remove themselves (self-leave). Adding is idempotent — re-adding with the same role writes nothing. A member who has access to the project through its space cannot be removed (member_via_space): switch the project's visibility to members or remove them from the space. Read-only actions: list.

Inputs: action (required), project (required), role, userId, +1 more.

skein_project_resolveA
Read-onlyIdempotent
Inspect

Resolve a free-form project handle (alias, name, repo URL, or fuzzy/typo input) into a canonical project. Returns match: {id, name, ...} when the resolver is confident, or match: null plus a candidates list of up to 10 alternatives when ambiguous. Use before any tool that takes a project arg if the input came from a human ("demo-service", "the api one", etc.).

Inputs: q (required).

skein_project_updateA
Destructive
Inspect

Wide-scope patch for an existing project (its owner, the owner of its space or the instance admin only). Optional fields: name, description, gitlabGroup (empty string clears), archived (true|false), aliasesAdd, aliasesRemove, gitReposAdd, gitReposRemove (by repo UUID). At least one must be present. Every change lands in one SERIALIZABLE tx — a single bad alias collision or repo removal that hits an in-use repo rolls back the entire patch. visibility (space|members) switches who sees the project: members → space gives it to every member of the space, space → members takes it from everyone without a project membership. A project never changes its space: a space value is refused with space_change_forbidden and nothing changes. Returns the updated ProjectDTO.

Inputs: project (required), name, space, archived, +7 more.

skein_readyA
Read-onlyIdempotent
Inspect

List unblocked open issues for a project, ordered by priority (P0 first) then most-recently-updated. "Ready" means status=open AND no open blocks/waits-for dependencies. By default — and in every mode, there is no other one — a row is the lean summary: no description, no comments, and two keys missing outright (below). Pair with skein_show on the top hit to start work.

Rows carry NO labels and NO componentIds keys: this query does not load them (the aggregate joins would make it N+1), and returning an empty array would assert that the issue has none. The keys are absent rather than empty so you can tell «not supplied here» from «none». Read both with skein_show on the issue — its card carries them. skein_list carries labels in its default row, but NOT componentIds: there you have to name it, skein_list(fields:["componentIds"]). There is no isBlocked key here either, and that one needs no lookup: every row on this list is unblocked — that is the predicate the list selects on.

Inputs: project (required), type, limit, assignee, +1 more.

skein_remindA
Destructive
Inspect

Personal reminders on issues — your own alarm clock, invisible to everyone else. When remindAt passes, the scheduler pings you with a bell notification and the issue shows up in your ringing-reminders lens; state is derived from remindAt, armed (future) vs ringing (due), and a past remindAt rings on the next scheduler tick.

Multiple reminders per issue are allowed, so snooze and dismiss need a target: pass reminderId (from set or list), or issueId when you have exactly one active reminder on that issue — with more than one the error lists the candidates and you retry with reminderId. Read-only actions: list.

Inputs: action (required), by, note, state, +6 more.

skein_reparentA
Destructive
Inspect

Move an EXISTING issue under a new parent. This is THE tool for re-parenting — skein_dep_add(type="parent-child") only ADDS a parent and rejects a second one with parent_already_set; setting the parent at creation uses parentId on skein_create. skein_reparent atomically replaces the issue's single parent-child edge in one SERIALIZABLE transaction (self-healing a double-parented child to exactly one edge), bumps the issue version, and emits issue.reparented plus the per-edge dependency.deleted / dependency.created audit rows. ANSWERS WITH AN ACKNOWLEDGEMENT by default — the id, the applied state and the new version, no issue card and no prose — so the expectedVersion cycle continues without a re-read; pass verbose for the full IssueDetail.

Required: id (the issue to move), newParentId (the new parent), and expectedVersion for optimistic concurrency. On version mismatch returns {error:"version_conflict", currentVersion:<int>} — re-read with skein_show and retry. The new parent must be in the SAME project (reparent_cross_project otherwise) and must not already be a descendant of the issue (dependency_cycle). An issue cannot be its own parent (reparent_self).

NOT lock-gated: reparent moves a graph edge (like skein_dep_add / skein_dep_remove), so a lock held by another session does not block it — concurrency is guarded by expectedVersion alone.

NOT idempotent: re-running with the same newParentId still performs a real move (DELETE+INSERT) and bumps the version — compare parentIdCurrent first if you want to avoid a no-op move. On success returns the acknowledgement (id, status, the new version); pass verbose for the full IssueDetail.

Post-action summary: report ✓ <id> re-parented under <newParentId> so the user can scan the move.

Inputs: id (required), newParentId (required), expectedVersion (required), verbose.

skein_resume_delegatedA
Idempotent
Inspect

Undo skein_drop_delegated — bring this issue back to your Delegated tab. Removes your user id from the issue's dismissed_by array so the Delegated predicate stops filtering it out. ANSWERS WITH AN ACKNOWLEDGEMENT by default — the id, the applied state and the new version, no issue card and no prose — so the expectedVersion cycle continues without a re-read; pass verbose for the full IssueDetail.

Does NOT re-arm bell notifications: the unsubscribe done by skein_drop_delegated (and its opt-out, when one was recorded) stays in effect until you call skein_watchers(action="watch") separately. Two distinct intents — Delegated lens visibility vs bell-notification flow — and resume only restores the first.

Idempotent: calling on an issue you haven't dropped is a silent no-op. The acknowledgement carries dismissed — YOUR membership in dismissed_by after the call (false here) — because neither status nor version moves, so it is the only proof the write applied.

Inputs: id (required), verbose.

skein_showA
Read-onlyIdempotent
Inspect

Read one issue by id. BY DEFAULT you get: the issue card, dependency COUNTS (depsCounts) and no edges, child counters, and the last 3 comments. Edges of a named type, the thread index and the event feed are opt-in — each is named below. Use this when you have an id; for browsing without one use skein_list. id must be the full hierarchical id (e.g. "demo-42" or "demo-42.1") — bare numbers are rejected.

Navigating DOWN a tree without pulling the graph: read the counters here (unclosedChildrenCount, or depsCounts.down["parent-child"]), then list the children with skein_list(parentId=), then ask for the edges you actually need with skein_show(depsTypes:["parent-child"]). Each step is bounded; the graph dump is not.

Comments (when includeComments=true, the default):

  • Default returns the last 3 comments ASC (to save tokens).

  • commentsTotal / commentsReturned / commentsTruncated are always present (including 0/false on empty threads).

  • commentsLimit:0 returns the thread INDEX — one entry per comment (id, author, date, first 80 characters, and a per-entry truncation flag), capped at 200 most recent of the population — plus the last 3 comments in full. Read any one of them whole with skein_comment_get(id); to get N bodies at once ask for commentsLimit:N.

  • commentsSince (RFC3339) is a coarse exclusive lower bound on createdAt (not a lossless cursor).

  • commentsTruncated means the thread's ENUMERATION is not known to be complete — NOT that some bodies were withheld. It is set when fewer entries were listed than the population holds, AND whenever the listing hit the index cap (200): a listing exactly as long as the valve that produced it is reported incomplete regardless of commentsTotal, because total and the page are read in two statements and a thread that grew between them would otherwise pass a CUT listing off as whole. So on a thread of exactly 200 comments, commentsLimit:0 returns returned == total with commentsTruncated=true — the safe direction, not a contradiction. On an unfiltered show with commentsTruncated=true, commentsLimit:0 lists every comment up to that cap. When includeComments=false: comments is null, meta keys are absent, and limit/since params are ignored (even if invalid).

Description: pass includeDescription:false for the cheap re-read of version before a write — the description key then arrives as null (present, not absent); the other prose fields are unaffected.

Dependencies: the default is depsCounts — a direction × type × status aggregate (up = parent side, down = children side, plus an unavailable bucket per cell for edges whose other end you cannot see) — and NO edges. A non-zero unavailable means the counts cannot answer "is this branch finished". Ask for edges by naming types in depsTypes; includeDeps:false drops the block entirely — including the card's childrenCount / unclosedChildrenCount, which ride with it. The deps key is no longer guaranteed to be present.

Inputs: id (required), depsTypes, includeDeps, commentsLimit, +4 more.

skein_themeA
Destructive
Inspect

The caller's visual theme — colours, fonts, sizes, spacing. Call action="instructions" FIRST when the user asks for visual changes ("change the color", "another font", "make it darker", "like Alice's"): it returns the canonical fork → preview → confirm → persist workflow and the onboarding script, and improvising without it is the documented way to get this wrong. Built-in light/dark are read-only, so editing always starts with action="fork". Read-only actions: instructions, describe.

Inputs: action (required), name, source, themeId, +1 more.

skein_unclaimAInspect

Release the caller's lock on an issue AND atomically transition status to the caller-declared nextStatus. Three valid intents: nextStatus="in_progress" = handoff (status stays in_progress so a teammate can claim and continue; pair with a comment describing what's done); nextStatus="open" = give up (status reverts to open so the issue is visible again in skein_ready); nextStatus="deferred" = punt (status → deferred; set defer_until via a follow-up skein_update if needed). nextStatus="closed" is rejected — use skein_close instead, which carries reason + commits[]. reason is required (non-empty after trim) and lands on both the issue.unclaimed event and (when status actually changed) the issue.status_changed event. Idempotent on an already-unlocked issue (returns the issue post-state unchanged; nextStatus is ignored — use skein_update directly if you want to flip status without a lock). Caller must hold the lock or pass force=true (force_unlock: project owner, space owner, instance admin; others get force_unlock_forbidden); event reason becomes " (force-overridden)" on the force path. ANSWERS WITH AN ACKNOWLEDGEMENT by default — the id, the applied state and the new version, no issue card and no prose — so the expectedVersion cycle continues without a re-read; pass verbose for the full IssueDetail.

Inputs: id (required), reason (required), nextStatus (required), force, +1 more.

skein_updateAInspect

Patch an existing issue. Supports field updates (title / description / type / priority / assignee / due dates / design / acceptance / notes / external ref / source context / estimated minutes), status transitions, component-set replace, and label add/remove — all in one tool call. Required: id (full hierarchical) and expectedVersion for optimistic concurrency. On version mismatch returns {error:"version_conflict", currentVersion:<int>}. Locks are honoured: caller must hold the lock or pass force=true (force_unlock: project owner, space owner, instance admin). This tool does NOT touch the lock — to release the lock use skein_unclaim(id, nextStatus, reason) (atomic over lock+status) or skein_close (terminal close). Multi-op note: when the call sets a mix of fields, status, components, and labels, each sub-operation is committed independently — a failure mid-way leaves a partial state and you should re-fetch with skein_show before retrying. ANSWERS WITH AN ACKNOWLEDGEMENT by default — the id, the applied state and the new version, no issue card and no prose — so the expectedVersion cycle continues without a re-read; pass verbose for the full IssueDetail.

A transaction abort with an unchanged declared version returns serialization_retry. Earlier sub-operations may have committed: re-read and resend only unapplied changes.

closeOutcome is required when transitioning to closed; repeating a close without it preserves the previous outcome.

Text patches (description, design, acceptanceCriteria, notes, externalRef, sourceContext): omitted leaves unchanged; null or an empty string clears. Other non-string values are refused before any write with validation_error, code text_field_not_string and field naming the argument.

⚠ Field bleed: tool-call wrapper literals (<invoke, <parameter name=, </parameter>) in the prose fields (description / acceptanceCriteria / notes / design) are rejected with validation_error (code: tool_call_syntax_leaked) — a hit means you closed a parameter block with the wrong tag. Closed set + the look-alikes that are NOT rejected: skein_get_instructions ("Text fields — forbidden literals").

Inputs: id (required), expectedVersion (required), type, dueAt, +21 more.

skein_update_manyAInspect

Patch many issues in one round-trip. Each items[i] carries its own id + expectedVersion + the same partialPatch shape that skein_update accepts (title / description / type / priority / assignee / componentIds / labelsAdd / labelsRemove / status / reason / dueAt / deferUntil / design / acceptanceCriteria / notes / externalRef / sourceContext / estimatedMinutes). The N patches are independent — passing the same value in every item gives mass-edit semantics. Text patches (description, design, acceptanceCriteria, notes, externalRef, sourceContext): omitted leaves unchanged; null or an empty string clears. Other non-string values are refused before any write with validation_error, code text_field_not_string and field naming the argument. Refusal applies only to the offending row. Each row runs the full skein_update chain in its own SERIALIZABLE sub-transactions per step; mid-chain failure on one row leaves THAT row in partial state (earlier sub-ops are NOT rolled back — this matches single-tool behaviour) but does not affect the rest of the batch. Inspect results[i].ok / results[i].error.code per row. A row stopped by batch_deadline_exceeded / request_cancelled while in progress may have committed earlier steps: re-read it and resend only what is missing, with the live version. A row may return serialization_retry after a sub-operation rolls back; earlier changes in that row may have committed. Re-read and resend only missing changes.

closeOutcome is required when transitioning to closed; repeating a close without it preserves the previous outcome. No per-row closeTarget: close an OPEN issue as duplicate / superseded with skein_close / skein_update. Caps at 100 items per call. force=true requires admin and applies uniformly to every row. reviewRequired is NOT accepted here: the review flag travels its own path (it moves neither version nor updated_at, so a batch row could not report it). A row carrying it is rejected with validation_error and writes nothing — use skein_update, one call per issue.

Inputs: items (required), force.

skein_userAInspect

Instance-wide user accounts. ADMIN ONLY — every action returns not_admin to a non-admin caller, before any database access. action="update" covers both the patch (name / role / password) and the deactivate / reactivate flip: deactivating cascades into API key revocation in the same transaction and is reversible with deactivated=false. Role admin is refused with admin_role_disabled while the instance admin role is disabled on the installation. action="space_add" makes the user a member of a space at once (no invitation, no consent). Read-only actions: list.

Inputs: action (required), name, role, email, +10 more.

skein_watchersAInspect

The caller's own notification subscriptions, at three scopes: one issue, a whole project, or a saved filter (custom watcher). Automatic subscriptions: the assignee of an issue (except one filled in at create by the "assign agent-created issues to me" setting); its creator (the human who owns the API key) only for issues created by hand or as a TOP-LEVEL agent_directed issue — an issue your agent files under a parent, or with source=agent_autonomous, does not subscribe you. Unsubscribing from an issue you watch records an opt-out: the auto-watch paths (creator, assignee, reopen) will not re-subscribe you to that issue until you watch it again with action="watch", and the issue's card leaves your Watching column. Per-user cap: 10 filter watchers. Also stops one of your task monitors (action="monitor_stop", by the monitorId skein_get_monitor_script returned). Read-only actions: list, filter_list.

Inputs: action (required), name, filter, issueId, +5 more.

skein_whoamiA
Read-onlyIdempotent
Inspect

Returns the current MCP session identity: the resolved user, the agent (this MCP session), the spaces the user is in (their short ids are the space of skein_project_create) and the projects the user can see with their role and space. Read-only, idempotent. Call once at session start so future tool calls have the user's permission context cached on your side.

Inputs: no named parameters.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      B
      quality
      B
      maintenance
      Self-hosted shared brain for AI agents: Git-versioned Markdown knowledge plus a task queue with atomic claims, per-space ACL, audit log and human approval, so Claude, ChatGPT and other agents can hand work to each other.
      27
      AGPL 3.0
    • A
      license
      A
      quality
      B
      maintenance
      Self-hosted task tracker and MCP server for AI coding agents. Append-only case files preserve decisions, failed attempts, questions, and check results across sessions. A live web board lets people track progress and answer agents. Runs locally in Docker and connects to Claude Code, Codex, Cursor, and other Streamable HTTP MCP clients. MIT licensed.
      30
      6
      MIT
    Try in Browser

    Glama MCP Gateway

    Add one secure layer between your agents and this server.