mcp-trellis 2.1.0 → 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 (106) hide show
  1. package/README.md +41 -39
  2. package/dist/adapters/node-request.d.ts +5 -0
  3. package/dist/adapters/node-request.d.ts.map +1 -1
  4. package/dist/adapters/node-request.js +17 -1
  5. package/dist/adapters/node-request.js.map +1 -1
  6. package/dist/adapters/node.d.ts +15 -0
  7. package/dist/adapters/node.d.ts.map +1 -1
  8. package/dist/adapters/node.js +38 -0
  9. package/dist/adapters/node.js.map +1 -1
  10. package/dist/audit.d.ts.map +1 -1
  11. package/dist/audit.js +63 -6
  12. package/dist/audit.js.map +1 -1
  13. package/dist/auth/signed-token.d.ts +26 -0
  14. package/dist/auth/signed-token.d.ts.map +1 -0
  15. package/dist/auth/signed-token.js +96 -0
  16. package/dist/auth/signed-token.js.map +1 -0
  17. package/dist/clients.js +2 -2
  18. package/dist/clients.js.map +1 -1
  19. package/dist/http/origins.d.ts.map +1 -1
  20. package/dist/http/origins.js +13 -2
  21. package/dist/http/origins.js.map +1 -1
  22. package/dist/mcp/dispatch.d.ts.map +1 -1
  23. package/dist/mcp/dispatch.js +2 -1
  24. package/dist/mcp/dispatch.js.map +1 -1
  25. package/dist/mcp/methods.d.ts +1 -0
  26. package/dist/mcp/methods.d.ts.map +1 -1
  27. package/dist/mcp/methods.js +1 -0
  28. package/dist/mcp/methods.js.map +1 -1
  29. package/dist/mcp/registry.d.ts.map +1 -1
  30. package/dist/mcp/registry.js +15 -3
  31. package/dist/mcp/registry.js.map +1 -1
  32. package/dist/mcp/rpc-handlers.d.ts.map +1 -1
  33. package/dist/mcp/rpc-handlers.js +58 -11
  34. package/dist/mcp/rpc-handlers.js.map +1 -1
  35. package/dist/mcp/tools.d.ts +14 -0
  36. package/dist/mcp/tools.d.ts.map +1 -1
  37. package/dist/mcp/tools.js +114 -17
  38. package/dist/mcp/tools.js.map +1 -1
  39. package/dist/mcp/validate.d.ts +7 -1
  40. package/dist/mcp/validate.d.ts.map +1 -1
  41. package/dist/mcp/validate.js +58 -24
  42. package/dist/mcp/validate.js.map +1 -1
  43. package/dist/oauth/audit.d.ts +10 -0
  44. package/dist/oauth/audit.d.ts.map +1 -1
  45. package/dist/oauth/audit.js +25 -1
  46. package/dist/oauth/audit.js.map +1 -1
  47. package/dist/oauth/cimd/cimd-parse.d.ts.map +1 -1
  48. package/dist/oauth/cimd/cimd-parse.js +27 -14
  49. package/dist/oauth/cimd/cimd-parse.js.map +1 -1
  50. package/dist/oauth/cimd/cimd-ssrf.d.ts.map +1 -1
  51. package/dist/oauth/cimd/cimd-ssrf.js +39 -6
  52. package/dist/oauth/cimd/cimd-ssrf.js.map +1 -1
  53. package/dist/oauth/cimd/cimd.d.ts +11 -2
  54. package/dist/oauth/cimd/cimd.d.ts.map +1 -1
  55. package/dist/oauth/cimd/cimd.js +40 -34
  56. package/dist/oauth/cimd/cimd.js.map +1 -1
  57. package/dist/oauth/cimd/failure.d.ts +19 -0
  58. package/dist/oauth/cimd/failure.d.ts.map +1 -0
  59. package/dist/oauth/cimd/failure.js +28 -0
  60. package/dist/oauth/cimd/failure.js.map +1 -0
  61. package/dist/oauth/crypto/clientauth.d.ts.map +1 -1
  62. package/dist/oauth/crypto/clientauth.js +7 -6
  63. package/dist/oauth/crypto/clientauth.js.map +1 -1
  64. package/dist/oauth/endpoints/authorize.d.ts.map +1 -1
  65. package/dist/oauth/endpoints/authorize.js +24 -6
  66. package/dist/oauth/endpoints/authorize.js.map +1 -1
  67. package/dist/oauth/endpoints/consent-page.d.ts.map +1 -1
  68. package/dist/oauth/endpoints/consent-page.js +34 -1
  69. package/dist/oauth/endpoints/consent-page.js.map +1 -1
  70. package/dist/oauth/endpoints/token.d.ts.map +1 -1
  71. package/dist/oauth/endpoints/token.js +7 -1
  72. package/dist/oauth/endpoints/token.js.map +1 -1
  73. package/dist/oauth/policy/redirect.d.ts +6 -0
  74. package/dist/oauth/policy/redirect.d.ts.map +1 -1
  75. package/dist/oauth/policy/redirect.js +6 -0
  76. package/dist/oauth/policy/redirect.js.map +1 -1
  77. package/dist/oauth/policy/reqbody.d.ts +6 -1
  78. package/dist/oauth/policy/reqbody.d.ts.map +1 -1
  79. package/dist/oauth/policy/reqbody.js +8 -3
  80. package/dist/oauth/policy/reqbody.js.map +1 -1
  81. package/dist/oauth/types.d.ts +3 -1
  82. package/dist/oauth/types.d.ts.map +1 -1
  83. package/dist/server.d.ts +1 -0
  84. package/dist/server.d.ts.map +1 -1
  85. package/dist/server.js +1 -0
  86. package/dist/server.js.map +1 -1
  87. package/dist/util/clip.d.ts +7 -0
  88. package/dist/util/clip.d.ts.map +1 -0
  89. package/dist/util/clip.js +13 -0
  90. package/dist/util/clip.js.map +1 -0
  91. package/dist/util/json.d.ts +5 -0
  92. package/dist/util/json.d.ts.map +1 -1
  93. package/dist/util/json.js +21 -0
  94. package/dist/util/json.js.map +1 -1
  95. package/dist/util/own.d.ts +8 -0
  96. package/dist/util/own.d.ts.map +1 -0
  97. package/dist/util/own.js +8 -0
  98. package/dist/util/own.js.map +1 -0
  99. package/docs/ROADMAP.md +3 -1
  100. package/docs/compatibility.md +13 -11
  101. package/docs/diagrams/demo-tool-result.png +0 -0
  102. package/docs/guide.md +26 -9
  103. package/docs/reference.md +3 -1
  104. package/docs/security.md +6 -4
  105. package/docs/troubleshooting.md +1 -0
  106. package/package.json +8 -4
@@ -33,7 +33,9 @@ export type ConsentOptions = {
33
33
  * redirect to your own route. Omit for the built-in hardened interstitial.
34
34
  *
35
35
  * If your page sets its own `Content-Security-Policy` with `form-action`,
36
- * include `new URL(input.redirectUri).origin` in it — Chromium enforces
36
+ * include the redirect target's origin in it (`new URL(input.redirectUri).origin`;
37
+ * for a custom-scheme URI such as `myapp://cb` that origin is the opaque
38
+ * `"null"`, so use the scheme `myapp:` instead) — Chromium enforces
37
39
  * `form-action` across the redirect your approval POST triggers, not just
38
40
  * the immediate submission target, so `'self'` alone blocks the eventual
39
41
  * redirect back to the client's own callback.
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/oauth/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/oauth/types.ts"],"names":[],"mappings":"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"}
package/dist/server.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  export { createMcpApp, type McpApp, type McpAppAuth, type McpAppOptions, type UnifiedAuditEntry, type VerifiedToken, } from "./app.js";
2
2
  export { consoleAudit } from "./audit.js";
3
3
  export { parseBearer, rejectQueryToken, type WwwAuthenticateOptions, wwwAuthenticateHeader, } from "./auth/bearer.js";
4
+ export { type SignedTokenAuthOptions, signedTokenAuth, } from "./auth/signed-token.js";
4
5
  export { authMethodsFor, CLIENT_PROFILES, type ClientName, type ClientProfile, DEFAULT_CLIENTS, hasDynamicClient, preRegisteredClients, redirectUrisFor, } from "./clients.js";
5
6
  export * from "./deprecated.js";
6
7
  export { type JsonResponseInput, type JsonResponseOptions, jsonResponse, } from "./http/http.js";
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,YAAY,EACZ,KAAK,MAAM,EACX,KAAK,UAAU,EACf,KAAK,aAAa,EAClB,KAAK,iBAAiB,EACtB,KAAK,aAAa,GACnB,MAAM,UAAU,CAAC;AAClB,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1C,OAAO,EACL,WAAW,EACX,gBAAgB,EAChB,KAAK,sBAAsB,EAC3B,qBAAqB,GACtB,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EACL,cAAc,EACd,eAAe,EACf,KAAK,UAAU,EACf,KAAK,aAAa,EAClB,eAAe,EACf,gBAAgB,EAChB,oBAAoB,EACpB,eAAe,GAChB,MAAM,cAAc,CAAC;AAEtB,cAAc,iBAAiB,CAAC;AAChC,OAAO,EACL,KAAK,iBAAiB,EACtB,KAAK,mBAAmB,EACxB,YAAY,GACb,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,KAAK,UAAU,EACf,gBAAgB,EAChB,KAAK,UAAU,EACf,KAAK,iBAAiB,EACtB,KAAK,QAAQ,EACb,KAAK,SAAS,EACd,KAAK,UAAU,GAChB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,sBAAsB,EACtB,sBAAsB,EACtB,uBAAuB,EACvB,wBAAwB,EACxB,mBAAmB,EACnB,oBAAoB,EACpB,KAAK,SAAS,EACd,KAAK,cAAc,EACnB,KAAK,eAAe,EACpB,QAAQ,EACR,SAAS,GACV,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,wBAAwB,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAChF,OAAO,EACL,kBAAkB,EAClB,KAAK,eAAe,EACpB,KAAK,OAAO,EACZ,KAAK,WAAW,EAChB,KAAK,aAAa,EAClB,KAAK,YAAY,EACjB,KAAK,UAAU,GAChB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,KAAK,UAAU,EACf,KAAK,cAAc,EACnB,OAAO,EACP,KAAK,iBAAiB,EACtB,UAAU,EACV,KAAK,gBAAgB,GACtB,MAAM,gBAAgB,CAAC;AACxB,YAAY,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AACpD,YAAY,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AACzD,kFAAkF;AAClF,YAAY,EACV,WAAW,EACX,cAAc,EACd,cAAc,EACd,oBAAoB,EACpB,WAAW,EACX,SAAS,EACT,uBAAuB,EACvB,gBAAgB,EAChB,gBAAgB,GACjB,MAAM,kBAAkB,CAAC"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,YAAY,EACZ,KAAK,MAAM,EACX,KAAK,UAAU,EACf,KAAK,aAAa,EAClB,KAAK,iBAAiB,EACtB,KAAK,aAAa,GACnB,MAAM,UAAU,CAAC;AAClB,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1C,OAAO,EACL,WAAW,EACX,gBAAgB,EAChB,KAAK,sBAAsB,EAC3B,qBAAqB,GACtB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,KAAK,sBAAsB,EAC3B,eAAe,GAChB,MAAM,wBAAwB,CAAC;AAEhC,OAAO,EACL,cAAc,EACd,eAAe,EACf,KAAK,UAAU,EACf,KAAK,aAAa,EAClB,eAAe,EACf,gBAAgB,EAChB,oBAAoB,EACpB,eAAe,GAChB,MAAM,cAAc,CAAC;AAEtB,cAAc,iBAAiB,CAAC;AAChC,OAAO,EACL,KAAK,iBAAiB,EACtB,KAAK,mBAAmB,EACxB,YAAY,GACb,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,KAAK,UAAU,EACf,gBAAgB,EAChB,KAAK,UAAU,EACf,KAAK,iBAAiB,EACtB,KAAK,QAAQ,EACb,KAAK,SAAS,EACd,KAAK,UAAU,GAChB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,sBAAsB,EACtB,sBAAsB,EACtB,uBAAuB,EACvB,wBAAwB,EACxB,mBAAmB,EACnB,oBAAoB,EACpB,KAAK,SAAS,EACd,KAAK,cAAc,EACnB,KAAK,eAAe,EACpB,QAAQ,EACR,SAAS,GACV,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,wBAAwB,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAChF,OAAO,EACL,kBAAkB,EAClB,KAAK,eAAe,EACpB,KAAK,OAAO,EACZ,KAAK,WAAW,EAChB,KAAK,aAAa,EAClB,KAAK,YAAY,EACjB,KAAK,UAAU,GAChB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,KAAK,UAAU,EACf,KAAK,cAAc,EACnB,OAAO,EACP,KAAK,iBAAiB,EACtB,UAAU,EACV,KAAK,gBAAgB,GACtB,MAAM,gBAAgB,CAAC;AACxB,YAAY,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AACpD,YAAY,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AACzD,kFAAkF;AAClF,YAAY,EACV,WAAW,EACX,cAAc,EACd,cAAc,EACd,oBAAoB,EACpB,WAAW,EACX,SAAS,EACT,uBAAuB,EACvB,gBAAgB,EAChB,gBAAgB,GACjB,MAAM,kBAAkB,CAAC"}
package/dist/server.js CHANGED
@@ -1,6 +1,7 @@
1
1
  export { createMcpApp, } from "./app.js";
2
2
  export { consoleAudit } from "./audit.js";
3
3
  export { parseBearer, rejectQueryToken, wwwAuthenticateHeader, } from "./auth/bearer.js";
4
+ export { signedTokenAuth, } from "./auth/signed-token.js";
4
5
  export { authMethodsFor, CLIENT_PROFILES, DEFAULT_CLIENTS, hasDynamicClient, preRegisteredClients, redirectUrisFor, } from "./clients.js";
5
6
  // Moved to `mcp-trellis/advanced`; deprecated aliases until 3.0.
6
7
  export * from "./deprecated.js";
@@ -1 +1 @@
1
- {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,YAAY,GAMb,MAAM,UAAU,CAAC;AAClB,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1C,OAAO,EACL,WAAW,EACX,gBAAgB,EAEhB,qBAAqB,GACtB,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EACL,cAAc,EACd,eAAe,EAGf,eAAe,EACf,gBAAgB,EAChB,oBAAoB,EACpB,eAAe,GAChB,MAAM,cAAc,CAAC;AACtB,iEAAiE;AACjE,cAAc,iBAAiB,CAAC;AAChC,OAAO,EAGL,YAAY,GACb,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAEL,gBAAgB,GAMjB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,sBAAsB,EACtB,sBAAsB,EACtB,uBAAuB,EACvB,wBAAwB,EACxB,mBAAmB,EACnB,oBAAoB,EAIpB,QAAQ,EACR,SAAS,GACV,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,wBAAwB,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAChF,OAAO,EACL,kBAAkB,GAOnB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAGL,OAAO,EAEP,UAAU,GAEX,MAAM,gBAAgB,CAAC"}
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,YAAY,GAMb,MAAM,UAAU,CAAC;AAClB,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1C,OAAO,EACL,WAAW,EACX,gBAAgB,EAEhB,qBAAqB,GACtB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAEL,eAAe,GAChB,MAAM,wBAAwB,CAAC;AAEhC,OAAO,EACL,cAAc,EACd,eAAe,EAGf,eAAe,EACf,gBAAgB,EAChB,oBAAoB,EACpB,eAAe,GAChB,MAAM,cAAc,CAAC;AACtB,iEAAiE;AACjE,cAAc,iBAAiB,CAAC;AAChC,OAAO,EAGL,YAAY,GACb,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAEL,gBAAgB,GAMjB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,sBAAsB,EACtB,sBAAsB,EACtB,uBAAuB,EACvB,wBAAwB,EACxB,mBAAmB,EACnB,oBAAoB,EAIpB,QAAQ,EACR,SAAS,GACV,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,wBAAwB,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAChF,OAAO,EACL,kBAAkB,GAOnB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAGL,OAAO,EAEP,UAAU,GAEX,MAAM,gBAAgB,CAAC"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * The first `max` UTF-16 units of `value`, without leaving half a surrogate
3
+ * pair at the cut — a lone surrogate is invalid in JSON and many DB sinks.
4
+ * `suffix` is appended only when something was cut.
5
+ */
6
+ export declare const clipText: (value: string, max: number, suffix?: string) => string;
7
+ //# sourceMappingURL=clip.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clip.d.ts","sourceRoot":"","sources":["../../src/util/clip.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,eAAO,MAAM,QAAQ,GAAI,OAAO,MAAM,EAAE,KAAK,MAAM,EAAE,eAAW,KAAG,MAKlE,CAAC"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * The first `max` UTF-16 units of `value`, without leaving half a surrogate
3
+ * pair at the cut — a lone surrogate is invalid in JSON and many DB sinks.
4
+ * `suffix` is appended only when something was cut.
5
+ */
6
+ export const clipText = (value, max, suffix = "") => {
7
+ if (value.length <= max)
8
+ return value;
9
+ const last = value.charCodeAt(max - 1);
10
+ const cut = last >= 0xd800 && last <= 0xdbff ? max - 1 : max;
11
+ return `${value.slice(0, cut)}${suffix}`;
12
+ };
13
+ //# sourceMappingURL=clip.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clip.js","sourceRoot":"","sources":["../../src/util/clip.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,KAAa,EAAE,GAAW,EAAE,MAAM,GAAG,EAAE,EAAU,EAAE;IAC1E,IAAI,KAAK,CAAC,MAAM,IAAI,GAAG;QAAE,OAAO,KAAK,CAAC;IACtC,MAAM,IAAI,GAAG,KAAK,CAAC,UAAU,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC;IACvC,MAAM,GAAG,GAAG,IAAI,IAAI,MAAM,IAAI,IAAI,IAAI,MAAM,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;IAC7D,OAAO,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,GAAG,MAAM,EAAE,CAAC;AAC3C,CAAC,CAAC"}
@@ -1,3 +1,8 @@
1
1
  /** A parsed JSON object — not `null`, not an array. */
2
2
  export declare const isJsonObject: (value: unknown) => value is Record<string, unknown>;
3
+ /**
4
+ * True when two JSON values match structurally (object key order ignored).
5
+ * ponytail: assumes acyclic JSON-parsed inputs; upgrade = depth/cycle guard.
6
+ */
7
+ export declare const areJsonValuesEqual: (value: unknown, other: unknown) => boolean;
3
8
  //# sourceMappingURL=json.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"json.d.ts","sourceRoot":"","sources":["../../src/util/json.ts"],"names":[],"mappings":"AAAA,uDAAuD;AACvD,eAAO,MAAM,YAAY,GAAI,OAAO,OAAO,KAAG,KAAK,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CACP,CAAC"}
1
+ {"version":3,"file":"json.d.ts","sourceRoot":"","sources":["../../src/util/json.ts"],"names":[],"mappings":"AAAA,uDAAuD;AACvD,eAAO,MAAM,YAAY,GAAI,OAAO,OAAO,KAAG,KAAK,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CACP,CAAC;AAEvE;;;GAGG;AACH,eAAO,MAAM,kBAAkB,GAAI,OAAO,OAAO,EAAE,OAAO,OAAO,KAAG,OAqBnE,CAAC"}
package/dist/util/json.js CHANGED
@@ -1,3 +1,24 @@
1
1
  /** A parsed JSON object — not `null`, not an array. */
2
2
  export const isJsonObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
3
+ /**
4
+ * True when two JSON values match structurally (object key order ignored).
5
+ * ponytail: assumes acyclic JSON-parsed inputs; upgrade = depth/cycle guard.
6
+ */
7
+ export const areJsonValuesEqual = (value, other) => {
8
+ // `===` (not `Object.is`): JSON `-0` must still equal `0`, as `enum.includes` always did.
9
+ if (value === other)
10
+ return true;
11
+ if (Array.isArray(value) || Array.isArray(other)) {
12
+ return (Array.isArray(value) &&
13
+ Array.isArray(other) &&
14
+ value.length === other.length &&
15
+ value.every((item, index) => areJsonValuesEqual(item, other[index])));
16
+ }
17
+ if (!isJsonObject(value) || !isJsonObject(other))
18
+ return false;
19
+ const valueKeys = Object.keys(value);
20
+ const otherKeys = Object.keys(other);
21
+ return (valueKeys.length === otherKeys.length &&
22
+ valueKeys.every((key) => Object.hasOwn(other, key) && areJsonValuesEqual(value[key], other[key])));
23
+ };
3
24
  //# sourceMappingURL=json.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"json.js","sourceRoot":"","sources":["../../src/util/json.ts"],"names":[],"mappings":"AAAA,uDAAuD;AACvD,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,KAAc,EAAoC,EAAE,CAC/E,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC"}
1
+ {"version":3,"file":"json.js","sourceRoot":"","sources":["../../src/util/json.ts"],"names":[],"mappings":"AAAA,uDAAuD;AACvD,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,KAAc,EAAoC,EAAE,CAC/E,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AAEvE;;;GAGG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,KAAc,EAAE,KAAc,EAAW,EAAE;IAC5E,0FAA0F;IAC1F,IAAI,KAAK,KAAK,KAAK;QAAE,OAAO,IAAI,CAAC;IACjC,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACjD,OAAO,CACL,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;YACpB,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;YACpB,KAAK,CAAC,MAAM,KAAK,KAAK,CAAC,MAAM;YAC7B,KAAK,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,kBAAkB,CAAC,IAAI,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CACrE,CAAC;IACJ,CAAC;IACD,IAAI,CAAC,YAAY,CAAC,KAAK,CAAC,IAAI,CAAC,YAAY,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IAE/D,MAAM,SAAS,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACrC,MAAM,SAAS,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACrC,OAAO,CACL,SAAS,CAAC,MAAM,KAAK,SAAS,CAAC,MAAM;QACrC,SAAS,CAAC,KAAK,CACb,CAAC,GAAG,EAAE,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,GAAG,CAAC,IAAI,kBAAkB,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CACjF,CACF,CAAC;AACJ,CAAC,CAAC"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * `record[key]` for an attacker-supplied `key`: only an own property counts.
3
+ * A plain object literal also answers `"constructor"`, `"toString"` and
4
+ * `"__proto__"` from `Object.prototype`, which would otherwise be mistaken
5
+ * for a registered handler.
6
+ */
7
+ export declare const ownValue: <V>(record: Readonly<Record<string, V>>, key: string) => V | undefined;
8
+ //# sourceMappingURL=own.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"own.d.ts","sourceRoot":"","sources":["../../src/util/own.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,eAAO,MAAM,QAAQ,GAAI,CAAC,EAAE,QAAQ,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,MAAM,KAAG,CAAC,GAAG,SAC7B,CAAC"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * `record[key]` for an attacker-supplied `key`: only an own property counts.
3
+ * A plain object literal also answers `"constructor"`, `"toString"` and
4
+ * `"__proto__"` from `Object.prototype`, which would otherwise be mistaken
5
+ * for a registered handler.
6
+ */
7
+ export const ownValue = (record, key) => Object.hasOwn(record, key) ? record[key] : undefined;
8
+ //# sourceMappingURL=own.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"own.js","sourceRoot":"","sources":["../../src/util/own.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAI,MAAmC,EAAE,GAAW,EAAiB,EAAE,CAC7F,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC"}
package/docs/ROADMAP.md CHANGED
@@ -231,7 +231,9 @@ a proper JSON-RPC error.
231
231
  - **Unified `audit` sink** on `createMcpApp` — one callback for MCP and
232
232
  OAuth events (`source: "mcp" | "oauth"`); `auth.audit` remains an
233
233
  OAuth-only override.
234
- - **Framework recipes** — `examples/nextjs-route.ts`, `examples/express.ts`.
234
+ - **Framework recipes** — `examples/nextjs-saas/`, `examples/express.ts`.
235
+ - **Reference MCP Registry server** — `examples/project-desk/`
236
+ (`io.github.amir1824/project-desk`, remotes only; not the SDK package).
235
237
  - Fail-closed defaults: `allowInMemoryCodeStore` / `validateArgs`.
236
238
  - **RFC 9207 `iss`** on authorize / error redirects; AS metadata advertises
237
239
  `authorization_response_iss_parameter_supported`.
@@ -18,24 +18,25 @@ The Gemini profile does not imply compatibility with Gemini CLI or every Gemini
18
18
 
19
19
  ## Demo deploy (Worker)
20
20
 
21
- Compile-checked Worker sketch: [`examples/cloudflare-worker.ts`](../examples/cloudflare-worker.ts).
22
-
23
- One-liner after you add a minimal `wrangler.toml` (name, `compatibility_date`, and secrets
24
- `OAUTH_CODE_SECRET` / `ACCESS_TOKEN_SECRET`):
21
+ **Project desk** (Registry remotes target): [`examples/project-desk/worker.ts`](../examples/project-desk/worker.ts)
22
+ via root [`wrangler.toml`](../wrangler.toml).
25
23
 
26
24
  ```bash
25
+ npx wrangler secret put DEMO_PASSWORD # project-desk-demo for the public demo
27
26
  npx wrangler secret put OAUTH_CODE_SECRET
28
- npx wrangler secret put ACCESS_TOKEN_SECRET
29
- npx wrangler deploy examples/cloudflare-worker.ts
27
+ npx wrangler deploy
30
28
  ```
31
29
 
32
- Point a connector at `https://YOUR-WORKER/mcp` only after secrets and an origin
33
- allowlist match your route. This path proves the Worker mounts; it does **not**
34
- replace the live client rows below.
30
+ MCP URL: `https://mcp-trellis-project-desk.<workers-subdomain>.workers.dev/mcp`.
31
+ Operator checklist: [`examples/project-desk/PUBLISH.md`](../examples/project-desk/PUBLISH.md).
32
+ This does **not** replace the live client rows below.
33
+
34
+ Minimal ping-only Worker recipe (not the hosted demo):
35
+ [`examples/cloudflare-worker.ts`](../examples/cloudflare-worker.ts).
35
36
 
36
37
  ## Host contract covered in-repo
37
38
 
38
- `examples/saas-demo` now mints refresh tokens and enforces RFC 6749 §6: requested
39
+ `examples/project-desk` mints refresh tokens and enforces RFC 6749 §6: requested
39
40
  refresh `scope` must be ⊆ the scopes originally bound to that refresh token
40
41
  (`isScopeSubset` pattern also in `examples/stores.ts`). Escalation →
41
42
  `invalid_grant`. Covered by `test/e2e/saas-demo.test.ts`.
@@ -58,10 +59,11 @@ release; it does **not** replace the live client row.
58
59
 
59
60
  Record client product, version/build (or web UI and test date), account tier, package version/commit, hosting environment and configured callback. Capture discovery → registration → app login → consent → token exchange → initialize → tools/list → `list_my_projects`. Verify wrong audience/invalid token rejection and user isolation separately. Save sanitized output or recording; exclude cookies, passwords, codes and tokens.
60
61
 
61
- Against a public HTTPS demo (`PUBLIC_ORIGIN` + `DEMO_PASSWORD`):
62
+ Against a public HTTPS demo (`PUBLIC_ORIGIN` + `DEMO_PASSWORD=project-desk-demo`):
62
63
 
63
64
  ```bash
64
65
  npm run demo # reverse-proxy to HTTPS; set PUBLIC_ORIGIN
66
+ # or use the deployed Worker from examples/project-desk/PUBLISH.md
65
67
  node scripts/prep-claude-oauth.mjs # scripted OAuth + refresh scope + tool
66
68
  # Then open the vendor UI with MCP URL https://YOUR-HOST/mcp and record below.
67
69
  ```
package/docs/guide.md CHANGED
@@ -2,8 +2,11 @@
2
2
 
3
3
  Practical deep dive: architecture, host recipes, ports, tools, and multi-tenant
4
4
  connectors. For a working minimal server, start from the [README](../README.md).
5
- API tables and exports live in [reference.md](reference.md). Security guarantees
6
- and threat model: [security.md](security.md).
5
+ The **canonical example** is [`examples/project-desk`](../examples/project-desk/)
6
+ (Project desk: login → consent → user-scoped tools; local docs in
7
+ [`examples/saas-demo`](../examples/saas-demo/)). API tables and exports live
8
+ in [reference.md](reference.md). Security guarantees and threat model:
9
+ [security.md](security.md).
7
10
 
8
11
  [Architecture](#how-it-fits-together) · [Clients](#clients) · [Recipes](#recipes) ·
9
12
  [Compose the primitives](#advanced-compose-the-primitives) ·
@@ -69,7 +72,7 @@ registration.
69
72
  // Worker
70
73
  export default { fetch: (req: Request, env: Env) => buildApp(env).fetch(req) };
71
74
 
72
- // Next.js App Router — app/[[...mcp]]/route.ts
75
+ // Next.js App Router — see examples/nextjs-saas/
73
76
  export const GET = (req: Request) => app.fetch(req);
74
77
  export const POST = (req: Request) => app.fetch(req);
75
78
  export const OPTIONS = (req: Request) => app.fetch(req);
@@ -181,8 +184,7 @@ Also mount `.well-known/*` and `/mcp/oauth/*` the same way (or route everything
181
184
 
182
185
  Express, Cloud Functions, Cloud Run, `http.createServer` — bridge with
183
186
  `asNodeHandler`. Runnable recipe: [`examples/http-server.ts`](../examples/http-server.ts)
184
- (set `OAUTH_CODE_SECRET` and `ACCESS_TOKEN_SECRET`, e.g. from `openssl rand -base64 32`,
185
- then `npx tsx examples/http-server.ts`).
187
+ (set `MCP_SECRET`, e.g. from `openssl rand -base64 32`, then `npx tsx examples/http-server.ts`).
186
188
 
187
189
  ## Ports — what you implement
188
190
 
@@ -192,6 +194,10 @@ The library stays protocol-shaped. Your app plugs in the seams:
192
194
 
193
195
  ### App (`createMcpApp`)
194
196
 
197
+ Prefer `signedTokenAuth({ secret, resolveUser, loginUrl, codeStore })` to fill
198
+ `codeSecret` / `mintAccessToken` / `verifyToken` from one secret. The table
199
+ below is the advanced port surface when you outgrow the helper.
200
+
195
201
  | Port | Role |
196
202
  |------|------|
197
203
  | `verifyToken(token, req)` | Decode a bearer and return `{ userId, scopes, audience, claims? }`, or `null`. **The library compares `audience` against this server's canonical resource** — you cannot forget the check. Optional `claims` are passed to `context` on `principal` |
@@ -242,8 +248,15 @@ createMcpApp({
242
248
 
243
249
  Failures (MCP denials and tool exceptions) go to `console.error` when using
244
250
  `consoleAudit`; MCP successes and OAuth events go to `console.log`. Entries never carry raw
245
- exceptions or stack traces, so console output is safe. For anything beyond
246
- stdout, write your own `audit` function — that is the intended production path.
251
+ exceptions or stack traces from your tools, so console output is safe: each
252
+ caller-chosen value (`client_id`, tool name, error text) is cut to 200
253
+ characters, control and invisible characters are escaped, and a value
254
+ containing a space, `=` or a quote is printed as a quoted string, so it cannot
255
+ forge a log line or a `key=value` field (`error="Tool execution failed"`,
256
+ `reason="secretHash mismatch"`). Host-level failures that reach `audit` carry
257
+ the exception's message. For anything beyond stdout, write your own `audit`
258
+ function — that is the intended production path; the entries it receives
259
+ are not escaped, so escape or parameterize them for your sink.
247
260
 
248
261
  `wwwAuthenticate` can be a static object or `(req) => …` when the PRM URL depends on origin.
249
262
 
@@ -317,7 +330,7 @@ authorization code, or vice versa.
317
330
  Schema and handler live in one place — no parallel "defs" and "handlers" lists to keep in sync:
318
331
 
319
332
  ```ts
320
- createToolRegistry(tools, { validateArgs: false }) // default: off, easy adoption
333
+ createToolRegistry(tools) // validateArgs defaults to true; pass { validateArgs: false } to skip it
321
334
  // onToolError?: (exc) => string — default redacts to "Tool execution failed"
322
335
  ```
323
336
 
@@ -383,7 +396,11 @@ const getWeather = apiTool({
383
396
  — **status only by default, the upstream body is never forwarded** (it
384
397
  could contain internal detail, tokens, or SQL an upstream error page
385
398
  echoes back). Pass `onError(res)` to shape it yourself, e.g. include a
386
- redacted version of the body. `fetch` is overridable — testing, request
399
+ redacted version of the body. Redirects are **not followed** by default
400
+ (`redirect: "manual"`; `"error"` and `"follow"` are available): `fetch`
401
+ forwards custom headers such as `X-Api-Key`, and a POST body on a 307/308,
402
+ to whatever `Location` the upstream names, and would let an open redirect
403
+ reach internal addresses. `fetch` is overridable — testing, request
387
404
  signing, a custom agent.
388
405
  - **`timeoutMs`** (default `30000`) aborts the upstream call past that many
389
406
  ms — an upstream that never responds otherwise hangs the tool call (and
package/docs/reference.md CHANGED
@@ -154,9 +154,10 @@ A single-scope server has no such ambiguity and is unaffected.
154
154
  - `createMcpApp`, `createMcpHandler`, `createToolRegistry`
155
155
  - `consoleAudit` — convenience unified `audit` sink that logs to the console (pass any function for metrics / DB / APM)
156
156
  - `UnifiedAuditEntry` — `{ source: "mcp" } & AuditEntry` \| `{ source: "oauth" } & OAuthAuditEntry`
157
- - `defineTool`, `apiTool` — typed, validated tool authoring on top of `ToolDef`. `apiTool`'s `timeoutMs` (default 30000, or `false` to disable) and `maxResponseBytes` (default 1 MiB) bound the upstream call; either surfaces as `isError: true`, not a thrown exception
157
+ - `defineTool`, `apiTool` — typed, validated tool authoring on top of `ToolDef`. `apiTool`'s `timeoutMs` (default 30000, or `false` to disable) and `maxResponseBytes` (default 1 MiB) bound the upstream call; either surfaces as `isError: true`, not a thrown exception. `redirect` (`"manual"` by default, `"error"`, or `"follow"`) controls upstream redirects — see the guide before enabling `"follow"`. An abort on the `Request` your `request` callback returns surfaces as `Request was cancelled`
158
158
  - `CLIENT_PROFILES`, `DEFAULT_CLIENTS`, `authMethodsFor`, `redirectUrisFor`, `preRegisteredClients`, `hasDynamicClient`
159
159
  - `parseBearer`, `wwwAuthenticateHeader`, `rejectQueryToken`
160
+ - `signedTokenAuth` — one-secret convenience `McpAppAuth` (HMAC access tokens via HKDF; no refresh/revoke). Type `SignedTokenAuthOptions`
160
161
  - `rpcResult`, `rpcError`, JSON-RPC error constants
161
162
  - `PROTOCOL_VERSIONS`, `DEFAULT_PROTOCOL_VERSION`
162
163
  - `jsonResponse({ data, status?, headers?, cors? })` — type `JsonResponseInput` exported
@@ -204,6 +205,7 @@ Building blocks for hosts assembling their own transport or OAuth flow. Less sta
204
205
  <summary><code>mcp-trellis/node</code></summary>
205
206
 
206
207
  - `asNodeHandler`, `resolveOrigin`, `isAllowedOrigin`, `toWebRequest`, `sendWebResponse`, `readNodeBody`
208
+ - `asNodeHandler` options: `origin`, `trustProxy`, `allowedOrigins`, and `allowedHosts` — the exact `Host` values to answer to (**400** otherwise). On by default for a loopback `origin` (`127.0.0.1` / `localhost` / `[::1]` on its port), to stop DNS rebinding; set it yourself for any other fixed origin or to add a LAN address
207
209
  - Types: `NodeRequestLike`, `NodeResponseLike`, `ToWebRequestOptions`, `ResolveOriginOptions`, `AsNodeHandlerOptions`, `OriginAllowlistOptions`
208
210
  - `InvalidOriginError` — thrown by `resolveOrigin` for a Host/`X-Forwarded-*` header that can't safely become an origin (missing, carrying a path/query/fragment/credentials, malformed, or a non-`http(s)` scheme). `asNodeHandler` answers it with **400**, not the generic 500 it gives an actual unexpected failure
209
211
 
package/docs/security.md CHANGED
@@ -21,13 +21,13 @@ This is a **library** threat model, not a third-party audit badge.
21
21
 
22
22
  | Risk | Mitigation |
23
23
  |------|------------|
24
- | Attacker-driven cross-site authorization (a logged-in user visits an attacker page and is silently walked through `/authorize`, catching the code on an attacker-controlled `redirect_uri`) | **`/authorize` never issues a code directly.** A resolved session renders a consent interstitial (built in, hardened — CSP, `X-Frame-Options: DENY`, `Referrer-Policy: no-referrer`, escaped reflection — or your own via `consent.render`) and only issues a code after an explicit `POST ${oauthPath}/consent` approval. The approval ticket is sealed (AES-GCM, HKDF-derived per-type key, type bound in as AEAD data — see `sealed.ts`), single-use, 5-minute TTL, and re-checked against the resolved user at redemption; it can never be redeemed as an auth code or vice versa. PKCE alone does **not** defend against this — it proves possession of the verifier, not identity of a legitimate client — so this is a distinct control, not a restatement of PKCE. `preApprovedClientIds` skips the screen only for ids `clientStore` actually resolves; a DCR or self-invented id is never trusted to skip it. The built-in interstitial's CSP scopes `form-action` to `'self'` plus the validated `redirect_uri`'s own origin — Chromium enforces `form-action` across the redirect the approval POST produces, not just its immediate target, so a custom `consent.render` that sets its own CSP must do the same or the callback to the client's own origin gets silently blocked. |
24
+ | Attacker-driven cross-site authorization (a logged-in user visits an attacker page and is silently walked through `/authorize`, catching the code on an attacker-controlled `redirect_uri`) | **`/authorize` never issues a code directly.** A resolved session renders a consent interstitial (built in, hardened — CSP, `X-Frame-Options: DENY`, `Referrer-Policy: no-referrer`, escaped reflection — or your own via `consent.render`) and only issues a code after an explicit `POST ${oauthPath}/consent` approval. The approval ticket is sealed (AES-GCM, HKDF-derived per-type key, type bound in as AEAD data — see `sealed.ts`), single-use, 5-minute TTL, and re-checked against the resolved user at redemption; it can never be redeemed as an auth code or vice versa. PKCE alone does **not** defend against this — it proves possession of the verifier, not identity of a legitimate client — so this is a distinct control, not a restatement of PKCE. `preApprovedClientIds` skips the screen only for ids `clientStore` actually resolves; a DCR or self-invented id is never trusted to skip it. The built-in interstitial's CSP scopes `form-action` to `'self'` plus the validated `redirect_uri`'s own origin (its scheme, e.g. `myapp:`, for a custom-scheme URI whose origin is opaque) — Chromium enforces `form-action` across the redirect the approval POST produces, not just its immediate target, so a custom `consent.render` that sets its own CSP must do the same or the callback to the client's own origin gets silently blocked. |
25
25
  | Confused deputy (token usable at the wrong MCP) | RFC 8707: `resource` required on authorize + token; bound into the auth code; must equal this AS's canonical resource (`canonicalResource(origin, resourcePath)`). Passed into `mintAccessToken` / `refreshAccessToken` so you can embed `aud`. |
26
26
  | Refresh token replayed at another resource on the same AS | Library cannot inspect opaque refresh tokens — **`refreshAccessToken` MUST reject tokens not originally issued for the given `resource`** |
27
27
  | Audience not enforced at the RS | **`createMcpApp` enforces this** — `verifyToken` returns the token's `audience` and the library rejects any mismatch against `canonicalResource(origin, resourcePath)`. With bare `createMcpHandler`, the check is yours. |
28
28
  | Open redirect after login | Your login page must validate `next` is same-origin before redirecting |
29
29
  | Auth-code replay across instances | Pass a shared `codeStore`; in-memory `jti` (the auth code's single-use id) map is process-local only |
30
- | Origin spoofing on Node | Pass explicit `origin`, or `trustProxy: true` only behind a proxy that strips client `X-Forwarded-*`. Host-derived origin **requires** non-empty `allowedOrigins` on `asNodeHandler` (`["*"]` to opt out loudly) |
30
+ | Origin spoofing on Node | Pass explicit `origin`, or `trustProxy: true` only behind a proxy that strips client `X-Forwarded-*`. Host-derived origin **requires** non-empty `allowedOrigins` on `asNodeHandler` (`["*"]` to opt out loudly). A fixed `origin` ignores the `Host` header, so a DNS-rebinding page could otherwise drive the whole OAuth flow from a victim's browser: for a loopback `origin` `asNodeHandler` rejects any other `Host` by default, and `allowedHosts` does the same for any origin |
31
31
  | A Host/`X-Forwarded-*` header carrying a path, query, fragment, or embedded credentials (`evil.test/../trusted.test`, `user:pass@evil.test`) building an origin string that merely looks well-formed | `resolveOrigin` always normalizes and validates through `URL`, rejecting any of those (plus a non-`http(s)` `X-Forwarded-Proto`) with `InvalidOriginError` — `asNodeHandler` answers it **400**, not the generic 500 an actual unexpected failure gets |
32
32
  | Browser `Origin` on MCP (DNS rebinding / CSRF) | `allowedRequestOrigins` on `createMcpHandler` / `createMcpApp`. No `Origin` header → allowed (native clients). Omitted allowlist → **reject** any present `Origin`. `"*"` opts out. Distinct from Node Host `allowedOrigins` |
33
33
  | Scope escalation at authorize | Requested `scope` is validated against the advertised `scopes` and rejected with `invalid_scope`; the grant is bound into the auth code and handed to `mintAccessToken` |
@@ -38,12 +38,14 @@ This is a **library** threat model, not a third-party audit badge.
38
38
  | An unauthenticated caller enumerating which `client_id`s a locked-down server knows, via distinguishable `/token`/`/revoke` error text or response timing | Every client-auth failure at `/token` and `/revoke` collapses to one `invalid_client` / "client authentication failed" — previously up to five distinguishable messages named exactly why (wrong method, missing secret, misconfigured store, unknown id). Every rejection path in `firstClientAuthError` for a client_id that *is* known — wrong auth method, no secret presented, wrong secret, no `secretHash`/`verifySecret` on file — runs the same fixed-cost dummy or real comparison before returning, so response timing alone can't tell "known client, wrong method" apart from "known client, wrong secret." An unknown `client_id` on a locked-down server (`allowUnregisteredClients: false`) pays the identical fixed cost, so the crypto-verification cost itself can't distinguish "unknown" from "known, wrong secret" either. `/authorize`'s `unauthorized_client` is unchanged and stays descriptive — a human debugging a browser flow, not a bare API response, and a locked-down server already announces the same fact by omitting `registration_endpoint` |
39
39
  | Auth codes readable by anyone who sees one — browser history, `Referer`, proxy logs | Auth codes are sealed (AES-GCM, `sealed.ts`), not signed-only. Legacy v1 (HMAC-signed, not encrypted) codes are **rejected** since 1.0 — complete any rolling deploy before upgrading past 0.4.x |
40
40
  | An upstream API's error body leaking into the model or the audit log via `apiTool` | Default is status-only — the upstream response body is never forwarded on a non-2xx result, since it's a return value `onToolError` redaction never sees. Use `onError` for (a redacted version of) the body |
41
+ | `apiTool` as a credential-forwarding / SSRF primitive via upstream redirects | Redirects are not followed by default (`redirect: "manual"`): `fetch` would forward custom auth headers (`X-Api-Key`) and a 307 body to the `Location` and could fetch an internal address, returning it to the model. `"follow"` is an explicit opt-in for a fully trusted upstream |
42
+ | A tool call that amplifies a small request into a huge validation error | Validation stops after 20 violations and the message is capped at 2000 characters; audit copies of tool errors are capped at 1000 |
41
43
  | `/authorize` errors delivered as a bare JSON body a connector can't act on | RFC 6749 §4.1.2.1: once `redirect_uri` is validated, every remaining error (`unsupported_response_type`, PKCE, `invalid_scope`, `invalid_target`, …) redirects to the client's own callback with `?error=…&state=…` instead. `redirect_uri` validity itself, and an oversized `state`, stay direct JSON — the URI is what's in question in the first case, and reflecting an oversized `state` into its own error redirect would recreate the DoS the length cap exists to prevent |
42
44
  | DCR-issued `client_id` reused with a substituted `redirect_uri` | `/register` no longer returns a bare random id — the `client_id` **is** a sealed, self-verifying assertion of the exact `redirect_uris` it registered (`sealed.ts`, type `"client"`), with zero server-side storage. `/authorize` unseals it and binds the request to that list, the same protection a stored registration would give. This does not require the *identity* of the client to be verified — an attacker can still call `/register` and mint their own sealed id — so it stops URI substitution on a legitimate connector's id, not impersonation; the consent step above is what stops impersonation. `requireRegisteredClients` defaults to **true** since 1.0 (reject any `client_id` that isn't `clientStore`-resolved or self-sealed); set `false` only to accept invented public ids. Enforced at `/authorize`, and — since 1.2.0 — at `/token`'s `refresh_token` grant and at `/revoke` too, closing a gap where an invented `client_id` that never went through `/authorize` could still authenticate on either (the `authorization_code` grant was never actually exposed by this: a redeemable code's `client_id` is bound into its sealed payload at `/authorize` time, where the check already ran). Opt into CIMD with `cimd: true` for URL-shaped client_ids (off by default) |
43
- | CIMD fetch as SSRF proxy (attacker supplies `https://…` `client_id`) | **Off by default** (`cimd: true` to enable). When on: HTTPS-only fetch, private/loopback/link-local/NAT64/`::ffff:` blocked (incl. bracketed IPv6 **and full-form mapped encodings DNS may return**, e.g. `0:0:0:0:0:ffff:7f00:1`), DNS lookup required before fetch and re-checked after the body, unparseable IPv6 lookup results fail closed, 5 KB stream-capped body / 10 s per hop covering the body read / 3 same-origin redirects (cross-origin redirects rejected), public clients only, `http:` redirect_uris loopback-only (`127.0.0.1` / `[::1]`, never `localhost`). Also blocked: `192.0.0/24`, `198.18/15`, multicast/reserved `224/4`–`240/4`, IPv6 multicast `ff00::/8`, 6to4 `2002::/16`, IPv4-compatible `::a.b.c.d` (and full-form private embeds). `/token` and `/revoke` **re-resolve** CIMD — never accept URL-shaped ids by shape alone; pass `cimdCache` (strongly recommended) or every refresh/revoke re-fetches the document, and a failed re-resolve is audited as `cimd_resolve_failed`. **Ceiling:** TCP connect is not pinned to the looked-up addresses (zero-dep Web `fetch`); classic DNS rebinding between lookup and connect remains possible until a custom dispatcher pins the IP — do not treat CIMD as production-complete SSRF defense until then |
45
+ | CIMD fetch as SSRF proxy (attacker supplies `https://…` `client_id`) | **Off by default** (`cimd: true` to enable). When on: HTTPS-only fetch, private/loopback/link-local/NAT64/`::ffff:` blocked (incl. bracketed IPv6 **and full-form mapped encodings DNS may return**, e.g. `0:0:0:0:0:ffff:7f00:1`), DNS lookup required before fetch and re-checked after the body, unparseable IPv6 lookup results fail closed, 5 KB stream-capped body / 10 s per hop covering the body read / 3 same-origin redirects (cross-origin redirects rejected), public clients only, `http:` redirect_uris loopback-only (`127.0.0.1` / `[::1]`, never `localhost`). Also blocked: `192.0.0/24`, `198.18/15`, multicast/reserved `224/4`–`240/4`, IPv6 multicast `ff00::/8`, 6to4 `2002::/16`, IPv4-compatible `::a.b.c.d` (and full-form private embeds). `/token` and `/revoke` **re-resolve** CIMD — never accept URL-shaped ids by shape alone; pass `cimdCache` (strongly recommended) or every refresh/revoke re-fetches the document, and a failed resolve is audited as `cimd_resolve_failed` at `/authorize`, `/token` and `/revoke` with a reason code (`http_status: status 403 (authorize)`; never an address, header, body or redirect target) while the connector gets one generic `unauthorized_client`. **Ceiling:** TCP connect is not pinned to the looked-up addresses (zero-dep Web `fetch`); classic DNS rebinding between lookup and connect remains possible until a custom dispatcher pins the IP — do not treat CIMD as production-complete SSRF defense until then |
44
46
  | Naming only confidential clients doesn't actually block public ones | `clients: ["gemini"]` (or `allowUnregisteredClients: false`) is **enforced**: DCR unmounted, dropped from AS metadata, unresolved `client_id` rejected with `unauthorized_client` at `authorize` and `invalid_client` at `token` and `revoke` |
45
47
  | DCR / token-endpoint abuse | Edge or reverse-proxy rate limiting on `/register` and `/token`, **plus** a library-enforced request body cap (see "Also designed in") — the library does not rate-limit by request count, but an unbounded body was previously a memory-exhaustion vector on its own regardless of rate |
46
- | A weak or copy-pasted `codeSecret` (it can forge an auth code — or, since 0.3.0, a consent ticket or DCR client assertion — for any userId/scope/resource) | Validated at the point of use: a string or array form throws at `createOAuthRouter` construction (every array entry checked, and the array itself must be non-empty), a function form on every call. Minimum 32 characters per entry, and the exact literals this package's own docs and examples have published are denylisted by name, as is any secret containing the examples' `do-not-reuse` marker — copy-paste is the realistic failure mode. The examples themselves read `OAUTH_CODE_SECRET` / `ACCESS_TOKEN_SECRET` from the environment and ship no usable default, and a test fails if any `codeSecret` literal in `examples/`, `README.md` or `docs/` is one the library would accept. This only catches the obvious cases; a leaked or brute-forceable 32+ character secret is still a full bypass, same as before |
48
+ | A weak or copy-pasted `codeSecret` (it can forge an auth code — or, since 0.3.0, a consent ticket or DCR client assertion — for any userId/scope/resource) | Validated at the point of use: a string or array form throws at `createOAuthRouter` construction (every array entry checked, and the array itself must be non-empty), a function form on every call. Minimum 32 characters per entry, and the exact literals this package's own docs and examples have published are denylisted by name, as is any secret containing the examples' `do-not-reuse` marker — copy-paste is the realistic failure mode. The examples themselves read `MCP_SECRET` (or `OAUTH_CODE_SECRET` for the advanced multi-secret recipes) from the environment and ship no usable default, and a test fails if any `codeSecret` literal in `examples/`, `README.md` or `docs/` is one the library would accept. This only catches the obvious cases; a leaked or brute-forceable 32+ character secret is still a full bypass, same as before |
47
49
  | An operator unable to diagnose *why* a client was rejected, once `/token`/`/revoke` collapsed every failure to one generic `invalid_client` (see the enumeration-oracle row above) | `OAuthPorts.audit` (and `McpAppAuth.audit` on `createMcpApp`) — opt-in, sees the real reason (`"client requires client_secret_basic, got none"`, a rejected `codeSecret`'s actual message, …) that never reaches the caller. Same "never fails the request" guarantee as the MCP-side `audit` port |
48
50
  | A hanging `ports.audit` sink stalling every audited response indefinitely | `auditTimeoutMs` (default 1000ms) on both `McpHandlerOptions` and `OAuthRouterOptions` races the audit call against a timeout; "can't fail a request" already held, this adds "can't delay one" past a bound. A slow sink keeps running in the background and its own internal `catch` means it can never surface as an unhandled rejection later |
49
51
 
@@ -14,6 +14,7 @@
14
14
  | Wrong user's data | Data query ignores authenticated identity | Filter by verified principal and tenant; never trust a tool argument for ownership. |
15
15
  | Switching login does not switch connector user | Connector retains its previously issued token | Disconnect and reauthorize with the intended account. |
16
16
  | Initialization works but tools fail | Initialize is public | Test authenticated `tools/call`; initialize alone proves no auth flow. |
17
+ | `unauthorized_client: CIMD client_id could not be resolved` | The connector is told one generic error on purpose; the cause is in your audit sink | Read the `cimd_resolve_failed` entry: `reason` is `<code>[: detail] (authorize\|token/revoke)`. `http_status: status 403` = the document host refused the fetch; `auth_method_unsupported` = its document excludes `none`; `dns_failed (ENOTFOUND)` / `dns_unavailable` = no DNS from this runtime, pass `cimdLookup`; `blocked_address` = it resolves to a non-public address. Pass `cimdCache` so refresh does not re-fetch. |
17
18
  | Client requests an unsupported protocol | Package supports the revisions in `src/mcp/protocol.ts` | Record negotiation; do not claim a live pass from a successful HTTP status alone. |
18
19
 
19
20
  See [security responsibilities](security.md) before replacing production token verification with example code. Share sanitized errors in an issue, never access tokens or session cookies.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "mcp-trellis",
3
- "version": "2.1.0",
4
- "description": "Build secure remote MCP servers for Claude, ChatGPT and Gemini using your existing auth.",
3
+ "version": "3.0.0",
4
+ "description": "Add authenticated remote MCP connectors to existing TypeScript SaaS apps using your existing users and auth.",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "sideEffects": false,
@@ -27,7 +27,11 @@
27
27
  "authorization-server",
28
28
  "connector",
29
29
  "typescript",
30
- "mcp-server"
30
+ "mcp-server",
31
+ "saas",
32
+ "remote-mcp",
33
+ "byo-auth",
34
+ "ai-connector"
31
35
  ],
32
36
  "types": "./dist/server.d.ts",
33
37
  "exports": {
@@ -77,7 +81,7 @@
77
81
  "node": ">=20"
78
82
  },
79
83
  "devDependencies": {
80
- "@biomejs/biome": "2.5.11",
84
+ "@biomejs/biome": "2.5.14",
81
85
  "@types/node": "^26.4.1",
82
86
  "tsx": "^4.19.3",
83
87
  "typescript": "^5.8.2"