@toolplex/client 0.1.18 → 0.1.19

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 (38) hide show
  1. package/dist/mcp-server/index.js +10 -0
  2. package/dist/mcp-server/registry.d.ts +17 -0
  3. package/dist/mcp-server/registry.js +23 -0
  4. package/dist/mcp-server/toolHandlers/initHandler.d.ts +9 -0
  5. package/dist/mcp-server/toolHandlers/initHandler.js +46 -1
  6. package/dist/mcp-server/toolplexServer.js +5 -0
  7. package/dist/mcp-server/utils/runtimeCheck.d.ts +18 -0
  8. package/dist/mcp-server/utils/runtimeCheck.js +70 -6
  9. package/dist/server-manager/serverManager.js +3 -9
  10. package/dist/shared/enhancedPath.js +3 -3
  11. package/dist/shared/mcpServerTypes.d.ts +12 -0
  12. package/dist/src/mcp-server/clientContext.js +118 -0
  13. package/dist/src/mcp-server/logging/telemetryLogger.js +54 -0
  14. package/dist/src/mcp-server/policy/callToolObserver.js +27 -0
  15. package/dist/src/mcp-server/policy/feedbackPolicy.js +39 -0
  16. package/dist/src/mcp-server/policy/installObserver.js +35 -0
  17. package/dist/src/mcp-server/policy/playbookPolicy.js +81 -0
  18. package/dist/src/mcp-server/policy/policyEnforcer.js +105 -0
  19. package/dist/src/mcp-server/policy/serverPolicy.js +63 -0
  20. package/dist/src/mcp-server/promptsCache.js +51 -0
  21. package/dist/src/mcp-server/registry.js +134 -0
  22. package/dist/src/mcp-server/serversCache.js +100 -0
  23. package/dist/src/mcp-server/toolDefinitionsCache.js +67 -0
  24. package/dist/src/mcp-server/toolHandlers/initHandler.js +185 -0
  25. package/dist/src/mcp-server/toolplexApi/service.js +221 -0
  26. package/dist/src/mcp-server/toolplexApi/types.js +1 -0
  27. package/dist/src/mcp-server/utils/initServerManagers.js +31 -0
  28. package/dist/src/mcp-server/utils/runtimeCheck.js +94 -0
  29. package/dist/src/shared/enhancedPath.js +52 -0
  30. package/dist/src/shared/fileLogger.js +66 -0
  31. package/dist/src/shared/mcpServerTypes.js +158 -0
  32. package/dist/src/shared/serverManagerTypes.js +73 -0
  33. package/dist/src/shared/stdioServerManagerClient.js +98 -0
  34. package/dist/tests/unit/bundledDependencies.test.js +152 -0
  35. package/dist/tests/unit/registry.test.js +216 -0
  36. package/dist/version.d.ts +1 -1
  37. package/dist/version.js +1 -1
  38. package/package.json +8 -3
@@ -0,0 +1,35 @@
1
+ class InstallObserver {
2
+ constructor() {
3
+ this.serverInstallActions = new Map();
4
+ }
5
+ // Record an install action on a server
6
+ recordInstall(serverId) {
7
+ this.recordAction(serverId, "install");
8
+ }
9
+ // Record an uninstall action on a server
10
+ recordUninstall(serverId) {
11
+ this.recordAction(serverId, "uninstall");
12
+ }
13
+ // Check if a server has been installed
14
+ wasServerInstalled(serverId) {
15
+ return (this.serverInstallActions.has(serverId) &&
16
+ this.serverInstallActions.get(serverId).has("install"));
17
+ }
18
+ // Check if a server has been uninstalled
19
+ wasServerUninstalled(serverId) {
20
+ return (this.serverInstallActions.has(serverId) &&
21
+ this.serverInstallActions.get(serverId).has("uninstall"));
22
+ }
23
+ // Optionally, clear all records (for testing or reset)
24
+ clear() {
25
+ this.serverInstallActions.clear();
26
+ }
27
+ // Private method to record an action
28
+ recordAction(serverId, action) {
29
+ if (!this.serverInstallActions.has(serverId)) {
30
+ this.serverInstallActions.set(serverId, new Set());
31
+ }
32
+ this.serverInstallActions.get(serverId).add(action);
33
+ }
34
+ }
35
+ export default InstallObserver;
@@ -0,0 +1,81 @@
1
+ import Registry from "../registry.js";
2
+ export class PlaybookPolicy {
3
+ constructor(clientContext, callToolObserver) {
4
+ this.callToolObserver = callToolObserver;
5
+ this.clientContext = clientContext;
6
+ this.blockedMcpServersSet = new Set(clientContext.flags.blocked_mcp_servers || []);
7
+ }
8
+ /**
9
+ * Validates a playbook before saving by checking that:
10
+ * - Referenced servers and tools have been used
11
+ * - No blocked servers are referenced
12
+ * - Private playbooks are only created when enabled
13
+ *
14
+ * For each action with a 'call' property, verifies that:
15
+ * - Any referenced server has been connected to
16
+ * - Any referenced server/tool combination has been executed
17
+ * - The server is not in the blocked servers list
18
+ *
19
+ * @throws Error if a referenced server or tool has not been used in the current session,
20
+ * if a blocked server is referenced, or if trying to create a private playbook when disabled
21
+ */
22
+ enforceSavePlaybookPolicy(playbook) {
23
+ if (!Array.isArray(playbook.actions)) {
24
+ throw new Error("Playbook actions must be an array");
25
+ }
26
+ for (const [idx, action] of playbook.actions.entries()) {
27
+ if (!action.call)
28
+ continue;
29
+ // Parse the call string
30
+ // Supported formats:
31
+ // - "mcp_server_id:<server_id>::<tool_name>"
32
+ // - "mcp_server_id:<server_id>"
33
+ // - "playbook_id:<playbook_id>"
34
+ const call = action.call.trim();
35
+ if (call.startsWith("mcp_server_id:")) {
36
+ // Could be with or without tool_name
37
+ // e.g. mcp_server_id:abc123::toolX or mcp_server_id:abc123
38
+ const match = call.match(/^mcp_server_id:([^:]+)(?:::([^:]+))?$/);
39
+ if (!match) {
40
+ throw new Error(`Invalid call format in action ${idx + 1}: "${call}"`);
41
+ }
42
+ const serverId = match[1];
43
+ const toolName = match[2];
44
+ // Check if server is blocked
45
+ if (this.blockedMcpServersSet.has(serverId)) {
46
+ throw new Error(`Playbook action ${idx + 1} references blocked server "${serverId}"`);
47
+ }
48
+ if (toolName) {
49
+ // Must have called this tool on this server
50
+ if (!this.callToolObserver.wasToolCalled(serverId, toolName)) {
51
+ throw new Error(`Playbook action ${idx + 1} references tool "${toolName}" on server "${serverId}" which has not been used in this session.`);
52
+ }
53
+ }
54
+ else {
55
+ // Only server referenced, must have called any tool on this server
56
+ if (!this.callToolObserver.wasServerCalled(serverId)) {
57
+ throw new Error(`Playbook action ${idx + 1} references server "${serverId}" which has not been used in this session.`);
58
+ }
59
+ }
60
+ }
61
+ else if (call.startsWith("playbook_id:")) {
62
+ // For playbook references, we could skip or add logic if needed
63
+ // For now, we do not validate playbook_id usage
64
+ continue;
65
+ }
66
+ else {
67
+ throw new Error(`Playbook action ${idx + 1} has an unrecognized call format: "${call}"`);
68
+ }
69
+ }
70
+ }
71
+ /**
72
+ * Validates if playbook usage logging is allowed based on read-only mode
73
+ * @throws Error if read-only mode is enabled
74
+ */
75
+ enforceLogPlaybookUsagePolicy() {
76
+ if (this.clientContext.permissions.enable_read_only_mode) {
77
+ const promptsCache = Registry.getPromptsCache();
78
+ throw new Error(promptsCache.getPrompt("log_playbook_usage_disabled"));
79
+ }
80
+ }
81
+ }
@@ -0,0 +1,105 @@
1
+ import { PlaybookPolicy } from "./playbookPolicy.js";
2
+ import { FeedbackPolicy } from "./feedbackPolicy.js";
3
+ import CallToolObserver from "./callToolObserver.js";
4
+ import InstallObserver from "./installObserver.js";
5
+ import { ServerPolicy } from "./serverPolicy.js";
6
+ export class PolicyEnforcer {
7
+ constructor() {
8
+ this.playbookPolicy = null;
9
+ this.feedbackPolicy = null;
10
+ this.serverPolicy = null;
11
+ this.callToolObserver = null;
12
+ this.installObserver = null;
13
+ }
14
+ /**
15
+ * Initialize the policy enforcer with the client context
16
+ */
17
+ init(clientContext) {
18
+ this.callToolObserver = new CallToolObserver();
19
+ this.installObserver = new InstallObserver();
20
+ this.playbookPolicy = new PlaybookPolicy(clientContext, this.callToolObserver);
21
+ this.feedbackPolicy = new FeedbackPolicy(clientContext, this.callToolObserver, this.installObserver);
22
+ this.serverPolicy = new ServerPolicy(clientContext);
23
+ }
24
+ /**
25
+ * Enforce playbook policy validation before saving.
26
+ * Throws if the playbook does not pass policy.
27
+ */
28
+ enforceSavePlaybookPolicy(playbook) {
29
+ if (!this.playbookPolicy) {
30
+ throw new Error("PolicyEnforcer not initialized");
31
+ }
32
+ this.playbookPolicy.enforceSavePlaybookPolicy(playbook);
33
+ }
34
+ /**
35
+ * Enforce feedback policy validation.
36
+ * Throws if the feedback does not pass policy.
37
+ */
38
+ enforceFeedbackPolicy(feedback) {
39
+ if (!this.feedbackPolicy) {
40
+ throw new Error("PolicyEnforcer not initialized");
41
+ }
42
+ this.feedbackPolicy.enforceFeedbackPolicy(feedback);
43
+ }
44
+ /**
45
+ * Enforce server call tool policy validation.
46
+ * Throws if attempting to call a tool on a blocked server.
47
+ */
48
+ enforceCallToolPolicy(serverId) {
49
+ if (!this.serverPolicy) {
50
+ throw new Error("PolicyEnforcer not initialized");
51
+ }
52
+ this.serverPolicy.enforceCallToolPolicy(serverId);
53
+ }
54
+ /**
55
+ * Enforce server config policy validation.
56
+ * Throws if attempting to use a blocked or disallowed server.
57
+ */
58
+ enforceUseServerPolicy(serverId) {
59
+ if (!this.serverPolicy) {
60
+ throw new Error("PolicyEnforcer not initialized");
61
+ }
62
+ this.serverPolicy.enforceUseServerPolicy(serverId);
63
+ }
64
+ /**
65
+ * Enforce playbook usage logging policy validation.
66
+ * Throws if read-only mode is enabled.
67
+ */
68
+ enforceLogPlaybookUsagePolicy() {
69
+ if (!this.playbookPolicy) {
70
+ throw new Error("PolicyEnforcer not initialized");
71
+ }
72
+ this.playbookPolicy.enforceLogPlaybookUsagePolicy();
73
+ }
74
+ /**
75
+ * Filters out blocked servers from a list of objects.
76
+ *
77
+ * @param servers List of objects containing server IDs
78
+ * @param getServerId Function that extracts the server ID from an object
79
+ * @returns Filtered list with blocked servers removed
80
+ */
81
+ filterBlockedMcpServers(servers, getServerId) {
82
+ if (!this.serverPolicy) {
83
+ throw new Error("PolicyEnforcer not initialized");
84
+ }
85
+ return this.serverPolicy.filterBlockedMcpServers(servers, getServerId);
86
+ }
87
+ /**
88
+ * Get a reference to the CallToolObserver instance.
89
+ */
90
+ getCallToolObserver() {
91
+ if (!this.callToolObserver) {
92
+ throw new Error("PolicyEnforcer not initialized");
93
+ }
94
+ return this.callToolObserver;
95
+ }
96
+ /**
97
+ * Get a reference to the InstallObserver instance.
98
+ */
99
+ getInstallObserver() {
100
+ if (!this.installObserver) {
101
+ throw new Error("PolicyEnforcer not initialized");
102
+ }
103
+ return this.installObserver;
104
+ }
105
+ }
@@ -0,0 +1,63 @@
1
+ export class ServerPolicy {
2
+ constructor(clientContext) {
3
+ this.clientContext = clientContext;
4
+ this.blockedMcpServersSet = new Set(clientContext.flags.blocked_mcp_servers || []);
5
+ }
6
+ /**
7
+ * Validates that a server is not blocked.
8
+ *
9
+ * @throws Error if attempting to use a blocked server
10
+ */
11
+ enforceBlockedServerPolicy(serverId) {
12
+ if (this.blockedMcpServersSet.has(serverId)) {
13
+ throw new Error(`Cannot use blocked server "${serverId}. Questions? Contact support@toolplex.ai"`);
14
+ }
15
+ }
16
+ /**
17
+ * Validates that a server is allowed.
18
+ *
19
+ * @throws Error if attempting to use a server not in the allowed list
20
+ */
21
+ enforceAllowedServerPolicy(serverId) {
22
+ const allowedServers = this.clientContext.permissions.allowed_mcp_servers;
23
+ if (allowedServers &&
24
+ allowedServers.length > 0 &&
25
+ !allowedServers.includes(serverId)) {
26
+ throw new Error(`Server "${serverId}" is not allowed for your account. Please adjust the Allowed MCP Servers permissions on the ToolPlex Dashboard if this is a mistake.`);
27
+ }
28
+ }
29
+ /**
30
+ * Validates that a server is not blocked before calling a tool on it.
31
+ * Also checks if desktop commander is enabled when calling tools on the desktop commander server.
32
+ *
33
+ * @throws Error if attempting to call a tool on a blocked server or if desktop commander is disabled
34
+ */
35
+ enforceCallToolPolicy(serverId) {
36
+ this.enforceBlockedServerPolicy(serverId);
37
+ this.enforceAllowedServerPolicy(serverId);
38
+ // Check if desktop commander is disabled and this is the desktop commander server
39
+ if (!this.clientContext.permissions.use_desktop_commander &&
40
+ serverId === this.clientContext.flags.desktop_commander_server_id) {
41
+ throw new Error("Desktop Commander is disabled for your account");
42
+ }
43
+ }
44
+ /**
45
+ * Validates that a server can be used.
46
+ *
47
+ * @throws Error if attempting to use a blocked or disallowed server
48
+ */
49
+ enforceUseServerPolicy(serverId) {
50
+ this.enforceBlockedServerPolicy(serverId);
51
+ this.enforceAllowedServerPolicy(serverId);
52
+ }
53
+ /**
54
+ * Filters out blocked servers from a list of objects.
55
+ *
56
+ * @param servers List of objects containing server IDs
57
+ * @param getServerId Function that extracts the server ID from an object
58
+ * @returns Filtered list with blocked servers removed
59
+ */
60
+ filterBlockedMcpServers(servers, getServerId) {
61
+ return servers.filter((server) => !this.blockedMcpServersSet.has(getServerId(server)));
62
+ }
63
+ }
@@ -0,0 +1,51 @@
1
+ export class PromptsCache {
2
+ constructor() {
3
+ this._prompts = null;
4
+ this._version = null;
5
+ this._prompts = null;
6
+ this._version = null;
7
+ }
8
+ /**
9
+ * Initialize prompts cache with prompts from init response
10
+ */
11
+ init(prompts) {
12
+ // Allow re-init.
13
+ this._prompts = prompts;
14
+ this._version = prompts._version;
15
+ }
16
+ /**
17
+ * Get a specific prompt by key from the cache
18
+ */
19
+ getPrompt(key) {
20
+ if (!this._prompts) {
21
+ throw new Error("PromptsCache not initialized");
22
+ }
23
+ const prompt = this._prompts[key];
24
+ if (!prompt) {
25
+ throw new Error(`Prompt "${key}" not found in cache`);
26
+ }
27
+ return prompt;
28
+ }
29
+ /**
30
+ * Get the version of the current prompts
31
+ */
32
+ getVersion() {
33
+ if (!this._version) {
34
+ throw new Error("PromptsCache not initialized");
35
+ }
36
+ return this._version;
37
+ }
38
+ /**
39
+ * Check if the cache is initialized
40
+ */
41
+ isInitialized() {
42
+ return this._prompts !== null;
43
+ }
44
+ /**
45
+ * Reset the cache
46
+ */
47
+ reset() {
48
+ this._prompts = null;
49
+ this._version = null;
50
+ }
51
+ }
@@ -0,0 +1,134 @@
1
+ import { ToolplexApiService } from "./toolplexApi/service.js";
2
+ import { TelemetryLogger } from "./logging/telemetryLogger.js";
3
+ import { PromptsCache } from "./promptsCache.js";
4
+ import { ToolDefinitionsCache } from "./toolDefinitionsCache.js";
5
+ import { ServersCache } from "./serversCache.js";
6
+ import { PolicyEnforcer } from "./policy/policyEnforcer.js";
7
+ /**
8
+ * In-memory global registry for the ToolPlex client.
9
+ * Maintains singleton instances of core services and clients used throughout the application.
10
+ */
11
+ class Registry {
12
+ static async init(clientContext) {
13
+ if (this._clientContext ||
14
+ this._toolplexApiService ||
15
+ this._serverManagerClients) {
16
+ throw new Error("Registry already initialized");
17
+ }
18
+ this._clientContext = clientContext;
19
+ this._toolplexApiService = new ToolplexApiService(clientContext);
20
+ this._serverManagerClients = {};
21
+ this._telemetryLogger = new TelemetryLogger();
22
+ this._promptsCache = new PromptsCache();
23
+ this._toolDefinitionsCache = new ToolDefinitionsCache();
24
+ this._serversCache = new ServersCache();
25
+ this._policyEnforcer = new PolicyEnforcer();
26
+ // Tool definitions must be initialized early to use tools like initialize_toolplex.
27
+ await this._toolDefinitionsCache.init(this._toolplexApiService, clientContext);
28
+ }
29
+ static getClientContext() {
30
+ if (!this._clientContext) {
31
+ throw new Error("ClientContext not initialized in Registry");
32
+ }
33
+ return this._clientContext;
34
+ }
35
+ static getToolplexApiService() {
36
+ if (!this._toolplexApiService) {
37
+ throw new Error("ToolplexApiService not initialized in Registry");
38
+ }
39
+ return this._toolplexApiService;
40
+ }
41
+ static getServerManagerClients() {
42
+ if (!this._serverManagerClients) {
43
+ throw new Error("ServerManagerClients not initialized in Registry");
44
+ }
45
+ return this._serverManagerClients;
46
+ }
47
+ static setServerManagerClients(clients) {
48
+ if (!this._serverManagerClients) {
49
+ throw new Error("Registry not initialized");
50
+ }
51
+ this._serverManagerClients = clients;
52
+ }
53
+ static getTelemetryLogger() {
54
+ if (!this._telemetryLogger) {
55
+ throw new Error("TelemetryLogger not initialized in Registry");
56
+ }
57
+ return this._telemetryLogger;
58
+ }
59
+ static getPromptsCache() {
60
+ if (!this._promptsCache) {
61
+ throw new Error("PromptsCache not initialized in Registry");
62
+ }
63
+ return this._promptsCache;
64
+ }
65
+ static getToolDefinitionsCache() {
66
+ if (!this._toolDefinitionsCache) {
67
+ throw new Error("ToolDefinitionsCache not initialized in Registry");
68
+ }
69
+ return this._toolDefinitionsCache;
70
+ }
71
+ static getServersCache() {
72
+ if (!this._serversCache) {
73
+ throw new Error("ServersCache not initialized in Registry");
74
+ }
75
+ return this._serversCache;
76
+ }
77
+ static getPolicyEnforcer() {
78
+ if (!this._policyEnforcer) {
79
+ throw new Error("PolicyEnforcer not initialized in Registry");
80
+ }
81
+ return this._policyEnforcer;
82
+ }
83
+ /**
84
+ * Set bundled dependencies (paths to Node, Python, Git, etc.)
85
+ * provided by the host application (e.g., Electron desktop app).
86
+ */
87
+ static setBundledDependencies(deps) {
88
+ this._bundledDependencies = deps;
89
+ }
90
+ /**
91
+ * Get bundled dependencies (paths to required executables).
92
+ * Returns a copy of the bundled dependencies object.
93
+ */
94
+ static getBundledDependencies() {
95
+ return { ...this._bundledDependencies };
96
+ }
97
+ /**
98
+ * Get the path for a specific bundled dependency by name.
99
+ * Returns undefined if the dependency is not available.
100
+ */
101
+ static getBundledDependencyPath(depName) {
102
+ return this._bundledDependencies[depName];
103
+ }
104
+ static reset() {
105
+ this._clientContext = null;
106
+ this._toolplexApiService = null;
107
+ this._serverManagerClients = null;
108
+ this._telemetryLogger = null;
109
+ if (this._promptsCache) {
110
+ this._promptsCache.reset();
111
+ this._promptsCache = null;
112
+ }
113
+ if (this._toolDefinitionsCache) {
114
+ this._toolDefinitionsCache.reset();
115
+ this._toolDefinitionsCache = null;
116
+ }
117
+ if (this._serversCache) {
118
+ this._serversCache.reset();
119
+ this._serversCache = null;
120
+ }
121
+ this._policyEnforcer = null;
122
+ this._bundledDependencies = {};
123
+ }
124
+ }
125
+ Registry._clientContext = null;
126
+ Registry._toolplexApiService = null;
127
+ Registry._serverManagerClients = null;
128
+ Registry._telemetryLogger = null;
129
+ Registry._promptsCache = null;
130
+ Registry._toolDefinitionsCache = null;
131
+ Registry._serversCache = null;
132
+ Registry._policyEnforcer = null;
133
+ Registry._bundledDependencies = {};
134
+ export default Registry;
@@ -0,0 +1,100 @@
1
+ import { ListServersResultSchema } from "../shared/serverManagerTypes.js";
2
+ import { FileLogger } from "../shared/fileLogger.js";
3
+ const logger = FileLogger;
4
+ /**
5
+ * An in-memory cache that tracks currently installed servers.
6
+ * Maintains a set of server IDs for quick lookup of installed servers.
7
+ * Can be refreshed by querying server manager clients for their current server lists.
8
+ */
9
+ export class ServersCache {
10
+ constructor() {
11
+ this._serverIds = null;
12
+ this._serverIds = null;
13
+ }
14
+ /**
15
+ * Initialize the cache with a list of servers, e.g. from initialize() succeeded list.
16
+ * Only tracks server IDs.
17
+ */
18
+ init(servers) {
19
+ this._serverIds = new Set(servers.map((s) => s.server_id));
20
+ }
21
+ /**
22
+ * Update the cache with a new list of servers, e.g. after calling listServersHandler.
23
+ * This does not imply initialization, but is meant to refresh the cache with the latest list.
24
+ * Only tracks server IDs.
25
+ */
26
+ updateServers(servers) {
27
+ this._serverIds = new Set(servers.map((s) => s.server_id));
28
+ }
29
+ /**
30
+ * Returns true if the server is installed (present in the cache).
31
+ * Throws an error if the cache is not initialized.
32
+ */
33
+ isInstalled(serverId) {
34
+ if (!this.isInitialized()) {
35
+ throw new Error("ServersCache not initialized");
36
+ }
37
+ if (!serverId) {
38
+ throw new Error(`Invalid serverId: "${serverId}"`);
39
+ }
40
+ return this._serverIds.has(serverId);
41
+ }
42
+ /**
43
+ * Get all cached server IDs.
44
+ */
45
+ getServerIds() {
46
+ if (!this._serverIds) {
47
+ throw new Error("ServersCache not initialized");
48
+ }
49
+ return Array.from(this._serverIds);
50
+ }
51
+ /**
52
+ * Refresh the cache by calling list_servers on all server manager clients.
53
+ * This follows the pattern in handleListServers (listServersHandler.ts):
54
+ * - Use sendRequest('list_servers', {}) on each client.
55
+ * - Validate/parse the response with ListServersResultSchema.
56
+ * - Collect all servers from all runtimes.
57
+ * @param serverManagerClients - Record of server manager clients (e.g. from Registry)
58
+ */
59
+ async refreshCache(serverManagerClients) {
60
+ const allServerIds = new Set();
61
+ for (const [runtime, client] of Object.entries(serverManagerClients)) {
62
+ try {
63
+ const response_data = await client.sendRequest("list_servers", {});
64
+ if (response_data.error) {
65
+ await logger.warn(`Error from server manager client "${runtime}": ${response_data.error}`);
66
+ continue;
67
+ }
68
+ const parsed = ListServersResultSchema.safeParse(response_data);
69
+ if (!parsed.success) {
70
+ await logger.warn(`Failed to parse list_servers response from "${runtime}": ${JSON.stringify(response_data)}`);
71
+ continue;
72
+ }
73
+ if (parsed.data.servers && parsed.data.servers.length > 0) {
74
+ for (const server of parsed.data.servers) {
75
+ if (server && typeof server.server_id === "string") {
76
+ allServerIds.add(server.server_id);
77
+ }
78
+ }
79
+ }
80
+ }
81
+ catch (err) {
82
+ await logger.warn(`Exception while refreshing cache from server manager client "${runtime}": ${err instanceof Error ? err.message : String(err)}`);
83
+ // Continue to next client
84
+ }
85
+ }
86
+ this._serverIds = allServerIds;
87
+ }
88
+ /**
89
+ * Check if the cache is initialized
90
+ */
91
+ isInitialized() {
92
+ return this._serverIds !== null;
93
+ }
94
+ /**
95
+ * Reset the cache
96
+ */
97
+ reset() {
98
+ this._serverIds = null;
99
+ }
100
+ }
@@ -0,0 +1,67 @@
1
+ export class ToolDefinitionsCache {
2
+ constructor() {
3
+ this._tools = null;
4
+ this._version = null;
5
+ this._tools = null;
6
+ this._version = null;
7
+ }
8
+ /**
9
+ * Initialize tools cache by fetching tools from API
10
+ * Handles errors if fetching fails, but does not rethrow.
11
+ */
12
+ async init(service, _clientContext) {
13
+ try {
14
+ const toolsResponse = await service.getTools();
15
+ this._tools = toolsResponse.tools;
16
+ this._version = toolsResponse._version;
17
+ }
18
+ catch {
19
+ this._tools = null;
20
+ this._version = null;
21
+ }
22
+ }
23
+ /**
24
+ * Get all cached tool definitions
25
+ */
26
+ getTools() {
27
+ if (!this._tools) {
28
+ throw new Error("ToolDefinitionsCache not initialized");
29
+ }
30
+ return this._tools;
31
+ }
32
+ /**
33
+ * Get a specific tool by name from the cache
34
+ */
35
+ getTool(name) {
36
+ if (!this._tools) {
37
+ throw new Error("ToolDefinitionsCache not initialized");
38
+ }
39
+ const tool = this._tools.find((t) => t.name === name);
40
+ if (!tool) {
41
+ throw new Error(`Tool "${name}" not found in cache`);
42
+ }
43
+ return tool;
44
+ }
45
+ /**
46
+ * Get the version of the current tools
47
+ */
48
+ getVersion() {
49
+ if (!this._version) {
50
+ throw new Error("ToolDefinitionsCache not initialized");
51
+ }
52
+ return this._version;
53
+ }
54
+ /**
55
+ * Check if the cache is initialized
56
+ */
57
+ isInitialized() {
58
+ return this._tools !== null;
59
+ }
60
+ /**
61
+ * Reset the cache
62
+ */
63
+ reset() {
64
+ this._tools = null;
65
+ this._version = null;
66
+ }
67
+ }