@productbrain/mcp 0.0.1-beta.179 → 0.0.1-beta.180

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.
@@ -49,7 +49,8 @@ function newKeyState() {
49
49
  apiKeyScope: "readwrite",
50
50
  sessionOriented: false,
51
51
  sessionClosed: false,
52
- lastAccess: Date.now()
52
+ lastAccess: Date.now(),
53
+ deploymentUrl: null
53
54
  };
54
55
  }
55
56
  function getKeyState(apiKey) {
@@ -152,7 +153,8 @@ var _stdioState = {
152
153
  apiKeyScope: "readwrite",
153
154
  sessionOriented: false,
154
155
  sessionClosed: false,
155
- lastAccess: 0
156
+ lastAccess: 0,
157
+ deploymentUrl: null
156
158
  };
157
159
  function state() {
158
160
  const reqKey = getRequestApiKey();
@@ -278,10 +280,44 @@ function bootstrap() {
278
280
  function bootstrapHttp() {
279
281
  process.env.CONVEX_SITE_URL ??= process.env.PRODUCTBRAIN_URL ?? DEFAULT_CLOUD_URL;
280
282
  }
281
- function getEnv(key) {
282
- const value = process.env[key];
283
- if (!value) throw new Error(`${key} environment variable is required`);
284
- return value;
283
+ function parseFallbackUrls() {
284
+ const raw = process.env.CONVEX_FALLBACK_URLS;
285
+ if (!raw) return [];
286
+ return raw.split(",").map((u) => u.trim()).filter(Boolean);
287
+ }
288
+ async function resolveDeploymentUrl() {
289
+ const s = state();
290
+ if (s.deploymentUrl) return s.deploymentUrl;
291
+ const primaryUrl = (process.env.CONVEX_SITE_URL ?? DEFAULT_CLOUD_URL).replace(/\/$/, "");
292
+ const fallbacks = parseFallbackUrls();
293
+ if (fallbacks.length === 0) {
294
+ return primaryUrl;
295
+ }
296
+ const candidates = [primaryUrl, ...fallbacks.map((u) => u.replace(/\/$/, ""))];
297
+ let apiKey;
298
+ try {
299
+ apiKey = getActiveApiKey();
300
+ } catch {
301
+ return primaryUrl;
302
+ }
303
+ for (const candidate of candidates) {
304
+ try {
305
+ const probeRes = await fetch(`${candidate}/api/key-check`, {
306
+ method: "POST",
307
+ headers: { "Authorization": `Bearer ${apiKey}`, "Content-Type": "application/json" },
308
+ signal: AbortSignal.timeout(3e3)
309
+ });
310
+ if (probeRes.ok) {
311
+ const data = await probeRes.json();
312
+ if (data.ok) {
313
+ s.deploymentUrl = candidate;
314
+ return candidate;
315
+ }
316
+ }
317
+ } catch {
318
+ }
319
+ }
320
+ return candidates[0];
285
321
  }
286
322
  function shouldLogAudit(status) {
287
323
  return status === "error" || process.env.MCP_DEBUG === "1";
@@ -318,7 +354,7 @@ var TOUCH_EXCLUDED = /* @__PURE__ */ new Set([
318
354
  "agent.closeSession"
319
355
  ]);
320
356
  async function callGateway(fn, args) {
321
- const siteUrl = getEnv("CONVEX_SITE_URL").replace(/\/$/, "");
357
+ const siteUrl = await resolveDeploymentUrl();
322
358
  const apiKey = getActiveApiKey();
323
359
  const start = Date.now();
324
360
  let res;
@@ -1878,7 +1914,7 @@ Reason: ${resolved.reasoning || "low signal"}
1878
1914
  Choose one of these and retry with \`collection\`:
1879
1915
  ${suggestions}
1880
1916
 
1881
- Correction path: rerun with your chosen \`collection\`, or capture and use \`entries action=move\` later.`;
1917
+ Correction path: rerun with your chosen \`collection\`, or capture and use \`move-entry\` later.`;
1882
1918
  return {
1883
1919
  content: [{ type: "text", text: textBody }],
1884
1920
  structuredContent: failure(
@@ -3334,7 +3370,7 @@ Use \`entries action=get\` to inspect the existing entry, or \`update-entry\` to
3334
3370
  lines.push(`- **${r.entryId}**: ${r.name} [${r.collection}] \u2014 \`${r.status}\` (${r.classifiedBy} ${r.confidence}%)${linkNote}`);
3335
3371
  }
3336
3372
  lines.push(`
3337
- _Use \`entries action=move\` to correct any misclassified entries._`);
3373
+ _Use \`move-entry\` to correct any misclassified entries._`);
3338
3374
  }
3339
3375
  }
3340
3376
  if (skippedLowConfidence.length > 0) {
@@ -4160,20 +4196,23 @@ function sanitizeEntryData(data) {
4160
4196
  );
4161
4197
  return Object.keys(filtered).length > 0 ? filtered : void 0;
4162
4198
  }
4163
- var ENTRIES_ACTIONS = ["list", "get", "batch", "search", "move"];
4199
+ var ENTRIES_ACTIONS = ["list", "get", "batch", "search"];
4164
4200
  var entriesSchema = z4.object({
4165
4201
  action: z4.enum(ENTRIES_ACTIONS).describe(
4166
- "'list': browse entries with filters. 'get': fetch one entry by ID. 'batch': fetch multiple entries. 'search': full-text search. 'move': move entry to a different collection."
4202
+ "'list': browse entries with filters. 'get': fetch one entry by ID. 'batch': fetch multiple entries. 'search': full-text search."
4167
4203
  ),
4168
- entryId: z4.string().optional().describe("Entry ID for get/move action, e.g. 'DEC-42', 'BR-001'"),
4204
+ entryId: z4.string().optional().describe("Entry ID for get action, e.g. 'DEC-42', 'BR-001'"),
4169
4205
  entryIds: z4.array(z4.string()).min(1).max(20).optional().describe("Entry IDs for batch action, e.g. ['TYPE-strategy', 'STR-jljeg7']"),
4170
4206
  collection: z4.string().optional().describe("Collection slug for list/search, e.g. 'glossary', 'tracking-events', 'business-rules'"),
4171
- toCollection: z4.string().optional().describe("Target collection slug for move action, e.g. 'decisions', 'architecture'"),
4172
4207
  status: z4.string().optional().describe("Filter: draft | active | deprecated | archived"),
4173
4208
  tag: z4.string().optional().describe("Filter by internal tag for list"),
4174
4209
  label: z4.string().optional().describe("Filter by label slug for list \u2014 matches entries across all collections"),
4175
4210
  query: z4.string().optional().describe("Search text for search action (min 2 characters)")
4176
4211
  });
4212
+ var moveEntrySchema = z4.object({
4213
+ entryId: z4.string().describe("Entry ID to move, e.g. 'DEC-42', 'BR-001'"),
4214
+ toCollection: z4.string().describe("Target collection slug, e.g. 'decisions', 'architecture'")
4215
+ });
4177
4216
  var entriesGetOutputSchema = z4.object({
4178
4217
  entryId: z4.string(),
4179
4218
  name: z4.string(),
@@ -4235,14 +4274,14 @@ function registerEntriesTools(server) {
4235
4274
  "entries",
4236
4275
  {
4237
4276
  title: "Entries",
4238
- description: 'Read entries from the Chain. One tool for all entry reading.\n\n- **list**: Browse entries with optional filters (collection, status, tag, label). Use collections action=list first to discover slugs.\n- **get**: Fetch a single entry by ID \u2014 full record with data, labels, relations, history.\n- **batch**: Fetch multiple entries (max 20) in one call. Same shape as get per entry.\n- **search**: Full-text search across entries. Scope by collection or filter by status.\n- **move**: Move an entry to a different collection. Use when classifier misrouted or user wants to reclassify.\n\nUse `entries action=get entryId="..."` to fetch one. Use `entries action=search query="..."` to discover.',
4277
+ description: 'Read entries from the Chain. One tool for all entry reading.\n\n- **list**: Browse entries with optional filters (collection, status, tag, label). Use collections action=list first to discover slugs.\n- **get**: Fetch a single entry by ID \u2014 full record with data, labels, relations, history.\n- **batch**: Fetch multiple entries (max 20) in one call. Same shape as get per entry.\n- **search**: Full-text search across entries. Scope by collection or filter by status.\n\nUse `entries action=get entryId="..."` to fetch one. Use `entries action=search query="..."` to discover. To reclassify, use `move-entry`.',
4239
4278
  inputSchema: entriesSchema,
4240
- annotations: { idempotentHint: false, openWorldHint: false }
4279
+ annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false }
4241
4280
  },
4242
4281
  thinWrapper(async (args) => {
4243
4282
  const parsed = parseOrFail(entriesSchema, args);
4244
4283
  if (!parsed.ok) return parsed.result;
4245
- const { action, entryId, entryIds, collection, toCollection, status, tag, label, query } = parsed.data;
4284
+ const { action, entryId, entryIds, collection, status, tag, label, query } = parsed.data;
4246
4285
  return runWithToolContext({ tool: "entries", action }, async () => {
4247
4286
  if (action === "get") {
4248
4287
  if (!entryId) {
@@ -4265,15 +4304,27 @@ function registerEntriesTools(server) {
4265
4304
  if (action === "list") {
4266
4305
  return handleList(collection, status, tag, label);
4267
4306
  }
4268
- if (action === "move") {
4269
- if (!entryId) return validationResult("entryId is required when action is 'move'.");
4270
- if (!toCollection) return validationResult("toCollection is required when action is 'move'.");
4271
- return handleMove(entryId, toCollection);
4272
- }
4273
4307
  return unknownAction(action, ENTRIES_ACTIONS);
4274
4308
  });
4275
4309
  })
4276
4310
  );
4311
+ server.registerTool(
4312
+ "move-entry",
4313
+ {
4314
+ title: "Move Entry",
4315
+ description: 'Move an entry to a different collection. Use when the classifier misrouted or the user wants to reclassify.\n\nExample: `move-entry entryId="DEC-42" toCollection="tensions"` reclassifies DEC-42 into the tensions collection.',
4316
+ inputSchema: moveEntrySchema,
4317
+ annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false }
4318
+ },
4319
+ thinWrapper(async (args) => {
4320
+ const parsed = parseOrFail(moveEntrySchema, args);
4321
+ if (!parsed.ok) return parsed.result;
4322
+ return runWithToolContext(
4323
+ { tool: "move-entry", action: "move" },
4324
+ () => handleMove(parsed.data.entryId, parsed.data.toCollection)
4325
+ );
4326
+ })
4327
+ );
4277
4328
  }
4278
4329
  async function handleGet(entryId) {
4279
4330
  const entry = await kernelQuery("chain.getEntry", { entryId });
@@ -4953,37 +5004,6 @@ async function handleCreate(from, to, type, score, preview) {
4953
5004
  sessionId: agentSessionId ?? void 0,
4954
5005
  ...preview ? { preview: true } : {}
4955
5006
  });
4956
- if (result?.chainDebtPrevented) {
4957
- const alts = result.suggestedAlternatives ?? [];
4958
- const altList = alts.map((a) => ` \u2022 \`${a.type}\`: ${a.reason}`).join("\n");
4959
- return {
4960
- content: [{
4961
- type: "text",
4962
- text: `# Chain Debt Prevented
4963
-
4964
- **"${result.originalType ?? type}"** was intercepted as a common misuse for this entry pair and converted to an agent sign-off proposal.
4965
-
4966
- **Proposal ID:** \`${result.proposalId}\`
4967
-
4968
- **Suggested alternatives:**
4969
- ${altList}
4970
-
4971
- Review at /review in Cortex UI.`
4972
- }],
4973
- structuredContent: success(
4974
- `Chain debt prevented: "${result.originalType ?? type}" was intercepted and converted to an agent sign-off proposal.`,
4975
- {
4976
- chainDebtPrevented: true,
4977
- proposalId: result.proposalId,
4978
- originalType: result.originalType ?? type,
4979
- suggestedAlternatives: result.suggestedAlternatives
4980
- },
4981
- [
4982
- { tool: "relations", description: "Use correct type (e.g. informed_by)", parameters: { action: "create", from, to, type: alts[0]?.type ?? "informed_by" } }
4983
- ]
4984
- )
4985
- };
4986
- }
4987
5007
  if (result?.preview) {
4988
5008
  const suggested2 = result.suggestedType;
4989
5009
  const suggestedWithPosture2 = suggested2 ? { ...suggested2, governancePosture: suggested2.type === "governs" ? "requires_consent" : "direct" } : void 0;
@@ -9800,7 +9820,7 @@ var facilitateSchema = z14.object({
9800
9820
  betEntryId: z14.string().optional().describe("Bet entry ID. Required for both actions."),
9801
9821
  operationId: z14.string().optional().describe("Optional idempotency key for commit-constellation retries.")
9802
9822
  });
9803
- function buildCortexUrl(workspaceSlug, entryId) {
9823
+ function buildStudioUrl(workspaceSlug, entryId) {
9804
9824
  const appUrl = process.env.PRODUCTBRAIN_APP_URL ?? "https://work.productbrain.io";
9805
9825
  return `${appUrl}/${workspaceSlug}/legacy/entries/${entryId}`;
9806
9826
  }
@@ -9851,7 +9871,7 @@ async function handleResume(args) {
9851
9871
  });
9852
9872
  const enrichedData = {
9853
9873
  ...envelope.data,
9854
- cortexUrl: buildCortexUrl(workspaceSlug, betId)
9874
+ studioUrl: buildStudioUrl(workspaceSlug, betId)
9855
9875
  };
9856
9876
  const bet = enrichedData.betEntry;
9857
9877
  const cx = enrichedData.constellation;
@@ -9867,7 +9887,7 @@ async function handleResume(args) {
9867
9887
  `# ${bet.name}`,
9868
9888
  "",
9869
9889
  `**ID:** \`${bet.betEntryId}\` \xB7 **Status:** ${bet.status}`,
9870
- enrichedData.cortexUrl ? `**Cortex:** ${enrichedData.cortexUrl}` : "",
9890
+ enrichedData.studioUrl ? `**Cortex:** ${enrichedData.studioUrl}` : "",
9871
9891
  "",
9872
9892
  `**Constellation:** ${constellationSummary}`,
9873
9893
  ...draftLines.length > 0 ? ["", "**Session drafts:**", ...draftLines] : []
@@ -15670,6 +15690,8 @@ function initFeatureFlags(_posthogClient) {
15670
15690
  export {
15671
15691
  hashKey,
15672
15692
  runWithAuth,
15693
+ getKeyState,
15694
+ DEFAULT_CLOUD_URL,
15673
15695
  getAgentSessionId,
15674
15696
  orphanAgentSession,
15675
15697
  bootstrap,
@@ -15680,4 +15702,4 @@ export {
15680
15702
  createProductBrainServer,
15681
15703
  initFeatureFlags
15682
15704
  };
15683
- //# sourceMappingURL=chunk-IOEZLNTK.js.map
15705
+ //# sourceMappingURL=chunk-XPFSARXP.js.map