@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.
- package/dist/host-config.cjs +59 -1
- package/dist/host-config.js +60 -2
- package/dist/index.d.cts +14 -3
- package/dist/index.d.ts +14 -3
- package/package.json +4 -4
package/dist/host-config.cjs
CHANGED
|
@@ -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 =
|
|
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/host-config.js
CHANGED
|
@@ -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 =
|
|
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
|
|
94
|
-
*
|
|
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
|
-
/**
|
|
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
|
|
94
|
-
*
|
|
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
|
-
/**
|
|
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.
|
|
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.
|
|
23
|
-
"@alfe.ai/
|
|
24
|
-
"@alfe.ai/
|
|
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",
|