@wingman-ai/gateway 0.3.1 → 0.4.0
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/agent/config/mcpClientManager.cjs +48 -9
- package/dist/agent/config/mcpClientManager.d.ts +12 -0
- package/dist/agent/config/mcpClientManager.js +48 -9
- package/dist/agent/tests/internet_search.test.cjs +22 -28
- package/dist/agent/tests/internet_search.test.js +22 -28
- package/dist/agent/tests/mcpClientManager.test.cjs +50 -0
- package/dist/agent/tests/mcpClientManager.test.js +50 -0
- package/dist/agent/tools/internet_search.cjs +9 -5
- package/dist/agent/tools/internet_search.js +9 -5
- package/dist/cli/commands/skill.cjs +12 -4
- package/dist/cli/commands/skill.js +12 -4
- package/dist/cli/config/jsonSchema.cjs +55 -0
- package/dist/cli/config/jsonSchema.d.ts +2 -0
- package/dist/cli/config/jsonSchema.js +18 -0
- package/dist/cli/config/loader.cjs +33 -1
- package/dist/cli/config/loader.js +33 -1
- package/dist/cli/config/schema.cjs +119 -2
- package/dist/cli/config/schema.d.ts +40 -0
- package/dist/cli/config/schema.js +119 -2
- package/dist/cli/core/agentInvoker.cjs +4 -1
- package/dist/cli/core/agentInvoker.d.ts +3 -0
- package/dist/cli/core/agentInvoker.js +4 -1
- package/dist/cli/services/skillRepository.cjs +138 -20
- package/dist/cli/services/skillRepository.d.ts +10 -2
- package/dist/cli/services/skillRepository.js +138 -20
- package/dist/cli/services/skillSecurityScanner.cjs +158 -0
- package/dist/cli/services/skillSecurityScanner.d.ts +28 -0
- package/dist/cli/services/skillSecurityScanner.js +121 -0
- package/dist/cli/services/skillService.cjs +44 -12
- package/dist/cli/services/skillService.d.ts +2 -0
- package/dist/cli/services/skillService.js +46 -14
- package/dist/cli/types/skill.d.ts +9 -0
- package/dist/gateway/server.cjs +5 -1
- package/dist/gateway/server.js +5 -1
- package/dist/gateway/types.d.ts +9 -0
- package/dist/tests/cli-config-loader.test.cjs +33 -1
- package/dist/tests/cli-config-loader.test.js +33 -1
- package/dist/tests/config-json-schema.test.cjs +25 -0
- package/dist/tests/config-json-schema.test.d.ts +1 -0
- package/dist/tests/config-json-schema.test.js +19 -0
- package/dist/tests/skill-repository.test.cjs +106 -0
- package/dist/tests/skill-repository.test.d.ts +1 -0
- package/dist/tests/skill-repository.test.js +100 -0
- package/dist/tests/skill-security-scanner.test.cjs +126 -0
- package/dist/tests/skill-security-scanner.test.d.ts +1 -0
- package/dist/tests/skill-security-scanner.test.js +120 -0
- package/dist/tests/uv.test.cjs +47 -0
- package/dist/tests/uv.test.d.ts +1 -0
- package/dist/tests/uv.test.js +41 -0
- package/dist/utils/uv.cjs +64 -0
- package/dist/utils/uv.d.ts +3 -0
- package/dist/utils/uv.js +24 -0
- package/dist/webui/assets/index-Cwkg4DKj.css +11 -0
- package/dist/webui/assets/{index-C8-oboEC.js → index-DHbfLOUR.js} +21 -19
- package/dist/webui/index.html +2 -2
- package/package.json +2 -3
- package/skills/gog/SKILL.md +36 -0
- package/skills/weather/SKILL.md +49 -0
- package/dist/webui/assets/index-BW9nM0J2.css +0 -11
|
@@ -52,15 +52,7 @@ class MCPClientManager {
|
|
|
52
52
|
for (const [key, value] of Object.entries(stdioServer.env || {}))resolvedEnv[key] = resolveEnvValue(value);
|
|
53
53
|
const runtimeEnv = this.applyRuntimeEnv(resolvedEnv);
|
|
54
54
|
const defaultToolTimeout = getDefaultToolTimeout(stdioServer);
|
|
55
|
-
mcpServers[server.name] =
|
|
56
|
-
transport: "stdio",
|
|
57
|
-
command: stdioServer.command,
|
|
58
|
-
args: stdioServer.args || [],
|
|
59
|
-
env: runtimeEnv,
|
|
60
|
-
...void 0 !== defaultToolTimeout ? {
|
|
61
|
-
defaultToolTimeout
|
|
62
|
-
} : {}
|
|
63
|
-
};
|
|
55
|
+
mcpServers[server.name] = this.buildStdioServerConfig(stdioServer, runtimeEnv, defaultToolTimeout);
|
|
64
56
|
} else if ("sse" === server.transport) {
|
|
65
57
|
const sseServer = server;
|
|
66
58
|
const defaultToolTimeout = getDefaultToolTimeout(sseServer);
|
|
@@ -83,6 +75,51 @@ class MCPClientManager {
|
|
|
83
75
|
}
|
|
84
76
|
};
|
|
85
77
|
}
|
|
78
|
+
buildStdioServerConfig(server, env, defaultToolTimeout) {
|
|
79
|
+
const baseConfig = {
|
|
80
|
+
transport: "stdio",
|
|
81
|
+
command: server.command,
|
|
82
|
+
args: server.args || [],
|
|
83
|
+
env,
|
|
84
|
+
...void 0 !== defaultToolTimeout ? {
|
|
85
|
+
defaultToolTimeout
|
|
86
|
+
} : {}
|
|
87
|
+
};
|
|
88
|
+
if (!this.proxyConfig?.enabled) return baseConfig;
|
|
89
|
+
const proxyCommand = this.proxyConfig.command?.trim() || "uvx";
|
|
90
|
+
const proxyBaseArgs = this.proxyConfig.baseArgs && this.proxyConfig.baseArgs.length > 0 ? this.proxyConfig.baseArgs : [
|
|
91
|
+
"invariant-gateway@latest",
|
|
92
|
+
"mcp"
|
|
93
|
+
];
|
|
94
|
+
const proxyEnv = {
|
|
95
|
+
...env
|
|
96
|
+
};
|
|
97
|
+
if (this.proxyConfig.apiKey) proxyEnv.INVARIANT_API_KEY = this.proxyConfig.apiKey;
|
|
98
|
+
if (this.proxyConfig.apiUrl) {
|
|
99
|
+
proxyEnv.INVARIANT_API_URL = this.proxyConfig.apiUrl;
|
|
100
|
+
proxyEnv.GUARDRAILS_API_URL = this.proxyConfig.apiUrl;
|
|
101
|
+
}
|
|
102
|
+
const proxyArgs = [
|
|
103
|
+
...proxyBaseArgs,
|
|
104
|
+
"--project-name",
|
|
105
|
+
this.proxyConfig.projectName || "wingman-gateway",
|
|
106
|
+
...this.proxyConfig.pushExplorer ? [
|
|
107
|
+
"--push-explorer"
|
|
108
|
+
] : [],
|
|
109
|
+
"--exec",
|
|
110
|
+
baseConfig.command,
|
|
111
|
+
...baseConfig.args || []
|
|
112
|
+
];
|
|
113
|
+
return {
|
|
114
|
+
transport: "stdio",
|
|
115
|
+
command: proxyCommand,
|
|
116
|
+
args: proxyArgs,
|
|
117
|
+
env: proxyEnv,
|
|
118
|
+
...void 0 !== defaultToolTimeout ? {
|
|
119
|
+
defaultToolTimeout
|
|
120
|
+
} : {}
|
|
121
|
+
};
|
|
122
|
+
}
|
|
86
123
|
applyRuntimeEnv(env) {
|
|
87
124
|
if (!this.executionWorkspace) return env;
|
|
88
125
|
const next = {
|
|
@@ -176,9 +213,11 @@ class MCPClientManager {
|
|
|
176
213
|
_define_property(this, "logger", void 0);
|
|
177
214
|
_define_property(this, "serverConfigs", void 0);
|
|
178
215
|
_define_property(this, "executionWorkspace", void 0);
|
|
216
|
+
_define_property(this, "proxyConfig", void 0);
|
|
179
217
|
this.logger = logger;
|
|
180
218
|
this.serverConfigs = this.mergeConfigs(configs);
|
|
181
219
|
this.executionWorkspace = options?.executionWorkspace?.trim() || null;
|
|
220
|
+
this.proxyConfig = options?.proxyConfig;
|
|
182
221
|
}
|
|
183
222
|
}
|
|
184
223
|
function resolveEnvValue(value) {
|
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
import type { StructuredTool } from "@langchain/core/tools";
|
|
2
2
|
import type { Logger } from "@/logger.js";
|
|
3
3
|
import type { MCPServersConfig } from "@/types/mcp.js";
|
|
4
|
+
export type MCPProxyConfig = {
|
|
5
|
+
enabled?: boolean;
|
|
6
|
+
command?: string;
|
|
7
|
+
baseArgs?: string[];
|
|
8
|
+
projectName?: string;
|
|
9
|
+
pushExplorer?: boolean;
|
|
10
|
+
apiKey?: string;
|
|
11
|
+
apiUrl?: string;
|
|
12
|
+
};
|
|
4
13
|
/**
|
|
5
14
|
* Manages MCP server connections and tool retrieval
|
|
6
15
|
* Handles server lifecycle: initialization, tool loading, and cleanup
|
|
@@ -10,8 +19,10 @@ export declare class MCPClientManager {
|
|
|
10
19
|
private logger;
|
|
11
20
|
private serverConfigs;
|
|
12
21
|
private executionWorkspace;
|
|
22
|
+
private proxyConfig;
|
|
13
23
|
constructor(configs: MCPServersConfig[], logger: Logger, options?: {
|
|
14
24
|
executionWorkspace?: string | null;
|
|
25
|
+
proxyConfig?: MCPProxyConfig;
|
|
15
26
|
});
|
|
16
27
|
/**
|
|
17
28
|
* Merge multiple MCP configurations (global + agent-specific)
|
|
@@ -22,6 +33,7 @@ export declare class MCPClientManager {
|
|
|
22
33
|
* Convert Wingman MCP config to MultiServerMCPClient format
|
|
23
34
|
*/
|
|
24
35
|
private buildClientConfig;
|
|
36
|
+
private buildStdioServerConfig;
|
|
25
37
|
private applyRuntimeEnv;
|
|
26
38
|
/**
|
|
27
39
|
* Initialize MCP client and connect to servers
|
|
@@ -24,15 +24,7 @@ class MCPClientManager {
|
|
|
24
24
|
for (const [key, value] of Object.entries(stdioServer.env || {}))resolvedEnv[key] = resolveEnvValue(value);
|
|
25
25
|
const runtimeEnv = this.applyRuntimeEnv(resolvedEnv);
|
|
26
26
|
const defaultToolTimeout = getDefaultToolTimeout(stdioServer);
|
|
27
|
-
mcpServers[server.name] =
|
|
28
|
-
transport: "stdio",
|
|
29
|
-
command: stdioServer.command,
|
|
30
|
-
args: stdioServer.args || [],
|
|
31
|
-
env: runtimeEnv,
|
|
32
|
-
...void 0 !== defaultToolTimeout ? {
|
|
33
|
-
defaultToolTimeout
|
|
34
|
-
} : {}
|
|
35
|
-
};
|
|
27
|
+
mcpServers[server.name] = this.buildStdioServerConfig(stdioServer, runtimeEnv, defaultToolTimeout);
|
|
36
28
|
} else if ("sse" === server.transport) {
|
|
37
29
|
const sseServer = server;
|
|
38
30
|
const defaultToolTimeout = getDefaultToolTimeout(sseServer);
|
|
@@ -55,6 +47,51 @@ class MCPClientManager {
|
|
|
55
47
|
}
|
|
56
48
|
};
|
|
57
49
|
}
|
|
50
|
+
buildStdioServerConfig(server, env, defaultToolTimeout) {
|
|
51
|
+
const baseConfig = {
|
|
52
|
+
transport: "stdio",
|
|
53
|
+
command: server.command,
|
|
54
|
+
args: server.args || [],
|
|
55
|
+
env,
|
|
56
|
+
...void 0 !== defaultToolTimeout ? {
|
|
57
|
+
defaultToolTimeout
|
|
58
|
+
} : {}
|
|
59
|
+
};
|
|
60
|
+
if (!this.proxyConfig?.enabled) return baseConfig;
|
|
61
|
+
const proxyCommand = this.proxyConfig.command?.trim() || "uvx";
|
|
62
|
+
const proxyBaseArgs = this.proxyConfig.baseArgs && this.proxyConfig.baseArgs.length > 0 ? this.proxyConfig.baseArgs : [
|
|
63
|
+
"invariant-gateway@latest",
|
|
64
|
+
"mcp"
|
|
65
|
+
];
|
|
66
|
+
const proxyEnv = {
|
|
67
|
+
...env
|
|
68
|
+
};
|
|
69
|
+
if (this.proxyConfig.apiKey) proxyEnv.INVARIANT_API_KEY = this.proxyConfig.apiKey;
|
|
70
|
+
if (this.proxyConfig.apiUrl) {
|
|
71
|
+
proxyEnv.INVARIANT_API_URL = this.proxyConfig.apiUrl;
|
|
72
|
+
proxyEnv.GUARDRAILS_API_URL = this.proxyConfig.apiUrl;
|
|
73
|
+
}
|
|
74
|
+
const proxyArgs = [
|
|
75
|
+
...proxyBaseArgs,
|
|
76
|
+
"--project-name",
|
|
77
|
+
this.proxyConfig.projectName || "wingman-gateway",
|
|
78
|
+
...this.proxyConfig.pushExplorer ? [
|
|
79
|
+
"--push-explorer"
|
|
80
|
+
] : [],
|
|
81
|
+
"--exec",
|
|
82
|
+
baseConfig.command,
|
|
83
|
+
...baseConfig.args || []
|
|
84
|
+
];
|
|
85
|
+
return {
|
|
86
|
+
transport: "stdio",
|
|
87
|
+
command: proxyCommand,
|
|
88
|
+
args: proxyArgs,
|
|
89
|
+
env: proxyEnv,
|
|
90
|
+
...void 0 !== defaultToolTimeout ? {
|
|
91
|
+
defaultToolTimeout
|
|
92
|
+
} : {}
|
|
93
|
+
};
|
|
94
|
+
}
|
|
58
95
|
applyRuntimeEnv(env) {
|
|
59
96
|
if (!this.executionWorkspace) return env;
|
|
60
97
|
const next = {
|
|
@@ -148,9 +185,11 @@ class MCPClientManager {
|
|
|
148
185
|
_define_property(this, "logger", void 0);
|
|
149
186
|
_define_property(this, "serverConfigs", void 0);
|
|
150
187
|
_define_property(this, "executionWorkspace", void 0);
|
|
188
|
+
_define_property(this, "proxyConfig", void 0);
|
|
151
189
|
this.logger = logger;
|
|
152
190
|
this.serverConfigs = this.mergeConfigs(configs);
|
|
153
191
|
this.executionWorkspace = options?.executionWorkspace?.trim() || null;
|
|
192
|
+
this.proxyConfig = options?.proxyConfig;
|
|
154
193
|
}
|
|
155
194
|
}
|
|
156
195
|
function resolveEnvValue(value) {
|
|
@@ -1,16 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
var __webpack_exports__ = {};
|
|
3
3
|
const external_vitest_namespaceObject = require("vitest");
|
|
4
|
-
function _define_property(obj, key, value) {
|
|
5
|
-
if (key in obj) Object.defineProperty(obj, key, {
|
|
6
|
-
value: value,
|
|
7
|
-
enumerable: true,
|
|
8
|
-
configurable: true,
|
|
9
|
-
writable: true
|
|
10
|
-
});
|
|
11
|
-
else obj[key] = value;
|
|
12
|
-
return obj;
|
|
13
|
-
}
|
|
14
4
|
const originalEnv = {
|
|
15
5
|
...process.env
|
|
16
6
|
};
|
|
@@ -22,18 +12,14 @@ const loadSearchModule = async (overrides)=>{
|
|
|
22
12
|
for (const [key, value] of Object.entries(overrides))if (void 0 === value) delete process.env[key];
|
|
23
13
|
else process.env[key] = value;
|
|
24
14
|
external_vitest_namespaceObject.vi.resetModules();
|
|
25
|
-
const
|
|
26
|
-
external_vitest_namespaceObject.vi.doMock("
|
|
27
|
-
|
|
28
|
-
constructor(){
|
|
29
|
-
_define_property(this, "_call", callMock);
|
|
30
|
-
}
|
|
31
|
-
}
|
|
15
|
+
const searchMock = external_vitest_namespaceObject.vi.fn();
|
|
16
|
+
external_vitest_namespaceObject.vi.doMock("duck-duck-scrape", ()=>({
|
|
17
|
+
search: searchMock
|
|
32
18
|
}));
|
|
33
19
|
const module = await import("../tools/internet_search.cjs");
|
|
34
20
|
return {
|
|
35
21
|
...module,
|
|
36
|
-
|
|
22
|
+
searchMock
|
|
37
23
|
};
|
|
38
24
|
};
|
|
39
25
|
(0, external_vitest_namespaceObject.beforeEach)(()=>{
|
|
@@ -47,13 +33,21 @@ const loadSearchModule = async (overrides)=>{
|
|
|
47
33
|
});
|
|
48
34
|
(0, external_vitest_namespaceObject.describe)("Internet search tool (DuckDuckGo)", ()=>{
|
|
49
35
|
(0, external_vitest_namespaceObject.it)("retries on DDG anomaly and succeeds", async ()=>{
|
|
50
|
-
const { createInternetSearchTool,
|
|
36
|
+
const { createInternetSearchTool, searchMock } = await loadSearchModule({
|
|
51
37
|
WINGMAN_DDG_MIN_DELAY_MS: "0",
|
|
52
38
|
WINGMAN_DDG_BACKOFF_BASE_MS: "1",
|
|
53
39
|
WINGMAN_DDG_BACKOFF_MAX_MS: "1",
|
|
54
40
|
WINGMAN_DDG_MAX_RETRIES: "2"
|
|
55
41
|
});
|
|
56
|
-
|
|
42
|
+
searchMock.mockRejectedValueOnce(new Error("DDG detected an anomaly in the request, you are likely making requests too quickly.")).mockResolvedValueOnce({
|
|
43
|
+
results: [
|
|
44
|
+
{
|
|
45
|
+
title: "Wingman",
|
|
46
|
+
url: "https://wingman.ai",
|
|
47
|
+
description: "Agentic coding assistant"
|
|
48
|
+
}
|
|
49
|
+
]
|
|
50
|
+
});
|
|
57
51
|
const tool = createInternetSearchTool({
|
|
58
52
|
provider: "duckduckgo",
|
|
59
53
|
maxResults: 3
|
|
@@ -61,19 +55,19 @@ const loadSearchModule = async (overrides)=>{
|
|
|
61
55
|
const resultPromise = tool.invoke({
|
|
62
56
|
query: "test"
|
|
63
57
|
});
|
|
64
|
-
const expectation = (0, external_vitest_namespaceObject.expect)(resultPromise).resolves.
|
|
58
|
+
const expectation = (0, external_vitest_namespaceObject.expect)(resultPromise).resolves.toContain("Wingman");
|
|
65
59
|
await external_vitest_namespaceObject.vi.runAllTimersAsync();
|
|
66
60
|
await expectation;
|
|
67
|
-
(0, external_vitest_namespaceObject.expect)(
|
|
61
|
+
(0, external_vitest_namespaceObject.expect)(searchMock).toHaveBeenCalledTimes(2);
|
|
68
62
|
});
|
|
69
63
|
(0, external_vitest_namespaceObject.it)("throws a friendly error after retry exhaustion", async ()=>{
|
|
70
|
-
const { createInternetSearchTool,
|
|
64
|
+
const { createInternetSearchTool, searchMock } = await loadSearchModule({
|
|
71
65
|
WINGMAN_DDG_MIN_DELAY_MS: "0",
|
|
72
66
|
WINGMAN_DDG_BACKOFF_BASE_MS: "1",
|
|
73
67
|
WINGMAN_DDG_BACKOFF_MAX_MS: "1",
|
|
74
68
|
WINGMAN_DDG_MAX_RETRIES: "1"
|
|
75
69
|
});
|
|
76
|
-
|
|
70
|
+
searchMock.mockRejectedValue(new Error("DDG detected an anomaly in the request, you are likely making requests too quickly."));
|
|
77
71
|
const tool = createInternetSearchTool({
|
|
78
72
|
provider: "duckduckgo",
|
|
79
73
|
maxResults: 3
|
|
@@ -84,16 +78,16 @@ const loadSearchModule = async (overrides)=>{
|
|
|
84
78
|
const expectation = (0, external_vitest_namespaceObject.expect)(resultPromise).rejects.toThrow(/rate-limited|anomaly/i);
|
|
85
79
|
await external_vitest_namespaceObject.vi.runAllTimersAsync();
|
|
86
80
|
await expectation;
|
|
87
|
-
(0, external_vitest_namespaceObject.expect)(
|
|
81
|
+
(0, external_vitest_namespaceObject.expect)(searchMock).toHaveBeenCalledTimes(2);
|
|
88
82
|
});
|
|
89
83
|
(0, external_vitest_namespaceObject.it)("does not retry on non-anomaly errors", async ()=>{
|
|
90
|
-
const { createInternetSearchTool,
|
|
84
|
+
const { createInternetSearchTool, searchMock } = await loadSearchModule({
|
|
91
85
|
WINGMAN_DDG_MIN_DELAY_MS: "0",
|
|
92
86
|
WINGMAN_DDG_BACKOFF_BASE_MS: "1",
|
|
93
87
|
WINGMAN_DDG_BACKOFF_MAX_MS: "1",
|
|
94
88
|
WINGMAN_DDG_MAX_RETRIES: "2"
|
|
95
89
|
});
|
|
96
|
-
|
|
90
|
+
searchMock.mockRejectedValue(new Error("network down"));
|
|
97
91
|
const tool = createInternetSearchTool({
|
|
98
92
|
provider: "duckduckgo",
|
|
99
93
|
maxResults: 3
|
|
@@ -104,7 +98,7 @@ const loadSearchModule = async (overrides)=>{
|
|
|
104
98
|
const expectation = (0, external_vitest_namespaceObject.expect)(resultPromise).rejects.toThrow("network down");
|
|
105
99
|
await external_vitest_namespaceObject.vi.runAllTimersAsync();
|
|
106
100
|
await expectation;
|
|
107
|
-
(0, external_vitest_namespaceObject.expect)(
|
|
101
|
+
(0, external_vitest_namespaceObject.expect)(searchMock).toHaveBeenCalledTimes(1);
|
|
108
102
|
});
|
|
109
103
|
});
|
|
110
104
|
for(var __rspack_i in __webpack_exports__)exports[__rspack_i] = __webpack_exports__[__rspack_i];
|
|
@@ -1,14 +1,4 @@
|
|
|
1
1
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
-
function _define_property(obj, key, value) {
|
|
3
|
-
if (key in obj) Object.defineProperty(obj, key, {
|
|
4
|
-
value: value,
|
|
5
|
-
enumerable: true,
|
|
6
|
-
configurable: true,
|
|
7
|
-
writable: true
|
|
8
|
-
});
|
|
9
|
-
else obj[key] = value;
|
|
10
|
-
return obj;
|
|
11
|
-
}
|
|
12
2
|
const originalEnv = {
|
|
13
3
|
...process.env
|
|
14
4
|
};
|
|
@@ -20,18 +10,14 @@ const loadSearchModule = async (overrides)=>{
|
|
|
20
10
|
for (const [key, value] of Object.entries(overrides))if (void 0 === value) delete process.env[key];
|
|
21
11
|
else process.env[key] = value;
|
|
22
12
|
vi.resetModules();
|
|
23
|
-
const
|
|
24
|
-
vi.doMock("
|
|
25
|
-
|
|
26
|
-
constructor(){
|
|
27
|
-
_define_property(this, "_call", callMock);
|
|
28
|
-
}
|
|
29
|
-
}
|
|
13
|
+
const searchMock = vi.fn();
|
|
14
|
+
vi.doMock("duck-duck-scrape", ()=>({
|
|
15
|
+
search: searchMock
|
|
30
16
|
}));
|
|
31
17
|
const module = await import("../tools/internet_search.js");
|
|
32
18
|
return {
|
|
33
19
|
...module,
|
|
34
|
-
|
|
20
|
+
searchMock
|
|
35
21
|
};
|
|
36
22
|
};
|
|
37
23
|
beforeEach(()=>{
|
|
@@ -45,13 +31,21 @@ afterEach(()=>{
|
|
|
45
31
|
});
|
|
46
32
|
describe("Internet search tool (DuckDuckGo)", ()=>{
|
|
47
33
|
it("retries on DDG anomaly and succeeds", async ()=>{
|
|
48
|
-
const { createInternetSearchTool,
|
|
34
|
+
const { createInternetSearchTool, searchMock } = await loadSearchModule({
|
|
49
35
|
WINGMAN_DDG_MIN_DELAY_MS: "0",
|
|
50
36
|
WINGMAN_DDG_BACKOFF_BASE_MS: "1",
|
|
51
37
|
WINGMAN_DDG_BACKOFF_MAX_MS: "1",
|
|
52
38
|
WINGMAN_DDG_MAX_RETRIES: "2"
|
|
53
39
|
});
|
|
54
|
-
|
|
40
|
+
searchMock.mockRejectedValueOnce(new Error("DDG detected an anomaly in the request, you are likely making requests too quickly.")).mockResolvedValueOnce({
|
|
41
|
+
results: [
|
|
42
|
+
{
|
|
43
|
+
title: "Wingman",
|
|
44
|
+
url: "https://wingman.ai",
|
|
45
|
+
description: "Agentic coding assistant"
|
|
46
|
+
}
|
|
47
|
+
]
|
|
48
|
+
});
|
|
55
49
|
const tool = createInternetSearchTool({
|
|
56
50
|
provider: "duckduckgo",
|
|
57
51
|
maxResults: 3
|
|
@@ -59,19 +53,19 @@ describe("Internet search tool (DuckDuckGo)", ()=>{
|
|
|
59
53
|
const resultPromise = tool.invoke({
|
|
60
54
|
query: "test"
|
|
61
55
|
});
|
|
62
|
-
const expectation = expect(resultPromise).resolves.
|
|
56
|
+
const expectation = expect(resultPromise).resolves.toContain("Wingman");
|
|
63
57
|
await vi.runAllTimersAsync();
|
|
64
58
|
await expectation;
|
|
65
|
-
expect(
|
|
59
|
+
expect(searchMock).toHaveBeenCalledTimes(2);
|
|
66
60
|
});
|
|
67
61
|
it("throws a friendly error after retry exhaustion", async ()=>{
|
|
68
|
-
const { createInternetSearchTool,
|
|
62
|
+
const { createInternetSearchTool, searchMock } = await loadSearchModule({
|
|
69
63
|
WINGMAN_DDG_MIN_DELAY_MS: "0",
|
|
70
64
|
WINGMAN_DDG_BACKOFF_BASE_MS: "1",
|
|
71
65
|
WINGMAN_DDG_BACKOFF_MAX_MS: "1",
|
|
72
66
|
WINGMAN_DDG_MAX_RETRIES: "1"
|
|
73
67
|
});
|
|
74
|
-
|
|
68
|
+
searchMock.mockRejectedValue(new Error("DDG detected an anomaly in the request, you are likely making requests too quickly."));
|
|
75
69
|
const tool = createInternetSearchTool({
|
|
76
70
|
provider: "duckduckgo",
|
|
77
71
|
maxResults: 3
|
|
@@ -82,16 +76,16 @@ describe("Internet search tool (DuckDuckGo)", ()=>{
|
|
|
82
76
|
const expectation = expect(resultPromise).rejects.toThrow(/rate-limited|anomaly/i);
|
|
83
77
|
await vi.runAllTimersAsync();
|
|
84
78
|
await expectation;
|
|
85
|
-
expect(
|
|
79
|
+
expect(searchMock).toHaveBeenCalledTimes(2);
|
|
86
80
|
});
|
|
87
81
|
it("does not retry on non-anomaly errors", async ()=>{
|
|
88
|
-
const { createInternetSearchTool,
|
|
82
|
+
const { createInternetSearchTool, searchMock } = await loadSearchModule({
|
|
89
83
|
WINGMAN_DDG_MIN_DELAY_MS: "0",
|
|
90
84
|
WINGMAN_DDG_BACKOFF_BASE_MS: "1",
|
|
91
85
|
WINGMAN_DDG_BACKOFF_MAX_MS: "1",
|
|
92
86
|
WINGMAN_DDG_MAX_RETRIES: "2"
|
|
93
87
|
});
|
|
94
|
-
|
|
88
|
+
searchMock.mockRejectedValue(new Error("network down"));
|
|
95
89
|
const tool = createInternetSearchTool({
|
|
96
90
|
provider: "duckduckgo",
|
|
97
91
|
maxResults: 3
|
|
@@ -102,6 +96,6 @@ describe("Internet search tool (DuckDuckGo)", ()=>{
|
|
|
102
96
|
const expectation = expect(resultPromise).rejects.toThrow("network down");
|
|
103
97
|
await vi.runAllTimersAsync();
|
|
104
98
|
await expectation;
|
|
105
|
-
expect(
|
|
99
|
+
expect(searchMock).toHaveBeenCalledTimes(1);
|
|
106
100
|
});
|
|
107
101
|
});
|
|
@@ -117,6 +117,56 @@ const getClientConfig = (manager)=>manager.buildClientConfig();
|
|
|
117
117
|
const clientConfig = getClientConfig(manager);
|
|
118
118
|
(0, external_vitest_namespaceObject.expect)(clientConfig.mcpServers["fal-ai"].defaultToolTimeout).toBe(300000);
|
|
119
119
|
});
|
|
120
|
+
(0, external_vitest_namespaceObject.it)("wraps stdio servers with proxy command when enabled", ()=>{
|
|
121
|
+
const configs = [
|
|
122
|
+
{
|
|
123
|
+
servers: [
|
|
124
|
+
{
|
|
125
|
+
name: "fal-ai",
|
|
126
|
+
transport: "stdio",
|
|
127
|
+
command: "bun",
|
|
128
|
+
args: [
|
|
129
|
+
"run",
|
|
130
|
+
"src/tools/mcp-fal-ai.ts"
|
|
131
|
+
],
|
|
132
|
+
env: {
|
|
133
|
+
EXISTING: "value"
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
]
|
|
137
|
+
}
|
|
138
|
+
];
|
|
139
|
+
const manager = new mcpClientManager_cjs_namespaceObject.MCPClientManager(configs, testLogger, {
|
|
140
|
+
proxyConfig: {
|
|
141
|
+
enabled: true,
|
|
142
|
+
command: "uvx",
|
|
143
|
+
baseArgs: [
|
|
144
|
+
"invariant-gateway@latest",
|
|
145
|
+
"mcp"
|
|
146
|
+
],
|
|
147
|
+
projectName: "wingman-gateway",
|
|
148
|
+
apiKey: "test-api-key",
|
|
149
|
+
apiUrl: "https://explorer.invariantlabs.ai"
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
const clientConfig = getClientConfig(manager);
|
|
153
|
+
const server = clientConfig.mcpServers["fal-ai"];
|
|
154
|
+
(0, external_vitest_namespaceObject.expect)(server.command).toBe("uvx");
|
|
155
|
+
(0, external_vitest_namespaceObject.expect)(server.args).toEqual([
|
|
156
|
+
"invariant-gateway@latest",
|
|
157
|
+
"mcp",
|
|
158
|
+
"--project-name",
|
|
159
|
+
"wingman-gateway",
|
|
160
|
+
"--exec",
|
|
161
|
+
"bun",
|
|
162
|
+
"run",
|
|
163
|
+
"src/tools/mcp-fal-ai.ts"
|
|
164
|
+
]);
|
|
165
|
+
(0, external_vitest_namespaceObject.expect)(server.env.EXISTING).toBe("value");
|
|
166
|
+
(0, external_vitest_namespaceObject.expect)(server.env.INVARIANT_API_KEY).toBe("test-api-key");
|
|
167
|
+
(0, external_vitest_namespaceObject.expect)(server.env.INVARIANT_API_URL).toBe("https://explorer.invariantlabs.ai");
|
|
168
|
+
(0, external_vitest_namespaceObject.expect)(server.env.GUARDRAILS_API_URL).toBe("https://explorer.invariantlabs.ai");
|
|
169
|
+
});
|
|
120
170
|
});
|
|
121
171
|
for(var __rspack_i in __webpack_exports__)exports[__rspack_i] = __webpack_exports__[__rspack_i];
|
|
122
172
|
Object.defineProperty(exports, '__esModule', {
|
|
@@ -115,4 +115,54 @@ describe("MCPClientManager runtime env", ()=>{
|
|
|
115
115
|
const clientConfig = getClientConfig(manager);
|
|
116
116
|
expect(clientConfig.mcpServers["fal-ai"].defaultToolTimeout).toBe(300000);
|
|
117
117
|
});
|
|
118
|
+
it("wraps stdio servers with proxy command when enabled", ()=>{
|
|
119
|
+
const configs = [
|
|
120
|
+
{
|
|
121
|
+
servers: [
|
|
122
|
+
{
|
|
123
|
+
name: "fal-ai",
|
|
124
|
+
transport: "stdio",
|
|
125
|
+
command: "bun",
|
|
126
|
+
args: [
|
|
127
|
+
"run",
|
|
128
|
+
"src/tools/mcp-fal-ai.ts"
|
|
129
|
+
],
|
|
130
|
+
env: {
|
|
131
|
+
EXISTING: "value"
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
]
|
|
135
|
+
}
|
|
136
|
+
];
|
|
137
|
+
const manager = new MCPClientManager(configs, testLogger, {
|
|
138
|
+
proxyConfig: {
|
|
139
|
+
enabled: true,
|
|
140
|
+
command: "uvx",
|
|
141
|
+
baseArgs: [
|
|
142
|
+
"invariant-gateway@latest",
|
|
143
|
+
"mcp"
|
|
144
|
+
],
|
|
145
|
+
projectName: "wingman-gateway",
|
|
146
|
+
apiKey: "test-api-key",
|
|
147
|
+
apiUrl: "https://explorer.invariantlabs.ai"
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
const clientConfig = getClientConfig(manager);
|
|
151
|
+
const server = clientConfig.mcpServers["fal-ai"];
|
|
152
|
+
expect(server.command).toBe("uvx");
|
|
153
|
+
expect(server.args).toEqual([
|
|
154
|
+
"invariant-gateway@latest",
|
|
155
|
+
"mcp",
|
|
156
|
+
"--project-name",
|
|
157
|
+
"wingman-gateway",
|
|
158
|
+
"--exec",
|
|
159
|
+
"bun",
|
|
160
|
+
"run",
|
|
161
|
+
"src/tools/mcp-fal-ai.ts"
|
|
162
|
+
]);
|
|
163
|
+
expect(server.env.EXISTING).toBe("value");
|
|
164
|
+
expect(server.env.INVARIANT_API_KEY).toBe("test-api-key");
|
|
165
|
+
expect(server.env.INVARIANT_API_URL).toBe("https://explorer.invariantlabs.ai");
|
|
166
|
+
expect(server.env.GUARDRAILS_API_URL).toBe("https://explorer.invariantlabs.ai");
|
|
167
|
+
});
|
|
118
168
|
});
|
|
@@ -28,7 +28,7 @@ __webpack_require__.d(__webpack_exports__, {
|
|
|
28
28
|
internetSearch: ()=>internetSearch
|
|
29
29
|
});
|
|
30
30
|
const external_langchain_namespaceObject = require("langchain");
|
|
31
|
-
const
|
|
31
|
+
const external_duck_duck_scrape_namespaceObject = require("duck-duck-scrape");
|
|
32
32
|
const openai_namespaceObject = require("@langchain/openai");
|
|
33
33
|
const external_zod_namespaceObject = require("zod");
|
|
34
34
|
function _define_property(obj, key, value) {
|
|
@@ -103,13 +103,17 @@ const formatDdgError = (error, attempts)=>new Error(`DuckDuckGo search was rate-
|
|
|
103
103
|
cause: error
|
|
104
104
|
});
|
|
105
105
|
async function runDuckDuckGoSearch(query, maxResults) {
|
|
106
|
-
const search = new duckduckgo_search_namespaceObject.DuckDuckGoSearch({
|
|
107
|
-
maxResults
|
|
108
|
-
});
|
|
109
106
|
let attempt = 0;
|
|
110
107
|
let delayMs = ddgBackoffBaseMs;
|
|
111
108
|
while(true)try {
|
|
112
|
-
return await ddgRateLimiter.run(()=>
|
|
109
|
+
return await ddgRateLimiter.run(async ()=>{
|
|
110
|
+
const { results } = await (0, external_duck_duck_scrape_namespaceObject.search)(query);
|
|
111
|
+
return JSON.stringify(results.map((result)=>({
|
|
112
|
+
title: result.title,
|
|
113
|
+
link: result.url,
|
|
114
|
+
snippet: result.description
|
|
115
|
+
})).slice(0, maxResults));
|
|
116
|
+
});
|
|
113
117
|
} catch (error) {
|
|
114
118
|
const shouldRetry = isDdgAnomalyError(error);
|
|
115
119
|
if (!shouldRetry || attempt >= ddgMaxRetries) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { tool } from "langchain";
|
|
2
|
-
import {
|
|
2
|
+
import { search } from "duck-duck-scrape";
|
|
3
3
|
import { ChatOpenAI } from "@langchain/openai";
|
|
4
4
|
import { number, object, string } from "zod";
|
|
5
5
|
function _define_property(obj, key, value) {
|
|
@@ -74,13 +74,17 @@ const formatDdgError = (error, attempts)=>new Error(`DuckDuckGo search was rate-
|
|
|
74
74
|
cause: error
|
|
75
75
|
});
|
|
76
76
|
async function runDuckDuckGoSearch(query, maxResults) {
|
|
77
|
-
const search = new DuckDuckGoSearch({
|
|
78
|
-
maxResults
|
|
79
|
-
});
|
|
80
77
|
let attempt = 0;
|
|
81
78
|
let delayMs = ddgBackoffBaseMs;
|
|
82
79
|
while(true)try {
|
|
83
|
-
return await ddgRateLimiter.run(()=>
|
|
80
|
+
return await ddgRateLimiter.run(async ()=>{
|
|
81
|
+
const { results } = await search(query);
|
|
82
|
+
return JSON.stringify(results.map((result)=>({
|
|
83
|
+
title: result.title,
|
|
84
|
+
link: result.url,
|
|
85
|
+
snippet: result.description
|
|
86
|
+
})).slice(0, maxResults));
|
|
87
|
+
});
|
|
84
88
|
} catch (error) {
|
|
85
89
|
const shouldRetry = isDdgAnomalyError(error);
|
|
86
90
|
if (!shouldRetry || attempt >= ddgMaxRetries) {
|
|
@@ -41,14 +41,17 @@ async function executeSkillCommand(args, options = {}) {
|
|
|
41
41
|
const config = configLoader.loadConfig();
|
|
42
42
|
try {
|
|
43
43
|
const repository = new skillRepository_cjs_namespaceObject.SkillRepository({
|
|
44
|
+
provider: config.skills?.provider,
|
|
44
45
|
repositoryOwner: config.skills?.repositoryOwner,
|
|
45
46
|
repositoryName: config.skills?.repositoryName,
|
|
46
|
-
githubToken: config.skills?.githubToken
|
|
47
|
+
githubToken: config.skills?.githubToken,
|
|
48
|
+
clawhubBaseUrl: config.skills?.clawhubBaseUrl
|
|
47
49
|
});
|
|
48
50
|
const service = new skillService_cjs_namespaceObject.SkillService(repository, outputManager, logger, {
|
|
49
51
|
workspace,
|
|
50
52
|
skillsDirectory: config.skills?.skillsDirectory,
|
|
51
|
-
outputMode: args.outputMode
|
|
53
|
+
outputMode: args.outputMode,
|
|
54
|
+
security: config.skills?.security
|
|
52
55
|
});
|
|
53
56
|
const subcommand = args.subcommand;
|
|
54
57
|
const subcommandArgs = args.args;
|
|
@@ -100,7 +103,7 @@ async function executeSkillCommand(args, options = {}) {
|
|
|
100
103
|
}
|
|
101
104
|
function showSkillHelp(outputManager) {
|
|
102
105
|
if ("interactive" === outputManager.getMode()) console.log(`
|
|
103
|
-
Wingman Skill Manager - Install skills from
|
|
106
|
+
Wingman Skill Manager - Install skills from configured registries
|
|
104
107
|
|
|
105
108
|
Usage:
|
|
106
109
|
wingman skill browse Browse available skills
|
|
@@ -123,10 +126,15 @@ Configuration:
|
|
|
123
126
|
Skills can be configured in .wingman/wingman.config.json:
|
|
124
127
|
{
|
|
125
128
|
"skills": {
|
|
129
|
+
"provider": "github",
|
|
126
130
|
"repositoryOwner": "anthropics",
|
|
127
131
|
"repositoryName": "skills",
|
|
128
132
|
"githubToken": "optional-token",
|
|
129
|
-
"
|
|
133
|
+
"clawhubBaseUrl": "https://clawhub.ai",
|
|
134
|
+
"skillsDirectory": "skills",
|
|
135
|
+
"security": {
|
|
136
|
+
"scanOnInstall": true
|
|
137
|
+
}
|
|
130
138
|
}
|
|
131
139
|
}
|
|
132
140
|
`);
|