@solarisdk/mcp 0.3.2 → 0.3.3

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/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.3";
112362
+
112360
112363
  // node_modules/puppeteer-core/lib/esm/puppeteer/index.js
112361
112364
  init_index_browser();
112362
112365
 
@@ -114238,6 +114241,7 @@ async function releaseAllBrowserSessions(cfg, reg, fetchApi = api) {
114238
114241
  // src/server.ts
114239
114242
  var import_meta2 = {};
114240
114243
  var MAX_TOOL_TEXT = 3e4;
114244
+ var MAX_LIST_PAGES = 5;
114241
114245
  var text2 = (o) => {
114242
114246
  const s = typeof o === "string" ? o : JSON.stringify(o, null, 2);
114243
114247
  const capped = s.length > MAX_TOOL_TEXT ? `${s.slice(0, MAX_TOOL_TEXT)}
@@ -114294,9 +114298,45 @@ function makeToolset(client, reg) {
114294
114298
  }
114295
114299
  },
114296
114300
  solari_list: {
114297
- description: "List the org's sandboxes.",
114298
- inputSchema: {},
114299
- handler: async () => text2(await client.sandboxes.list())
114301
+ 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.",
114302
+ inputSchema: {
114303
+ kind: external_exports.enum(["sandbox", "desktop"]).optional(),
114304
+ state: external_exports.string().optional()
114305
+ },
114306
+ handler: async (a2) => {
114307
+ const kinds = a2.kind ? [a2.kind] : ["sandbox", "desktop"];
114308
+ const vms = [];
114309
+ const counts = { sandbox: 0, desktop: 0 };
114310
+ let truncated = false;
114311
+ for (const kind of kinds) {
114312
+ let cursor;
114313
+ for (let page = 0; page < MAX_LIST_PAGES; page++) {
114314
+ const res = await client.sandboxes.list({
114315
+ kind,
114316
+ // `state` is a closed union on the SDK; an unknown value is
114317
+ // simply ignored by the gateway, so pass it through.
114318
+ ...a2.state ? { state: a2.state } : {},
114319
+ ...cursor ? { cursor } : {}
114320
+ });
114321
+ for (const v2 of res.sandboxes ?? []) {
114322
+ const rec = v2;
114323
+ const id = rec.sandboxId ?? rec.sessionId;
114324
+ const k = rec.kind ?? kind;
114325
+ vms.push({
114326
+ ...rec,
114327
+ ...id ? { sessionId: id } : {},
114328
+ kind: k,
114329
+ registered: id ? reg.sessions.has(id) : false
114330
+ });
114331
+ counts[k] = (counts[k] ?? 0) + 1;
114332
+ }
114333
+ cursor = res.nextCursor;
114334
+ if (!cursor) break;
114335
+ if (page === MAX_LIST_PAGES - 1) truncated = true;
114336
+ }
114337
+ }
114338
+ return text2({ counts, total: vms.length, ...truncated ? { truncated } : {}, vms });
114339
+ }
114300
114340
  },
114301
114341
  solari_kill: {
114302
114342
  description: "Destroy a session by id.",
@@ -114470,7 +114510,7 @@ function buildServerParts(client, browserCfg) {
114470
114510
  apiKey: process.env.SOLARI_BROWSER_API_KEY ?? apiKey,
114471
114511
  baseUrl: process.env.SOLARI_BROWSER_URL ?? process.env.SOLARI_BASE_URL ?? "https://api.getsolari.com"
114472
114512
  };
114473
- const server = new McpServer({ name: "solari-mcp", version: "0.3.0" });
114513
+ const server = new McpServer({ name: "solari-mcp", version: VERSION });
114474
114514
  const browserReg = { sessions: /* @__PURE__ */ new Map() };
114475
114515
  const vmReg = { sessions: /* @__PURE__ */ new Map() };
114476
114516
  registerToolset(server, {
@@ -111026,6 +111026,9 @@ var SolariClient = class {
111026
111026
  }
111027
111027
  };
111028
111028
 
111029
+ // src/version.ts
111030
+ var VERSION = "0.3.3";
111031
+
111029
111032
  // node_modules/puppeteer-core/lib/esm/puppeteer/index.js
111030
111033
  init_index_browser();
111031
111034
 
@@ -112907,6 +112910,7 @@ async function releaseAllBrowserSessions(cfg, reg, fetchApi = api) {
112907
112910
  // src/server.ts
112908
112911
  var import_meta2 = {};
112909
112912
  var MAX_TOOL_TEXT = 3e4;
112913
+ var MAX_LIST_PAGES = 5;
112910
112914
  var text2 = (o) => {
112911
112915
  const s = typeof o === "string" ? o : JSON.stringify(o, null, 2);
112912
112916
  const capped = s.length > MAX_TOOL_TEXT ? `${s.slice(0, MAX_TOOL_TEXT)}
@@ -112963,9 +112967,45 @@ function makeToolset(client, reg) {
112963
112967
  }
112964
112968
  },
112965
112969
  solari_list: {
112966
- description: "List the org's sandboxes.",
112967
- inputSchema: {},
112968
- handler: async () => text2(await client.sandboxes.list())
112970
+ 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.",
112971
+ inputSchema: {
112972
+ kind: external_exports.enum(["sandbox", "desktop"]).optional(),
112973
+ state: external_exports.string().optional()
112974
+ },
112975
+ handler: async (a2) => {
112976
+ const kinds = a2.kind ? [a2.kind] : ["sandbox", "desktop"];
112977
+ const vms = [];
112978
+ const counts = { sandbox: 0, desktop: 0 };
112979
+ let truncated = false;
112980
+ for (const kind of kinds) {
112981
+ let cursor;
112982
+ for (let page = 0; page < MAX_LIST_PAGES; page++) {
112983
+ const res = await client.sandboxes.list({
112984
+ kind,
112985
+ // `state` is a closed union on the SDK; an unknown value is
112986
+ // simply ignored by the gateway, so pass it through.
112987
+ ...a2.state ? { state: a2.state } : {},
112988
+ ...cursor ? { cursor } : {}
112989
+ });
112990
+ for (const v2 of res.sandboxes ?? []) {
112991
+ const rec = v2;
112992
+ const id = rec.sandboxId ?? rec.sessionId;
112993
+ const k = rec.kind ?? kind;
112994
+ vms.push({
112995
+ ...rec,
112996
+ ...id ? { sessionId: id } : {},
112997
+ kind: k,
112998
+ registered: id ? reg.sessions.has(id) : false
112999
+ });
113000
+ counts[k] = (counts[k] ?? 0) + 1;
113001
+ }
113002
+ cursor = res.nextCursor;
113003
+ if (!cursor) break;
113004
+ if (page === MAX_LIST_PAGES - 1) truncated = true;
113005
+ }
113006
+ }
113007
+ return text2({ counts, total: vms.length, ...truncated ? { truncated } : {}, vms });
113008
+ }
112969
113009
  },
112970
113010
  solari_kill: {
112971
113011
  description: "Destroy a session by id.",
@@ -113139,7 +113179,7 @@ function buildServerParts(client, browserCfg) {
113139
113179
  apiKey: process.env.SOLARI_BROWSER_API_KEY ?? apiKey,
113140
113180
  baseUrl: process.env.SOLARI_BROWSER_URL ?? process.env.SOLARI_BASE_URL ?? "https://api.getsolari.com"
113141
113181
  };
113142
- const server = new McpServer({ name: "solari-mcp", version: "0.3.0" });
113182
+ const server = new McpServer({ name: "solari-mcp", version: VERSION });
113143
113183
  const browserReg = { sessions: /* @__PURE__ */ new Map() };
113144
113184
  const vmReg = { sessions: /* @__PURE__ */ new Map() };
113145
113185
  registerToolset(server, {
@@ -0,0 +1 @@
1
+ export declare const VERSION = "0.3.3";
@@ -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.3";
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@solarisdk/mcp",
3
- "version": "0.3.2",
4
- "description": "Model Context Protocol server for the Solari cloud browser, sandboxes + desktops \u2014 drive them from Claude Desktop/Cowork, Claude Code, Cursor, etc.",
3
+ "version": "0.3.3",
4
+ "description": "Model Context Protocol server for the Solari cloud browser, sandboxes + desktops drive them from Claude Desktop/Cowork, Claude Code, Cursor, etc.",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "solari-mcp": "./dist/cli.js"