@toolplex/client 0.1.18 → 0.1.20
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/dist/mcp-server/index.js +12 -0
- package/dist/mcp-server/registry.d.ts +17 -0
- package/dist/mcp-server/registry.js +23 -0
- package/dist/mcp-server/toolHandlers/callToolHandler.js +1 -0
- package/dist/mcp-server/toolHandlers/initHandler.d.ts +9 -0
- package/dist/mcp-server/toolHandlers/initHandler.js +50 -1
- package/dist/mcp-server/toolHandlers/lookupEntityHandler.js +1 -0
- package/dist/mcp-server/toolHandlers/searchHandler.js +1 -1
- package/dist/mcp-server/toolplexServer.js +5 -0
- package/dist/mcp-server/utils/runtimeCheck.d.ts +18 -0
- package/dist/mcp-server/utils/runtimeCheck.js +70 -6
- package/dist/server-manager/serverManager.js +3 -9
- package/dist/shared/enhancedPath.js +3 -3
- package/dist/shared/mcpServerTypes.d.ts +14 -0
- package/dist/src/mcp-server/clientContext.js +118 -0
- package/dist/src/mcp-server/logging/telemetryLogger.js +54 -0
- package/dist/src/mcp-server/policy/callToolObserver.js +27 -0
- package/dist/src/mcp-server/policy/feedbackPolicy.js +39 -0
- package/dist/src/mcp-server/policy/installObserver.js +35 -0
- package/dist/src/mcp-server/policy/playbookPolicy.js +81 -0
- package/dist/src/mcp-server/policy/policyEnforcer.js +105 -0
- package/dist/src/mcp-server/policy/serverPolicy.js +63 -0
- package/dist/src/mcp-server/promptsCache.js +51 -0
- package/dist/src/mcp-server/registry.js +134 -0
- package/dist/src/mcp-server/serversCache.js +100 -0
- package/dist/src/mcp-server/toolDefinitionsCache.js +67 -0
- package/dist/src/mcp-server/toolHandlers/initHandler.js +189 -0
- package/dist/src/mcp-server/toolplexApi/service.js +221 -0
- package/dist/src/mcp-server/toolplexApi/types.js +1 -0
- package/dist/src/mcp-server/utils/initServerManagers.js +31 -0
- package/dist/src/mcp-server/utils/runtimeCheck.js +94 -0
- package/dist/src/shared/enhancedPath.js +52 -0
- package/dist/src/shared/fileLogger.js +66 -0
- package/dist/src/shared/mcpServerTypes.js +158 -0
- package/dist/src/shared/serverManagerTypes.js +73 -0
- package/dist/src/shared/stdioServerManagerClient.js +98 -0
- package/dist/tests/unit/bundledDependencies.test.js +152 -0
- package/dist/tests/unit/registry.test.js +216 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +8 -3
package/dist/mcp-server/index.js
CHANGED
|
@@ -9,6 +9,17 @@ const apiKey = process.env.TOOLPLEX_API_KEY;
|
|
|
9
9
|
const clientMode = process.env.TOOLPLEX_CLIENT_MODE || "standard";
|
|
10
10
|
const clientName = process.env.CLIENT_NAME || "unknown";
|
|
11
11
|
const logLevel = process.env.LOG_LEVEL || "info";
|
|
12
|
+
// Read bundled dependency paths from environment variables
|
|
13
|
+
// These are provided by the host application (e.g., Electron desktop)
|
|
14
|
+
const bundledDependencies = {
|
|
15
|
+
node: process.env.TOOLPLEX_NODE_PATH,
|
|
16
|
+
npx: process.env.TOOLPLEX_NPX_PATH,
|
|
17
|
+
python: process.env.TOOLPLEX_PYTHON_PATH,
|
|
18
|
+
pip: process.env.TOOLPLEX_PIP_PATH,
|
|
19
|
+
uv: process.env.TOOLPLEX_UV_PATH,
|
|
20
|
+
uvx: process.env.TOOLPLEX_UVX_PATH,
|
|
21
|
+
git: process.env.TOOLPLEX_GIT_PATH,
|
|
22
|
+
};
|
|
12
23
|
if (!apiKey) {
|
|
13
24
|
process.exit(1);
|
|
14
25
|
}
|
|
@@ -18,6 +29,7 @@ const config = {
|
|
|
18
29
|
clientMode,
|
|
19
30
|
clientName,
|
|
20
31
|
logLevel,
|
|
32
|
+
bundledDependencies,
|
|
21
33
|
};
|
|
22
34
|
serve(config).catch(() => {
|
|
23
35
|
process.exit(1);
|
|
@@ -6,6 +6,7 @@ import { PromptsCache } from "./promptsCache.js";
|
|
|
6
6
|
import { ToolDefinitionsCache } from "./toolDefinitionsCache.js";
|
|
7
7
|
import { ServersCache } from "./serversCache.js";
|
|
8
8
|
import { PolicyEnforcer } from "./policy/policyEnforcer.js";
|
|
9
|
+
import { BundledDependencies } from "../shared/mcpServerTypes.js";
|
|
9
10
|
/**
|
|
10
11
|
* In-memory global registry for the ToolPlex client.
|
|
11
12
|
* Maintains singleton instances of core services and clients used throughout the application.
|
|
@@ -19,6 +20,7 @@ declare class Registry {
|
|
|
19
20
|
private static _toolDefinitionsCache;
|
|
20
21
|
private static _serversCache;
|
|
21
22
|
private static _policyEnforcer;
|
|
23
|
+
private static _bundledDependencies;
|
|
22
24
|
static init(clientContext: ClientContext): Promise<void>;
|
|
23
25
|
static getClientContext(): ClientContext;
|
|
24
26
|
static getToolplexApiService(): ToolplexApiService;
|
|
@@ -29,6 +31,21 @@ declare class Registry {
|
|
|
29
31
|
static getToolDefinitionsCache(): ToolDefinitionsCache;
|
|
30
32
|
static getServersCache(): ServersCache;
|
|
31
33
|
static getPolicyEnforcer(): PolicyEnforcer;
|
|
34
|
+
/**
|
|
35
|
+
* Set bundled dependencies (paths to Node, Python, Git, etc.)
|
|
36
|
+
* provided by the host application (e.g., Electron desktop app).
|
|
37
|
+
*/
|
|
38
|
+
static setBundledDependencies(deps: BundledDependencies): void;
|
|
39
|
+
/**
|
|
40
|
+
* Get bundled dependencies (paths to required executables).
|
|
41
|
+
* Returns a copy of the bundled dependencies object.
|
|
42
|
+
*/
|
|
43
|
+
static getBundledDependencies(): BundledDependencies;
|
|
44
|
+
/**
|
|
45
|
+
* Get the path for a specific bundled dependency by name.
|
|
46
|
+
* Returns undefined if the dependency is not available.
|
|
47
|
+
*/
|
|
48
|
+
static getBundledDependencyPath(depName: "node" | "python" | "git" | "uvx" | "npx"): string | undefined;
|
|
32
49
|
static reset(): void;
|
|
33
50
|
}
|
|
34
51
|
export default Registry;
|
|
@@ -80,6 +80,27 @@ class Registry {
|
|
|
80
80
|
}
|
|
81
81
|
return this._policyEnforcer;
|
|
82
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
|
+
}
|
|
83
104
|
static reset() {
|
|
84
105
|
this._clientContext = null;
|
|
85
106
|
this._toolplexApiService = null;
|
|
@@ -98,6 +119,7 @@ class Registry {
|
|
|
98
119
|
this._serversCache = null;
|
|
99
120
|
}
|
|
100
121
|
this._policyEnforcer = null;
|
|
122
|
+
this._bundledDependencies = {};
|
|
101
123
|
}
|
|
102
124
|
}
|
|
103
125
|
Registry._clientContext = null;
|
|
@@ -108,4 +130,5 @@ Registry._promptsCache = null;
|
|
|
108
130
|
Registry._toolDefinitionsCache = null;
|
|
109
131
|
Registry._serversCache = null;
|
|
110
132
|
Registry._policyEnforcer = null;
|
|
133
|
+
Registry._bundledDependencies = {};
|
|
111
134
|
export default Registry;
|
|
@@ -1,3 +1,12 @@
|
|
|
1
1
|
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
|
2
2
|
import { InitializeToolplexParams } from "../../shared/mcpServerTypes.js";
|
|
3
|
+
/**
|
|
4
|
+
* Helper to resolve dependency path with fallback:
|
|
5
|
+
* 1. Bundled dependency (if available)
|
|
6
|
+
* 2. System PATH (fallback)
|
|
7
|
+
* 3. "not available" if neither exists
|
|
8
|
+
*
|
|
9
|
+
* Exported for testing purposes.
|
|
10
|
+
*/
|
|
11
|
+
export declare function resolveDependencyForInit(bundledPath: string | undefined, commandName: string): string;
|
|
3
12
|
export declare function handleInitialize(params: InitializeToolplexParams): Promise<CallToolResult>;
|
|
@@ -3,7 +3,39 @@ import os from "os";
|
|
|
3
3
|
import { initServerManagersOnly } from "../utils/initServerManagers.js";
|
|
4
4
|
import Registry from "../registry.js";
|
|
5
5
|
import envPaths from "env-paths";
|
|
6
|
+
import which from "which";
|
|
7
|
+
import { getEnhancedPath } from "../../shared/enhancedPath.js";
|
|
8
|
+
import * as fs from "fs";
|
|
6
9
|
const logger = FileLogger;
|
|
10
|
+
/**
|
|
11
|
+
* Helper to resolve dependency path with fallback:
|
|
12
|
+
* 1. Bundled dependency (if available)
|
|
13
|
+
* 2. System PATH (fallback)
|
|
14
|
+
* 3. "not available" if neither exists
|
|
15
|
+
*
|
|
16
|
+
* Exported for testing purposes.
|
|
17
|
+
*/
|
|
18
|
+
export function resolveDependencyForInit(bundledPath, commandName) {
|
|
19
|
+
// Check bundled first
|
|
20
|
+
if (bundledPath && fs.existsSync(bundledPath)) {
|
|
21
|
+
return bundledPath;
|
|
22
|
+
}
|
|
23
|
+
// Fall back to system PATH
|
|
24
|
+
try {
|
|
25
|
+
const enhancedPath = getEnhancedPath();
|
|
26
|
+
const systemPath = which.sync(commandName, {
|
|
27
|
+
path: enhancedPath,
|
|
28
|
+
nothrow: true,
|
|
29
|
+
});
|
|
30
|
+
if (systemPath) {
|
|
31
|
+
return `${systemPath} (system)`;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
// Ignore errors, will return "not available"
|
|
36
|
+
}
|
|
37
|
+
return "not available";
|
|
38
|
+
}
|
|
7
39
|
export async function handleInitialize(params) {
|
|
8
40
|
const startTime = Date.now();
|
|
9
41
|
await logger.info("Initializing ToolPlex");
|
|
@@ -23,6 +55,8 @@ export async function handleInitialize(params) {
|
|
|
23
55
|
? "Windows"
|
|
24
56
|
: platform.charAt(0).toUpperCase() + platform.slice(1);
|
|
25
57
|
const paths = envPaths("ToolPlex", { suffix: "" });
|
|
58
|
+
// Get bundled dependency information
|
|
59
|
+
const bundledDeps = Registry.getBundledDependencies();
|
|
26
60
|
const systemInfo = {
|
|
27
61
|
os: `${osName} ${os.release()}`,
|
|
28
62
|
arch: os.arch(),
|
|
@@ -35,6 +69,14 @@ export async function handleInitialize(params) {
|
|
|
35
69
|
month: "long",
|
|
36
70
|
day: "numeric",
|
|
37
71
|
}),
|
|
72
|
+
// Resolve dependency paths with bundled > system > not available priority
|
|
73
|
+
nodePath: resolveDependencyForInit(bundledDeps.node, "node"),
|
|
74
|
+
npxPath: resolveDependencyForInit(bundledDeps.npx, "npx"),
|
|
75
|
+
pythonPath: resolveDependencyForInit(bundledDeps.python, platform === "win32" ? "python" : "python3"),
|
|
76
|
+
pipPath: resolveDependencyForInit(bundledDeps.pip, platform === "win32" ? "pip" : "pip3"),
|
|
77
|
+
uvPath: resolveDependencyForInit(bundledDeps.uv, "uv"),
|
|
78
|
+
uvxPath: resolveDependencyForInit(bundledDeps.uvx, "uvx"),
|
|
79
|
+
gitPath: resolveDependencyForInit(bundledDeps.git, "git"),
|
|
38
80
|
};
|
|
39
81
|
await logger.debug("Initializing server managers and API service");
|
|
40
82
|
const [serverManagerInitResults, toolplexApiInitResponse] = await Promise.all([
|
|
@@ -76,7 +118,14 @@ export async function handleInitialize(params) {
|
|
|
76
118
|
.replace("{ARGS.memory}", systemInfo.memory)
|
|
77
119
|
.replace("{ARGS.cpuCores}", systemInfo.cpuCores.toString())
|
|
78
120
|
.replace("{ARGS.workDir}", systemInfo.workDir)
|
|
79
|
-
.replace("{ARGS.date}", systemInfo.date)
|
|
121
|
+
.replace("{ARGS.date}", systemInfo.date)
|
|
122
|
+
.replace("{ARGS.nodePath}", systemInfo.nodePath)
|
|
123
|
+
.replace("{ARGS.npxPath}", systemInfo.npxPath)
|
|
124
|
+
.replace("{ARGS.pythonPath}", systemInfo.pythonPath)
|
|
125
|
+
.replace("{ARGS.pipPath}", systemInfo.pipPath)
|
|
126
|
+
.replace("{ARGS.uvPath}", systemInfo.uvPath)
|
|
127
|
+
.replace("{ARGS.uvxPath}", systemInfo.uvxPath)
|
|
128
|
+
.replace("{ARGS.gitPath}", systemInfo.gitPath),
|
|
80
129
|
},
|
|
81
130
|
],
|
|
82
131
|
};
|
|
@@ -121,10 +121,10 @@ export async function handleSearchTool(params) {
|
|
|
121
121
|
type: "text",
|
|
122
122
|
text: JSON.stringify(responseData),
|
|
123
123
|
},
|
|
124
|
-
// Second: Followup instructions
|
|
125
124
|
{
|
|
126
125
|
type: "text",
|
|
127
126
|
text: promptsCache.getPrompt("search_results_footer"),
|
|
127
|
+
_meta: { role: "system" },
|
|
128
128
|
},
|
|
129
129
|
];
|
|
130
130
|
await logger.info("Search completed successfully");
|
|
@@ -34,6 +34,11 @@ export async function serve(config) {
|
|
|
34
34
|
clientContext.clientName = config.clientName;
|
|
35
35
|
clientContext.clientVersion = clientVersion;
|
|
36
36
|
await Registry.init(clientContext);
|
|
37
|
+
// Store bundled dependencies in Registry for use throughout the application
|
|
38
|
+
if (config.bundledDependencies) {
|
|
39
|
+
Registry.setBundledDependencies(config.bundledDependencies);
|
|
40
|
+
await logger.debug(`Bundled dependencies registered: ${JSON.stringify(config.bundledDependencies)}`);
|
|
41
|
+
}
|
|
37
42
|
await logger.info(`Starting Toolplex server in ${config.dev ? "development" : "production"} mode`);
|
|
38
43
|
const server = new Server({
|
|
39
44
|
name: "toolplex-server",
|
|
@@ -1,4 +1,22 @@
|
|
|
1
1
|
export declare class RuntimeCheck {
|
|
2
|
+
/**
|
|
3
|
+
* Resolve a dependency path with priority order:
|
|
4
|
+
* 1. Bundled dependencies (if provided by host application like ToolPlex Desktop)
|
|
5
|
+
* 2. System PATH (fallback for standalone @client usage)
|
|
6
|
+
* 3. Error if neither available
|
|
7
|
+
*
|
|
8
|
+
* This allows ToolPlex Desktop to provide reliable bundled dependencies while
|
|
9
|
+
* still supporting standalone users who have system dependencies installed.
|
|
10
|
+
*
|
|
11
|
+
* @param commandName - The command to resolve
|
|
12
|
+
* @returns The full path to the command executable
|
|
13
|
+
* @throws Error if the command is not available in bundled deps or system PATH
|
|
14
|
+
*/
|
|
15
|
+
static resolveDependency(commandName: string): string;
|
|
16
|
+
/**
|
|
17
|
+
* Validate that a command is available (either bundled or in system PATH).
|
|
18
|
+
* Throws an error if the command is not found.
|
|
19
|
+
*/
|
|
2
20
|
static validateCommandOrThrow(rawCommand: string): void;
|
|
3
21
|
static extractCommandName(command: string): string;
|
|
4
22
|
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { getEnhancedPath } from "../../shared/enhancedPath.js";
|
|
2
2
|
import which from "which";
|
|
3
|
+
import Registry from "../registry.js";
|
|
4
|
+
import * as fs from "fs";
|
|
3
5
|
const INSTALL_HINTS = {
|
|
4
6
|
uvx: "Install uvx: https://docs.astral.sh/uv/getting-started/installation/",
|
|
5
7
|
uv: "Install uv: https://docs.astral.sh/uv/getting-started/installation/",
|
|
@@ -7,22 +9,84 @@ const INSTALL_HINTS = {
|
|
|
7
9
|
python3: "Install Python: https://www.python.org/downloads/. Or check if you have `python` installed.",
|
|
8
10
|
node: "Install Node.js: https://nodejs.org/en/download/",
|
|
9
11
|
npx: "Install npx (comes with Node.js): https://nodejs.org/en/download/",
|
|
12
|
+
git: "Install Git: https://git-scm.com/downloads",
|
|
10
13
|
};
|
|
14
|
+
// Commands that should use bundled dependencies (required)
|
|
15
|
+
const BUNDLED_DEPENDENCY_COMMANDS = [
|
|
16
|
+
"node",
|
|
17
|
+
"python",
|
|
18
|
+
"python3",
|
|
19
|
+
"git",
|
|
20
|
+
"npx",
|
|
21
|
+
"uvx",
|
|
22
|
+
];
|
|
11
23
|
export class RuntimeCheck {
|
|
12
|
-
|
|
13
|
-
|
|
24
|
+
/**
|
|
25
|
+
* Resolve a dependency path with priority order:
|
|
26
|
+
* 1. Bundled dependencies (if provided by host application like ToolPlex Desktop)
|
|
27
|
+
* 2. System PATH (fallback for standalone @client usage)
|
|
28
|
+
* 3. Error if neither available
|
|
29
|
+
*
|
|
30
|
+
* This allows ToolPlex Desktop to provide reliable bundled dependencies while
|
|
31
|
+
* still supporting standalone users who have system dependencies installed.
|
|
32
|
+
*
|
|
33
|
+
* @param commandName - The command to resolve
|
|
34
|
+
* @returns The full path to the command executable
|
|
35
|
+
* @throws Error if the command is not available in bundled deps or system PATH
|
|
36
|
+
*/
|
|
37
|
+
static resolveDependency(commandName) {
|
|
38
|
+
// Check if this is a known bundled dependency type
|
|
39
|
+
const isBundledDep = BUNDLED_DEPENDENCY_COMMANDS.includes(commandName);
|
|
40
|
+
if (isBundledDep) {
|
|
41
|
+
// Priority 1: Try bundled dependency first (preferred for ToolPlex Desktop)
|
|
42
|
+
const bundledPath = Registry.getBundledDependencyPath(commandName);
|
|
43
|
+
if (bundledPath && fs.existsSync(bundledPath)) {
|
|
44
|
+
return bundledPath;
|
|
45
|
+
}
|
|
46
|
+
// Handle python3 -> python mapping for bundled deps
|
|
47
|
+
if (commandName === "python3") {
|
|
48
|
+
const pythonPath = Registry.getBundledDependencyPath("python");
|
|
49
|
+
if (pythonPath && fs.existsSync(pythonPath)) {
|
|
50
|
+
return pythonPath;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
// Priority 2: Fall back to system PATH (for standalone @client usage)
|
|
54
|
+
const enhancedPath = getEnhancedPath();
|
|
55
|
+
const resolved = which.sync(commandName, {
|
|
56
|
+
path: enhancedPath,
|
|
57
|
+
nothrow: true,
|
|
58
|
+
});
|
|
59
|
+
if (resolved) {
|
|
60
|
+
return resolved;
|
|
61
|
+
}
|
|
62
|
+
// Priority 3: Neither bundled nor system available - error
|
|
63
|
+
const hint = INSTALL_HINTS[commandName];
|
|
64
|
+
throw new Error(`Missing required command: '${commandName}'.\n` +
|
|
65
|
+
`This command is not available in bundled dependencies or system PATH.\n` +
|
|
66
|
+
(hint ? `👉 ${hint}` : ""));
|
|
67
|
+
}
|
|
68
|
+
// For non-bundled dependencies, only check system PATH
|
|
14
69
|
const enhancedPath = getEnhancedPath();
|
|
15
|
-
const resolved = which.sync(
|
|
70
|
+
const resolved = which.sync(commandName, {
|
|
16
71
|
path: enhancedPath,
|
|
17
72
|
nothrow: true,
|
|
18
73
|
});
|
|
19
74
|
if (!resolved) {
|
|
20
|
-
const hint = INSTALL_HINTS[
|
|
75
|
+
const hint = INSTALL_HINTS[commandName];
|
|
21
76
|
if (hint) {
|
|
22
|
-
throw new Error(`Missing required command: '${
|
|
77
|
+
throw new Error(`Missing required command: '${commandName}'.\n👉 ${hint}`);
|
|
23
78
|
}
|
|
24
|
-
throw new Error(`Command '${
|
|
79
|
+
throw new Error(`Command '${commandName}' not found in enhanced PATH. Please install it manually or check your config.`);
|
|
25
80
|
}
|
|
81
|
+
return resolved;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Validate that a command is available (either bundled or in system PATH).
|
|
85
|
+
* Throws an error if the command is not found.
|
|
86
|
+
*/
|
|
87
|
+
static validateCommandOrThrow(rawCommand) {
|
|
88
|
+
const command = this.extractCommandName(rawCommand);
|
|
89
|
+
this.resolveDependency(command);
|
|
26
90
|
}
|
|
27
91
|
static extractCommandName(command) {
|
|
28
92
|
return command.trim().split(/\s+/)[0];
|
|
@@ -3,7 +3,6 @@ import { StdioClientTransport, } from "@modelcontextprotocol/sdk/client/stdio.js
|
|
|
3
3
|
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
|
|
4
4
|
import * as fs from "fs/promises";
|
|
5
5
|
import * as path from "path";
|
|
6
|
-
import which from "which";
|
|
7
6
|
import { FileLogger } from "../shared/fileLogger.js";
|
|
8
7
|
import envPaths from "env-paths";
|
|
9
8
|
import { getEnhancedPath } from "../shared/enhancedPath.js";
|
|
@@ -204,15 +203,10 @@ export class ServerManager {
|
|
|
204
203
|
else if (config.transport === "stdio") {
|
|
205
204
|
if (!config.command)
|
|
206
205
|
throw new Error("Command is required for stdio transport");
|
|
206
|
+
// Use RuntimeCheck to resolve the command, which prioritizes bundled dependencies
|
|
207
|
+
const { RuntimeCheck } = await import("../mcp-server/utils/runtimeCheck.js");
|
|
208
|
+
const resolvedCommand = RuntimeCheck.resolveDependency(config.command);
|
|
207
209
|
const enhancedPath = getEnhancedPath();
|
|
208
|
-
let resolvedCommand = which.sync(config.command, {
|
|
209
|
-
path: enhancedPath,
|
|
210
|
-
nothrow: true,
|
|
211
|
-
});
|
|
212
|
-
if (!resolvedCommand) {
|
|
213
|
-
// Fallback to supplied command
|
|
214
|
-
resolvedCommand = config.command;
|
|
215
|
-
}
|
|
216
210
|
const serverParams = {
|
|
217
211
|
command: resolvedCommand,
|
|
218
212
|
args: config.args || [],
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as path from "path";
|
|
2
|
-
import
|
|
2
|
+
import * as fs from "fs";
|
|
3
3
|
import { homedir } from "os";
|
|
4
4
|
import { glob } from "glob";
|
|
5
5
|
/**
|
|
@@ -18,14 +18,14 @@ export function getEnhancedPath() {
|
|
|
18
18
|
if (extraPath.includes("*")) {
|
|
19
19
|
const matches = glob.sync(extraPath);
|
|
20
20
|
for (const match of matches) {
|
|
21
|
-
if (existsSync(match) && !seen.has(match)) {
|
|
21
|
+
if (fs.existsSync(match) && !seen.has(match)) {
|
|
22
22
|
seen.add(match);
|
|
23
23
|
allPaths.unshift(match);
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
27
|
else {
|
|
28
|
-
if (existsSync(extraPath) && !seen.has(extraPath)) {
|
|
28
|
+
if (fs.existsSync(extraPath) && !seen.has(extraPath)) {
|
|
29
29
|
seen.add(extraPath);
|
|
30
30
|
allPaths.unshift(extraPath);
|
|
31
31
|
}
|
|
@@ -1,12 +1,26 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
export type ClientMode = "standard" | "restricted";
|
|
3
3
|
export type LogLevel = "error" | "warn" | "info" | "debug";
|
|
4
|
+
/**
|
|
5
|
+
* Paths to bundled dependencies provided by the host application (e.g., Electron).
|
|
6
|
+
* These dependencies are required for MCP server installations and execution.
|
|
7
|
+
*/
|
|
8
|
+
export interface BundledDependencies {
|
|
9
|
+
node?: string;
|
|
10
|
+
npx?: string;
|
|
11
|
+
python?: string;
|
|
12
|
+
pip?: string;
|
|
13
|
+
uv?: string;
|
|
14
|
+
uvx?: string;
|
|
15
|
+
git?: string;
|
|
16
|
+
}
|
|
4
17
|
export interface ToolplexServerConfig {
|
|
5
18
|
dev: boolean;
|
|
6
19
|
apiKey: string;
|
|
7
20
|
clientMode: ClientMode;
|
|
8
21
|
clientName: string;
|
|
9
22
|
logLevel: LogLevel;
|
|
23
|
+
bundledDependencies?: BundledDependencies;
|
|
10
24
|
}
|
|
11
25
|
export declare const TransportTypeSchema: z.ZodEnum<["stdio", "sse"]>;
|
|
12
26
|
export type TransportType = z.infer<typeof TransportTypeSchema>;
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Maintains client context for the ToolPlex server
|
|
3
|
+
*/
|
|
4
|
+
export class ClientContext {
|
|
5
|
+
constructor() {
|
|
6
|
+
this._sessionId = null;
|
|
7
|
+
this._dev = null;
|
|
8
|
+
this._apiKey = null;
|
|
9
|
+
this._clientMode = null;
|
|
10
|
+
this._llmContext = null;
|
|
11
|
+
this._clientVersion = null;
|
|
12
|
+
this._permissions = null;
|
|
13
|
+
this._flags = null;
|
|
14
|
+
this._isOrgUser = null;
|
|
15
|
+
this._clientName = null;
|
|
16
|
+
}
|
|
17
|
+
get sessionId() {
|
|
18
|
+
if (!this._sessionId) {
|
|
19
|
+
throw new Error("Session ID not set - ToolPlex not initialized");
|
|
20
|
+
}
|
|
21
|
+
return this._sessionId;
|
|
22
|
+
}
|
|
23
|
+
set sessionId(id) {
|
|
24
|
+
this._sessionId = id;
|
|
25
|
+
}
|
|
26
|
+
get dev() {
|
|
27
|
+
if (this._dev === null) {
|
|
28
|
+
throw new Error("Dev mode not set - ToolPlex not initialized");
|
|
29
|
+
}
|
|
30
|
+
return this._dev;
|
|
31
|
+
}
|
|
32
|
+
set dev(isDev) {
|
|
33
|
+
this._dev = isDev;
|
|
34
|
+
}
|
|
35
|
+
get apiKey() {
|
|
36
|
+
if (!this._apiKey) {
|
|
37
|
+
throw new Error("API key not set - ToolPlex not initialized");
|
|
38
|
+
}
|
|
39
|
+
return this._apiKey;
|
|
40
|
+
}
|
|
41
|
+
set apiKey(key) {
|
|
42
|
+
this._apiKey = key;
|
|
43
|
+
}
|
|
44
|
+
get clientMode() {
|
|
45
|
+
if (!this._clientMode) {
|
|
46
|
+
throw new Error("Client mode not set - ToolPlex not initialized");
|
|
47
|
+
}
|
|
48
|
+
return this._clientMode;
|
|
49
|
+
}
|
|
50
|
+
set clientMode(mode) {
|
|
51
|
+
this._clientMode = mode;
|
|
52
|
+
}
|
|
53
|
+
get clientName() {
|
|
54
|
+
if (!this._clientName) {
|
|
55
|
+
throw new Error("Client name not set - ToolPlex not initialized");
|
|
56
|
+
}
|
|
57
|
+
return this._clientName;
|
|
58
|
+
}
|
|
59
|
+
set clientName(name) {
|
|
60
|
+
this._clientName = name;
|
|
61
|
+
}
|
|
62
|
+
get llmContext() {
|
|
63
|
+
if (!this._llmContext) {
|
|
64
|
+
throw new Error("LLM context not set - ToolPlex not initialized");
|
|
65
|
+
}
|
|
66
|
+
return this._llmContext;
|
|
67
|
+
}
|
|
68
|
+
set llmContext(context) {
|
|
69
|
+
this._llmContext = context;
|
|
70
|
+
}
|
|
71
|
+
get clientVersion() {
|
|
72
|
+
if (!this._clientVersion) {
|
|
73
|
+
throw new Error("Client version not set - ToolPlex not initialized");
|
|
74
|
+
}
|
|
75
|
+
return this._clientVersion;
|
|
76
|
+
}
|
|
77
|
+
set clientVersion(version) {
|
|
78
|
+
this._clientVersion = version;
|
|
79
|
+
}
|
|
80
|
+
get permissions() {
|
|
81
|
+
if (!this._permissions) {
|
|
82
|
+
throw new Error("Permissions not set - ToolPlex not initialized");
|
|
83
|
+
}
|
|
84
|
+
return this._permissions;
|
|
85
|
+
}
|
|
86
|
+
set permissions(perms) {
|
|
87
|
+
this._permissions = perms;
|
|
88
|
+
}
|
|
89
|
+
get flags() {
|
|
90
|
+
if (!this._flags) {
|
|
91
|
+
throw new Error("Consts not set - ToolPlex not initialized");
|
|
92
|
+
}
|
|
93
|
+
return this._flags;
|
|
94
|
+
}
|
|
95
|
+
set flags(consts) {
|
|
96
|
+
this._flags = consts;
|
|
97
|
+
}
|
|
98
|
+
get isOrgUser() {
|
|
99
|
+
if (this._isOrgUser === null) {
|
|
100
|
+
throw new Error("Organization user status not set - ToolPlex not initialized");
|
|
101
|
+
}
|
|
102
|
+
return this._isOrgUser;
|
|
103
|
+
}
|
|
104
|
+
set isOrgUser(isOrgUser) {
|
|
105
|
+
this._isOrgUser = isOrgUser;
|
|
106
|
+
}
|
|
107
|
+
isInitialized() {
|
|
108
|
+
return !!(this._sessionId &&
|
|
109
|
+
this._apiKey &&
|
|
110
|
+
this._clientMode &&
|
|
111
|
+
this._llmContext &&
|
|
112
|
+
this._clientVersion &&
|
|
113
|
+
this._permissions &&
|
|
114
|
+
this._flags &&
|
|
115
|
+
this._isOrgUser !== null &&
|
|
116
|
+
this._clientName);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import Registry from "../registry.js";
|
|
2
|
+
import { FileLogger } from "../../shared/fileLogger.js";
|
|
3
|
+
const logger = FileLogger;
|
|
4
|
+
export class TelemetryLogger {
|
|
5
|
+
constructor() {
|
|
6
|
+
this.eventQueue = [];
|
|
7
|
+
this.flushTimeout = null;
|
|
8
|
+
this.BATCH_SIZE = 10;
|
|
9
|
+
this.FLUSH_INTERVAL = 30000;
|
|
10
|
+
this.scheduleFlush();
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Log a telemetry event
|
|
14
|
+
*/
|
|
15
|
+
async log(eventType, data) {
|
|
16
|
+
this.eventQueue.push({ eventType, data });
|
|
17
|
+
if (this.eventQueue.length >= this.BATCH_SIZE) {
|
|
18
|
+
await this.flush();
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
async flush() {
|
|
22
|
+
if (this.eventQueue.length === 0)
|
|
23
|
+
return;
|
|
24
|
+
try {
|
|
25
|
+
const apiService = Registry.getToolplexApiService();
|
|
26
|
+
const events = [...this.eventQueue];
|
|
27
|
+
this.eventQueue = [];
|
|
28
|
+
await logger.debug(`Flushing ${events.length} telemetry events`);
|
|
29
|
+
await apiService.logTelemetryEvents(events);
|
|
30
|
+
}
|
|
31
|
+
catch (err) {
|
|
32
|
+
await logger.error(`Error flushing telemetry events: ${err}`);
|
|
33
|
+
this.eventQueue.unshift(...this.eventQueue);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
scheduleFlush() {
|
|
37
|
+
if (this.flushTimeout) {
|
|
38
|
+
clearTimeout(this.flushTimeout);
|
|
39
|
+
}
|
|
40
|
+
this.flushTimeout = setTimeout(async () => {
|
|
41
|
+
await this.flush();
|
|
42
|
+
this.scheduleFlush();
|
|
43
|
+
}, this.FLUSH_INTERVAL);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Clean up resources - should be called when shutting down
|
|
47
|
+
*/
|
|
48
|
+
async dispose() {
|
|
49
|
+
if (this.flushTimeout) {
|
|
50
|
+
clearTimeout(this.flushTimeout);
|
|
51
|
+
}
|
|
52
|
+
await this.flush();
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
class CallToolObserver {
|
|
2
|
+
constructor() {
|
|
3
|
+
this.serverToolCalls = new Map();
|
|
4
|
+
}
|
|
5
|
+
// Record a call to a tool on a server
|
|
6
|
+
recordCall(serverId, toolName) {
|
|
7
|
+
if (!this.serverToolCalls.has(serverId)) {
|
|
8
|
+
this.serverToolCalls.set(serverId, new Set());
|
|
9
|
+
}
|
|
10
|
+
this.serverToolCalls.get(serverId).add(toolName);
|
|
11
|
+
}
|
|
12
|
+
// Check if a server was called at all
|
|
13
|
+
wasServerCalled(serverId) {
|
|
14
|
+
return (this.serverToolCalls.has(serverId) &&
|
|
15
|
+
this.serverToolCalls.get(serverId).size > 0);
|
|
16
|
+
}
|
|
17
|
+
// Check if a specific tool was called on a server
|
|
18
|
+
wasToolCalled(serverId, toolName) {
|
|
19
|
+
return (this.serverToolCalls.has(serverId) &&
|
|
20
|
+
this.serverToolCalls.get(serverId).has(toolName));
|
|
21
|
+
}
|
|
22
|
+
// Optionally, clear all records (for testing or reset)
|
|
23
|
+
clear() {
|
|
24
|
+
this.serverToolCalls.clear();
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export default CallToolObserver;
|