@solarisdk/mcp 0.3.2 → 0.3.4

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/README.md CHANGED
@@ -178,7 +178,7 @@ cookie count and origins it stored, so they can see what was persisted.
178
178
  |---|---|
179
179
  | `solari_sandbox_create` | Create a headless sandbox → `sessionId` |
180
180
  | `solari_desktop_create` | Create a GUI desktop → `sessionId` + `streamUrl` |
181
- | `solari_list` | List the org's sandboxes |
181
+ | `solari_list` | List the org's VMs — **both** sandboxes and desktops, each labelled with its `kind` (optional `kind`/`state` filters) |
182
182
  | `solari_kill` | Destroy a session |
183
183
  | `solari_connect` | Re-attach to a session by id across restarts (auto-resumes if paused) |
184
184
  | `solari_exec` | Run a shell command (via `sh -c`) → `{stdout,stderr,exitCode}` |
package/dist/browser.js CHANGED
@@ -363,10 +363,15 @@ export function makeBrowserToolset(cfg, reg, deps = defaultDeps) {
363
363
  catch {
364
364
  /* already gone — that is the point */
365
365
  }
366
+ // Prefer the short link when the gateway offers one — it is far easier
367
+ // to relay to someone on a phone. Fall back to the long URL for older
368
+ // gateways that do not return shortUrl yet.
369
+ const showUrl = h.shortUrl ?? h.url;
366
370
  return text({
367
371
  mode: "hot",
368
372
  handoffId: h.handoffId,
369
- url: h.url,
373
+ url: showUrl,
374
+ fullUrl: h.url,
370
375
  expiresAt: h.expiresAt,
371
376
  next: "Show the url to the user, then call solari_browser_await_login.",
372
377
  });
package/dist/server.js CHANGED
@@ -12,11 +12,14 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
12
12
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
13
13
  import { z } from "zod";
14
14
  import { SolariClient } from "@solarisdk/sdk";
15
+ import { VERSION } from "./version.js";
15
16
  import { makeBrowserToolset, releaseAllBrowserSessions, } from "./browser.js";
16
17
  // Guest output (file reads, command stdout, code results) is unbounded on the
17
18
  // wire; cap it so one `cat` of a big file can't blow the MCP payload budget or
18
19
  // OOM the shared hosted task.
19
20
  const MAX_TOOL_TEXT = 30_000;
21
+ // Pages of `GET /sandboxes` solari_list will follow per kind (100 rows each).
22
+ const MAX_LIST_PAGES = 5;
20
23
  const text = (o) => {
21
24
  const s = typeof o === "string" ? o : JSON.stringify(o, null, 2);
22
25
  const capped = s.length > MAX_TOOL_TEXT
@@ -81,9 +84,59 @@ export function makeToolset(client, reg) {
81
84
  },
82
85
  },
83
86
  solari_list: {
84
- description: "List the org's sandboxes.",
85
- inputSchema: {},
86
- handler: async () => text(await client.sandboxes.list()),
87
+ description: "List the org's live VMs — BOTH sandboxes and desktops. Each entry is labelled " +
88
+ "with its `kind`; `registered:true` means this MCP session already holds a handle " +
89
+ "so the other tools accept its sessionId directly (otherwise call solari_connect " +
90
+ "first). Optional kind/state filters.",
91
+ inputSchema: {
92
+ kind: z.enum(["sandbox", "desktop"]).optional(),
93
+ state: z.string().optional(),
94
+ },
95
+ handler: async (a) => {
96
+ // GET /sandboxes is the unified VM index (desktops write the same
97
+ // SandboxRecord), but it is queried per-kind here so the two flavours
98
+ // are merged EXPLICITLY: an agent asking "what do I have running"
99
+ // must never silently lose desktops to a default filter, and the
100
+ // per-kind counts are what make the answer legible.
101
+ const kinds = a.kind ? [a.kind] : ["sandbox", "desktop"];
102
+ const vms = [];
103
+ const counts = { sandbox: 0, desktop: 0 };
104
+ let truncated = false;
105
+ for (const kind of kinds) {
106
+ // Follow nextCursor so a big org's desktops aren't cut off by the
107
+ // gateway's 100-row default page — bounded so one call can't spin.
108
+ let cursor;
109
+ for (let page = 0; page < MAX_LIST_PAGES; page++) {
110
+ const res = await client.sandboxes.list({
111
+ kind,
112
+ // `state` is a closed union on the SDK; an unknown value is
113
+ // simply ignored by the gateway, so pass it through.
114
+ ...(a.state ? { state: a.state } : {}),
115
+ ...(cursor ? { cursor } : {}),
116
+ });
117
+ for (const v of res.sandboxes ?? []) {
118
+ const rec = v;
119
+ // The wire calls the id `sandboxId` for both kinds; re-label it
120
+ // `sessionId` because that is what every other tool here takes.
121
+ const id = (rec.sandboxId ?? rec.sessionId);
122
+ const k = rec.kind ?? kind;
123
+ vms.push({
124
+ ...rec,
125
+ ...(id ? { sessionId: id } : {}),
126
+ kind: k,
127
+ registered: id ? reg.sessions.has(id) : false,
128
+ });
129
+ counts[k] = (counts[k] ?? 0) + 1;
130
+ }
131
+ cursor = res.nextCursor;
132
+ if (!cursor)
133
+ break;
134
+ if (page === MAX_LIST_PAGES - 1)
135
+ truncated = true;
136
+ }
137
+ }
138
+ return text({ counts, total: vms.length, ...(truncated ? { truncated } : {}), vms });
139
+ },
87
140
  },
88
141
  solari_kill: {
89
142
  description: "Destroy a session by id.",
@@ -284,7 +337,7 @@ export function buildServerParts(client, browserCfg) {
284
337
  process.env.SOLARI_BASE_URL ??
285
338
  "https://api.getsolari.com",
286
339
  };
287
- const server = new McpServer({ name: "solari-mcp", version: "0.3.0" });
340
+ const server = new McpServer({ name: "solari-mcp", version: VERSION });
288
341
  const browserReg = { sessions: new Map() };
289
342
  const vmReg = { sessions: new Map() };
290
343
  registerToolset(server, {
@@ -112357,6 +112357,9 @@ var SolariClient = class {
112357
112357
  }
112358
112358
  };
112359
112359
 
112360
+ // src/version.ts
112361
+ var VERSION = "0.3.4";
112362
+
112360
112363
  // node_modules/puppeteer-core/lib/esm/puppeteer/index.js
112361
112364
  init_index_browser();
112362
112365
 
@@ -113919,10 +113922,12 @@ function makeBrowserToolset(cfg, reg, deps = defaultDeps) {
113919
113922
  await e.browser.close();
113920
113923
  } catch {
113921
113924
  }
113925
+ const showUrl = h.shortUrl ?? h.url;
113922
113926
  return text({
113923
113927
  mode: "hot",
113924
113928
  handoffId: h.handoffId,
113925
- url: h.url,
113929
+ url: showUrl,
113930
+ fullUrl: h.url,
113926
113931
  expiresAt: h.expiresAt,
113927
113932
  next: "Show the url to the user, then call solari_browser_await_login."
113928
113933
  });
@@ -114238,6 +114243,7 @@ async function releaseAllBrowserSessions(cfg, reg, fetchApi = api) {
114238
114243
  // src/server.ts
114239
114244
  var import_meta2 = {};
114240
114245
  var MAX_TOOL_TEXT = 3e4;
114246
+ var MAX_LIST_PAGES = 5;
114241
114247
  var text2 = (o) => {
114242
114248
  const s = typeof o === "string" ? o : JSON.stringify(o, null, 2);
114243
114249
  const capped = s.length > MAX_TOOL_TEXT ? `${s.slice(0, MAX_TOOL_TEXT)}
@@ -114294,9 +114300,45 @@ function makeToolset(client, reg) {
114294
114300
  }
114295
114301
  },
114296
114302
  solari_list: {
114297
- description: "List the org's sandboxes.",
114298
- inputSchema: {},
114299
- handler: async () => text2(await client.sandboxes.list())
114303
+ description: "List the org's live VMs \u2014 BOTH sandboxes and desktops. Each entry is labelled with its `kind`; `registered:true` means this MCP session already holds a handle so the other tools accept its sessionId directly (otherwise call solari_connect first). Optional kind/state filters.",
114304
+ inputSchema: {
114305
+ kind: external_exports.enum(["sandbox", "desktop"]).optional(),
114306
+ state: external_exports.string().optional()
114307
+ },
114308
+ handler: async (a2) => {
114309
+ const kinds = a2.kind ? [a2.kind] : ["sandbox", "desktop"];
114310
+ const vms = [];
114311
+ const counts = { sandbox: 0, desktop: 0 };
114312
+ let truncated = false;
114313
+ for (const kind of kinds) {
114314
+ let cursor;
114315
+ for (let page = 0; page < MAX_LIST_PAGES; page++) {
114316
+ const res = await client.sandboxes.list({
114317
+ kind,
114318
+ // `state` is a closed union on the SDK; an unknown value is
114319
+ // simply ignored by the gateway, so pass it through.
114320
+ ...a2.state ? { state: a2.state } : {},
114321
+ ...cursor ? { cursor } : {}
114322
+ });
114323
+ for (const v2 of res.sandboxes ?? []) {
114324
+ const rec = v2;
114325
+ const id = rec.sandboxId ?? rec.sessionId;
114326
+ const k = rec.kind ?? kind;
114327
+ vms.push({
114328
+ ...rec,
114329
+ ...id ? { sessionId: id } : {},
114330
+ kind: k,
114331
+ registered: id ? reg.sessions.has(id) : false
114332
+ });
114333
+ counts[k] = (counts[k] ?? 0) + 1;
114334
+ }
114335
+ cursor = res.nextCursor;
114336
+ if (!cursor) break;
114337
+ if (page === MAX_LIST_PAGES - 1) truncated = true;
114338
+ }
114339
+ }
114340
+ return text2({ counts, total: vms.length, ...truncated ? { truncated } : {}, vms });
114341
+ }
114300
114342
  },
114301
114343
  solari_kill: {
114302
114344
  description: "Destroy a session by id.",
@@ -114470,7 +114512,7 @@ function buildServerParts(client, browserCfg) {
114470
114512
  apiKey: process.env.SOLARI_BROWSER_API_KEY ?? apiKey,
114471
114513
  baseUrl: process.env.SOLARI_BROWSER_URL ?? process.env.SOLARI_BASE_URL ?? "https://api.getsolari.com"
114472
114514
  };
114473
- const server = new McpServer({ name: "solari-mcp", version: "0.3.0" });
114515
+ const server = new McpServer({ name: "solari-mcp", version: VERSION });
114474
114516
  const browserReg = { sessions: /* @__PURE__ */ new Map() };
114475
114517
  const vmReg = { sessions: /* @__PURE__ */ new Map() };
114476
114518
  registerToolset(server, {
@@ -111026,6 +111026,9 @@ var SolariClient = class {
111026
111026
  }
111027
111027
  };
111028
111028
 
111029
+ // src/version.ts
111030
+ var VERSION = "0.3.4";
111031
+
111029
111032
  // node_modules/puppeteer-core/lib/esm/puppeteer/index.js
111030
111033
  init_index_browser();
111031
111034
 
@@ -112588,10 +112591,12 @@ function makeBrowserToolset(cfg, reg, deps = defaultDeps) {
112588
112591
  await e.browser.close();
112589
112592
  } catch {
112590
112593
  }
112594
+ const showUrl = h.shortUrl ?? h.url;
112591
112595
  return text({
112592
112596
  mode: "hot",
112593
112597
  handoffId: h.handoffId,
112594
- url: h.url,
112598
+ url: showUrl,
112599
+ fullUrl: h.url,
112595
112600
  expiresAt: h.expiresAt,
112596
112601
  next: "Show the url to the user, then call solari_browser_await_login."
112597
112602
  });
@@ -112907,6 +112912,7 @@ async function releaseAllBrowserSessions(cfg, reg, fetchApi = api) {
112907
112912
  // src/server.ts
112908
112913
  var import_meta2 = {};
112909
112914
  var MAX_TOOL_TEXT = 3e4;
112915
+ var MAX_LIST_PAGES = 5;
112910
112916
  var text2 = (o) => {
112911
112917
  const s = typeof o === "string" ? o : JSON.stringify(o, null, 2);
112912
112918
  const capped = s.length > MAX_TOOL_TEXT ? `${s.slice(0, MAX_TOOL_TEXT)}
@@ -112963,9 +112969,45 @@ function makeToolset(client, reg) {
112963
112969
  }
112964
112970
  },
112965
112971
  solari_list: {
112966
- description: "List the org's sandboxes.",
112967
- inputSchema: {},
112968
- handler: async () => text2(await client.sandboxes.list())
112972
+ description: "List the org's live VMs \u2014 BOTH sandboxes and desktops. Each entry is labelled with its `kind`; `registered:true` means this MCP session already holds a handle so the other tools accept its sessionId directly (otherwise call solari_connect first). Optional kind/state filters.",
112973
+ inputSchema: {
112974
+ kind: external_exports.enum(["sandbox", "desktop"]).optional(),
112975
+ state: external_exports.string().optional()
112976
+ },
112977
+ handler: async (a2) => {
112978
+ const kinds = a2.kind ? [a2.kind] : ["sandbox", "desktop"];
112979
+ const vms = [];
112980
+ const counts = { sandbox: 0, desktop: 0 };
112981
+ let truncated = false;
112982
+ for (const kind of kinds) {
112983
+ let cursor;
112984
+ for (let page = 0; page < MAX_LIST_PAGES; page++) {
112985
+ const res = await client.sandboxes.list({
112986
+ kind,
112987
+ // `state` is a closed union on the SDK; an unknown value is
112988
+ // simply ignored by the gateway, so pass it through.
112989
+ ...a2.state ? { state: a2.state } : {},
112990
+ ...cursor ? { cursor } : {}
112991
+ });
112992
+ for (const v2 of res.sandboxes ?? []) {
112993
+ const rec = v2;
112994
+ const id = rec.sandboxId ?? rec.sessionId;
112995
+ const k = rec.kind ?? kind;
112996
+ vms.push({
112997
+ ...rec,
112998
+ ...id ? { sessionId: id } : {},
112999
+ kind: k,
113000
+ registered: id ? reg.sessions.has(id) : false
113001
+ });
113002
+ counts[k] = (counts[k] ?? 0) + 1;
113003
+ }
113004
+ cursor = res.nextCursor;
113005
+ if (!cursor) break;
113006
+ if (page === MAX_LIST_PAGES - 1) truncated = true;
113007
+ }
113008
+ }
113009
+ return text2({ counts, total: vms.length, ...truncated ? { truncated } : {}, vms });
113010
+ }
112969
113011
  },
112970
113012
  solari_kill: {
112971
113013
  description: "Destroy a session by id.",
@@ -113139,7 +113181,7 @@ function buildServerParts(client, browserCfg) {
113139
113181
  apiKey: process.env.SOLARI_BROWSER_API_KEY ?? apiKey,
113140
113182
  baseUrl: process.env.SOLARI_BROWSER_URL ?? process.env.SOLARI_BASE_URL ?? "https://api.getsolari.com"
113141
113183
  };
113142
- const server = new McpServer({ name: "solari-mcp", version: "0.3.0" });
113184
+ const server = new McpServer({ name: "solari-mcp", version: VERSION });
113143
113185
  const browserReg = { sessions: /* @__PURE__ */ new Map() };
113144
113186
  const vmReg = { sessions: /* @__PURE__ */ new Map() };
113145
113187
  registerToolset(server, {
@@ -0,0 +1 @@
1
+ export declare const VERSION = "0.3.4";
@@ -0,0 +1,11 @@
1
+ // The single source of truth for the version this server reports to MCP
2
+ // clients (`initialize` → serverInfo.version).
3
+ //
4
+ // It CANNOT be imported straight from package.json: tsconfig pins
5
+ // `rootDir: "src"`, so `import "../package.json"` is outside the root and tsc
6
+ // refuses it; widening rootDir would emit `dist/src/*.js` and break the `bin`
7
+ // path + the esbuild bundles. So it is a literal here — and
8
+ // `test/version.test.mjs` FAILS the build if it ever drifts from package.json.
9
+ //
10
+ // KEEP IN SYNC WITH sdk/mcp/package.json "version".
11
+ export const VERSION = "0.3.4";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solarisdk/mcp",
3
- "version": "0.3.2",
3
+ "version": "0.3.4",
4
4
  "description": "Model Context Protocol server for the Solari cloud browser, sandboxes + desktops \u2014 drive them from Claude Desktop/Cowork, Claude Code, Cursor, etc.",
5
5
  "type": "module",
6
6
  "bin": {