pipebrain-mcp 0.60.0 → 0.61.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.
Files changed (3) hide show
  1. package/README.md +1 -1
  2. package/dist/main.js +2 -259
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # pipebrain-mcp
2
2
 
3
3
  The [Pipebrain](https://pipebrain.dev) MCP server: issues, requirements, decision records,
4
- conventions, content drafts and deliverables, assets, milestones, metadata-only secrets,
4
+ conventions, content drafts and deliverables, milestones, metadata-only secrets,
5
5
  and more of the platform's modules, exposed to Claude Code and Claude Desktop
6
6
  over stdio.
7
7
 
package/dist/main.js CHANGED
@@ -432,74 +432,6 @@ function agentConfigNotice(status) {
432
432
  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;
433
433
  }
434
434
 
435
- // ../../libs/agent-shared/dist/asset-transfer.js
436
- import { createHash as createHash2 } from "node:crypto";
437
- import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync } from "node:fs";
438
- import { basename, extname } from "node:path";
439
- var MIME_BY_EXT = {
440
- ".png": "image/png",
441
- ".jpg": "image/jpeg",
442
- ".jpeg": "image/jpeg",
443
- ".gif": "image/gif",
444
- ".webp": "image/webp",
445
- ".svg": "image/svg+xml",
446
- ".pdf": "application/pdf",
447
- ".txt": "text/plain",
448
- ".md": "text/markdown",
449
- ".json": "application/json",
450
- ".csv": "text/csv",
451
- ".html": "text/html",
452
- ".mp4": "video/mp4",
453
- ".mov": "video/quicktime",
454
- ".zip": "application/zip"
455
- };
456
- function guessContentType(filename) {
457
- return MIME_BY_EXT[extname(filename).toLowerCase()] ?? "application/octet-stream";
458
- }
459
- async function uploadAssetFromPath(client2, apiBaseUrl, input) {
460
- if (!existsSync2(input.path))
461
- throw new Error(`file not found: ${input.path}`);
462
- const bytes = readFileSync2(input.path);
463
- const filename = basename(input.path);
464
- const contentType = input.contentType ?? guessContentType(filename);
465
- const sha256 = createHash2("sha256").update(bytes).digest("hex");
466
- const { asset, uploadUrl } = await client2.createAsset({
467
- project: input.project,
468
- filename,
469
- contentType,
470
- size: bytes.length,
471
- sha256
472
- });
473
- const base = apiBaseUrl.replace(/\/+$/, "");
474
- const target = uploadUrl.startsWith("http") ? uploadUrl : base + uploadUrl;
475
- const put = await fetch(target, {
476
- method: "PUT",
477
- headers: { "content-type": contentType },
478
- body: bytes
479
- });
480
- if (!put.ok) {
481
- throw new Error(`upload PUT failed (${put.status} ${put.statusText}).`);
482
- }
483
- const verified = await client2.completeAsset(asset.id);
484
- if (verified.status !== "active") {
485
- throw new Error(`verification failed (${verified.status}${verified.rejectReason ? `: ${verified.rejectReason}` : ""}).`);
486
- }
487
- return verified;
488
- }
489
- async function downloadAssetToPath(client2, input) {
490
- const asset = await client2.getAsset(input.asset);
491
- if (asset.status !== "active") {
492
- throw new Error(`asset ${input.asset} is ${asset.status}, not active \u2014 no bytes to download.`);
493
- }
494
- const buf = Buffer.from(await client2.downloadAsset(input.asset));
495
- const got = createHash2("sha256").update(buf).digest("hex");
496
- if (got !== asset.sha256) {
497
- throw new Error(`integrity check failed: downloaded sha256 ${got} != recorded ${asset.sha256}.`);
498
- }
499
- writeFileSync(input.path, buf);
500
- return { asset, path: input.path, bytesWritten: buf.length };
501
- }
502
-
503
435
  // ../../libs/agent-shared/dist/binding.js
504
436
  function apiBindingLabel(apiBaseUrl) {
505
437
  return /\/\/(localhost|127\.0\.0\.1|0\.0\.0\.0|\[::1\])(:|\/|$)/.test(apiBaseUrl) ? "LOCAL dev" : "production/remote";
@@ -15078,15 +15010,6 @@ var inviteRoleSchema = external_exports.enum(["ADMIN", "CONTRIBUTOR"]);
15078
15010
  var requirementLifecycleSchema = external_exports.enum(["draft", "approved", "amended"]);
15079
15011
  var decisionStatusSchema = external_exports.enum(["proposed", "accepted", "superseded"]);
15080
15012
  var platformSchema = external_exports.enum(["bsky", "x", "reddit", "hn", "blog"]);
15081
- var assetStatusSchema = external_exports.enum(["pending", "active", "rejected"]);
15082
- var assetLinkTargetSchema = external_exports.enum([
15083
- "deliverable_revision",
15084
- "content_draft",
15085
- "issue",
15086
- "requirement",
15087
- "run",
15088
- "criterion"
15089
- ]);
15090
15013
  var instructionScopeSchema = external_exports.enum([
15091
15014
  "general",
15092
15015
  "requirements",
@@ -15141,9 +15064,6 @@ var SECRET_NAME_RE = /^[A-Z_][A-Z0-9_]*$/;
15141
15064
  var secretNameSchema = external_exports.string().regex(SECRET_NAME_RE, {
15142
15065
  message: "secret name must be UPPER_SNAKE_CASE: start with A\u2013Z or _, then only A\u2013Z, 0\u20139, or _ (e.g. API_KEY)"
15143
15066
  }).max(128, { message: "secret name must be at most 128 characters" });
15144
- var sha256Schema = external_exports.string().regex(/^[a-fA-F0-9]{64}$/, {
15145
- message: "expected a hex-encoded SHA-256 (64 hex characters)"
15146
- });
15147
15067
  var metadataSchema = external_exports.object({
15148
15068
  system: external_exports.record(external_exports.string(), external_exports.unknown()).default({}),
15149
15069
  client: external_exports.record(external_exports.string(), external_exports.unknown()).default({})
@@ -15799,47 +15719,6 @@ var issueDeliverableLinkSchema = external_exports.object({
15799
15719
  createdAt: external_exports.iso.datetime({ offset: true }),
15800
15720
  deletedAt: external_exports.iso.datetime({ offset: true }).nullable()
15801
15721
  });
15802
- var assetSchema = external_exports.object({
15803
- id: uuidSchema,
15804
- // The asset's own identifier (PIPE-362), `ASSET-{n}`: numbered per ORGANISATION, the
15805
- // way conventions are, because an asset may belong to no project and may move between
15806
- // projects — a per-project number would be impossible for one and stale for the other.
15807
- displayId: external_exports.string().min(1),
15808
- orgId: uuidSchema,
15809
- // Null for an ORG-level asset (a chat attachment, PIPE-15): assets were
15810
- // project-scoped until chat introduced org-level files. Project assets keep a
15811
- // uuid; the DB column and every read path (assetToWire) carry the null through.
15812
- projectId: uuidSchema.nullable(),
15813
- filename: external_exports.string().min(1),
15814
- contentType: external_exports.string().min(1),
15815
- size: positiveIntSchema,
15816
- sha256: sha256Schema,
15817
- status: assetStatusSchema,
15818
- // Set only when `status === 'rejected'`: one of object_missing / size_mismatch /
15819
- // digest_mismatch / too_large.
15820
- rejectReason: external_exports.string().nullable(),
15821
- // Whether a server-generated thumbnail object exists for this asset (images only,
15822
- // best-effort at verify). The key itself is internal, like storageKey — the bytes
15823
- // are served through a separate presigned-GET route, never a value field here.
15824
- hasThumbnail: external_exports.boolean(),
15825
- // Count of live (non-soft-deleted) asset_links referencing this asset — derived
15826
- // server-side; 0 when unlinked (and only then is the asset safe to delete).
15827
- linkCount: external_exports.number().int().nonnegative(),
15828
- createdBy: uuidSchema.nullable(),
15829
- createdAt: external_exports.iso.datetime({ offset: true }),
15830
- verifiedAt: external_exports.iso.datetime({ offset: true }).nullable(),
15831
- deletedAt: external_exports.iso.datetime({ offset: true }).nullable()
15832
- });
15833
- var assetLinkSchema = external_exports.object({
15834
- id: uuidSchema,
15835
- orgId: uuidSchema,
15836
- assetId: uuidSchema,
15837
- targetType: assetLinkTargetSchema,
15838
- targetId: uuidSchema,
15839
- createdBy: uuidSchema.nullable(),
15840
- createdAt: external_exports.iso.datetime({ offset: true }),
15841
- deletedAt: external_exports.iso.datetime({ offset: true }).nullable()
15842
- });
15843
15722
  var statusHistoryEntrySchema = external_exports.object({
15844
15723
  id: uuidSchema,
15845
15724
  orgId: uuidSchema,
@@ -16477,47 +16356,6 @@ var listContentFilterSchema = external_exports.strictObject({
16477
16356
  limit: external_exports.number().int().min(1, { message: "limit must be at least 1" }).max(500, { message: "limit must be at most 500" }).optional(),
16478
16357
  offset: external_exports.number().int().nonnegative().optional()
16479
16358
  });
16480
- var createAssetInputSchema = external_exports.strictObject({
16481
- // Optional since PIPE-15: omit for an ORG-level asset (a chat attachment, stored
16482
- // under `staging/org/{orgId}/…`). A project ref scopes the asset to that project.
16483
- project: projectRefSchema.optional(),
16484
- filename: external_exports.string().min(1, { message: "filename must not be empty" }),
16485
- contentType: external_exports.string().min(1, { message: "content type must not be empty" }),
16486
- size: positiveIntSchema,
16487
- sha256: sha256Schema
16488
- });
16489
- var uploadAssetInputSchema = external_exports.strictObject({
16490
- // Optional since PIPE-15: omit to upload an ORG-level asset (not scoped to any
16491
- // project). A project ref scopes the upload to that project.
16492
- project: projectRefSchema.optional(),
16493
- path: external_exports.string().min(1, { message: "path must not be empty \u2014 give the local file to upload" }),
16494
- contentType: external_exports.string().min(1, { message: "content type must not be empty" }).optional()
16495
- });
16496
- var downloadAssetInputSchema = external_exports.strictObject({
16497
- asset: uuidSchema,
16498
- path: external_exports.string().min(1, { message: "path must not be empty \u2014 give the local file to write" })
16499
- });
16500
- var linkAssetInputSchema = external_exports.strictObject({
16501
- asset: external_exports.string().min(1, { message: "asset reference must not be empty" }),
16502
- targetType: external_exports.enum(["deliverable_revision", "content_draft"]),
16503
- targetId: uuidSchema
16504
- });
16505
- var unlinkAssetInputSchema = external_exports.strictObject({
16506
- linkId: uuidSchema
16507
- });
16508
- var assetCategorySchema = external_exports.enum(["image", "video", "document"]);
16509
- var assetSortSchema = external_exports.enum(["date", "size"]);
16510
- var sortDirSchema = external_exports.enum(["asc", "desc"]);
16511
- var listAssetsFilterSchema = external_exports.strictObject({
16512
- project: projectRefSchema.optional(),
16513
- status: external_exports.array(assetStatusSchema).optional(),
16514
- category: assetCategorySchema.optional(),
16515
- sort: assetSortSchema.optional(),
16516
- dir: sortDirSchema.optional(),
16517
- text: external_exports.string().optional(),
16518
- limit: external_exports.number().int().min(1, { message: "limit must be at least 1" }).max(500, { message: "limit must be at most 500" }).optional(),
16519
- offset: external_exports.number().int().nonnegative().optional()
16520
- });
16521
16359
  var setOrgInstructionInputSchema = external_exports.strictObject({
16522
16360
  scope: instructionScopeSchema,
16523
16361
  body: external_exports.string().min(1, { message: "instruction body must not be empty \u2014 clear the scope instead" }).max(4e3, { message: "instruction body must be at most 4000 characters" })
@@ -17068,73 +16906,6 @@ var ApiClient = class _ApiClient {
17068
16906
  const { contentRef, platform } = splitDeliverableRef(deliverableRef);
17069
16907
  return this.request("POST", `/api/v1/content/${enc(contentRef)}/deliverables/${enc(platform)}/promote`, { body: input });
17070
16908
  }
17071
- // --- assets ----------------------------------------------------------------
17072
- createAsset(input) {
17073
- return this.request("POST", "/api/v1/assets", { body: input });
17074
- }
17075
- completeAsset(id) {
17076
- return this.request("POST", `/api/v1/assets/${enc(id)}/complete`);
17077
- }
17078
- listAssets(filter = {}) {
17079
- return this.requestList("GET", "/api/v1/assets", { query: filter });
17080
- }
17081
- getAsset(id) {
17082
- return this.request("GET", `/api/v1/assets/${enc(id)}`);
17083
- }
17084
- /**
17085
- * The presigned-GET download route (reveal plane) — navigate/fetch with auth; server
17086
- * 302s to S3. Prefixed with `baseUrl` (PIPE-180): relative under the web's
17087
- * same-origin '' base, absolute when the client targets a separate API origin.
17088
- */
17089
- assetDownloadPath(id) {
17090
- return `${this.baseUrl}/api/v1/assets/${enc(id)}/download`;
17091
- }
17092
- /**
17093
- * Fetches an active asset's raw bytes via the download route, following the 302 to
17094
- * the presigned GET (a cross-origin redirect drops the auth header, so S3 gets a
17095
- * clean presigned request). Returns the bytes for a caller to write to a file — the
17096
- * MCP `download_asset` tool and the CLI write to a local path and surface metadata
17097
- * only, so bytes never cross a tool boundary. `active` assets only (409 otherwise).
17098
- */
17099
- async downloadAsset(id) {
17100
- let response;
17101
- try {
17102
- response = await this.fetchImpl(this.buildUrl(`/api/v1/assets/${enc(id)}/download`, void 0), {
17103
- method: "GET",
17104
- headers: this.authHeaders(),
17105
- redirect: "follow",
17106
- signal: AbortSignal.timeout(this.timeoutMs)
17107
- });
17108
- } catch (err) {
17109
- throw toTransportError(err, this.timeoutMs);
17110
- }
17111
- if (!response.ok) {
17112
- const rawText = await response.text();
17113
- throw toApiError(response.status, rawText.length === 0 ? void 0 : safeJsonParse(rawText), rawText);
17114
- }
17115
- return response.arrayBuffer();
17116
- }
17117
- /**
17118
- * The presigned-GET thumbnail route (reveal plane) — an `<img src>` follows the 302;
17119
- * 404 ⇒ no thumbnail. Prefixed with `baseUrl` like {@link assetDownloadPath}.
17120
- */
17121
- assetThumbnailPath(id) {
17122
- return `${this.baseUrl}/api/v1/assets/${enc(id)}/thumbnail`;
17123
- }
17124
- /** Soft-delete an asset — 409 if it still has live references (delete is web/CLI only). */
17125
- deleteAsset(id) {
17126
- return this.request("DELETE", `/api/v1/assets/${enc(id)}`);
17127
- }
17128
- /** The live references TO an asset, resolved to display labels (the asset-detail panel). */
17129
- listAssetLinks(id) {
17130
- return this.requestList("GET", `/api/v1/assets/${enc(id)}/links`);
17131
- }
17132
- linkAsset(input) {
17133
- return this.request("POST", "/api/v1/asset-links", { body: input });
17134
- }
17135
- unlinkAsset(linkId) {
17136
- return this.request("DELETE", `/api/v1/asset-links/${enc(linkId)}`);
17137
- }
17138
16909
  // --- projects --------------------------------------------------------------
17139
16910
  createProject(input) {
17140
16911
  return this.request("POST", "/api/v1/projects", { body: input });
@@ -17640,7 +17411,6 @@ function createMcpServer(baseClient, apiBaseUrl, orgInstructions2 = /* @__PURE__
17640
17411
  return createMcpServerWithRegistry(baseClient, apiBaseUrl, orgInstructions2, opts).server;
17641
17412
  }
17642
17413
  function createMcpServerWithRegistry(baseClient, apiBaseUrl, orgInstructions2 = /* @__PURE__ */ new Map(), opts = {}) {
17643
- const surface = opts.surface ?? "local";
17644
17414
  const registry2 = [];
17645
17415
  const scope = new AsyncLocalStorage();
17646
17416
  function client2() {
@@ -18173,7 +17943,7 @@ function createMcpServerWithRegistry(baseClient, apiBaseUrl, orgInstructions2 =
18173
17943
  inputSchema: listContentFilterSchema.shape
18174
17944
  }, (args) => run(() => client2().listContent(args), GUIDANCE_POINTER));
18175
17945
  registerStrict("get_content", {
18176
- 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.",
17946
+ description: "Get a content draft by display id (`KEY-C{n}`) or uuid: its revisions, latest or the `revision` you name, plus every deliverable and linked issue.",
18177
17947
  inputSchema: { content: contentRefSchema, revision: positiveIntSchema.optional() }
18178
17948
  }, (args) => run(() => client2().getContent(args.content, args.revision), GUIDANCE_POINTER));
18179
17949
  registerStrict("create_content", {
@@ -18222,32 +17992,6 @@ function createMcpServerWithRegistry(baseClient, apiBaseUrl, orgInstructions2 =
18222
17992
  ...refusedIssueHeaderFields
18223
17993
  }
18224
17994
  }, ({ deliverable, ...fields }) => run(() => client2().promoteDeliverable(deliverable, fields)));
18225
- registerStrict("list_assets", {
18226
- 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.",
18227
- inputSchema: listAssetsFilterSchema.shape
18228
- }, (args) => run(() => client2().listAssets(args), GUIDANCE_POINTER));
18229
- registerStrict("get_asset", {
18230
- 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`.",
18231
- inputSchema: { asset: uuidSchema }
18232
- }, (args) => run(() => client2().getAsset(args.asset), GUIDANCE_POINTER));
18233
- if (surface === "local") {
18234
- registerStrict("upload_asset", {
18235
- 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.",
18236
- inputSchema: uploadAssetInputSchema.shape
18237
- }, (args) => run(() => uploadAssetFromPath(client2(), apiBaseUrl, args)));
18238
- registerStrict("download_asset", {
18239
- description: "Write an ACTIVE asset\u2019s bytes to a local file at `path`, created or overwritten. Addressed by asset uuid. The bytes are checked against the recorded sha256; the result is metadata and the path.",
18240
- inputSchema: downloadAssetInputSchema.shape
18241
- }, (args) => run(() => downloadAssetToPath(client2(), args)));
18242
- }
18243
- registerStrict("link_asset", {
18244
- 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.",
18245
- inputSchema: linkAssetInputSchema.shape
18246
- }, (args) => run(() => client2().linkAsset(args)));
18247
- registerStrict("unlink_asset", {
18248
- description: "Remove an asset reference by its link id. This drops the reference only; the asset itself is untouched. Any member can remove any link.",
18249
- inputSchema: unlinkAssetInputSchema.shape
18250
- }, (args) => run(() => client2().unlinkAsset(args.linkId)));
18251
17995
  registerStrict("set_secret", {
18252
17996
  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 \u201CA secret value never travels in a tool call\u201D in the doctrine `get_work_context` returns.",
18253
17997
  inputSchema: {
@@ -18387,7 +18131,7 @@ function loadMcpConfig() {
18387
18131
  }
18388
18132
 
18389
18133
  // src/version.ts
18390
- var VERSION = "0.60.0";
18134
+ var VERSION = "0.61.0";
18391
18135
 
18392
18136
  // src/main.ts
18393
18137
  async function scopedClient(base, org) {
@@ -18411,7 +18155,6 @@ var client = await scopedClient(
18411
18155
  var orgInstructions = await fetchOrgInstructionsSafe(client);
18412
18156
  var server = createMcpServer(client, config2.apiUrl, orgInstructions, {
18413
18157
  version: VERSION,
18414
- surface: "local",
18415
18158
  // The writing rules follow the organization the CALL acts in (PIPE-410). Without this,
18416
18159
  // a `get_work_context` naming another organization returns that organization's work,
18417
18160
  // requirements and conventions under THIS one's rules, in one document, with nothing in
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pipebrain-mcp",
3
- "version": "0.60.0",
4
- "description": "Pipebrain MCP server for Claude Code and Claude Desktop — issues, requirements, decision records, conventions, content, assets, metadata-only secrets, and more of the platform's modules over the Pipebrain API.",
3
+ "version": "0.61.0",
4
+ "description": "Pipebrain MCP server for Claude Code and Claude Desktop — issues, requirements, decision records, conventions, content, metadata-only secrets, and more of the platform's modules over the Pipebrain API.",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "main": "./dist/main.js",