@alfe.ai/claude-code-host 0.1.16 → 0.1.17

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.
@@ -478,6 +478,64 @@ var CredentialStatusReporter = class {
478
478
  }
479
479
  };
480
480
  //#endregion
481
+ //#region src/mcp-allow-rules.ts
482
+ /**
483
+ * Allow rules for the MCP servers a bridged turn loads.
484
+ *
485
+ * Turns run under `--permission-mode dontAsk`, which DENIES any tool not in
486
+ * `--allowedTools`. MCP tools are named `mcp__<server>__<tool>`, so without a
487
+ * rule per server every MCP call (alfe-platform memory/voice/send/secrets and
488
+ * every integration server) is refused (prod, 2026-10-17). Turns pass
489
+ * `--strict-mcp-config --mcp-config <file>`, so the servers in that file are
490
+ * exactly the Alfe-installed set; each gets a server-wide `mcp__<server>` rule.
491
+ * No blanket `mcp__*` wildcard: the allow-list stays scoped to what Alfe wrote.
492
+ *
493
+ * The file is read on every spawn, so a newly installed integration server is
494
+ * allowed on the next turn without restarting the host.
495
+ */
496
+ const CLAUDE_AI_PREFIX = "claude.ai ";
497
+ /**
498
+ * Mirrors how claude (2.1.x) builds the `<server>` segment of an MCP tool id:
499
+ * every character outside `[a-zA-Z0-9_-]` becomes `_` (hyphens are kept), and
500
+ * `claude.ai ` connector names also collapse and trim underscores. Permission
501
+ * rules match on this normalised name, so a rule must use it too.
502
+ */
503
+ function normalizeMcpServerName(name) {
504
+ let normalized = name.replace(/[^a-zA-Z0-9_-]/g, "_");
505
+ if (name.startsWith(CLAUDE_AI_PREFIX)) normalized = normalized.replace(/_+/g, "_").replace(/^_|_$/g, "");
506
+ return normalized;
507
+ }
508
+ /**
509
+ * Server-wide allow rules (`mcp__<server>`) for every server in the
510
+ * `--mcp-config` file. A missing or malformed file yields no rules and one
511
+ * warning; the turn still runs, it just cannot call MCP tools.
512
+ */
513
+ function mcpServerAllowRules(configPath, log) {
514
+ if (!configPath) return [];
515
+ let parsed;
516
+ try {
517
+ parsed = JSON.parse((0, node_fs.readFileSync)(configPath, "utf8"));
518
+ } catch (err) {
519
+ log.warn(`[claude-host] could not read MCP config ${configPath}; MCP tools are not allowed this turn: ${err instanceof Error ? err.message : String(err)}`);
520
+ return [];
521
+ }
522
+ const servers = parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed.mcpServers : void 0;
523
+ if (servers === null || typeof servers !== "object" || Array.isArray(servers)) {
524
+ log.warn(`[claude-host] MCP config ${configPath} has no mcpServers map; MCP tools are not allowed this turn`);
525
+ return [];
526
+ }
527
+ const rules = /* @__PURE__ */ new Set();
528
+ for (const name of Object.keys(servers)) {
529
+ const normalized = normalizeMcpServerName(name);
530
+ if (!normalized || normalized.includes("__")) {
531
+ log.warn(`[claude-host] MCP server "${name}" cannot be expressed as a server-wide allow rule; its tools stay denied`);
532
+ continue;
533
+ }
534
+ rules.add(`mcp__${normalized}`);
535
+ }
536
+ return [...rules];
537
+ }
538
+ //#endregion
481
539
  //#region src/stream-mapper.ts
482
540
  /**
483
541
  * Claude Code `--output-format stream-json` → Alfe chat-activity mapper.
@@ -970,7 +1028,7 @@ var SessionDriver = class {
970
1028
  return this.runPerTurn(params);
971
1029
  }
972
1030
  buildArgs(params) {
973
- const allowed = (params.allowedTools ?? (this.managed ? MANAGED_ALLOWED_TOOLS : DEFAULT_ALLOWED_TOOLS)).join(",");
1031
+ const allowed = [...params.allowedTools ?? (this.managed ? MANAGED_ALLOWED_TOOLS : DEFAULT_ALLOWED_TOOLS), ...mcpServerAllowRules(params.mcpConfigPath, this.log)].join(",");
974
1032
  const permissionMode = params.permissionMode ?? "dontAsk";
975
1033
  if (!CLAUDE_PERMISSION_MODES.includes(permissionMode)) throw new Error(`Unsupported Claude permission mode: ${permissionMode}`);
976
1034
  const args = [
@@ -1,6 +1,6 @@
1
1
  import { AgentApiClient } from "@alfe.ai/agent-api-client";
2
2
  import { ChatServiceClient, resolveAlfeChat } from "@alfe.ai/chat";
3
- import { existsSync } from "node:fs";
3
+ import { existsSync, readFileSync } from "node:fs";
4
4
  import { homedir, tmpdir } from "node:os";
5
5
  import { mkdir, readFile, readdir, rename, stat, unlink, writeFile } from "node:fs/promises";
6
6
  import { basename, dirname, join } from "node:path";
@@ -478,6 +478,64 @@ var CredentialStatusReporter = class {
478
478
  }
479
479
  };
480
480
  //#endregion
481
+ //#region src/mcp-allow-rules.ts
482
+ /**
483
+ * Allow rules for the MCP servers a bridged turn loads.
484
+ *
485
+ * Turns run under `--permission-mode dontAsk`, which DENIES any tool not in
486
+ * `--allowedTools`. MCP tools are named `mcp__<server>__<tool>`, so without a
487
+ * rule per server every MCP call (alfe-platform memory/voice/send/secrets and
488
+ * every integration server) is refused (prod, 2026-10-17). Turns pass
489
+ * `--strict-mcp-config --mcp-config <file>`, so the servers in that file are
490
+ * exactly the Alfe-installed set; each gets a server-wide `mcp__<server>` rule.
491
+ * No blanket `mcp__*` wildcard: the allow-list stays scoped to what Alfe wrote.
492
+ *
493
+ * The file is read on every spawn, so a newly installed integration server is
494
+ * allowed on the next turn without restarting the host.
495
+ */
496
+ const CLAUDE_AI_PREFIX = "claude.ai ";
497
+ /**
498
+ * Mirrors how claude (2.1.x) builds the `<server>` segment of an MCP tool id:
499
+ * every character outside `[a-zA-Z0-9_-]` becomes `_` (hyphens are kept), and
500
+ * `claude.ai ` connector names also collapse and trim underscores. Permission
501
+ * rules match on this normalised name, so a rule must use it too.
502
+ */
503
+ function normalizeMcpServerName(name) {
504
+ let normalized = name.replace(/[^a-zA-Z0-9_-]/g, "_");
505
+ if (name.startsWith(CLAUDE_AI_PREFIX)) normalized = normalized.replace(/_+/g, "_").replace(/^_|_$/g, "");
506
+ return normalized;
507
+ }
508
+ /**
509
+ * Server-wide allow rules (`mcp__<server>`) for every server in the
510
+ * `--mcp-config` file. A missing or malformed file yields no rules and one
511
+ * warning; the turn still runs, it just cannot call MCP tools.
512
+ */
513
+ function mcpServerAllowRules(configPath, log) {
514
+ if (!configPath) return [];
515
+ let parsed;
516
+ try {
517
+ parsed = JSON.parse(readFileSync(configPath, "utf8"));
518
+ } catch (err) {
519
+ log.warn(`[claude-host] could not read MCP config ${configPath}; MCP tools are not allowed this turn: ${err instanceof Error ? err.message : String(err)}`);
520
+ return [];
521
+ }
522
+ const servers = parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed.mcpServers : void 0;
523
+ if (servers === null || typeof servers !== "object" || Array.isArray(servers)) {
524
+ log.warn(`[claude-host] MCP config ${configPath} has no mcpServers map; MCP tools are not allowed this turn`);
525
+ return [];
526
+ }
527
+ const rules = /* @__PURE__ */ new Set();
528
+ for (const name of Object.keys(servers)) {
529
+ const normalized = normalizeMcpServerName(name);
530
+ if (!normalized || normalized.includes("__")) {
531
+ log.warn(`[claude-host] MCP server "${name}" cannot be expressed as a server-wide allow rule; its tools stay denied`);
532
+ continue;
533
+ }
534
+ rules.add(`mcp__${normalized}`);
535
+ }
536
+ return [...rules];
537
+ }
538
+ //#endregion
481
539
  //#region src/stream-mapper.ts
482
540
  /**
483
541
  * Claude Code `--output-format stream-json` → Alfe chat-activity mapper.
@@ -970,7 +1028,7 @@ var SessionDriver = class {
970
1028
  return this.runPerTurn(params);
971
1029
  }
972
1030
  buildArgs(params) {
973
- const allowed = (params.allowedTools ?? (this.managed ? MANAGED_ALLOWED_TOOLS : DEFAULT_ALLOWED_TOOLS)).join(",");
1031
+ const allowed = [...params.allowedTools ?? (this.managed ? MANAGED_ALLOWED_TOOLS : DEFAULT_ALLOWED_TOOLS), ...mcpServerAllowRules(params.mcpConfigPath, this.log)].join(",");
974
1032
  const permissionMode = params.permissionMode ?? "dontAsk";
975
1033
  if (!CLAUDE_PERMISSION_MODES.includes(permissionMode)) throw new Error(`Unsupported Claude permission mode: ${permissionMode}`);
976
1034
  const args = [
package/dist/index.d.cts CHANGED
@@ -90,12 +90,19 @@ interface ConnectProviderAccounts {
90
90
  }
91
91
  /**
92
92
  * One AWS CLI profile an agent may use (`GET /agent/connect/aws/accounts`).
93
- * `roleArn` is null for a connection's Direct identity. Carries no secret
94
- * material; sessions are minted per call via `getAwsSession`.
93
+ * `roleArn` is null for a connection's Direct identity and for IAM Identity
94
+ * Center (SSO) profiles, which name their `permissionSet` instead. Carries no
95
+ * secret material; sessions are minted per call via `getAwsSession`.
95
96
  */
96
97
  interface AwsProfile {
97
98
  profile: string;
98
99
  roleArn: string | null;
100
+ /**
101
+ * IAM Identity Center permission set (role name) for an SSO profile; `null`
102
+ * for role, access-key and shared-session profiles. Older servers omit the
103
+ * field, which normalizes to `null`.
104
+ */
105
+ permissionSet: string | null;
99
106
  accountId: string;
100
107
  region: string;
101
108
  label: string | null;
@@ -2636,7 +2643,11 @@ interface RunTurnParams {
2636
2643
  * mode: the host always passes a registry id there).
2637
2644
  */
2638
2645
  model?: string;
2639
- /** Tool allow-list (defaults to DEFAULT_ALLOWED_TOOLS). */
2646
+ /**
2647
+ * Built-in tool allow-list (defaults to DEFAULT_ALLOWED_TOOLS, or
2648
+ * MANAGED_ALLOWED_TOOLS on a managed VM). The `--mcp-config` servers' rules
2649
+ * are always appended.
2650
+ */
2640
2651
  allowedTools?: readonly string[];
2641
2652
  /** Permission mode (defaults to DEFAULT_PERMISSION_MODE — never bypass). */
2642
2653
  permissionMode?: ClaudePermissionMode;
package/dist/index.d.ts CHANGED
@@ -90,12 +90,19 @@ interface ConnectProviderAccounts {
90
90
  }
91
91
  /**
92
92
  * One AWS CLI profile an agent may use (`GET /agent/connect/aws/accounts`).
93
- * `roleArn` is null for a connection's Direct identity. Carries no secret
94
- * material; sessions are minted per call via `getAwsSession`.
93
+ * `roleArn` is null for a connection's Direct identity and for IAM Identity
94
+ * Center (SSO) profiles, which name their `permissionSet` instead. Carries no
95
+ * secret material; sessions are minted per call via `getAwsSession`.
95
96
  */
96
97
  interface AwsProfile {
97
98
  profile: string;
98
99
  roleArn: string | null;
100
+ /**
101
+ * IAM Identity Center permission set (role name) for an SSO profile; `null`
102
+ * for role, access-key and shared-session profiles. Older servers omit the
103
+ * field, which normalizes to `null`.
104
+ */
105
+ permissionSet: string | null;
99
106
  accountId: string;
100
107
  region: string;
101
108
  label: string | null;
@@ -2636,7 +2643,11 @@ interface RunTurnParams {
2636
2643
  * mode: the host always passes a registry id there).
2637
2644
  */
2638
2645
  model?: string;
2639
- /** Tool allow-list (defaults to DEFAULT_ALLOWED_TOOLS). */
2646
+ /**
2647
+ * Built-in tool allow-list (defaults to DEFAULT_ALLOWED_TOOLS, or
2648
+ * MANAGED_ALLOWED_TOOLS on a managed VM). The `--mcp-config` servers' rules
2649
+ * are always appended.
2650
+ */
2640
2651
  allowedTools?: readonly string[];
2641
2652
  /** Permission mode (defaults to DEFAULT_PERMISSION_MODE — never bypass). */
2642
2653
  permissionMode?: ClaudePermissionMode;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alfe.ai/claude-code-host",
3
- "version": "0.1.16",
3
+ "version": "0.1.17",
4
4
  "description": "Chat bridge that makes a Claude Code agent reachable from Alfe chat, on its Claude credential, the local claude login, or Alfe AI credits",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -19,9 +19,9 @@
19
19
  "dist"
20
20
  ],
21
21
  "dependencies": {
22
- "@alfe.ai/agent-api-client": "^0.21.1",
23
- "@alfe.ai/chat": "^0.0.19",
24
- "@alfe.ai/config": "^0.4.2"
22
+ "@alfe.ai/agent-api-client": "^0.21.2",
23
+ "@alfe.ai/config": "^0.4.2",
24
+ "@alfe.ai/chat": "^0.0.19"
25
25
  },
26
26
  "license": "UNLICENSED",
27
27
  "homepage": "https://alfe.ai",