@pellux/goodvibes-sdk 0.33.26 → 0.33.27

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.
@@ -1,67 +1,186 @@
1
1
  /**
2
2
  * MCP server configuration — scans multiple locations in precedence order.
3
3
  */
4
- import { existsSync, readFileSync } from 'fs';
5
- import { join } from 'path';
4
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
5
+ import { dirname, join } from 'path';
6
6
  import { logger } from '../utils/logger.js';
7
7
  import { summarizeError } from '../utils/error-display.js';
8
+ function isRecord(value) {
9
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
10
+ }
11
+ function optionalStringArray(value) {
12
+ return Array.isArray(value) && value.every((entry) => typeof entry === 'string')
13
+ ? [...value]
14
+ : undefined;
15
+ }
16
+ function optionalStringRecord(value) {
17
+ if (!isRecord(value))
18
+ return undefined;
19
+ const entries = Object.entries(value).filter((entry) => typeof entry[1] === 'string');
20
+ return entries.length > 0 ? Object.fromEntries(entries) : undefined;
21
+ }
22
+ function normalizeServerConfig(name, raw) {
23
+ if (typeof raw.command !== 'string' || !raw.command.trim())
24
+ return null;
25
+ return {
26
+ name,
27
+ command: raw.command,
28
+ args: optionalStringArray(raw.args) ?? [],
29
+ env: optionalStringRecord(raw.env),
30
+ role: typeof raw.role === 'string' ? raw.role : undefined,
31
+ trustMode: typeof raw.trustMode === 'string' ? raw.trustMode : undefined,
32
+ allowedPaths: optionalStringArray(raw.allowedPaths),
33
+ allowedHosts: optionalStringArray(raw.allowedHosts),
34
+ };
35
+ }
36
+ export function getMcpConfigLocations(roots) {
37
+ const cwd = roots.workingDirectory;
38
+ const home = roots.homeDirectory;
39
+ return [
40
+ {
41
+ scope: 'global',
42
+ kind: 'global-xdg',
43
+ path: join(home, '.config', 'mcp', 'mcp.json'),
44
+ writable: true,
45
+ },
46
+ {
47
+ scope: 'external',
48
+ kind: 'global-dotdir',
49
+ path: join(home, '.mcp', 'mcp.json'),
50
+ writable: false,
51
+ },
52
+ {
53
+ scope: 'external',
54
+ kind: 'claude-desktop',
55
+ path: join(home, '.config', 'claude', 'claude_desktop_config.json'),
56
+ writable: false,
57
+ },
58
+ {
59
+ scope: 'external',
60
+ kind: 'project-mcp',
61
+ path: join(cwd, '.mcp', 'mcp.json'),
62
+ writable: false,
63
+ },
64
+ {
65
+ scope: 'project',
66
+ kind: 'project-goodvibes',
67
+ path: join(cwd, '.goodvibes', 'mcp.json'),
68
+ writable: true,
69
+ },
70
+ ];
71
+ }
72
+ function writableMcpConfigLocation(roots, scope) {
73
+ const location = getMcpConfigLocations(roots).find((entry) => entry.scope === scope && entry.writable);
74
+ if (!location)
75
+ throw new Error(`No writable MCP config location is available for scope '${scope}'.`);
76
+ return location;
77
+ }
78
+ function parseMcpServers(raw) {
79
+ if (!isRecord(raw))
80
+ return null;
81
+ if (isRecord(raw.mcpServers)) {
82
+ const servers = [];
83
+ for (const [name, value] of Object.entries(raw.mcpServers)) {
84
+ if (!isRecord(value))
85
+ continue;
86
+ const server = normalizeServerConfig(name, value);
87
+ if (server)
88
+ servers.push(server);
89
+ }
90
+ return servers;
91
+ }
92
+ if (isMcpConfig(raw))
93
+ return raw.servers.map((server) => ({ ...server }));
94
+ return null;
95
+ }
96
+ function readMcpConfigAtPath(path) {
97
+ if (!existsSync(path))
98
+ return { servers: [] };
99
+ const raw = JSON.parse(readFileSync(path, 'utf-8'));
100
+ const servers = parseMcpServers(raw);
101
+ return { servers: servers ?? [] };
102
+ }
103
+ function assertServerConfig(server) {
104
+ if (!server.name.trim())
105
+ throw new Error('MCP server name is required.');
106
+ if (server.name.includes(':') || server.name.includes('/')) {
107
+ throw new Error('MCP server name may not contain ":" or "/".');
108
+ }
109
+ if (!server.command.trim())
110
+ throw new Error('MCP server command is required.');
111
+ }
112
+ function writeMcpConfigFile(path, config) {
113
+ mkdirSync(dirname(path), { recursive: true });
114
+ const normalized = {
115
+ servers: config.servers.map((server) => {
116
+ assertServerConfig(server);
117
+ return {
118
+ name: server.name,
119
+ command: server.command,
120
+ ...(server.args !== undefined ? { args: [...server.args] } : {}),
121
+ ...(server.env !== undefined ? { env: { ...server.env } } : {}),
122
+ ...(server.role !== undefined ? { role: server.role } : {}),
123
+ ...(server.trustMode !== undefined ? { trustMode: server.trustMode } : {}),
124
+ ...(server.allowedPaths !== undefined ? { allowedPaths: [...server.allowedPaths] } : {}),
125
+ ...(server.allowedHosts !== undefined ? { allowedHosts: [...server.allowedHosts] } : {}),
126
+ };
127
+ }),
128
+ };
129
+ writeFileSync(path, `${JSON.stringify(normalized, null, 2)}\n`);
130
+ }
8
131
  /**
9
132
  * loadMcpConfig - Scan multiple locations in precedence order (later wins).
10
133
  * Returns merged config from all found files. Returns empty config on failure.
11
134
  */
12
135
  export function loadMcpConfig(roots) {
13
- const cwd = roots.workingDirectory;
14
- const home = roots.homeDirectory;
15
- // Scan locations in precedence order (later wins)
16
- const locations = [
17
- join(home, '.config', 'mcp', 'mcp.json'), // global XDG
18
- join(home, '.mcp', 'mcp.json'), // global dotdir
19
- join(home, '.config', 'claude', 'claude_desktop_config.json'), // Claude Desktop
20
- join(cwd, '.mcp', 'mcp.json'), // project-local
21
- join(cwd, '.goodvibes', 'mcp.json'), // goodvibes project
22
- ];
23
- const merged = { servers: [] };
136
+ return { servers: loadMcpEffectiveConfig(roots).servers.map((entry) => entry.server) };
137
+ }
138
+ export function loadMcpEffectiveConfig(roots) {
139
+ const locations = getMcpConfigLocations(roots);
24
140
  const serversByName = new Map();
25
- for (const path of locations) {
141
+ for (const location of locations) {
26
142
  try {
27
- if (!existsSync(path))
28
- continue;
29
- const raw = JSON.parse(readFileSync(path, 'utf-8'));
30
- // Handle Claude Desktop format
31
- if (typeof raw === 'object' && raw !== null && 'mcpServers' in raw) {
32
- const obj = raw;
33
- if (typeof obj['mcpServers'] === 'object' && obj['mcpServers'] !== null) {
34
- for (const [name, srv] of Object.entries(obj['mcpServers'])) {
35
- const s = srv;
36
- if (typeof s.command === 'string') {
37
- serversByName.set(name, {
38
- name,
39
- command: s.command,
40
- args: Array.isArray(s.args) ? s.args.filter((a) => typeof a === 'string') : [],
41
- env: typeof s.env === 'object' && s.env ? Object.fromEntries(Object.entries(s.env).filter(([, v]) => typeof v === 'string')) : undefined,
42
- role: typeof s.role === 'string' ? s.role : undefined,
43
- trustMode: typeof s.trustMode === 'string' ? s.trustMode : undefined,
44
- allowedPaths: Array.isArray(s.allowedPaths) ? s.allowedPaths.filter((v) => typeof v === 'string') : undefined,
45
- allowedHosts: Array.isArray(s.allowedHosts) ? s.allowedHosts.filter((v) => typeof v === 'string') : undefined,
46
- });
47
- }
48
- }
49
- }
143
+ if (!existsSync(location.path))
50
144
  continue;
51
- }
52
- // Handle goodvibes format
53
- if (isMcpConfig(raw)) {
54
- for (const srv of raw.servers) {
55
- serversByName.set(srv.name, srv);
56
- }
145
+ const config = readMcpConfigAtPath(location.path);
146
+ for (const server of config.servers) {
147
+ serversByName.set(server.name, { server, source: location });
57
148
  }
58
149
  }
59
150
  catch (err) {
60
- logger.warn(`[MCP] Failed to read ${path}`, { error: summarizeError(err) });
151
+ logger.warn(`[MCP] Failed to read ${location.path}`, { error: summarizeError(err) });
61
152
  }
62
153
  }
63
- merged.servers = [...serversByName.values()];
64
- return merged;
154
+ return {
155
+ servers: [...serversByName.values()],
156
+ locations,
157
+ };
158
+ }
159
+ export function loadWritableMcpConfig(roots, scope) {
160
+ const location = writableMcpConfigLocation(roots, scope);
161
+ return readMcpConfigAtPath(location.path);
162
+ }
163
+ export function upsertMcpServerConfig(roots, scope, server) {
164
+ assertServerConfig(server);
165
+ const location = writableMcpConfigLocation(roots, scope);
166
+ const current = readMcpConfigAtPath(location.path);
167
+ const nextServers = current.servers.filter((entry) => entry.name !== server.name);
168
+ nextServers.push({ ...server });
169
+ nextServers.sort((a, b) => a.name.localeCompare(b.name));
170
+ const config = { servers: nextServers };
171
+ writeMcpConfigFile(location.path, config);
172
+ return { path: location.path, config };
173
+ }
174
+ export function removeMcpServerConfig(roots, scope, serverName) {
175
+ if (!serverName.trim())
176
+ throw new Error('MCP server name is required.');
177
+ const location = writableMcpConfigLocation(roots, scope);
178
+ const current = readMcpConfigAtPath(location.path);
179
+ const config = { servers: current.servers.filter((entry) => entry.name !== serverName) };
180
+ const removed = config.servers.length !== current.servers.length;
181
+ if (removed || existsSync(location.path))
182
+ writeMcpConfigFile(location.path, config);
183
+ return { path: location.path, removed, config };
65
184
  }
66
185
  /** Type guard for McpConfig (goodvibes format) */
67
186
  function isMcpConfig(v) {
@@ -1,8 +1,8 @@
1
1
  export { McpRegistry } from './registry.js';
2
- export type { RegisteredTool } from './registry.js';
2
+ export type { McpReloadResult, McpReloadServerResult, RegisteredTool } from './registry.js';
3
3
  export { McpClient } from './client.js';
4
4
  export { createMcpApi } from './mcp-api.js';
5
5
  export type { McpApi, McpApiRegistry, McpSandboxBindingRecord, McpServerRecord, McpServerSecurityRecord, } from './mcp-api.js';
6
- export { loadMcpConfig } from './config.js';
7
- export type { McpConfig, McpConfigRoots, McpServerConfig } from './config.js';
6
+ export { getMcpConfigLocations, loadMcpConfig, loadMcpEffectiveConfig, loadWritableMcpConfig, removeMcpServerConfig, upsertMcpServerConfig, } from './config.js';
7
+ export type { McpConfig, McpConfigLocation, McpConfigRoots, McpConfigScope, McpEffectiveConfig, McpServerConfig, McpServerConfigEntry, } from './config.js';
8
8
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/platform/mcp/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,YAAY,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AACpD,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,YAAY,EACV,MAAM,EACN,cAAc,EACd,uBAAuB,EACvB,eAAe,EACf,uBAAuB,GACxB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,YAAY,EAAE,SAAS,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/platform/mcp/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,YAAY,EAAE,eAAe,EAAE,qBAAqB,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC5F,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,YAAY,EACV,MAAM,EACN,cAAc,EACd,uBAAuB,EACvB,eAAe,EACf,uBAAuB,GACxB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,qBAAqB,EACrB,aAAa,EACb,sBAAsB,EACtB,qBAAqB,EACrB,qBAAqB,EACrB,qBAAqB,GACtB,MAAM,aAAa,CAAC;AACrB,YAAY,EACV,SAAS,EACT,iBAAiB,EACjB,cAAc,EACd,cAAc,EACd,kBAAkB,EAClB,eAAe,EACf,oBAAoB,GACrB,MAAM,aAAa,CAAC"}
@@ -1,4 +1,4 @@
1
1
  export { McpRegistry } from './registry.js';
2
2
  export { McpClient } from './client.js';
3
3
  export { createMcpApi } from './mcp-api.js';
4
- export { loadMcpConfig } from './config.js';
4
+ export { getMcpConfigLocations, loadMcpConfig, loadMcpEffectiveConfig, loadWritableMcpConfig, removeMcpServerConfig, upsertMcpServerConfig, } from './config.js';
@@ -1,4 +1,6 @@
1
1
  import type { RegisteredTool } from './registry.js';
2
+ import type { McpConfigRoots, McpConfigScope, McpEffectiveConfig, McpServerConfig } from './config.js';
3
+ import type { McpReloadResult } from './registry.js';
2
4
  import type { McpDecisionRecord, McpServerRole, McpTrustMode, QuarantineReason, SchemaFreshness } from '../runtime/mcp/types.js';
3
5
  export interface McpServerRecord {
4
6
  readonly name: string;
@@ -25,6 +27,17 @@ export interface McpSandboxBindingRecord {
25
27
  readonly startupStatus?: 'verified' | 'planned' | 'failed' | undefined;
26
28
  }
27
29
  export interface McpApi {
30
+ getEffectiveConfig(roots: McpConfigRoots): McpEffectiveConfig;
31
+ reload(roots: McpConfigRoots): Promise<McpReloadResult>;
32
+ upsertServerConfig(roots: McpConfigRoots, scope: McpConfigScope, serverConfig: McpServerConfig): Promise<{
33
+ readonly path: string;
34
+ readonly reload: McpReloadResult;
35
+ }>;
36
+ removeServerConfig(roots: McpConfigRoots, scope: McpConfigScope, serverName: string): Promise<{
37
+ readonly path: string;
38
+ readonly removed: boolean;
39
+ readonly reload: McpReloadResult;
40
+ }>;
28
41
  listServerNames(): readonly string[];
29
42
  listServers(): readonly McpServerRecord[];
30
43
  listServerSecurity(): readonly McpServerSecurityRecord[];
@@ -38,6 +51,17 @@ export interface McpApi {
38
51
  }
39
52
  export interface McpApiRegistry {
40
53
  readonly serverNames: readonly string[];
54
+ getEffectiveConfig(roots: McpConfigRoots): McpEffectiveConfig;
55
+ reload(roots: McpConfigRoots): Promise<McpReloadResult>;
56
+ upsertServerConfig(roots: McpConfigRoots, scope: McpConfigScope, serverConfig: McpServerConfig): Promise<{
57
+ readonly path: string;
58
+ readonly reload: McpReloadResult;
59
+ }>;
60
+ removeServerConfig(roots: McpConfigRoots, scope: McpConfigScope, serverName: string): Promise<{
61
+ readonly path: string;
62
+ readonly removed: boolean;
63
+ readonly reload: McpReloadResult;
64
+ }>;
41
65
  listServers(): readonly McpServerRecord[];
42
66
  listServerSecurity(): readonly McpServerSecurityRecord[];
43
67
  listServerSandboxBindings(): readonly McpSandboxBindingRecord[];
@@ -1 +1 @@
1
- {"version":3,"file":"mcp-api.d.ts","sourceRoot":"","sources":["../../../src/platform/mcp/mcp-api.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"mcp-api.d.ts","sourceRoot":"","sources":["../../../src/platform/mcp/mcp-api.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,17 @@
1
1
  export function createMcpApi(registry) {
2
2
  return {
3
+ getEffectiveConfig(roots) {
4
+ return registry.getEffectiveConfig(roots);
5
+ },
6
+ reload(roots) {
7
+ return registry.reload(roots);
8
+ },
9
+ upsertServerConfig(roots, scope, serverConfig) {
10
+ return registry.upsertServerConfig(roots, scope, serverConfig);
11
+ },
12
+ removeServerConfig(roots, scope, serverName) {
13
+ return registry.removeServerConfig(roots, scope, serverName);
14
+ },
3
15
  listServerNames() {
4
16
  return registry.serverNames;
5
17
  },
@@ -1,11 +1,10 @@
1
1
  import { McpClient } from './client.js';
2
2
  import type { McpToolSchema } from './client.js';
3
- import type { McpServerConfig } from './config.js';
3
+ import type { McpConfigRoots, McpConfigScope, McpEffectiveConfig, McpServerConfig } from './config.js';
4
4
  import type { HookDispatcher } from '../hooks/dispatcher.js';
5
5
  import type { McpDecisionRecord, QuarantineReason, SchemaFreshness } from '../runtime/mcp/types.js';
6
6
  import type { RuntimeEventBus } from '../runtime/events/index.js';
7
7
  import type { ConfigManager } from '../config/manager.js';
8
- import type { McpConfigRoots } from './config.js';
9
8
  import { type SandboxSessionRegistry } from '../runtime/sandbox/session-registry.js';
10
9
  export interface RegisteredTool {
11
10
  /** Fully-qualified tool name: mcp:<server>:<tool> */
@@ -14,8 +13,21 @@ export interface RegisteredTool {
14
13
  toolName: string;
15
14
  description: string;
16
15
  }
16
+ export interface McpReloadServerResult {
17
+ readonly name: string;
18
+ readonly action: 'added' | 'changed' | 'removed' | 'unchanged';
19
+ readonly connected: boolean;
20
+ }
21
+ export interface McpReloadResult {
22
+ readonly added: number;
23
+ readonly changed: number;
24
+ readonly removed: number;
25
+ readonly unchanged: number;
26
+ readonly servers: readonly McpReloadServerResult[];
27
+ }
17
28
  export declare class McpRegistry {
18
29
  private clients;
30
+ private serverConfigs;
19
31
  private permissions;
20
32
  private freshness;
21
33
  private runtimeBus;
@@ -39,6 +51,18 @@ export declare class McpRegistry {
39
51
  * Exposed for programmatic use (testing, dynamic registration).
40
52
  */
41
53
  connectServer(serverConfig: McpServerConfig): Promise<void>;
54
+ reload(roots: McpConfigRoots): Promise<McpReloadResult>;
55
+ getEffectiveConfig(roots: McpConfigRoots): McpEffectiveConfig;
56
+ upsertServerConfig(roots: McpConfigRoots, scope: McpConfigScope, serverConfig: McpServerConfig): Promise<{
57
+ readonly path: string;
58
+ readonly reload: McpReloadResult;
59
+ }>;
60
+ removeServerConfig(roots: McpConfigRoots, scope: McpConfigScope, serverName: string): Promise<{
61
+ readonly path: string;
62
+ readonly removed: boolean;
63
+ readonly reload: McpReloadResult;
64
+ }>;
65
+ applyConfig(serverConfigs: readonly McpServerConfig[]): Promise<McpReloadResult>;
42
66
  /**
43
67
  * listAllTools — Return all registered tools (name + description) from all connected servers.
44
68
  * Only loads tool names and descriptions — full schemas are NOT fetched here.
@@ -58,6 +82,7 @@ export declare class McpRegistry {
58
82
  * disconnectAll — Stop all connected MCP server processes.
59
83
  */
60
84
  disconnectAll(): Promise<void>;
85
+ disconnectServer(serverName: string, reason?: string): Promise<boolean>;
61
86
  /**
62
87
  * getClient — Get the McpClient for a given server name (for advanced use).
63
88
  */
@@ -1 +1 @@
1
- {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../../src/platform/mcp/registry.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../../src/platform/mcp/registry.ts"],"names":[],"mappings":"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"}
@@ -8,11 +8,11 @@
8
8
  * Tool namespace: mcp:<server-name>:<tool-name>
9
9
  */
10
10
  import { logger } from '../utils/logger.js';
11
- import { loadMcpConfig } from './config.js';
11
+ import { loadMcpEffectiveConfig, removeMcpServerConfig, upsertMcpServerConfig, } from './config.js';
12
12
  import { McpClient } from './client.js';
13
13
  import { McpPermissionManager } from '../runtime/mcp/permissions.js';
14
14
  import { McpSchemaFreshnessTracker } from '../runtime/mcp/schema-freshness.js';
15
- import { emitMcpConfigured, emitMcpPolicyUpdated, emitMcpSchemaQuarantineApproved, emitMcpSchemaQuarantined, } from '../runtime/emitters/mcp.js';
15
+ import { emitMcpConfigured, emitMcpDisconnected, emitMcpPolicyUpdated, emitMcpSchemaQuarantineApproved, emitMcpSchemaQuarantined, } from '../runtime/emitters/mcp.js';
16
16
  import { getSandboxConfigSnapshot } from '../runtime/sandbox/manager.js';
17
17
  import {} from '../runtime/sandbox/session-registry.js';
18
18
  import { resolveSandboxCommandPlan } from '../runtime/sandbox/backend.js';
@@ -20,8 +20,12 @@ import { summarizeError } from '../utils/error-display.js';
20
20
  function compactEnv(env) {
21
21
  return Object.fromEntries(Object.entries(env).filter((entry) => typeof entry[1] === 'string'));
22
22
  }
23
+ function sameServerConfig(a, b) {
24
+ return JSON.stringify(a ?? null) === JSON.stringify(b ?? null);
25
+ }
23
26
  export class McpRegistry {
24
27
  clients = new Map();
28
+ serverConfigs = new Map();
25
29
  permissions = new McpPermissionManager();
26
30
  freshness = new McpSchemaFreshnessTracker();
27
31
  runtimeBus = null;
@@ -45,16 +49,74 @@ export class McpRegistry {
45
49
  * Errors on individual servers are logged but do not abort the whole startup.
46
50
  */
47
51
  async connectAll(roots) {
48
- const mcpConfig = loadMcpConfig(roots);
49
- await Promise.allSettled(mcpConfig.servers.map((serverConfig) => this._connectServer(serverConfig)));
52
+ await this.reload(roots);
50
53
  }
51
54
  /**
52
55
  * connectServer — Connect a single MCP server by config.
53
56
  * Exposed for programmatic use (testing, dynamic registration).
54
57
  */
55
58
  async connectServer(serverConfig) {
59
+ this.serverConfigs.set(serverConfig.name, serverConfig);
56
60
  await this._connectServer(serverConfig);
57
61
  }
62
+ async reload(roots) {
63
+ const effective = loadMcpEffectiveConfig(roots);
64
+ return this.applyConfig(effective.servers.map((entry) => entry.server));
65
+ }
66
+ getEffectiveConfig(roots) {
67
+ return loadMcpEffectiveConfig(roots);
68
+ }
69
+ async upsertServerConfig(roots, scope, serverConfig) {
70
+ const written = upsertMcpServerConfig(roots, scope, serverConfig);
71
+ return { path: written.path, reload: await this.reload(roots) };
72
+ }
73
+ async removeServerConfig(roots, scope, serverName) {
74
+ const written = removeMcpServerConfig(roots, scope, serverName);
75
+ return { path: written.path, removed: written.removed, reload: await this.reload(roots) };
76
+ }
77
+ async applyConfig(serverConfigs) {
78
+ const next = new Map(serverConfigs.map((serverConfig) => [serverConfig.name, serverConfig]));
79
+ const results = [];
80
+ let added = 0;
81
+ let changed = 0;
82
+ let removed = 0;
83
+ let unchanged = 0;
84
+ for (const name of [...this.serverConfigs.keys()]) {
85
+ if (next.has(name))
86
+ continue;
87
+ await this.disconnectServer(name, 'config-removed');
88
+ this.serverConfigs.delete(name);
89
+ removed += 1;
90
+ results.push({ name, action: 'removed', connected: false });
91
+ }
92
+ for (const [name, serverConfig] of next) {
93
+ const previous = this.serverConfigs.get(name);
94
+ if (previous && sameServerConfig(previous, serverConfig)) {
95
+ unchanged += 1;
96
+ const client = this.clients.get(name);
97
+ if (!client?.isConnected) {
98
+ await this._connectServer(serverConfig);
99
+ }
100
+ results.push({ name, action: 'unchanged', connected: this.clients.get(name)?.isConnected ?? false });
101
+ continue;
102
+ }
103
+ if (previous) {
104
+ await this.disconnectServer(name, 'config-changed');
105
+ changed += 1;
106
+ }
107
+ else {
108
+ added += 1;
109
+ }
110
+ this.serverConfigs.set(name, serverConfig);
111
+ await this._connectServer(serverConfig);
112
+ results.push({
113
+ name,
114
+ action: previous ? 'changed' : 'added',
115
+ connected: this.clients.get(name)?.isConnected ?? false,
116
+ });
117
+ }
118
+ return { added, changed, removed, unchanged, servers: results };
119
+ }
58
120
  /**
59
121
  * listAllTools — Return all registered tools (name + description) from all connected servers.
60
122
  * Only loads tool names and descriptions — full schemas are NOT fetched here.
@@ -194,6 +256,38 @@ export class McpRegistry {
194
256
  }
195
257
  this.sandboxSessionByServer.clear();
196
258
  }
259
+ async disconnectServer(serverName, reason = 'manual') {
260
+ const client = this.clients.get(serverName);
261
+ if (!client)
262
+ return false;
263
+ await client.disconnect();
264
+ this.clients.delete(serverName);
265
+ const sessionId = this.sandboxSessionByServer.get(serverName);
266
+ if (sessionId) {
267
+ this.sandboxSessions.stop(sessionId);
268
+ this.sandboxSessionByServer.delete(serverName);
269
+ }
270
+ if (this.runtimeBus) {
271
+ emitMcpDisconnected(this.runtimeBus, {
272
+ sessionId: 'mcp-registry',
273
+ traceId: `mcp-registry:${serverName}:disconnected`,
274
+ source: 'mcp-registry',
275
+ }, { serverId: serverName, reason, willRetry: false });
276
+ }
277
+ const disconnectedEvent = {
278
+ path: 'Lifecycle:mcp:disconnected',
279
+ phase: 'Lifecycle',
280
+ category: 'mcp',
281
+ specific: 'disconnected',
282
+ sessionId: '',
283
+ timestamp: Date.now(),
284
+ payload: { server: serverName, reason },
285
+ };
286
+ this.hookDispatcher.fire(disconnectedEvent).catch((err) => {
287
+ logger.warn('Lifecycle:mcp:disconnected hook error', { error: summarizeError(err) });
288
+ });
289
+ return true;
290
+ }
197
291
  /**
198
292
  * getClient — Get the McpClient for a given server name (for advanced use).
199
293
  */
@@ -202,15 +296,15 @@ export class McpRegistry {
202
296
  }
203
297
  /** Connected server names. */
204
298
  get serverNames() {
205
- return Array.from(this.clients.keys());
299
+ return Array.from(new Set([...this.serverConfigs.keys(), ...this.clients.keys()])).sort();
206
300
  }
207
301
  /**
208
302
  * listServers — Return status info for all known servers (connected or not).
209
303
  */
210
304
  listServers() {
211
- return Array.from(this.clients.entries()).map(([name, client]) => ({
305
+ return this.serverNames.map((name) => ({
212
306
  name,
213
- connected: client.isConnected,
307
+ connected: this.clients.get(name)?.isConnected ?? false,
214
308
  }));
215
309
  }
216
310
  listServerSecurity() {
@@ -283,10 +377,14 @@ export class McpRegistry {
283
377
  // ---------------------------------------------------------------------------
284
378
  async _connectServer(serverConfig) {
285
379
  const { name } = serverConfig;
286
- if (this.clients.has(name)) {
380
+ const existing = this.clients.get(name);
381
+ if (existing?.isConnected) {
287
382
  logger.info('McpRegistry: server already registered', { name });
288
383
  return;
289
384
  }
385
+ if (existing) {
386
+ await this.disconnectServer(name, 'reconnect');
387
+ }
290
388
  let sandboxSessionId = null;
291
389
  let processSpec;
292
390
  if (this.sandboxConfigManager) {
@@ -1,6 +1,6 @@
1
1
  import { readFileSync } from 'node:fs';
2
2
  import { join } from 'node:path';
3
- let version = '0.33.26';
3
+ let version = '0.33.27';
4
4
  try {
5
5
  const pkg = JSON.parse(readFileSync(join(import.meta.dir, '..', '..', 'package.json'), 'utf-8'));
6
6
  version = pkg.version ?? version;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pellux/goodvibes-sdk",
3
- "version": "0.33.26",
3
+ "version": "0.33.27",
4
4
  "description": "TypeScript SDK for building GoodVibes operator, peer, web, mobile, and daemon-connected apps with typed contracts, auth, realtime events, and transport layers.",
5
5
  "keywords": [
6
6
  "goodvibes",
@@ -449,14 +449,14 @@
449
449
  "sideEffects": false,
450
450
  "type": "module",
451
451
  "dependencies": {
452
- "@pellux/goodvibes-contracts": "0.33.26",
453
- "@pellux/goodvibes-daemon-sdk": "0.33.26",
454
- "@pellux/goodvibes-errors": "0.33.26",
455
- "@pellux/goodvibes-operator-sdk": "0.33.26",
456
- "@pellux/goodvibes-peer-sdk": "0.33.26",
457
- "@pellux/goodvibes-transport-core": "0.33.26",
458
- "@pellux/goodvibes-transport-http": "0.33.26",
459
- "@pellux/goodvibes-transport-realtime": "0.33.26"
452
+ "@pellux/goodvibes-contracts": "0.33.27",
453
+ "@pellux/goodvibes-daemon-sdk": "0.33.27",
454
+ "@pellux/goodvibes-errors": "0.33.27",
455
+ "@pellux/goodvibes-operator-sdk": "0.33.27",
456
+ "@pellux/goodvibes-peer-sdk": "0.33.27",
457
+ "@pellux/goodvibes-transport-core": "0.33.27",
458
+ "@pellux/goodvibes-transport-http": "0.33.27",
459
+ "@pellux/goodvibes-transport-realtime": "0.33.27"
460
460
  },
461
461
  "optionalDependencies": {
462
462
  "@agentclientprotocol/sdk": "^0.21.0",