pipebrain-mcp 0.32.0 → 0.33.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/dist/main.js +117 -13
- package/package.json +2 -2
package/dist/main.js
CHANGED
|
@@ -356,9 +356,52 @@ var require_main = __commonJS({
|
|
|
356
356
|
}
|
|
357
357
|
});
|
|
358
358
|
|
|
359
|
+
// ../../libs/agent-shared/dist/agent-config.js
|
|
360
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
361
|
+
import { homedir } from "node:os";
|
|
362
|
+
import { join } from "node:path";
|
|
363
|
+
|
|
364
|
+
// ../../libs/agent-shared/dist/agent-config-manifest.js
|
|
365
|
+
var AGENT_CONFIG_DIGEST = "898da8356dac55bd14531dcb5440aa3bd7bbcaa72e873d0fee4a102e3d718ee1";
|
|
366
|
+
|
|
367
|
+
// ../../libs/agent-shared/dist/agent-config.js
|
|
368
|
+
function agentConfigDir(env = process.env) {
|
|
369
|
+
const configured = env.CLAUDE_CONFIG_DIR;
|
|
370
|
+
return configured !== void 0 && configured !== "" ? configured : join(homedir(), ".claude");
|
|
371
|
+
}
|
|
372
|
+
var STAMP_REL = "pipebrain/installed.json";
|
|
373
|
+
function readInstalledStamp(dir) {
|
|
374
|
+
const file2 = join(dir, STAMP_REL);
|
|
375
|
+
if (!existsSync(file2))
|
|
376
|
+
return null;
|
|
377
|
+
try {
|
|
378
|
+
const parsed = JSON.parse(readFileSync(file2, "utf8"));
|
|
379
|
+
if (typeof parsed !== "object" || parsed === null)
|
|
380
|
+
return null;
|
|
381
|
+
const stamp = parsed;
|
|
382
|
+
return typeof stamp.configDigest === "string" && stamp.configDigest !== "" ? {
|
|
383
|
+
configDigest: stamp.configDigest,
|
|
384
|
+
installedBy: typeof stamp.installedBy === "string" ? stamp.installedBy : "unknown",
|
|
385
|
+
installedAt: typeof stamp.installedAt === "string" ? stamp.installedAt : "unknown"
|
|
386
|
+
} : null;
|
|
387
|
+
} catch {
|
|
388
|
+
return null;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
function agentConfigStatus(dir, shipped = AGENT_CONFIG_DIGEST) {
|
|
392
|
+
const stamp = readInstalledStamp(dir);
|
|
393
|
+
if (stamp === null)
|
|
394
|
+
return "absent";
|
|
395
|
+
return stamp.configDigest === shipped ? "current" : "behind";
|
|
396
|
+
}
|
|
397
|
+
var UPDATE_COMMAND = "ppb setup";
|
|
398
|
+
function agentConfigNotice(status) {
|
|
399
|
+
return status === "behind" ? `This machine's shared agent configuration is not the one this build ships \u2014 run \`${UPDATE_COMMAND}\` to see what would change and update it.` : null;
|
|
400
|
+
}
|
|
401
|
+
|
|
359
402
|
// ../../libs/agent-shared/dist/asset-transfer.js
|
|
360
403
|
import { createHash } from "node:crypto";
|
|
361
|
-
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
404
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync } from "node:fs";
|
|
362
405
|
import { basename, extname } from "node:path";
|
|
363
406
|
var MIME_BY_EXT = {
|
|
364
407
|
".png": "image/png",
|
|
@@ -381,9 +424,9 @@ function guessContentType(filename) {
|
|
|
381
424
|
return MIME_BY_EXT[extname(filename).toLowerCase()] ?? "application/octet-stream";
|
|
382
425
|
}
|
|
383
426
|
async function uploadAssetFromPath(client2, apiBaseUrl, input) {
|
|
384
|
-
if (!
|
|
427
|
+
if (!existsSync2(input.path))
|
|
385
428
|
throw new Error(`file not found: ${input.path}`);
|
|
386
|
-
const bytes =
|
|
429
|
+
const bytes = readFileSync2(input.path);
|
|
387
430
|
const filename = basename(input.path);
|
|
388
431
|
const contentType = input.contentType ?? guessContentType(filename);
|
|
389
432
|
const sha256 = createHash("sha256").update(bytes).digest("hex");
|
|
@@ -15589,6 +15632,22 @@ var orgInstructionSchema = external_exports.object({
|
|
|
15589
15632
|
body: external_exports.string().min(1),
|
|
15590
15633
|
createdBy: uuidSchema.nullable(),
|
|
15591
15634
|
viaTokenId: uuidSchema.nullable(),
|
|
15635
|
+
/**
|
|
15636
|
+
* sha256 of the shipped default this row was SEEDED from, or null when nothing seeded
|
|
15637
|
+
* it — a row written by a person, a row re-created after its scope was cleared, or a row
|
|
15638
|
+
* that predates seeding.
|
|
15639
|
+
*
|
|
15640
|
+
* It is what lets a settings page say the shipped default has MOVED since this
|
|
15641
|
+
* organization got it, without the product ever applying that move: comparing this digest
|
|
15642
|
+
* against the running release's default for the same scope is the whole signal. It is not
|
|
15643
|
+
* updated by an edit, because what it records is what was seeded — which stays true after
|
|
15644
|
+
* the organization rewrites the body, and is exactly then that the comparison is worth
|
|
15645
|
+
* making.
|
|
15646
|
+
*
|
|
15647
|
+
* `.default(null)` rather than a bare nullable, for the tolerance this schema keeps
|
|
15648
|
+
* throughout: a payload from a server that predates the column still parses.
|
|
15649
|
+
*/
|
|
15650
|
+
seededDefaultSha: external_exports.string().nullable().default(null),
|
|
15592
15651
|
createdAt: external_exports.iso.datetime({ offset: true }),
|
|
15593
15652
|
updatedAt: external_exports.iso.datetime({ offset: true }),
|
|
15594
15653
|
deletedAt: external_exports.iso.datetime({ offset: true }).nullable()
|
|
@@ -16105,6 +16164,12 @@ var listEnvelopeSchema = (item) => external_exports.object({ items: external_exp
|
|
|
16105
16164
|
var mintedTokenSchema = apiTokenSchema.extend({ token: external_exports.string().min(1) }).loose();
|
|
16106
16165
|
var tokenListSchema = listEnvelopeSchema(apiTokenSchema);
|
|
16107
16166
|
var orgInstructionListSchema = listEnvelopeSchema(orgInstructionSchema);
|
|
16167
|
+
var instructionDefaultSchema = external_exports.object({
|
|
16168
|
+
scope: external_exports.string().min(1),
|
|
16169
|
+
body: external_exports.string().min(1),
|
|
16170
|
+
sha256: external_exports.string().min(1)
|
|
16171
|
+
}).loose();
|
|
16172
|
+
var instructionDefaultListSchema = listEnvelopeSchema(instructionDefaultSchema);
|
|
16108
16173
|
var ApiClient = class _ApiClient {
|
|
16109
16174
|
baseUrl;
|
|
16110
16175
|
auth;
|
|
@@ -16715,6 +16780,18 @@ var ApiClient = class _ApiClient {
|
|
|
16715
16780
|
const body = await this.request("GET", "/api/v1/org-instructions", {}, orgInstructionListSchema);
|
|
16716
16781
|
return body.items;
|
|
16717
16782
|
}
|
|
16783
|
+
/**
|
|
16784
|
+
* The starting text THIS RELEASE ships, per scope, with the digest a seeded row records.
|
|
16785
|
+
*
|
|
16786
|
+
* Shipped content rather than the org's, so it carries no tenant data and any
|
|
16787
|
+
* authenticated caller may read it. Comparing a row's `seededDefaultSha` against the
|
|
16788
|
+
* `sha256` here is how a settings page says the shipped default has MOVED since this
|
|
16789
|
+
* organization was seeded — a difference the product reports and never applies.
|
|
16790
|
+
*/
|
|
16791
|
+
async listInstructionDefaults() {
|
|
16792
|
+
const body = await this.request("GET", "/api/v1/org-instructions/defaults", {}, instructionDefaultListSchema);
|
|
16793
|
+
return body.items;
|
|
16794
|
+
}
|
|
16718
16795
|
/** Upserts one scope's instruction text (ADMIN+; body ≤ 4000 chars). */
|
|
16719
16796
|
setOrgInstruction(scope, body) {
|
|
16720
16797
|
return this.request("PUT", `/api/v1/org-instructions/${enc(scope)}`, { body: { body } }, orgInstructionSchema);
|
|
@@ -16867,11 +16944,18 @@ function bindingBanner(apiBaseUrl) {
|
|
|
16867
16944
|
return `Pipebrain MCP server, bound to the ${apiBindingLabel(apiBaseUrl)} API at ${apiBaseUrl}. Every tool reads and writes THAT environment \u2014 confirm it is the one you intend before mutating.`;
|
|
16868
16945
|
}
|
|
16869
16946
|
var INDEX_RULES = "Until you have it: an open decision is filed as a question with `ask_questions` and never written into a body; search for an existing record before creating one, because a duplicate splits one thing\u2019s history across two identifiers and nothing merges them afterward; and a secret value never travels in a tool call, because a value in a call is disclosed before the server can encrypt it.";
|
|
16870
|
-
function composeIndex(base) {
|
|
16947
|
+
function composeIndex(base, machineNotice2 = null) {
|
|
16871
16948
|
return [
|
|
16872
16949
|
base,
|
|
16873
16950
|
"Call `get_work_context` before you write anything \u2014 it returns the binding guidance, the product doctrine and this organization\u2019s own writing rules, together with the work. Nothing else delivers that guidance in full; every other read only names this call.",
|
|
16874
|
-
INDEX_RULES
|
|
16951
|
+
INDEX_RULES,
|
|
16952
|
+
// LAST, and usually absent. It is about the MACHINE rather than about the work, so it
|
|
16953
|
+
// must never displace a rule: a squeezed share keeps the prefix, and losing a sentence
|
|
16954
|
+
// about an installer costs less than losing the sentence that recovers everything else.
|
|
16955
|
+
// Present only when this machine's shared configuration is not the one this build ships
|
|
16956
|
+
// (PIPE-354 AC-5) — a machine that never installed it is passed over in silence, since
|
|
16957
|
+
// it has no configuration to be behind with.
|
|
16958
|
+
...machineNotice2 === null ? [] : [machineNotice2]
|
|
16875
16959
|
].join("\n\n");
|
|
16876
16960
|
}
|
|
16877
16961
|
async function fetchOrgInstructionsSafe(client2) {
|
|
@@ -16917,6 +17001,13 @@ function successorNotice() {
|
|
|
16917
17001
|
const armed = process.env.PIPELINE_MCP_SUCCESSOR_NOTICE;
|
|
16918
17002
|
return armed === void 0 || armed === "" ? "" : SUCCESSOR_NOTICE;
|
|
16919
17003
|
}
|
|
17004
|
+
function machineNotice() {
|
|
17005
|
+
try {
|
|
17006
|
+
return agentConfigNotice(agentConfigStatus(agentConfigDir()));
|
|
17007
|
+
} catch {
|
|
17008
|
+
return null;
|
|
17009
|
+
}
|
|
17010
|
+
}
|
|
16920
17011
|
var DEFAULT_VERSION = "0.0.0";
|
|
16921
17012
|
function ok(data, appended) {
|
|
16922
17013
|
const content = [
|
|
@@ -16983,7 +17074,13 @@ function createMcpServer(client2, apiBaseUrl, orgInstructions2 = /* @__PURE__ */
|
|
|
16983
17074
|
// which environment this is bound to, the call that returns the binding guidance,
|
|
16984
17075
|
// and the three rules whose violation cannot be undone. Everything else comes from
|
|
16985
17076
|
// `docs/DOCTRINE.md` and the org's saved texts, on the work-context read.
|
|
16986
|
-
|
|
17077
|
+
//
|
|
17078
|
+
// Plus, only when there is something to say, one line about the MACHINE: its shared
|
|
17079
|
+
// agent configuration is not the one this build ships (PIPE-354 AC-5). Read here
|
|
17080
|
+
// rather than passed in, because the fact is about the filesystem this process is
|
|
17081
|
+
// running on and there is no caller better placed to know it; a read failure
|
|
17082
|
+
// degrades to silence, since a session must not fail to start over an installer.
|
|
17083
|
+
instructions: composeIndex(successorNotice() + banner, machineNotice())
|
|
16987
17084
|
});
|
|
16988
17085
|
server2.registerTool("list_projects", {
|
|
16989
17086
|
description: "List all projects with their key, environments, milestones (token, label, active), and open-issue counts \u2014 the project switcher and workflow entry point (\xA77).",
|
|
@@ -17012,10 +17109,17 @@ function createMcpServer(client2, apiBaseUrl, orgInstructions2 = /* @__PURE__ */
|
|
|
17012
17109
|
// path that has no precedent anywhere on this surface. The viewer narrowing the
|
|
17013
17110
|
// web queue offers is filter state on that view, not something an agent picks.
|
|
17014
17111
|
}, () => (
|
|
17015
|
-
// `{ items }`, mirroring the HTTP envelope rather than unwrapping to a bare
|
|
17016
|
-
//
|
|
17017
|
-
//
|
|
17018
|
-
//
|
|
17112
|
+
// `{ items }`, mirroring the HTTP envelope rather than unwrapping to a bare array
|
|
17113
|
+
// like the sibling list tools. The reason is the envelope itself: this tool is a
|
|
17114
|
+
// pass-through of one endpoint's answer, and reshaping it here would be a second
|
|
17115
|
+
// place for that answer's shape to be decided.
|
|
17116
|
+
//
|
|
17117
|
+
// The two surfaces are NOT interchangeable, and nothing here should suggest they
|
|
17118
|
+
// are. This tool takes no narrowing arguments and returns the org's WHOLE queue,
|
|
17119
|
+
// while the web view narrows what it DRAWS — by project, by category, and by viewer
|
|
17120
|
+
// scope. One endpoint decides what counts as a gate, so that definition cannot drift
|
|
17121
|
+
// between them; what an agent reads here is everything waiting, rather than whatever
|
|
17122
|
+
// the operator happens to be looking at.
|
|
17019
17123
|
run(async () => ({ items: await client2.listWaiting() }), GUIDANCE_POINTER)
|
|
17020
17124
|
));
|
|
17021
17125
|
server2.registerTool(
|
|
@@ -17352,11 +17456,11 @@ function createMcpServer(client2, apiBaseUrl, orgInstructions2 = /* @__PURE__ */
|
|
|
17352
17456
|
inputSchema: linkAssetInputSchema.shape
|
|
17353
17457
|
}, (args) => run(() => client2.linkAsset(args)));
|
|
17354
17458
|
server2.registerTool("unlink_asset", {
|
|
17355
|
-
description: "Remove an asset reference by its link id (soft-delete). The asset itself is untouched \u2014 this only drops the reference from its target.",
|
|
17459
|
+
description: "Remove an asset reference by its link id (soft-delete). The asset itself is untouched \u2014 this only drops the reference from its target. A link whose target is a CHAT MESSAGE is author-only: anyone but the person who wrote that message is refused with `not_message_author` (403), so an attachment you did not post is not yours to detach. Every other target type is removable by any member.",
|
|
17356
17460
|
inputSchema: unlinkAssetInputSchema.shape
|
|
17357
17461
|
}, (args) => run(() => client2.unlinkAsset(args.linkId)));
|
|
17358
17462
|
server2.registerTool("set_secret", {
|
|
17359
|
-
description: "Create a secret or add a new version under a project + environment (metadata only \u2014 the response NEVER echoes the value). `name` is UPPER_SNAKE_CASE (e.g. API_KEY); `environment` is the environment slug (dev/prod); optional `description`.",
|
|
17463
|
+
description: "Create a secret or add a new version under a project + environment (metadata only \u2014 the response NEVER echoes the value). `name` is UPPER_SNAKE_CASE (e.g. API_KEY); `environment` is the environment slug (dev/prod); optional `description`. Writing guidance for that description lives in the product doctrine, which `get_work_context` returns.",
|
|
17360
17464
|
inputSchema: {
|
|
17361
17465
|
project: projectKeySchema,
|
|
17362
17466
|
environment: external_exports.string().min(1),
|
|
@@ -17485,7 +17589,7 @@ function loadMcpConfig() {
|
|
|
17485
17589
|
}
|
|
17486
17590
|
|
|
17487
17591
|
// src/version.ts
|
|
17488
|
-
var VERSION = "0.
|
|
17592
|
+
var VERSION = "0.33.0";
|
|
17489
17593
|
|
|
17490
17594
|
// src/main.ts
|
|
17491
17595
|
var config2 = loadMcpConfig();
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pipebrain-mcp",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Pipebrain MCP server for Claude Code and Claude Desktop
|
|
3
|
+
"version": "0.33.0",
|
|
4
|
+
"description": "Pipebrain MCP server for Claude Code and Claude Desktop \u2014 issues, requirements, content, assets, and metadata-only secrets over the Pipebrain API.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"main": "./dist/main.js",
|