@drop2run/mcp 1.0.0 → 1.1.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/index.js +44 -10
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -87,6 +87,14 @@ function missingCredentialsMessage(surface = "cli") {
87
87
  "The token is shown once when it is created and cannot be recovered afterwards."
88
88
  ].join("\n");
89
89
  }
90
+ function problemCode(type, status) {
91
+ return typeof type === "string" && type !== "" && !/^[a-z][a-z0-9+.-]*:/i.test(type) ? type : `http_${status}`;
92
+ }
93
+ function problemTraceId(body) {
94
+ if (typeof body !== "object" || body === null) return null;
95
+ const value = body.traceId;
96
+ return typeof value === "string" && value !== "" ? value : null;
97
+ }
90
98
  function sizeOf(content) {
91
99
  if (ArrayBuffer.isView(content)) return content.byteLength;
92
100
  if (isArrayBuffer(content)) return content.byteLength;
@@ -270,7 +278,7 @@ async function problemToError(response) {
270
278
  `The server responded with ${response.status}.`
271
279
  );
272
280
  }
273
- const code = typeof problem.type === "string" ? problem.type : `http_${response.status}`;
281
+ const code = problemCode(problem.type, response.status);
274
282
  const message = typeof problem.detail === "string" ? problem.detail : typeof problem.title === "string" ? problem.title : `The server responded with ${response.status}.`;
275
283
  return new DeployError(code, message, problem);
276
284
  }
@@ -1542,6 +1550,7 @@ function asDeployError(error) {
1542
1550
  );
1543
1551
  }
1544
1552
  async function deploy(source, siteId, onProgress, signal, options = {}) {
1553
+ let deployId;
1545
1554
  try {
1546
1555
  onProgress({ type: "reading", bytes: 0, total: 0 });
1547
1556
  const collected = await source(
@@ -1563,6 +1572,7 @@ async function deploy(source, siteId, onProgress, signal, options = {}) {
1563
1572
  onProgress({ type: "unchanged", url: prepared.url ?? "" });
1564
1573
  return;
1565
1574
  }
1575
+ deployId = prepared.deployId;
1566
1576
  const sizes = new Map(hashed.map((file) => [file.path, sizeOf(file.bytes)]));
1567
1577
  const batches = prepared.batches ?? [];
1568
1578
  const targets = [...prepared.upload, ...batches];
@@ -1623,7 +1633,8 @@ async function deploy(source, siteId, onProgress, signal, options = {}) {
1623
1633
  type: "error",
1624
1634
  code: failure.code,
1625
1635
  message: failure.message,
1626
- ...failure.detail === void 0 ? {} : { detail: failure.detail }
1636
+ ...failure.detail === void 0 ? {} : { detail: failure.detail },
1637
+ ...deployId === void 0 ? {} : { deployId }
1627
1638
  });
1628
1639
  throw failure;
1629
1640
  }
@@ -1644,7 +1655,9 @@ async function call(credentials, path, init = {}) {
1644
1655
  async function failureOf(response) {
1645
1656
  const problem = await response.json().catch(() => null);
1646
1657
  return Object.assign(new Error(problem?.detail ?? `The API answered ${response.status}.`), {
1647
- status: response.status
1658
+ status: response.status,
1659
+ code: problemCode(problem?.type, response.status),
1660
+ traceId: problemTraceId(problem)
1648
1661
  });
1649
1662
  }
1650
1663
  function isTokenRefused(error) {
@@ -2305,6 +2318,22 @@ function describeSite(site, folderPath) {
2305
2318
  ...more > 0 ? [` … and ${more} older`] : []
2306
2319
  ].join("\n");
2307
2320
  }
2321
+ function failureContext(error) {
2322
+ if (typeof error !== "object" || error === null) return { code: null, traceId: null };
2323
+ const { code, traceId, detail } = error;
2324
+ return {
2325
+ code: typeof code === "string" && code !== "" ? code : null,
2326
+ traceId: typeof traceId === "string" && traceId !== "" ? traceId : problemTraceId(detail)
2327
+ };
2328
+ }
2329
+ function reportLink(dashboardOrigin, context, source) {
2330
+ if (context.traceId === null) return null;
2331
+ const url = new URL("/report", dashboardOrigin);
2332
+ if (context.code !== null) url.searchParams.set("code", context.code);
2333
+ url.searchParams.set("trace", context.traceId);
2334
+ url.searchParams.set("source", source);
2335
+ return url.toString();
2336
+ }
2308
2337
  const DEFAULT_WAIT_SECONDS = 120;
2309
2338
  const MAX_WAIT_SECONDS = 240;
2310
2339
  const ATTEMPT_LIFETIME_MS = 15 * 60 * 1e3;
@@ -2481,7 +2510,7 @@ async function signInWithCode(waitSeconds, replace = false) {
2481
2510
  "Call this tool again to keep waiting. Nothing has been changed yet."
2482
2511
  ].join("\n");
2483
2512
  }
2484
- const SERVER_VERSION = "1.0.0";
2513
+ const SERVER_VERSION = "1.1.0";
2485
2514
  const REPLACES_A_SITE = {
2486
2515
  readOnlyHint: false,
2487
2516
  destructiveHint: true,
@@ -2706,16 +2735,21 @@ async function withCredentials(context, work) {
2706
2735
  () => work(credentials),
2707
2736
  (error) => {
2708
2737
  if (isTokenRefused(error)) context.tokenRefused?.();
2709
- }
2738
+ },
2739
+ dashboardUrlFor(credentials.apiBaseUrl)
2710
2740
  );
2711
2741
  }
2712
- async function attempt(work, observe) {
2742
+ async function attempt(work, observe, dashboardOrigin) {
2713
2743
  try {
2714
2744
  const answer = await work();
2715
2745
  return typeof answer === "string" ? say(answer) : answer;
2716
2746
  } catch (error) {
2717
2747
  observe?.(error);
2718
- return say(error instanceof Error ? error.message : String(error), true);
2748
+ const message = error instanceof Error ? error.message : String(error);
2749
+ const report2 = dashboardOrigin === void 0 ? null : reportLink(dashboardOrigin, failureContext(error), "mcp");
2750
+ return say(report2 === null ? message : `${message}
2751
+
2752
+ Report it: ${report2}`, true);
2719
2753
  }
2720
2754
  }
2721
2755
  function describe(result) {
@@ -2834,7 +2868,7 @@ function registerSiteTools(server, context) {
2834
2868
  "delete_site",
2835
2869
  {
2836
2870
  ...titled("Delete a site", REMOVES_A_SITE),
2837
- description: "Takes a site down permanently — its files, its history and its subdomain. Nothing here undoes it, and the subdomain stays reserved — nobody can use it again. Requires `confirm` to repeat the site's own subdomain exactly; ask the person for it rather than filling it in from what you already know, because that is the step this asks for.",
2871
+ description: "Takes a site down permanently — its files, its history and its subdomain. Nothing here undoes it, and the subdomain stays reserved — nobody can use it again. Requires `confirm` to repeat the site's own subdomain exactly; ask the person for it rather than filling it in from what you already know, because that is the step this asks for. To take a site off the air and keep it, use pause_site instead; resume_site brings it back.",
2838
2872
  inputSchema: {
2839
2873
  site: z.string().min(1).describe("Subdomain or site id to delete."),
2840
2874
  confirm: z.string().min(1).describe(
@@ -2899,7 +2933,7 @@ function registerSiteTools(server, context) {
2899
2933
  "list_sites",
2900
2934
  {
2901
2935
  ...titled("List sites", READS_ONLY),
2902
- description: "Lists the sites on this account, so a publish can go to one of them.",
2936
+ description: "Lists the sites on this account, newest first, so a publish can go to one of them. Returns up to 100 with the account's total; there are no pages to fetch beyond that. Use get_site for one site's settings and versions.",
2903
2937
  inputSchema: {},
2904
2938
  outputSchema: {
2905
2939
  sites: z.array(SITE_OUTPUT).describe("The account's sites, newest first — every one unless `total` is larger."),
@@ -3330,7 +3364,7 @@ function createServer() {
3330
3364
  "login",
3331
3365
  {
3332
3366
  ...titled("Sign in to Drop2Run", CREDENTIAL_WRITE),
3333
- description: "Signs in through a browser on this machine and stores an access token, which every other tool then uses. Opens the browser here and waits for the person to approve. If it answers that nothing has been approved yet, call it again to keep waiting — that is not a failure. Needs no command line and no token pasted by hand.",
3367
+ description: "Signs in through a browser on this machine and stores an access token, which every other tool then uses. Opens the browser here and waits for the person to approve. If it answers that nothing has been approved yet, call it again to keep waiting — that is not a failure. Needs no command line and no token pasted by hand. Where no browser can open on this machine — a container, a remote host — use login_code instead.",
3334
3368
  inputSchema: {
3335
3369
  waitSeconds: z.number().int().min(5).max(MAX_WAIT_SECONDS).optional().describe("How long this call waits for the approval before answering. Default 120."),
3336
3370
  replace: z.boolean().optional().describe(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@drop2run/mcp",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "MCP server that publishes what an AI made — a page, a document or a whole site — to Drop2Run and returns a live URL.",
5
5
  "mcpName": "run.dropto/drop2run",
6
6
  "license": "MIT",