@mafdet/mcp 1.6.0 → 1.7.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/client.js CHANGED
@@ -1,4 +1,4 @@
1
- import { redact } from "./logger.js";
1
+ import { redact, logError } from "./logger.js";
2
2
  // MCP-002B: the single HTTP client for every Mafdet call. It enforces a total
3
3
  // timeout, a response-body size cap, and JSON validation; attaches the Bearer
4
4
  // key ONLY on account requests; and maps every failure to a stable, actionable
@@ -16,37 +16,54 @@ export class MafdetClientError extends Error {
16
16
  export async function getJson(options) {
17
17
  const url = buildUrl(options.baseUrl, options.path, options.query);
18
18
  const controller = new AbortController();
19
- const timer = setTimeout(() => controller.abort(), options.timeoutMs ?? DEFAULT_TIMEOUT_MS);
20
- let response;
21
- try {
22
- const headers = { Accept: "application/json" };
23
- if (options.authKey) {
24
- headers.Authorization = `Bearer ${options.authKey}`;
25
- }
26
- response = await fetch(url, {
27
- method: "GET",
28
- headers,
29
- signal: controller.signal,
30
- redirect: "error",
31
- });
32
- if (!response.ok)
33
- throw mapStatus(response.status);
34
- const contentType = response.headers.get("content-type") ?? "";
35
- if (!contentType.includes("application/json")) {
36
- throw new MafdetClientError("bad_response", "Mafdet AI returned an unexpected (non-JSON) response");
37
- }
38
- const text = await readBounded(response);
19
+ let reader;
20
+ let timer;
21
+ const deadline = new Promise((_resolve, reject) => {
22
+ timer = setTimeout(() => {
23
+ reject(new MafdetClientError("network", "could not reach Mafdet AI (timed out)"));
24
+ void reader
25
+ ?.cancel()
26
+ .catch(() => logError("response cleanup failed after timeout"));
27
+ controller.abort();
28
+ }, options.timeoutMs ?? DEFAULT_TIMEOUT_MS);
29
+ });
30
+ const request = async () => {
31
+ let response;
39
32
  try {
40
- return JSON.parse(text);
33
+ const headers = { Accept: "application/json" };
34
+ if (options.authKey) {
35
+ headers.Authorization = `Bearer ${options.authKey}`;
36
+ }
37
+ response = await fetch(url, {
38
+ method: "GET",
39
+ headers,
40
+ signal: controller.signal,
41
+ redirect: "error",
42
+ });
43
+ if (!response.ok)
44
+ throw mapStatus(response.status);
45
+ const contentType = response.headers.get("content-type") ?? "";
46
+ if (!contentType.includes("application/json")) {
47
+ throw new MafdetClientError("bad_response", "Mafdet AI returned an unexpected (non-JSON) response");
48
+ }
49
+ const text = await readBounded(response, (value) => {
50
+ reader = value;
51
+ });
52
+ try {
53
+ return JSON.parse(text);
54
+ }
55
+ catch {
56
+ throw new MafdetClientError("bad_response", "Mafdet AI returned malformed JSON");
57
+ }
41
58
  }
42
- catch {
43
- throw new MafdetClientError("bad_response", "Mafdet AI returned malformed JSON");
59
+ catch (error) {
60
+ if (error instanceof MafdetClientError)
61
+ throw error;
62
+ throw new MafdetClientError("network", `could not reach Mafdet AI (${describeNetworkError(error)})`);
44
63
  }
45
- }
46
- catch (error) {
47
- if (error instanceof MafdetClientError)
48
- throw error;
49
- throw new MafdetClientError("network", `could not reach Mafdet AI (${describeNetworkError(error)})`);
64
+ };
65
+ try {
66
+ return await Promise.race([request(), deadline]);
50
67
  }
51
68
  finally {
52
69
  clearTimeout(timer);
@@ -76,11 +93,12 @@ function mapStatus(status) {
76
93
  return new MafdetClientError("unavailable", `Mafdet AI is temporarily unavailable (HTTP ${status})`);
77
94
  }
78
95
  // Read the response body but never buffer more than the cap.
79
- async function readBounded(response) {
96
+ async function readBounded(response, onReader) {
80
97
  const reader = response.body?.getReader();
81
98
  if (!reader) {
82
99
  return "";
83
100
  }
101
+ onReader(reader);
84
102
  const decoder = new TextDecoder();
85
103
  let total = 0;
86
104
  let out = "";
package/dist/index.js CHANGED
File without changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mafdet/mcp",
3
- "version": "1.6.0",
3
+ "version": "1.7.0",
4
4
  "description": "Mafdet AI MCP server — read-only account tools (models, balance, usage, subscription, docs) for Claude Code, Cursor, Codex, OpenCode, and Claude Desktop.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -33,12 +33,6 @@
33
33
  "dist",
34
34
  "README.md"
35
35
  ],
36
- "scripts": {
37
- "build": "tsc -p tsconfig.json",
38
- "lint": "prettier --check \"src/**/*.ts\" \"test/**/*.mjs\" package.json tsconfig.json",
39
- "test": "pnpm run build && node --test \"test/**/*.test.mjs\"",
40
- "typecheck": "tsc --noEmit -p tsconfig.json"
41
- },
42
36
  "dependencies": {
43
37
  "@modelcontextprotocol/sdk": "^1.32.2",
44
38
  "zod": "^4.6.7"
@@ -49,5 +43,11 @@
49
43
  },
50
44
  "engines": {
51
45
  "node": ">=20"
46
+ },
47
+ "scripts": {
48
+ "build": "tsc -p tsconfig.json",
49
+ "lint": "prettier --check \"src/**/*.ts\" \"test/**/*.mjs\" package.json tsconfig.json",
50
+ "test": "pnpm run build && node --test \"test/**/*.test.mjs\"",
51
+ "typecheck": "tsc --noEmit -p tsconfig.json"
52
52
  }
53
- }
53
+ }