@toolplex/client 0.1.1

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 (91) hide show
  1. package/LICENSE +98 -0
  2. package/README.md +112 -0
  3. package/dist/mcp-server/clientContext.d.ts +35 -0
  4. package/dist/mcp-server/clientContext.js +107 -0
  5. package/dist/mcp-server/index.d.ts +1 -0
  6. package/dist/mcp-server/index.js +22 -0
  7. package/dist/mcp-server/logging/telemetryLogger.d.ts +18 -0
  8. package/dist/mcp-server/logging/telemetryLogger.js +54 -0
  9. package/dist/mcp-server/policy/callToolObserver.d.ts +9 -0
  10. package/dist/mcp-server/policy/callToolObserver.js +25 -0
  11. package/dist/mcp-server/policy/feedbackPolicy.d.ts +27 -0
  12. package/dist/mcp-server/policy/feedbackPolicy.js +39 -0
  13. package/dist/mcp-server/policy/installObserver.d.ts +11 -0
  14. package/dist/mcp-server/policy/installObserver.js +35 -0
  15. package/dist/mcp-server/policy/playbookPolicy.d.ts +29 -0
  16. package/dist/mcp-server/policy/playbookPolicy.js +81 -0
  17. package/dist/mcp-server/policy/policyEnforcer.d.ts +57 -0
  18. package/dist/mcp-server/policy/policyEnforcer.js +105 -0
  19. package/dist/mcp-server/policy/serverPolicy.d.ts +39 -0
  20. package/dist/mcp-server/policy/serverPolicy.js +61 -0
  21. package/dist/mcp-server/promptsCache.d.ts +25 -0
  22. package/dist/mcp-server/promptsCache.js +51 -0
  23. package/dist/mcp-server/registry.d.ts +34 -0
  24. package/dist/mcp-server/registry.js +109 -0
  25. package/dist/mcp-server/serversCache.d.ts +53 -0
  26. package/dist/mcp-server/serversCache.js +100 -0
  27. package/dist/mcp-server/staticPrompts.d.ts +6 -0
  28. package/dist/mcp-server/staticPrompts.js +6 -0
  29. package/dist/mcp-server/toolDefinitionsCache.d.ts +33 -0
  30. package/dist/mcp-server/toolDefinitionsCache.js +67 -0
  31. package/dist/mcp-server/toolHandlers/callToolHandler.d.ts +3 -0
  32. package/dist/mcp-server/toolHandlers/callToolHandler.js +79 -0
  33. package/dist/mcp-server/toolHandlers/getServerConfigHandler.d.ts +3 -0
  34. package/dist/mcp-server/toolHandlers/getServerConfigHandler.js +69 -0
  35. package/dist/mcp-server/toolHandlers/initHandler.d.ts +3 -0
  36. package/dist/mcp-server/toolHandlers/initHandler.js +117 -0
  37. package/dist/mcp-server/toolHandlers/installServerHandler.d.ts +3 -0
  38. package/dist/mcp-server/toolHandlers/installServerHandler.js +151 -0
  39. package/dist/mcp-server/toolHandlers/listServersHandler.d.ts +2 -0
  40. package/dist/mcp-server/toolHandlers/listServersHandler.js +81 -0
  41. package/dist/mcp-server/toolHandlers/listToolsHandler.d.ts +3 -0
  42. package/dist/mcp-server/toolHandlers/listToolsHandler.js +112 -0
  43. package/dist/mcp-server/toolHandlers/logPlaybookUsageHandler.d.ts +3 -0
  44. package/dist/mcp-server/toolHandlers/logPlaybookUsageHandler.js +65 -0
  45. package/dist/mcp-server/toolHandlers/lookupEntityHandler.d.ts +3 -0
  46. package/dist/mcp-server/toolHandlers/lookupEntityHandler.js +112 -0
  47. package/dist/mcp-server/toolHandlers/savePlaybookHandler.d.ts +3 -0
  48. package/dist/mcp-server/toolHandlers/savePlaybookHandler.js +65 -0
  49. package/dist/mcp-server/toolHandlers/searchHandler.d.ts +3 -0
  50. package/dist/mcp-server/toolHandlers/searchHandler.js +114 -0
  51. package/dist/mcp-server/toolHandlers/serverManagerUtils.d.ts +2 -0
  52. package/dist/mcp-server/toolHandlers/serverManagerUtils.js +20 -0
  53. package/dist/mcp-server/toolHandlers/submitFeedbackHandler.d.ts +3 -0
  54. package/dist/mcp-server/toolHandlers/submitFeedbackHandler.js +70 -0
  55. package/dist/mcp-server/toolHandlers/uninstallServerHandler.d.ts +3 -0
  56. package/dist/mcp-server/toolHandlers/uninstallServerHandler.js +83 -0
  57. package/dist/mcp-server/toolplexApi/service.d.ts +32 -0
  58. package/dist/mcp-server/toolplexApi/service.js +222 -0
  59. package/dist/mcp-server/toolplexApi/types.d.ts +124 -0
  60. package/dist/mcp-server/toolplexApi/types.js +1 -0
  61. package/dist/mcp-server/toolplexServer.d.ts +3 -0
  62. package/dist/mcp-server/toolplexServer.js +249 -0
  63. package/dist/mcp-server/tools.d.ts +2 -0
  64. package/dist/mcp-server/tools.js +13 -0
  65. package/dist/mcp-server/utils/initServerManagers.d.ts +6 -0
  66. package/dist/mcp-server/utils/initServerManagers.js +31 -0
  67. package/dist/mcp-server/utils/resultAnnotators.d.ts +23 -0
  68. package/dist/mcp-server/utils/resultAnnotators.js +50 -0
  69. package/dist/mcp-server/utils/runtimeCheck.d.ts +4 -0
  70. package/dist/mcp-server/utils/runtimeCheck.js +30 -0
  71. package/dist/server-manager/index.d.ts +1 -0
  72. package/dist/server-manager/index.js +8 -0
  73. package/dist/server-manager/serverManager.d.ts +37 -0
  74. package/dist/server-manager/serverManager.js +419 -0
  75. package/dist/server-manager/stdioServer.d.ts +9 -0
  76. package/dist/server-manager/stdioServer.js +136 -0
  77. package/dist/server-manager/stdioTransportProtocol.d.ts +31 -0
  78. package/dist/server-manager/stdioTransportProtocol.js +67 -0
  79. package/dist/shared/enhancedPath.d.ts +7 -0
  80. package/dist/shared/enhancedPath.js +52 -0
  81. package/dist/shared/fileLogger.d.ts +13 -0
  82. package/dist/shared/fileLogger.js +66 -0
  83. package/dist/shared/mcpServerTypes.d.ts +398 -0
  84. package/dist/shared/mcpServerTypes.js +148 -0
  85. package/dist/shared/serverManagerTypes.d.ts +179 -0
  86. package/dist/shared/serverManagerTypes.js +73 -0
  87. package/dist/shared/stdioServerManagerClient.d.ts +12 -0
  88. package/dist/shared/stdioServerManagerClient.js +96 -0
  89. package/dist/version.d.ts +1 -0
  90. package/dist/version.js +1 -0
  91. package/package.json +70 -0
@@ -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,6 @@
1
+ export declare const PRE_INITIALIZATION_PROMPTS: {
2
+ tools_initialization_error: string;
3
+ enforce_init_toolplex: string;
4
+ unknown_tool: string;
5
+ unexpected_error: string;
6
+ };
@@ -0,0 +1,6 @@
1
+ export const PRE_INITIALIZATION_PROMPTS = {
2
+ tools_initialization_error: `ERROR: Unable to connect to ToolPlex. Please check your API key and network connection. If the problem persists, contact support at support@toolplex.ai for assistance.`,
3
+ enforce_init_toolplex: `ERROR: Attempted to call tool {TOOL_NAME} before calling initalize_toolplex().\nPlease init and retry.`,
4
+ unknown_tool: 'Unknown tool: {TOOL_NAME}. Use list_toolplex_tools() to see available tools.',
5
+ unexpected_error: 'Unexpected error: {ERROR}',
6
+ };
@@ -0,0 +1,33 @@
1
+ import { Tool } from '@modelcontextprotocol/sdk/types.js';
2
+ import { ToolplexApiService } from './toolplexApi/service.js';
3
+ import { ClientContext } from './clientContext.js';
4
+ export declare class ToolDefinitionsCache {
5
+ private _tools;
6
+ private _version;
7
+ constructor();
8
+ /**
9
+ * Initialize tools cache by fetching tools from API
10
+ * Handles errors if fetching fails, but does not rethrow.
11
+ */
12
+ init(service: ToolplexApiService, _clientContext: ClientContext): Promise<void>;
13
+ /**
14
+ * Get all cached tool definitions
15
+ */
16
+ getTools(): Tool[];
17
+ /**
18
+ * Get a specific tool by name from the cache
19
+ */
20
+ getTool(name: string): Tool;
21
+ /**
22
+ * Get the version of the current tools
23
+ */
24
+ getVersion(): string;
25
+ /**
26
+ * Check if the cache is initialized
27
+ */
28
+ isInitialized(): boolean;
29
+ /**
30
+ * Reset the cache
31
+ */
32
+ reset(): void;
33
+ }
@@ -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
+ }
@@ -0,0 +1,3 @@
1
+ import { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
2
+ import { CallToolParams } from '../../shared/mcpServerTypes.js';
3
+ export declare function handleCallTool(params: CallToolParams): Promise<CallToolResult>;
@@ -0,0 +1,79 @@
1
+ import { findServerManagerClient } from './serverManagerUtils.js';
2
+ import { CallToolResultSchema } from '../../shared/serverManagerTypes.js';
3
+ import { FileLogger } from '../../shared/fileLogger.js';
4
+ import Registry from '../registry.js';
5
+ const logger = FileLogger;
6
+ export async function handleCallTool(params) {
7
+ await logger.debug(`Handling call tool request with params: ${JSON.stringify(params)}`);
8
+ const serverManagerClients = Registry.getServerManagerClients();
9
+ const telemetryLogger = Registry.getTelemetryLogger();
10
+ const promptsCache = Registry.getPromptsCache();
11
+ const clientContext = Registry.getClientContext();
12
+ const startTime = Date.now();
13
+ // Get the CallToolObserver from the PolicyEnforcer via Registry
14
+ const policyEnforcer = Registry.getPolicyEnforcer();
15
+ const callToolObserver = policyEnforcer.getCallToolObserver();
16
+ try {
17
+ // Enforce call tool policy
18
+ policyEnforcer.enforceCallToolPolicy(params.server_id);
19
+ const client = await findServerManagerClient(params.server_id, serverManagerClients);
20
+ const response = await client.sendRequest('call_tool', params);
21
+ if ('error' in response) {
22
+ throw new Error(`Failed to call tool with params: ${JSON.stringify(params)}, error message: ${response.error.message}`);
23
+ }
24
+ const parsed = CallToolResultSchema.safeParse(response);
25
+ if (!parsed.success) {
26
+ throw new Error(`Invalid call_tool response: ${parsed.error}`);
27
+ }
28
+ // Record the successful tool call for Playbook policy enforcement
29
+ callToolObserver.recordCall(params.server_id, params.tool_name);
30
+ const result = parsed.data.result;
31
+ const content = Array.isArray(result) ? result : [result];
32
+ if (!clientContext.permissions.enable_read_only_mode &&
33
+ clientContext.clientMode !== 'restricted') {
34
+ content.push({
35
+ type: 'text',
36
+ text: promptsCache.getPrompt('tool_call_next_steps'),
37
+ });
38
+ }
39
+ await logger.debug('Tool called successfully');
40
+ await telemetryLogger.log('client_call_tool', {
41
+ success: true,
42
+ log_context: {
43
+ server_id: params.server_id,
44
+ tool_name: params.tool_name,
45
+ },
46
+ latency_ms: Date.now() - startTime,
47
+ });
48
+ return {
49
+ role: 'system',
50
+ content: content,
51
+ };
52
+ }
53
+ catch (error) {
54
+ const errorMessage = error instanceof Error ? error.message : promptsCache.getPrompt('unexpected_error');
55
+ await logger.error(`Failed to call tool: ${errorMessage}`);
56
+ await telemetryLogger.log('client_call_tool', {
57
+ success: false,
58
+ log_context: {
59
+ server_id: params.server_id,
60
+ tool_name: params.tool_name,
61
+ },
62
+ pii_sanitized_error_message: errorMessage,
63
+ latency_ms: Date.now() - startTime,
64
+ });
65
+ return {
66
+ role: 'system',
67
+ content: [
68
+ {
69
+ type: 'text',
70
+ text: promptsCache
71
+ .getPrompt('tool_call_failure')
72
+ .replace('{ERROR}', errorMessage)
73
+ .replace('{SERVER_ID}', params.server_id)
74
+ .replace('{TOOL_NAME}', params.tool_name),
75
+ },
76
+ ],
77
+ };
78
+ }
79
+ }
@@ -0,0 +1,3 @@
1
+ import { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
2
+ import { GetServerConfigParams } from '../../shared/mcpServerTypes.js';
3
+ export declare function handleGetServerConfig(params: GetServerConfigParams): Promise<CallToolResult>;
@@ -0,0 +1,69 @@
1
+ import { FileLogger } from '../../shared/fileLogger.js';
2
+ import { findServerManagerClient } from './serverManagerUtils.js';
3
+ import Registry from '../registry.js';
4
+ const logger = FileLogger;
5
+ export async function handleGetServerConfig(params) {
6
+ const startTime = Date.now();
7
+ const serverManagerClients = Registry.getServerManagerClients();
8
+ const telemetryLogger = Registry.getTelemetryLogger();
9
+ const promptsCache = Registry.getPromptsCache();
10
+ const policyEnforcer = Registry.getPolicyEnforcer();
11
+ try {
12
+ const server_id = params.server_id;
13
+ if (!server_id) {
14
+ throw new Error('Missing server_id');
15
+ }
16
+ // Check if server is blocked using policy enforcer
17
+ policyEnforcer.enforceUseServerPolicy(server_id);
18
+ await logger.debug(`Getting config for server: ${server_id}`);
19
+ const client = await findServerManagerClient(server_id, serverManagerClients);
20
+ const response_data = await client.sendRequest('get_server_config', { server_id });
21
+ if ('error' in response_data) {
22
+ throw new Error(`Failed to get config for server_id ${server_id}, error message: ${response_data.error.message}`);
23
+ }
24
+ // The config is returned directly as an object
25
+ const config = response_data;
26
+ await logger.debug('Successfully retrieved server config');
27
+ await telemetryLogger.log('client_get_server_config', {
28
+ success: true,
29
+ log_context: {
30
+ server_id,
31
+ },
32
+ latency_ms: Date.now() - startTime,
33
+ });
34
+ return {
35
+ role: 'system',
36
+ content: [
37
+ {
38
+ type: 'text',
39
+ text: promptsCache.getPrompt('get_server_config_header').replace('{SERVER_ID}', server_id) +
40
+ '\n' +
41
+ JSON.stringify(config, null, 2),
42
+ },
43
+ ],
44
+ };
45
+ }
46
+ catch (error) {
47
+ const errorMessage = error instanceof Error ? error.message : promptsCache.getPrompt('unexpected_error');
48
+ await logger.error(`Failed to get server config: ${errorMessage}`);
49
+ await telemetryLogger.log('client_get_server_config', {
50
+ success: false,
51
+ log_context: {
52
+ server_id: params.server_id,
53
+ },
54
+ pii_sanitized_error_message: errorMessage,
55
+ latency_ms: Date.now() - startTime,
56
+ });
57
+ return {
58
+ role: 'system',
59
+ content: [
60
+ {
61
+ type: 'text',
62
+ text: promptsCache
63
+ .getPrompt('get_server_config_failure')
64
+ .replace('{ERROR}', errorMessage),
65
+ },
66
+ ],
67
+ };
68
+ }
69
+ }
@@ -0,0 +1,3 @@
1
+ import { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
2
+ import { InitializeToolplexParams } from '../../shared/mcpServerTypes.js';
3
+ export declare function handleInitialize(params: InitializeToolplexParams): Promise<CallToolResult>;
@@ -0,0 +1,117 @@
1
+ import { FileLogger } from '../../shared/fileLogger.js';
2
+ import os from 'os';
3
+ import { initServerManagersOnly } from '../utils/initServerManagers.js';
4
+ import Registry from '../registry.js';
5
+ const logger = FileLogger;
6
+ export async function handleInitialize(params) {
7
+ const startTime = Date.now();
8
+ await logger.info('Initializing ToolPlex');
9
+ await logger.debug(`Initialization params: ${JSON.stringify(params)}`);
10
+ const clientContext = Registry.getClientContext();
11
+ const apiService = Registry.getToolplexApiService();
12
+ const serverManagerClients = Registry.getServerManagerClients();
13
+ const telemetryLogger = Registry.getTelemetryLogger();
14
+ const promptsCache = Registry.getPromptsCache();
15
+ const serversCache = Registry.getServersCache();
16
+ const policyEnforcer = Registry.getPolicyEnforcer();
17
+ await logger.debug(`Server manager clients: ${Object.keys(serverManagerClients).join(', ')}`);
18
+ const platform = os.platform();
19
+ const osName = platform === 'darwin'
20
+ ? 'macOS'
21
+ : platform === 'win32'
22
+ ? 'Windows'
23
+ : platform.charAt(0).toUpperCase() + platform.slice(1);
24
+ const systemInfo = {
25
+ os: `${osName} ${os.release()}`,
26
+ arch: os.arch(),
27
+ memory: `${Math.round(os.totalmem() / (1024 * 1024 * 1024))}GB`,
28
+ cpuCores: os.cpus().length,
29
+ workDir: process.cwd(),
30
+ };
31
+ await logger.debug('Initializing server managers and API service');
32
+ const [serverManagerInitResults, toolplexApiInitResponse] = await Promise.all([
33
+ initServerManagersOnly(serverManagerClients).catch((err) => {
34
+ logger.warn(`Server manager init failed: ${err}`);
35
+ return { succeeded: [], failures: {} };
36
+ }),
37
+ apiService.init(),
38
+ ]);
39
+ clientContext.isOrgUser = toolplexApiInitResponse.is_org_user;
40
+ clientContext.sessionId = toolplexApiInitResponse.session_id;
41
+ clientContext.permissions = toolplexApiInitResponse.permissions;
42
+ clientContext.flags = toolplexApiInitResponse.flags;
43
+ promptsCache.init(toolplexApiInitResponse.prompts);
44
+ // Init PolicyEnforce after setting permissions and flags
45
+ policyEnforcer.init(clientContext);
46
+ const allSucceeded = serverManagerInitResults.succeeded;
47
+ const allFailures = serverManagerInitResults.failures;
48
+ // Initialize the serversCache with the succeeded servers
49
+ serversCache.init(allSucceeded);
50
+ await logger.debug(`Total successes: ${allSucceeded.length}, Total failures: ${Object.keys(allFailures).length}`);
51
+ await logger.debug('Building initialization response');
52
+ // Safe to use prompts after init.
53
+ const result = {
54
+ role: 'system',
55
+ content: [
56
+ {
57
+ type: 'text',
58
+ text: promptsCache
59
+ .getPrompt('initialization')
60
+ .replace('{ARGS.os}', systemInfo.os)
61
+ .replace('{ARGS.arch}', systemInfo.arch)
62
+ .replace('{ARGS.memory}', systemInfo.memory)
63
+ .replace('{ARGS.cpuCores}', systemInfo.cpuCores.toString())
64
+ .replace('{ARGS.workDir}', systemInfo.workDir),
65
+ },
66
+ ],
67
+ };
68
+ result.content.push({
69
+ type: 'text',
70
+ text: promptsCache
71
+ .getPrompt('initialization_results')
72
+ .replace('{SUCCEEDED}', allSucceeded.map((s) => `${s.server_id} (${s.server_name})`).join(', ') || 'none')
73
+ .replace('{FAILURES}', Object.entries(allFailures)
74
+ .map(([serverId, failure]) => `${serverId} (${failure.server_name}): ${failure.error}`)
75
+ .join(', ') || 'none')
76
+ .replace('{FAILURE_NOTE}', Object.keys(allFailures).length > 0
77
+ ? 'Please note there were failures installing some servers. Inform the user.'
78
+ : ''),
79
+ });
80
+ if (clientContext.permissions.allowed_mcp_servers &&
81
+ clientContext.permissions.allowed_mcp_servers.length > 0) {
82
+ result.content.push({
83
+ type: 'text',
84
+ text: promptsCache
85
+ .getPrompt('allowed_mcp_servers')
86
+ .replace('{ALLOWED_MCP_SERVERS}', clientContext.permissions.allowed_mcp_servers.join(', ')),
87
+ });
88
+ }
89
+ result.content.push({
90
+ type: 'text',
91
+ text: 'Your Most Recently Used Playbooks:\n' +
92
+ toolplexApiInitResponse.playbooks.playbooks
93
+ .map((p) => `- ${p.id}: ${p.description}\n` +
94
+ ` Used ${p.times_used} times` +
95
+ (p.days_since_last_used !== null
96
+ ? `, last use: ${p.days_since_last_used} ${p.days_since_last_used === 1 ? 'day' : 'days'} ago`
97
+ : ''))
98
+ .join('\n') +
99
+ '\n\nMore playbooks are available through the search tool.',
100
+ });
101
+ await telemetryLogger.log('client_initialize_toolplex', {
102
+ session_id: toolplexApiInitResponse.session_id,
103
+ success: Object.keys(allFailures).length === 0,
104
+ log_context: {
105
+ os_platform: platform,
106
+ os_arch: systemInfo.arch,
107
+ cpu_cores: systemInfo.cpuCores,
108
+ total_memory_gb: Math.round(os.totalmem() / (1024 * 1024 * 1024)),
109
+ num_succeeded_servers: allSucceeded.length,
110
+ num_failed_servers: Object.keys(allFailures).length,
111
+ num_recent_playbooks: toolplexApiInitResponse.playbooks.playbooks.length,
112
+ },
113
+ latency_ms: Date.now() - startTime,
114
+ });
115
+ await logger.info('ToolPlex initialization completed');
116
+ return result;
117
+ }
@@ -0,0 +1,3 @@
1
+ import { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
2
+ import { InstallParams } from '../../shared/mcpServerTypes.js';
3
+ export declare function handleInstallServer(params: InstallParams): Promise<CallToolResult>;
@@ -0,0 +1,151 @@
1
+ import { FileLogger } from '../../shared/fileLogger.js';
2
+ import { ServerInstallResultSchema, ListToolsResultSchema, } from '../../shared/serverManagerTypes.js';
3
+ import Registry from '../registry.js';
4
+ import { RuntimeCheck } from '../utils/runtimeCheck.js';
5
+ const logger = FileLogger;
6
+ async function installServer(serverId, serverName, description, serverManagerClient, serverConfig) {
7
+ await logger.info(`Starting installation of tool ${serverId}: ${serverName}`);
8
+ await logger.debug(`Server config: ${JSON.stringify(serverConfig)}, Server ID: ${serverId}`);
9
+ const response = await serverManagerClient.sendRequest('install', {
10
+ server_id: serverId,
11
+ server_name: serverName,
12
+ description: description,
13
+ config: serverConfig,
14
+ });
15
+ if ('error' in response) {
16
+ const error = `Server installation failed: ${response.error.message}`;
17
+ await logger.error(error);
18
+ throw new Error(error);
19
+ }
20
+ const parsed = ServerInstallResultSchema.safeParse(response);
21
+ if (!parsed.success) {
22
+ throw new Error(`Invalid server install response: ${JSON.stringify(parsed.error.errors)}`);
23
+ }
24
+ logger.debug(`Install response: ${JSON.stringify(response)}`);
25
+ if (!response) {
26
+ const error = 'Server installation failed: No response received';
27
+ await logger.error(error);
28
+ throw new Error(error);
29
+ }
30
+ await logger.info(`Successfully installed tool ${serverId}`);
31
+ return parsed.data;
32
+ }
33
+ export async function handleInstallServer(params) {
34
+ const serverManagerClients = Registry.getServerManagerClients();
35
+ const telemetryLogger = Registry.getTelemetryLogger();
36
+ const promptsCache = Registry.getPromptsCache();
37
+ const serversCache = Registry.getServersCache();
38
+ const clientContext = Registry.getClientContext();
39
+ const policyEnforcer = Registry.getPolicyEnforcer();
40
+ const installObserver = policyEnforcer.getInstallObserver();
41
+ const startTime = Date.now();
42
+ let errorMessage;
43
+ try {
44
+ // Check if the client is in restricted mode
45
+ if (clientContext.clientMode === 'restricted') {
46
+ throw new Error('Install functionality is disabled in restricted mode.');
47
+ }
48
+ const { config, server_id, server_name } = params;
49
+ const description = params.description || server_name;
50
+ if (!config || !server_id || !server_name) {
51
+ throw new Error('Missing required install parameters');
52
+ }
53
+ // Validate command is installed before proceeding
54
+ if (config.command) {
55
+ await RuntimeCheck.validateCommandOrThrow(config.command);
56
+ }
57
+ // Check if server is disallowed using policy enforcer
58
+ policyEnforcer.enforceUseServerPolicy(server_id);
59
+ const runtime = config.runtime || 'node';
60
+ const runtimeKey = 'node';
61
+ const client = serverManagerClients[runtimeKey];
62
+ // Keep for now -- may add separate server managers for different runtimes if needed.
63
+ if (!client) {
64
+ throw new Error(`Unsupported runtime: ${runtime}`);
65
+ }
66
+ // Install server
67
+ const installResult = await installServer(server_id, server_name, description, client, config);
68
+ // After successful install, refresh the servers cache
69
+ await serversCache.refreshCache(serverManagerClients);
70
+ // List tools on the newly installed server
71
+ const toolsResponse = await client.sendRequest('list_tools', {
72
+ server_id: installResult.server_id,
73
+ });
74
+ if ('error' in toolsResponse) {
75
+ throw new Error(`Failed to list tools for server_id ${installResult.server_id}, error message: ${toolsResponse.error.message}`);
76
+ }
77
+ const parsed = ListToolsResultSchema.safeParse(toolsResponse);
78
+ if (!parsed.success) {
79
+ throw new Error(`Invalid list_tools response: ${JSON.stringify(parsed.error.errors)}, response was: ${JSON.stringify(toolsResponse)}`);
80
+ }
81
+ let toolsText = '';
82
+ const tools = parsed.data.tools || [];
83
+ if (tools.length > 0) {
84
+ toolsText = 'Available tools from this server:\n\n';
85
+ for (const tool of tools) {
86
+ toolsText += `- ${tool.name}: ${tool.description}\n`;
87
+ toolsText += ` Input Schema: ${JSON.stringify(tool.inputSchema, null, 2)}\n\n`;
88
+ }
89
+ }
90
+ await telemetryLogger.log('client_install', {
91
+ success: true,
92
+ log_context: {
93
+ server_id: installResult.server_id,
94
+ },
95
+ latency_ms: Date.now() - startTime,
96
+ });
97
+ const content = [
98
+ {
99
+ type: 'text',
100
+ text: promptsCache
101
+ .getPrompt('install_success')
102
+ .replace('{SERVER_ID}', installResult.server_id)
103
+ .replace('{SERVER_NAME}', installResult.server_name)
104
+ .replace('{TOOLS_LIST}', toolsText),
105
+ },
106
+ ];
107
+ if (!clientContext.permissions.enable_read_only_mode) {
108
+ content.push({
109
+ type: 'text',
110
+ text: promptsCache.getPrompt('install_next_steps'),
111
+ });
112
+ }
113
+ return {
114
+ role: 'system',
115
+ content,
116
+ };
117
+ }
118
+ catch (error) {
119
+ errorMessage =
120
+ error instanceof Error ? error.message : promptsCache.getPrompt('unexpected_error');
121
+ await logger.error(`Installation failed: ${errorMessage}`);
122
+ await telemetryLogger.log('client_install', {
123
+ success: false,
124
+ log_context: {
125
+ server_id: params.server_id,
126
+ },
127
+ pii_sanitized_error_message: errorMessage,
128
+ latency_ms: Date.now() - startTime,
129
+ });
130
+ const content = [
131
+ {
132
+ type: 'text',
133
+ text: promptsCache.getPrompt('install_failure').replace('{ERROR}', errorMessage),
134
+ },
135
+ ];
136
+ if (!clientContext.permissions.enable_read_only_mode) {
137
+ content.push({
138
+ type: 'text',
139
+ text: promptsCache.getPrompt('install_next_steps'),
140
+ });
141
+ }
142
+ return {
143
+ role: 'system',
144
+ content,
145
+ };
146
+ }
147
+ finally {
148
+ // Record the install action regardless of success or failure
149
+ installObserver.recordInstall(params.server_id);
150
+ }
151
+ }
@@ -0,0 +1,2 @@
1
+ import { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
2
+ export declare function handleListServers(): Promise<CallToolResult>;