claudemesh-cli 0.7.1 → 0.8.1

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 +703 -20
  2. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -47663,6 +47663,75 @@ var TOOLS = [
47663
47663
  },
47664
47664
  required: ["name"]
47665
47665
  }
47666
+ },
47667
+ {
47668
+ name: "mesh_mcp_deploy",
47669
+ description: "Deploy an MCP server to the mesh from a zip file or git repo. Runs on the broker VPS, persists across peer sessions. Default scope: private (only you).",
47670
+ inputSchema: {
47671
+ type: "object",
47672
+ properties: {
47673
+ server_name: { type: "string", description: "Unique name for the server in this mesh" },
47674
+ file_id: { type: "string", description: "File ID of uploaded zip (from share_file)" },
47675
+ git_url: { type: "string", description: "Git repo URL" },
47676
+ git_branch: { type: "string", description: "Branch to clone (default: main)" },
47677
+ env: { type: "object", description: "Environment variables. Use $vault:<key> for vault secrets." },
47678
+ runtime: { type: "string", enum: ["node", "python", "bun"], description: "Runtime (auto-detected if omitted)" },
47679
+ memory_mb: { type: "number", description: "Memory limit in MB (default: 256)" },
47680
+ network_allow: { type: "array", items: { type: "string" }, description: "Allowed outbound hosts (default: none)" },
47681
+ scope: { description: "Visibility: 'peer' (default), 'mesh', or {group/groups/role/peers}" }
47682
+ },
47683
+ required: ["server_name"]
47684
+ }
47685
+ },
47686
+ {
47687
+ name: "mesh_mcp_undeploy",
47688
+ description: "Stop and remove a managed MCP server from the mesh.",
47689
+ inputSchema: { type: "object", properties: { server_name: { type: "string" } }, required: ["server_name"] }
47690
+ },
47691
+ {
47692
+ name: "mesh_mcp_update",
47693
+ description: "Pull latest code and restart a git-sourced MCP server.",
47694
+ inputSchema: { type: "object", properties: { server_name: { type: "string" } }, required: ["server_name"] }
47695
+ },
47696
+ {
47697
+ name: "mesh_mcp_logs",
47698
+ description: "View recent logs from a managed MCP server.",
47699
+ inputSchema: { type: "object", properties: { server_name: { type: "string" }, lines: { type: "number", description: "Lines (default: 50, max: 1000)" } }, required: ["server_name"] }
47700
+ },
47701
+ {
47702
+ name: "mesh_mcp_scope",
47703
+ description: "Get or set the visibility scope of a deployed MCP server.",
47704
+ inputSchema: { type: "object", properties: { server_name: { type: "string" }, scope: { description: "New scope to set. Omit to read current." } }, required: ["server_name"] }
47705
+ },
47706
+ {
47707
+ name: "mesh_mcp_schema",
47708
+ description: "Inspect tool schemas for a deployed MCP server.",
47709
+ inputSchema: { type: "object", properties: { server_name: { type: "string" }, tool_name: { type: "string", description: "Specific tool (omit for all)" } }, required: ["server_name"] }
47710
+ },
47711
+ {
47712
+ name: "mesh_mcp_catalog",
47713
+ description: "List all deployed services in the mesh with status, scope, and tool count.",
47714
+ inputSchema: { type: "object", properties: {} }
47715
+ },
47716
+ {
47717
+ name: "mesh_skill_deploy",
47718
+ description: "Deploy a multi-file skill bundle from a zip or git repo.",
47719
+ inputSchema: { type: "object", properties: { file_id: { type: "string" }, git_url: { type: "string" }, git_branch: { type: "string" } } }
47720
+ },
47721
+ {
47722
+ name: "vault_set",
47723
+ description: "Store an encrypted credential in your vault. Reference in mesh_mcp_deploy with $vault:<key>.",
47724
+ inputSchema: { type: "object", properties: { key: { type: "string" }, value: { type: "string", description: "Secret value or local file path (for type=file)" }, type: { type: "string", enum: ["env", "file"] }, mount_path: { type: "string" }, description: { type: "string" } }, required: ["key", "value"] }
47725
+ },
47726
+ {
47727
+ name: "vault_list",
47728
+ description: "List your vault entries (keys and metadata only, no secret values).",
47729
+ inputSchema: { type: "object", properties: {} }
47730
+ },
47731
+ {
47732
+ name: "vault_delete",
47733
+ description: "Remove a credential from your vault.",
47734
+ inputSchema: { type: "object", properties: { key: { type: "string" } }, required: ["key"] }
47666
47735
  }
47667
47736
  ];
47668
47737
 
@@ -47751,6 +47820,10 @@ class BrokerClient {
47751
47820
  peerFileResponseResolvers = new Map;
47752
47821
  peerDirResponseResolvers = new Map;
47753
47822
  sharedDirs = [process.cwd()];
47823
+ _serviceCatalog = [];
47824
+ get serviceCatalog() {
47825
+ return this._serviceCatalog;
47826
+ }
47754
47827
  closed = false;
47755
47828
  reconnectAttempt = 0;
47756
47829
  helloTimer = null;
@@ -47858,6 +47931,9 @@ class BrokerClient {
47858
47931
  this._statsCounters.errors = rs.errors ?? 0;
47859
47932
  }
47860
47933
  }
47934
+ if (msg.services) {
47935
+ this._serviceCatalog = msg.services;
47936
+ }
47861
47937
  resolve();
47862
47938
  return;
47863
47939
  }
@@ -48146,6 +48222,14 @@ class BrokerClient {
48146
48222
  mcpListResolvers = new Map;
48147
48223
  mcpCallResolvers = new Map;
48148
48224
  mcpCallForwardHandler = null;
48225
+ vaultAckResolvers = new Map;
48226
+ vaultListResolvers = new Map;
48227
+ mcpDeployResolvers = new Map;
48228
+ mcpLogsResolvers = new Map;
48229
+ mcpSchemaServiceResolvers = new Map;
48230
+ mcpCatalogResolvers = new Map;
48231
+ mcpScopeResolvers = new Map;
48232
+ skillDeployResolvers = new Map;
48149
48233
  async messageStatus(messageId) {
48150
48234
  if (!this.ws || this.ws.readyState !== this.ws.OPEN)
48151
48235
  return null;
@@ -48678,6 +48762,138 @@ class BrokerClient {
48678
48762
  this.ws.send(JSON.stringify({ type: "peer_dir_request", targetPubkey, dirPath, ...pattern ? { pattern } : {}, _reqId: reqId }));
48679
48763
  });
48680
48764
  }
48765
+ async vaultSet(key, ciphertext, nonce, sealedKey, entryType, mountPath, description) {
48766
+ return new Promise((resolve) => {
48767
+ const reqId = `vset_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
48768
+ const timer = setTimeout(() => {
48769
+ this.vaultAckResolvers.delete(reqId);
48770
+ resolve(false);
48771
+ }, 1e4);
48772
+ this.vaultAckResolvers.set(reqId, { resolve, timer });
48773
+ this.sendRaw({ type: "vault_set", key, ciphertext, nonce, sealed_key: sealedKey, entry_type: entryType, mount_path: mountPath, description, _reqId: reqId });
48774
+ });
48775
+ }
48776
+ async vaultList() {
48777
+ return new Promise((resolve) => {
48778
+ const reqId = `vlist_${Date.now()}`;
48779
+ const timer = setTimeout(() => {
48780
+ this.vaultListResolvers.delete(reqId);
48781
+ resolve([]);
48782
+ }, 1e4);
48783
+ this.vaultListResolvers.set(reqId, { resolve, timer });
48784
+ this.sendRaw({ type: "vault_list", _reqId: reqId });
48785
+ });
48786
+ }
48787
+ async vaultDelete(key) {
48788
+ return new Promise((resolve) => {
48789
+ const reqId = `vdel_${Date.now()}`;
48790
+ const timer = setTimeout(() => {
48791
+ this.vaultAckResolvers.delete(reqId);
48792
+ resolve(false);
48793
+ }, 1e4);
48794
+ this.vaultAckResolvers.set(reqId, { resolve, timer });
48795
+ this.sendRaw({ type: "vault_delete", key, _reqId: reqId });
48796
+ });
48797
+ }
48798
+ async mcpDeploy(serverName, source, config2, scope) {
48799
+ return new Promise((resolve) => {
48800
+ const reqId = `deploy_${Date.now()}`;
48801
+ const timer = setTimeout(() => {
48802
+ this.mcpDeployResolvers.delete(reqId);
48803
+ resolve({ status: "timeout" });
48804
+ }, 60000);
48805
+ this.mcpDeployResolvers.set(reqId, { resolve, timer });
48806
+ this.sendRaw({ type: "mcp_deploy", server_name: serverName, source, config: config2, scope, _reqId: reqId });
48807
+ });
48808
+ }
48809
+ async mcpUndeploy(serverName) {
48810
+ return new Promise((resolve) => {
48811
+ const reqId = `undeploy_${Date.now()}`;
48812
+ const timer = setTimeout(() => {
48813
+ this.mcpDeployResolvers.delete(reqId);
48814
+ resolve(false);
48815
+ }, 1e4);
48816
+ this.mcpDeployResolvers.set(reqId, { resolve: (r) => resolve(r.status === "stopped"), timer });
48817
+ this.sendRaw({ type: "mcp_undeploy", server_name: serverName, _reqId: reqId });
48818
+ });
48819
+ }
48820
+ async mcpUpdate(serverName) {
48821
+ return new Promise((resolve) => {
48822
+ const reqId = `update_${Date.now()}`;
48823
+ const timer = setTimeout(() => {
48824
+ this.mcpDeployResolvers.delete(reqId);
48825
+ resolve({ status: "timeout" });
48826
+ }, 60000);
48827
+ this.mcpDeployResolvers.set(reqId, { resolve, timer });
48828
+ this.sendRaw({ type: "mcp_update", server_name: serverName, _reqId: reqId });
48829
+ });
48830
+ }
48831
+ async mcpLogs(serverName, lines) {
48832
+ return new Promise((resolve) => {
48833
+ const reqId = `logs_${Date.now()}`;
48834
+ const timer = setTimeout(() => {
48835
+ this.mcpLogsResolvers.delete(reqId);
48836
+ resolve([]);
48837
+ }, 1e4);
48838
+ this.mcpLogsResolvers.set(reqId, { resolve, timer });
48839
+ this.sendRaw({ type: "mcp_logs", server_name: serverName, lines, _reqId: reqId });
48840
+ });
48841
+ }
48842
+ async mcpScope(serverName, scope) {
48843
+ return new Promise((resolve) => {
48844
+ const reqId = `scope_${Date.now()}`;
48845
+ const timer = setTimeout(() => {
48846
+ this.mcpScopeResolvers.delete(reqId);
48847
+ resolve({ scope: { type: "peer" }, deployed_by: "unknown" });
48848
+ }, 1e4);
48849
+ this.mcpScopeResolvers.set(reqId, { resolve, timer });
48850
+ this.sendRaw({ type: "mcp_scope", server_name: serverName, scope, _reqId: reqId });
48851
+ });
48852
+ }
48853
+ async mcpServiceSchema(serverName, toolName) {
48854
+ return new Promise((resolve) => {
48855
+ const reqId = `schema_${Date.now()}`;
48856
+ const timer = setTimeout(() => {
48857
+ this.mcpSchemaServiceResolvers.delete(reqId);
48858
+ resolve([]);
48859
+ }, 1e4);
48860
+ this.mcpSchemaServiceResolvers.set(reqId, { resolve, timer });
48861
+ this.sendRaw({ type: "mcp_schema", server_name: serverName, tool_name: toolName, _reqId: reqId });
48862
+ });
48863
+ }
48864
+ async mcpCatalog() {
48865
+ return new Promise((resolve) => {
48866
+ const reqId = `catalog_${Date.now()}`;
48867
+ const timer = setTimeout(() => {
48868
+ this.mcpCatalogResolvers.delete(reqId);
48869
+ resolve([]);
48870
+ }, 1e4);
48871
+ this.mcpCatalogResolvers.set(reqId, { resolve, timer });
48872
+ this.sendRaw({ type: "mcp_catalog", _reqId: reqId });
48873
+ });
48874
+ }
48875
+ async skillDeploy(source) {
48876
+ return new Promise((resolve) => {
48877
+ const reqId = `skilldeploy_${Date.now()}`;
48878
+ const timer = setTimeout(() => {
48879
+ this.skillDeployResolvers.delete(reqId);
48880
+ resolve({ name: "unknown", files: [] });
48881
+ }, 30000);
48882
+ this.skillDeployResolvers.set(reqId, { resolve, timer });
48883
+ this.sendRaw({ type: "skill_deploy", source, _reqId: reqId });
48884
+ });
48885
+ }
48886
+ async getServiceTools(serviceName) {
48887
+ const cached2 = this._serviceCatalog.find((s) => s.name === serviceName);
48888
+ if (cached2?.tools?.length)
48889
+ return cached2.tools;
48890
+ return this.mcpServiceSchema(serviceName);
48891
+ }
48892
+ sendRaw(payload) {
48893
+ if (!this.ws || this.ws.readyState !== this.ws.OPEN)
48894
+ return;
48895
+ this.ws.send(JSON.stringify(payload));
48896
+ }
48681
48897
  close() {
48682
48898
  this.closed = true;
48683
48899
  this.stopStatsReporting();
@@ -49179,6 +49395,78 @@ class BrokerClient {
49179
49395
  this.resolveFromMap(this.webhookListResolvers, msgReqId, webhooks);
49180
49396
  return;
49181
49397
  }
49398
+ if (msg.type === "vault_ack") {
49399
+ const reqId = msg._reqId;
49400
+ if (reqId && this.vaultAckResolvers.has(reqId)) {
49401
+ const r = this.vaultAckResolvers.get(reqId);
49402
+ clearTimeout(r.timer);
49403
+ this.vaultAckResolvers.delete(reqId);
49404
+ r.resolve(msg.action !== "not_found");
49405
+ }
49406
+ }
49407
+ if (msg.type === "vault_list_result") {
49408
+ const reqId = msg._reqId;
49409
+ if (reqId && this.vaultListResolvers.has(reqId)) {
49410
+ const r = this.vaultListResolvers.get(reqId);
49411
+ clearTimeout(r.timer);
49412
+ this.vaultListResolvers.delete(reqId);
49413
+ r.resolve(msg.entries ?? []);
49414
+ }
49415
+ }
49416
+ if (msg.type === "mcp_deploy_status") {
49417
+ const reqId = msg._reqId;
49418
+ if (reqId && this.mcpDeployResolvers.has(reqId)) {
49419
+ const r = this.mcpDeployResolvers.get(reqId);
49420
+ clearTimeout(r.timer);
49421
+ this.mcpDeployResolvers.delete(reqId);
49422
+ r.resolve({ status: msg.status, tools: msg.tools, error: msg.error });
49423
+ }
49424
+ }
49425
+ if (msg.type === "mcp_logs_result") {
49426
+ const reqId = msg._reqId;
49427
+ if (reqId && this.mcpLogsResolvers.has(reqId)) {
49428
+ const r = this.mcpLogsResolvers.get(reqId);
49429
+ clearTimeout(r.timer);
49430
+ this.mcpLogsResolvers.delete(reqId);
49431
+ r.resolve(msg.lines ?? []);
49432
+ }
49433
+ }
49434
+ if (msg.type === "mcp_schema_result") {
49435
+ const reqId = msg._reqId;
49436
+ if (reqId && this.mcpSchemaServiceResolvers.has(reqId)) {
49437
+ const r = this.mcpSchemaServiceResolvers.get(reqId);
49438
+ clearTimeout(r.timer);
49439
+ this.mcpSchemaServiceResolvers.delete(reqId);
49440
+ r.resolve(msg.tools ?? []);
49441
+ }
49442
+ }
49443
+ if (msg.type === "mcp_catalog_result") {
49444
+ const reqId = msg._reqId;
49445
+ if (reqId && this.mcpCatalogResolvers.has(reqId)) {
49446
+ const r = this.mcpCatalogResolvers.get(reqId);
49447
+ clearTimeout(r.timer);
49448
+ this.mcpCatalogResolvers.delete(reqId);
49449
+ r.resolve(msg.services ?? []);
49450
+ }
49451
+ }
49452
+ if (msg.type === "mcp_scope_result") {
49453
+ const reqId = msg._reqId;
49454
+ if (reqId && this.mcpScopeResolvers.has(reqId)) {
49455
+ const r = this.mcpScopeResolvers.get(reqId);
49456
+ clearTimeout(r.timer);
49457
+ this.mcpScopeResolvers.delete(reqId);
49458
+ r.resolve({ scope: msg.scope, deployed_by: msg.deployed_by });
49459
+ }
49460
+ }
49461
+ if (msg.type === "skill_deploy_ack") {
49462
+ const reqId = msg._reqId;
49463
+ if (reqId && this.skillDeployResolvers.has(reqId)) {
49464
+ const r = this.skillDeployResolvers.get(reqId);
49465
+ clearTimeout(r.timer);
49466
+ this.skillDeployResolvers.delete(reqId);
49467
+ r.resolve({ name: msg.name, files: msg.files ?? [] });
49468
+ }
49469
+ }
49182
49470
  if (msg.type === "error") {
49183
49471
  this.debug(`broker error: ${msg.code} ${msg.message}`);
49184
49472
  const id = msg.id ? String(msg.id) : null;
@@ -49232,7 +49520,15 @@ class BrokerClient {
49232
49520
  [this.peerFileResponseResolvers, { error: "broker error" }],
49233
49521
  [this.peerDirResponseResolvers, { error: "broker error" }],
49234
49522
  [this.webhookAckResolvers, null],
49235
- [this.webhookListResolvers, []]
49523
+ [this.webhookListResolvers, []],
49524
+ [this.vaultAckResolvers, false],
49525
+ [this.vaultListResolvers, []],
49526
+ [this.mcpDeployResolvers, { status: "error" }],
49527
+ [this.mcpLogsResolvers, []],
49528
+ [this.mcpSchemaServiceResolvers, []],
49529
+ [this.mcpCatalogResolvers, []],
49530
+ [this.mcpScopeResolvers, { scope: { type: "peer" }, deployed_by: "unknown" }],
49531
+ [this.skillDeployResolvers, { name: "unknown", files: [] }]
49236
49532
  ];
49237
49533
  for (const [map2, defaultVal] of allMaps) {
49238
49534
  const first = map2.entries().next().value;
@@ -49428,6 +49724,10 @@ function formatPush(p, meshSlug) {
49428
49724
  ${body}`;
49429
49725
  }
49430
49726
  async function startMcpServer() {
49727
+ const serviceIdx = process.argv.indexOf("--service");
49728
+ if (serviceIdx !== -1 && process.argv[serviceIdx + 1]) {
49729
+ return startServiceProxy(process.argv[serviceIdx + 1]);
49730
+ }
49431
49731
  const config2 = loadConfig();
49432
49732
  const myName = config2.displayName ?? "unnamed";
49433
49733
  const myRole = config2.role ?? process.env.CLAUDEMESH_ROLE ?? null;
@@ -50672,6 +50972,178 @@ ${lines.join(`
50672
50972
  const ok = await client2.deleteWebhook(delName);
50673
50973
  return text(ok ? `Webhook "${delName}" deactivated.` : `Failed to deactivate webhook "${delName}".`, !ok);
50674
50974
  }
50975
+ case "vault_set": {
50976
+ const { key, value, type: vType, mount_path, description } = args ?? {};
50977
+ if (!key || !value)
50978
+ return text("vault_set: `key` and `value` required", true);
50979
+ const client2 = allClients()[0];
50980
+ if (!client2)
50981
+ return text("vault_set: not connected", true);
50982
+ const entryType = vType ?? "env";
50983
+ let plaintext = value;
50984
+ if (entryType === "file") {
50985
+ const { existsSync: existsSync2, readFileSync: readFileSync2 } = await import("node:fs");
50986
+ if (!existsSync2(value))
50987
+ return text(`vault_set: file not found: ${value}`, true);
50988
+ plaintext = readFileSync2(value, "base64");
50989
+ }
50990
+ const encoded = Buffer.from(plaintext).toString("base64");
50991
+ const ok = await client2.vaultSet(key, encoded, "placeholder-nonce", "placeholder-sealed", entryType, mount_path, description);
50992
+ if (!ok)
50993
+ return text("vault_set: broker did not acknowledge", true);
50994
+ return text(`Vault entry "${key}" stored (${entryType}).`);
50995
+ }
50996
+ case "vault_list": {
50997
+ const client2 = allClients()[0];
50998
+ if (!client2)
50999
+ return text("vault_list: not connected", true);
51000
+ const entries = await client2.vaultList();
51001
+ if (entries.length === 0)
51002
+ return text("Vault is empty.");
51003
+ const lines = entries.map((e) => `- **${e.key}** (${e.entry_type}${e.mount_path ? ` → ${e.mount_path}` : ""})${e.description ? ` — ${e.description}` : ""} (${e.updated_at})`);
51004
+ return text(`${entries.length} vault entry(s):
51005
+ ${lines.join(`
51006
+ `)}`);
51007
+ }
51008
+ case "vault_delete": {
51009
+ const { key } = args ?? {};
51010
+ if (!key)
51011
+ return text("vault_delete: `key` required", true);
51012
+ const client2 = allClients()[0];
51013
+ if (!client2)
51014
+ return text("vault_delete: not connected", true);
51015
+ const ok = await client2.vaultDelete(key);
51016
+ return text(ok ? `Vault entry "${key}" deleted.` : `Vault entry "${key}" not found.`);
51017
+ }
51018
+ case "mesh_mcp_deploy": {
51019
+ const { server_name, file_id, git_url, git_branch, env: deployEnv, runtime, memory_mb, network_allow, scope } = args ?? {};
51020
+ if (!server_name)
51021
+ return text("mesh_mcp_deploy: `server_name` required", true);
51022
+ if (!file_id && !git_url)
51023
+ return text("mesh_mcp_deploy: either `file_id` or `git_url` required", true);
51024
+ const client2 = allClients()[0];
51025
+ if (!client2)
51026
+ return text("mesh_mcp_deploy: not connected", true);
51027
+ const source = file_id ? { type: "zip", file_id } : { type: "git", url: git_url, branch: git_branch };
51028
+ const config3 = {};
51029
+ if (deployEnv)
51030
+ config3.env = deployEnv;
51031
+ if (runtime)
51032
+ config3.runtime = runtime;
51033
+ if (memory_mb)
51034
+ config3.memory_mb = memory_mb;
51035
+ if (network_allow)
51036
+ config3.network_allow = network_allow;
51037
+ const result = await client2.mcpDeploy(server_name, source, Object.keys(config3).length > 0 ? config3 : undefined, scope);
51038
+ const toolList = result.tools?.map((t) => ` - ${t.name}: ${t.description}`).join(`
51039
+ `) ?? " (pending)";
51040
+ return text(`Deployed "${server_name}" (status: ${result.status}).
51041
+
51042
+ Tools:
51043
+ ${toolList}
51044
+
51045
+ Default scope: peer (private). Use mesh_mcp_scope to share.`);
51046
+ }
51047
+ case "mesh_mcp_undeploy": {
51048
+ const { server_name } = args ?? {};
51049
+ if (!server_name)
51050
+ return text("mesh_mcp_undeploy: `server_name` required", true);
51051
+ const client2 = allClients()[0];
51052
+ if (!client2)
51053
+ return text("mesh_mcp_undeploy: not connected", true);
51054
+ const ok = await client2.mcpUndeploy(server_name);
51055
+ return text(ok ? `Service "${server_name}" undeployed.` : `Failed to undeploy "${server_name}".`);
51056
+ }
51057
+ case "mesh_mcp_update": {
51058
+ const { server_name } = args ?? {};
51059
+ if (!server_name)
51060
+ return text("mesh_mcp_update: `server_name` required", true);
51061
+ const client2 = allClients()[0];
51062
+ if (!client2)
51063
+ return text("mesh_mcp_update: not connected", true);
51064
+ const result = await client2.mcpUpdate(server_name);
51065
+ return text(`Updated "${server_name}" (status: ${result.status}).`);
51066
+ }
51067
+ case "mesh_mcp_logs": {
51068
+ const { server_name, lines: logLines } = args ?? {};
51069
+ if (!server_name)
51070
+ return text("mesh_mcp_logs: `server_name` required", true);
51071
+ const client2 = allClients()[0];
51072
+ if (!client2)
51073
+ return text("mesh_mcp_logs: not connected", true);
51074
+ const logs = await client2.mcpLogs(server_name, logLines);
51075
+ if (logs.length === 0)
51076
+ return text(`No logs for "${server_name}".`);
51077
+ return text(`Logs for "${server_name}" (${logs.length} lines):
51078
+ \`\`\`
51079
+ ${logs.join(`
51080
+ `)}
51081
+ \`\`\``);
51082
+ }
51083
+ case "mesh_mcp_scope": {
51084
+ const { server_name, scope } = args ?? {};
51085
+ if (!server_name)
51086
+ return text("mesh_mcp_scope: `server_name` required", true);
51087
+ const client2 = allClients()[0];
51088
+ if (!client2)
51089
+ return text("mesh_mcp_scope: not connected", true);
51090
+ const result = await client2.mcpScope(server_name, scope);
51091
+ if (scope !== undefined) {
51092
+ return text(`Scope for "${server_name}" updated to: ${JSON.stringify(result.scope)}`);
51093
+ }
51094
+ return text(`**${server_name}** scope: ${JSON.stringify(result.scope)}
51095
+ Deployed by: ${result.deployed_by}`);
51096
+ }
51097
+ case "mesh_mcp_schema": {
51098
+ const { server_name, tool_name } = args ?? {};
51099
+ if (!server_name)
51100
+ return text("mesh_mcp_schema: `server_name` required", true);
51101
+ const client2 = allClients()[0];
51102
+ if (!client2)
51103
+ return text("mesh_mcp_schema: not connected", true);
51104
+ const tools = await client2.mcpServiceSchema(server_name, tool_name);
51105
+ if (tools.length === 0)
51106
+ return text(`No tools found for "${server_name}"${tool_name ? ` (tool: ${tool_name})` : ""}.`);
51107
+ const lines = tools.map((t) => `### ${t.name}
51108
+ ${t.description}
51109
+ \`\`\`json
51110
+ ${JSON.stringify(t.inputSchema, null, 2)}
51111
+ \`\`\``);
51112
+ return text(`Tools for "${server_name}":
51113
+
51114
+ ${lines.join(`
51115
+
51116
+ `)}`);
51117
+ }
51118
+ case "mesh_mcp_catalog": {
51119
+ const client2 = allClients()[0];
51120
+ if (!client2)
51121
+ return text("mesh_mcp_catalog: not connected", true);
51122
+ const services = await client2.mcpCatalog();
51123
+ if (services.length === 0)
51124
+ return text("No services deployed in the mesh.");
51125
+ const lines = services.map((s) => {
51126
+ const scopeStr = typeof s.scope === "string" ? s.scope : JSON.stringify(s.scope);
51127
+ return `- **${s.name}** (${s.type}, ${s.status}) — ${s.description}
51128
+ ${s.tool_count} tools | scope: ${scopeStr} | by ${s.deployed_by} | ${s.source_type}${s.runtime ? ` (${s.runtime})` : ""}`;
51129
+ });
51130
+ return text(`${services.length} service(s) in mesh:
51131
+
51132
+ ${lines.join(`
51133
+ `)}`);
51134
+ }
51135
+ case "mesh_skill_deploy": {
51136
+ const { file_id, git_url, git_branch } = args ?? {};
51137
+ if (!file_id && !git_url)
51138
+ return text("mesh_skill_deploy: either `file_id` or `git_url` required", true);
51139
+ const client2 = allClients()[0];
51140
+ if (!client2)
51141
+ return text("mesh_skill_deploy: not connected", true);
51142
+ const source = file_id ? { type: "zip", file_id } : { type: "git", url: git_url, branch: git_branch };
51143
+ const result = await client2.skillDeploy(source);
51144
+ return text(`Skill "${result.name}" deployed.
51145
+ Files: ${result.files.join(", ")}`);
51146
+ }
50675
51147
  default:
50676
51148
  return text(`Unknown tool: ${name}`, true);
50677
51149
  }
@@ -50830,6 +51302,120 @@ ${lines.join(`
50830
51302
  process.on("SIGTERM", shutdown);
50831
51303
  process.on("SIGINT", shutdown);
50832
51304
  }
51305
+ async function startServiceProxy(serviceName) {
51306
+ const config2 = loadConfig();
51307
+ if (config2.meshes.length === 0) {
51308
+ process.stderr.write(`[mesh:${serviceName}] no meshes joined
51309
+ `);
51310
+ process.exit(1);
51311
+ }
51312
+ const mesh = config2.meshes[0];
51313
+ const client2 = new BrokerClient(mesh, {
51314
+ displayName: config2.displayName ?? `proxy:${serviceName}`
51315
+ });
51316
+ try {
51317
+ await client2.connect();
51318
+ } catch (e) {
51319
+ process.stderr.write(`[mesh:${serviceName}] broker connect failed: ${e instanceof Error ? e.message : String(e)}
51320
+ `);
51321
+ process.exit(1);
51322
+ }
51323
+ await new Promise((r) => setTimeout(r, 1500));
51324
+ let tools = [];
51325
+ try {
51326
+ const fetched = await client2.getServiceTools(serviceName);
51327
+ tools = fetched;
51328
+ } catch {
51329
+ const cached2 = client2.serviceCatalog.find((s) => s.name === serviceName);
51330
+ if (cached2) {
51331
+ tools = cached2.tools;
51332
+ }
51333
+ }
51334
+ if (tools.length === 0) {
51335
+ process.stderr.write(`[mesh:${serviceName}] no tools found — service may not be running
51336
+ `);
51337
+ }
51338
+ const server = new Server({ name: `mesh:${serviceName}`, version: "0.1.0" }, { capabilities: { tools: {} } });
51339
+ server.setRequestHandler(ListToolsRequestSchema, () => ({
51340
+ tools: tools.map((t) => ({
51341
+ name: t.name,
51342
+ description: `[mesh:${serviceName}] ${t.description}`,
51343
+ inputSchema: t.inputSchema
51344
+ }))
51345
+ }));
51346
+ server.setRequestHandler(CallToolRequestSchema, async (req) => {
51347
+ const toolName = req.params.name;
51348
+ const args = req.params.arguments ?? {};
51349
+ if (client2.status !== "open") {
51350
+ let waited = 0;
51351
+ while (client2.status !== "open" && waited < 1e4) {
51352
+ await new Promise((r) => setTimeout(r, 500));
51353
+ waited += 500;
51354
+ }
51355
+ if (client2.status !== "open") {
51356
+ return {
51357
+ content: [
51358
+ {
51359
+ type: "text",
51360
+ text: `Service temporarily unavailable — broker reconnecting. Retry in a few seconds.`
51361
+ }
51362
+ ],
51363
+ isError: true
51364
+ };
51365
+ }
51366
+ }
51367
+ try {
51368
+ const result = await client2.mcpCall(serviceName, toolName, args);
51369
+ if (result.error) {
51370
+ return {
51371
+ content: [{ type: "text", text: `Error: ${result.error}` }],
51372
+ isError: true
51373
+ };
51374
+ }
51375
+ const resultText = typeof result.result === "string" ? result.result : JSON.stringify(result.result, null, 2);
51376
+ return {
51377
+ content: [{ type: "text", text: resultText }]
51378
+ };
51379
+ } catch (e) {
51380
+ return {
51381
+ content: [
51382
+ {
51383
+ type: "text",
51384
+ text: `Call failed: ${e instanceof Error ? e.message : String(e)}`
51385
+ }
51386
+ ],
51387
+ isError: true
51388
+ };
51389
+ }
51390
+ });
51391
+ client2.onPush((push) => {
51392
+ if (push.event === "mcp_undeployed" && push.eventData?.name === serviceName) {
51393
+ process.stderr.write(`[mesh:${serviceName}] service undeployed — exiting
51394
+ `);
51395
+ client2.close();
51396
+ process.exit(0);
51397
+ }
51398
+ if (push.event === "mcp_updated" && push.eventData?.name === serviceName) {
51399
+ const newTools = push.eventData?.tools;
51400
+ if (Array.isArray(newTools)) {
51401
+ tools = newTools;
51402
+ server.notification({
51403
+ method: "notifications/tools/list_changed"
51404
+ }).catch(() => {});
51405
+ }
51406
+ }
51407
+ });
51408
+ const transport = new StdioServerTransport;
51409
+ await server.connect(transport);
51410
+ const keepalive = setInterval(() => {}, 1000);
51411
+ const shutdown = () => {
51412
+ clearInterval(keepalive);
51413
+ client2.close();
51414
+ process.exit(0);
51415
+ };
51416
+ process.on("SIGTERM", shutdown);
51417
+ process.on("SIGINT", shutdown);
51418
+ }
50833
51419
 
50834
51420
  // src/commands/install.ts
50835
51421
  init_config();
@@ -51526,8 +52112,8 @@ async function runHook(args) {
51526
52112
  // src/commands/launch.ts
51527
52113
  init_config();
51528
52114
  import { spawn } from "node:child_process";
51529
- import { mkdtempSync, writeFileSync as writeFileSync4, rmSync, readdirSync, statSync } from "node:fs";
51530
- import { tmpdir, hostname as hostname2 } from "node:os";
52115
+ import { mkdtempSync, writeFileSync as writeFileSync4, rmSync, readdirSync, statSync, existsSync as existsSync3, readFileSync as readFileSync3 } from "node:fs";
52116
+ import { tmpdir, hostname as hostname2, homedir as homedir4 } from "node:os";
51531
52117
  import { join as join4 } from "node:path";
51532
52118
  import { createInterface } from "node:readline";
51533
52119
  async function pickMesh(meshes) {
@@ -51726,6 +52312,44 @@ async function runLaunch(flags, rawArgs) {
51726
52312
  rmSync(full, { recursive: true, force: true });
51727
52313
  }
51728
52314
  } catch {}
52315
+ try {
52316
+ const claudeConfigPath = join4(homedir4(), ".claude.json");
52317
+ if (existsSync3(claudeConfigPath)) {
52318
+ const claudeConfig = JSON.parse(readFileSync3(claudeConfigPath, "utf-8"));
52319
+ const mcpServers = claudeConfig.mcpServers ?? {};
52320
+ let cleaned = 0;
52321
+ for (const key of Object.keys(mcpServers)) {
52322
+ if (!key.startsWith("mesh:"))
52323
+ continue;
52324
+ const meta2 = mcpServers[key]?._meshSession;
52325
+ if (!meta2?.pid)
52326
+ continue;
52327
+ try {
52328
+ process.kill(meta2.pid, 0);
52329
+ } catch {
52330
+ delete mcpServers[key];
52331
+ cleaned++;
52332
+ }
52333
+ }
52334
+ if (cleaned > 0) {
52335
+ claudeConfig.mcpServers = mcpServers;
52336
+ writeFileSync4(claudeConfigPath, JSON.stringify(claudeConfig, null, 2) + `
52337
+ `, "utf-8");
52338
+ }
52339
+ }
52340
+ } catch {}
52341
+ let serviceCatalog = [];
52342
+ try {
52343
+ const tmpClient = new BrokerClient(mesh, { displayName });
52344
+ await tmpClient.connect();
52345
+ await new Promise((r) => setTimeout(r, 2000));
52346
+ serviceCatalog = tmpClient.serviceCatalog;
52347
+ tmpClient.close();
52348
+ } catch {
52349
+ if (!args.quiet) {
52350
+ console.log(" (Could not fetch service catalog — mesh services won't be natively available)");
52351
+ }
52352
+ }
51729
52353
  const tmpDir = mkdtempSync(join4(tmpdir(), "claudemesh-"));
51730
52354
  const sessionConfig = {
51731
52355
  version: 1,
@@ -51743,6 +52367,50 @@ async function runLaunch(flags, rawArgs) {
51743
52367
  await confirmPermissions();
51744
52368
  }
51745
52369
  }
52370
+ const meshMcpEntries = [];
52371
+ if (serviceCatalog.length > 0) {
52372
+ const claudeConfigPath = join4(homedir4(), ".claude.json");
52373
+ let claudeConfig = {};
52374
+ try {
52375
+ claudeConfig = JSON.parse(readFileSync3(claudeConfigPath, "utf-8"));
52376
+ } catch {
52377
+ claudeConfig = {};
52378
+ }
52379
+ const mcpServers = claudeConfig.mcpServers ?? {};
52380
+ const sessionTag = `${process.pid}`;
52381
+ for (const svc of serviceCatalog) {
52382
+ if (svc.status !== "running")
52383
+ continue;
52384
+ const entryKey = `mesh:${svc.name}:${sessionTag}`;
52385
+ const entry = {
52386
+ command: "claudemesh",
52387
+ args: ["mcp", "--service", svc.name],
52388
+ env: {
52389
+ CLAUDEMESH_CONFIG_DIR: tmpDir
52390
+ },
52391
+ _meshSession: {
52392
+ pid: process.pid,
52393
+ meshSlug: mesh.slug,
52394
+ serviceName: svc.name,
52395
+ createdAt: new Date().toISOString()
52396
+ }
52397
+ };
52398
+ mcpServers[entryKey] = entry;
52399
+ meshMcpEntries.push({ key: entryKey, entry });
52400
+ }
52401
+ claudeConfig.mcpServers = mcpServers;
52402
+ writeFileSync4(claudeConfigPath, JSON.stringify(claudeConfig, null, 2) + `
52403
+ `, "utf-8");
52404
+ if (!args.quiet && meshMcpEntries.length > 0) {
52405
+ console.log(` ${meshMcpEntries.length} mesh service(s) registered as native MCPs:`);
52406
+ for (const { key } of meshMcpEntries) {
52407
+ const svcName = key.split(":")[1];
52408
+ const svc = serviceCatalog.find((s) => s.name === svcName);
52409
+ console.log(` ${svcName} (${svc?.tools.length ?? 0} tools)`);
52410
+ }
52411
+ console.log("");
52412
+ }
52413
+ }
51746
52414
  const filtered = [];
51747
52415
  for (let i = 0;i < args.claudeArgs.length; i++) {
51748
52416
  if (args.claudeArgs[i] === "--dangerously-load-development-channels" || args.claudeArgs[i] === "--dangerously-skip-permissions") {
@@ -51767,10 +52435,25 @@ async function runLaunch(flags, rawArgs) {
51767
52435
  ...process.env,
51768
52436
  CLAUDEMESH_CONFIG_DIR: tmpDir,
51769
52437
  CLAUDEMESH_DISPLAY_NAME: displayName,
52438
+ MCP_TIMEOUT: process.env.MCP_TIMEOUT ?? "30000",
52439
+ MAX_MCP_OUTPUT_TOKENS: process.env.MAX_MCP_OUTPUT_TOKENS ?? "50000",
51770
52440
  ...role ? { CLAUDEMESH_ROLE: role } : {}
51771
52441
  }
51772
52442
  });
51773
52443
  const cleanup = () => {
52444
+ if (meshMcpEntries.length > 0) {
52445
+ try {
52446
+ const claudeConfigPath = join4(homedir4(), ".claude.json");
52447
+ const claudeConfig = JSON.parse(readFileSync3(claudeConfigPath, "utf-8"));
52448
+ const mcpServers = claudeConfig.mcpServers ?? {};
52449
+ for (const { key } of meshMcpEntries) {
52450
+ delete mcpServers[key];
52451
+ }
52452
+ claudeConfig.mcpServers = mcpServers;
52453
+ writeFileSync4(claudeConfigPath, JSON.stringify(claudeConfig, null, 2) + `
52454
+ `, "utf-8");
52455
+ } catch {}
52456
+ }
51774
52457
  try {
51775
52458
  rmSync(tmpDir, { recursive: true, force: true });
51776
52459
  } catch {}
@@ -51803,12 +52486,12 @@ async function runLaunch(flags, rawArgs) {
51803
52486
  }
51804
52487
 
51805
52488
  // src/commands/status.ts
51806
- import { statSync as statSync2, existsSync as existsSync3 } from "node:fs";
52489
+ import { statSync as statSync2, existsSync as existsSync4 } from "node:fs";
51807
52490
  init_config();
51808
52491
  // package.json
51809
52492
  var package_default = {
51810
52493
  name: "claudemesh-cli",
51811
- version: "0.7.1",
52494
+ version: "0.8.1",
51812
52495
  description: "Claude Code MCP client for claudemesh — peer mesh messaging between Claude sessions.",
51813
52496
  keywords: [
51814
52497
  "claude-code",
@@ -51909,7 +52592,7 @@ async function runStatus() {
51909
52592
  console.log("─".repeat(60));
51910
52593
  const configPath = getConfigPath();
51911
52594
  let configPerms = "missing";
51912
- if (existsSync3(configPath)) {
52595
+ if (existsSync4(configPath)) {
51913
52596
  const st = statSync2(configPath);
51914
52597
  const mode = (st.mode & 511).toString(8).padStart(4, "0");
51915
52598
  configPerms = mode === "0600" ? `${mode} ✓` : `${mode} ⚠ (expected 0600)`;
@@ -51958,8 +52641,8 @@ async function runStatus() {
51958
52641
 
51959
52642
  // src/commands/doctor.ts
51960
52643
  init_config();
51961
- import { existsSync as existsSync4, readFileSync as readFileSync3, statSync as statSync3 } from "node:fs";
51962
- import { homedir as homedir4, platform as platform2 } from "node:os";
52644
+ import { existsSync as existsSync5, readFileSync as readFileSync4, statSync as statSync3 } from "node:fs";
52645
+ import { homedir as homedir5, platform as platform2 } from "node:os";
51963
52646
  import { join as join5 } from "node:path";
51964
52647
  import { spawnSync as spawnSync2 } from "node:child_process";
51965
52648
  function checkNode() {
@@ -51984,8 +52667,8 @@ function checkClaudeOnPath() {
51984
52667
  };
51985
52668
  }
51986
52669
  function checkMcpRegistered() {
51987
- const claudeConfig = join5(homedir4(), ".claude.json");
51988
- if (!existsSync4(claudeConfig)) {
52670
+ const claudeConfig = join5(homedir5(), ".claude.json");
52671
+ if (!existsSync5(claudeConfig)) {
51989
52672
  return {
51990
52673
  name: "claudemesh MCP registered in ~/.claude.json",
51991
52674
  pass: false,
@@ -51993,7 +52676,7 @@ function checkMcpRegistered() {
51993
52676
  };
51994
52677
  }
51995
52678
  try {
51996
- const cfg = JSON.parse(readFileSync3(claudeConfig, "utf-8"));
52679
+ const cfg = JSON.parse(readFileSync4(claudeConfig, "utf-8"));
51997
52680
  const registered = Boolean(cfg.mcpServers?.["claudemesh"]);
51998
52681
  return {
51999
52682
  name: "claudemesh MCP registered in ~/.claude.json",
@@ -52010,8 +52693,8 @@ function checkMcpRegistered() {
52010
52693
  }
52011
52694
  }
52012
52695
  function checkHooksRegistered() {
52013
- const settings = join5(homedir4(), ".claude", "settings.json");
52014
- if (!existsSync4(settings)) {
52696
+ const settings = join5(homedir5(), ".claude", "settings.json");
52697
+ if (!existsSync5(settings)) {
52015
52698
  return {
52016
52699
  name: "Status hooks registered in ~/.claude/settings.json",
52017
52700
  pass: false,
@@ -52019,7 +52702,7 @@ function checkHooksRegistered() {
52019
52702
  };
52020
52703
  }
52021
52704
  try {
52022
- const raw = readFileSync3(settings, "utf-8");
52705
+ const raw = readFileSync4(settings, "utf-8");
52023
52706
  const has = raw.includes("claudemesh hook ");
52024
52707
  return {
52025
52708
  name: "Status hooks registered in ~/.claude/settings.json",
@@ -52036,7 +52719,7 @@ function checkHooksRegistered() {
52036
52719
  }
52037
52720
  function checkConfigFile() {
52038
52721
  const path = getConfigPath();
52039
- if (!existsSync4(path)) {
52722
+ if (!existsSync5(path)) {
52040
52723
  return {
52041
52724
  name: "~/.claudemesh/config.json exists and parses",
52042
52725
  pass: true,
@@ -52140,15 +52823,15 @@ async function runDoctor() {
52140
52823
 
52141
52824
  // src/commands/welcome.ts
52142
52825
  init_config();
52143
- import { existsSync as existsSync5, readFileSync as readFileSync4 } from "node:fs";
52144
- import { homedir as homedir5 } from "node:os";
52826
+ import { existsSync as existsSync6, readFileSync as readFileSync5 } from "node:fs";
52827
+ import { homedir as homedir6 } from "node:os";
52145
52828
  import { join as join6 } from "node:path";
52146
52829
  function detectState() {
52147
- const claudeConfig = join6(homedir5(), ".claude.json");
52830
+ const claudeConfig = join6(homedir6(), ".claude.json");
52148
52831
  let mcpRegistered = false;
52149
- if (existsSync5(claudeConfig)) {
52832
+ if (existsSync6(claudeConfig)) {
52150
52833
  try {
52151
- const cfg = JSON.parse(readFileSync4(claudeConfig, "utf-8"));
52834
+ const cfg = JSON.parse(readFileSync5(claudeConfig, "utf-8"));
52152
52835
  mcpRegistered = Boolean(cfg.mcpServers?.["claudemesh"]);
52153
52836
  } catch {}
52154
52837
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claudemesh-cli",
3
- "version": "0.7.1",
3
+ "version": "0.8.1",
4
4
  "description": "Claude Code MCP client for claudemesh — peer mesh messaging between Claude sessions.",
5
5
  "keywords": [
6
6
  "claude-code",
@@ -48,10 +48,10 @@
48
48
  "prettier": "3.6.2",
49
49
  "typescript": "5.9.3",
50
50
  "vitest": "4.0.14",
51
- "@turbostarter/eslint-config": "0.1.0",
51
+ "@turbostarter/vitest-config": "0.1.0",
52
52
  "@turbostarter/tsconfig": "0.1.0",
53
53
  "@turbostarter/prettier-config": "0.1.0",
54
- "@turbostarter/vitest-config": "0.1.0"
54
+ "@turbostarter/eslint-config": "0.1.0"
55
55
  },
56
56
  "scripts": {
57
57
  "build": "bun build src/index.ts --target=node --outfile dist/index.js --banner \"#!/usr/bin/env node\" && chmod +x dist/index.js",