@cloudgrid-io/mcp 0.19.1 → 0.19.2

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/package.json +3 -2
  2. package/src/tools.js +42 -15
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cloudgrid-io/mcp",
3
- "version": "0.19.1",
3
+ "version": "0.19.2",
4
4
  "description": "MCP server for CloudGrid. Three editions: a local stdio server (full toolset), a hosted web server (light, CLI-free toolset), and a public docs server (search_cloudgrid_documentation) over MCP Streamable HTTP.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -40,7 +40,8 @@
40
40
  "start:docs": "node src/docs.js",
41
41
  "snapshot:corpus": "node scripts/snapshot-corpus.mjs",
42
42
  "build:mcpb": "node scripts/build-mcpb.mjs",
43
- "test:widgets": "node test/apps-widgets.test.mjs"
43
+ "test:widgets": "node test/apps-widgets.test.mjs",
44
+ "test:fork": "node test/fork.test.mjs"
44
45
  },
45
46
  "dependencies": {
46
47
  "@modelcontextprotocol/sdk": "^1.0.0",
package/src/tools.js CHANGED
@@ -1931,24 +1931,50 @@ async function authedApiCall(ctx, { method, pathName, body, verb }) {
1931
1931
  }
1932
1932
  if (!res.ok) {
1933
1933
  const msg = data?.error?.message || data?.message || raw || `HTTP ${res.status}`;
1934
- const code = data?.error?.code ? ` ${data.error.code}` : "";
1935
- throw new Error(`${verb} failed (HTTP ${res.status}${code}): ${msg}`);
1934
+ const codeStr = data?.error?.code || null;
1935
+ const err = new Error(`${verb} failed (HTTP ${res.status}${codeStr ? ` ${codeStr}` : ""}): ${msg}`);
1936
+ // Expose the structured status/code so callers can branch (e.g. runFork's
1937
+ // NOT_A_RUNTIME → inspiration-route fallback). Additive: existing callers
1938
+ // only read `.message`.
1939
+ err.status = res.status;
1940
+ err.code = codeStr;
1941
+ throw err;
1936
1942
  }
1937
1943
  return data;
1938
1944
  }
1939
1945
 
1940
- // Fork: start a NEW entity from an existing runtime, copy-on-write with lineage.
1941
- async function runFork(ctx, { id, into_org_slug, name, source_version_id }) {
1942
- const data = await authedApiCall(ctx, {
1943
- method: "POST",
1944
- pathName: `/api/v2/runtimes/${encodeURIComponent(id)}/fork`,
1945
- body: {
1946
- ...(into_org_slug ? { into_org_slug } : {}),
1947
- ...(name ? { name } : {}),
1948
- ...(source_version_id ? { source_version_id } : {}),
1949
- },
1950
- verb: "Fork",
1951
- });
1946
+ // Fork: start a NEW entity from an existing source, copy-on-write with lineage.
1947
+ // Kind-aware (one fork verb, mirrors the CLI `fork` command): the caller may
1948
+ // not know the source's kind, so try the runtime route first and, when the
1949
+ // server reports the target is an inspiration (`400 NOT_A_RUNTIME`, emitted by
1950
+ // requireEntityAccess before the runtime handler runs), retry the inspiration
1951
+ // route. The inspiration remix lands in the caller's active grid, so no
1952
+ // `into_org_slug`; `source_version_id` has no meaning for inspirations.
1953
+ export async function runFork(ctx, { id, into_org_slug, name, source_version_id }) {
1954
+ let data;
1955
+ try {
1956
+ data = await authedApiCall(ctx, {
1957
+ method: "POST",
1958
+ pathName: `/api/v2/runtimes/${encodeURIComponent(id)}/fork`,
1959
+ body: {
1960
+ ...(into_org_slug ? { into_org_slug } : {}),
1961
+ ...(name ? { name } : {}),
1962
+ ...(source_version_id ? { source_version_id } : {}),
1963
+ },
1964
+ verb: "Fork",
1965
+ });
1966
+ } catch (err) {
1967
+ if (err && err.code === "NOT_A_RUNTIME") {
1968
+ data = await authedApiCall(ctx, {
1969
+ method: "POST",
1970
+ pathName: `/api/v2/inspirations/${encodeURIComponent(id)}/fork`,
1971
+ body: { ...(name ? { name } : {}) },
1972
+ verb: "Fork",
1973
+ });
1974
+ } else {
1975
+ throw err;
1976
+ }
1977
+ }
1952
1978
  const gridSlug = data?.grid_slug ?? data?.org?.slug ?? null;
1953
1979
  const lines = [
1954
1980
  `Forked: ${data?.name ?? id} (entity_id=${data?.entity_id ?? "?"})${gridSlug ? ` in grid ${gridSlug}` : ""}`,
@@ -2511,7 +2537,8 @@ export function registerTools(server, ctx) {
2511
2537
  description:
2512
2538
  "Start a NEW entity from an existing runtime (copy-on-write, lineage recorded). Lands in the " +
2513
2539
  "source's home grid by default; cross-grid only for system templates or forkable:'public' sources. " +
2514
- "Requires sign-in. Calls POST /api/v2/runtimes/:id/fork directly.",
2540
+ "Requires sign-in. Kind-aware: forks a runtime (app/agent) OR an inspiration — " +
2541
+ "tries POST /api/v2/runtimes/:id/fork and falls back to /api/v2/inspirations/:id/fork.",
2515
2542
  inputSchema: {
2516
2543
  id: z.string().describe("The source runtime: a canonical UUID or <grid-slug>/<entity-slug>."),
2517
2544
  into_org_slug: z.string().optional().describe("Destination grid slug. Required only when you belong to more than one grid."),