@mrrlin-dev/mcp 0.2.6 → 0.3.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 (2) hide show
  1. package/dist/bin.cjs +770 -83
  2. package/package.json +2 -2
package/dist/bin.cjs CHANGED
@@ -19266,13 +19266,15 @@ async function acquireThread(codex, opts) {
19266
19266
  }
19267
19267
 
19268
19268
  // ../../packages/codex-client/dist/render-context-bundle.js
19269
+ var DEFAULT_SPEC_BUDGET_CHARS = 6e3;
19270
+ var DEFAULT_ARTIFACT_PAYLOAD_BUDGET_CHARS = 400;
19269
19271
  function renderContextBundle(input) {
19270
19272
  const sections = [];
19271
19273
  sections.push(renderTaskSummary(input.task));
19272
- sections.push(renderSpec(input.specMarkdown));
19274
+ sections.push(renderSpec(input.specMarkdown, input.compact));
19273
19275
  sections.push(renderRunHistory(input.runs, input.openRunId));
19274
19276
  sections.push(renderOpenRun(input.openRunId));
19275
- sections.push(renderArtifacts(input.artifacts, input.omittedArtifactCount ?? 0));
19277
+ sections.push(renderArtifacts(input.artifacts, input.omittedArtifactCount ?? 0, input.compact));
19276
19278
  sections.push(renderAutonomyEvents(input.autonomyEvents));
19277
19279
  return sections.join("\n\n");
19278
19280
  }
@@ -19290,11 +19292,55 @@ function renderTaskSummary(task) {
19290
19292
  }
19291
19293
  return lines2.join("\n");
19292
19294
  }
19293
- function renderSpec(specMarkdown) {
19295
+ function renderSpec(specMarkdown, compact) {
19294
19296
  if (!specMarkdown)
19295
19297
  return "## Spec\n(none)";
19296
- return `## Spec
19298
+ if (!compact)
19299
+ return `## Spec
19300
+ ${specMarkdown}`;
19301
+ const budget = compact.specBudgetChars ?? DEFAULT_SPEC_BUDGET_CHARS;
19302
+ if (specMarkdown.length <= budget)
19303
+ return `## Spec
19297
19304
  ${specMarkdown}`;
19305
+ return `## Spec
19306
+ ${compactSpecMarkdown(specMarkdown, budget, compact.projectSlug ?? null, compact.specWikiPageId ?? null)}`;
19307
+ }
19308
+ function compactSpecMarkdown(markdown, totalBudget, projectSlug, specWikiPageId) {
19309
+ const lines2 = markdown.split(/\r?\n/);
19310
+ const sections = [{ heading: "", body: [] }];
19311
+ for (const line of lines2) {
19312
+ if (/^#{1,6}\s/.test(line)) {
19313
+ sections.push({ heading: line, body: [] });
19314
+ } else {
19315
+ sections[sections.length - 1].body.push(line);
19316
+ }
19317
+ }
19318
+ const sectionCount = Math.max(1, sections.length);
19319
+ const perSectionBudget = Math.max(120, Math.floor(totalBudget / sectionCount));
19320
+ const out = [];
19321
+ for (const section of sections) {
19322
+ const bodyText = section.body.join("\n").replace(/^\s+|\s+$/g, "");
19323
+ if (section.heading)
19324
+ out.push(section.heading);
19325
+ if (!bodyText)
19326
+ continue;
19327
+ if (bodyText.length <= perSectionBudget) {
19328
+ out.push(bodyText);
19329
+ } else {
19330
+ const kept = bodyText.slice(0, perSectionBudget);
19331
+ const omitted = bodyText.length - perSectionBudget;
19332
+ out.push(`${kept}
19333
+ \u2026(${omitted} chars omitted; retrieve full spec for the rest)`);
19334
+ }
19335
+ }
19336
+ if (projectSlug && specWikiPageId) {
19337
+ out.push("");
19338
+ out.push(`> Compact spec. Source: mrrlin://wiki/${projectSlug}/${specWikiPageId} \u2014 call retrieve_context with this handle before any irreversible decision.`);
19339
+ } else {
19340
+ out.push("");
19341
+ out.push("> Compact spec. Retrieve the Specs/{id} wiki page via get_wiki_page before any irreversible decision.");
19342
+ }
19343
+ return out.join("\n");
19298
19344
  }
19299
19345
  function renderRunHistory(runs, openRunId) {
19300
19346
  if (runs.length === 0)
@@ -19310,13 +19356,14 @@ function renderOpenRun(openRunId) {
19310
19356
  return `## Open run
19311
19357
  ${openRunId ?? "(none)"}`;
19312
19358
  }
19313
- function renderArtifacts(artifacts, omittedCount) {
19359
+ function renderArtifacts(artifacts, omittedCount, compact) {
19314
19360
  if (artifacts.length === 0 && omittedCount === 0)
19315
19361
  return "## Artifacts\n(none)";
19316
19362
  const lines2 = ["## Artifacts"];
19317
19363
  if (omittedCount > 0) {
19318
19364
  lines2.push(`(+${omittedCount} older artifact${omittedCount === 1 ? "" : "s"} omitted)`);
19319
19365
  }
19366
+ let truncatedAny = false;
19320
19367
  for (const artifact of artifacts) {
19321
19368
  if (artifact.kind === "file") {
19322
19369
  const p = artifact.payload;
@@ -19325,7 +19372,16 @@ function renderArtifacts(artifacts, omittedCount) {
19325
19372
  continue;
19326
19373
  }
19327
19374
  }
19328
- lines2.push(`- ${artifact.createdAt} [${artifact.kind}] run=${artifact.runId} ${JSON.stringify(artifact.payload)}`);
19375
+ const payloadText = JSON.stringify(artifact.payload);
19376
+ const budget = compact?.artifactPayloadBudgetChars ?? DEFAULT_ARTIFACT_PAYLOAD_BUDGET_CHARS;
19377
+ const willTruncate = !!compact && payloadText.length > budget;
19378
+ const rendered = willTruncate ? `${payloadText.slice(0, budget)}\u2026(${payloadText.length - budget} chars omitted)` : payloadText;
19379
+ if (willTruncate)
19380
+ truncatedAny = true;
19381
+ lines2.push(`- ${artifact.createdAt} [${artifact.kind}] run=${artifact.runId} ${rendered}`);
19382
+ }
19383
+ if (compact && (omittedCount > 0 || truncatedAny)) {
19384
+ lines2.push("> Compact artifacts. Use retrieve_context with a `mrrlin://run/{projectSlug}/{runId}/artifacts` handle (substitute the slug and run id from the lines above) to load a run's full artifact history.");
19329
19385
  }
19330
19386
  return lines2.join("\n");
19331
19387
  }
@@ -33901,7 +33957,6 @@ var openApiOperationIds = {
33901
33957
  listExecutionArtifacts: "listExecutionArtifacts",
33902
33958
  listExecutionRuns: "listExecutionRuns",
33903
33959
  listGithubInstallations: "listGithubInstallations",
33904
- listGithubWebhookDeliveries: "listGithubWebhookDeliveries",
33905
33960
  listGithubWorkflows: "listGithubWorkflows",
33906
33961
  listPlans: "listPlans",
33907
33962
  listProjects: "listProjects",
@@ -33918,7 +33973,6 @@ var openApiOperationIds = {
33918
33973
  decideInboxItem: "decideInboxItem",
33919
33974
  markInboxItemRead: "markInboxItemRead",
33920
33975
  markAllInboxItemsRead: "markAllInboxItemsRead",
33921
- receiveGithubWebhook: "receiveGithubWebhook",
33922
33976
  searchKnowledge: "searchKnowledge",
33923
33977
  updateExecutionRun: "updateExecutionRun",
33924
33978
  upsertProjectSchedulerPolicy: "upsertProjectSchedulerPolicy",
@@ -33935,6 +33989,10 @@ var openApiOperationIds = {
33935
33989
  listAgentCredentials: "listAgentCredentials",
33936
33990
  revokeAgentCredential: "revokeAgentCredential",
33937
33991
  exchangeAgentToken: "exchangeAgentToken",
33992
+ // ADR 0016 — operator-scoped credential routes.
33993
+ issueOperatorAgentCredential: "issueOperatorAgentCredential",
33994
+ listOperatorAgentCredentials: "listOperatorAgentCredentials",
33995
+ revokeOperatorAgentCredential: "revokeOperatorAgentCredential",
33938
33996
  createArtifactFile: "createArtifactFile",
33939
33997
  completeArtifactFile: "completeArtifactFile",
33940
33998
  getArtifactFile: "getArtifactFile",
@@ -33958,7 +34016,12 @@ var operatorSessionSchema = external_exports.object({
33958
34016
  login: external_exports.string().min(1),
33959
34017
  projectSlug: external_exports.string().min(1),
33960
34018
  provider: external_exports.literal("github"),
33961
- repo: external_exports.string().min(1).nullable()
34019
+ repo: external_exports.string().min(1).nullable(),
34020
+ // ADR 0016: `scope` distinguishes project-pinned agent tokens (legacy, default) from
34021
+ // operator-scoped agent tokens. Only relevant for kind:"agent". `web` sessions ignore it.
34022
+ // Default 'project' for backward-compat: tokens minted before this field existed parse
34023
+ // cleanly into the project branch of the middleware (which mirrors today's behavior).
34024
+ scope: external_exports.enum(["project", "operator"]).default("project")
33962
34025
  });
33963
34026
  var AGENT_SESSION_TTL_MS = 1e3 * 60 * 60;
33964
34027
 
@@ -34260,15 +34323,6 @@ var mrrlinGithubWorkflowDispatchSchema = external_exports.object({
34260
34323
  ref: external_exports.string().min(1),
34261
34324
  workflowId: external_exports.string().min(1)
34262
34325
  });
34263
- var mrrlinGithubWebhookDeliverySchema = external_exports.object({
34264
- action: external_exports.string().min(1).nullable(),
34265
- deliveryId: external_exports.string().min(1),
34266
- eventType: external_exports.string().min(1),
34267
- installationId: external_exports.number().int().positive().nullable(),
34268
- projectSlug: mrrlinProjectSlugSchema.nullable(),
34269
- receivedAt: external_exports.string().datetime(),
34270
- repositoryFullName: external_exports.string().regex(/^[^/]+\/[^/]+$/).nullable()
34271
- });
34272
34326
  var mrrlinProjectSummarySchema = external_exports.object({
34273
34327
  displayName: external_exports.string().min(1),
34274
34328
  emoji: mrrlinProjectEmojiSchema,
@@ -34494,14 +34548,6 @@ var githubBindProjectRepoRequestSchema = external_exports.object({
34494
34548
  var githubBindProjectRepoResponseSchema = external_exports.object({
34495
34549
  data: mrrlinProjectSummarySchema
34496
34550
  });
34497
- var githubWebhookReceiptResponseSchema = external_exports.object({
34498
- data: external_exports.object({
34499
- deliveryId: external_exports.string().min(1),
34500
- eventType: external_exports.string().min(1),
34501
- linkedProjectSlugs: external_exports.array(mrrlinProjectSlugSchema),
34502
- stored: external_exports.boolean()
34503
- })
34504
- });
34505
34551
  var githubCreateBranchRequestSchema = external_exports.object({
34506
34552
  baseBranch: external_exports.string().min(1).default("main"),
34507
34553
  branchName: external_exports.string().min(1)
@@ -34551,9 +34597,6 @@ var githubLatestDeploymentQuerySchema = external_exports.object({
34551
34597
  var githubLatestDeploymentResponseSchema = external_exports.object({
34552
34598
  data: external_exports.object({ deploymentId: external_exports.number().int(), environmentUrl: external_exports.string() }).nullable()
34553
34599
  });
34554
- var githubWebhookDeliveryListResponseSchema = external_exports.object({
34555
- data: external_exports.array(mrrlinGithubWebhookDeliverySchema)
34556
- });
34557
34600
  var githubMintPushTokenRequestSchema = external_exports.object({
34558
34601
  runId: mrrlinExecutionRunIdSchema,
34559
34602
  leaseId: external_exports.string().min(1)
@@ -34766,13 +34809,24 @@ var agentCredentialSummarySchema = external_exports.object({
34766
34809
  createdAt: external_exports.string().datetime(),
34767
34810
  lastUsedAt: external_exports.string().datetime().nullable(),
34768
34811
  revokedAt: external_exports.string().datetime().nullable(),
34769
- expiresAt: external_exports.string().datetime().nullable()
34812
+ expiresAt: external_exports.string().datetime().nullable(),
34813
+ // ADR 0016: surface scope so UI can label rows; seedProjectSlug is audit-only ("issued from
34814
+ // project X"). Both default to 'project'/null for back-compat with legacy rows.
34815
+ scope: external_exports.enum(["project", "operator"]).default("project"),
34816
+ seedProjectSlug: external_exports.string().nullable().default(null)
34770
34817
  });
34771
34818
  var agentCredentialListResponseSchema = external_exports.object({
34772
34819
  data: external_exports.array(agentCredentialSummarySchema)
34773
34820
  });
34774
34821
  var exchangeAgentTokenRequestSchema = external_exports.object({
34775
- secret: external_exports.string().min(1)
34822
+ // ADR 0016 §3 no-oracle: the exchange schema MUST accept any string shape for `secret` and
34823
+ // `projectSlug`. Empty/whitespace/missing inputs are handled by the handler's constant-work
34824
+ // 5-step loop and flow into the same uniform 401 path as wrong-secret. A schema-level 400
34825
+ // would leak (attacker can distinguish "malformed input" from "wrong secret" before the
34826
+ // handler runs). Validation of structural content (e.g. selector parseability for `secret`)
34827
+ // happens inside the handler against the dummy hash.
34828
+ secret: external_exports.string(),
34829
+ projectSlug: external_exports.string().optional()
34776
34830
  });
34777
34831
  var exchangeAgentTokenResponseSchema = external_exports.object({
34778
34832
  token: external_exports.string().min(1),
@@ -34987,10 +35041,10 @@ var AgentTokenProviderError = class extends Error {
34987
35041
  this.code = code;
34988
35042
  }
34989
35043
  };
34990
- async function defaultExchange(secret, baseUrl) {
35044
+ async function defaultExchange(secret, baseUrl, projectSlug) {
34991
35045
  const url2 = `${baseUrl.replace(/\/$/, "")}/auth/agent/token`;
34992
35046
  const response = await fetch(url2, {
34993
- body: JSON.stringify({ secret }),
35047
+ body: JSON.stringify({ secret, ...projectSlug ? { projectSlug } : {} }),
34994
35048
  headers: { "content-type": "application/json" },
34995
35049
  method: "POST"
34996
35050
  });
@@ -35015,22 +35069,28 @@ function createAgentTokenProvider(opts) {
35015
35069
  const { baseUrl, secret } = opts;
35016
35070
  const exchange = opts.exchange ?? defaultExchange;
35017
35071
  const now = opts.now ?? (() => Date.now());
35018
- let cached2 = null;
35019
- function isValid() {
35020
- if (!cached2)
35072
+ const LEGACY_KEY = "__mrrlin_agent_no_slug__";
35073
+ const cacheByKey = /* @__PURE__ */ new Map();
35074
+ function cacheKey(projectSlug) {
35075
+ return projectSlug ?? LEGACY_KEY;
35076
+ }
35077
+ function isValid(entry) {
35078
+ if (!entry)
35021
35079
  return false;
35022
- const expiresAtMs = new Date(cached2.expiresAt).getTime();
35080
+ const expiresAtMs = new Date(entry.expiresAt).getTime();
35023
35081
  return now() < expiresAtMs - REFRESH_BUFFER_MS;
35024
35082
  }
35025
- async function getSession() {
35026
- if (!isValid()) {
35027
- cached2 = await exchange(secret, baseUrl);
35083
+ async function getSession(projectSlug) {
35084
+ const key = cacheKey(projectSlug);
35085
+ let entry = cacheByKey.get(key);
35086
+ if (!isValid(entry)) {
35087
+ entry = await exchange(secret, baseUrl, projectSlug);
35088
+ cacheByKey.set(key, entry);
35028
35089
  }
35029
- const c = cached2;
35030
- return { token: c.token, projectSlug: c.projectSlug, login: c.login };
35090
+ return { token: entry.token, projectSlug: entry.projectSlug, login: entry.login };
35031
35091
  }
35032
- async function getToken() {
35033
- return (await getSession()).token;
35092
+ async function getToken(projectSlug) {
35093
+ return (await getSession(projectSlug)).token;
35034
35094
  }
35035
35095
  return { getToken, getSession };
35036
35096
  }
@@ -35328,10 +35388,6 @@ function createMrrlinClient(config2) {
35328
35388
  const body = await request(`${projectPath(projectSlug)}/github/workflows`);
35329
35389
  return githubWorkflowListResponseSchema.parse(body).data;
35330
35390
  },
35331
- async listGithubWebhookDeliveries(projectSlug) {
35332
- const body = await request(`${projectPath(projectSlug)}/github/webhook-deliveries`);
35333
- return githubWebhookDeliveryListResponseSchema.parse(body).data;
35334
- },
35335
35391
  async listProjects() {
35336
35392
  const body = await request("/projects");
35337
35393
  return projectSummaryListResponseSchema.parse(body).data;
@@ -35763,6 +35819,24 @@ function createMrrlinClient(config2) {
35763
35819
  method: "POST"
35764
35820
  });
35765
35821
  return exchangeAgentTokenResponseSchema.parse(body);
35822
+ },
35823
+ // ── ADR 0016 — operator-scoped credential client methods ─────────────────
35824
+ async issueOperatorAgentCredential(input) {
35825
+ const body = await request("/operator/agent-credentials", {
35826
+ body: JSON.stringify(input),
35827
+ headers: { "content-type": "application/json" },
35828
+ method: "POST"
35829
+ });
35830
+ return agentCredentialResponseSchema.parse(body.data);
35831
+ },
35832
+ async listOperatorAgentCredentials() {
35833
+ const body = await request("/operator/agent-credentials");
35834
+ return agentCredentialListResponseSchema.parse(body).data;
35835
+ },
35836
+ async revokeOperatorAgentCredential(credentialId) {
35837
+ await request(`/operator/agent-credentials/${encodeURIComponent(credentialId)}`, {
35838
+ method: "DELETE"
35839
+ });
35766
35840
  }
35767
35841
  };
35768
35842
  }
@@ -36436,7 +36510,17 @@ async function driveRun(deps, projectSlug, runId) {
36436
36510
  }
36437
36511
  const leaseId = claimed.leaseId;
36438
36512
  const task = await deps.client.getTask(projectSlug, claimed.taskId);
36439
- const bundle = renderContextBundle(await gatherContext(deps.client, projectSlug, task, runId));
36513
+ const compactEnabled = (process.env.MRRLIN_MCP_COMPACT_DISABLE ?? "").trim() !== "1";
36514
+ const context = await gatherContext(deps.client, projectSlug, task, runId);
36515
+ const bundle = renderContextBundle(
36516
+ compactEnabled ? {
36517
+ ...context,
36518
+ compact: {
36519
+ projectSlug,
36520
+ specWikiPageId: task.specWikiPageId ?? null
36521
+ }
36522
+ } : context
36523
+ );
36440
36524
  const checkout = deps.checkoutRegistry?.get(projectSlug) ?? null;
36441
36525
  let sandbox = "read-only";
36442
36526
  let turnCwd = deps.codexCwd ?? null;
@@ -41256,8 +41340,16 @@ var NOOP_LOGGER = {
41256
41340
  }
41257
41341
  };
41258
41342
  var REDACT_DEPTH_CAP = 8;
41343
+ var IMAGE_DATA_URI_RE = /^(data:image\/[a-zA-Z0-9.+-]+;base64,)([A-Za-z0-9+/=]+)$/;
41344
+ function redactImageDataUri(value) {
41345
+ const m = value.match(IMAGE_DATA_URI_RE);
41346
+ if (!m) return value;
41347
+ const prefix = m[1];
41348
+ const body = m[2];
41349
+ return `${prefix}\u2026(${body.length} bytes)`;
41350
+ }
41259
41351
  function redactValue(value, depth) {
41260
- if (typeof value === "string") return redact(value);
41352
+ if (typeof value === "string") return redact(redactImageDataUri(value));
41261
41353
  if (value === null || typeof value !== "object") return value;
41262
41354
  if (depth >= REDACT_DEPTH_CAP) {
41263
41355
  try {
@@ -41883,13 +41975,30 @@ function createBridgeMessageHandler(deps) {
41883
41975
  sendForSpan({ type: "error", error: "Invalid message schema." });
41884
41976
  return;
41885
41977
  }
41978
+ const rawImage = msg.imageDataUri;
41979
+ let imageDataUri = null;
41980
+ if (rawImage !== void 0 && rawImage !== null) {
41981
+ if (typeof rawImage !== "string") {
41982
+ sendForSpan({ type: "error", error: "imageDataUri must be a string." });
41983
+ return;
41984
+ }
41985
+ if (!rawImage.startsWith("data:image/")) {
41986
+ sendForSpan({ type: "error", error: "imageDataUri must be a data URI for an image." });
41987
+ return;
41988
+ }
41989
+ if (rawImage.length > 3 * 1024 * 1024) {
41990
+ sendForSpan({ type: "error", error: "Image too large." });
41991
+ return;
41992
+ }
41993
+ imageDataUri = rawImage;
41994
+ }
41886
41995
  const directorSessionId = msg.directorSessionId.trim();
41887
41996
  if (!directorSessionId) {
41888
41997
  sendForSpan({ type: "error", error: "directorSessionId is required." });
41889
41998
  return;
41890
41999
  }
41891
42000
  const inputText = msg.message.trim();
41892
- if (!inputText) {
42001
+ if (!inputText && !imageDataUri) {
41893
42002
  sendForSpan({ type: "error", directorSessionId, error: "message is required." });
41894
42003
  return;
41895
42004
  }
@@ -42035,11 +42144,19 @@ function createBridgeMessageHandler(deps) {
42035
42144
  socket.once("close", onSocketClose);
42036
42145
  pokeActivity = () => watchdog.poke();
42037
42146
  const runSequence = (async () => {
42147
+ const turnInput = [];
42148
+ if (inputText.length > 0) {
42149
+ const composed = contextPrefix ? `${contextPrefix}
42150
+
42151
+ ${inputText}` : inputText;
42152
+ turnInput.push({ type: "text", text: composed, text_elements: [] });
42153
+ }
42154
+ if (imageDataUri) {
42155
+ turnInput.push({ type: "image", url: imageDataUri });
42156
+ }
42038
42157
  await codexClient.turn.start({
42039
42158
  threadId: thread.threadId,
42040
- input: [{ type: "text", text: contextPrefix ? `${contextPrefix}
42041
-
42042
- ${inputText}` : inputText }],
42159
+ input: turnInput,
42043
42160
  ...thread.reasoningEffort ? { effort: thread.reasoningEffort } : {}
42044
42161
  });
42045
42162
  await turnFinished;
@@ -47218,8 +47335,7 @@ function remoteMatchesRepo(remoteUrl, repoFullName) {
47218
47335
  return a !== null && a === repoFullName.trim().toLowerCase();
47219
47336
  }
47220
47337
 
47221
- // src/tools.ts
47222
- registerAsyncTool(consensusDescriptor);
47338
+ // src/tool-names.ts
47223
47339
  var mcpToolNames = {
47224
47340
  appendExecutionArtifact: "append_execution_artifact",
47225
47341
  archivePlan: "archive_plan",
@@ -47255,7 +47371,6 @@ var mcpToolNames = {
47255
47371
  listExecutionArtifacts: "list_execution_artifacts",
47256
47372
  listExecutionRuns: "list_execution_runs",
47257
47373
  listInboxItems: "list_inbox_items",
47258
- listGithubWebhookDeliveries: "list_github_webhook_deliveries",
47259
47374
  listGithubInstallations: "list_github_installations",
47260
47375
  listGithubWorkflows: "list_github_workflows",
47261
47376
  listPlans: "list_plans",
@@ -47279,8 +47394,499 @@ var mcpToolNames = {
47279
47394
  uploadArtifact: "upload_artifact",
47280
47395
  listArtifactFiles: "list_artifact_files",
47281
47396
  resolveHandoff: "resolve_handoff",
47282
- registerLocalCheckout: "register_local_checkout"
47397
+ registerLocalCheckout: "register_local_checkout",
47398
+ /** Compact-context retrieval: rehydrate a mrrlin:// handle into the canonical full record. */
47399
+ retrieveContext: "retrieve_context"
47283
47400
  };
47401
+
47402
+ // src/context-compact/policy.ts
47403
+ var DEFAULT_THRESHOLD_CHARS = 12e3;
47404
+ var DEFAULT_MAX_ITEMS = 25;
47405
+ function readPositiveInt(value, fallback) {
47406
+ const raw = (value ?? "").trim();
47407
+ if (!raw) return fallback;
47408
+ const parsed = Number.parseInt(raw, 10);
47409
+ return Number.isInteger(parsed) && parsed > 0 ? parsed : fallback;
47410
+ }
47411
+ function readCompactPolicy(env = process.env) {
47412
+ const disabled = (env.MRRLIN_MCP_COMPACT_DISABLE ?? "").trim() === "1";
47413
+ return {
47414
+ enabled: !disabled,
47415
+ thresholdChars: readPositiveInt(env.MRRLIN_MCP_COMPACT_THRESHOLD_CHARS, DEFAULT_THRESHOLD_CHARS),
47416
+ maxItems: readPositiveInt(env.MRRLIN_MCP_COMPACT_MAX_ITEMS, DEFAULT_MAX_ITEMS),
47417
+ debug: (env.MRRLIN_MCP_COMPACT_DEBUG ?? "").trim() === "1"
47418
+ };
47419
+ }
47420
+
47421
+ // src/context-compact/handles.ts
47422
+ var MRRLIN_HANDLE_SCHEME = "mrrlin://";
47423
+ var SLUG_RE = /^[a-z0-9][a-z0-9-]*[a-z0-9]$|^[a-z0-9]$/;
47424
+ var TASK_ID_RE = /^GT-[A-Za-z0-9-]+$/;
47425
+ var WIKI_ID_RE = /^WK-[A-Za-z0-9-]+$/;
47426
+ var RUN_ID_RE = /^ER-[A-Za-z0-9-]+$/;
47427
+ function isProjectSlug(value) {
47428
+ return SLUG_RE.test(value);
47429
+ }
47430
+ function formatTaskHandle(projectSlug, taskId) {
47431
+ return `${MRRLIN_HANDLE_SCHEME}task/${projectSlug}/${taskId}`;
47432
+ }
47433
+ function formatTaskEventsHandle(projectSlug, taskId) {
47434
+ return `${MRRLIN_HANDLE_SCHEME}task/${projectSlug}/${taskId}/events`;
47435
+ }
47436
+ function formatWikiHandle(projectSlug, pageId) {
47437
+ return `${MRRLIN_HANDLE_SCHEME}wiki/${projectSlug}/${pageId}`;
47438
+ }
47439
+ function formatProjectSnapshotHandle(projectSlug) {
47440
+ return `${MRRLIN_HANDLE_SCHEME}project/${projectSlug}/snapshot`;
47441
+ }
47442
+ function formatRunArtifactsHandle(projectSlug, runId) {
47443
+ return `${MRRLIN_HANDLE_SCHEME}run/${projectSlug}/${runId}/artifacts`;
47444
+ }
47445
+ function parseMrrlinHandle(value) {
47446
+ if (typeof value !== "string") return null;
47447
+ if (!value.startsWith(MRRLIN_HANDLE_SCHEME)) return null;
47448
+ const rest = value.slice(MRRLIN_HANDLE_SCHEME.length);
47449
+ if (!rest) return null;
47450
+ if (rest.includes("?") || rest.includes("#")) return null;
47451
+ const parts = rest.split("/");
47452
+ if (parts.length < 2) return null;
47453
+ const [kind, slug, ...tail2] = parts;
47454
+ if (!kind || !slug) return null;
47455
+ if (!isProjectSlug(slug)) return null;
47456
+ switch (kind) {
47457
+ case "task": {
47458
+ if (tail2.length < 1 || tail2.length > 2) return null;
47459
+ const taskId = tail2[0] ?? "";
47460
+ if (!TASK_ID_RE.test(taskId)) return null;
47461
+ if (tail2.length === 1) return { kind: "task", projectSlug: slug, taskId };
47462
+ if (tail2[1] !== "events") return null;
47463
+ return { kind: "task-events", projectSlug: slug, taskId };
47464
+ }
47465
+ case "wiki": {
47466
+ if (tail2.length !== 1) return null;
47467
+ const pageId = tail2[0] ?? "";
47468
+ if (!WIKI_ID_RE.test(pageId)) return null;
47469
+ return { kind: "wiki", projectSlug: slug, pageId };
47470
+ }
47471
+ case "project": {
47472
+ if (tail2.length !== 1) return null;
47473
+ const sub = tail2[0];
47474
+ if (sub === "snapshot") return { kind: "project-snapshot", projectSlug: slug };
47475
+ if (sub === "tasks") return { kind: "project-tasks", projectSlug: slug };
47476
+ return null;
47477
+ }
47478
+ case "run": {
47479
+ if (tail2.length < 1 || tail2.length > 2) return null;
47480
+ const runId = tail2[0] ?? "";
47481
+ if (!RUN_ID_RE.test(runId)) return null;
47482
+ if (tail2.length === 1) return { kind: "run", projectSlug: slug, runId };
47483
+ if (tail2[1] !== "artifacts") return null;
47484
+ return { kind: "run-artifacts", projectSlug: slug, runId };
47485
+ }
47486
+ default:
47487
+ return null;
47488
+ }
47489
+ }
47490
+
47491
+ // src/context-compact/compactors.ts
47492
+ var NOTES_EXCERPT_CHARS = 200;
47493
+ var TITLE_TRUNCATE_CHARS = 160;
47494
+ var EXCERPT_TRUNCATE_CHARS = 240;
47495
+ function truncate(value, maxChars) {
47496
+ if (value.length <= maxChars) return value;
47497
+ return `${value.slice(0, Math.max(0, maxChars - 1))}\u2026`;
47498
+ }
47499
+ function firstHeadingLine(markdown) {
47500
+ for (const line of markdown.split(/\r?\n/, 32)) {
47501
+ if (line.startsWith("#")) return line.replace(/\s+$/u, "");
47502
+ }
47503
+ return null;
47504
+ }
47505
+ function compactTaskList(projectSlug, tasks, maxItems) {
47506
+ const total = tasks.length;
47507
+ const included = Math.min(total, maxItems);
47508
+ const slice = tasks.slice(0, included);
47509
+ const items = slice.map((t) => ({
47510
+ id: t.id,
47511
+ title: truncate(t.title, TITLE_TRUNCATE_CHARS),
47512
+ status: t.status,
47513
+ type: t.type,
47514
+ category: t.category,
47515
+ subcategory: t.subcategory,
47516
+ impact: t.impact,
47517
+ autonomy: t.autonomyLevel,
47518
+ tags: t.tags,
47519
+ assignee: t.assignee,
47520
+ planId: t.planId,
47521
+ specWikiPageId: t.specWikiPageId,
47522
+ notesExcerpt: t.notes ? truncate(t.notes, NOTES_EXCERPT_CHARS) : null,
47523
+ latestJudgementExcerpt: t.latestJudgement ? truncate(t.latestJudgement, NOTES_EXCERPT_CHARS) : null,
47524
+ handle: formatTaskHandle(projectSlug, t.id)
47525
+ }));
47526
+ const handles = slice.map((t) => ({
47527
+ handle: formatTaskHandle(projectSlug, t.id),
47528
+ retrieveTool: mcpToolNames.getTask,
47529
+ retrieveArgs: { projectSlug, taskId: t.id }
47530
+ }));
47531
+ return {
47532
+ summary: `${total} task${total === 1 ? "" : "s"} in project ${projectSlug}; ${included} included in this compact view.`,
47533
+ items,
47534
+ handles,
47535
+ counts: { total, included, omitted: total - included }
47536
+ };
47537
+ }
47538
+ function compactWikiPageList(projectSlug, pages, maxItems) {
47539
+ const total = pages.length;
47540
+ const included = Math.min(total, maxItems);
47541
+ const slice = pages.slice(0, included);
47542
+ const items = slice.map((p) => ({
47543
+ id: p.id,
47544
+ title: truncate(p.title, TITLE_TRUNCATE_CHARS),
47545
+ folder: p.folder,
47546
+ path: p.path,
47547
+ taskId: p.taskId,
47548
+ specReady: p.specReady,
47549
+ firstHeading: firstHeadingLine(p.markdownBody),
47550
+ handle: formatWikiHandle(projectSlug, p.id)
47551
+ }));
47552
+ const handles = slice.map((p) => ({
47553
+ handle: formatWikiHandle(projectSlug, p.id),
47554
+ retrieveTool: mcpToolNames.getWikiPage,
47555
+ retrieveArgs: { projectSlug, pageId: p.id }
47556
+ }));
47557
+ return {
47558
+ summary: `${total} wiki page${total === 1 ? "" : "s"} in project ${projectSlug}; ${included} included in this compact view.`,
47559
+ items,
47560
+ handles,
47561
+ counts: { total, included, omitted: total - included }
47562
+ };
47563
+ }
47564
+ function compactWikiSearch(projectSlug, results, maxItems) {
47565
+ const total = results.length;
47566
+ const included = Math.min(total, maxItems);
47567
+ const slice = results.slice(0, included);
47568
+ const items = slice.map((r) => ({
47569
+ pageId: r.id,
47570
+ title: truncate(r.title, TITLE_TRUNCATE_CHARS),
47571
+ folder: r.folder,
47572
+ path: r.path,
47573
+ rank: r.rank,
47574
+ snippet: truncate(r.snippet, EXCERPT_TRUNCATE_CHARS),
47575
+ handle: formatWikiHandle(projectSlug, r.id)
47576
+ }));
47577
+ const handles = slice.map((r) => ({
47578
+ handle: formatWikiHandle(projectSlug, r.id),
47579
+ retrieveTool: mcpToolNames.getWikiPage,
47580
+ retrieveArgs: { projectSlug, pageId: r.id }
47581
+ }));
47582
+ return {
47583
+ summary: `${total} wiki search hit${total === 1 ? "" : "s"} in project ${projectSlug}; ${included} included.`,
47584
+ items,
47585
+ handles,
47586
+ counts: { total, included, omitted: total - included }
47587
+ };
47588
+ }
47589
+ function compactProjectSnapshot(projectSlug, snapshot, maxItems) {
47590
+ const taskBody = compactTaskList(projectSlug, snapshot.tasks, maxItems);
47591
+ const wikiBody = compactWikiPageList(projectSlug, snapshot.wikiPages, maxItems);
47592
+ const statusCounts = {};
47593
+ for (const t of snapshot.tasks) {
47594
+ statusCounts[t.status] = (statusCounts[t.status] ?? 0) + 1;
47595
+ }
47596
+ const categoryCounts = {};
47597
+ for (const t of snapshot.tasks) {
47598
+ const key = `${t.category}/${t.subcategory}`;
47599
+ categoryCounts[key] = (categoryCounts[key] ?? 0) + 1;
47600
+ }
47601
+ const compactPlans = snapshot.plans.map((p) => ({
47602
+ id: p.id,
47603
+ name: truncate(p.name, TITLE_TRUNCATE_CHARS),
47604
+ status: p.status,
47605
+ taskCount: p.taskCount,
47606
+ closedTaskCount: p.closedTaskCount,
47607
+ progress: p.progress,
47608
+ dueDate: p.dueDate
47609
+ }));
47610
+ const items = [
47611
+ {
47612
+ project: snapshot.project,
47613
+ github: {
47614
+ installationState: snapshot.github.appInstallationState,
47615
+ installationId: snapshot.github.installationId,
47616
+ operator: snapshot.github.operator
47617
+ },
47618
+ taskCount: snapshot.tasks.length,
47619
+ wikiCount: snapshot.wikiPages.length,
47620
+ planCount: snapshot.plans.length,
47621
+ statusCounts,
47622
+ categoryCounts,
47623
+ plans: compactPlans,
47624
+ tasks: taskBody.items,
47625
+ wikiPages: wikiBody.items
47626
+ }
47627
+ ];
47628
+ const handles = [
47629
+ {
47630
+ handle: formatProjectSnapshotHandle(projectSlug),
47631
+ retrieveTool: mcpToolNames.getProjectSnapshot,
47632
+ retrieveArgs: { projectSlug }
47633
+ },
47634
+ ...taskBody.handles,
47635
+ ...wikiBody.handles
47636
+ ];
47637
+ const total = snapshot.tasks.length + snapshot.wikiPages.length;
47638
+ const included = taskBody.counts.included + wikiBody.counts.included;
47639
+ return {
47640
+ summary: `Snapshot for ${projectSlug}: ${snapshot.tasks.length} tasks, ${snapshot.wikiPages.length} wiki pages, ${snapshot.plans.length} plans.`,
47641
+ items,
47642
+ handles,
47643
+ counts: { total, included, omitted: total - included }
47644
+ };
47645
+ }
47646
+ var ARTIFACT_PAYLOAD_BUDGET = 400;
47647
+ var COMMAND_OUTPUT_TAIL_LINES = 10;
47648
+ function compactArtifactPayload(artifact) {
47649
+ const payload = artifact.payload ?? {};
47650
+ switch (artifact.kind) {
47651
+ case "file": {
47652
+ return {
47653
+ filename: payload.filename ?? null,
47654
+ contentType: payload.contentType ?? null,
47655
+ sizeBytes: payload.sizeBytes ?? null,
47656
+ class: payload.class ?? null,
47657
+ stableUrl: payload.stableUrl ?? null,
47658
+ description: payload.description ?? null
47659
+ };
47660
+ }
47661
+ case "command_output": {
47662
+ const event = payload.event ?? void 0;
47663
+ const command = payload.command ?? event?.tool?.input?.command ?? null;
47664
+ const cwd = payload.cwd ?? event?.tool?.input?.cwd ?? null;
47665
+ const exitCode = payload.exitCode ?? null;
47666
+ const rawOutput = payload.stdout ?? payload.output ?? event?.content ?? "";
47667
+ const lines2 = rawOutput.split(/\r?\n/);
47668
+ const tail2 = lines2.slice(-COMMAND_OUTPUT_TAIL_LINES).join("\n");
47669
+ const firstError = lines2.find((l) => /error|fail/i.test(l)) ?? null;
47670
+ return {
47671
+ command,
47672
+ cwd,
47673
+ exitCode,
47674
+ firstError,
47675
+ tail: truncate(tail2, ARTIFACT_PAYLOAD_BUDGET),
47676
+ totalLines: lines2.length
47677
+ };
47678
+ }
47679
+ case "test_result": {
47680
+ return {
47681
+ status: payload.status ?? null,
47682
+ command: payload.command ?? null,
47683
+ summary: typeof payload.summary === "string" ? truncate(payload.summary, ARTIFACT_PAYLOAD_BUDGET) : null,
47684
+ failed: Array.isArray(payload.failedTests) ? payload.failedTests.slice(0, 25) : null
47685
+ };
47686
+ }
47687
+ case "diff_summary": {
47688
+ return {
47689
+ files: Array.isArray(payload.files) ? payload.files.slice(0, 50) : null,
47690
+ added: payload.added ?? null,
47691
+ removed: payload.removed ?? null,
47692
+ renamed: payload.renamed ?? null
47693
+ };
47694
+ }
47695
+ case "handoff_judgement":
47696
+ case "self_review":
47697
+ case "error":
47698
+ case "checkpoint":
47699
+ default: {
47700
+ const flattened = JSON.stringify(payload);
47701
+ return { excerpt: truncate(flattened, ARTIFACT_PAYLOAD_BUDGET) };
47702
+ }
47703
+ }
47704
+ }
47705
+ function compactExecutionArtifacts(projectSlug, artifacts, maxItems) {
47706
+ const total = artifacts.length;
47707
+ const included = Math.min(total, maxItems);
47708
+ const slice = total > included ? artifacts.slice(total - included) : artifacts;
47709
+ const items = slice.map((a) => ({
47710
+ id: a.id,
47711
+ runId: a.runId,
47712
+ kind: a.kind,
47713
+ createdAt: a.createdAt,
47714
+ payload: compactArtifactPayload(a)
47715
+ }));
47716
+ const runIds = Array.from(new Set(slice.map((a) => a.runId)));
47717
+ const handles = runIds.map((runId) => ({
47718
+ handle: formatRunArtifactsHandle(projectSlug, runId),
47719
+ retrieveTool: mcpToolNames.listExecutionArtifacts,
47720
+ retrieveArgs: { projectSlug, runId }
47721
+ }));
47722
+ return {
47723
+ summary: `${total} execution artifact${total === 1 ? "" : "s"}; ${included} included (most recent first dropped older).`,
47724
+ items,
47725
+ handles,
47726
+ counts: { total, included, omitted: total - included }
47727
+ };
47728
+ }
47729
+ function compactTaskEvents(projectSlug, taskId, events, maxItems) {
47730
+ const total = events.length;
47731
+ const included = Math.min(total, maxItems);
47732
+ const slice = total > included ? events.slice(total - included) : events;
47733
+ const items = slice.map((e) => ({
47734
+ id: e.id,
47735
+ eventType: e.eventType,
47736
+ actor: e.actor,
47737
+ createdAt: e.createdAt,
47738
+ autonomyLevel: e.autonomyLevel,
47739
+ autonomyReason: e.autonomyReason,
47740
+ bodyExcerpt: e.body ? truncate(e.body, NOTES_EXCERPT_CHARS) : null
47741
+ }));
47742
+ const handles = [
47743
+ {
47744
+ handle: formatTaskEventsHandle(projectSlug, taskId),
47745
+ retrieveTool: mcpToolNames.listTaskEvents,
47746
+ retrieveArgs: { projectSlug, taskId }
47747
+ }
47748
+ ];
47749
+ return {
47750
+ summary: `${total} task event${total === 1 ? "" : "s"} for ${taskId}; ${included} most recent included.`,
47751
+ items,
47752
+ handles,
47753
+ counts: { total, included, omitted: total - included }
47754
+ };
47755
+ }
47756
+ function compactGeneric(value, maxItems, projectSlug) {
47757
+ if (Array.isArray(value)) {
47758
+ const total = value.length;
47759
+ const included = Math.min(total, maxItems);
47760
+ const slice = value.slice(0, included).map((entry) => {
47761
+ const text2 = typeof entry === "string" ? entry : JSON.stringify(entry);
47762
+ return truncate(text2, ARTIFACT_PAYLOAD_BUDGET);
47763
+ });
47764
+ return {
47765
+ summary: `Compact array view: ${total} entries, ${included} included.`,
47766
+ items: slice,
47767
+ handles: [],
47768
+ counts: { total, included, omitted: total - included }
47769
+ };
47770
+ }
47771
+ if (value && typeof value === "object") {
47772
+ const obj = value;
47773
+ const keys = Object.keys(obj);
47774
+ return {
47775
+ summary: `Compact object view (project=${projectSlug}): keys=[${keys.join(", ")}].`,
47776
+ items: [
47777
+ Object.fromEntries(
47778
+ keys.map((k) => {
47779
+ const entry = obj[k];
47780
+ if (Array.isArray(entry)) return [k, `[Array(${entry.length})]`];
47781
+ if (entry && typeof entry === "object") return [k, "[Object]"];
47782
+ return [k, entry];
47783
+ })
47784
+ )
47785
+ ],
47786
+ handles: [],
47787
+ counts: { total: 1, included: 1, omitted: 0 }
47788
+ };
47789
+ }
47790
+ const text = typeof value === "string" ? value : JSON.stringify(value);
47791
+ return {
47792
+ summary: "Compact scalar view.",
47793
+ items: [truncate(text, ARTIFACT_PAYLOAD_BUDGET)],
47794
+ handles: [],
47795
+ counts: { total: 1, included: 1, omitted: 0 }
47796
+ };
47797
+ }
47798
+
47799
+ // src/context-compact/index.ts
47800
+ var COMPACT_WARNING = "Compact result. Source of truth lives in the Mrrlin API; call `retrieve_context` with one of the embedded mrrlin:// handles to load the full record before any irreversible decision.";
47801
+ function serializedChars(value) {
47802
+ return JSON.stringify({ data: value }, null, 2).length;
47803
+ }
47804
+ function buildEnvelope(dispatch, body, originalChars) {
47805
+ const partial2 = {
47806
+ compact: true,
47807
+ kind: dispatch.kind,
47808
+ toolName: dispatch.toolName,
47809
+ projectSlug: dispatch.projectSlug,
47810
+ summary: body.summary,
47811
+ counts: body.counts,
47812
+ items: body.items,
47813
+ handles: body.handles,
47814
+ warning: COMPACT_WARNING
47815
+ };
47816
+ const compactNoStats = JSON.stringify({ data: partial2 }, null, 2).length;
47817
+ const statsOverhead = 220;
47818
+ const compactChars = compactNoStats + statsOverhead;
47819
+ const savedChars = originalChars - compactChars;
47820
+ const savedRatio = originalChars > 0 ? Math.max(0, savedChars / originalChars) : 0;
47821
+ const envelope = {
47822
+ ...partial2,
47823
+ stats: {
47824
+ originalChars,
47825
+ compactChars,
47826
+ savedChars,
47827
+ savedRatio: Number(savedRatio.toFixed(4))
47828
+ }
47829
+ };
47830
+ return { envelope, shrunk: compactChars < originalChars };
47831
+ }
47832
+ function runCompactor(dispatch, policy) {
47833
+ switch (dispatch.kind) {
47834
+ case "task-list":
47835
+ return compactTaskList(dispatch.projectSlug, dispatch.rawValue, policy.maxItems);
47836
+ case "wiki-list":
47837
+ return compactWikiPageList(dispatch.projectSlug, dispatch.rawValue, policy.maxItems);
47838
+ case "wiki-search":
47839
+ return compactWikiSearch(
47840
+ dispatch.projectSlug,
47841
+ dispatch.rawValue,
47842
+ policy.maxItems
47843
+ );
47844
+ case "project-snapshot":
47845
+ return compactProjectSnapshot(
47846
+ dispatch.projectSlug,
47847
+ dispatch.rawValue,
47848
+ policy.maxItems
47849
+ );
47850
+ case "execution-artifacts":
47851
+ return compactExecutionArtifacts(
47852
+ dispatch.projectSlug,
47853
+ dispatch.rawValue,
47854
+ policy.maxItems
47855
+ );
47856
+ case "task-events": {
47857
+ if (!dispatch.taskId) throw new Error("task-events compact requires taskId");
47858
+ return compactTaskEvents(dispatch.projectSlug, dispatch.taskId, dispatch.rawValue, policy.maxItems);
47859
+ }
47860
+ case "generic-json":
47861
+ return compactGeneric(dispatch.rawValue, policy.maxItems, dispatch.projectSlug);
47862
+ }
47863
+ }
47864
+ function maybeCompact(dispatch, policy = readCompactPolicy()) {
47865
+ if (!policy.enabled) return null;
47866
+ const originalChars = serializedChars(dispatch.rawValue);
47867
+ if (originalChars <= policy.thresholdChars) return null;
47868
+ const body = runCompactor(dispatch, policy);
47869
+ const result = buildEnvelope(dispatch, body, originalChars);
47870
+ if (policy.debug) {
47871
+ process.stderr.write(
47872
+ `[mrrlin-mcp compact] tool=${dispatch.toolName} kind=${dispatch.kind} originalChars=${originalChars} compactChars=${result.envelope.stats.compactChars} savedRatio=${result.envelope.stats.savedRatio}
47873
+ `
47874
+ );
47875
+ }
47876
+ if (!result.shrunk) return null;
47877
+ return result.envelope;
47878
+ }
47879
+ var COMPACT_OPT_IN = {
47880
+ [mcpToolNames.listTasks]: "task-list",
47881
+ [mcpToolNames.listWikiPages]: "wiki-list",
47882
+ [mcpToolNames.searchWikiPages]: "wiki-search",
47883
+ [mcpToolNames.getProjectSnapshot]: "project-snapshot",
47884
+ [mcpToolNames.listExecutionArtifacts]: "execution-artifacts",
47885
+ [mcpToolNames.listTaskEvents]: "task-events"
47886
+ };
47887
+
47888
+ // src/tools.ts
47889
+ registerAsyncTool(consensusDescriptor);
47284
47890
  var projectScopedSchema = external_exports.object({ projectSlug: mrrlinProjectSlugSchema });
47285
47891
  var taskScopedSchema = projectScopedSchema.extend({ taskId: mrrlinTaskIdSchema });
47286
47892
  var planScopedSchema = projectScopedSchema.extend({ planId: mrrlinPlanIdSchema });
@@ -47332,7 +47938,6 @@ var runCodeReviewerGateInputSchema = external_exports.object({
47332
47938
  var searchKnowledgeInputSchema = wikiKnowledgeSearchInputSchema;
47333
47939
  var createGithubBranchInputSchema = projectScopedSchema.merge(githubCreateBranchRequestSchema);
47334
47940
  var createGithubPullRequestInputSchema = projectScopedSchema.merge(githubCreatePullRequestRequestSchema);
47335
- var listGithubWebhookDeliveriesInputSchema = projectScopedSchema;
47336
47941
  var listGithubWorkflowsInputSchema = projectScopedSchema;
47337
47942
  var dispatchGithubWorkflowInputSchema = projectScopedSchema.extend({ workflowId: external_exports.string().min(1) }).merge(githubWorkflowDispatchRequestSchema);
47338
47943
  var dispatchTaskDeployInputSchema = taskScopedSchema.extend({
@@ -47381,6 +47986,14 @@ var registerLocalCheckoutInputSchema = projectScopedSchema.extend({
47381
47986
  path: external_exports.string().min(1).describe("Absolute path to the local git checkout (must be a git repo whose origin matches `repo`)."),
47382
47987
  repo: external_exports.string().min(1).describe("The project's bound GitHub repo full name in `owner/repo` form.")
47383
47988
  });
47989
+ var retrieveContextInputSchema = external_exports.object({
47990
+ handle: external_exports.string().min(1).describe(
47991
+ "A mrrlin:// handle emitted in a compact envelope. Examples: mrrlin://task/{slug}/GT-123, mrrlin://wiki/{slug}/WK-1, mrrlin://project/{slug}/snapshot, mrrlin://run/{slug}/ER-9, mrrlin://run/{slug}/ER-9/artifacts."
47992
+ ),
47993
+ mode: external_exports.enum(["full", "summary", "metadata"]).optional().describe(
47994
+ "Reserved. v1 always returns the full record regardless; future versions will use this to scope rehydration."
47995
+ )
47996
+ });
47384
47997
  var mcpToolInputSchemas = {
47385
47998
  [mcpToolNames.appendExecutionArtifact]: appendExecutionArtifactInputSchema,
47386
47999
  [mcpToolNames.archivePlan]: archivePlanInputSchema,
@@ -47413,7 +48026,6 @@ var mcpToolInputSchemas = {
47413
48026
  [mcpToolNames.getGithubInstallationRepositories]: getGithubInstallationRepositoriesInputSchema,
47414
48027
  [mcpToolNames.listExecutionArtifacts]: listExecutionArtifactsInputSchema,
47415
48028
  [mcpToolNames.listExecutionRuns]: listExecutionRunsInputSchema,
47416
- [mcpToolNames.listGithubWebhookDeliveries]: listGithubWebhookDeliveriesInputSchema,
47417
48029
  [mcpToolNames.listGithubInstallations]: listGithubInstallationsInputSchema,
47418
48030
  [mcpToolNames.listGithubWorkflows]: listGithubWorkflowsInputSchema,
47419
48031
  [mcpToolNames.listPlans]: listPlansInputSchema,
@@ -47440,7 +48052,8 @@ var mcpToolInputSchemas = {
47440
48052
  [mcpToolNames.uploadArtifact]: uploadArtifactInputSchema,
47441
48053
  [mcpToolNames.listArtifactFiles]: listArtifactFilesInputSchema,
47442
48054
  [mcpToolNames.resolveHandoff]: resolveHandoffInputSchema,
47443
- [mcpToolNames.registerLocalCheckout]: registerLocalCheckoutInputSchema
48055
+ [mcpToolNames.registerLocalCheckout]: registerLocalCheckoutInputSchema,
48056
+ [mcpToolNames.retrieveContext]: retrieveContextInputSchema
47444
48057
  };
47445
48058
  var mcpToolOperationIds = {
47446
48059
  [mcpToolNames.appendExecutionArtifact]: openApiOperationIds.appendExecutionArtifact,
@@ -47474,7 +48087,6 @@ var mcpToolOperationIds = {
47474
48087
  [mcpToolNames.getGithubInstallationRepositories]: openApiOperationIds.getGithubInstallationRepositories,
47475
48088
  [mcpToolNames.listExecutionArtifacts]: openApiOperationIds.listExecutionArtifacts,
47476
48089
  [mcpToolNames.listExecutionRuns]: openApiOperationIds.listExecutionRuns,
47477
- [mcpToolNames.listGithubWebhookDeliveries]: openApiOperationIds.listGithubWebhookDeliveries,
47478
48090
  [mcpToolNames.listGithubInstallations]: openApiOperationIds.listGithubInstallations,
47479
48091
  [mcpToolNames.listGithubWorkflows]: openApiOperationIds.listGithubWorkflows,
47480
48092
  [mcpToolNames.listPlans]: openApiOperationIds.listPlans,
@@ -47524,7 +48136,6 @@ var mcpToolDescriptions = {
47524
48136
  [mcpToolNames.dispatchGithubWorkflow]: "Dispatch a GitHub Actions workflow by workflow file/id against a ref visible to the linked installation.",
47525
48137
  [mcpToolNames.dispatchTaskDeploy]: "Dispatch a task's dev or prod deploy; caller supplies workflowId chosen for this run (ADR 0012). Subject to auto_deploy policy and prod dev-verification gate.",
47526
48138
  [mcpToolNames.getGithubInstallationRepositories]: "List repositories visible to a GitHub App installation id.",
47527
- [mcpToolNames.listGithubWebhookDeliveries]: "List recent persisted GitHub webhook deliveries for the project so install/webhook smoke can be verified without raw DB access.",
47528
48139
  [mcpToolNames.listGithubInstallations]: "List persisted GitHub App installations.",
47529
48140
  [mcpToolNames.listGithubWorkflows]: "List the bound repository's GitHub Actions workflows live through the linked installation. No Mrrlin-side cache.",
47530
48141
  [mcpToolNames.listProjects]: "List every project visible to the operator.",
@@ -47559,7 +48170,8 @@ var mcpToolDescriptions = {
47559
48170
  [mcpToolNames.uploadArtifact]: "Upload a local file (md/txt/html/csv/json/pdf/png/jpg/webp/gif, <=5MB) as a project artifact. Returns a stable markdown link to embed in chat/spec/handoff text. Use class=temp for one-off hand-offs, class=durable for anything referenced by specs or evidence. Pass runId to record the file in that run's artifact history; pass taskId to surface it on the task's Artifacts list.",
47560
48171
  [mcpToolNames.listArtifactFiles]: "List uploaded artifact files for the project (optionally filtered by taskId/runId), newest first.",
47561
48172
  [mcpToolNames.resolveHandoff]: "Resolve a tool_assisted handoff for an execution run (outcome success|failure). Success returns the task to the board; failure opens a new handoff cycle. Idempotent.",
47562
- [mcpToolNames.registerLocalCheckout]: "Register the operator-local git checkout path for `projectSlug` so execution-run workers run code on this machine instead of the cloud. Validates the path is a git repo whose `origin` remote matches `repo`, then writes the (slug -> absolute path) entry to the operator-local checkout registry. Call this after locating the checkout on disk (e.g. via filesystem search) and confirming the choice with the user in chat \u2014 they should NOT have to paste the path into Settings. Returns `{ projectSlug, path, confirmedAt }`. Idempotent: re-registering overwrites the prior entry."
48173
+ [mcpToolNames.registerLocalCheckout]: "Register the operator-local git checkout path for `projectSlug` so execution-run workers run code on this machine instead of the cloud. Validates the path is a git repo whose `origin` remote matches `repo`, then writes the (slug -> absolute path) entry to the operator-local checkout registry. Call this after locating the checkout on disk (e.g. via filesystem search) and confirming the choice with the user in chat \u2014 they should NOT have to paste the path into Settings. Returns `{ projectSlug, path, confirmedAt }`. Idempotent: re-registering overwrites the prior entry.",
48174
+ [mcpToolNames.retrieveContext]: "Rehydrate a mrrlin:// handle emitted by the compact-context layer into the full canonical Mrrlin record (task/wiki page/project snapshot/execution run/artifacts). Use whenever you need the full source \u2014 irreversible decisions (closing tasks, dispatching deploys, writing specs) MUST call this first; never act on a compact envelope alone. Returns `{ kind, data }`. Invalid or unsupported handle \u2192 structured `HANDLE_INVALID` error."
47563
48175
  };
47564
48176
  var ARTIFACT_EXTENSION_TYPES = {
47565
48177
  ".md": "text/markdown",
@@ -47663,6 +48275,41 @@ function makeTool(client, name, fallbackCode, fallbackMessage, handler) {
47663
48275
  }
47664
48276
  };
47665
48277
  }
48278
+ function structuredOkCompactEnvelope(envelope) {
48279
+ return {
48280
+ content: [{ text: JSON.stringify({ data: envelope }, null, 2), type: "text" }],
48281
+ isError: false,
48282
+ structuredContent: { data: envelope }
48283
+ };
48284
+ }
48285
+ function structuredOkMaybeCompact(name, projectSlug, value, context = {}) {
48286
+ const kind = COMPACT_OPT_IN[name];
48287
+ if (!kind) return structuredOk(value);
48288
+ const envelope = maybeCompact(
48289
+ { toolName: name, projectSlug, kind, rawValue: value, taskId: context.taskId },
48290
+ readCompactPolicy()
48291
+ );
48292
+ if (!envelope) return structuredOk(value);
48293
+ return structuredOkCompactEnvelope(envelope);
48294
+ }
48295
+ function makeReadTool(client, name, fallbackCode, fallbackMessage, handler, projectSlugFrom, taskIdFrom) {
48296
+ return {
48297
+ description: mcpToolDescriptions[name],
48298
+ inputSchema: mcpToolInputSchemas[name],
48299
+ name,
48300
+ openApiOperationId: mcpToolOperationIds[name],
48301
+ async invoke(input) {
48302
+ try {
48303
+ const value = await handler(client, input);
48304
+ return structuredOkMaybeCompact(name, projectSlugFrom(input), value, {
48305
+ taskId: taskIdFrom ? taskIdFrom(input) : void 0
48306
+ });
48307
+ } catch (error51) {
48308
+ return structuredErr(classifyClientError(fallbackCode, fallbackMessage, error51));
48309
+ }
48310
+ }
48311
+ };
48312
+ }
47666
48313
  function createMrrlinTools(options) {
47667
48314
  const client = createMrrlinClient(withAuth(options));
47668
48315
  return {
@@ -47799,12 +48446,13 @@ function createMrrlinTools(options) {
47799
48446
  "Unable to read project.",
47800
48447
  async (c, { projectSlug }) => await c.getProject(projectSlug)
47801
48448
  ),
47802
- [mcpToolNames.getProjectSnapshot]: makeTool(
48449
+ [mcpToolNames.getProjectSnapshot]: makeReadTool(
47803
48450
  client,
47804
48451
  mcpToolNames.getProjectSnapshot,
47805
48452
  "PROJECT_SNAPSHOT_READ_FAILED",
47806
48453
  "Unable to read project snapshot.",
47807
- async (c, { projectSlug }) => await c.getProjectSnapshot(projectSlug)
48454
+ async (c, { projectSlug }) => await c.getProjectSnapshot(projectSlug),
48455
+ ({ projectSlug }) => projectSlug
47808
48456
  ),
47809
48457
  [mcpToolNames.getTask]: makeTool(
47810
48458
  client,
@@ -47834,13 +48482,6 @@ function createMrrlinTools(options) {
47834
48482
  "Unable to dispatch the task deploy workflow.",
47835
48483
  async (c, { projectSlug, taskId, workflowId, ...rest }) => await c.dispatchTaskDeploy(projectSlug, taskId, { ...rest, workflowId })
47836
48484
  ),
47837
- [mcpToolNames.listGithubWebhookDeliveries]: makeTool(
47838
- client,
47839
- mcpToolNames.listGithubWebhookDeliveries,
47840
- "GITHUB_WEBHOOK_DELIVERY_LIST_FAILED",
47841
- "Unable to list GitHub webhook deliveries.",
47842
- async (c, { projectSlug }) => await c.listGithubWebhookDeliveries(projectSlug)
47843
- ),
47844
48485
  [mcpToolNames.listGithubWorkflows]: makeTool(
47845
48486
  client,
47846
48487
  mcpToolNames.listGithubWorkflows,
@@ -47869,19 +48510,22 @@ function createMrrlinTools(options) {
47869
48510
  "Unable to list task dependencies.",
47870
48511
  async (c, { projectSlug }) => await c.listTaskDependencies(projectSlug)
47871
48512
  ),
47872
- [mcpToolNames.listTaskEvents]: makeTool(
48513
+ [mcpToolNames.listTaskEvents]: makeReadTool(
47873
48514
  client,
47874
48515
  mcpToolNames.listTaskEvents,
47875
48516
  "TASK_EVENT_LIST_FAILED",
47876
48517
  "Unable to list task events.",
47877
- async (c, { projectSlug, taskId }) => await c.listTaskEvents(projectSlug, taskId)
48518
+ async (c, { projectSlug, taskId }) => await c.listTaskEvents(projectSlug, taskId),
48519
+ ({ projectSlug }) => projectSlug,
48520
+ ({ taskId }) => taskId
47878
48521
  ),
47879
- [mcpToolNames.listTasks]: makeTool(
48522
+ [mcpToolNames.listTasks]: makeReadTool(
47880
48523
  client,
47881
48524
  mcpToolNames.listTasks,
47882
48525
  "TASK_LIST_FAILED",
47883
48526
  "Unable to list tasks.",
47884
- async (c, { projectSlug, ...filters }) => await c.listTasks(projectSlug, filters)
48527
+ async (c, { projectSlug, ...filters }) => await c.listTasks(projectSlug, filters),
48528
+ ({ projectSlug }) => projectSlug
47885
48529
  ),
47886
48530
  [mcpToolNames.listTaxonomy]: makeTool(
47887
48531
  client,
@@ -47890,19 +48534,21 @@ function createMrrlinTools(options) {
47890
48534
  "Unable to list taxonomy.",
47891
48535
  async (c, { projectSlug }) => await c.listTaxonomy(projectSlug)
47892
48536
  ),
47893
- [mcpToolNames.listWikiPages]: makeTool(
48537
+ [mcpToolNames.listWikiPages]: makeReadTool(
47894
48538
  client,
47895
48539
  mcpToolNames.listWikiPages,
47896
48540
  "WIKI_PAGE_LIST_FAILED",
47897
48541
  "Unable to list wiki pages.",
47898
- async (c, { projectSlug, ...filters }) => await c.listWikiPages(projectSlug, filters)
48542
+ async (c, { projectSlug, ...filters }) => await c.listWikiPages(projectSlug, filters),
48543
+ ({ projectSlug }) => projectSlug
47899
48544
  ),
47900
- [mcpToolNames.searchWikiPages]: makeTool(
48545
+ [mcpToolNames.searchWikiPages]: makeReadTool(
47901
48546
  client,
47902
48547
  mcpToolNames.searchWikiPages,
47903
48548
  "WIKI_PAGE_SEARCH_FAILED",
47904
48549
  "Unable to search wiki pages.",
47905
- async (c, { projectSlug, ...query }) => await c.searchWikiPages(projectSlug, query)
48550
+ async (c, { projectSlug, ...query }) => await c.searchWikiPages(projectSlug, query),
48551
+ ({ projectSlug }) => projectSlug
47906
48552
  ),
47907
48553
  [mcpToolNames.searchKnowledge]: makeTool(
47908
48554
  client,
@@ -48049,12 +48695,13 @@ function createMrrlinTools(options) {
48049
48695
  "Unable to update execution run.",
48050
48696
  async (c, { projectSlug, runId, ...patch }) => await c.updateExecutionRun(projectSlug, runId, patch)
48051
48697
  ),
48052
- [mcpToolNames.listExecutionArtifacts]: makeTool(
48698
+ [mcpToolNames.listExecutionArtifacts]: makeReadTool(
48053
48699
  client,
48054
48700
  mcpToolNames.listExecutionArtifacts,
48055
48701
  "EXECUTION_ARTIFACTS_LIST_FAILED",
48056
48702
  "Unable to list execution artifacts.",
48057
- async (c, { projectSlug, runId }) => await c.listExecutionArtifacts(projectSlug, runId)
48703
+ async (c, { projectSlug, runId }) => await c.listExecutionArtifacts(projectSlug, runId),
48704
+ ({ projectSlug }) => projectSlug
48058
48705
  ),
48059
48706
  [mcpToolNames.appendExecutionArtifact]: makeTool(
48060
48707
  client,
@@ -48273,9 +48920,49 @@ function createMrrlinTools(options) {
48273
48920
  registry3.confirm(projectSlug, resolvedPath, confirmedAt);
48274
48921
  return { projectSlug, path: resolvedPath, confirmedAt };
48275
48922
  }
48923
+ ),
48924
+ [mcpToolNames.retrieveContext]: makeTool(
48925
+ client,
48926
+ mcpToolNames.retrieveContext,
48927
+ "HANDLE_INVALID",
48928
+ "Unable to retrieve handle.",
48929
+ async (c, { handle }) => {
48930
+ const parsed = parseMrrlinHandle(handle);
48931
+ if (!parsed) {
48932
+ throw new McpToolCodedError(
48933
+ "HANDLE_INVALID",
48934
+ `Unrecognized mrrlin:// handle: ${handle}. Valid kinds: task | wiki | project/{slug}/snapshot | project/{slug}/tasks | run/{slug}/{runId} | run/{slug}/{runId}/artifacts.`
48935
+ );
48936
+ }
48937
+ return await dispatchHandle(c, parsed);
48938
+ }
48276
48939
  )
48277
48940
  };
48278
48941
  }
48942
+ async function dispatchHandle(client, handle) {
48943
+ switch (handle.kind) {
48944
+ case "task":
48945
+ return { kind: handle.kind, data: await client.getTask(handle.projectSlug, handle.taskId) };
48946
+ case "task-events":
48947
+ return {
48948
+ kind: handle.kind,
48949
+ data: await client.listTaskEvents(handle.projectSlug, handle.taskId)
48950
+ };
48951
+ case "wiki":
48952
+ return { kind: handle.kind, data: await client.getWikiPage(handle.projectSlug, handle.pageId) };
48953
+ case "project-snapshot":
48954
+ return { kind: handle.kind, data: await client.getProjectSnapshot(handle.projectSlug) };
48955
+ case "project-tasks":
48956
+ return { kind: handle.kind, data: await client.listTasks(handle.projectSlug, {}) };
48957
+ case "run":
48958
+ return { kind: handle.kind, data: await client.getExecutionRun(handle.projectSlug, handle.runId) };
48959
+ case "run-artifacts":
48960
+ return {
48961
+ kind: handle.kind,
48962
+ data: await client.listExecutionArtifacts(handle.projectSlug, handle.runId)
48963
+ };
48964
+ }
48965
+ }
48279
48966
 
48280
48967
  // src/server.ts
48281
48968
  function redactUrlForLog(value) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrrlin-dev/mcp",
3
- "version": "0.2.6",
3
+ "version": "0.3.0",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "mrrlin-mcp": "dist/bin.cjs"
@@ -20,8 +20,8 @@
20
20
  "@types/ws": "^8.18.1",
21
21
  "esbuild": "^0.24.0",
22
22
  "tsx": "^4.22.3",
23
- "@mrrlin/client": "0.0.0",
24
23
  "@mrrlin/director-e2e": "0.0.0",
24
+ "@mrrlin/client": "0.0.0",
25
25
  "@mrrlin/wiki": "0.0.0",
26
26
  "@mrrlin/codex-client": "0.0.0",
27
27
  "@mrrlin/schemas": "0.0.0",