@remixhq/mcp 0.1.17 → 0.1.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -251,6 +251,15 @@ function normalizeByMessage(err) {
251
251
  category: "remote_state"
252
252
  });
253
253
  }
254
+ if (statusCode === 409) {
255
+ return makeNormalized({
256
+ code: ERROR_CODES.REMOTE_ERROR,
257
+ message,
258
+ hint,
259
+ retryable: true,
260
+ category: "remote_state"
261
+ });
262
+ }
254
263
  if (message.includes("Timed out") || message.includes("failed") || message.includes("error state")) {
255
264
  return makeNormalized({
256
265
  code: ERROR_CODES.REMOTE_ERROR,
@@ -427,7 +436,6 @@ function makeSuccessResult(envelope) {
427
436
  function makeErrorResult(envelope) {
428
437
  return {
429
438
  content: [{ type: "text", text: toJsonText(envelope) }],
430
- structuredContent: envelope,
431
439
  isError: true
432
440
  };
433
441
  }
@@ -506,9 +514,6 @@ var applyInputSchema = {
506
514
  confirm: z2.boolean(),
507
515
  allowBranchMismatch: z2.boolean().optional()
508
516
  };
509
- var reAnchorInputSchema = {
510
- ...applyInputSchema
511
- };
512
517
  var requestMergeInputSchema = {
513
518
  ...commonRequestFieldsSchema
514
519
  };
@@ -621,7 +626,7 @@ var initSyncDataSchema = z2.object({
621
626
  repoRoot: z2.string(),
622
627
  bindingMode: z2.enum(["legacy", "lane", "explicit_root"]).optional(),
623
628
  createdCanonicalFamily: z2.boolean().optional(),
624
- baselineStatus: z2.enum(["seeded", "existing", "requires_re_anchor", "requires_sync"]).optional()
629
+ baselineStatus: z2.enum(["seeded", "existing", "baseline_missing", "requires_sync"]).optional()
625
630
  });
626
631
  var initQueuedDataSchema = z2.object({
627
632
  queued: z2.literal(true),
@@ -669,7 +674,6 @@ var drainFinalizeQueueDataSchema = z2.object({
669
674
  results: z2.array(genericRecordSchema)
670
675
  });
671
676
  var syncDataSchema = genericRecordSchema;
672
- var reAnchorDataSchema = genericRecordSchema;
673
677
  var requestMergeDataSchema = genericRecordSchema;
674
678
  var mergeRequestQueueDataSchema = z2.object({
675
679
  queue: mergeRequestQueueSchema,
@@ -745,7 +749,6 @@ var addSuccessSchema = makeSuccessSchema(addDataSchema);
745
749
  var recordTurnSuccessSchema = makeSuccessSchema(recordTurnDataSchema);
746
750
  var drainFinalizeQueueSuccessSchema = makeSuccessSchema(drainFinalizeQueueDataSchema);
747
751
  var syncSuccessSchema = makeSuccessSchema(syncDataSchema);
748
- var reAnchorSuccessSchema = makeSuccessSchema(reAnchorDataSchema);
749
752
  var requestMergeSuccessSchema = makeSuccessSchema(requestMergeDataSchema);
750
753
  var mergeRequestQueueSuccessSchema = makeSuccessSchema(mergeRequestQueueDataSchema);
751
754
  var viewMergeRequestSuccessSchema = makeSuccessSchema(viewMergeRequestDataSchema);
@@ -772,7 +775,6 @@ import {
772
775
  collabCheckout as coreCollabCheckout,
773
776
  collabListMergeRequests as coreCollabListMergeRequests,
774
777
  collabInit as coreCollabInit,
775
- collabReAnchor as coreCollabReAnchor,
776
778
  collabInvite as coreCollabInvite,
777
779
  collabReconcile as coreCollabReconcile,
778
780
  collabReject as coreCollabReject,
@@ -787,7 +789,7 @@ import { findGitRoot } from "@remixhq/core/repo";
787
789
  function getRiskLevel(status) {
788
790
  if (status.recommendedAction === "reconcile") return "high";
789
791
  if (status.recommendedAction === "choose_family" || status.recommendedAction === "await_finalize") return "medium";
790
- if (status.recommendedAction === "pull" || status.recommendedAction === "re_anchor" || status.remote.incomingOpenMergeRequestCount) {
792
+ if (status.recommendedAction === "pull" || status.remote.incomingOpenMergeRequestCount) {
791
793
  return "medium";
792
794
  }
793
795
  if (status.repo.branchMismatch || !status.repo.isGitRepo || !status.binding.isBound || !status.repo.worktree.isClean) return "medium";
@@ -804,10 +806,6 @@ function getRecommendedNextActions(status) {
804
806
  return ["Run remix_collab_init to bind the repository to Remix before using any Remix collaboration mutation flow."];
805
807
  case "pull":
806
808
  return ["Run remix_collab_sync_preview, then remix_collab_sync_apply if the preview is acceptable. This pulls the server delta into the local working tree without rewriting local git history."];
807
- case "re_anchor":
808
- return [
809
- "Run remix_collab_re_anchor_preview, then remix_collab_re_anchor_apply. This seeds a local Remix baseline. It is required because no local baseline exists for this lane yet (fresh clone, deleted .remix/ state, or first init didn't seed) \u2014 not because of any specific git operation. After it succeeds, automatic hook recording can capture completed turns."
810
- ];
811
809
  case "record":
812
810
  return [
813
811
  "No MCP recording tool is required. Automatic hook finalization will capture the local boundary at the end of the completed turn; this covers agent edits, manual user edits, git commit, git pull, git merge, git rebase, and git reset."
@@ -892,8 +890,8 @@ async function initCollab(params) {
892
890
  return {
893
891
  data: syncResult,
894
892
  warnings: collectResultWarnings(result),
895
- recommendedNextActions: syncResult.baselineStatus === "requires_re_anchor" ? [
896
- "This checkout has no local Remix baseline yet. Run remix_collab_re_anchor_preview, then remix_collab_re_anchor_apply to seed one. After it succeeds, automatic hook recording can capture completed turns."
893
+ recommendedNextActions: syncResult.baselineStatus === "baseline_missing" ? [
894
+ "This checkout has no local Remix revision baseline yet. Run remix_collab_init or remix_collab_sync_preview/apply to seed one. After it succeeds, automatic hook recording can capture completed turns."
897
895
  ] : syncResult.baselineStatus === "requires_sync" ? [
898
896
  "Run remix_collab_sync_preview, then remix_collab_sync_apply to pull the server delta and create the first local baseline for this checkout."
899
897
  ] : ["Run remix_collab_status to inspect sync, reconcile, and merge-request readiness before mutating bound-repo state."],
@@ -996,26 +994,6 @@ async function syncCollab(params) {
996
994
  }
997
995
  };
998
996
  }
999
- async function reAnchor(params) {
1000
- const api = await createCollabApiClient();
1001
- const result = await coreCollabReAnchor({
1002
- api,
1003
- cwd: params.cwd,
1004
- dryRun: params.dryRun,
1005
- allowBranchMismatch: params.allowBranchMismatch ?? false
1006
- });
1007
- return {
1008
- data: result,
1009
- warnings: collectWarnings(result.warnings),
1010
- recommendedNextActions: params.dryRun ? [
1011
- "Run remix_collab_re_anchor_apply with confirm=true to seed a local Remix baseline for this checkout. Re-anchor is for missing-baseline cases only and does not replace automatic hook recording for ordinary local content changes."
1012
- ] : [],
1013
- logContext: {
1014
- repoRoot: result.repoRoot,
1015
- appId: result.currentAppId
1016
- }
1017
- };
1018
- }
1019
997
  async function requestMerge(params) {
1020
998
  const api = await createCollabApiClient();
1021
999
  const drainWarnings = await drainBeforeMutation(api);
@@ -1752,7 +1730,10 @@ function shouldAutoSpawnHistoryImport(repoRoot) {
1752
1730
  return false;
1753
1731
  }
1754
1732
  }
1755
- function spawnHistoryImportDetached(repoRoot) {
1733
+ function isAutoSpawnEligibleBindingMode(bindingMode) {
1734
+ return bindingMode === "explicit_root";
1735
+ }
1736
+ function spawnHistoryImportDetached(repoRoot, options) {
1756
1737
  const remixDir = path2.join(repoRoot, ".remix");
1757
1738
  try {
1758
1739
  mkdirSync(remixDir, { recursive: true });
@@ -1768,6 +1749,8 @@ function spawnHistoryImportDetached(repoRoot) {
1768
1749
  "import",
1769
1750
  "--repo",
1770
1751
  repoRoot,
1752
+ "--before",
1753
+ options.cutoffAt,
1771
1754
  // Include prompt text for parity with the CLI auto-spawn path:
1772
1755
  // first-time UX is a lot worse if the dashboard renders every
1773
1756
  // historical row as "(prompt not uploaded)".
@@ -1837,6 +1820,24 @@ function buildErrorEnvelope(tool, requestId, error) {
1837
1820
  recommendedNextActions
1838
1821
  };
1839
1822
  }
1823
+ function buildOutputValidationErrorEnvelope(tool, requestId, error) {
1824
+ return {
1825
+ schemaVersion: SCHEMA_VERSION,
1826
+ ok: false,
1827
+ tool,
1828
+ requestId: requestId ?? null,
1829
+ error: {
1830
+ code: ERROR_CODES.INTERNAL_ERROR,
1831
+ message: "Tool output failed validation against its declared MCP schema.",
1832
+ hint: error.issues.map((issue) => `${issue.path.join(".") || "output"}: ${issue.message}`).join("; "),
1833
+ retryable: false,
1834
+ category: "internal"
1835
+ },
1836
+ warnings: [],
1837
+ risks: ["The MCP server returned an output shape that did not match the tool descriptor."],
1838
+ recommendedNextActions: ["Report this MCP schema mismatch with the tool name and error hint."]
1839
+ };
1840
+ }
1840
1841
  function registerTool(server, context, params) {
1841
1842
  const errorSchema = makeErrorSchema();
1842
1843
  server.registerTool(
@@ -1855,7 +1856,20 @@ function registerTool(server, context, params) {
1855
1856
  assertToolAccess(context.policy, params.access);
1856
1857
  const result = await params.run(rawArgs);
1857
1858
  const envelope = buildSuccessEnvelope(params.name, requestId, result);
1858
- params.outputSchema.parse(envelope);
1859
+ const parsedEnvelope = params.outputSchema.safeParse(envelope);
1860
+ if (!parsedEnvelope.success) {
1861
+ const errorEnvelope = buildOutputValidationErrorEnvelope(params.name, requestId, parsedEnvelope.error);
1862
+ context.logger.log({
1863
+ level: "error",
1864
+ message: "tool_output_validation_failed",
1865
+ tool: params.name,
1866
+ requestId: errorEnvelope.requestId,
1867
+ durationMs: Date.now() - startedAt,
1868
+ result: "error",
1869
+ errorCode: errorEnvelope.error.code
1870
+ });
1871
+ return makeErrorResult(errorEnvelope);
1872
+ }
1859
1873
  context.logger.log({
1860
1874
  level: "info",
1861
1875
  message: "tool_completed",
@@ -1918,11 +1932,13 @@ function registerCollabTools(server, context) {
1918
1932
  });
1919
1933
  try {
1920
1934
  const repoRoot = result && typeof result === "object" && "data" in result && result.data && typeof result.data.repoRoot === "string" ? result.data.repoRoot : null;
1921
- if (repoRoot && shouldAutoSpawnHistoryImport(repoRoot)) {
1922
- const spawned = spawnHistoryImportDetached(repoRoot);
1935
+ const bindingMode = result && typeof result === "object" && "data" in result && result.data && typeof result.data.bindingMode === "string" ? result.data.bindingMode : null;
1936
+ if (repoRoot && isAutoSpawnEligibleBindingMode(bindingMode) && shouldAutoSpawnHistoryImport(repoRoot)) {
1937
+ const cutoffAt = (/* @__PURE__ */ new Date()).toISOString();
1938
+ const spawned = spawnHistoryImportDetached(repoRoot, { cutoffAt });
1923
1939
  context.logger.log({
1924
1940
  level: "info",
1925
- message: `history_import_auto_spawned pid=${spawned.pid ?? "?"} log=${spawned.logPath}`,
1941
+ message: `history_import_auto_spawned pid=${spawned.pid ?? "?"} log=${spawned.logPath} cutoffAt=${cutoffAt}`,
1926
1942
  tool: "remix_collab_init",
1927
1943
  repoRoot
1928
1944
  });
@@ -2026,31 +2042,6 @@ function registerCollabTools(server, context) {
2026
2042
  return syncCollab({ cwd, dryRun: false, allowBranchMismatch: input.allowBranchMismatch ?? false });
2027
2043
  }
2028
2044
  });
2029
- registerTool(server, context, {
2030
- name: "remix_collab_re_anchor_preview",
2031
- description: "Preview whether this checkout needs a fresh local Remix baseline. Use only when status reports `re_anchor` (no local baseline exists for this lane yet \u2014 fresh clone, deleted `.remix/` state, or first init didn't seed). Re-anchor does not replace automatic hook recording; ordinary local content changes (including merges, pulls, and rebases) are captured at the completed-turn boundary, not by re-anchor.",
2032
- access: "read",
2033
- inputSchema: previewInputSchema,
2034
- outputSchema: reAnchorSuccessSchema,
2035
- run: async (args) => {
2036
- const input = z3.object(previewInputSchema).parse(args);
2037
- const cwd = resolvePolicyCwd(context.policy, input.cwd);
2038
- return reAnchor({ cwd, dryRun: true });
2039
- }
2040
- });
2041
- registerTool(server, context, {
2042
- name: "remix_collab_re_anchor_apply",
2043
- description: "Establish a local Remix baseline for the current checkout against the existing app head, without rewriting the local checkout afterward. Required only when status reports `re_anchor` (missing local baseline). It does not replace automatic hook recording \u2014 local commits, pulls, merges, and rebases are still captured at the completed-turn boundary.",
2044
- access: "local_write",
2045
- inputSchema: reAnchorInputSchema,
2046
- outputSchema: reAnchorSuccessSchema,
2047
- run: async (args) => {
2048
- const input = z3.object(reAnchorInputSchema).parse(args);
2049
- assertConfirm(input.confirm, "remix_collab_re_anchor_apply");
2050
- const cwd = resolvePolicyCwd(context.policy, input.cwd);
2051
- return reAnchor({ cwd, dryRun: false, allowBranchMismatch: input.allowBranchMismatch ?? false });
2052
- }
2053
- });
2054
2045
  registerTool(server, context, {
2055
2046
  name: "remix_collab_request_merge",
2056
2047
  description: "Open a prompt-backed Remix merge request from the current bound repository to its upstream app instead of merging locally with raw git.",