@novorb/mcp-core 2.1.2 → 3.0.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.
Files changed (240) hide show
  1. package/README.md +77 -163
  2. package/bin/mcp-core-jest.mjs +112 -0
  3. package/dist/auth.d.ts +57 -0
  4. package/dist/auth.d.ts.map +1 -0
  5. package/dist/auth.js +74 -0
  6. package/dist/auth.js.map +1 -0
  7. package/dist/bootstrap.d.ts +129 -0
  8. package/dist/bootstrap.d.ts.map +1 -0
  9. package/dist/bootstrap.js +206 -0
  10. package/dist/bootstrap.js.map +1 -0
  11. package/dist/client-input.d.ts +72 -0
  12. package/dist/client-input.d.ts.map +1 -0
  13. package/dist/client-input.js +109 -0
  14. package/dist/client-input.js.map +1 -0
  15. package/dist/credential-checks.d.ts +80 -0
  16. package/dist/credential-checks.d.ts.map +1 -0
  17. package/dist/credential-checks.js +189 -0
  18. package/dist/credential-checks.js.map +1 -0
  19. package/dist/credential-health.d.ts +12 -0
  20. package/dist/credential-health.d.ts.map +1 -0
  21. package/dist/credential-health.js +12 -0
  22. package/dist/credential-health.js.map +1 -0
  23. package/dist/credential-report.d.ts +46 -0
  24. package/dist/credential-report.d.ts.map +1 -0
  25. package/dist/credential-report.js +135 -0
  26. package/dist/credential-report.js.map +1 -0
  27. package/dist/credential-status-tool.d.ts +20 -0
  28. package/dist/credential-status-tool.d.ts.map +1 -0
  29. package/dist/credential-status-tool.js +33 -0
  30. package/dist/credential-status-tool.js.map +1 -0
  31. package/dist/credentials.d.ts +288 -0
  32. package/dist/credentials.d.ts.map +1 -0
  33. package/dist/credentials.js +652 -0
  34. package/dist/credentials.js.map +1 -0
  35. package/dist/express-peers.d.ts +29 -0
  36. package/dist/express-peers.d.ts.map +1 -0
  37. package/dist/express-peers.js +36 -0
  38. package/dist/express-peers.js.map +1 -0
  39. package/dist/express.d.ts +74 -0
  40. package/dist/express.d.ts.map +1 -0
  41. package/dist/express.js +164 -0
  42. package/dist/express.js.map +1 -0
  43. package/dist/http-client.d.ts +277 -42
  44. package/dist/http-client.d.ts.map +1 -1
  45. package/dist/http-client.js +620 -180
  46. package/dist/http-client.js.map +1 -1
  47. package/dist/index.d.ts +27 -6
  48. package/dist/index.d.ts.map +1 -1
  49. package/dist/index.js +47 -8
  50. package/dist/index.js.map +1 -1
  51. package/dist/limits.d.ts +28 -0
  52. package/dist/limits.d.ts.map +1 -0
  53. package/dist/limits.js +37 -0
  54. package/dist/limits.js.map +1 -0
  55. package/dist/list-pagination.d.ts +41 -0
  56. package/dist/list-pagination.d.ts.map +1 -0
  57. package/dist/list-pagination.js +104 -0
  58. package/dist/list-pagination.js.map +1 -0
  59. package/dist/net.d.ts +112 -0
  60. package/dist/net.d.ts.map +1 -0
  61. package/dist/net.js +490 -0
  62. package/dist/net.js.map +1 -0
  63. package/dist/notifications.d.ts +78 -0
  64. package/dist/notifications.d.ts.map +1 -0
  65. package/dist/notifications.js +173 -0
  66. package/dist/notifications.js.map +1 -0
  67. package/dist/pagination.d.ts +236 -5
  68. package/dist/pagination.d.ts.map +1 -1
  69. package/dist/pagination.js +393 -6
  70. package/dist/pagination.js.map +1 -1
  71. package/dist/prompts.d.ts +70 -0
  72. package/dist/prompts.d.ts.map +1 -0
  73. package/dist/prompts.js +109 -0
  74. package/dist/prompts.js.map +1 -0
  75. package/dist/protocol-errors.d.ts +15 -0
  76. package/dist/protocol-errors.d.ts.map +1 -0
  77. package/dist/protocol-errors.js +28 -0
  78. package/dist/protocol-errors.js.map +1 -0
  79. package/dist/protocol-logging.d.ts +37 -0
  80. package/dist/protocol-logging.d.ts.map +1 -0
  81. package/dist/protocol-logging.js +77 -0
  82. package/dist/protocol-logging.js.map +1 -0
  83. package/dist/rate-limiter.d.ts +36 -0
  84. package/dist/rate-limiter.d.ts.map +1 -0
  85. package/dist/rate-limiter.js +79 -0
  86. package/dist/rate-limiter.js.map +1 -0
  87. package/dist/request-state.d.ts +74 -0
  88. package/dist/request-state.d.ts.map +1 -0
  89. package/dist/request-state.js +190 -0
  90. package/dist/request-state.js.map +1 -0
  91. package/dist/resources.d.ts +181 -0
  92. package/dist/resources.d.ts.map +1 -0
  93. package/dist/resources.js +302 -0
  94. package/dist/resources.js.map +1 -0
  95. package/dist/result-limits.d.ts +80 -0
  96. package/dist/result-limits.d.ts.map +1 -0
  97. package/dist/result-limits.js +248 -0
  98. package/dist/result-limits.js.map +1 -0
  99. package/dist/retry.d.ts +79 -23
  100. package/dist/retry.d.ts.map +1 -1
  101. package/dist/retry.js +170 -57
  102. package/dist/retry.js.map +1 -1
  103. package/dist/sdk/client-stdio.d.ts +6 -0
  104. package/dist/sdk/client-stdio.d.ts.map +1 -0
  105. package/dist/sdk/client-stdio.js +6 -0
  106. package/dist/sdk/client-stdio.js.map +1 -0
  107. package/dist/sdk/client.d.ts +6 -0
  108. package/dist/sdk/client.d.ts.map +1 -0
  109. package/dist/sdk/client.js +6 -0
  110. package/dist/sdk/client.js.map +1 -0
  111. package/dist/sdk/express.d.ts +3 -0
  112. package/dist/sdk/express.d.ts.map +1 -0
  113. package/dist/sdk/express.js +11 -0
  114. package/dist/sdk/express.js.map +1 -0
  115. package/dist/sdk/node.d.ts +6 -0
  116. package/dist/sdk/node.d.ts.map +1 -0
  117. package/dist/sdk/node.js +6 -0
  118. package/dist/sdk/node.js.map +1 -0
  119. package/dist/sdk/server-stdio.d.ts +6 -0
  120. package/dist/sdk/server-stdio.d.ts.map +1 -0
  121. package/dist/sdk/server-stdio.js +6 -0
  122. package/dist/sdk/server-stdio.js.map +1 -0
  123. package/dist/sdk/server.d.ts +6 -0
  124. package/dist/sdk/server.d.ts.map +1 -0
  125. package/dist/sdk/server.js +6 -0
  126. package/dist/sdk/server.js.map +1 -0
  127. package/dist/server.d.ts +88 -35
  128. package/dist/server.d.ts.map +1 -1
  129. package/dist/server.js +98 -44
  130. package/dist/server.js.map +1 -1
  131. package/dist/task-request.d.ts +20 -0
  132. package/dist/task-request.d.ts.map +1 -0
  133. package/dist/task-request.js +43 -0
  134. package/dist/task-request.js.map +1 -0
  135. package/dist/task-store.d.ts +230 -0
  136. package/dist/task-store.d.ts.map +1 -0
  137. package/dist/task-store.js +459 -0
  138. package/dist/task-store.js.map +1 -0
  139. package/dist/task-tools.d.ts +115 -0
  140. package/dist/task-tools.d.ts.map +1 -0
  141. package/dist/task-tools.js +397 -0
  142. package/dist/task-tools.js.map +1 -0
  143. package/dist/testing/client.d.ts +25 -0
  144. package/dist/testing/client.d.ts.map +1 -0
  145. package/dist/testing/client.js +11 -0
  146. package/dist/testing/client.js.map +1 -0
  147. package/dist/testing/in-memory.d.ts +26 -0
  148. package/dist/testing/in-memory.d.ts.map +1 -0
  149. package/dist/testing/in-memory.js +41 -0
  150. package/dist/testing/in-memory.js.map +1 -0
  151. package/dist/testing/index.d.ts +20 -0
  152. package/dist/testing/index.d.ts.map +1 -0
  153. package/dist/testing/index.js +20 -0
  154. package/dist/testing/index.js.map +1 -0
  155. package/dist/testing/isolated-dirs.d.ts +27 -0
  156. package/dist/testing/isolated-dirs.d.ts.map +1 -0
  157. package/dist/testing/isolated-dirs.js +67 -0
  158. package/dist/testing/isolated-dirs.js.map +1 -0
  159. package/dist/testing/logs.d.ts +61 -0
  160. package/dist/testing/logs.d.ts.map +1 -0
  161. package/dist/testing/logs.js +113 -0
  162. package/dist/testing/logs.js.map +1 -0
  163. package/dist/testing/raw-http.d.ts +25 -0
  164. package/dist/testing/raw-http.d.ts.map +1 -0
  165. package/dist/testing/raw-http.js +41 -0
  166. package/dist/testing/raw-http.js.map +1 -0
  167. package/dist/testing/raw-peer.d.ts +43 -0
  168. package/dist/testing/raw-peer.d.ts.map +1 -0
  169. package/dist/testing/raw-peer.js +101 -0
  170. package/dist/testing/raw-peer.js.map +1 -0
  171. package/dist/testing/recording-server.d.ts +38 -0
  172. package/dist/testing/recording-server.d.ts.map +1 -0
  173. package/dist/testing/recording-server.js +38 -0
  174. package/dist/testing/recording-server.js.map +1 -0
  175. package/dist/testing/server-process.d.ts +76 -0
  176. package/dist/testing/server-process.d.ts.map +1 -0
  177. package/dist/testing/server-process.js +241 -0
  178. package/dist/testing/server-process.js.map +1 -0
  179. package/dist/testing/tools.d.ts +53 -0
  180. package/dist/testing/tools.d.ts.map +1 -0
  181. package/dist/testing/tools.js +98 -0
  182. package/dist/testing/tools.js.map +1 -0
  183. package/dist/testing/unit.d.ts +52 -0
  184. package/dist/testing/unit.d.ts.map +1 -0
  185. package/dist/testing/unit.js +95 -0
  186. package/dist/testing/unit.js.map +1 -0
  187. package/dist/tools.d.ts +205 -0
  188. package/dist/tools.d.ts.map +1 -0
  189. package/dist/tools.js +370 -0
  190. package/dist/tools.js.map +1 -0
  191. package/dist/transport.d.ts +143 -52
  192. package/dist/transport.d.ts.map +1 -1
  193. package/dist/transport.js +292 -123
  194. package/dist/transport.js.map +1 -1
  195. package/dist/url-path.d.ts +49 -0
  196. package/dist/url-path.d.ts.map +1 -0
  197. package/dist/url-path.js +165 -0
  198. package/dist/url-path.js.map +1 -0
  199. package/dist/utils/dataStore.d.ts +143 -0
  200. package/dist/utils/dataStore.d.ts.map +1 -0
  201. package/dist/utils/dataStore.js +429 -0
  202. package/dist/utils/dataStore.js.map +1 -0
  203. package/dist/utils/envLoader.d.ts +129 -0
  204. package/dist/utils/envLoader.d.ts.map +1 -0
  205. package/dist/utils/envLoader.js +399 -0
  206. package/dist/utils/envLoader.js.map +1 -0
  207. package/dist/utils/logger.d.ts +181 -0
  208. package/dist/utils/logger.d.ts.map +1 -0
  209. package/dist/utils/logger.js +664 -0
  210. package/dist/utils/logger.js.map +1 -0
  211. package/dist/utils/redact.d.ts +50 -0
  212. package/dist/utils/redact.d.ts.map +1 -0
  213. package/dist/utils/redact.js +318 -0
  214. package/dist/utils/redact.js.map +1 -0
  215. package/docs/api-reference.md +29 -0
  216. package/docs/building-a-server.md +63 -0
  217. package/docs/credentials.md +131 -0
  218. package/docs/data-store.md +37 -0
  219. package/docs/env.md +38 -0
  220. package/docs/features.md +28 -0
  221. package/docs/http-client.md +116 -0
  222. package/docs/logger.md +100 -0
  223. package/docs/migrating-to-v3.md +45 -0
  224. package/docs/outbound-urls.md +104 -0
  225. package/docs/pagination.md +45 -0
  226. package/docs/protocol-features.md +62 -0
  227. package/docs/resources-prompts-tasks.md +95 -0
  228. package/docs/sdk.md +33 -0
  229. package/docs/server.md +68 -0
  230. package/docs/testing-harness.md +139 -0
  231. package/docs/testing.md +151 -0
  232. package/docs/tools.md +88 -0
  233. package/docs/transport.md +85 -0
  234. package/examples/testing/tools.integration.test.example.ts +312 -0
  235. package/examples/testing/tools.unit.test.example.ts +149 -0
  236. package/package.json +130 -17
  237. package/dist/logger.d.ts +0 -80
  238. package/dist/logger.d.ts.map +0 -1
  239. package/dist/logger.js +0 -237
  240. package/dist/logger.js.map +0 -1
@@ -0,0 +1,165 @@
1
+ /**
2
+ * @novorb/mcp-core - Upstream URL path segments
3
+ *
4
+ * `encodeURIComponent` alone is not enough to put a caller value (an id, a name) into an upstream URL
5
+ * path: it leaves `.` and `..` intact and the URL parser collapses them (`/repositories/..` addresses
6
+ * the parent resource). `pathSegment` encodes one value as exactly one segment and refuses what would
7
+ * not stay one, as the most lenient upstream would read it: empty, `.`, `..`, their percent-encoded
8
+ * forms (also double-encoded, for upstreams that decode twice), overlong UTF-8 dots (`%c0%ae`), unicode
9
+ * dot forms after NFKC normalisation (fullwidth, small, one-dot leader, ideographic full stop), and C0 /
10
+ * C1 control characters, DEL, U+2028 and U+2029 (raw or encoded).
11
+ *
12
+ * The error (`InvalidPathSegmentError`) names the argument and never echoes the value; `registerTools`
13
+ * turns it into an actionable isError (`Invalid <name>: ...`) whatever the server's `mapError` does.
14
+ *
15
+ * const path = buildPath`/repositories/${{ name: 'repository', value: args.repository }}/components`;
16
+ * const same = joinPath('repositories', { name: 'repository', value: args.repository }, 'components');
17
+ */
18
+ const PROBLEM_TEXT = {
19
+ empty: 'must not be empty',
20
+ 'dot-segment': 'must not be "." or ".." (or an encoded form of them)',
21
+ control: 'must not contain control characters',
22
+ invalid: 'must be a string or a finite number',
23
+ };
24
+ /** A value that cannot be one upstream path segment. The message names the argument, never the value. */
25
+ export class InvalidPathSegmentError extends Error {
26
+ /** The argument name (`options.name`, default `path segment`). */
27
+ argument;
28
+ problem;
29
+ constructor(argument, problem) {
30
+ super(`Invalid ${argument}: ${PROBLEM_TEXT[problem]}`);
31
+ this.name = 'InvalidPathSegmentError';
32
+ this.argument = argument;
33
+ this.problem = problem;
34
+ }
35
+ }
36
+ const DEFAULT_NAME = 'path segment';
37
+ /** How many percent-decoding rounds are checked (an upstream that decodes twice, plus one). */
38
+ const DECODE_ROUNDS = 3;
39
+ /** The ideographic full stop: NFKC keeps it (and maps the halfwidth form onto it), so it is mapped here. */
40
+ const IDEOGRAPHIC_FULL_STOP = String.fromCharCode(0x3002);
41
+ const REPLACEMENT = String.fromCharCode(0xfffd);
42
+ /** Whether `text` has a C0 or C1 control, DEL, U+2028 or U+2029 (compared by code unit). */
43
+ function hasControl(text) {
44
+ for (let i = 0; i < text.length; i++) {
45
+ const c = text.charCodeAt(i);
46
+ if (c <= 0x1f || (c >= 0x7f && c <= 0x9f) || c === 0x2028 || c === 0x2029)
47
+ return true;
48
+ }
49
+ return false;
50
+ }
51
+ /** The UTF-8 bytes of `text`, with each `%XX` taken as one byte (a stray `%` stays a byte). */
52
+ function percentBytes(text) {
53
+ const bytes = [];
54
+ for (let i = 0; i < text.length; i++) {
55
+ if (text[i] === '%' && /^[0-9A-Fa-f]{2}$/.test(text.slice(i + 1, i + 3))) {
56
+ bytes.push(Number.parseInt(text.slice(i + 1, i + 3), 16));
57
+ i += 2;
58
+ }
59
+ else {
60
+ const code = text.codePointAt(i) ?? 0;
61
+ if (code > 0xffff)
62
+ i++;
63
+ bytes.push(...Buffer.from(String.fromCodePoint(code), 'utf8'));
64
+ }
65
+ }
66
+ return bytes;
67
+ }
68
+ /**
69
+ * A LENIENT UTF-8 decode, as a forgiving upstream would do it: overlong forms are accepted (`%c0%ae` is
70
+ * `.`), anything unreadable becomes U+FFFD. The point is to see what the most lenient reader would see.
71
+ */
72
+ function lenientUtf8(bytes) {
73
+ let out = '';
74
+ for (let i = 0; i < bytes.length;) {
75
+ const lead = bytes[i];
76
+ if (lead < 0x80) {
77
+ out += String.fromCharCode(lead);
78
+ i++;
79
+ continue;
80
+ }
81
+ const extra = lead >= 0xf0 && lead <= 0xf7 ? 3 : lead >= 0xe0 && lead < 0xf0 ? 2 : lead >= 0xc0 && lead < 0xe0 ? 1 : 0;
82
+ let code = lead & (0xff >> (extra + 2));
83
+ let ok = extra > 0 && i + extra < bytes.length;
84
+ for (let k = 1; ok && k <= extra; k++) {
85
+ const cont = bytes[i + k];
86
+ if ((cont & 0xc0) !== 0x80)
87
+ ok = false;
88
+ else
89
+ code = (code << 6) | (cont & 0x3f);
90
+ }
91
+ if (!ok || code > 0x10ffff || (code >= 0xd800 && code <= 0xdfff)) {
92
+ out += REPLACEMENT;
93
+ i++;
94
+ continue;
95
+ }
96
+ out += String.fromCodePoint(code);
97
+ i += extra + 1;
98
+ }
99
+ return out;
100
+ }
101
+ /** The value, its leniently percent-decoded forms (up to DECODE_ROUNDS), and the NFKC form of each. */
102
+ function decodedForms(value) {
103
+ const forms = [value];
104
+ let current = value;
105
+ for (let i = 0; i < DECODE_ROUNDS; i++) {
106
+ if (!current.includes('%'))
107
+ break;
108
+ const next = lenientUtf8(percentBytes(current));
109
+ if (next === current)
110
+ break;
111
+ forms.push(next);
112
+ current = next;
113
+ }
114
+ return forms.flatMap((form) => [form, form.normalize('NFKC').split(IDEOGRAPHIC_FULL_STOP).join('.')]);
115
+ }
116
+ /**
117
+ * Encode `value` as exactly one URL path segment (`encodeURIComponent`), refusing empty, dot segments in
118
+ * every encoded, overlong and unicode form, and control characters. Throws `InvalidPathSegmentError`
119
+ * naming `options.name`.
120
+ */
121
+ export function pathSegment(value, options = {}) {
122
+ const name = options.name ?? DEFAULT_NAME;
123
+ let text;
124
+ if (typeof value === 'number') {
125
+ if (!Number.isFinite(value))
126
+ throw new InvalidPathSegmentError(name, 'invalid');
127
+ text = String(value);
128
+ }
129
+ else if (typeof value === 'string') {
130
+ text = value;
131
+ }
132
+ else {
133
+ throw new InvalidPathSegmentError(name, 'invalid');
134
+ }
135
+ if (text === '')
136
+ throw new InvalidPathSegmentError(name, 'empty');
137
+ const forms = decodedForms(text);
138
+ if (forms.some(hasControl))
139
+ throw new InvalidPathSegmentError(name, 'control');
140
+ if (forms.some((form) => form === '' || form === '.' || form === '..')) {
141
+ throw new InvalidPathSegmentError(name, 'dot-segment');
142
+ }
143
+ return encodeURIComponent(text);
144
+ }
145
+ function encodeValue(part) {
146
+ if (typeof part === 'object' && part !== null)
147
+ return pathSegment(part.value, { name: part.name });
148
+ return pathSegment(part);
149
+ }
150
+ /**
151
+ * Tagged template: the literal parts are kept as written, every interpolated value goes through
152
+ * `pathSegment` (pass `{ name, value }` to name the argument in the error).
153
+ */
154
+ export function buildPath(template, ...values) {
155
+ let out = template[0];
156
+ values.forEach((value, i) => {
157
+ out += encodeValue(value) + template[i + 1];
158
+ });
159
+ return out;
160
+ }
161
+ /** Join segments, each through `pathSegment`, with one leading slash (`/` for none). */
162
+ export function joinPath(...segments) {
163
+ return `/${segments.map(encodeValue).join('/')}`;
164
+ }
165
+ //# sourceMappingURL=url-path.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"url-path.js","sourceRoot":"","sources":["../src/url-path.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,143 @@
1
+ /**
2
+ * @novorb/mcp-core - Data store
3
+ *
4
+ * The one module an MCP server uses to write to disk (discovery caches, downloads, persisted settings).
5
+ *
6
+ * - Directory: `DATA_PATH` (absolute, or relative to rootDir), default `<rootDir>/.data`. Created
7
+ * lazily on the first write, never at import time and never relative to cwd.
8
+ * - `DATA_STORAGE=false` runs the server STATELESS: nothing is written, no directory is created;
9
+ * `jsonCache` falls back to process memory and `downloadToFile` returns the bytes instead of a path.
10
+ * - `downloadToFile` fetches a URL source through `guardedFetch` (`../net.ts`): public addresses only
11
+ * unless the caller passes `allowPrivate: true`, every redirect hop re-validated.
12
+ * - Every path goes through `resolveInside`, which refuses `..`, absolute segments, drive letters, NUL
13
+ * and symlink escapes. Names that come from tool arguments or remote APIs go through `safeName` first.
14
+ *
15
+ * const store = createDataStore({ rootDir, env });
16
+ * const cache = store.jsonCache<Discovery>({ name: 'discovery', ttlMs: 3_600_000 });
17
+ * const hit = await cache.read();
18
+ */
19
+ import type { LookupFunction } from 'node:net';
20
+ import type { CallToolResult } from '@modelcontextprotocol/server';
21
+ import type { LoadedEnv } from './envLoader.js';
22
+ import { type Logger } from './logger.js';
23
+ /** A path that would leave the data directory (or is malformed). */
24
+ export declare class DataPathError extends Error {
25
+ constructor(message: string);
26
+ }
27
+ /** A write was attempted while DATA_STORAGE=false. */
28
+ export declare class DataStorageDisabledError extends Error {
29
+ constructor(feature: string);
30
+ }
31
+ /** A download failed: bad status, too large, too slow, too many redirects. */
32
+ export declare class DownloadError extends Error {
33
+ /**
34
+ * `blocked`: the SSRF guard refused the URL or a redirect hop (invalid URL, userinfo, origin off
35
+ * `allowedOrigins`, private address); `content-type`: off `allowedContentTypes`.
36
+ */
37
+ readonly reason: 'status' | 'too-large' | 'timeout' | 'redirects' | 'protocol' | 'aborted' | 'network' | 'blocked' | 'content-type';
38
+ readonly status?: number;
39
+ constructor(reason: DownloadError['reason'], message: string, status?: number);
40
+ }
41
+ /**
42
+ * Turn an untrusted name (a tool argument, a remote file name) into one safe path segment: separators
43
+ * and characters outside `[A-Za-z0-9._ -]` become `_`, leading dots and trailing dots/spaces are
44
+ * removed, Windows reserved device names are prefixed with `_`, length is capped. Never empty.
45
+ */
46
+ export declare function safeName(name: string): string;
47
+ /** Join `segments` under `baseDir`, throwing `DataPathError` if the result would escape it. */
48
+ export declare function resolveInside(baseDir: string, ...segments: string[]): string;
49
+ export interface JsonCache<T> {
50
+ /** The cached value and its age, or null when absent, expired or unreadable. */
51
+ read(): Promise<{
52
+ data: T;
53
+ ageMs: number;
54
+ } | null>;
55
+ /** Store a value (now). */
56
+ write(data: T): Promise<void>;
57
+ /** Drop the cached value. */
58
+ clear(): Promise<void>;
59
+ }
60
+ export type DownloadSource = string | URL
61
+ /** Any client: return a fetch `Response` for the request; mcp-core reads it with the same caps. */
62
+ | ((init: {
63
+ signal: AbortSignal;
64
+ }) => Promise<Response>);
65
+ export interface DownloadOptions {
66
+ /** Max body size (default 50 MiB); larger is a `DownloadError('too-large')`. */
67
+ maxBytes?: number;
68
+ /** Total time limit in ms (default 60000). */
69
+ timeoutMs?: number;
70
+ /** Caller cancellation (pass `ctx.signal`). */
71
+ signal?: AbortSignal;
72
+ /** URL sources only: redirects followed (default 3), every hop re-validated by the SSRF guard. */
73
+ maxRedirects?: number;
74
+ /** URL sources only: request headers (dropped when a redirect changes origin). */
75
+ headers?: Record<string, string>;
76
+ /**
77
+ * URL sources only: explicit opt-in to private, loopback and link-local targets. Default false: a URL
78
+ * source is fetched through `guardedFetch` (`@novorb/mcp-core/net`) and only public addresses connect.
79
+ */
80
+ allowPrivate?: boolean;
81
+ /** URL sources only: the URL and every redirect hop must be on one of these origins. */
82
+ allowedOrigins?: readonly string[];
83
+ /** URL sources only: content types a 2xx answer may have (`type/*` allowed); unset = any. */
84
+ allowedContentTypes?: readonly string[];
85
+ /** URL sources only: the DNS resolver (a test seam; every answer is still checked). */
86
+ lookup?: LookupFunction;
87
+ /** URL sources only: a custom address policy replacing the public-only default (a test seam). */
88
+ isAllowedAddress?: (address: string) => boolean;
89
+ }
90
+ export interface DownloadResult {
91
+ bytes: number;
92
+ contentType?: string;
93
+ /** The written file, or null when storage is disabled. */
94
+ path: string | null;
95
+ /** The body, when storage is disabled (return it inline from the tool instead of a path). */
96
+ data?: Buffer;
97
+ }
98
+ export interface DataStore {
99
+ readonly enabled: boolean;
100
+ /** The data directory, or null when storage is disabled. */
101
+ readonly dir: string | null;
102
+ /** Join under the data directory; throws on escape (and DataStorageDisabledError when disabled). */
103
+ resolveInside(...segments: string[]): string;
104
+ /** Atomically write (temp file + rename). Throws DataStorageDisabledError when disabled. */
105
+ writeFile(relPath: string, data: string | Uint8Array): Promise<string>;
106
+ /** The file contents, or null when absent (always null when disabled). */
107
+ readFile(relPath: string): Promise<Buffer | null>;
108
+ /** Remove a file or directory; true when something was removed (false when disabled). */
109
+ remove(relPath: string): Promise<boolean>;
110
+ /** Create a directory (recursive). Throws DataStorageDisabledError when disabled. */
111
+ ensureDir(relPath: string): Promise<string>;
112
+ /** A TTL-checked JSON cache at `cache/<name>.json`; in memory when disabled. */
113
+ jsonCache<T>(opts: {
114
+ name: string;
115
+ ttlMs: number;
116
+ }): JsonCache<T>;
117
+ /** Bounded download (size, time, redirects); returns the bytes instead of writing when disabled. */
118
+ downloadToFile(source: DownloadSource, relPath: string, opts?: DownloadOptions): Promise<DownloadResult>;
119
+ /** undefined when enabled; otherwise the uniform toolError for a feature that needs storage. */
120
+ requireEnabled(feature: string): CallToolResult | undefined;
121
+ }
122
+ export interface CreateDataStoreOptions {
123
+ rootDir: string;
124
+ env: LoadedEnv;
125
+ logger?: Logger;
126
+ }
127
+ export interface RenameRetryOptions {
128
+ /** Platform deciding whether transient codes are retried (default `process.platform`; only win32 retries). */
129
+ platform?: NodeJS.Platform;
130
+ /** Total retry budget in ms (default 2000). */
131
+ budgetMs?: number;
132
+ /** The rename to run (default `fs/promises` rename; tests inject a failing one). */
133
+ rename?: (from: string, to: string) => Promise<void>;
134
+ }
135
+ /**
136
+ * `rename(from, to)` with the Windows replace race handled: on win32 an EPERM/EACCES/EBUSY (the target is
137
+ * open in another handle, for example a poller reading it) is retried with a short growing backoff
138
+ * within `budgetMs`, then rethrown. Other platforms and other errors fail at once.
139
+ */
140
+ export declare function renameWithRetry(from: string, to: string, options?: RenameRetryOptions): Promise<void>;
141
+ /** Create the data store for a server (see the module header). */
142
+ export declare function createDataStore(opts: CreateDataStoreOptions): DataStore;
143
+ //# sourceMappingURL=dataStore.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dataStore.d.ts","sourceRoot":"","sources":["../../src/utils/dataStore.ts"],"names":[],"mappings":"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"}