@ferrlabs/mcp 5.2.3 → 5.2.5
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.
|
@@ -3,9 +3,11 @@ import { apiRequest } from '../api-client.js';
|
|
|
3
3
|
const mockFetch = vi.fn();
|
|
4
4
|
vi.stubGlobal('fetch', mockFetch);
|
|
5
5
|
function makeResponse(body, status = 200) {
|
|
6
|
+
const text = body === undefined ? '' : JSON.stringify(body);
|
|
6
7
|
return {
|
|
7
8
|
ok: status >= 200 && status < 300,
|
|
8
9
|
status,
|
|
10
|
+
text: () => Promise.resolve(text),
|
|
9
11
|
json: () => Promise.resolve(body),
|
|
10
12
|
};
|
|
11
13
|
}
|
|
@@ -36,7 +38,7 @@ describe('apiRequest', () => {
|
|
|
36
38
|
});
|
|
37
39
|
it('throws a generic error when response has no error field', async () => {
|
|
38
40
|
mockFetch.mockResolvedValue(makeResponse({}, 500));
|
|
39
|
-
await expect(apiRequest('/broken')).rejects.toThrow('API error: 500');
|
|
41
|
+
await expect(apiRequest('/broken')).rejects.toThrow('API error: HTTP 500');
|
|
40
42
|
});
|
|
41
43
|
it('sends a JSON body for POST requests', async () => {
|
|
42
44
|
mockFetch.mockResolvedValue(makeResponse({ id: 'tok-1' }));
|
|
@@ -9,9 +9,11 @@ const mockServer = {
|
|
|
9
9
|
const mockFetch = vi.fn();
|
|
10
10
|
vi.stubGlobal('fetch', mockFetch);
|
|
11
11
|
function makeResponse(body, status = 200) {
|
|
12
|
+
const text = body === undefined ? '' : JSON.stringify(body);
|
|
12
13
|
return {
|
|
13
14
|
ok: status >= 200 && status < 300,
|
|
14
15
|
status,
|
|
16
|
+
text: () => Promise.resolve(text),
|
|
15
17
|
json: () => Promise.resolve(body),
|
|
16
18
|
};
|
|
17
19
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api-client.d.ts","sourceRoot":"","sources":["../../src/tools/api-client.ts"],"names":[],"mappings":"AAEA,UAAU,cAAc;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,wBAAsB,UAAU,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,cAAmB,GAAG,OAAO,CAAC,CAAC,CAAC,
|
|
1
|
+
{"version":3,"file":"api-client.d.ts","sourceRoot":"","sources":["../../src/tools/api-client.ts"],"names":[],"mappings":"AAEA,UAAU,cAAc;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,wBAAsB,UAAU,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,cAAmB,GAAG,OAAO,CAAC,CAAC,CAAC,CA0C1F"}
|
package/dist/tools/api-client.js
CHANGED
|
@@ -7,6 +7,7 @@ export async function apiRequest(path, options = {}) {
|
|
|
7
7
|
};
|
|
8
8
|
if (token) {
|
|
9
9
|
headers['x-api-token'] = token;
|
|
10
|
+
headers['Authorization'] = `Bearer ${token}`;
|
|
10
11
|
}
|
|
11
12
|
const res = await fetch(`${API_URL}${path}`, {
|
|
12
13
|
method,
|
|
@@ -16,9 +17,21 @@ export async function apiRequest(path, options = {}) {
|
|
|
16
17
|
if (res.status === 204) {
|
|
17
18
|
return undefined;
|
|
18
19
|
}
|
|
19
|
-
const
|
|
20
|
+
const raw = await res.text();
|
|
21
|
+
let data = undefined;
|
|
22
|
+
if (raw) {
|
|
23
|
+
try {
|
|
24
|
+
data = JSON.parse(raw);
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
throw new Error(`API non-JSON response (HTTP ${res.status}): ${raw.slice(0, 200)}`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
20
30
|
if (!res.ok) {
|
|
21
|
-
|
|
31
|
+
const errMsg = data?.error ??
|
|
32
|
+
data?.message ??
|
|
33
|
+
`API error: HTTP ${res.status}`;
|
|
34
|
+
throw new Error(errMsg);
|
|
22
35
|
}
|
|
23
36
|
return data;
|
|
24
37
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../src/transports/http.ts"],"names":[],"mappings":"AAaA,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAoBD,wBAAsB,eAAe,CAAC,IAAI,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../src/transports/http.ts"],"names":[],"mappings":"AAaA,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAoBD,wBAAsB,eAAe,CAAC,IAAI,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CA2E5E"}
|
package/dist/transports/http.js
CHANGED
|
@@ -36,19 +36,33 @@ export async function startHttpServer(opts) {
|
|
|
36
36
|
async function handleMcpRequest(req, res) {
|
|
37
37
|
const sessionId = req.headers['mcp-session-id'] ?? undefined;
|
|
38
38
|
let transport = sessionId ? transports.get(sessionId) : undefined;
|
|
39
|
+
if (sessionId && !transport && !stateless) {
|
|
40
|
+
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
41
|
+
res.end(JSON.stringify({
|
|
42
|
+
jsonrpc: '2.0',
|
|
43
|
+
error: {
|
|
44
|
+
code: -32001,
|
|
45
|
+
message: `Session ${sessionId} not found — reinitialize to obtain a new session`,
|
|
46
|
+
},
|
|
47
|
+
id: null,
|
|
48
|
+
}));
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
39
51
|
if (!transport) {
|
|
52
|
+
let createdSessionId;
|
|
40
53
|
transport = new StreamableHTTPServerTransport({
|
|
41
54
|
sessionIdGenerator: stateless ? undefined : randomUUID,
|
|
42
55
|
onsessioninitialized: stateless
|
|
43
56
|
? undefined
|
|
44
57
|
: (newSessionId) => {
|
|
58
|
+
createdSessionId = newSessionId;
|
|
45
59
|
if (transport)
|
|
46
60
|
transports.set(newSessionId, transport);
|
|
47
61
|
},
|
|
48
62
|
});
|
|
49
63
|
transport.onclose = () => {
|
|
50
|
-
if (
|
|
51
|
-
transports.delete(
|
|
64
|
+
if (createdSessionId)
|
|
65
|
+
transports.delete(createdSessionId);
|
|
52
66
|
};
|
|
53
67
|
const server = createMcpServer();
|
|
54
68
|
await server.connect(transport);
|