pipebrain-mcp 0.49.0 → 0.51.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -5
- package/dist/main.js +101 -104
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -14,16 +14,24 @@ Register it with Claude Code (no install step — `npx` fetches it), at **user s
|
|
|
14
14
|
every repo on the machine gets the tools:
|
|
15
15
|
|
|
16
16
|
```
|
|
17
|
-
claude mcp add pipebrain -s user -e PIPEBRAIN_API_URL=https://api.pipebrain.dev -e 'PIPEBRAIN_TOKEN=${PIPEBRAIN_TOKEN}' -- npx -y pipebrain-mcp@latest
|
|
17
|
+
claude mcp add pipebrain -s user -e PIPEBRAIN_API_URL=https://api.pipebrain.dev -e 'PIPEBRAIN_TOKEN=${PIPEBRAIN_TOKEN}' -e PIPEBRAIN_ORG=your-org-slug -- npx -y pipebrain-mcp@latest
|
|
18
18
|
```
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
environment of the process that launches it, and refuses to start if either value
|
|
22
|
-
missing or malformed — including an unexpanded `${PIPEBRAIN_TOKEN}` placeholder, which
|
|
23
|
-
the failure that otherwise looks like a revoked token. There is no default API URL (see
|
|
20
|
+
The first two `-e` flags are required, not decoration. The server reads its configuration
|
|
21
|
+
from the environment of the process that launches it, and refuses to start if either value
|
|
22
|
+
is missing or malformed — including an unexpanded `${PIPEBRAIN_TOKEN}` placeholder, which
|
|
23
|
+
is the failure that otherwise looks like a revoked token. There is no default API URL (see
|
|
24
24
|
the table below), so a registration that passes neither flag produces a server that never
|
|
25
25
|
starts.
|
|
26
26
|
|
|
27
|
+
The third flag fails the other way, which is why it is easy to miss. Every call acts inside
|
|
28
|
+
one organization and no credential carries one, so a server registered without
|
|
29
|
+
`PIPEBRAIN_ORG` starts perfectly and is then refused on its first organization-scoped call.
|
|
30
|
+
Replace `your-org-slug` with your own slug or id; `ppb doctor` (from `pipebrain-cli`)
|
|
31
|
+
prints the ones you may name before any of this is wired. A call may still name a
|
|
32
|
+
different organization than the default, and `list_orgs` returns every organization your
|
|
33
|
+
credential may name — the same set the refusal lists.
|
|
34
|
+
|
|
27
35
|
The single quotes around the token are load-bearing: they keep `${PIPEBRAIN_TOKEN}` a
|
|
28
36
|
literal *reference* in Claude's config instead of expanding it as you type, so the token
|
|
29
37
|
value itself never lands in a config file — the server resolves it from your environment
|
|
@@ -35,6 +43,7 @@ at each launch.
|
|
|
35
43
|
| --- | --- |
|
|
36
44
|
| `PIPEBRAIN_TOKEN` | Your `pbt_…` API token (Settings → Organization → API tokens). |
|
|
37
45
|
| `PIPEBRAIN_API_URL` | API base URL, e.g. `https://api.pipebrain.dev`. Required — there is no default, so a session can always tell which environment it is bound to. |
|
|
46
|
+
| `PIPEBRAIN_ORG` | The organization slug or id calls act in unless a call names another. Optional, and its absence does not stop the server starting — but a server with no default is refused on every organization-scoped call, since no credential carries one. `list_orgs` returns the ones you may name. |
|
|
38
47
|
|
|
39
48
|
No tool here RETURNS a secret value: the secrets tools answer with metadata only, a
|
|
40
49
|
response never echoes a value, and there is deliberately no value-returning tool — no get,
|
package/dist/main.js
CHANGED
|
@@ -383,9 +383,9 @@ var AGENT_CONFIG_ENTRIES = [
|
|
|
383
383
|
{ target: "skills/capture-correction/SKILL.md", sha256: "907ee8fc8e7ea15c8296acd23a816e1f9c6454edcf37a93f18af79b1c542fb75" },
|
|
384
384
|
{ target: "skills/pipebrain-header-pass/SKILL.md", sha256: "10545e3c1c5215b031e1c76281e65cd2ecf7420b3f07a4221824591a8263c5fc" },
|
|
385
385
|
{ target: "skills/pipebrain-secrets/SKILL.md", sha256: "52c2136a3cca5612981d50b926195eae3e804911e1e9723dd5eba76e66b52d6f" },
|
|
386
|
-
{ target: "skills/pipebrain-work-session/SKILL.md", sha256: "
|
|
386
|
+
{ target: "skills/pipebrain-work-session/SKILL.md", sha256: "1272d0a403b5826a52616cdbf2dc706f69746c21ac6c930e22df87750a2509b7" },
|
|
387
387
|
{ target: "skills/pipebrain-writing/SKILL.md", sha256: "8e5ed4636afcca0cacc220d8fa5310364d2d6854a9fc68ffa03e4c428989f8b8" },
|
|
388
|
-
{ target: "skills/pipebrain/SKILL.md", sha256: "
|
|
388
|
+
{ target: "skills/pipebrain/SKILL.md", sha256: "9fdad1b78bb506e9cd91ba55f4baee0adc5f4d2c5d9d418d0e8c922554e8df74" },
|
|
389
389
|
{ target: "skills/requirements-altitude/SKILL.md", sha256: "60d1916da22a52138c046fcf4e5cd4cc420b7a5679a6652eb76573b7a69a1cf4" }
|
|
390
390
|
];
|
|
391
391
|
|
|
@@ -505,7 +505,7 @@ function apiBindingLabel(apiBaseUrl) {
|
|
|
505
505
|
}
|
|
506
506
|
|
|
507
507
|
// ../../libs/agent-shared/dist/doctrine.js
|
|
508
|
-
var DOCTRINE = "# Pipebrain doctrine\n\nHow an agent works inside Pipebrain. These rules ship with the product and are the same in every organization. Each one protects something the product guarantees.\n\n## How to write here\n\nThe person reading what you write is the operator, deciding something. They have about two minutes. Put the point first. Use short sentences and ordinary words. Headings and bullets are fine. Explain why once, not under every bullet.\n\nA criterion is one sentence: a concrete situation, and what must be true afterward. If you need a general rule, write two or three criteria instead.\n\nEvidence on a criterion opens with the verdict, then how you drove it, then what it does not cover. Keep it to one screen; name the test or command, not every file you read.\n\nA closing comment says what landed, what did not and why, what you filed by key, and whether the tests are green.\n\nAnything else you notice goes to a question, when the operator has to choose; to a new issue at triage, when it is real work; or, most often, nowhere. Never add a notes, caveats, or next-steps section. An item filed and repeated in prose gets triaged twice.\n\nYour organization may add its own writing rules below this document. Most have none.\n\n## Search before you create\n\nLook for an existing record before you add one. A duplicate splits one record's history across two keys, and nothing merges them later. Update or link the record you find.\n\n## Link by key, never paste\n\nRefer to a requirement, decision, or issue by its key. Never paste its text into a body. When the source changes the copy stays stale, and the stale copy is what gets built.\n\n## Most choices are yours\n\nInside an approved requirement you choose the design, the libraries, and the order of work. Ask only when a choice changes what a user sees, costs money, or contradicts a requirement; file that one as a question. Asking about everything makes real questions wait behind choices that were yours. A technical choice worth keeping goes in a decision record.\n\n## File decisions as questions\n\nAn open decision goes in a question on the issue, filed with `ask_questions` and carrying the options you propose. It does not belong in a body, under a heading or in a field. Prose asks nobody and records no answer, so a later session cannot tell whether it was ever decided. Withdraw a question
|
|
508
|
+
var DOCTRINE = "# Pipebrain doctrine\n\nHow an agent works inside Pipebrain. These rules ship with the product and are the same in every organization. Each one protects something the product guarantees.\n\n## How to write here\n\nThe person reading what you write is the operator, deciding something. They have about two minutes. Put the point first. Use short sentences and ordinary words. Headings and bullets are fine. Explain why once, not under every bullet.\n\nA criterion is one sentence: a concrete situation, and what must be true afterward. If you need a general rule, write two or three criteria instead.\n\nEvidence on a criterion opens with the verdict, then how you drove it, then what it does not cover. Keep it to one screen; name the test or command, not every file you read.\n\nA closing comment says what landed, what did not and why, what you filed by key, and whether the tests are green.\n\nAnything else you notice goes to a question, when the operator has to choose; to a new issue at triage, when it is real work; or, most often, nowhere. Never add a notes, caveats, or next-steps section. An item filed and repeated in prose gets triaged twice.\n\nYour organization may add its own writing rules below this document. Most have none.\n\n## Search before you create\n\nLook for an existing record before you add one. A duplicate splits one record's history across two keys, and nothing merges them later. Update or link the record you find.\n\n## Link by key, never paste\n\nRefer to a requirement, decision, or issue by its key. Never paste its text into a body. When the source changes the copy stays stale, and the stale copy is what gets built.\n\n## Most choices are yours\n\nInside an approved requirement you choose the design, the libraries, and the order of work. Ask only when a choice changes what a user sees, costs money, or contradicts a requirement; file that one as a question. Asking about everything makes real questions wait behind choices that were yours. A technical choice worth keeping goes in a decision record.\n\n## File decisions as questions\n\nAn open decision goes in a question on the issue, filed with `ask_questions` and carrying the options you propose. It does not belong in a body, under a heading or in a field. Prose asks nobody and records no answer, so a later session cannot tell whether it was ever decided. If your own work settles a question, answer it with `answer_question` \u2014 the answer is worth keeping, and withdrawing throws it away. Withdraw only a question that should never have been asked.\n\n## Cross-cutting requirements arrive on their own\n\nThey bind every epic and come back with a work-item read, at their latest approved revision. Never restate them in a body and never add them as explicit requirement links. A copy drifts, and an explicit link pins one revision where the read delivers the newest.\n\n## Conventions need a person's approval\n\nApproved conventions come back with the same work-item read and bind every project in the organization. Never restate one in a body and never add one as an explicit link; a copy goes stale.\n\nA convention you create or amend over the agent surface is a proposal. It is not in force until a person approves it in a browser. Until then, do not work from your own unapproved wording.\n\nA statement that stops being true when this project ends is a cross-cutting requirement. One still true on the next project is a convention. File one when an evaluator rejects work or a criterion fails, and rarely otherwise: each reaches every read, after a person reviews it.\n\n## Choosing auto or manual\n\nFirst find out what this project can drive end to end, and with which command. A criterion is `auto` when an evaluator can drive the input and read the result on the running system, including an interface where a browser harness exists. Only the evaluator's own run qualifies it; a test the implementer wrote does not. An auto criterion nobody can drive never gets evidence.\n\nMark a criterion `manual` only when the verdict needs a person's judgment. A manual row a machine could have checked holds the issue open until a person gets to it. If you cannot say how to drive a criterion, it is worded too vaguely; reword it against something observable. A manual criterion carries instructions for a stranger to the code.\n\nA manual criterion with no recorded artifact is the human gate working as designed, and not a finding.\n\nThis choice also decides who can finish the work. What gates finishing is the work rather than the caller \u2014 the status tool names what the gate reads, and the server never asks who is calling. A manual row is the one a credential cannot pass, so work carrying one waits for a person; work whose criteria are all machine-checked does not, and the session that has just passed them can set it done.\n\n## The bar for filing a bug\n\nFile a bug only when the behavior breaks an approved requirement or a defined acceptance criterion, and name which one first. Everything else, including unclear code and edge cases nobody specified, is feedback or nothing. A queue that mixes defects with taste has to be read in full before any of it can be trusted.\n\n## A decision record is written once\n\nA decision record says what was decided and why, at the time. A proposed record is still being written: correct it with `update_decision`, and accept it once it says what you decided. An accepted record is settled, so never edit one; write a superseding record instead. The chain of records shows how the thinking changed, and an edit destroys it. Read existing records before reopening a settled question.\n\n## A secret value never travels in a tool call\n\nNo tool returns a secret value, by design, and none should receive one. A value in a tool call is in the transcript before the server can encrypt it; the only remedy is rotation. Set values with the command-line tool, from a file or standard input, and inject at run time.\n\n## Setting an instruction replaces the whole body\n\nThere is no partial update. An instruction write stores exactly the body you supply and discards everything that was there. Read the live value, compose the complete replacement, write it, then read it again to confirm what landed.\n\nThis guidance and the tool descriptions are a snapshot from the session's start; an edit reaches the next session, never this one. `list_org_instructions` returns the saved rows as they are now; that is the confirming read.\n";
|
|
509
509
|
|
|
510
510
|
// ../../libs/agent-shared/dist/org-reference.js
|
|
511
511
|
var CANONICAL_UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
@@ -16877,8 +16877,9 @@ var ApiClient = class _ApiClient {
|
|
|
16877
16877
|
/**
|
|
16878
16878
|
* Answers a question by its stable `Q-{n}` number with EXACTLY one of `option`
|
|
16879
16879
|
* (1-based, into the question's options) or `text` (PIPE-93); answering again
|
|
16880
|
-
* revises.
|
|
16881
|
-
*
|
|
16880
|
+
* revises. Token OR session (PIPE-450): the bearer refusal PIPE-R22 r2 withdrew is
|
|
16881
|
+
* gone, and the answer records the credential behind it instead. Returns the updated
|
|
16882
|
+
* detail.
|
|
16882
16883
|
*/
|
|
16883
16884
|
answerQuestion(ref, number4, answer) {
|
|
16884
16885
|
return this.request("POST", `/api/v1/issues/${enc(ref)}/questions/${number4}/answer`, { body: answer });
|
|
@@ -17208,9 +17209,12 @@ var ApiClient = class _ApiClient {
|
|
|
17208
17209
|
}
|
|
17209
17210
|
// --- the waiting queue -----------------------------------------------------
|
|
17210
17211
|
/**
|
|
17211
|
-
*
|
|
17212
|
+
* Everything not moving on its own across ALL projects of the caller's organisation
|
|
17212
17213
|
* (PIPE-87): revisions awaiting approval, questions awaiting an answer, and work
|
|
17213
|
-
* in `verify` with a criterion not yet passed, longest wait first.
|
|
17214
|
+
* in `verify` with a criterion not yet passed, longest wait first. Not all of it
|
|
17215
|
+
* waits on a person — a question clears with `answer_question` and an `auto`
|
|
17216
|
+
* criterion with `set_criterion_state`; the one a credential cannot pass is a
|
|
17217
|
+
* criterion marked for human judgment.
|
|
17214
17218
|
*
|
|
17215
17219
|
* Takes NO project and NO org PARAMETER: a queue you point at a project is the
|
|
17216
17220
|
* per-project surface this replaces, and the organization is a property of the CLIENT,
|
|
@@ -17675,10 +17679,7 @@ function createMcpServer(baseClient, apiBaseUrl, orgInstructions2 = /* @__PURE__
|
|
|
17675
17679
|
);
|
|
17676
17680
|
}
|
|
17677
17681
|
registerStrict("list_orgs", {
|
|
17678
|
-
description: "List the organizations this credential may act in
|
|
17679
|
-
// NO inputSchema, and the absence is the point rather than an omission. There is
|
|
17680
|
-
// nothing to narrow: the answer is the caller's own memberships, and a filter over a
|
|
17681
|
-
// list this short would be a second way to ask one question.
|
|
17682
|
+
description: "List the organizations this credential may act in, with each name, slug, id, kind and your role. Use it to find the names you may pass. This is the one tool that needs no `org`: every other call names the organization it acts in, and omitting it is refused unless this session has a default. The answer is your memberships right now, so a joined organization appears and a left one does not."
|
|
17682
17683
|
}, () => (
|
|
17683
17684
|
// A BARE ARRAY, unlike `list_waiting`'s `{ items }`, and for the reason that tool
|
|
17684
17685
|
// gives rather than against it: the envelope has already been unwrapped by
|
|
@@ -17698,7 +17699,7 @@ function createMcpServer(baseClient, apiBaseUrl, orgInstructions2 = /* @__PURE__
|
|
|
17698
17699
|
run(() => client2().unscoped().listOrgs(), GUIDANCE_POINTER)
|
|
17699
17700
|
));
|
|
17700
17701
|
registerStrict("list_projects", {
|
|
17701
|
-
description: "List
|
|
17702
|
+
description: "List every project: key, environments, milestones (an archived one leaves the pickers, staying valid on issues naming it), deliverable targets and open-issue counts. Pages with `limit` (default 200, max 500) and `offset`.",
|
|
17702
17703
|
// Optional pagination (§9) — bounds mirror list_issues/list_requirements; the
|
|
17703
17704
|
// server defaults limit to 200 (max 500) and passes limit/offset as query params.
|
|
17704
17705
|
inputSchema: {
|
|
@@ -17707,7 +17708,7 @@ function createMcpServer(baseClient, apiBaseUrl, orgInstructions2 = /* @__PURE__
|
|
|
17707
17708
|
}
|
|
17708
17709
|
}, (args) => run(() => client2().listProjects(args), GUIDANCE_POINTER));
|
|
17709
17710
|
registerStrict("create_project", {
|
|
17710
|
-
description: "Create a
|
|
17711
|
+
description: "Create a project. Nothing can be filed until one exists. `name` is the display label; `key` is 2\u20135 uppercase letters (e.g. `DWB`), unique per organization, and prefixes every display id.",
|
|
17711
17712
|
// Inline shape: @pipeline/domain has no standalone create-project schema — the
|
|
17712
17713
|
// same `{ name, key }` pair lives only inside createOrgInputSchema.project, so
|
|
17713
17714
|
// this mirrors it exactly (key = 2–5 A–Z via projectKeySchema) and matches
|
|
@@ -17718,12 +17719,7 @@ function createMcpServer(baseClient, apiBaseUrl, orgInstructions2 = /* @__PURE__
|
|
|
17718
17719
|
}
|
|
17719
17720
|
}, (args) => run(() => client2().createProject(args)));
|
|
17720
17721
|
registerStrict("list_waiting", {
|
|
17721
|
-
description: "
|
|
17722
|
-
// NO NARROWING ARGUMENTS. The one argument this tool does take — `org`, which every
|
|
17723
|
-
// tool takes — is not a narrowing: it says WHICH ORGANIZATION the call acts in, not
|
|
17724
|
-
// which of that organization's gates come back, and the caller's membership there is
|
|
17725
|
-
// checked on the way in (PIPE-410). The viewer narrowing the web queue offers is
|
|
17726
|
-
// filter state on that view, not something an agent picks.
|
|
17722
|
+
description: "Everything not moving on its own across all projects in the organization named by `org`, longest wait first: a requirement revision awaiting approval, an unanswered question, and work at verify holding a criterion that has not passed. Each entry names its project, the ref to act on, and when the wait began. Read-only. A revision clears with `approve_requirement`, a question with `answer_question`, an `auto` criterion with `set_criterion_state`. A criterion marked for human judgment waits for a person."
|
|
17727
17723
|
}, () => (
|
|
17728
17724
|
// `{ items }`, mirroring the HTTP envelope rather than unwrapping to a bare array
|
|
17729
17725
|
// like the sibling list tools. The reason is the envelope itself: this tool is a
|
|
@@ -17746,12 +17742,7 @@ function createMcpServer(baseClient, apiBaseUrl, orgInstructions2 = /* @__PURE__
|
|
|
17746
17742
|
// one description loaded into EVERY session by `anthropic/alwaysLoad`. The clauses
|
|
17747
17743
|
// that went were the ones arguing for the rule rather than stating it; what a caller
|
|
17748
17744
|
// needs to predict a refusal all stayed.
|
|
17749
|
-
description:
|
|
17750
|
-
// The WHOLE domain schema rather than its `.shape`: the exactly-one-of rule is a
|
|
17751
|
-
// refinement on the OBJECT, and a raw shape drops it — the SDK validates against
|
|
17752
|
-
// what it is handed, so the shape alone would quietly accept both arguments and
|
|
17753
|
-
// neither. Handing it the schema lets its refusal reach the caller verbatim,
|
|
17754
|
-
// naming the two arguments it refused rather than a reworded stand-in.
|
|
17745
|
+
description: "Everything a session needs before starting work. Call it first: it alone returns the doctrine and the organization\u2019s writing rules in full. Address it by `issue` or `project`, never both. An issue returns its detail plus the text of each requirement it links, comments capped at `commentLimit` (default " + WORK_CONTEXT_COMMENT_LIMIT + "), with `commentCount` the true total. A project returns `available` \u2014 startable work, excluding done, killed, blocked, person-reserved and others\u2019 claims \u2014 capped at `availableLimit` with `availableCount` as the true total. Read-only.",
|
|
17755
17746
|
inputSchema: getWorkContextInputSchema,
|
|
17756
17747
|
// THE TWO ANNOTATIONS PIPE-D30 REQUIRES, and the reason each key is spelled out
|
|
17757
17748
|
// rather than built: an unknown `_meta` key is IGNORED — no error, no warning — so a
|
|
@@ -17792,7 +17783,7 @@ function createMcpServer(baseClient, apiBaseUrl, orgInstructions2 = /* @__PURE__
|
|
|
17792
17783
|
}
|
|
17793
17784
|
);
|
|
17794
17785
|
registerStrict("create_milestone", {
|
|
17795
|
-
description: "Create a milestone
|
|
17786
|
+
description: "Create a milestone. `token` is the ref issues point at (e.g. `M1`) and is unique per project; a duplicate is rejected. `label` names it; `position` orders it, 0-based, last by default. `active: true` demotes whichever was active.",
|
|
17796
17787
|
inputSchema: {
|
|
17797
17788
|
project: projectKeySchema,
|
|
17798
17789
|
token: external_exports.string().min(1),
|
|
@@ -17802,7 +17793,7 @@ function createMcpServer(baseClient, apiBaseUrl, orgInstructions2 = /* @__PURE__
|
|
|
17802
17793
|
}
|
|
17803
17794
|
}, ({ project, ...input }) => run(() => client2().createMilestone(project, input)));
|
|
17804
17795
|
registerStrict("update_milestone", {
|
|
17805
|
-
description: "Rename, reorder, archive or restore a milestone,
|
|
17796
|
+
description: "Rename, reorder, archive or restore a milestone, by token (`M1`) or uuid. Supply at least one of `token`, `label`, `position` or `archived`; an empty edit is rejected. Renaming a token does not rewrite the issues carrying it, so prefer `label`. The active milestone cannot be archived (`milestone_active`), and an archived one cannot be activated (`milestone_archived`).",
|
|
17806
17797
|
inputSchema: {
|
|
17807
17798
|
project: projectKeySchema,
|
|
17808
17799
|
milestone: external_exports.string().min(1),
|
|
@@ -17813,14 +17804,14 @@ function createMcpServer(baseClient, apiBaseUrl, orgInstructions2 = /* @__PURE__
|
|
|
17813
17804
|
}
|
|
17814
17805
|
}, ({ project, milestone, ...fields }) => run(async () => client2().updateMilestone(project, await resolveMilestoneId(client2(), project, milestone), fields)));
|
|
17815
17806
|
registerStrict("set_active_milestone", {
|
|
17816
|
-
description: "Make a milestone
|
|
17807
|
+
description: "Make a milestone its project\u2019s active one, by token (e.g. `M2`) or uuid. At most one per project is active, so this clears the previous in the same transaction.",
|
|
17817
17808
|
inputSchema: {
|
|
17818
17809
|
project: projectKeySchema,
|
|
17819
17810
|
milestone: external_exports.string().min(1)
|
|
17820
17811
|
}
|
|
17821
17812
|
}, ({ project, milestone }) => run(async () => client2().activateMilestone(project, await resolveMilestoneId(client2(), project, milestone))));
|
|
17822
17813
|
registerStrict("delete_milestone", {
|
|
17823
|
-
description: "Soft-delete a milestone
|
|
17814
|
+
description: "Soft-delete a milestone by token (e.g. `M3`) or uuid; the token frees up for reuse. Issues naming it are not rewritten and keep a dangling token, so re-point them first.",
|
|
17824
17815
|
inputSchema: {
|
|
17825
17816
|
project: projectKeySchema,
|
|
17826
17817
|
milestone: external_exports.string().min(1)
|
|
@@ -17831,7 +17822,7 @@ function createMcpServer(baseClient, apiBaseUrl, orgInstructions2 = /* @__PURE__
|
|
|
17831
17822
|
return { deleted: true, project, milestone, id };
|
|
17832
17823
|
}));
|
|
17833
17824
|
registerStrict("create_deliverable_target", {
|
|
17834
|
-
description: "Add a
|
|
17825
|
+
description: "Add a platform a deliverable can target. `slug` is the immutable id it stores (e.g. `bsky`), unique per project. `label` names it, `position` orders it, `active` (default true) gates new deliverables.",
|
|
17835
17826
|
inputSchema: {
|
|
17836
17827
|
project: projectKeySchema,
|
|
17837
17828
|
slug: platformSlugSchema,
|
|
@@ -17841,7 +17832,7 @@ function createMcpServer(baseClient, apiBaseUrl, orgInstructions2 = /* @__PURE__
|
|
|
17841
17832
|
}
|
|
17842
17833
|
}, ({ project, ...input }) => run(() => client2().createDeliverableTarget(project, input)));
|
|
17843
17834
|
registerStrict("update_deliverable_target", {
|
|
17844
|
-
description: "Update a deliverable target
|
|
17835
|
+
description: "Update a deliverable target by slug or uuid. Supply at least one of `label`, `position` or `active`; an empty edit is rejected. The slug is immutable; `active: false` hides it from new deliverables.",
|
|
17845
17836
|
inputSchema: {
|
|
17846
17837
|
project: projectKeySchema,
|
|
17847
17838
|
target: external_exports.string().min(1),
|
|
@@ -17851,7 +17842,7 @@ function createMcpServer(baseClient, apiBaseUrl, orgInstructions2 = /* @__PURE__
|
|
|
17851
17842
|
}
|
|
17852
17843
|
}, ({ project, target, ...fields }) => run(async () => client2().updateDeliverableTarget(project, await resolveDeliverableTargetId(client2(), project, target), fields)));
|
|
17853
17844
|
registerStrict("delete_deliverable_target", {
|
|
17854
|
-
description: "Delete a deliverable target
|
|
17845
|
+
description: "Delete a deliverable target by slug or uuid. Only a target with no deliverables can be deleted; one in use is rejected \u2014 deactivate it with `update_deliverable_target` instead.",
|
|
17855
17846
|
inputSchema: {
|
|
17856
17847
|
project: projectKeySchema,
|
|
17857
17848
|
target: external_exports.string().min(1)
|
|
@@ -17864,7 +17855,7 @@ function createMcpServer(baseClient, apiBaseUrl, orgInstructions2 = /* @__PURE__
|
|
|
17864
17855
|
registerStrict(
|
|
17865
17856
|
"list_issues",
|
|
17866
17857
|
{
|
|
17867
|
-
description:
|
|
17858
|
+
description: "List issue summaries \u2014 status, priority, milestone, claimer, criteria progress and the header \u2014 filtered by project, status, type, claimer, priority, milestone, text, parent, blocked and needsHeader. With no status filter the default is buildable work only: " + OPEN_STATUSES.join(", ") + '. Terminal work is excluded until you name it, so pass status ["done","killed"] for finished and abandoned work. An explicit filter is used verbatim, never merged. needsHeader true returns the issues whose header is missing or stale.',
|
|
17868
17859
|
inputSchema: listIssuesFilterSchema.shape
|
|
17869
17860
|
},
|
|
17870
17861
|
// The default lives in the API now, not here (PIPE-D35, Listing issues
|
|
@@ -17880,7 +17871,7 @@ function createMcpServer(baseClient, apiBaseUrl, orgInstructions2 = /* @__PURE__
|
|
|
17880
17871
|
(args) => run(() => client2().listIssues(args), GUIDANCE_POINTER)
|
|
17881
17872
|
);
|
|
17882
17873
|
registerStrict("get_issue", {
|
|
17883
|
-
description:
|
|
17874
|
+
description: "Full issue detail by key or uuid: header, body, criteria, requirement links, cross-cutting requirements, approved conventions, children and comments. `terminal` is true when the issue is done or killed, so it is not buildable; `projectArchived` is true when the project is retired, and this read still answers while lists and writes do not. `headerStale` is true when no header was written or the body moved. Long records page with `bodyOffset` and `commentOffset`; `omitted` and `next` say what was left out.",
|
|
17884
17875
|
inputSchema: getIssueInputSchema.shape,
|
|
17885
17876
|
// The same result-threshold annotation `get_work_context` declares, and the same
|
|
17886
17877
|
// key spelled out for the same reason (an unknown `_meta` key is silently
|
|
@@ -17908,78 +17899,66 @@ function createMcpServer(baseClient, apiBaseUrl, orgInstructions2 = /* @__PURE__
|
|
|
17908
17899
|
})
|
|
17909
17900
|
), GUIDANCE_POINTER));
|
|
17910
17901
|
registerStrict("create_issue", {
|
|
17911
|
-
description: "Create an
|
|
17902
|
+
description: "Create an epic, task, bug or feedback. Status defaults to triage; anything but triage or killed needs a milestone, else refused `milestone_required`. An archived milestone is refused `milestone_archived_write`. An open issue under a done or killed parent is refused `parent_terminal`. Epics, tasks and bugs carry criteria; feedback is refused `criteria_not_supported`. A `manual` criterion needs `howTo`, else refused `how_to_required`. A bug may name the criterion it was filed against. See the doctrine `get_work_context` returns, under \u201CSearch before you create\u201D.",
|
|
17912
17903
|
inputSchema: createIssueInputSchema.shape
|
|
17913
17904
|
}, (args) => run(() => client2().createIssue(args)));
|
|
17914
17905
|
registerStrict("promote_feedback", {
|
|
17915
|
-
description: "
|
|
17906
|
+
description: "Retype a feedback report as real work \u2014 an epic or a task \u2014 so it can carry criteria and be built. Feedback itself is refused `criteria_not_supported`. Scheduling the new work into an archived milestone is refused `milestone_archived_write`. Closing the source feedback is refused `status_guard` when anything beneath it, at any depth, is not done or killed. A `manual` criterion needs `howTo`, else refused `how_to_required`. See the doctrine `get_work_context` returns, under \u201CThe bar for filing a bug\u201D.",
|
|
17916
17907
|
inputSchema: {
|
|
17917
17908
|
issue: external_exports.string().min(1),
|
|
17918
17909
|
...promoteFeedbackInputSchema.shape
|
|
17919
17910
|
}
|
|
17920
17911
|
}, ({ issue: issue2, ...fields }) => run(() => client2().promoteFeedback(issue2, fields)));
|
|
17921
17912
|
registerStrict("open_review", {
|
|
17922
|
-
description: "Open
|
|
17923
|
-
// The WHOLE domain schema rather than its `.shape`, for `get_work_context`'s
|
|
17924
|
-
// reason: the exactly-one-of rule is a refinement on the OBJECT, and a raw
|
|
17925
|
-
// shape drops it — the refusal naming the two forms would never reach the
|
|
17926
|
-
// caller, and both arguments (or neither) would be accepted here.
|
|
17913
|
+
description: "Open an epic\u2019s completion review, read by whoever neither built nor evaluated it: one ready task carrying the checklist. Pass exactly one of `epic`, or `project` plus `milestone`. A first call is refused `review_target_not_epic`, `review_target_terminal` (finished epic), `milestone_required` (no milestone), `milestone_not_found` (unknown token). One review per scope; a second is refused `review_already_open`. Epic-form also holds that milestone scope; milestone-form blocks no epic. File findings under the epic, not the review. See the doctrine `get_work_context` returns, under \u201CHow to write here\u201D.",
|
|
17927
17914
|
inputSchema: openReviewInputSchema
|
|
17928
17915
|
}, (args) => run(() => client2().openReview(args)));
|
|
17929
17916
|
registerStrict("update_issue", {
|
|
17930
|
-
description: "
|
|
17917
|
+
description: "Change an issue\u2019s fields, parent and links. Header fields are refused; `set_criteria` sets criteria, which epics, tasks and bugs carry. An archived milestone is refused `milestone_archived_write`, unless the issue or its parent carries it. A cycle-closing re-parent is refused `parent_cycle`. A finished parent refuses an open issue, `parent_terminal`; a finished one holding open work, `parent_terminal_subtree`. Attaching open work starts a parent that is not blocked, unscheduled or under finished work. See the doctrine `get_work_context` returns, under \u201CMost choices are yours\u201D.",
|
|
17931
17918
|
inputSchema: updateIssueInputSchema.shape
|
|
17932
17919
|
}, ({ issue: issue2, ...fields }) => run(() => client2().updateIssue(issue2, fields)));
|
|
17933
17920
|
registerStrict("set_issue_header", {
|
|
17934
|
-
description: "Write an issue\u2019s
|
|
17921
|
+
description: "Write an issue\u2019s header \u2014 `summary` (the change in one sentence), `why` (the user, business or cost reason, not the technical one) and `affectedSurfaces` (what this touches and who feels it). All three are required and replace the header whole. Write them in plain words: no identifiers, no file paths, no jargon. A summary shaped like code is refused; `summary` caps at 350 characters. Create and update refuse these fields. See the doctrine `get_work_context` returns, under \u201CHow to write here\u201D.",
|
|
17935
17922
|
inputSchema: setIssueHeaderInputSchema.shape
|
|
17936
17923
|
}, ({ issue: issue2, ...fields }) => run(() => client2().setIssueHeader(issue2, fields)));
|
|
17937
17924
|
registerStrict("set_status", {
|
|
17938
|
-
description: "
|
|
17925
|
+
description: "Move an issue to another status. An open status under a done or killed parent is refused `parent_terminal_reopen`. verify needs every auto criterion passed; done needs every criterion passed and everything beneath it, at any depth, done or killed. An epic with no criteria is refused at both gates; a task or bug with none moves through. Blocked work cannot reach ready or in_progress until blockers clear. Work with no milestone can only be triage or killed.",
|
|
17939
17926
|
inputSchema: setStatusInputSchema.shape
|
|
17940
17927
|
}, (args) => run(() => client2().setStatus(args.issue, args.status)));
|
|
17941
17928
|
registerStrict("claim_issue", {
|
|
17942
|
-
description: "
|
|
17929
|
+
description: "Claim an issue for this session. It succeeds only if unclaimed; under a race one caller wins and the loser is told who holds it. Claiming is separate from status. It is refused `reserved_for_person` when an operator has reserved the work for a person \u2014 that mark refuses the claim and nothing else, so reading, commenting and moving status all still work. Only a person in the browser can lift it; say in a comment why the work looks agent-ready.",
|
|
17943
17930
|
inputSchema: { issue: issueRefSchema }
|
|
17944
17931
|
}, (args) => run(() => client2().claimIssue(args.issue)));
|
|
17945
17932
|
registerStrict("release_issue", {
|
|
17946
|
-
description: "Release an issue
|
|
17933
|
+
description: "Release your claim on an issue, leaving it unclaimed and its status unchanged.",
|
|
17947
17934
|
inputSchema: { issue: issueRefSchema }
|
|
17948
17935
|
}, (args) => run(() => client2().releaseIssue(args.issue)));
|
|
17949
17936
|
registerStrict("add_comment", {
|
|
17950
|
-
description: "Add a markdown comment to an issue, attributed to the operator
|
|
17937
|
+
description: "Add a markdown comment to an issue, attributed to the operator and token label. A comment explains a blockage; `set_blocked` and `link_blocker` are what record one. See the doctrine `get_work_context` returns, under \u201CHow to write here\u201D.",
|
|
17951
17938
|
inputSchema: commentInputSchema.shape
|
|
17952
17939
|
}, (args) => run(() => client2().addComment(args.issue, args.body)));
|
|
17953
17940
|
registerStrict("set_blocked", {
|
|
17954
|
-
description: "
|
|
17941
|
+
description: "Mark an issue blocked with a required `reason` \u2014 state on the issue, not a comment, and only `clear_blocked` ends it. Setting it again replaces the reason and keeps the old one as history. The reason is a line or two on what the work waits on; what you tried goes in a comment. See the doctrine `get_work_context` returns, under \u201CHow to write here\u201D.",
|
|
17955
17942
|
inputSchema: setBlockedInputSchema.shape
|
|
17956
17943
|
}, (args) => run(() => client2().setBlocked(args.issue, args.reason)));
|
|
17957
17944
|
registerStrict("clear_blocked", {
|
|
17958
|
-
description: "Clear an issue\u2019s blocked mark
|
|
17945
|
+
description: "Clear an issue\u2019s blocked mark. Clearing one that is not blocked is a no-op, not an error. The mark stays as history, and `blocked` on the returned issue is null.",
|
|
17959
17946
|
inputSchema: { issue: issueRefSchema }
|
|
17960
17947
|
}, (args) => run(() => client2().clearBlocked(args.issue)));
|
|
17961
17948
|
registerStrict("link_blocker", {
|
|
17962
|
-
description: "Record that `issue` is
|
|
17949
|
+
description: "Record that `issue` is waiting on `blocker`. One call writes both sides: the waiting issue lists it under `blockedBy`, the blocker under `blocks`. Both must be in the same project. An issue cannot block itself, and an edge that would close a cycle is refused with nothing recorded.",
|
|
17963
17950
|
inputSchema: linkBlockerInputSchema.shape
|
|
17964
17951
|
}, (args) => run(() => client2().addBlocker(args.issue, args.blocker)));
|
|
17965
17952
|
registerStrict("unlink_blocker", {
|
|
17966
|
-
description: "Remove the blocked-by edge between `issue` and `blocker
|
|
17953
|
+
description: "Remove the blocked-by edge between `issue` and `blocker`; one call clears it from both sides. Unlinking a pair that is not linked is a no-op, not an error.",
|
|
17967
17954
|
inputSchema: linkBlockerInputSchema.shape
|
|
17968
17955
|
}, (args) => run(() => client2().removeBlocker(args.issue, args.blocker)));
|
|
17969
17956
|
registerStrict("set_criteria", {
|
|
17970
|
-
description: "Define or replace the
|
|
17957
|
+
description: "Define or replace the acceptance criteria on an issue. A criterion is one sentence: a concrete situation and what must be true afterward. Epics, tasks and bugs all carry criteria; feedback is refused `criteria_not_supported` \u2014 promote it first. Matched rows keep their AC number; changing text or mode resets state. A `manual` criterion needs `howTo`, else refused `how_to_required`; `howTo` on an `auto` criterion is refused by the schema. See the doctrine `get_work_context` returns, under \u201CChoosing auto or manual\u201D.",
|
|
17971
17958
|
inputSchema: setCriteriaInputSchema.shape
|
|
17972
17959
|
}, (args) => run(() => client2().setCriteria(args.issue, args.criteria)));
|
|
17973
17960
|
registerStrict("set_criterion_state", {
|
|
17974
|
-
description: "Set a criterion\u2019s state
|
|
17975
|
-
// `issue` is OPTIONAL: the criterionId-only form needs no issue ref. The
|
|
17976
|
-
// exactly-one-of rule can't be expressed in a raw shape, so the handler
|
|
17977
|
-
// enforces it via the shared `@pipeline/domain` refine (mirror of §7).
|
|
17978
|
-
//
|
|
17979
|
-
// `criterionNumber` takes `int4Schema` so the ADVERTISED JSON Schema carries
|
|
17980
|
-
// the real ceiling (PIPE-149) — hand-rolling it from `positiveIntSchema` told
|
|
17981
|
-
// clients the maximum was 2^53-1 while the column and the API both stop at
|
|
17982
|
-
// 2,147,483,647, so a client refusal that should happen never did.
|
|
17961
|
+
description: "Set a criterion\u2019s state \u2014 pending, passed or failed \u2014 on an epic, a task or a bug alike. Address it by `criterionId` alone, or by issue plus `criterionNumber`; exactly one form. Feedback never carries criteria, so neither address resolves to one. A token is refused on a manual criterion, which only a person in the browser may set, and a token marking passed or failed must supply evidence. See the doctrine `get_work_context` returns, under \u201CChoosing auto or manual\u201D.",
|
|
17983
17962
|
inputSchema: {
|
|
17984
17963
|
issue: issueRefSchema.optional(),
|
|
17985
17964
|
criterionId: uuidSchema.optional(),
|
|
@@ -18002,27 +17981,31 @@ function createMcpServer(baseClient, apiBaseUrl, orgInstructions2 = /* @__PURE__
|
|
|
18002
17981
|
});
|
|
18003
17982
|
}));
|
|
18004
17983
|
registerStrict("ask_questions", {
|
|
18005
|
-
description: "Ask the operator
|
|
17984
|
+
description: "Ask the operator questions on an issue. Send them in one call \u2014 a half-asked set costs a second pass. Each question gets a stable `Q-{n}` number, never reused; `options` are the choices you propose. Answers come back on every later read; read them before asking again. Answer one yourself with `answer_question` when your work settles it; withdraw one with `withdraw_question` if it should never have been asked. See the doctrine `get_work_context` returns, under \u201CFile decisions as questions\u201D.",
|
|
18006
17985
|
inputSchema: askQuestionsInputSchema.shape
|
|
18007
17986
|
}, (args) => run(() => client2().askQuestions(args.issue, args.questions)));
|
|
18008
17987
|
registerStrict("withdraw_question", {
|
|
18009
|
-
description: "Withdraw a question by its
|
|
17988
|
+
description: "Withdraw a question by its `Q-{n}` number, in the organization named by `org`. Only the asker may withdraw, and the asker is the person your credential belongs to, so another agent\u2019s question is not yours to clear. Use it for a question that should never have been asked; one your own work settled deserves `answer_question` instead, which keeps the answer. The question is kept for the audit trail, its number retires, and later reads no longer carry it.",
|
|
18010
17989
|
inputSchema: withdrawQuestionInputSchema.shape
|
|
18011
17990
|
}, (args) => run(() => client2().withdrawQuestion(args.issue, args.number)));
|
|
17991
|
+
registerStrict("answer_question", {
|
|
17992
|
+
description: "Answer a question by its `Q-{n}` number, in the organization named by `org`. Pass exactly one of `option` (a 1-based index into the choices offered) or `text` \u2014 both, or neither, is refused here before anything is sent. Answering again revises; every answer records who answered and which credential. An out-of-range index is refused `option_out_of_range`, naming how many choices there are; a withdrawn or unknown number, `question_not_found`. Answer what your own work settled; leave the rest to the operator.",
|
|
17993
|
+
inputSchema: answerQuestionInputSchema
|
|
17994
|
+
}, (args) => run(() => client2().answerQuestion(args.issue, args.number, { option: args.option, text: args.text })));
|
|
18012
17995
|
registerStrict("list_requirements", {
|
|
18013
|
-
description: "List requirements filtered by project and text:
|
|
17996
|
+
description: "List requirements filtered by project and text: lifecycle (draft/approved/amended), the latest and approved revision numbers, the cross-cutting flag and the linked-epic count.",
|
|
18014
17997
|
inputSchema: listRequirementsFilterSchema.shape
|
|
18015
17998
|
}, (args) => run(() => client2().listRequirements(args), GUIDANCE_POINTER));
|
|
18016
17999
|
registerStrict("get_requirement", {
|
|
18017
|
-
description: "Get a requirement by display id or uuid
|
|
18000
|
+
description: "Get a requirement by display id or uuid: the approved revision by default, the latest when none is approved, or the `revision` you name. Includes lifecycle, approvals and linked issues.",
|
|
18018
18001
|
inputSchema: { requirement: requirementRefSchema, revision: positiveIntSchema.optional() }
|
|
18019
18002
|
}, (args) => run(() => client2().getRequirement(args.requirement, args.revision), GUIDANCE_POINTER));
|
|
18020
18003
|
registerStrict("create_requirement", {
|
|
18021
|
-
description:
|
|
18004
|
+
description: "Create a project-scoped requirement at revision 1 from `title` and `body`. Optional `summary` is a short outline, versioned with them; `area` is a free-text product-area label. The cross-cutting flag makes it bind every epic in the project on its own; never link one explicitly. See the doctrine `get_work_context` returns, under \u201CSearch before you create\u201D.",
|
|
18022
18005
|
inputSchema: createRequirementInputSchema.shape
|
|
18023
18006
|
}, (args) => run(() => client2().createRequirement(args)));
|
|
18024
18007
|
registerStrict("update_requirement", {
|
|
18025
|
-
description: "Update a requirement
|
|
18008
|
+
description: "Update a requirement. `title`, `body` and `summary` append a new revision \u2014 nothing is edited in place \u2014 and `note` records why. Area, cross-cutting and decision links are metadata, with no revision. Turning cross-cutting on is rejected while explicit links exist; unlink first. An empty edit is refused (400). See the doctrine `get_work_context` returns, under \u201CLink by key, never paste\u201D.",
|
|
18026
18009
|
// Inline shape (updateRequirementInputSchema is a refine, no .shape) — add
|
|
18027
18010
|
// summary/area here explicitly (§210) and decisionLinks (PIPE-84 §6); the
|
|
18028
18011
|
// empty-edit refine is enforced by the API, not this shape.
|
|
@@ -18038,23 +18021,23 @@ function createMcpServer(baseClient, apiBaseUrl, orgInstructions2 = /* @__PURE__
|
|
|
18038
18021
|
}
|
|
18039
18022
|
}, ({ requirement, ...fields }) => run(() => client2().updateRequirement(requirement, fields)));
|
|
18040
18023
|
registerStrict("approve_requirement", {
|
|
18041
|
-
description: "Approve
|
|
18024
|
+
description: "Approve a requirement\u2019s latest revision, or the `revision` you name. It enters the approval history and becomes the approved revision reads and links build against.",
|
|
18042
18025
|
inputSchema: approveRequirementInputSchema.shape
|
|
18043
18026
|
}, (args) => run(() => client2().approveRequirement(args.requirement, args.revision)));
|
|
18044
18027
|
registerStrict("list_conventions", {
|
|
18045
|
-
description: "List this organization\u2019s conventions \u2014 standing guidance that binds
|
|
18028
|
+
description: "List this organization\u2019s conventions \u2014 standing guidance that binds every project here. Returns display id (`CONV-{n}`), title, the standing text, lifecycle and revision numbers. Retired ones are included and marked. `text` matches title and body. Only approved, unretired conventions are delivered to agents.",
|
|
18046
18029
|
inputSchema: listConventionsFilterSchema.shape
|
|
18047
18030
|
}, (args) => run(() => client2().listConventions(args), GUIDANCE_POINTER));
|
|
18048
18031
|
registerStrict("get_convention", {
|
|
18049
|
-
description: "Get one convention by display id (`CONV-{n}`) or uuid:
|
|
18032
|
+
description: "Get one convention by display id (`CONV-{n}`) or uuid: its approved text, the revision and approval history, and what replaced it once retired. One never approved reads here and binds nobody.",
|
|
18050
18033
|
inputSchema: { convention: conventionRefSchema }
|
|
18051
18034
|
}, (args) => run(() => client2().getConvention(args.convention), GUIDANCE_POINTER));
|
|
18052
18035
|
registerStrict("create_convention", {
|
|
18053
|
-
description: "Write a convention:
|
|
18036
|
+
description: "Write a convention: standing guidance for the whole organization, with no project attached. Pass `outlivesAnyProject: true` or the write is refused. Text naming one project is refused `convention_names_project_scope`; file that as a cross-cutting requirement instead. It is a draft that binds nobody until a person approves it in a browser.",
|
|
18054
18037
|
inputSchema: createConventionInputSchema.shape
|
|
18055
18038
|
}, (args) => run(() => client2().createConvention(args)));
|
|
18056
18039
|
registerStrict("update_convention", {
|
|
18057
|
-
description: "Amend a convention
|
|
18040
|
+
description: "Amend a convention. `title` and `body` append a new revision \u2014 nothing is edited in place \u2014 and `note` records why. Supplying neither is an empty edit (400). Amended text naming one project is refused as on create. The amendment binds nobody until a person approves it. See the doctrine `get_work_context` returns, under \u201CConventions need a person\u2019s approval\u201D.",
|
|
18058
18041
|
// Inline shape (updateConventionInputSchema is a refine, so it has no `.shape`);
|
|
18059
18042
|
// the empty-edit rule is enforced by the API, not by this shape.
|
|
18060
18043
|
inputSchema: {
|
|
@@ -18065,19 +18048,19 @@ function createMcpServer(baseClient, apiBaseUrl, orgInstructions2 = /* @__PURE__
|
|
|
18065
18048
|
}
|
|
18066
18049
|
}, ({ convention, ...fields }) => run(() => client2().updateConvention(convention, fields)));
|
|
18067
18050
|
registerStrict("list_prompts", {
|
|
18068
|
-
description: "List
|
|
18051
|
+
description: "List the organization\u2019s prompt library (`PROMPT-{n}`): display id, title, current text, latest revision and any attached project. With no filter this returns the whole library. `project` narrows to the ones attached there \u2014 an attachment says what a prompt is about, not who may run it. `text` matches title and body.",
|
|
18069
18052
|
inputSchema: listPromptsFilterSchema.shape
|
|
18070
18053
|
}, (args) => run(() => client2().listPrompts(args), GUIDANCE_POINTER));
|
|
18071
18054
|
registerStrict("get_prompt", {
|
|
18072
|
-
description: "Get one prompt by display id (`PROMPT-{n}`) or uuid: its current text
|
|
18055
|
+
description: "Get one prompt by display id (`PROMPT-{n}`) or uuid: its current text, which is simply the latest revision, plus every earlier one with who saved it, when and why.",
|
|
18073
18056
|
inputSchema: { prompt: promptRefSchema }
|
|
18074
18057
|
}, (args) => run(() => client2().getPrompt(args.prompt), GUIDANCE_POINTER));
|
|
18075
18058
|
registerStrict("create_prompt", {
|
|
18076
|
-
description: "Write a prompt: reusable
|
|
18059
|
+
description: "Write a prompt: reusable text held by the organization (`PROMPT-{n}`). `body` is the prompt and must not be empty; `title` names it; `note` says why. Optional `project` records what the prompt is about, not who may use it. Nothing approves or deletes a prompt; later wording is a new revision.",
|
|
18077
18060
|
inputSchema: createPromptInputSchema.shape
|
|
18078
18061
|
}, (args) => run(() => client2().createPrompt(args)));
|
|
18079
18062
|
registerStrict("update_prompt", {
|
|
18080
|
-
description: "Amend a prompt
|
|
18063
|
+
description: "Amend a prompt. `title` and `body` append a new revision \u2014 nothing is edited in place \u2014 and `note` records why. `project` moves the attachment and `project: null` detaches it; neither writes a revision. Naming none of the three is an empty edit (400). See the doctrine `get_work_context` returns, under \u201CHow to write here\u201D.",
|
|
18081
18064
|
// Inline shape (updatePromptInputSchema is a refine, so it has no `.shape`),
|
|
18082
18065
|
// mirroring that schema's fields — it is the wording this copy follows, `.min(1)`
|
|
18083
18066
|
// bounds included, so a caller learns a blank body is refused from the advertised
|
|
@@ -18092,51 +18075,65 @@ function createMcpServer(baseClient, apiBaseUrl, orgInstructions2 = /* @__PURE__
|
|
|
18092
18075
|
}
|
|
18093
18076
|
}, ({ prompt, ...fields }) => run(() => client2().updatePrompt(prompt, fields)));
|
|
18094
18077
|
registerStrict("create_decision", {
|
|
18095
|
-
description: "Record
|
|
18078
|
+
description: "Record one decision as a project-scoped record (`KEY-D{n}`): `title` names it, `body` says what was decided and why. It starts `proposed` and is frozen once accepted; changing it then means a new record plus `supersede_decision`. See the doctrine `get_work_context` returns, under \u201CA decision record is written once\u201D.",
|
|
18096
18079
|
inputSchema: createDecisionInputSchema.shape
|
|
18097
18080
|
}, (args) => run(() => client2().createDecision(args)));
|
|
18098
18081
|
registerStrict("get_decision", {
|
|
18099
|
-
description: "Get a decision record by display id (`KEY-D{n}`) or uuid:
|
|
18082
|
+
description: "Get a decision record by display id (`KEY-D{n}`) or uuid: its text, its state (`proposed`/`accepted`/`superseded`), who accepted it, what replaced it, and everything citing it.",
|
|
18100
18083
|
inputSchema: getDecisionInputSchema.shape
|
|
18101
18084
|
}, (args) => run(() => client2().getDecision(args.decision), GUIDANCE_POINTER));
|
|
18102
18085
|
registerStrict("list_decisions", {
|
|
18103
|
-
description: "List decision records filtered by `project` and `text`:
|
|
18086
|
+
description: "List decision records filtered by `project` and `text`: each one\u2019s state, its replacement when superseded, and how many requirements and work items cite it. `text` matches title and body.",
|
|
18104
18087
|
inputSchema: listDecisionsFilterSchema.shape
|
|
18105
18088
|
}, (args) => run(() => client2().listDecisions(args), GUIDANCE_POINTER));
|
|
18089
|
+
registerStrict("update_decision", {
|
|
18090
|
+
description: "Correct a decision record while it is still `proposed`: `title`, `body`, or both. An edit naming nothing to change is refused here, before anything is sent. Once a record is `accepted` or `superseded` its text is frozen, and the correction is refused `decision_frozen`, which says to write the change as a new record and supersede this one. Use `accept_decision` to accept; asking for `superseded` here is refused, because superseding must name the replacement.",
|
|
18091
|
+
inputSchema: updateDecisionInputSchema
|
|
18092
|
+
}, (args) => run(() => client2().updateDecision(args.decision, {
|
|
18093
|
+
title: args.title,
|
|
18094
|
+
body: args.body,
|
|
18095
|
+
status: args.status
|
|
18096
|
+
})));
|
|
18097
|
+
registerStrict("accept_decision", {
|
|
18098
|
+
description: "Accept a decision record that is still `proposed`: it becomes `accepted`, stamped with who accepted it and when, and its text is frozen from then on \u2014 changing it later means a new record plus `supersede_decision`. The tool names the move, so the record is the only argument. A record that is already `accepted` or `superseded` is refused `decision_transition_not_allowed`, naming what it can still do.",
|
|
18099
|
+
inputSchema: { decision: decisionRefSchema }
|
|
18100
|
+
}, (args) => run(() => client2().updateDecision(args.decision, { status: "accepted" })));
|
|
18106
18101
|
registerStrict("supersede_decision", {
|
|
18107
|
-
description: "Replace an
|
|
18102
|
+
description: "Replace an accepted decision record with another: `decision` becomes `superseded` and points at `replacedBy`, where the current decision lives. Refused while the record is still `proposed` (correct it instead), when a record replaces itself, when the replacement is in another project, or when its chain already leads back to this record.",
|
|
18108
18103
|
inputSchema: supersedeDecisionInputSchema.shape
|
|
18109
18104
|
}, (args) => run(() => client2().supersedeDecision(args.decision, args.replacedBy)));
|
|
18110
18105
|
registerStrict("list_content", {
|
|
18111
|
-
description: "List content drafts
|
|
18106
|
+
description: "List content drafts (`KEY-C{n}`) filtered by project, text, area and author. Content is a project-scoped sibling of requirements, not an issue type.",
|
|
18112
18107
|
inputSchema: listContentFilterSchema.shape
|
|
18113
18108
|
}, (args) => run(() => client2().listContent(args), GUIDANCE_POINTER));
|
|
18114
18109
|
registerStrict("get_content", {
|
|
18115
|
-
description: "Get a content draft by display id (KEY-C{n}) or uuid: its
|
|
18110
|
+
description: "Get a content draft by display id (`KEY-C{n}`) or uuid: its revisions, latest or the `revision` you name, plus every deliverable, linked issue and linked asset.",
|
|
18116
18111
|
inputSchema: { content: contentRefSchema, revision: positiveIntSchema.optional() }
|
|
18117
18112
|
}, (args) => run(() => client2().getContent(args.content, args.revision), GUIDANCE_POINTER));
|
|
18118
18113
|
registerStrict("create_content", {
|
|
18119
|
-
description:
|
|
18114
|
+
description: "Create a project-scoped content draft at revision 1 from `title` and `body`. Optional `summary` is versioned with them; `area` is a free-text label; `author` is the byline credited on the draft, not the operator calling; `metadata` is stored passively. See the doctrine `get_work_context` returns, under \u201CHow to write here\u201D.",
|
|
18120
18115
|
inputSchema: createContentInputSchema.shape
|
|
18121
18116
|
}, (args) => run(() => client2().createContent(args)));
|
|
18122
18117
|
registerStrict("update_content", {
|
|
18123
|
-
description: "Update a content draft
|
|
18118
|
+
description: "Update a content draft. `title`, `body` or `summary` appends a revision and `note` says why; `area`, `author` and `metadata` are metadata only. An empty edit is refused (400). See the doctrine `get_work_context` returns, under \u201CHow to write here\u201D.",
|
|
18124
18119
|
inputSchema: updateContentInputSchema.shape
|
|
18125
18120
|
}, ({ content, ...fields }) => run(() => client2().updateContent(content, fields)));
|
|
18126
18121
|
registerStrict("create_deliverable", {
|
|
18127
|
-
description: "Add a per-platform deliverable to a draft
|
|
18122
|
+
description: "Add a per-platform deliverable to a content draft at revision 1, addressed `DRAFT-KEY/platform`; optional `body` seeds it. `platform` must be one of the project\u2019s active deliverable targets (see `list_projects`); any other slug is rejected. One deliverable per platform per draft; another wording is a revision (`update_deliverable`). See the doctrine `get_work_context` returns, under \u201CHow to write here\u201D.",
|
|
18128
18123
|
inputSchema: createDeliverableInputSchema.shape
|
|
18129
18124
|
}, ({ content, ...fields }) => run(() => client2().createDeliverable(content, fields)));
|
|
18130
18125
|
registerStrict("update_deliverable", {
|
|
18131
|
-
description: "Append a
|
|
18126
|
+
description: "Append a revision of the post text to a deliverable, addressed `DRAFT-KEY/platform`; `note` says why. The platform itself is immutable. See the doctrine `get_work_context` returns, under \u201CHow to write here\u201D.",
|
|
18132
18127
|
inputSchema: updateDeliverableInputSchema.shape
|
|
18133
18128
|
}, ({ deliverable, ...fields }) => run(() => client2().updateDeliverable(deliverable, fields)));
|
|
18134
18129
|
registerStrict("record_post", {
|
|
18135
|
-
description: "Record that a deliverable (DRAFT-KEY/platform) was posted: its public url and
|
|
18130
|
+
description: "Record that a deliverable (`DRAFT-KEY/platform`) was posted: its public url and `postedAt` (now by default), pinning its current revision as posted. Pipebrain never posts; a person does, then records it here.",
|
|
18136
18131
|
inputSchema: recordPostInputSchema.shape
|
|
18137
18132
|
}, ({ deliverable, ...fields }) => run(() => client2().recordPost(deliverable, fields)));
|
|
18138
18133
|
registerStrict("promote_deliverable", {
|
|
18139
|
-
|
|
18134
|
+
// The paste exception is stated because without it an agent holding the doctrine
|
|
18135
|
+
// would refuse this tool's whole purpose.
|
|
18136
|
+
description: "Create a NEW issue from a deliverable (`DRAFT-KEY/platform`) with a protected link pinning its current revision, which cannot be removed. Needs `title` and `type`; `status` defaults to triage, `body` seeds the issue. A `status` past triage or killed needs a `milestone`, else refused `milestone_required`. An archived milestone is refused `milestone_archived_write`. Seeding `body` with the post text is the exception to linking rather than pasting. See the doctrine `get_work_context` returns, under \u201CLink by key, never paste\u201D.",
|
|
18140
18137
|
inputSchema: {
|
|
18141
18138
|
deliverable: external_exports.string().min(1),
|
|
18142
18139
|
title: external_exports.string().min(1),
|
|
@@ -18160,33 +18157,33 @@ function createMcpServer(baseClient, apiBaseUrl, orgInstructions2 = /* @__PURE__
|
|
|
18160
18157
|
}
|
|
18161
18158
|
}, ({ deliverable, ...fields }) => run(() => client2().promoteDeliverable(deliverable, fields)));
|
|
18162
18159
|
registerStrict("list_assets", {
|
|
18163
|
-
description: "List project assets
|
|
18160
|
+
description: "List project assets, filtered by status (pending/active/rejected), category (image/video/document) and filename text, sorted by date or size. No bytes cross this result.",
|
|
18164
18161
|
inputSchema: listAssetsFilterSchema.shape
|
|
18165
18162
|
}, (args) => run(() => client2().listAssets(args), GUIDANCE_POINTER));
|
|
18166
18163
|
registerStrict("get_asset", {
|
|
18167
|
-
description: "Get an asset\u2019s metadata by uuid: filename, contentType, size, sha256
|
|
18164
|
+
description: "Get an asset\u2019s metadata by uuid: filename, contentType, size, sha256, status and linkCount. No bytes and no URL come back; use `download_asset` or `ppb assets download`.",
|
|
18168
18165
|
inputSchema: { asset: uuidSchema }
|
|
18169
18166
|
}, (args) => run(() => client2().getAsset(args.asset), GUIDANCE_POINTER));
|
|
18170
18167
|
if (surface === "local") {
|
|
18171
18168
|
registerStrict("upload_asset", {
|
|
18172
|
-
description: "Upload a
|
|
18169
|
+
description: "Upload a local file as a project asset and activate it. Only the `path` crosses the wire \u2014 never base64 or file contents. `contentType` is guessed from the extension when omitted. Returns the asset metadata; `link_asset` then attaches it to a draft or a deliverable revision.",
|
|
18173
18170
|
inputSchema: uploadAssetInputSchema.shape
|
|
18174
18171
|
}, (args) => run(() => uploadAssetFromPath(client2(), apiBaseUrl, args)));
|
|
18175
18172
|
registerStrict("download_asset", {
|
|
18176
|
-
description: "
|
|
18173
|
+
description: "Write an ACTIVE asset\u2019s bytes to a local file at `path`, created or overwritten, so you can read an uploaded image or document. Addressed by asset uuid. The bytes are checked against the recorded sha256; the result is metadata and the path.",
|
|
18177
18174
|
inputSchema: downloadAssetInputSchema.shape
|
|
18178
18175
|
}, (args) => run(() => downloadAssetToPath(client2(), args)));
|
|
18179
18176
|
}
|
|
18180
18177
|
registerStrict("link_asset", {
|
|
18181
|
-
description: "Reference an active asset
|
|
18178
|
+
description: "Reference an active asset from a `deliverable_revision` (a content_deliverable_revision uuid) or a `content_draft` (a content_draft uuid, copied onto later deliverables). A non-active asset or a cross-project link is rejected.",
|
|
18182
18179
|
inputSchema: linkAssetInputSchema.shape
|
|
18183
18180
|
}, (args) => run(() => client2().linkAsset(args)));
|
|
18184
18181
|
registerStrict("unlink_asset", {
|
|
18185
|
-
description: "Remove an asset reference by its link id
|
|
18182
|
+
description: "Remove an asset reference by its link id. This drops the reference only; the asset itself is untouched. A link on a chat message is author-only \u2014 anyone else is refused `not_message_author`. Every other target type any member can remove.",
|
|
18186
18183
|
inputSchema: unlinkAssetInputSchema.shape
|
|
18187
18184
|
}, (args) => run(() => client2().unlinkAsset(args.linkId)));
|
|
18188
18185
|
registerStrict("set_secret", {
|
|
18189
|
-
description: "Create a secret or add a new version under a project
|
|
18186
|
+
description: "Create a secret or add a new version under a project and environment. Do not send a sensitive value here \u2014 it lands in the chat transcript before the server can encrypt it; use `ppb secrets set` from stdin or `--from-file`. `name` is UPPER_SNAKE_CASE, `environment` is a slug (dev/prod). See the doctrine `get_work_context` returns, under \u201CA secret value never travels in a tool call\u201D.",
|
|
18190
18187
|
inputSchema: {
|
|
18191
18188
|
project: projectKeySchema,
|
|
18192
18189
|
environment: external_exports.string().min(1),
|
|
@@ -18196,14 +18193,14 @@ function createMcpServer(baseClient, apiBaseUrl, orgInstructions2 = /* @__PURE__
|
|
|
18196
18193
|
}
|
|
18197
18194
|
}, ({ project, environment, name, value, description }) => run(() => client2().setSecret(project, { env: environment, name, value, description })));
|
|
18198
18195
|
registerStrict("list_secrets", {
|
|
18199
|
-
description: "List a project
|
|
18196
|
+
description: "List a project\u2019s secrets: id, name, environment, description, current version, timestamps. No value comes back. Optional `environment` slug narrows it.",
|
|
18200
18197
|
inputSchema: {
|
|
18201
18198
|
project: projectKeySchema,
|
|
18202
18199
|
environment: external_exports.string().min(1).optional()
|
|
18203
18200
|
}
|
|
18204
18201
|
}, ({ project, environment }) => run(() => client2().listSecrets(project, { env: environment }), GUIDANCE_POINTER));
|
|
18205
18202
|
registerStrict("describe_secret", {
|
|
18206
|
-
description: "Describe one secret: its metadata plus
|
|
18203
|
+
description: "Describe one secret: its metadata plus each version \u2014 when, by whom, and whether it was crypto-shredded. Nothing here returns a value.",
|
|
18207
18204
|
inputSchema: {
|
|
18208
18205
|
project: projectKeySchema,
|
|
18209
18206
|
environment: external_exports.string().min(1),
|
|
@@ -18211,7 +18208,7 @@ function createMcpServer(baseClient, apiBaseUrl, orgInstructions2 = /* @__PURE__
|
|
|
18211
18208
|
}
|
|
18212
18209
|
}, ({ project, environment, name }) => run(() => client2().describeSecret(project, name, { env: environment }), GUIDANCE_POINTER));
|
|
18213
18210
|
registerStrict("rotate_secret", {
|
|
18214
|
-
description: "
|
|
18211
|
+
description: "Add a new version to an existing secret and advance the pointer. Same value rules as `set_secret`: a value crosses inbound only and nothing here returns one, so set a real credential with `ppb secrets set`, from a file or stdin.",
|
|
18215
18212
|
inputSchema: {
|
|
18216
18213
|
project: projectKeySchema,
|
|
18217
18214
|
environment: external_exports.string().min(1),
|
|
@@ -18220,7 +18217,7 @@ function createMcpServer(baseClient, apiBaseUrl, orgInstructions2 = /* @__PURE__
|
|
|
18220
18217
|
}
|
|
18221
18218
|
}, ({ project, environment, name, value }) => run(() => client2().rotateSecret(project, name, { env: environment, value })));
|
|
18222
18219
|
registerStrict("update_secret_description", {
|
|
18223
|
-
description: "
|
|
18220
|
+
description: "Change a secret\u2019s description \u2014 metadata only: no new version, and nothing here returns a value. See the doctrine `get_work_context` returns, under \u201CHow to write here\u201D.",
|
|
18224
18221
|
inputSchema: {
|
|
18225
18222
|
project: projectKeySchema,
|
|
18226
18223
|
environment: external_exports.string().min(1),
|
|
@@ -18229,7 +18226,7 @@ function createMcpServer(baseClient, apiBaseUrl, orgInstructions2 = /* @__PURE__
|
|
|
18229
18226
|
}
|
|
18230
18227
|
}, ({ project, environment, name, description }) => run(() => client2().updateSecretDescription(project, name, { env: environment, description })));
|
|
18231
18228
|
registerStrict("delete_secret", {
|
|
18232
|
-
description: "Delete a secret: soft-delete the entry and crypto-shred
|
|
18229
|
+
description: "Delete a secret: soft-delete the entry and crypto-shred every version, so the value is unrecoverable. Returns confirmation metadata only.",
|
|
18233
18230
|
inputSchema: {
|
|
18234
18231
|
project: projectKeySchema,
|
|
18235
18232
|
environment: external_exports.string().min(1),
|
|
@@ -18240,14 +18237,14 @@ function createMcpServer(baseClient, apiBaseUrl, orgInstructions2 = /* @__PURE__
|
|
|
18240
18237
|
return { deleted: true, project, environment, name };
|
|
18241
18238
|
}));
|
|
18242
18239
|
registerStrict("list_org_instructions", {
|
|
18243
|
-
description: "List
|
|
18240
|
+
description: "List this organization\u2019s agent instructions, one per scope \u2014 the texts composed into the guidance `get_work_context` returns. What is saved here may be newer than what your session composed at startup."
|
|
18244
18241
|
}, () => run(() => client2().listOrgInstructions(), GUIDANCE_POINTER));
|
|
18245
18242
|
registerStrict("set_org_instruction", {
|
|
18246
|
-
description: "Set
|
|
18243
|
+
description: "Set one scope\u2019s organization-level agent instruction, up to 4000 characters; over that is refused, not truncated. Scopes are `general`, `requirements`, `content` and `work.<type>`. It writes the organization this call acts in, so passing `org` rewrites that organization\u2019s standing instructions to every agent working there. It needs the ADMIN role there, so a contributor is refused. New sessions pick it up; running ones keep what they started with. See the doctrine `get_work_context` returns, under \u201CSetting an instruction replaces the whole body\u201D.",
|
|
18247
18244
|
inputSchema: setOrgInstructionInputSchema.shape
|
|
18248
18245
|
}, (args) => run(() => client2().setOrgInstruction(args.scope, args.body)));
|
|
18249
18246
|
registerStrict("delete_org_instruction", {
|
|
18250
|
-
description: "Clear one scope\u2019s
|
|
18247
|
+
description: "Clear one scope\u2019s organization-level agent instruction, in the organization named by `org` \u2014 so this removes that organization\u2019s standing instruction to every agent working there. Clearing a scope that is already unset is an error naming it. It needs the ADMIN role in that organization, so a contributor is refused. New sessions pick up the change; running ones keep what they started with.",
|
|
18251
18248
|
inputSchema: deleteOrgInstructionInputSchema.shape
|
|
18252
18249
|
}, (args) => run(() => client2().deleteOrgInstruction(args.scope)));
|
|
18253
18250
|
return server2;
|
|
@@ -18316,7 +18313,7 @@ function loadMcpConfig() {
|
|
|
18316
18313
|
}
|
|
18317
18314
|
|
|
18318
18315
|
// src/version.ts
|
|
18319
|
-
var VERSION = "0.
|
|
18316
|
+
var VERSION = "0.51.0";
|
|
18320
18317
|
|
|
18321
18318
|
// src/main.ts
|
|
18322
18319
|
async function scopedClient(base, org) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pipebrain-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.51.0",
|
|
4
4
|
"description": "Pipebrain MCP server for Claude Code and Claude Desktop — issues, requirements, decision records, conventions, prompts, content, assets, metadata-only secrets, and more of the platform's modules over the Pipebrain API.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|