wolfpack-mcp 1.0.78 → 1.0.79
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/apiClient.js +1 -1
- package/dist/index.js +5 -1
- package/dist/proxyFetch.js +61 -0
- package/dist/proxyFetch.test.js +67 -0
- package/package.json +3 -1
package/dist/apiClient.js
CHANGED
package/dist/index.js
CHANGED
|
@@ -13,6 +13,7 @@ import { AGENT_BUILDER_TOOLS, handleAgentBuilderTool } from './agentBuilderTools
|
|
|
13
13
|
import { PROCEDURE_TOOLS, handleProcedureTool } from './procedureTools.js';
|
|
14
14
|
import { AGENT_SELF_TOOLS, handleAgentSelfTool } from './agentSelfTools.js';
|
|
15
15
|
import { resolveRadarItemId } from './resolveRadarItemId.js';
|
|
16
|
+
import { fetch as proxyFetch } from './proxyFetch.js';
|
|
16
17
|
// Get current package version
|
|
17
18
|
const require = createRequire(import.meta.url);
|
|
18
19
|
const packageJson = require('../package.json');
|
|
@@ -21,7 +22,10 @@ const PACKAGE_NAME = packageJson.name;
|
|
|
21
22
|
// Check for updates from npm registry
|
|
22
23
|
async function checkForUpdates() {
|
|
23
24
|
try {
|
|
24
|
-
|
|
25
|
+
// proxyFetch, not global fetch: in a restricted container the registry is
|
|
26
|
+
// only reachable through the proxy, and a direct attempt just stalls until
|
|
27
|
+
// the timeout on every start (#1793).
|
|
28
|
+
const response = await proxyFetch(`https://registry.npmjs.org/${PACKAGE_NAME}/latest`, {
|
|
25
29
|
headers: { Accept: 'application/json' },
|
|
26
30
|
signal: AbortSignal.timeout(5000), // 5 second timeout
|
|
27
31
|
});
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import nodeFetch from 'node-fetch';
|
|
2
|
+
import { HttpProxyAgent } from 'http-proxy-agent';
|
|
3
|
+
import { HttpsProxyAgent } from 'https-proxy-agent';
|
|
4
|
+
/**
|
|
5
|
+
* Whether a host is exempted from proxying by NO_PROXY. Entries match the host
|
|
6
|
+
* exactly or as a domain suffix (`.example.com`, or bare `example.com` for its
|
|
7
|
+
* subdomains); `*` exempts everything.
|
|
8
|
+
*/
|
|
9
|
+
export function bypassesProxy(hostname, noProxy) {
|
|
10
|
+
if (!noProxy)
|
|
11
|
+
return false;
|
|
12
|
+
const host = hostname.toLowerCase();
|
|
13
|
+
return noProxy
|
|
14
|
+
.split(',')
|
|
15
|
+
.map((entry) => entry.trim().toLowerCase())
|
|
16
|
+
.filter(Boolean)
|
|
17
|
+
.some((entry) => {
|
|
18
|
+
if (entry === '*')
|
|
19
|
+
return true;
|
|
20
|
+
const suffix = entry.startsWith('.') ? entry : `.${entry}`;
|
|
21
|
+
return host === entry.replace(/^\./, '') || host.endsWith(suffix);
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
/** The proxy URL that applies to a target, or null to connect directly. */
|
|
25
|
+
export function proxyForUrl(target, env = process.env) {
|
|
26
|
+
if (bypassesProxy(target.hostname, env.NO_PROXY ?? env.no_proxy))
|
|
27
|
+
return null;
|
|
28
|
+
const proxy = target.protocol === 'https:'
|
|
29
|
+
? (env.HTTPS_PROXY ?? env.https_proxy ?? env.HTTP_PROXY ?? env.http_proxy)
|
|
30
|
+
: (env.HTTP_PROXY ?? env.http_proxy);
|
|
31
|
+
return proxy || null;
|
|
32
|
+
}
|
|
33
|
+
const agents = new Map();
|
|
34
|
+
function agentFor(target) {
|
|
35
|
+
const proxy = proxyForUrl(target);
|
|
36
|
+
if (!proxy)
|
|
37
|
+
return undefined;
|
|
38
|
+
const key = `${target.protocol}${proxy}`;
|
|
39
|
+
let agent = agents.get(key);
|
|
40
|
+
if (!agent) {
|
|
41
|
+
agent = target.protocol === 'https:' ? new HttpsProxyAgent(proxy) : new HttpProxyAgent(proxy);
|
|
42
|
+
agents.set(key, agent);
|
|
43
|
+
}
|
|
44
|
+
return agent;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* `node-fetch` with the proxy from the environment applied.
|
|
48
|
+
*
|
|
49
|
+
* node-fetch does not read HTTP_PROXY/HTTPS_PROXY on its own, so inside a
|
|
50
|
+
* restricted agent container this client connected direct to the backend —
|
|
51
|
+
* which has no route out and no DNS, the container's only permitted egress
|
|
52
|
+
* being the Squid proxy (#1793). Agents are cached per origin, and requests
|
|
53
|
+
* are unaffected when no proxy variables are set.
|
|
54
|
+
*/
|
|
55
|
+
export function fetch(url, init = {}) {
|
|
56
|
+
if (init.agent !== undefined)
|
|
57
|
+
return nodeFetch(url, init);
|
|
58
|
+
const target = new URL(typeof url === 'string' ? url : url.url);
|
|
59
|
+
return nodeFetch(url, { ...init, agent: agentFor(target) });
|
|
60
|
+
}
|
|
61
|
+
export default fetch;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
2
|
+
import { createServer } from 'node:http';
|
|
3
|
+
import { once } from 'node:events';
|
|
4
|
+
import { bypassesProxy, fetch, proxyForUrl } from './proxyFetch.js';
|
|
5
|
+
const port = (server) => server.address().port;
|
|
6
|
+
describe('bypassesProxy', () => {
|
|
7
|
+
it('exempts an exact host and its subdomains, on a dot boundary only', () => {
|
|
8
|
+
expect(bypassesProxy('localhost', 'localhost,127.0.0.1')).toBe(true);
|
|
9
|
+
expect(bypassesProxy('api.example.com', '.example.com')).toBe(true);
|
|
10
|
+
expect(bypassesProxy('example.com', 'example.com')).toBe(true);
|
|
11
|
+
expect(bypassesProxy('notexample.com', 'example.com')).toBe(false);
|
|
12
|
+
expect(bypassesProxy('wolfpacks.work', 'localhost,127.0.0.1')).toBe(false);
|
|
13
|
+
});
|
|
14
|
+
it('treats * as exempting everything, and no NO_PROXY as exempting nothing', () => {
|
|
15
|
+
expect(bypassesProxy('wolfpacks.work', '*')).toBe(true);
|
|
16
|
+
expect(bypassesProxy('wolfpacks.work', undefined)).toBe(false);
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
describe('proxyForUrl', () => {
|
|
20
|
+
it('prefers HTTPS_PROXY for https and falls back to HTTP_PROXY', () => {
|
|
21
|
+
const env = { HTTPS_PROXY: 'http://s:1', HTTP_PROXY: 'http://p:2' };
|
|
22
|
+
expect(proxyForUrl(new URL('https://wolfpacks.work/api'), env)).toBe('http://s:1');
|
|
23
|
+
expect(proxyForUrl(new URL('http://wolfpacks.work/api'), env)).toBe('http://p:2');
|
|
24
|
+
expect(proxyForUrl(new URL('https://wolfpacks.work/api'), { HTTP_PROXY: 'http://p:2' })).toBe('http://p:2');
|
|
25
|
+
});
|
|
26
|
+
it('returns null for a NO_PROXY host and when no proxy is configured', () => {
|
|
27
|
+
expect(proxyForUrl(new URL('http://localhost:3001/api'), {
|
|
28
|
+
NO_PROXY: 'localhost',
|
|
29
|
+
HTTP_PROXY: 'http://p:1',
|
|
30
|
+
})).toBeNull();
|
|
31
|
+
expect(proxyForUrl(new URL('https://wolfpacks.work/api'), {})).toBeNull();
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
describe('fetch', () => {
|
|
35
|
+
let proxy;
|
|
36
|
+
let seen;
|
|
37
|
+
beforeEach(async () => {
|
|
38
|
+
seen = [];
|
|
39
|
+
proxy = createServer((req, res) => {
|
|
40
|
+
seen.push(req.url ?? '');
|
|
41
|
+
res.writeHead(200, { 'content-type': 'application/json' });
|
|
42
|
+
res.end('{"proxied":true}');
|
|
43
|
+
});
|
|
44
|
+
proxy.listen(0);
|
|
45
|
+
await once(proxy, 'listening');
|
|
46
|
+
});
|
|
47
|
+
afterEach(() => {
|
|
48
|
+
proxy.close();
|
|
49
|
+
delete process.env.HTTP_PROXY;
|
|
50
|
+
delete process.env.NO_PROXY;
|
|
51
|
+
});
|
|
52
|
+
// Regression test for #1793: node-fetch does not read the proxy variables,
|
|
53
|
+
// so inside a restricted container this client connected direct to a backend
|
|
54
|
+
// it had neither a route to nor DNS for.
|
|
55
|
+
it('sends the request to the proxy, for a host it cannot itself resolve', async () => {
|
|
56
|
+
process.env.HTTP_PROXY = `http://127.0.0.1:${port(proxy)}`;
|
|
57
|
+
const res = await fetch('http://nowhere.invalid/api/mcp/work-items');
|
|
58
|
+
expect(res.status).toBe(200);
|
|
59
|
+
expect(seen).toEqual(['http://nowhere.invalid/api/mcp/work-items']);
|
|
60
|
+
});
|
|
61
|
+
it('connects direct when no proxy is configured', async () => {
|
|
62
|
+
const res = await fetch(`http://127.0.0.1:${port(proxy)}/direct`);
|
|
63
|
+
expect(res.status).toBe(200);
|
|
64
|
+
// Reached the server as an origin request, not as a proxy request
|
|
65
|
+
expect(seen).toEqual(['/direct']);
|
|
66
|
+
});
|
|
67
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wolfpack-mcp",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.79",
|
|
4
4
|
"description": "MCP server for Wolfpack AI-enhanced software delivery tools",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -35,6 +35,8 @@
|
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
37
|
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
38
|
+
"http-proxy-agent": "^7.0.2",
|
|
39
|
+
"https-proxy-agent": "^7.0.6",
|
|
38
40
|
"node-fetch": "^3.3.2",
|
|
39
41
|
"yaml": "^2.8.0",
|
|
40
42
|
"zod": "^3.24.1"
|