mcp-trellis 1.0.0 → 2.1.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 (296) hide show
  1. package/README.md +63 -93
  2. package/dist/adapters/node-request.d.ts +53 -0
  3. package/dist/adapters/node-request.d.ts.map +1 -0
  4. package/dist/adapters/node-request.js +165 -0
  5. package/dist/adapters/node-request.js.map +1 -0
  6. package/dist/adapters/node.d.ts +13 -30
  7. package/dist/adapters/node.d.ts.map +1 -1
  8. package/dist/adapters/node.js +55 -101
  9. package/dist/adapters/node.js.map +1 -1
  10. package/dist/advanced.d.ts +18 -0
  11. package/dist/advanced.d.ts.map +1 -0
  12. package/dist/advanced.js +18 -0
  13. package/dist/advanced.js.map +1 -0
  14. package/dist/app-options.d.ts +132 -0
  15. package/dist/app-options.d.ts.map +1 -0
  16. package/dist/app-options.js +2 -0
  17. package/dist/app-options.js.map +1 -0
  18. package/dist/app.d.ts +2 -89
  19. package/dist/app.d.ts.map +1 -1
  20. package/dist/app.js +143 -63
  21. package/dist/app.js.map +1 -1
  22. package/dist/audit.d.ts +5 -5
  23. package/dist/audit.d.ts.map +1 -1
  24. package/dist/audit.js +19 -8
  25. package/dist/audit.js.map +1 -1
  26. package/dist/auth/bearer.d.ts +4 -1
  27. package/dist/auth/bearer.d.ts.map +1 -1
  28. package/dist/auth/bearer.js +19 -4
  29. package/dist/auth/bearer.js.map +1 -1
  30. package/dist/clients.js +1 -1
  31. package/dist/clients.js.map +1 -1
  32. package/dist/deprecated.d.ts +42 -0
  33. package/dist/deprecated.d.ts.map +1 -0
  34. package/dist/deprecated.js +35 -0
  35. package/dist/deprecated.js.map +1 -0
  36. package/dist/{body.d.ts → http/body.d.ts} +27 -6
  37. package/dist/http/body.d.ts.map +1 -0
  38. package/dist/{body.js → http/body.js} +33 -13
  39. package/dist/http/body.js.map +1 -0
  40. package/dist/http/http.d.ts +42 -0
  41. package/dist/http/http.d.ts.map +1 -0
  42. package/dist/http/http.js +100 -0
  43. package/dist/http/http.js.map +1 -0
  44. package/dist/http/origins.d.ts +37 -0
  45. package/dist/http/origins.d.ts.map +1 -0
  46. package/dist/http/origins.js +87 -0
  47. package/dist/http/origins.js.map +1 -0
  48. package/dist/{dispatch.d.ts → mcp/dispatch.d.ts} +1 -1
  49. package/dist/mcp/dispatch.d.ts.map +1 -0
  50. package/dist/mcp/dispatch.js +99 -0
  51. package/dist/mcp/dispatch.js.map +1 -0
  52. package/dist/{jsonrpc.d.ts → mcp/jsonrpc.d.ts} +9 -0
  53. package/dist/mcp/jsonrpc.d.ts.map +1 -0
  54. package/dist/mcp/jsonrpc.js +88 -0
  55. package/dist/mcp/jsonrpc.js.map +1 -0
  56. package/dist/{methods.d.ts → mcp/methods.d.ts} +60 -11
  57. package/dist/mcp/methods.d.ts.map +1 -0
  58. package/dist/mcp/methods.js +84 -0
  59. package/dist/mcp/methods.js.map +1 -0
  60. package/dist/{protocol.d.ts → mcp/protocol.d.ts} +1 -3
  61. package/dist/mcp/protocol.d.ts.map +1 -0
  62. package/dist/{protocol.js → mcp/protocol.js} +1 -3
  63. package/dist/mcp/protocol.js.map +1 -0
  64. package/dist/mcp/registry.d.ts +58 -0
  65. package/dist/mcp/registry.d.ts.map +1 -0
  66. package/dist/mcp/registry.js +81 -0
  67. package/dist/mcp/registry.js.map +1 -0
  68. package/dist/mcp/rpc-handlers.d.ts +11 -0
  69. package/dist/mcp/rpc-handlers.d.ts.map +1 -0
  70. package/dist/mcp/rpc-handlers.js +133 -0
  71. package/dist/mcp/rpc-handlers.js.map +1 -0
  72. package/dist/{tools.d.ts → mcp/tools.d.ts} +20 -1
  73. package/dist/mcp/tools.d.ts.map +1 -0
  74. package/dist/mcp/tools.js +166 -0
  75. package/dist/mcp/tools.js.map +1 -0
  76. package/dist/{validate.d.ts → mcp/validate.d.ts} +18 -21
  77. package/dist/mcp/validate.d.ts.map +1 -0
  78. package/dist/{validate.js → mcp/validate.js} +59 -41
  79. package/dist/mcp/validate.js.map +1 -0
  80. package/dist/oauth/audit.d.ts +20 -0
  81. package/dist/oauth/audit.d.ts.map +1 -0
  82. package/dist/oauth/audit.js +29 -0
  83. package/dist/oauth/audit.js.map +1 -0
  84. package/dist/oauth/cimd/cimd-parse.d.ts +4 -0
  85. package/dist/oauth/cimd/cimd-parse.d.ts.map +1 -0
  86. package/dist/oauth/cimd/cimd-parse.js +70 -0
  87. package/dist/oauth/cimd/cimd-parse.js.map +1 -0
  88. package/dist/oauth/cimd/cimd-ssrf.d.ts +8 -0
  89. package/dist/oauth/cimd/cimd-ssrf.d.ts.map +1 -0
  90. package/dist/oauth/cimd/cimd-ssrf.js +193 -0
  91. package/dist/oauth/cimd/cimd-ssrf.js.map +1 -0
  92. package/dist/oauth/cimd/cimd.d.ts +36 -0
  93. package/dist/oauth/cimd/cimd.d.ts.map +1 -0
  94. package/dist/oauth/cimd/cimd.js +132 -0
  95. package/dist/oauth/cimd/cimd.js.map +1 -0
  96. package/dist/oauth/cimd/types.d.ts +12 -0
  97. package/dist/oauth/cimd/types.d.ts.map +1 -0
  98. package/dist/oauth/cimd/types.js +3 -0
  99. package/dist/oauth/cimd/types.js.map +1 -0
  100. package/dist/oauth/config.d.ts +51 -0
  101. package/dist/oauth/config.d.ts.map +1 -0
  102. package/dist/oauth/config.js +108 -0
  103. package/dist/oauth/config.js.map +1 -0
  104. package/dist/oauth/constants.d.ts +11 -0
  105. package/dist/oauth/constants.d.ts.map +1 -1
  106. package/dist/oauth/constants.js +11 -0
  107. package/dist/oauth/constants.js.map +1 -1
  108. package/dist/oauth/{base64url.d.ts → crypto/base64url.d.ts} +0 -1
  109. package/dist/oauth/crypto/base64url.d.ts.map +1 -0
  110. package/dist/oauth/{base64url.js → crypto/base64url.js} +0 -1
  111. package/dist/oauth/crypto/base64url.js.map +1 -0
  112. package/dist/oauth/crypto/clientauth.d.ts +13 -0
  113. package/dist/oauth/crypto/clientauth.d.ts.map +1 -0
  114. package/dist/oauth/crypto/clientauth.js +195 -0
  115. package/dist/oauth/crypto/clientauth.js.map +1 -0
  116. package/dist/oauth/crypto/codes.d.ts +65 -0
  117. package/dist/oauth/crypto/codes.d.ts.map +1 -0
  118. package/dist/oauth/crypto/codes.js +121 -0
  119. package/dist/oauth/crypto/codes.js.map +1 -0
  120. package/dist/oauth/crypto/hkdf.d.ts +13 -0
  121. package/dist/oauth/crypto/hkdf.d.ts.map +1 -0
  122. package/dist/oauth/crypto/hkdf.js +30 -0
  123. package/dist/oauth/crypto/hkdf.js.map +1 -0
  124. package/dist/oauth/crypto/keycache.d.ts +19 -0
  125. package/dist/oauth/crypto/keycache.d.ts.map +1 -0
  126. package/dist/oauth/crypto/keycache.js +44 -0
  127. package/dist/oauth/crypto/keycache.js.map +1 -0
  128. package/dist/oauth/crypto/pkce.d.ts.map +1 -0
  129. package/dist/oauth/{pkce.js → crypto/pkce.js} +1 -1
  130. package/dist/oauth/crypto/pkce.js.map +1 -0
  131. package/dist/oauth/{sealed.d.ts → crypto/sealed.d.ts} +12 -3
  132. package/dist/oauth/crypto/sealed.d.ts.map +1 -0
  133. package/dist/oauth/{sealed.js → crypto/sealed.js} +22 -14
  134. package/dist/oauth/crypto/sealed.js.map +1 -0
  135. package/dist/oauth/{secrethash.d.ts → crypto/secrethash.d.ts} +14 -0
  136. package/dist/oauth/crypto/secrethash.d.ts.map +1 -0
  137. package/dist/oauth/{secrethash.js → crypto/secrethash.js} +27 -11
  138. package/dist/oauth/crypto/secrethash.js.map +1 -0
  139. package/dist/oauth/deprecated.d.ts +52 -0
  140. package/dist/oauth/deprecated.d.ts.map +1 -0
  141. package/dist/oauth/deprecated.js +49 -0
  142. package/dist/oauth/deprecated.js.map +1 -0
  143. package/dist/oauth/endpoints/authorize.d.ts +3 -0
  144. package/dist/oauth/endpoints/authorize.d.ts.map +1 -0
  145. package/dist/oauth/endpoints/authorize.js +209 -0
  146. package/dist/oauth/endpoints/authorize.js.map +1 -0
  147. package/dist/oauth/endpoints/callback.d.ts +20 -0
  148. package/dist/oauth/endpoints/callback.d.ts.map +1 -0
  149. package/dist/oauth/endpoints/callback.js +31 -0
  150. package/dist/oauth/endpoints/callback.js.map +1 -0
  151. package/dist/oauth/endpoints/consent-page.d.ts +5 -0
  152. package/dist/oauth/endpoints/consent-page.d.ts.map +1 -0
  153. package/dist/oauth/endpoints/consent-page.js +63 -0
  154. package/dist/oauth/endpoints/consent-page.js.map +1 -0
  155. package/dist/oauth/endpoints/consent.d.ts +29 -0
  156. package/dist/oauth/endpoints/consent.d.ts.map +1 -0
  157. package/dist/oauth/endpoints/consent.js +98 -0
  158. package/dist/oauth/endpoints/consent.js.map +1 -0
  159. package/dist/oauth/{metadata.d.ts → endpoints/metadata.d.ts} +8 -2
  160. package/dist/oauth/endpoints/metadata.d.ts.map +1 -0
  161. package/dist/oauth/{metadata.js → endpoints/metadata.js} +10 -4
  162. package/dist/oauth/endpoints/metadata.js.map +1 -0
  163. package/dist/oauth/endpoints/register.d.ts +10 -0
  164. package/dist/oauth/endpoints/register.d.ts.map +1 -0
  165. package/dist/oauth/endpoints/register.js +96 -0
  166. package/dist/oauth/endpoints/register.js.map +1 -0
  167. package/dist/oauth/{revoke.d.ts → endpoints/revoke.d.ts} +1 -1
  168. package/dist/oauth/endpoints/revoke.d.ts.map +1 -0
  169. package/dist/oauth/{revoke.js → endpoints/revoke.js} +8 -18
  170. package/dist/oauth/endpoints/revoke.js.map +1 -0
  171. package/dist/oauth/endpoints/token.d.ts +3 -0
  172. package/dist/oauth/endpoints/token.d.ts.map +1 -0
  173. package/dist/oauth/{token.js → endpoints/token.js} +32 -25
  174. package/dist/oauth/endpoints/token.js.map +1 -0
  175. package/dist/oauth/{wellknown.d.ts → endpoints/wellknown.d.ts} +1 -1
  176. package/dist/oauth/endpoints/wellknown.d.ts.map +1 -0
  177. package/dist/oauth/{wellknown.js → endpoints/wellknown.js} +7 -9
  178. package/dist/oauth/endpoints/wellknown.js.map +1 -0
  179. package/dist/oauth/policy/redirect.d.ts.map +1 -0
  180. package/dist/oauth/policy/redirect.js.map +1 -0
  181. package/dist/oauth/policy/reqbody.d.ts +18 -0
  182. package/dist/oauth/policy/reqbody.d.ts.map +1 -0
  183. package/dist/oauth/policy/reqbody.js +68 -0
  184. package/dist/oauth/policy/reqbody.js.map +1 -0
  185. package/dist/oauth/{resource.d.ts → policy/resource.d.ts} +1 -1
  186. package/dist/oauth/policy/resource.d.ts.map +1 -0
  187. package/dist/oauth/{resource.js → policy/resource.js} +2 -2
  188. package/dist/oauth/policy/resource.js.map +1 -0
  189. package/dist/oauth/{scope.d.ts → policy/scope.d.ts} +2 -2
  190. package/dist/oauth/policy/scope.d.ts.map +1 -0
  191. package/dist/oauth/{scope.js → policy/scope.js} +3 -3
  192. package/dist/oauth/policy/scope.js.map +1 -0
  193. package/dist/oauth/router.d.ts +10 -13
  194. package/dist/oauth/router.d.ts.map +1 -1
  195. package/dist/oauth/router.js +93 -115
  196. package/dist/oauth/router.js.map +1 -1
  197. package/dist/oauth/types.d.ts +99 -45
  198. package/dist/oauth/types.d.ts.map +1 -1
  199. package/dist/oauth/types.js +1 -90
  200. package/dist/oauth/types.js.map +1 -1
  201. package/dist/server.d.ts +12 -12
  202. package/dist/server.d.ts.map +1 -1
  203. package/dist/server.js +9 -8
  204. package/dist/server.js.map +1 -1
  205. package/dist/util/defined.d.ts +11 -0
  206. package/dist/util/defined.d.ts.map +1 -0
  207. package/dist/util/defined.js +15 -0
  208. package/dist/util/defined.js.map +1 -0
  209. package/dist/util/json.d.ts +3 -0
  210. package/dist/util/json.d.ts.map +1 -0
  211. package/dist/util/json.js +3 -0
  212. package/dist/util/json.js.map +1 -0
  213. package/docs/ROADMAP.md +35 -47
  214. package/docs/compatibility.md +84 -0
  215. package/docs/diagrams/mcp-trellis-architecture.html +305 -0
  216. package/docs/guide.md +37 -13
  217. package/docs/reference.md +54 -22
  218. package/docs/security.md +58 -9
  219. package/docs/troubleshooting.md +19 -0
  220. package/package.json +23 -10
  221. package/dist/adapters/origins.d.ts +0 -19
  222. package/dist/adapters/origins.d.ts.map +0 -1
  223. package/dist/adapters/origins.js +0 -52
  224. package/dist/adapters/origins.js.map +0 -1
  225. package/dist/body.d.ts.map +0 -1
  226. package/dist/body.js.map +0 -1
  227. package/dist/dispatch.d.ts.map +0 -1
  228. package/dist/dispatch.js +0 -119
  229. package/dist/dispatch.js.map +0 -1
  230. package/dist/http.d.ts +0 -17
  231. package/dist/http.d.ts.map +0 -1
  232. package/dist/http.js +0 -38
  233. package/dist/http.js.map +0 -1
  234. package/dist/jsonrpc.d.ts.map +0 -1
  235. package/dist/jsonrpc.js +0 -18
  236. package/dist/jsonrpc.js.map +0 -1
  237. package/dist/methods.d.ts.map +0 -1
  238. package/dist/methods.js +0 -130
  239. package/dist/methods.js.map +0 -1
  240. package/dist/oauth/authorize.d.ts +0 -3
  241. package/dist/oauth/authorize.d.ts.map +0 -1
  242. package/dist/oauth/authorize.js +0 -141
  243. package/dist/oauth/authorize.js.map +0 -1
  244. package/dist/oauth/base64url.d.ts.map +0 -1
  245. package/dist/oauth/base64url.js.map +0 -1
  246. package/dist/oauth/clientauth.d.ts +0 -24
  247. package/dist/oauth/clientauth.d.ts.map +0 -1
  248. package/dist/oauth/clientauth.js +0 -119
  249. package/dist/oauth/clientauth.js.map +0 -1
  250. package/dist/oauth/codes.d.ts +0 -30
  251. package/dist/oauth/codes.d.ts.map +0 -1
  252. package/dist/oauth/codes.js +0 -85
  253. package/dist/oauth/codes.js.map +0 -1
  254. package/dist/oauth/consent.d.ts +0 -65
  255. package/dist/oauth/consent.d.ts.map +0 -1
  256. package/dist/oauth/consent.js +0 -173
  257. package/dist/oauth/consent.js.map +0 -1
  258. package/dist/oauth/metadata.d.ts.map +0 -1
  259. package/dist/oauth/metadata.js.map +0 -1
  260. package/dist/oauth/pkce.d.ts.map +0 -1
  261. package/dist/oauth/pkce.js.map +0 -1
  262. package/dist/oauth/redirect.d.ts.map +0 -1
  263. package/dist/oauth/redirect.js.map +0 -1
  264. package/dist/oauth/reqbody.d.ts +0 -10
  265. package/dist/oauth/reqbody.d.ts.map +0 -1
  266. package/dist/oauth/reqbody.js +0 -29
  267. package/dist/oauth/reqbody.js.map +0 -1
  268. package/dist/oauth/resource.d.ts.map +0 -1
  269. package/dist/oauth/resource.js.map +0 -1
  270. package/dist/oauth/revoke.d.ts.map +0 -1
  271. package/dist/oauth/revoke.js.map +0 -1
  272. package/dist/oauth/scope.d.ts.map +0 -1
  273. package/dist/oauth/scope.js.map +0 -1
  274. package/dist/oauth/sealed.d.ts.map +0 -1
  275. package/dist/oauth/sealed.js.map +0 -1
  276. package/dist/oauth/secrethash.d.ts.map +0 -1
  277. package/dist/oauth/secrethash.js.map +0 -1
  278. package/dist/oauth/token.d.ts +0 -3
  279. package/dist/oauth/token.d.ts.map +0 -1
  280. package/dist/oauth/token.js.map +0 -1
  281. package/dist/oauth/wellknown.d.ts.map +0 -1
  282. package/dist/oauth/wellknown.js.map +0 -1
  283. package/dist/protocol.d.ts.map +0 -1
  284. package/dist/protocol.js.map +0 -1
  285. package/dist/registry.d.ts +0 -37
  286. package/dist/registry.d.ts.map +0 -1
  287. package/dist/registry.js +0 -77
  288. package/dist/registry.js.map +0 -1
  289. package/dist/tools.d.ts.map +0 -1
  290. package/dist/tools.js +0 -98
  291. package/dist/tools.js.map +0 -1
  292. package/dist/validate.d.ts.map +0 -1
  293. package/dist/validate.js.map +0 -1
  294. /package/dist/oauth/{pkce.d.ts → crypto/pkce.d.ts} +0 -0
  295. /package/dist/oauth/{redirect.d.ts → policy/redirect.d.ts} +0 -0
  296. /package/dist/oauth/{redirect.js → policy/redirect.js} +0 -0
@@ -0,0 +1,166 @@
1
+ /**
2
+ * Ergonomic tool authoring on top of `ToolDef` — turning "I have an API" into
3
+ * "I have an MCP tool" without hand-rolling validation or fetch plumbing.
4
+ *
5
+ * `inputSchema` (JSON Schema) stays required: it's what `tools/list` actually
6
+ * advertises to clients, and no library-agnostic way exists to derive it from
7
+ * an arbitrary Standard Schema validator. `input`, when supplied, is what
8
+ * actually parses and types `args` at call time — keep the two in sync.
9
+ */
10
+ import { BodyTooLargeError, DEFAULT_MCP_BODY_LIMIT, readBoundedBytes } from "../http/body.js";
11
+ import { pickDefined } from "../util/defined.js";
12
+ const pathToString = (path) => (path ?? [])
13
+ .map((segment) => segment && typeof segment === "object" && "key" in segment
14
+ ? String(segment.key)
15
+ : String(segment))
16
+ .join(".");
17
+ const parseWithSchema = async (schema, raw) => {
18
+ const result = await schema["~standard"].validate(raw);
19
+ const issues = result.issues;
20
+ if (issues?.length) {
21
+ return {
22
+ issues: issues.map((issue) => {
23
+ const path = pathToString(issue.path);
24
+ return path ? `${path}: ${issue.message}` : issue.message;
25
+ }),
26
+ };
27
+ }
28
+ return { value: result.value };
29
+ };
30
+ const issuesResult = (issues) => ({
31
+ content: [{ type: "text", text: issues.join("; ") }],
32
+ isError: true,
33
+ });
34
+ /**
35
+ * A `ToolDef` with typed, validated `args` — drop straight into
36
+ * `createToolRegistry` / `createMcpApp`'s `tools` array like any other tool.
37
+ */
38
+ export const defineTool = (options) => ({
39
+ name: options.name,
40
+ description: options.description,
41
+ inputSchema: options.inputSchema,
42
+ ...pickDefined(options, ["scope"]),
43
+ handler: async (ctx, rawArgs) => {
44
+ if (!options.input)
45
+ return options.handler(ctx, rawArgs);
46
+ const parsed = await parseWithSchema(options.input, rawArgs);
47
+ if ("issues" in parsed)
48
+ return issuesResult(parsed.issues);
49
+ return options.handler(ctx, parsed.value);
50
+ },
51
+ });
52
+ const defaultRespond = (res) => res.text();
53
+ /**
54
+ * Status only — the upstream body is never forwarded by default. This is a
55
+ * return value, not a thrown exception, so `onToolError` redaction never
56
+ * sees it; an upstream error page can echo internal detail, tokens or SQL
57
+ * into the tool result and audit log. Use `onError` to forward (a redacted
58
+ * version of) the body on purpose.
59
+ */
60
+ const defaultErrorResult = (res) => {
61
+ const status = [res.status, res.statusText].filter(Boolean).join(" ");
62
+ return {
63
+ content: [{ type: "text", text: `Request failed: ${status}` }],
64
+ isError: true,
65
+ };
66
+ };
67
+ const DEFAULT_API_TOOL_TIMEOUT_MS = 30_000;
68
+ const isTimeoutError = (caught) => {
69
+ if (!(caught instanceof Error))
70
+ return false;
71
+ return (caught.name === "TimeoutError" ||
72
+ caught.name === "AbortError" ||
73
+ caught.message === "body read aborted");
74
+ };
75
+ const timedOutResult = (timeoutMs) => ({
76
+ content: [{ type: "text", text: `Request timed out after ${timeoutMs}ms` }],
77
+ isError: true,
78
+ });
79
+ /** A timed-out fetch becomes a `ToolResult`; every other throw still propagates. */
80
+ const fetchWithTimeout = async (fetchImpl, input, timeoutMs) => {
81
+ if (timeoutMs === false)
82
+ return { response: await fetchImpl(input) };
83
+ const signal = AbortSignal.timeout(timeoutMs);
84
+ try {
85
+ return { response: await fetchImpl(input, { signal }), signal };
86
+ }
87
+ catch (caught) {
88
+ if (isTimeoutError(caught))
89
+ return timedOutResult(timeoutMs);
90
+ throw caught;
91
+ }
92
+ };
93
+ /**
94
+ * Capped before `respond`/`onError` ever see it — an unbounded res.text()
95
+ * on the upstream body is the same memory-exhaustion shape an unbounded
96
+ * *incoming* body is (see body.ts). Rebuilt as a fresh Response from the
97
+ * raw bytes — not decoded text — so `respond`/`onError` can still call
98
+ * res.arrayBuffer()/res.blob() on binary bodies as well as res.text()/
99
+ * res.json(), exactly as if they'd read the original response themselves.
100
+ * An oversized body, or the timeout signal firing mid-body, becomes a
101
+ * `ToolResult`; every other throw still propagates.
102
+ */
103
+ const boundResponse = async (res, maxResponseBytes, timeoutMs, signal) => {
104
+ let bytes;
105
+ try {
106
+ bytes = (await readBoundedBytes(res, maxResponseBytes, signal));
107
+ }
108
+ catch (caught) {
109
+ if (caught instanceof BodyTooLargeError) {
110
+ return {
111
+ content: [{ type: "text", text: `Response exceeded ${maxResponseBytes} bytes` }],
112
+ isError: true,
113
+ };
114
+ }
115
+ if (isTimeoutError(caught))
116
+ return timedOutResult(timeoutMs);
117
+ throw caught;
118
+ }
119
+ // The buffered body is already decoded and re-framed — the upstream
120
+ // encoding/length headers no longer describe it.
121
+ const headers = new Headers(res.headers);
122
+ headers.delete("content-encoding");
123
+ headers.delete("content-length");
124
+ return new Response(bytes, { status: res.status, statusText: res.statusText, headers });
125
+ };
126
+ const runApi = async (options, ctx, args) => {
127
+ const fetchImpl = options.fetch ?? fetch;
128
+ const input = await options.request(ctx, args);
129
+ const timeoutMs = options.timeoutMs ?? DEFAULT_API_TOOL_TIMEOUT_MS;
130
+ const fetched = await fetchWithTimeout(fetchImpl, input, timeoutMs);
131
+ if (!("response" in fetched))
132
+ return fetched;
133
+ const maxResponseBytes = options.maxResponseBytes ?? DEFAULT_MCP_BODY_LIMIT;
134
+ const bounded = await boundResponse(fetched.response, maxResponseBytes, timeoutMs, fetched.signal);
135
+ if (!(bounded instanceof Response))
136
+ return bounded;
137
+ if (!bounded.ok)
138
+ return options.onError ? options.onError(bounded) : defaultErrorResult(bounded);
139
+ return (options.respond ?? defaultRespond)(bounded);
140
+ };
141
+ /**
142
+ * Wrap a single REST call as an MCP tool in one call: build the request from
143
+ * typed args, run it, and shape the response — no hand-rolled fetch/error
144
+ * plumbing per tool. Built on `defineTool`, so `input` gets the same
145
+ * validation-before-call treatment.
146
+ */
147
+ export const apiTool = (options) => {
148
+ const core = {
149
+ name: options.name,
150
+ description: options.description,
151
+ inputSchema: options.inputSchema,
152
+ ...pickDefined(options, ["scope"]),
153
+ };
154
+ if (options.input) {
155
+ return defineTool({
156
+ ...core,
157
+ input: options.input,
158
+ handler: (ctx, args) => runApi(options, ctx, args),
159
+ });
160
+ }
161
+ return defineTool({
162
+ ...core,
163
+ handler: (ctx, args) => runApi(options, ctx, args),
164
+ });
165
+ };
166
+ //# sourceMappingURL=tools.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tools.js","sourceRoot":"","sources":["../../src/mcp/tools.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,iBAAiB,EAAE,sBAAsB,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAC9F,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AA2BjD,MAAM,YAAY,GAAG,CAAC,IAAwC,EAAU,EAAE,CACxE,CAAC,IAAI,IAAI,EAAE,CAAC;KACT,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CACf,OAAO,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,KAAK,IAAI,OAAO;IACxD,CAAC,CAAC,MAAM,CAAE,OAA4B,CAAC,GAAG,CAAC;IAC3C,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CACpB;KACA,IAAI,CAAC,GAAG,CAAC,CAAC;AAEf,MAAM,eAAe,GAAG,KAAK,EAC3B,MAAyC,EACzC,GAAY,EACkB,EAAE;IAChC,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,WAAW,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;IACvD,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC;IAC7B,IAAI,MAAM,EAAE,MAAM,EAAE,CAAC;QACnB,OAAO;YACL,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE;gBAC3B,MAAM,IAAI,GAAG,YAAY,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;gBACtC,OAAO,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,KAAK,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC;YAC5D,CAAC,CAAC;SACH,CAAC;IACJ,CAAC;IACD,OAAO,EAAE,KAAK,EAAG,MAA4B,CAAC,KAAK,EAAE,CAAC;AACxD,CAAC,CAAC;AAEF,MAAM,YAAY,GAAG,CAAC,MAAgB,EAAc,EAAE,CAAC,CAAC;IACtD,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;IACpD,OAAO,EAAE,IAAI;CACd,CAAC,CAAC;AA2BH;;;GAGG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG,CACxB,OAAuC,EACxB,EAAE,CAAC,CAAC;IACnB,IAAI,EAAE,OAAO,CAAC,IAAI;IAClB,WAAW,EAAE,OAAO,CAAC,WAAW;IAChC,WAAW,EAAE,OAAO,CAAC,WAAW;IAChC,GAAG,WAAW,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,CAAC;IAClC,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,OAAO,EAAE,EAAE;QAC9B,IAAI,CAAC,OAAO,CAAC,KAAK;YAAE,OAAO,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;QACzD,MAAM,MAAM,GAAG,MAAM,eAAe,CAAC,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;QAC7D,IAAI,QAAQ,IAAI,MAAM;YAAE,OAAO,YAAY,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QAC3D,OAAO,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC;IAC5C,CAAC;CACF,CAAC,CAAC;AAyCH,MAAM,cAAc,GAAG,CAAC,GAAa,EAAmB,EAAE,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC;AAEtE;;;;;;GAMG;AACH,MAAM,kBAAkB,GAAG,CAAC,GAAa,EAAc,EAAE;IACvD,MAAM,MAAM,GAAG,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,UAAU,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACtE,OAAO;QACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,mBAAmB,MAAM,EAAE,EAAE,CAAC;QAC9D,OAAO,EAAE,IAAI;KACd,CAAC;AACJ,CAAC,CAAC;AAEF,MAAM,2BAA2B,GAAG,MAAM,CAAC;AAE3C,MAAM,cAAc,GAAG,CAAC,MAAe,EAAW,EAAE;IAClD,IAAI,CAAC,CAAC,MAAM,YAAY,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IAC7C,OAAO,CACL,MAAM,CAAC,IAAI,KAAK,cAAc;QAC9B,MAAM,CAAC,IAAI,KAAK,YAAY;QAC5B,MAAM,CAAC,OAAO,KAAK,mBAAmB,CACvC,CAAC;AACJ,CAAC,CAAC;AAEF,MAAM,cAAc,GAAG,CAAC,SAAyB,EAAc,EAAE,CAAC,CAAC;IACjE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,2BAA2B,SAAS,IAAI,EAAE,CAAC;IAC3E,OAAO,EAAE,IAAI;CACd,CAAC,CAAC;AAEH,oFAAoF;AACpF,MAAM,gBAAgB,GAAG,KAAK,EAC5B,SAAuB,EACvB,KAAiB,EACjB,SAAyB,EAC2C,EAAE;IACtE,IAAI,SAAS,KAAK,KAAK;QAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC,KAAK,CAAC,EAAE,CAAC;IACrE,MAAM,MAAM,GAAG,WAAW,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAC9C,IAAI,CAAC;QACH,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC,KAAK,EAAE,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC;IAClE,CAAC;IAAC,OAAO,MAAM,EAAE,CAAC;QAChB,IAAI,cAAc,CAAC,MAAM,CAAC;YAAE,OAAO,cAAc,CAAC,SAAS,CAAC,CAAC;QAC7D,MAAM,MAAM,CAAC;IACf,CAAC;AACH,CAAC,CAAC;AAEF;;;;;;;;;GASG;AACH,MAAM,aAAa,GAAG,KAAK,EACzB,GAAa,EACb,gBAAwB,EACxB,SAAyB,EACzB,MAAoB,EACY,EAAE;IAClC,IAAI,KAA8B,CAAC;IACnC,IAAI,CAAC;QACH,KAAK,GAAG,CAAC,MAAM,gBAAgB,CAAC,GAAG,EAAE,gBAAgB,EAAE,MAAM,CAAC,CAA4B,CAAC;IAC7F,CAAC;IAAC,OAAO,MAAM,EAAE,CAAC;QAChB,IAAI,MAAM,YAAY,iBAAiB,EAAE,CAAC;YACxC,OAAO;gBACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,qBAAqB,gBAAgB,QAAQ,EAAE,CAAC;gBAChF,OAAO,EAAE,IAAI;aACd,CAAC;QACJ,CAAC;QACD,IAAI,cAAc,CAAC,MAAM,CAAC;YAAE,OAAO,cAAc,CAAC,SAAS,CAAC,CAAC;QAC7D,MAAM,MAAM,CAAC;IACf,CAAC;IACD,oEAAoE;IACpE,iDAAiD;IACjD,MAAM,OAAO,GAAG,IAAI,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IACzC,OAAO,CAAC,MAAM,CAAC,kBAAkB,CAAC,CAAC;IACnC,OAAO,CAAC,MAAM,CAAC,gBAAgB,CAAC,CAAC;IACjC,OAAO,IAAI,QAAQ,CAAC,KAAK,EAAE,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,EAAE,GAAG,CAAC,UAAU,EAAE,OAAO,EAAE,CAAC,CAAC;AAC1F,CAAC,CAAC;AAEF,MAAM,MAAM,GAAG,KAAK,EAClB,OAA6B,EAC7B,GAAS,EACT,IAAU,EACoB,EAAE;IAChC,MAAM,SAAS,GAAG,OAAO,CAAC,KAAK,IAAI,KAAK,CAAC;IACzC,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;IAC/C,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,2BAA2B,CAAC;IAEnE,MAAM,OAAO,GAAG,MAAM,gBAAgB,CAAC,SAAS,EAAE,KAAK,EAAE,SAAS,CAAC,CAAC;IACpE,IAAI,CAAC,CAAC,UAAU,IAAI,OAAO,CAAC;QAAE,OAAO,OAAO,CAAC;IAE7C,MAAM,gBAAgB,GAAG,OAAO,CAAC,gBAAgB,IAAI,sBAAsB,CAAC;IAC5E,MAAM,OAAO,GAAG,MAAM,aAAa,CACjC,OAAO,CAAC,QAAQ,EAChB,gBAAgB,EAChB,SAAS,EACT,OAAO,CAAC,MAAM,CACf,CAAC;IACF,IAAI,CAAC,CAAC,OAAO,YAAY,QAAQ,CAAC;QAAE,OAAO,OAAO,CAAC;IAEnD,IAAI,CAAC,OAAO,CAAC,EAAE;QAAE,OAAO,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,kBAAkB,CAAC,OAAO,CAAC,CAAC;IACjG,OAAO,CAAC,OAAO,CAAC,OAAO,IAAI,cAAc,CAAC,CAAC,OAAO,CAAC,CAAC;AACtD,CAAC,CAAC;AAEF;;;;;GAKG;AACH,MAAM,CAAC,MAAM,OAAO,GAAG,CACrB,OAAoC,EACrB,EAAE;IACjB,MAAM,IAAI,GAAG;QACX,IAAI,EAAE,OAAO,CAAC,IAAI;QAClB,WAAW,EAAE,OAAO,CAAC,WAAW;QAChC,WAAW,EAAE,OAAO,CAAC,WAAW;QAChC,GAAG,WAAW,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,CAAC;KACnC,CAAC;IACF,IAAI,OAAO,CAAC,KAAK,EAAE,CAAC;QAClB,OAAO,UAAU,CAAc;YAC7B,GAAG,IAAI;YACP,KAAK,EAAE,OAAO,CAAC,KAAK;YACpB,OAAO,EAAE,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE,CAAC,MAAM,CAAC,OAAO,EAAE,GAAG,EAAE,IAAI,CAAC;SACnD,CAAC,CAAC;IACL,CAAC;IACD,OAAO,UAAU,CAAO;QACtB,GAAG,IAAI;QACP,OAAO,EAAE,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE,CAAC,MAAM,CAAC,OAAO,EAAE,GAAG,EAAE,IAAI,CAAC;KACnD,CAAC,CAAC;AACL,CAAC,CAAC"}
@@ -7,16 +7,27 @@ export type JsonSchema = {
7
7
  items?: JsonSchema;
8
8
  minimum?: number;
9
9
  maximum?: number;
10
+ /** Only the boolean form is evaluated; a schema-valued form is unsupported. */
11
+ additionalProperties?: boolean | JsonSchema;
10
12
  description?: string;
11
13
  title?: string;
12
14
  [key: string]: unknown;
13
15
  };
16
+ export declare const JSON_SCHEMA_TYPES: {
17
+ readonly string: "string";
18
+ readonly number: "number";
19
+ readonly integer: "integer";
20
+ readonly boolean: "boolean";
21
+ readonly object: "object";
22
+ readonly array: "array";
23
+ readonly null: "null";
24
+ };
14
25
  /** Keywords `validateAgainstSchema` actually evaluates. */
15
26
  export declare const SUPPORTED_SCHEMA_KEYWORDS: ReadonlySet<string>;
16
27
  /**
17
28
  * Allowed on inputSchema but never evaluated (tools/list / generator metadata).
18
- * Semantically-enforcing keywords (`pattern`, `additionalProperties`, …) are
19
- * not ignored — with `validateArgs: true` they throw at construction.
29
+ * Semantically-enforcing keywords (`pattern`, `oneOf`, …) are not ignored —
30
+ * with `validateArgs: true` they throw at construction.
20
31
  */
21
32
  export declare const IGNORED_SCHEMA_KEYWORDS: ReadonlySet<string>;
22
33
  /**
@@ -26,27 +37,13 @@ export declare const IGNORED_SCHEMA_KEYWORDS: ReadonlySet<string>;
26
37
  export declare const unsupportedKeywords: (schema: JsonSchema, path?: string) => string[];
27
38
  /**
28
39
  * Paths where `properties`/`required` implies an object shape but `type`
29
- * doesn't say so — absent, or a single type/union not including
30
- * `"object"`. Without this, `{ properties: { x: { type: "string" } },
31
- * required: ["x"] }` validates *any* input, `{}` included: `typeMismatch`
32
- * short-circuits on a missing `type`, and the object-specific checks below
33
- * only ran when `schema.type === "object"`. Walks nested
34
- * `properties`/`items` the same way `unsupportedKeywords` does. Under
35
- * `validateArgs: true` this throws at construction instead of silently
36
- * validating nothing at runtime — `validateNode`'s dispatch-by-actual-type
37
- * (see below) is the separate runtime backstop for schemas that reach
38
- * validation unchecked (e.g. loaded from outside the tool definition).
40
+ * doesn't say so — absent, or a type/union without `"object"`. Such a
41
+ * schema would otherwise accept any input, `{}` included. Under
42
+ * `validateArgs: true` this throws at construction; `validateNode`'s
43
+ * dispatch on the value's runtime type is the backstop for schemas that
44
+ * reach validation unchecked.
39
45
  */
40
46
  export declare const missingObjectType: (schema: JsonSchema, path?: string) => string[];
41
- export declare const JSON_SCHEMA_TYPES: {
42
- readonly string: "string";
43
- readonly number: "number";
44
- readonly integer: "integer";
45
- readonly boolean: "boolean";
46
- readonly object: "object";
47
- readonly array: "array";
48
- readonly null: "null";
49
- };
50
47
  /** Minimal JSON Schema subset used by MCP tool inputSchema. Empty = valid. */
51
48
  export declare const validateAgainstSchema: (value: unknown, schema: JsonSchema) => string[];
52
49
  //# sourceMappingURL=validate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../src/mcp/validate.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,UAAU,GAAG;IACvB,6EAA6E;IAC7E,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IACzB,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IACxC,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,IAAI,CAAC,EAAE,OAAO,EAAE,CAAC;IACjB,KAAK,CAAC,EAAE,UAAU,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,+EAA+E;IAC/E,oBAAoB,CAAC,EAAE,OAAO,GAAG,UAAU,CAAC;IAC5C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB,CAAC;AAEF,eAAO,MAAM,iBAAiB;;;;;;;;CAQpB,CAAC;AAkBX,2DAA2D;AAC3D,eAAO,MAAM,yBAAyB,EAAE,WAAW,CAAC,MAAM,CASxD,CAAC;AAUH;;;;GAIG;AACH,eAAO,MAAM,uBAAuB,EAAE,WAAW,CAAC,MAAM,CAYtD,CAAC;AAEH;;;GAGG;AACH,eAAO,MAAM,mBAAmB,GAAI,QAAQ,UAAU,EAAE,aAAa,KAAG,MAAM,EAU7E,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,iBAAiB,GAAI,QAAQ,UAAU,EAAE,aAAa,KAAG,MAAM,EAS3E,CAAC;AAiHF,8EAA8E;AAC9E,eAAO,MAAM,qBAAqB,GAAI,OAAO,OAAO,EAAE,QAAQ,UAAU,KAAG,MAAM,EAC5C,CAAC"}
@@ -1,3 +1,25 @@
1
+ export const JSON_SCHEMA_TYPES = {
2
+ string: "string",
3
+ number: "number",
4
+ integer: "integer",
5
+ boolean: "boolean",
6
+ object: "object",
7
+ array: "array",
8
+ null: "null",
9
+ };
10
+ /** `schema.type` as an array either way — a single type is a union of one. */
11
+ const declaredTypesOf = (schema) => schema.type ? (Array.isArray(schema.type) ? schema.type : [schema.type]) : [];
12
+ /**
13
+ * Direct subschemas the walkers below descend into (`properties.*`, `items`),
14
+ * each with the path it is reported under.
15
+ */
16
+ const childSchemas = (schema, path) => [
17
+ ...Object.entries(schema.properties ?? {}).map(([key, child]) => [
18
+ child,
19
+ `${path}.properties.${key}`,
20
+ ]),
21
+ ...(schema.items ? [[schema.items, `${path}.items`]] : []),
22
+ ];
1
23
  /** Keywords `validateAgainstSchema` actually evaluates. */
2
24
  export const SUPPORTED_SCHEMA_KEYWORDS = new Set([
3
25
  "type",
@@ -7,11 +29,17 @@ export const SUPPORTED_SCHEMA_KEYWORDS = new Set([
7
29
  "items",
8
30
  "minimum",
9
31
  "maximum",
32
+ "additionalProperties",
10
33
  ]);
34
+ /** Supported only in these value shapes; any other value is reported as unsupported. */
35
+ const SUPPORTED_KEYWORD_VALUES = {
36
+ additionalProperties: (value) => typeof value === "boolean",
37
+ };
38
+ const isSupportedKeyword = (key, value) => SUPPORTED_SCHEMA_KEYWORDS.has(key) && (SUPPORTED_KEYWORD_VALUES[key]?.(value) ?? true);
11
39
  /**
12
40
  * Allowed on inputSchema but never evaluated (tools/list / generator metadata).
13
- * Semantically-enforcing keywords (`pattern`, `additionalProperties`, …) are
14
- * not ignored — with `validateArgs: true` they throw at construction.
41
+ * Semantically-enforcing keywords (`pattern`, `oneOf`, …) are not ignored —
42
+ * with `validateArgs: true` they throw at construction.
15
43
  */
16
44
  export const IGNORED_SCHEMA_KEYWORDS = new Set([
17
45
  "description",
@@ -31,46 +59,27 @@ export const IGNORED_SCHEMA_KEYWORDS = new Set([
31
59
  * Walks nested `properties` / `items`.
32
60
  */
33
61
  export const unsupportedKeywords = (schema, path = "args") => {
34
- const local = Object.keys(schema).flatMap((key) => SUPPORTED_SCHEMA_KEYWORDS.has(key) || IGNORED_SCHEMA_KEYWORDS.has(key)
35
- ? []
36
- : [`${path}.${key}`]);
37
- const fromProperties = Object.entries(schema.properties ?? {}).flatMap(([key, child]) => unsupportedKeywords(child, `${path}.properties.${key}`));
38
- const fromItems = schema.items ? unsupportedKeywords(schema.items, `${path}.items`) : [];
39
- return [...local, ...fromProperties, ...fromItems];
62
+ const local = Object.entries(schema).flatMap(([key, value]) => isSupportedKeyword(key, value) || IGNORED_SCHEMA_KEYWORDS.has(key) ? [] : [`${path}.${key}`]);
63
+ return [
64
+ ...local,
65
+ ...childSchemas(schema, path).flatMap(([child, childPath]) => unsupportedKeywords(child, childPath)),
66
+ ];
40
67
  };
41
68
  /**
42
69
  * Paths where `properties`/`required` implies an object shape but `type`
43
- * doesn't say so — absent, or a single type/union not including
44
- * `"object"`. Without this, `{ properties: { x: { type: "string" } },
45
- * required: ["x"] }` validates *any* input, `{}` included: `typeMismatch`
46
- * short-circuits on a missing `type`, and the object-specific checks below
47
- * only ran when `schema.type === "object"`. Walks nested
48
- * `properties`/`items` the same way `unsupportedKeywords` does. Under
49
- * `validateArgs: true` this throws at construction instead of silently
50
- * validating nothing at runtime — `validateNode`'s dispatch-by-actual-type
51
- * (see below) is the separate runtime backstop for schemas that reach
52
- * validation unchecked (e.g. loaded from outside the tool definition).
70
+ * doesn't say so — absent, or a type/union without `"object"`. Such a
71
+ * schema would otherwise accept any input, `{}` included. Under
72
+ * `validateArgs: true` this throws at construction; `validateNode`'s
73
+ * dispatch on the value's runtime type is the backstop for schemas that
74
+ * reach validation unchecked.
53
75
  */
54
76
  export const missingObjectType = (schema, path = "args") => {
55
- const declaredTypes = schema.type
56
- ? Array.isArray(schema.type)
57
- ? schema.type
58
- : [schema.type]
59
- : [];
60
77
  const impliesObject = schema.properties !== undefined || schema.required !== undefined;
61
- const local = impliesObject && !declaredTypes.includes(JSON_SCHEMA_TYPES.object) ? [path] : [];
62
- const fromProperties = Object.entries(schema.properties ?? {}).flatMap(([key, child]) => missingObjectType(child, `${path}.properties.${key}`));
63
- const fromItems = schema.items ? missingObjectType(schema.items, `${path}.items`) : [];
64
- return [...local, ...fromProperties, ...fromItems];
65
- };
66
- export const JSON_SCHEMA_TYPES = {
67
- string: "string",
68
- number: "number",
69
- integer: "integer",
70
- boolean: "boolean",
71
- object: "object",
72
- array: "array",
73
- null: "null",
78
+ const declaresObject = declaredTypesOf(schema).includes(JSON_SCHEMA_TYPES.object);
79
+ return [
80
+ ...(impliesObject && !declaresObject ? [path] : []),
81
+ ...childSchemas(schema, path).flatMap(([child, childPath]) => missingObjectType(child, childPath)),
82
+ ];
74
83
  };
75
84
  const runtimeTypeOf = (value) => {
76
85
  if (value === null)
@@ -83,8 +92,6 @@ const TYPE_MATCHERS = {
83
92
  [JSON_SCHEMA_TYPES.integer]: (actual, value) => actual === JSON_SCHEMA_TYPES.number && Number.isInteger(value),
84
93
  };
85
94
  const matchesDeclaredType = (actual, value, declared) => TYPE_MATCHERS[declared]?.(actual, value) ?? actual === declared;
86
- /** `schema.type` as an array either way — a single type is a union of one. */
87
- const declaredTypesOf = (schema) => schema.type ? (Array.isArray(schema.type) ? schema.type : [schema.type]) : [];
88
95
  const typeMismatch = (value, schema, path) => {
89
96
  const declared = declaredTypesOf(schema);
90
97
  if (declared.length === 0)
@@ -124,13 +131,24 @@ const objectErrors = (value, schema, path) => {
124
131
  if (!value || typeof value !== "object" || Array.isArray(value))
125
132
  return [];
126
133
  const record = value;
134
+ // `Object.hasOwn`, not `key in record` — `in` also matches inherited
135
+ // Object.prototype members (`toString`, `constructor`, `hasOwnProperty`,
136
+ // …), so `required: ["toString"]` would pass against `{}` and a
137
+ // `properties: { constructor: {...} }` entry would validate against the
138
+ // prototype's own `constructor`, neither of which is an actual argument
139
+ // the caller supplied.
127
140
  const requiredErrors = (schema.required ?? [])
128
- .filter((key) => !(key in record))
141
+ .filter((key) => !Object.hasOwn(record, key))
129
142
  .map((key) => `${path}.${key}: required`);
130
143
  const propertyErrors = Object.entries(schema.properties ?? {})
131
- .filter(([key]) => key in record)
144
+ .filter(([key]) => Object.hasOwn(record, key))
132
145
  .flatMap(([key, propSchema]) => validateNode(record[key], propSchema, `${path}.${key}`));
133
- return [...requiredErrors, ...propertyErrors];
146
+ const extraErrors = schema.additionalProperties === false
147
+ ? Object.keys(record)
148
+ .filter((key) => !Object.hasOwn(schema.properties ?? {}, key))
149
+ .map((key) => `${path}.${key}: not allowed`)
150
+ : [];
151
+ return [...requiredErrors, ...propertyErrors, ...extraErrors];
134
152
  };
135
153
  const arrayErrors = (value, schema, path) => {
136
154
  const items = schema.items;
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate.js","sourceRoot":"","sources":["../../src/mcp/validate.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Invoke `ports.audit`, swallowing any failure and racing it against
3
+ * `auditTimeoutMs` — the OAuth-side twin of `methods.ts`'s `safeAudit`.
4
+ * A hanging sink must not stall `/token` or `/authorize` error responses.
5
+ */
6
+ import type { OAuthAuditEntry, OAuthRouterOptions } from "./types.js";
7
+ export declare const safeOAuthAudit: (options: OAuthRouterOptions, entry: OAuthAuditEntry) => Promise<void>;
8
+ /**
9
+ * Fires `legacy_code_secret_used` when a value was recovered with a
10
+ * rotation-array entry other than the primary (`keyIndex > 0`) — the signal
11
+ * every codeSecret rotation site (`/authorize`, `/consent`, `/token`,
12
+ * `firstClientAuthError`) uses to tell an operator which old keys are still
13
+ * live. A no-op for the common case (`keyIndex === 0`, or none at all).
14
+ */
15
+ export declare const auditLegacyKeyUsage: (options: OAuthRouterOptions, usage: {
16
+ clientId: string;
17
+ keyIndex: number | null | undefined;
18
+ source: string;
19
+ }) => Promise<void>;
20
+ //# sourceMappingURL=audit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audit.d.ts","sourceRoot":"","sources":["../../src/oauth/audit.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,KAAK,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAEtE,eAAO,MAAM,cAAc,GACzB,SAAS,kBAAkB,EAC3B,OAAO,eAAe,KACrB,OAAO,CAAC,IAAI,CAKd,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,mBAAmB,GAC9B,SAAS,kBAAkB,EAC3B,OAAO;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,KAC/E,OAAO,CAAC,IAAI,CAOd,CAAC"}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Invoke `ports.audit`, swallowing any failure and racing it against
3
+ * `auditTimeoutMs` — the OAuth-side twin of `methods.ts`'s `safeAudit`.
4
+ * A hanging sink must not stall `/token` or `/authorize` error responses.
5
+ */
6
+ import { DEFAULT_AUDIT_TIMEOUT_MS, runAuditBounded } from "../http/http.js";
7
+ export const safeOAuthAudit = async (options, entry) => {
8
+ const audit = options.ports.audit;
9
+ if (!audit)
10
+ return;
11
+ await runAuditBounded(() => audit(entry), options.auditTimeoutMs ?? DEFAULT_AUDIT_TIMEOUT_MS);
12
+ };
13
+ /**
14
+ * Fires `legacy_code_secret_used` when a value was recovered with a
15
+ * rotation-array entry other than the primary (`keyIndex > 0`) — the signal
16
+ * every codeSecret rotation site (`/authorize`, `/consent`, `/token`,
17
+ * `firstClientAuthError`) uses to tell an operator which old keys are still
18
+ * live. A no-op for the common case (`keyIndex === 0`, or none at all).
19
+ */
20
+ export const auditLegacyKeyUsage = async (options, usage) => {
21
+ if (!usage.keyIndex)
22
+ return;
23
+ await safeOAuthAudit(options, {
24
+ event: "legacy_code_secret_used",
25
+ clientId: usage.clientId,
26
+ reason: `${usage.source} with codeSecret[${usage.keyIndex}]`,
27
+ });
28
+ };
29
+ //# sourceMappingURL=audit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audit.js","sourceRoot":"","sources":["../../src/oauth/audit.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,wBAAwB,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAG5E,MAAM,CAAC,MAAM,cAAc,GAAG,KAAK,EACjC,OAA2B,EAC3B,KAAsB,EACP,EAAE;IACjB,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC;IAClC,IAAI,CAAC,KAAK;QAAE,OAAO;IAEnB,MAAM,eAAe,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC,cAAc,IAAI,wBAAwB,CAAC,CAAC;AAChG,CAAC,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,KAAK,EACtC,OAA2B,EAC3B,KAAgF,EACjE,EAAE;IACjB,IAAI,CAAC,KAAK,CAAC,QAAQ;QAAE,OAAO;IAC5B,MAAM,cAAc,CAAC,OAAO,EAAE;QAC5B,KAAK,EAAE,yBAAyB;QAChC,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,MAAM,EAAE,GAAG,KAAK,CAAC,MAAM,oBAAoB,KAAK,CAAC,QAAQ,GAAG;KAC7D,CAAC,CAAC;AACL,CAAC,CAAC"}
@@ -0,0 +1,4 @@
1
+ import type { CimdDocument } from "./types.js";
2
+ export declare const parseCacheMaxAge: (header: string | null) => number;
3
+ export declare const parseDocument: (clientId: string, text: string) => CimdDocument;
4
+ //# sourceMappingURL=cimd-parse.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cimd-parse.d.ts","sourceRoot":"","sources":["../../../src/oauth/cimd/cimd-parse.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAK/C,eAAO,MAAM,gBAAgB,GAAI,QAAQ,MAAM,GAAG,IAAI,KAAG,MAGxD,CAAC;AAoCF,eAAO,MAAM,aAAa,GAAI,UAAU,MAAM,EAAE,MAAM,MAAM,KAAG,YAsB9D,CAAC"}
@@ -0,0 +1,70 @@
1
+ import { isJsonObject } from "../../util/json.js";
2
+ import { MAX_CLIENT_REDIRECT_URIS } from "../constants.js";
3
+ import { isLoopbackHostname } from "./cimd-ssrf.js";
4
+ const DEFAULT_CACHE_SECONDS = 300;
5
+ const MAX_CACHE_SECONDS = 3600;
6
+ export const parseCacheMaxAge = (header) => {
7
+ const maxAge = header ? /(?:^|,)\s*max-age=(\d+)/i.exec(header)?.[1] : undefined;
8
+ return maxAge === undefined ? DEFAULT_CACHE_SECONDS : Math.min(Number(maxAge), MAX_CACHE_SECONDS);
9
+ };
10
+ const assertHttpRedirectUri = (uri) => {
11
+ let parsed;
12
+ try {
13
+ parsed = new URL(uri);
14
+ }
15
+ catch {
16
+ throw new Error("CIMD redirect_uris entries must be absolute URLs");
17
+ }
18
+ if (parsed.username || parsed.password || parsed.hash) {
19
+ throw new Error("CIMD redirect_uris must not carry credentials or a fragment");
20
+ }
21
+ if (parsed.protocol === "https:")
22
+ return;
23
+ if (parsed.protocol === "http:") {
24
+ if (!isLoopbackHostname(parsed.hostname)) {
25
+ throw new Error("CIMD http redirect_uris must be loopback (127.0.0.1 / ::1)");
26
+ }
27
+ return;
28
+ }
29
+ throw new Error("CIMD redirect_uris must be http(s)");
30
+ };
31
+ const parseRedirectUris = (rawUris) => {
32
+ if (!Array.isArray(rawUris) || rawUris.length === 0) {
33
+ throw new Error("CIMD document must include a non-empty redirect_uris array");
34
+ }
35
+ if (rawUris.length > MAX_CLIENT_REDIRECT_URIS) {
36
+ throw new Error(`CIMD redirect_uris must have at most ${MAX_CLIENT_REDIRECT_URIS} entries`);
37
+ }
38
+ return rawUris.map((u) => {
39
+ if (typeof u !== "string" || !u)
40
+ throw new Error("CIMD redirect_uris entries must be strings");
41
+ assertHttpRedirectUri(u);
42
+ return u;
43
+ });
44
+ };
45
+ export const parseDocument = (clientId, text) => {
46
+ let parsed;
47
+ try {
48
+ parsed = JSON.parse(text);
49
+ }
50
+ catch {
51
+ throw new Error("CIMD document is not JSON");
52
+ }
53
+ if (!isJsonObject(parsed))
54
+ throw new Error("CIMD document must be a JSON object");
55
+ const record = parsed;
56
+ if (record.client_id !== clientId) {
57
+ throw new Error("CIMD document client_id must equal the request URL exactly");
58
+ }
59
+ const authMethod = record.token_endpoint_auth_method;
60
+ if (authMethod !== undefined && authMethod !== "none") {
61
+ throw new Error("CIMD clients must be public (token_endpoint_auth_method none)");
62
+ }
63
+ return {
64
+ client_id: clientId,
65
+ redirect_uris: parseRedirectUris(record.redirect_uris),
66
+ ...(typeof record.client_name === "string" ? { client_name: record.client_name } : {}),
67
+ token_endpoint_auth_method: "none",
68
+ };
69
+ };
70
+ //# sourceMappingURL=cimd-parse.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cimd-parse.js","sourceRoot":"","sources":["../../../src/oauth/cimd/cimd-parse.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * SSRF guards for CIMD URL fetch: hostname normalization, private/NAT64
3
+ * IP blocks, and DNS lookup before connect.
4
+ */
5
+ export declare const isLoopbackHostname: (hostname: string) => boolean;
6
+ export declare const defaultLookup: (hostname: string) => Promise<string[]>;
7
+ export declare const assertSafeUrl: (url: URL, lookup: (hostname: string) => Promise<string[]>) => Promise<void>;
8
+ //# sourceMappingURL=cimd-ssrf.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cimd-ssrf.d.ts","sourceRoot":"","sources":["../../../src/oauth/cimd/cimd-ssrf.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAoLH,eAAO,MAAM,kBAAkB,GAAI,UAAU,MAAM,KAAG,OAEL,CAAC;AAElD,eAAO,MAAM,aAAa,GAAU,UAAU,MAAM,KAAG,OAAO,CAAC,MAAM,EAAE,CAQtE,CAAC;AAiBF,eAAO,MAAM,aAAa,GACxB,KAAK,GAAG,EACR,QAAQ,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,EAAE,CAAC,KAC9C,OAAO,CAAC,IAAI,CASd,CAAC"}