@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.
- package/LICENSE +98 -0
- package/README.md +112 -0
- package/dist/mcp-server/clientContext.d.ts +35 -0
- package/dist/mcp-server/clientContext.js +107 -0
- package/dist/mcp-server/index.d.ts +1 -0
- package/dist/mcp-server/index.js +22 -0
- package/dist/mcp-server/logging/telemetryLogger.d.ts +18 -0
- package/dist/mcp-server/logging/telemetryLogger.js +54 -0
- package/dist/mcp-server/policy/callToolObserver.d.ts +9 -0
- package/dist/mcp-server/policy/callToolObserver.js +25 -0
- package/dist/mcp-server/policy/feedbackPolicy.d.ts +27 -0
- package/dist/mcp-server/policy/feedbackPolicy.js +39 -0
- package/dist/mcp-server/policy/installObserver.d.ts +11 -0
- package/dist/mcp-server/policy/installObserver.js +35 -0
- package/dist/mcp-server/policy/playbookPolicy.d.ts +29 -0
- package/dist/mcp-server/policy/playbookPolicy.js +81 -0
- package/dist/mcp-server/policy/policyEnforcer.d.ts +57 -0
- package/dist/mcp-server/policy/policyEnforcer.js +105 -0
- package/dist/mcp-server/policy/serverPolicy.d.ts +39 -0
- package/dist/mcp-server/policy/serverPolicy.js +61 -0
- package/dist/mcp-server/promptsCache.d.ts +25 -0
- package/dist/mcp-server/promptsCache.js +51 -0
- package/dist/mcp-server/registry.d.ts +34 -0
- package/dist/mcp-server/registry.js +109 -0
- package/dist/mcp-server/serversCache.d.ts +53 -0
- package/dist/mcp-server/serversCache.js +100 -0
- package/dist/mcp-server/staticPrompts.d.ts +6 -0
- package/dist/mcp-server/staticPrompts.js +6 -0
- package/dist/mcp-server/toolDefinitionsCache.d.ts +33 -0
- package/dist/mcp-server/toolDefinitionsCache.js +67 -0
- package/dist/mcp-server/toolHandlers/callToolHandler.d.ts +3 -0
- package/dist/mcp-server/toolHandlers/callToolHandler.js +79 -0
- package/dist/mcp-server/toolHandlers/getServerConfigHandler.d.ts +3 -0
- package/dist/mcp-server/toolHandlers/getServerConfigHandler.js +69 -0
- package/dist/mcp-server/toolHandlers/initHandler.d.ts +3 -0
- package/dist/mcp-server/toolHandlers/initHandler.js +117 -0
- package/dist/mcp-server/toolHandlers/installServerHandler.d.ts +3 -0
- package/dist/mcp-server/toolHandlers/installServerHandler.js +151 -0
- package/dist/mcp-server/toolHandlers/listServersHandler.d.ts +2 -0
- package/dist/mcp-server/toolHandlers/listServersHandler.js +81 -0
- package/dist/mcp-server/toolHandlers/listToolsHandler.d.ts +3 -0
- package/dist/mcp-server/toolHandlers/listToolsHandler.js +112 -0
- package/dist/mcp-server/toolHandlers/logPlaybookUsageHandler.d.ts +3 -0
- package/dist/mcp-server/toolHandlers/logPlaybookUsageHandler.js +65 -0
- package/dist/mcp-server/toolHandlers/lookupEntityHandler.d.ts +3 -0
- package/dist/mcp-server/toolHandlers/lookupEntityHandler.js +112 -0
- package/dist/mcp-server/toolHandlers/savePlaybookHandler.d.ts +3 -0
- package/dist/mcp-server/toolHandlers/savePlaybookHandler.js +65 -0
- package/dist/mcp-server/toolHandlers/searchHandler.d.ts +3 -0
- package/dist/mcp-server/toolHandlers/searchHandler.js +114 -0
- package/dist/mcp-server/toolHandlers/serverManagerUtils.d.ts +2 -0
- package/dist/mcp-server/toolHandlers/serverManagerUtils.js +20 -0
- package/dist/mcp-server/toolHandlers/submitFeedbackHandler.d.ts +3 -0
- package/dist/mcp-server/toolHandlers/submitFeedbackHandler.js +70 -0
- package/dist/mcp-server/toolHandlers/uninstallServerHandler.d.ts +3 -0
- package/dist/mcp-server/toolHandlers/uninstallServerHandler.js +83 -0
- package/dist/mcp-server/toolplexApi/service.d.ts +32 -0
- package/dist/mcp-server/toolplexApi/service.js +222 -0
- package/dist/mcp-server/toolplexApi/types.d.ts +124 -0
- package/dist/mcp-server/toolplexApi/types.js +1 -0
- package/dist/mcp-server/toolplexServer.d.ts +3 -0
- package/dist/mcp-server/toolplexServer.js +249 -0
- package/dist/mcp-server/tools.d.ts +2 -0
- package/dist/mcp-server/tools.js +13 -0
- package/dist/mcp-server/utils/initServerManagers.d.ts +6 -0
- package/dist/mcp-server/utils/initServerManagers.js +31 -0
- package/dist/mcp-server/utils/resultAnnotators.d.ts +23 -0
- package/dist/mcp-server/utils/resultAnnotators.js +50 -0
- package/dist/mcp-server/utils/runtimeCheck.d.ts +4 -0
- package/dist/mcp-server/utils/runtimeCheck.js +30 -0
- package/dist/server-manager/index.d.ts +1 -0
- package/dist/server-manager/index.js +8 -0
- package/dist/server-manager/serverManager.d.ts +37 -0
- package/dist/server-manager/serverManager.js +419 -0
- package/dist/server-manager/stdioServer.d.ts +9 -0
- package/dist/server-manager/stdioServer.js +136 -0
- package/dist/server-manager/stdioTransportProtocol.d.ts +31 -0
- package/dist/server-manager/stdioTransportProtocol.js +67 -0
- package/dist/shared/enhancedPath.d.ts +7 -0
- package/dist/shared/enhancedPath.js +52 -0
- package/dist/shared/fileLogger.d.ts +13 -0
- package/dist/shared/fileLogger.js +66 -0
- package/dist/shared/mcpServerTypes.d.ts +398 -0
- package/dist/shared/mcpServerTypes.js +148 -0
- package/dist/shared/serverManagerTypes.d.ts +179 -0
- package/dist/shared/serverManagerTypes.js +73 -0
- package/dist/shared/stdioServerManagerClient.d.ts +12 -0
- package/dist/shared/stdioServerManagerClient.js +96 -0
- package/dist/version.d.ts +1 -0
- package/dist/version.js +1 -0
- 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 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,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,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,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,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
|
+
}
|