@mrrlin-dev/mcp 0.4.57 → 0.4.59

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 (2) hide show
  1. package/dist/bin.cjs +196 -106
  2. package/package.json +1 -1
package/dist/bin.cjs CHANGED
@@ -36969,7 +36969,7 @@ var ARTIFACT_CHECKS = {
36969
36969
  var ARTIFACT_DESTINATIONS = {
36970
36970
  document: "wiki",
36971
36971
  page: "git",
36972
- file_set: null,
36972
+ file_set: "artifacts",
36973
36973
  code_change: "git",
36974
36974
  structured_data: null,
36975
36975
  message: null
@@ -36978,6 +36978,52 @@ function resolveKindCapability(kind) {
36978
36978
  return ARTIFACT_DESTINATIONS[kind];
36979
36979
  }
36980
36980
 
36981
+ // ../../packages/schemas/dist/capability-refusal.js
36982
+ var REFUSAL_TEMPLATES = {
36983
+ no_route: {
36984
+ happened: ({ artifactKind, projectSlug }) => `Nothing is configured to deliver \`${artifactKind}\` for project \`${projectSlug}\`.`,
36985
+ action: ({ capability }) => `Grant an MCP server that provides the "${capability}" capability and route it on the Integrations page. Retrying before that will refuse identically.`
36986
+ },
36987
+ no_grant: {
36988
+ happened: ({ projectSlug, capability }) => `Project \`${projectSlug}\` routes "${capability}" to a server whose access has been revoked.`,
36989
+ action: () => "Grant that server again on the Integrations page, or re-point the route. Retrying before that will refuse identically."
36990
+ },
36991
+ project_mismatch: {
36992
+ happened: ({ capability }) => `The route for "${capability}" belongs to a different project than this task does.`,
36993
+ action: () => "Mrrlin refuses rather than delivering one project's work into another project's destination. Re-point the route in the task's own project."
36994
+ },
36995
+ ambiguous_route: {
36996
+ happened: ({ projectSlug, artifactKind }) => `Project \`${projectSlug}\` has more than one route that delivers \`${artifactKind}\`.`,
36997
+ action: () => "Mrrlin will not guess which one. Name the capability explicitly, or remove the route you do not want."
36998
+ },
36999
+ blocked_kind: {
37000
+ happened: ({ artifactKind }) => `Mrrlin has no destination for \`${artifactKind}\` yet.`,
37001
+ action: () => "Produce a kind that does have one, or record the result on the task instead."
37002
+ },
37003
+ gate_refused: {
37004
+ happened: ({ projectSlug }) => `The operator declined this action for project \`${projectSlug}\`.`,
37005
+ action: () => "This is an answer, not a failure \u2014 do not retry it. Ask the operator directly if it should be allowed."
37006
+ },
37007
+ transport_error: {
37008
+ happened: () => "Mrrlin could not reach the service that decides whether this delivery is allowed, so nothing was sent.",
37009
+ action: () => "This is safe to retry."
37010
+ },
37011
+ delivery_failed: {
37012
+ happened: ({ projectSlug }) => `The delivery was allowed, but the destination itself failed for project \`${projectSlug}\`.`,
37013
+ action: () => "It may have partly completed, so do not blindly retry \u2014 check whether the result already exists first."
37014
+ }
37015
+ };
37016
+ function describeCapabilityRefusal(input) {
37017
+ const ctx = {
37018
+ projectSlug: input.projectSlug,
37019
+ capability: input.capability ?? "the requested capability",
37020
+ artifactKind: input.artifactKind ?? "this artifact kind"
37021
+ };
37022
+ const template = REFUSAL_TEMPLATES[input.reason];
37023
+ const happened = typeof input.detail === "string" && input.detail.length > 0 ? input.detail : template.happened(ctx);
37024
+ return `${happened} ${template.action(ctx)}`;
37025
+ }
37026
+
36981
37027
  // ../../packages/schemas/dist/operationIds.js
36982
37028
  var openApiOperationIds = {
36983
37029
  appendExecutionArtifact: "appendExecutionArtifact",
@@ -42488,7 +42534,7 @@ Do NOT derail the current task - if the operator has an in-flight question, answ
42488
42534
  }
42489
42535
 
42490
42536
  // src/_generated/version.ts
42491
- var PKG_VERSION = "0.4.57";
42537
+ var PKG_VERSION = "0.4.59";
42492
42538
 
42493
42539
  // src/api-base-url-migrate.ts
42494
42540
  var import_node_fs4 = __toESM(require("node:fs"), 1);
@@ -48135,7 +48181,7 @@ async function driveRun(deps, projectSlug, runId) {
48135
48181
  task,
48136
48182
  runId,
48137
48183
  leaseId,
48138
- `capability_not_routed: run ${runId} targets ${task.id}, which requires artifact kind "${kind}" -- that kind has no capability at all`,
48184
+ `capability_not_routed: run ${runId} targets ${task.id}, ${describeCapabilityRefusal({ reason: "blocked_kind", projectSlug, artifactKind: kind })}`,
48139
48185
  "capability_not_routed"
48140
48186
  );
48141
48187
  return;
@@ -48148,7 +48194,12 @@ async function driveRun(deps, projectSlug, runId) {
48148
48194
  task,
48149
48195
  runId,
48150
48196
  leaseId,
48151
- `capability_not_routed: run ${runId} targets ${task.id}, which requires artifact kind "${kind}" -- no route (${routeResolution.reason})`,
48197
+ // Consensus round 1 (chunk 4): capability was omitted here, so no_route/no_grant/
48198
+ // project_mismatch's action text (which interpolates it) fell back to the literal string
48199
+ // "the requested capability" instead of naming "wiki"/"artifacts"/"git" -- resolveKindCapability
48200
+ // is non-null on this branch (the blocked_kind check above already returned if it were),
48201
+ // matching deliverBuiltIn's identical call in tools.ts.
48202
+ `capability_not_routed: run ${runId} targets ${task.id}, ${describeCapabilityRefusal({ reason: routeResolution.reason, projectSlug, artifactKind: kind, capability: resolveKindCapability(kind), detail: routeResolution.message })}`,
48152
48203
  "capability_not_routed"
48153
48204
  );
48154
48205
  return;
@@ -64924,6 +64975,81 @@ async function deliverToGit(server, toolName, payload) {
64924
64975
  return invokeCapability(server, toolName, args);
64925
64976
  }
64926
64977
 
64978
+ // src/mcp-tool-error.ts
64979
+ var McpToolCodedError = class extends Error {
64980
+ code;
64981
+ violations;
64982
+ constructor(code, message, violations) {
64983
+ super(message);
64984
+ this.name = "McpToolCodedError";
64985
+ this.code = code;
64986
+ this.violations = violations;
64987
+ }
64988
+ };
64989
+
64990
+ // src/delivery/artifacts.ts
64991
+ async function deliverToArtifacts(client, projectSlug, payload, bareFetch) {
64992
+ const { bytes: bytes3, class: artifactClass, contentType, description, filename, runId, taskId } = payload;
64993
+ const created = await client.createArtifactFile(projectSlug, {
64994
+ class: artifactClass,
64995
+ contentType,
64996
+ filename,
64997
+ runId,
64998
+ sizeBytes: bytes3.byteLength,
64999
+ taskId
65000
+ });
65001
+ let stage = "upload";
65002
+ try {
65003
+ if (created.upload.kind === "presigned-post") {
65004
+ const form = new FormData();
65005
+ for (const [field, value] of Object.entries(created.upload.fields)) {
65006
+ form.append(field, value);
65007
+ }
65008
+ form.append("file", new Blob([bytes3], { type: contentType }), filename);
65009
+ const fetchImpl = bareFetch ?? globalThis.fetch;
65010
+ const uploadResponse = await fetchImpl(created.upload.url, { body: form, method: "POST" });
65011
+ if (!uploadResponse.ok) {
65012
+ throw new Error(`Presigned upload failed: ${uploadResponse.status} ${await uploadResponse.text()}`);
65013
+ }
65014
+ } else {
65015
+ await client.uploadArtifactFileContent(projectSlug, created.artifact.id, bytes3, contentType);
65016
+ }
65017
+ stage = "complete";
65018
+ const completed = await client.completeArtifactFile(projectSlug, created.artifact.id);
65019
+ if (completed.runId) {
65020
+ stage = "append-run-artifact";
65021
+ await client.appendExecutionArtifact(projectSlug, completed.runId, {
65022
+ id: `EA-${completed.id}`,
65023
+ kind: "file",
65024
+ payload: {
65025
+ artifactFileId: completed.id,
65026
+ class: completed.class,
65027
+ contentType: completed.contentType,
65028
+ description: description ?? null,
65029
+ filename: completed.filename,
65030
+ sizeBytes: completed.sizeBytes,
65031
+ stableUrl: created.stableUrl
65032
+ }
65033
+ });
65034
+ }
65035
+ const markdown = contentType.startsWith("image/") ? `![${completed.filename}](${created.stableUrl})` : `[${completed.filename}](${created.stableUrl})`;
65036
+ return {
65037
+ artifactFile: completed,
65038
+ markdown,
65039
+ stableUrl: created.stableUrl
65040
+ };
65041
+ } catch (error51) {
65042
+ const original = error51 instanceof MrrlinApiError ? error51.code : error51 instanceof McpToolCodedError ? error51.code : "ARTIFACT_FILE_UPLOAD_FAILED";
65043
+ const message = error51 instanceof Error ? error51.message : String(error51);
65044
+ const note = stage === "append-run-artifact" ? "file uploaded and completed successfully; only the run-history append failed \u2014 do NOT re-upload, reference the stableUrl directly" : "a pending row may remain; it is reaped automatically after 1h";
65045
+ throw new McpToolCodedError(
65046
+ original,
65047
+ `artifactFileId=${created.artifact.id} stableUrl=${created.stableUrl} (failed at ${stage}; ${note}): ${message}`,
65048
+ error51 instanceof MrrlinApiError ? error51.violations ?? void 0 : error51 instanceof McpToolCodedError ? error51.violations : void 0
65049
+ );
65050
+ }
65051
+ }
65052
+
64927
65053
  // src/delivery/index.ts
64928
65054
  async function deliverThroughCapability(client, input) {
64929
65055
  if (resolveKindCapability(input.artifactKind) === null) {
@@ -64941,7 +65067,7 @@ async function deliverThroughCapability(client, input) {
64941
65067
  return { ok: false, reason: "transport_error" };
64942
65068
  }
64943
65069
  if (!resolution.ok) {
64944
- return { ok: false, reason: resolution.reason };
65070
+ return { ok: false, reason: resolution.reason, message: resolution.message };
64945
65071
  }
64946
65072
  const { route, verdict } = resolution;
64947
65073
  if (verdict === "silent") {
@@ -64963,7 +65089,14 @@ var NoGrantForServerError = class extends Error {
64963
65089
  };
64964
65090
  async function proceedToDestination(client, route, input) {
64965
65091
  try {
64966
- const result = await callDestination(client, input.taskProjectSlug, route, input.payload, input.mcpConfigPath);
65092
+ const result = await callDestination(
65093
+ client,
65094
+ input.taskProjectSlug,
65095
+ route,
65096
+ input.payload,
65097
+ input.mcpConfigPath,
65098
+ input.bareFetch
65099
+ );
64967
65100
  const recorded = await recordUse(client, route, input, "ok");
64968
65101
  return { ok: true, use: recorded, result };
64969
65102
  } catch (error51) {
@@ -64972,7 +65105,15 @@ async function proceedToDestination(client, route, input) {
64972
65105
  return { ok: false, reason: "no_grant" };
64973
65106
  }
64974
65107
  await recordUse(client, route, input, "error");
64975
- return { ok: false, reason: "delivery_failed" };
65108
+ const code = error51 instanceof McpToolCodedError || error51 instanceof MrrlinApiError ? error51.code : void 0;
65109
+ const violations = error51 instanceof MrrlinApiError || error51 instanceof McpToolCodedError ? error51.violations : void 0;
65110
+ return {
65111
+ ok: false,
65112
+ reason: "delivery_failed",
65113
+ message: error51 instanceof Error ? error51.message : void 0,
65114
+ code,
65115
+ violations
65116
+ };
64976
65117
  }
64977
65118
  }
64978
65119
  async function refuse(client, route, input) {
@@ -65129,7 +65270,7 @@ async function recordUse(client, route, input, outcome) {
65129
65270
  return null;
65130
65271
  }
65131
65272
  }
65132
- async function callDestination(client, projectSlug, route, payload, mcpConfigPath) {
65273
+ async function callDestination(client, projectSlug, route, payload, mcpConfigPath, bareFetch) {
65133
65274
  const operation = isRecord6(payload) ? payload.operation : void 0;
65134
65275
  if (route.capability === "wiki") {
65135
65276
  if (operation !== "create" && operation !== "update") {
@@ -65145,6 +65286,12 @@ async function callDestination(client, projectSlug, route, payload, mcpConfigPat
65145
65286
  if (server === null) throw new NoGrantForServerError(`no local MCP config for server "${route.serverId}"`);
65146
65287
  return deliverToGit(server, route.toolName, payload);
65147
65288
  }
65289
+ if (route.capability === "artifacts") {
65290
+ if (operation !== "upload") {
65291
+ throw new Error(`callDestination: payload.operation "${String(operation)}" is not valid for artifacts`);
65292
+ }
65293
+ return deliverToArtifacts(client, projectSlug, payload, bareFetch);
65294
+ }
65148
65295
  throw new Error(`callDestination: no destination wired for capability "${route.capability}"`);
65149
65296
  }
65150
65297
  function isRecord6(value) {
@@ -67316,14 +67463,6 @@ var ARTIFACT_EXTENSION_TYPES = {
67316
67463
  ".webp": "image/webp",
67317
67464
  ".gif": "image/gif"
67318
67465
  };
67319
- var McpToolCodedError = class extends Error {
67320
- code;
67321
- constructor(code, message) {
67322
- super(message);
67323
- this.name = "McpToolCodedError";
67324
- this.code = code;
67325
- }
67326
- };
67327
67466
  function withAuth(options) {
67328
67467
  const baseFetch = options.fetch ?? globalThis.fetch;
67329
67468
  const authedFetch = async (input, init = {}) => {
@@ -67361,7 +67500,11 @@ function classifyClientError(fallbackCode, fallbackMessage, raw) {
67361
67500
  };
67362
67501
  }
67363
67502
  if (raw instanceof McpToolCodedError) {
67364
- return { code: raw.code, message: raw.message || fallbackMessage };
67503
+ return {
67504
+ code: raw.code,
67505
+ message: raw.message || fallbackMessage,
67506
+ ...raw.violations ? { violations: raw.violations } : {}
67507
+ };
67365
67508
  }
67366
67509
  if (raw instanceof Error) {
67367
67510
  return { code: fallbackCode, message: raw.message || fallbackMessage };
@@ -67388,18 +67531,33 @@ function pingAsyncWorkerDispatch(projectSlug, jobId) {
67388
67531
  }).catch(() => {
67389
67532
  });
67390
67533
  }
67391
- async function deliverBuiltIn(client, projectSlug, artifactKind, payload, mcpConfigPath) {
67534
+ async function deliverBuiltIn(client, projectSlug, artifactKind, payload, extra = {}) {
67392
67535
  const result = await deliverThroughCapability(client, {
67393
67536
  projectSlug,
67394
67537
  taskProjectSlug: projectSlug,
67395
67538
  artifactKind,
67396
- taskId: null,
67397
- runId: null,
67539
+ taskId: extra.taskId ?? null,
67540
+ runId: extra.runId ?? null,
67398
67541
  payload,
67399
- mcpConfigPath
67542
+ mcpConfigPath: extra.mcpConfigPath,
67543
+ bareFetch: extra.bareFetch
67400
67544
  });
67401
67545
  if (!result.ok) {
67402
- throw new McpToolCodedError(`CAPABILITY_${result.reason.toUpperCase()}`, `Delivery refused: ${result.reason}`);
67546
+ throw new McpToolCodedError(
67547
+ result.code ?? `CAPABILITY_${result.reason.toUpperCase()}`,
67548
+ describeCapabilityRefusal({
67549
+ reason: result.reason,
67550
+ projectSlug,
67551
+ artifactKind,
67552
+ capability: resolveKindCapability(artifactKind),
67553
+ detail: result.message
67554
+ }),
67555
+ // Consensus round 1 (chunk 4): a destination's own MrrlinApiError (e.g. artifact-file creation's
67556
+ // 409) carries structured violations that classifyClientError already surfaces for every other
67557
+ // tool -- deliverThroughCapability threads them onto DeliveryResult, converted here from the
67558
+ // nullable client shape to McpToolCodedError's optional-array one.
67559
+ result.violations ?? void 0
67560
+ );
67403
67561
  }
67404
67562
  return result.result;
67405
67563
  }
@@ -67481,68 +67639,6 @@ async function getAgentProviderForPanel(client, opts = {}) {
67481
67639
  }
67482
67640
  function createMrrlinTools(options) {
67483
67641
  const client = createMrrlinClient(withAuth(options));
67484
- async function uploadArtifactBytes(c, args) {
67485
- const { bytes: bytes3, class: artifactClass, contentType, description, filename, projectSlug, runId, taskId } = args;
67486
- const created = await c.createArtifactFile(projectSlug, {
67487
- class: artifactClass,
67488
- contentType,
67489
- filename,
67490
- runId,
67491
- sizeBytes: bytes3.byteLength,
67492
- taskId
67493
- });
67494
- let stage = "upload";
67495
- try {
67496
- if (created.upload.kind === "presigned-post") {
67497
- const form = new FormData();
67498
- for (const [field, value] of Object.entries(created.upload.fields)) {
67499
- form.append(field, value);
67500
- }
67501
- form.append("file", new Blob([bytes3], { type: contentType }), filename);
67502
- const bareFetch = options.fetch ?? globalThis.fetch;
67503
- const uploadResponse = await bareFetch(created.upload.url, { body: form, method: "POST" });
67504
- if (!uploadResponse.ok) {
67505
- throw new Error(
67506
- `Presigned upload failed: ${uploadResponse.status} ${await uploadResponse.text()}`
67507
- );
67508
- }
67509
- } else {
67510
- await c.uploadArtifactFileContent(projectSlug, created.artifact.id, bytes3, contentType);
67511
- }
67512
- stage = "complete";
67513
- const completed = await c.completeArtifactFile(projectSlug, created.artifact.id);
67514
- if (completed.runId) {
67515
- stage = "append-run-artifact";
67516
- await c.appendExecutionArtifact(projectSlug, completed.runId, {
67517
- id: `EA-${completed.id}`,
67518
- kind: "file",
67519
- payload: {
67520
- artifactFileId: completed.id,
67521
- class: completed.class,
67522
- contentType: completed.contentType,
67523
- description: description ?? null,
67524
- filename: completed.filename,
67525
- sizeBytes: completed.sizeBytes,
67526
- stableUrl: created.stableUrl
67527
- }
67528
- });
67529
- }
67530
- const markdown = contentType.startsWith("image/") ? `![${completed.filename}](${created.stableUrl})` : `[${completed.filename}](${created.stableUrl})`;
67531
- return {
67532
- artifactFile: completed,
67533
- markdown,
67534
- stableUrl: created.stableUrl
67535
- };
67536
- } catch (error51) {
67537
- const original = error51 instanceof MrrlinApiError ? error51.code : error51 instanceof McpToolCodedError ? error51.code : "ARTIFACT_FILE_UPLOAD_FAILED";
67538
- const message = error51 instanceof Error ? error51.message : String(error51);
67539
- const note = stage === "append-run-artifact" ? "file uploaded and completed successfully; only the run-history append failed \u2014 do NOT re-upload, reference the stableUrl directly" : "a pending row may remain; it is reaped automatically after 1h";
67540
- throw new McpToolCodedError(
67541
- original,
67542
- `artifactFileId=${created.artifact.id} stableUrl=${created.stableUrl} (failed at ${stage}; ${note}): ${message}`
67543
- );
67544
- }
67545
- }
67546
67642
  return {
67547
67643
  [mcpToolNames.archivePlan]: makeTool(
67548
67644
  client,
@@ -67587,7 +67683,7 @@ function createMrrlinTools(options) {
67587
67683
  projectSlug,
67588
67684
  "code_change",
67589
67685
  { operation: "branch", ...input },
67590
- options.mcpConfigPath
67686
+ { mcpConfigPath: options.mcpConfigPath }
67591
67687
  )
67592
67688
  ),
67593
67689
  [mcpToolNames.createGithubPullRequest]: makeTool(
@@ -67600,7 +67696,7 @@ function createMrrlinTools(options) {
67600
67696
  projectSlug,
67601
67697
  "code_change",
67602
67698
  { operation: "pull_request", ...input },
67603
- options.mcpConfigPath
67699
+ { mcpConfigPath: options.mcpConfigPath }
67604
67700
  )
67605
67701
  ),
67606
67702
  [mcpToolNames.createPlan]: makeTool(
@@ -67677,7 +67773,7 @@ function createMrrlinTools(options) {
67677
67773
  projectSlug,
67678
67774
  "document",
67679
67775
  { operation: "create", page: input },
67680
- options.mcpConfigPath
67776
+ { mcpConfigPath: options.mcpConfigPath }
67681
67777
  )
67682
67778
  ),
67683
67779
  [mcpToolNames.deleteTaskDependency]: makeTool(
@@ -67872,7 +67968,7 @@ function createMrrlinTools(options) {
67872
67968
  projectSlug,
67873
67969
  "document",
67874
67970
  { operation: "update", pageId, patch },
67875
- options.mcpConfigPath
67971
+ { mcpConfigPath: options.mcpConfigPath }
67876
67972
  )
67877
67973
  ),
67878
67974
  [mcpToolNames.markSpecReady]: makeTool(
@@ -68170,16 +68266,13 @@ function createMrrlinTools(options) {
68170
68266
  );
68171
68267
  }
68172
68268
  const filename = import_node_path32.default.basename(filePath);
68173
- return uploadArtifactBytes(c, {
68174
- bytes: bytes3,
68175
- class: artifactClass,
68176
- contentType,
68177
- description,
68178
- filename,
68269
+ return deliverBuiltIn(
68270
+ c,
68179
68271
  projectSlug,
68180
- runId,
68181
- taskId
68182
- });
68272
+ "file_set",
68273
+ { operation: "upload", bytes: bytes3, class: artifactClass, contentType, description, filename, runId, taskId },
68274
+ { mcpConfigPath: options.mcpConfigPath, bareFetch: options.fetch ?? globalThis.fetch, taskId, runId }
68275
+ );
68183
68276
  }
68184
68277
  ),
68185
68278
  [mcpToolNames.deliverFile]: makeTool(
@@ -68215,16 +68308,13 @@ function createMrrlinTools(options) {
68215
68308
  "Artifact exceeds 5MB; split or summarize instead."
68216
68309
  );
68217
68310
  }
68218
- return uploadArtifactBytes(c, {
68219
- bytes: bytes3,
68220
- class: artifactClass,
68221
- contentType,
68222
- description,
68223
- filename,
68311
+ return deliverBuiltIn(
68312
+ c,
68224
68313
  projectSlug,
68225
- runId,
68226
- taskId
68227
- });
68314
+ "file_set",
68315
+ { operation: "upload", bytes: bytes3, class: artifactClass, contentType, description, filename, runId, taskId },
68316
+ { mcpConfigPath: options.mcpConfigPath, bareFetch: options.fetch ?? globalThis.fetch, taskId, runId }
68317
+ );
68228
68318
  }
68229
68319
  ),
68230
68320
  [mcpToolNames.listArtifactFiles]: makeTool(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrrlin-dev/mcp",
3
- "version": "0.4.57",
3
+ "version": "0.4.59",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "mrrlin-mcp": "dist/bin.cjs"