hoomanjs 1.0.0 → 1.1.0

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
@@ -174,7 +174,7 @@ hooman acp --toolkit max
174
174
  ACP notes:
175
175
 
176
176
  - ACP sessions are stored under `~/.hooman/acp-sessions`
177
- - ACP uses Hooman's local `mcp.json`; client-provided MCP servers and client workspace tools are not exposed
177
+ - ACP loads MCP servers passed on `session/new` and `session/load`, in addition to Hooman's local `mcp.json`
178
178
  - ACP `session/new` and `session/load` support `_meta.userId` and `_meta.systemPrompt`
179
179
  - when `_meta.systemPrompt` is provided, it is appended to the agent system prompt with a section break
180
180
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hoomanjs",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "Bun-powered local AI agent CLI with chat, exec, ACP, MCP, and skills support.",
5
5
  "author": {
6
6
  "name": "Vaibhav Pandey",
@@ -42,6 +42,7 @@ import { extractAcpClientUserId } from "./meta/user-id.ts";
42
42
  import { deriveSessionTitleFromEcho } from "./sessions/title.ts";
43
43
  import { acpPromptEchoText, acpPromptToInvokeArgs } from "./prompt-invoke.ts";
44
44
  import type { Config } from "../core/config.ts";
45
+ import { normalizeAcpSessionMcpServers } from "./mcp-servers.ts";
45
46
  import {
46
47
  listStoredSessionIds,
47
48
  loadSessionMessages,
@@ -206,6 +207,10 @@ export class AcpAgent implements AgentContract {
206
207
  authMethods: [],
207
208
  agentCapabilities: {
208
209
  loadSession: true,
210
+ mcpCapabilities: {
211
+ http: true,
212
+ sse: true,
213
+ },
209
214
  promptCapabilities: {
210
215
  embeddedContext: true,
211
216
  image: true,
@@ -315,6 +320,7 @@ export class AcpAgent implements AgentContract {
315
320
  const clientSystemPrompt =
316
321
  extractAcpClientSystemPrompt(params._meta) ?? null;
317
322
  const bootstrapUserId = clientUserId ?? sessionId;
323
+ const mcpServers = normalizeAcpSessionMcpServers(params.mcpServers);
318
324
 
319
325
  const now = new Date().toISOString();
320
326
  const meta: SessionMetaFile = {
@@ -324,6 +330,7 @@ export class AcpAgent implements AgentContract {
324
330
  title: null,
325
331
  userId: clientUserId,
326
332
  systemPrompt: clientSystemPrompt,
333
+ mcpServers,
327
334
  };
328
335
  await writeSessionMeta(this.#acpRoot, sessionId, meta);
329
336
 
@@ -336,6 +343,7 @@ export class AcpAgent implements AgentContract {
336
343
  userId: bootstrapUserId,
337
344
  sessionId,
338
345
  toolkit: this.#toolkit,
346
+ mcpServers,
339
347
  ...(clientSystemPrompt ? { systemPrompt: clientSystemPrompt } : {}),
340
348
  },
341
349
  false,
@@ -419,6 +427,10 @@ export class AcpAgent implements AgentContract {
419
427
  ? requestedSystemPrompt
420
428
  : storedSystemPrompt;
421
429
  const bootstrapUserId = clientUserId ?? params.sessionId;
430
+ const mcpServers =
431
+ params.mcpServers.length > 0
432
+ ? normalizeAcpSessionMcpServers(params.mcpServers)
433
+ : (existing.mcpServers ?? []);
422
434
 
423
435
  const {
424
436
  config,
@@ -429,6 +441,7 @@ export class AcpAgent implements AgentContract {
429
441
  userId: bootstrapUserId,
430
442
  sessionId: params.sessionId,
431
443
  toolkit: this.#toolkit,
444
+ mcpServers,
432
445
  ...(clientSystemPrompt ? { systemPrompt: clientSystemPrompt } : {}),
433
446
  },
434
447
  false,
@@ -487,6 +500,7 @@ export class AcpAgent implements AgentContract {
487
500
  ...(requestedSystemPrompt !== undefined
488
501
  ? { systemPrompt: requestedSystemPrompt || null }
489
502
  : {}),
503
+ mcpServers,
490
504
  });
491
505
 
492
506
  return {
@@ -0,0 +1,64 @@
1
+ import { RequestError, type McpServer } from "@agentclientprotocol/sdk";
2
+ import type { NamedMcpTransport } from "../core/mcp/index.ts";
3
+
4
+ function pairsToRecord(
5
+ pairs: ReadonlyArray<{ name: string; value: string }>,
6
+ ): Record<string, string> | undefined {
7
+ if (pairs.length === 0) {
8
+ return undefined;
9
+ }
10
+ return Object.fromEntries(pairs.map((pair) => [pair.name, pair.value]));
11
+ }
12
+
13
+ function toNamedTransport(server: McpServer): NamedMcpTransport {
14
+ if ("command" in server) {
15
+ return {
16
+ name: server.name,
17
+ transport: {
18
+ type: "stdio",
19
+ command: server.command,
20
+ args: server.args,
21
+ env: pairsToRecord(server.env),
22
+ },
23
+ };
24
+ }
25
+ if (server.type === "http") {
26
+ return {
27
+ name: server.name,
28
+ transport: {
29
+ type: "streamable-http",
30
+ url: server.url,
31
+ headers: pairsToRecord(server.headers),
32
+ },
33
+ };
34
+ }
35
+ return {
36
+ name: server.name,
37
+ transport: {
38
+ type: "sse",
39
+ url: server.url,
40
+ headers: pairsToRecord(server.headers),
41
+ },
42
+ };
43
+ }
44
+
45
+ /**
46
+ * Convert ACP session MCP server definitions into the transport shape used by
47
+ * Hooman's MCP manager.
48
+ */
49
+ export function normalizeAcpSessionMcpServers(
50
+ servers: readonly McpServer[] | null | undefined,
51
+ ): NamedMcpTransport[] {
52
+ const normalized = (servers ?? []).map(toNamedTransport);
53
+ const seen = new Set<string>();
54
+ for (const { name } of normalized) {
55
+ if (seen.has(name)) {
56
+ throw RequestError.invalidParams({
57
+ mcpServers: servers,
58
+ message: `Duplicate ACP MCP server name "${name}" in session request`,
59
+ });
60
+ }
61
+ seen.add(name);
62
+ }
63
+ return normalized;
64
+ }
@@ -2,6 +2,7 @@ import { mkdir, readdir, readFile, writeFile } from "node:fs/promises";
2
2
  import { join } from "node:path";
3
3
  import type { SessionInfo } from "@agentclientprotocol/sdk";
4
4
  import type { MessageData } from "@strands-agents/sdk";
5
+ import type { NamedMcpTransport } from "../../core/mcp/config.ts";
5
6
 
6
7
  export type SessionMetaFile = {
7
8
  cwd: string;
@@ -12,6 +13,8 @@ export type SessionMetaFile = {
12
13
  userId?: string | null;
13
14
  /** Session-level system prompt from ACP client `_meta`. */
14
15
  systemPrompt?: string | null;
16
+ /** Session-scoped MCP servers requested by the ACP client. */
17
+ mcpServers?: NamedMcpTransport[];
15
18
  };
16
19
 
17
20
  const META = "meta.json";
package/src/core/index.ts CHANGED
@@ -6,6 +6,7 @@ import {
6
6
  createMcpManager,
7
7
  type Config as McpServersConfig,
8
8
  type Manager as McpConnectionManager,
9
+ type NamedMcpTransport,
9
10
  } from "./mcp/index.ts";
10
11
  import { createSkillsRegistry } from "./skills/index.ts";
11
12
  import type { Registry } from "./skills/index.ts";
@@ -23,6 +24,7 @@ export async function bootstrap(
23
24
  userId?: string;
24
25
  sessionId: string;
25
26
  systemPrompt?: string;
27
+ mcpServers?: NamedMcpTransport[];
26
28
  toolkit?: Toolkit;
27
29
  },
28
30
  print: boolean = false,
@@ -34,7 +36,7 @@ export async function bootstrap(
34
36
  }> {
35
37
  const config = new Config(configJsonPath());
36
38
  const mcpConfig = createMcpConfig(mcpJsonPath());
37
- const mcpManager = createMcpManager(mcpConfig);
39
+ const mcpManager = createMcpManager(mcpConfig, meta.mcpServers ?? []);
38
40
  const mcp = { config: mcpConfig, manager: mcpManager };
39
41
  const registry = createSkillsRegistry(basePath());
40
42
  const toolkit = meta.toolkit ?? "max";
@@ -1,13 +1,17 @@
1
- import { Config } from "./config.ts";
1
+ import { Config, type NamedMcpTransport } from "./config.ts";
2
2
  import { Manager } from "./manager.ts";
3
3
 
4
4
  export { Config, Manager };
5
+ export type { NamedMcpTransport };
5
6
  export { createMcpTools } from "./tools.ts";
6
7
 
7
8
  export function createMcpConfig(path: string): Config {
8
9
  return new Config(path);
9
10
  }
10
11
 
11
- export function createMcpManager(config: Config): Manager {
12
- return new Manager(config);
12
+ export function createMcpManager(
13
+ config: Config,
14
+ mcpServers: readonly NamedMcpTransport[] = [],
15
+ ): Manager {
16
+ return new Manager(config, mcpServers);
13
17
  }
@@ -4,7 +4,7 @@ import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js"
4
4
  import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
5
5
  import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
6
6
  import type { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
7
- import { Config } from "./config.ts";
7
+ import { Config, type NamedMcpTransport } from "./config.ts";
8
8
  import type { McpTransport } from "./types.ts";
9
9
 
10
10
  function transportFor(spec: McpTransport): Transport {
@@ -43,7 +43,10 @@ function transportFor(spec: McpTransport): Transport {
43
43
  export class Manager {
44
44
  private instances: Map<string, McpClient> | null = null;
45
45
 
46
- public constructor(private readonly config: Config) {}
46
+ public constructor(
47
+ private readonly config: Config,
48
+ private readonly mcpServers: readonly NamedMcpTransport[] = [],
49
+ ) {}
47
50
 
48
51
  /** Lazily builds clients from the current in-memory config (reloads file first). */
49
52
  get clients(): ReadonlyMap<string, McpClient> {
@@ -61,7 +64,12 @@ export class Manager {
61
64
  this.config.reload();
62
65
  const previous = this.instances;
63
66
  const next = new Map<string, McpClient>();
64
- for (const { name, transport } of this.config.list()) {
67
+ const transports = [
68
+ ...this.config.list(),
69
+ // Session-scoped ACP servers intentionally override local config names.
70
+ ...this.mcpServers,
71
+ ];
72
+ for (const { name, transport } of transports) {
65
73
  next.set(
66
74
  name,
67
75
  new McpClient({