cedar-mcp-server 1.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 (215) hide show
  1. package/.editorconfig +12 -0
  2. package/.github/workflows/ci.yml +31 -0
  3. package/.github/workflows/release.yml +42 -0
  4. package/.nvmrc +1 -0
  5. package/CHANGELOG.md +241 -0
  6. package/CONTRIBUTING.md +83 -0
  7. package/LICENSE +182 -0
  8. package/README.md +1635 -0
  9. package/SECURITY.md +37 -0
  10. package/dist/http-server.d.ts +61 -0
  11. package/dist/http-server.d.ts.map +1 -0
  12. package/dist/http-server.js +194 -0
  13. package/dist/http-server.js.map +1 -0
  14. package/dist/index.d.ts +32 -0
  15. package/dist/index.d.ts.map +1 -0
  16. package/dist/index.js +270 -0
  17. package/dist/index.js.map +1 -0
  18. package/dist/parser/policy-ast.d.ts +49 -0
  19. package/dist/parser/policy-ast.d.ts.map +1 -0
  20. package/dist/parser/policy-ast.js +311 -0
  21. package/dist/parser/policy-ast.js.map +1 -0
  22. package/dist/prompts/index.d.ts +38 -0
  23. package/dist/prompts/index.d.ts.map +1 -0
  24. package/dist/prompts/index.js +172 -0
  25. package/dist/prompts/index.js.map +1 -0
  26. package/dist/resources/ref-resolver.d.ts +23 -0
  27. package/dist/resources/ref-resolver.d.ts.map +1 -0
  28. package/dist/resources/ref-resolver.js +128 -0
  29. package/dist/resources/ref-resolver.js.map +1 -0
  30. package/dist/resources/store-manager.d.ts +64 -0
  31. package/dist/resources/store-manager.d.ts.map +1 -0
  32. package/dist/resources/store-manager.js +221 -0
  33. package/dist/resources/store-manager.js.map +1 -0
  34. package/dist/server.d.ts +18 -0
  35. package/dist/server.d.ts.map +1 -0
  36. package/dist/server.js +539 -0
  37. package/dist/server.js.map +1 -0
  38. package/dist/tools/advise/avp-rules.d.ts +49 -0
  39. package/dist/tools/advise/avp-rules.d.ts.map +1 -0
  40. package/dist/tools/advise/avp-rules.js +59 -0
  41. package/dist/tools/advise/avp-rules.js.map +1 -0
  42. package/dist/tools/advise/cedar-patterns.d.ts +24 -0
  43. package/dist/tools/advise/cedar-patterns.d.ts.map +1 -0
  44. package/dist/tools/advise/cedar-patterns.js +57 -0
  45. package/dist/tools/advise/cedar-patterns.js.map +1 -0
  46. package/dist/tools/advise/context-builder.d.ts +28 -0
  47. package/dist/tools/advise/context-builder.d.ts.map +1 -0
  48. package/dist/tools/advise/context-builder.js +89 -0
  49. package/dist/tools/advise/context-builder.js.map +1 -0
  50. package/dist/tools/advise/gotchas.d.ts +15 -0
  51. package/dist/tools/advise/gotchas.d.ts.map +1 -0
  52. package/dist/tools/advise/gotchas.js +83 -0
  53. package/dist/tools/advise/gotchas.js.map +1 -0
  54. package/dist/tools/advise.d.ts +96 -0
  55. package/dist/tools/advise.d.ts.map +1 -0
  56. package/dist/tools/advise.js +258 -0
  57. package/dist/tools/advise.js.map +1 -0
  58. package/dist/tools/authorize-batch.d.ts +35 -0
  59. package/dist/tools/authorize-batch.d.ts.map +1 -0
  60. package/dist/tools/authorize-batch.js +262 -0
  61. package/dist/tools/authorize-batch.js.map +1 -0
  62. package/dist/tools/authorize.d.ts +115 -0
  63. package/dist/tools/authorize.d.ts.map +1 -0
  64. package/dist/tools/authorize.js +373 -0
  65. package/dist/tools/authorize.js.map +1 -0
  66. package/dist/tools/check-change.d.ts +19 -0
  67. package/dist/tools/check-change.d.ts.map +1 -0
  68. package/dist/tools/check-change.js +91 -0
  69. package/dist/tools/check-change.js.map +1 -0
  70. package/dist/tools/diff-schema.d.ts +103 -0
  71. package/dist/tools/diff-schema.d.ts.map +1 -0
  72. package/dist/tools/diff-schema.js +379 -0
  73. package/dist/tools/diff-schema.js.map +1 -0
  74. package/dist/tools/diff-stores.d.ts +45 -0
  75. package/dist/tools/diff-stores.d.ts.map +1 -0
  76. package/dist/tools/diff-stores.js +222 -0
  77. package/dist/tools/diff-stores.js.map +1 -0
  78. package/dist/tools/explain.d.ts +80 -0
  79. package/dist/tools/explain.d.ts.map +1 -0
  80. package/dist/tools/explain.js +187 -0
  81. package/dist/tools/explain.js.map +1 -0
  82. package/dist/tools/format.d.ts +11 -0
  83. package/dist/tools/format.d.ts.map +1 -0
  84. package/dist/tools/format.js +20 -0
  85. package/dist/tools/format.js.map +1 -0
  86. package/dist/tools/generate-sample.d.ts +28 -0
  87. package/dist/tools/generate-sample.d.ts.map +1 -0
  88. package/dist/tools/generate-sample.js +568 -0
  89. package/dist/tools/generate-sample.js.map +1 -0
  90. package/dist/tools/link-template.d.ts +17 -0
  91. package/dist/tools/link-template.d.ts.map +1 -0
  92. package/dist/tools/link-template.js +78 -0
  93. package/dist/tools/link-template.js.map +1 -0
  94. package/dist/tools/list-template-links.d.ts +16 -0
  95. package/dist/tools/list-template-links.d.ts.map +1 -0
  96. package/dist/tools/list-template-links.js +22 -0
  97. package/dist/tools/list-template-links.js.map +1 -0
  98. package/dist/tools/list-templates.d.ts +16 -0
  99. package/dist/tools/list-templates.d.ts.map +1 -0
  100. package/dist/tools/list-templates.js +36 -0
  101. package/dist/tools/list-templates.js.map +1 -0
  102. package/dist/tools/translate.d.ts +11 -0
  103. package/dist/tools/translate.d.ts.map +1 -0
  104. package/dist/tools/translate.js +53 -0
  105. package/dist/tools/translate.js.map +1 -0
  106. package/dist/tools/validate-entities.d.ts +19 -0
  107. package/dist/tools/validate-entities.d.ts.map +1 -0
  108. package/dist/tools/validate-entities.js +88 -0
  109. package/dist/tools/validate-entities.js.map +1 -0
  110. package/dist/tools/validate-schema.d.ts +22 -0
  111. package/dist/tools/validate-schema.d.ts.map +1 -0
  112. package/dist/tools/validate-schema.js +89 -0
  113. package/dist/tools/validate-schema.js.map +1 -0
  114. package/dist/tools/validate-template.d.ts +18 -0
  115. package/dist/tools/validate-template.d.ts.map +1 -0
  116. package/dist/tools/validate-template.js +59 -0
  117. package/dist/tools/validate-template.js.map +1 -0
  118. package/dist/tools/validate.d.ts +90 -0
  119. package/dist/tools/validate.d.ts.map +1 -0
  120. package/dist/tools/validate.js +351 -0
  121. package/dist/tools/validate.js.map +1 -0
  122. package/dist/utils/format-detector.d.ts +49 -0
  123. package/dist/utils/format-detector.d.ts.map +1 -0
  124. package/dist/utils/format-detector.js +298 -0
  125. package/dist/utils/format-detector.js.map +1 -0
  126. package/examples/README.md +36 -0
  127. package/examples/abac-multi-tenant/README.md +150 -0
  128. package/examples/abac-multi-tenant/entities/users-and-docs.json +33 -0
  129. package/examples/abac-multi-tenant/policies/member-read-internal.cedar +9 -0
  130. package/examples/abac-multi-tenant/policies/owner-full-access.cedar +9 -0
  131. package/examples/abac-multi-tenant/policies/premium-share-guard.cedar +9 -0
  132. package/examples/abac-multi-tenant/policies/private-doc-guard.cedar +13 -0
  133. package/examples/abac-multi-tenant/run.ts +92 -0
  134. package/examples/abac-multi-tenant/schema.json +60 -0
  135. package/examples/api-gateway-path-routing/README.md +154 -0
  136. package/examples/api-gateway-path-routing/entities/users-and-roles.json +20 -0
  137. package/examples/api-gateway-path-routing/policies/admin-full-access.cedar +6 -0
  138. package/examples/api-gateway-path-routing/policies/developer-projects.cedar +14 -0
  139. package/examples/api-gateway-path-routing/policies/viewer-readonly.cedar +10 -0
  140. package/examples/api-gateway-path-routing/run.ts +108 -0
  141. package/examples/api-gateway-path-routing/schema.json +54 -0
  142. package/examples/rbac-document-management/README.md +167 -0
  143. package/examples/rbac-document-management/entities/users-and-docs.json +43 -0
  144. package/examples/rbac-document-management/policies/admin.cedar +6 -0
  145. package/examples/rbac-document-management/policies/editor.cedar +6 -0
  146. package/examples/rbac-document-management/policies/top-secret-forbid.cedar +13 -0
  147. package/examples/rbac-document-management/policies/viewer.cedar +6 -0
  148. package/examples/rbac-document-management/run.ts +87 -0
  149. package/examples/rbac-document-management/schema.json +57 -0
  150. package/package.json +50 -0
  151. package/src/http-server.ts +239 -0
  152. package/src/index.ts +294 -0
  153. package/src/parser/policy-ast.ts +345 -0
  154. package/src/prompts/README.md +3 -0
  155. package/src/prompts/index.ts +217 -0
  156. package/src/resources/ref-resolver.ts +134 -0
  157. package/src/resources/store-manager.ts +248 -0
  158. package/src/server.ts +711 -0
  159. package/src/tools/advise/avp-rules.ts +70 -0
  160. package/src/tools/advise/cedar-patterns.ts +73 -0
  161. package/src/tools/advise/context-builder.ts +109 -0
  162. package/src/tools/advise/gotchas.ts +92 -0
  163. package/src/tools/advise.ts +366 -0
  164. package/src/tools/authorize-batch.ts +345 -0
  165. package/src/tools/authorize.ts +464 -0
  166. package/src/tools/check-change.ts +119 -0
  167. package/src/tools/diff-schema.ts +510 -0
  168. package/src/tools/diff-stores.ts +298 -0
  169. package/src/tools/explain.ts +278 -0
  170. package/src/tools/format.ts +33 -0
  171. package/src/tools/generate-sample.ts +665 -0
  172. package/src/tools/link-template.ts +109 -0
  173. package/src/tools/list-template-links.ts +41 -0
  174. package/src/tools/list-templates.ts +55 -0
  175. package/src/tools/translate.ts +66 -0
  176. package/src/tools/validate-entities.ts +125 -0
  177. package/src/tools/validate-schema.ts +128 -0
  178. package/src/tools/validate-template.ts +72 -0
  179. package/src/tools/validate.ts +459 -0
  180. package/src/utils/format-detector.ts +356 -0
  181. package/test/fixtures/docmgmt.ts +121 -0
  182. package/test/fixtures/multitenant.ts +163 -0
  183. package/test/index.test.ts +96 -0
  184. package/test/integration/e2e/behavior.test.ts +359 -0
  185. package/test/integration/e2e/edge-cases.test.ts +365 -0
  186. package/test/integration/e2e/failure-modes.test.ts +266 -0
  187. package/test/integration/e2e/protocol.test.ts +252 -0
  188. package/test/integration/http-smoke.test.ts +588 -0
  189. package/test/integration/smoke.test.ts +475 -0
  190. package/test/prompts/prompts.test.ts +173 -0
  191. package/test/property/properties.test.ts +234 -0
  192. package/test/resources/ref-resolver.test.ts +186 -0
  193. package/test/resources/store-manager.test.ts +344 -0
  194. package/test/setup.test.ts +7 -0
  195. package/test/tools/advise/avp-rules.test.ts +76 -0
  196. package/test/tools/advise.test.ts +339 -0
  197. package/test/tools/authorize-batch.test.ts +459 -0
  198. package/test/tools/authorize.test.ts +682 -0
  199. package/test/tools/check-change.test.ts +104 -0
  200. package/test/tools/cross-fixture.test.ts +170 -0
  201. package/test/tools/diff-schema.test.ts +355 -0
  202. package/test/tools/diff-stores.test.ts +291 -0
  203. package/test/tools/explain.test.ts +221 -0
  204. package/test/tools/format.test.ts +33 -0
  205. package/test/tools/generate-sample.test.ts +480 -0
  206. package/test/tools/link-template.test.ts +90 -0
  207. package/test/tools/list-templates.test.ts +151 -0
  208. package/test/tools/translate.test.ts +89 -0
  209. package/test/tools/validate-entities.test.ts +178 -0
  210. package/test/tools/validate-schema.test.ts +86 -0
  211. package/test/tools/validate-template.test.ts +89 -0
  212. package/test/tools/validate.test.ts +331 -0
  213. package/test/utils/format-detector.test.ts +518 -0
  214. package/tsconfig.json +17 -0
  215. package/vitest.config.ts +13 -0
package/SECURITY.md ADDED
@@ -0,0 +1,37 @@
1
+ # Security
2
+
3
+ ## Trust boundary
4
+
5
+ `cedar-mcp-server` is a local MCP server. It runs as a child process on the same machine as the MCP client (Claude Code, Claude Desktop, Cursor, etc.). The trust model is: the client and its workspace are trusted. The server does not authenticate callers, enforce rate limits, or apply any network-level access controls.
6
+
7
+ Do not expose this server over a network without an authentication layer in front of it. The stdio transport (`npx cedar-mcp-server`) is local-only by design.
8
+
9
+ ## What the server does
10
+
11
+ - Evaluates Cedar policies and authorization requests in-process using `@cedar-policy/cedar-wasm`. No network calls.
12
+ - Reads `.cedar` and `.cedarschema` / `.json` files from directories configured as MCP Roots. Reads only; never writes to the filesystem.
13
+ - Calls the MCP client's `sampling/createMessage` capability for `cedar_advise`. The client decides whether to fulfill the sampling request and which model to use.
14
+
15
+ ## What the server does NOT do
16
+
17
+ - Makes no calls to AWS APIs or Amazon Verified Permissions.
18
+ - Makes no outbound network requests of any kind.
19
+ - Does not write, modify, or delete files on disk.
20
+ - Does not execute arbitrary code from policy inputs.
21
+ - Does not store policy text, entity data, or authorization results anywhere outside the MCP conversation.
22
+
23
+ ## Input validation
24
+
25
+ **Policy IDs.** Policy files are loaded from the `policies/` subdirectory of each configured root. Policy IDs are derived from filenames and validated against `^[a-zA-Z0-9_-]+$` before use. IDs containing path separators or `..` sequences are rejected. This prevents directory traversal attacks on the policy store.
26
+
27
+ **Root URIs.** Only `file://` URIs are accepted as MCP Roots. Non-`file://` URIs (e.g. `http://`) are rejected at load time.
28
+
29
+ **Policy and schema text.** Cedar policies and schemas passed as inline text or read from disk are evaluated inside the `@cedar-policy/cedar-wasm` WASM sandbox. They cannot execute arbitrary code. Malformed input returns a structured error, not a process crash.
30
+
31
+ **Entity JSON.** Entity data is parsed and passed to the Cedar evaluator. It is not executed. Excessively large entity payloads may cause slow evaluation but do not create a code execution path.
32
+
33
+ ## Reporting a vulnerability
34
+
35
+ If you find a security issue in this project, open a [GitHub Issue](https://github.com/Pigius/cedar-mcp-server/issues) with the label `security`. For issues you prefer not to disclose publicly, email the maintainer directly (contact via the GitHub profile).
36
+
37
+ Please include: a description of the issue, steps to reproduce, and the potential impact.
@@ -0,0 +1,61 @@
1
+ import { type Server as HttpServer } from "node:http";
2
+ export interface HttpServerOptions {
3
+ port: number;
4
+ host?: string;
5
+ roots?: Array<{
6
+ name: string;
7
+ path: string;
8
+ }>;
9
+ /** Max concurrent HTTP sessions. New sessions over the cap receive 503.
10
+ * Default: 100. Override via env CEDAR_MAX_HTTP_SESSIONS or this option. */
11
+ maxSessions?: number;
12
+ /** Idle session TTL in milliseconds. A session unused for longer is evicted
13
+ * by the reaper. Default: 30 minutes. Override via env
14
+ * CEDAR_HTTP_SESSION_IDLE_TTL_MS or this option. */
15
+ sessionIdleTtlMs?: number;
16
+ /** How often the reaper scans for stale sessions. Default: 60 seconds.
17
+ * Mostly relevant for tests; production deploys can leave the default. */
18
+ reaperIntervalMs?: number;
19
+ }
20
+ export interface RunningHttpServer {
21
+ httpServer: HttpServer;
22
+ port: number;
23
+ host: string;
24
+ close(): Promise<void>;
25
+ }
26
+ /**
27
+ * Boot cedar-mcp-server in Streamable HTTP mode.
28
+ *
29
+ * Per-session model: each MCP session gets its own McpServer + transport pair.
30
+ * The Streamable HTTP spec mandates this because each session has independent
31
+ * protocol state (initialized handshake, message history, capabilities).
32
+ *
33
+ * Shared across all sessions: the storeManager singleton. The deployment model
34
+ * is "one server per policy-store set, many team clients all seeing the same
35
+ * roots." Roots are deployer-configured via CLI flags at startup; client
36
+ * listRoots() is NOT called in HTTP mode. For per-tenant isolation, deploy
37
+ * multiple processes.
38
+ *
39
+ * Resource management:
40
+ * - Max-sessions cap: new sessions over the limit receive HTTP 503. This is
41
+ * backpressure, not eviction — existing sessions are not interrupted to
42
+ * make room. Default 100; override via maxSessions option or
43
+ * CEDAR_MAX_HTTP_SESSIONS env var.
44
+ * - Idle TTL: a reaper scans periodically and evicts sessions whose last
45
+ * observed request exceeds the TTL. Default 30 min idle / 60s scan;
46
+ * override via sessionIdleTtlMs / reaperIntervalMs options or env vars.
47
+ * This catches the case where transport.onclose doesn't fire (e.g.,
48
+ * network partition, TCP RST) and prevents the sessions map from leaking.
49
+ *
50
+ * Session lifecycle:
51
+ * 1. Client POSTs to /mcp without Mcp-Session-Id → server creates a new
52
+ * session (transport + server pair), runs initialize, returns the
53
+ * session ID in the response header. New sessions over maxSessions are
54
+ * rejected with HTTP 503.
55
+ * 2. Subsequent requests with that Mcp-Session-Id route to the same pair
56
+ * and refresh its lastActiveAt timestamp.
57
+ * 3. transport.onclose fires on graceful disconnect → session removed.
58
+ * 4. Reaper sweeps idle sessions out periodically as a backstop.
59
+ */
60
+ export declare function startHttpServer(options: HttpServerOptions): Promise<RunningHttpServer>;
61
+ //# sourceMappingURL=http-server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http-server.d.ts","sourceRoot":"","sources":["../src/http-server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoC,KAAK,MAAM,IAAI,UAAU,EAAE,MAAM,WAAW,CAAC;AAQxF,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC9C;iFAC6E;IAC7E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;yDAEqD;IACrD,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;+EAC2E;IAC3E,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,iBAAiB;IAChC,UAAU,EAAE,UAAU,CAAC;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAcD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,wBAAsB,eAAe,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAiK5F"}
@@ -0,0 +1,194 @@
1
+ import { createServer as createHttpServer } from "node:http";
2
+ import { randomUUID } from "node:crypto";
3
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
4
+ import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js";
5
+ import express from "express";
6
+ import { createServer } from "./server.js";
7
+ import { storeManager } from "./resources/store-manager.js";
8
+ const DEFAULT_MAX_SESSIONS = 100;
9
+ const DEFAULT_SESSION_IDLE_TTL_MS = 30 * 60 * 1000; // 30 min
10
+ const DEFAULT_REAPER_INTERVAL_MS = 60 * 1000; // 1 min
11
+ /**
12
+ * Boot cedar-mcp-server in Streamable HTTP mode.
13
+ *
14
+ * Per-session model: each MCP session gets its own McpServer + transport pair.
15
+ * The Streamable HTTP spec mandates this because each session has independent
16
+ * protocol state (initialized handshake, message history, capabilities).
17
+ *
18
+ * Shared across all sessions: the storeManager singleton. The deployment model
19
+ * is "one server per policy-store set, many team clients all seeing the same
20
+ * roots." Roots are deployer-configured via CLI flags at startup; client
21
+ * listRoots() is NOT called in HTTP mode. For per-tenant isolation, deploy
22
+ * multiple processes.
23
+ *
24
+ * Resource management:
25
+ * - Max-sessions cap: new sessions over the limit receive HTTP 503. This is
26
+ * backpressure, not eviction — existing sessions are not interrupted to
27
+ * make room. Default 100; override via maxSessions option or
28
+ * CEDAR_MAX_HTTP_SESSIONS env var.
29
+ * - Idle TTL: a reaper scans periodically and evicts sessions whose last
30
+ * observed request exceeds the TTL. Default 30 min idle / 60s scan;
31
+ * override via sessionIdleTtlMs / reaperIntervalMs options or env vars.
32
+ * This catches the case where transport.onclose doesn't fire (e.g.,
33
+ * network partition, TCP RST) and prevents the sessions map from leaking.
34
+ *
35
+ * Session lifecycle:
36
+ * 1. Client POSTs to /mcp without Mcp-Session-Id → server creates a new
37
+ * session (transport + server pair), runs initialize, returns the
38
+ * session ID in the response header. New sessions over maxSessions are
39
+ * rejected with HTTP 503.
40
+ * 2. Subsequent requests with that Mcp-Session-Id route to the same pair
41
+ * and refresh its lastActiveAt timestamp.
42
+ * 3. transport.onclose fires on graceful disconnect → session removed.
43
+ * 4. Reaper sweeps idle sessions out periodically as a backstop.
44
+ */
45
+ export async function startHttpServer(options) {
46
+ const host = options.host ?? "127.0.0.1";
47
+ const maxSessions = options.maxSessions
48
+ ?? (Number(process.env.CEDAR_MAX_HTTP_SESSIONS) || DEFAULT_MAX_SESSIONS);
49
+ const sessionIdleTtlMs = options.sessionIdleTtlMs
50
+ ?? (Number(process.env.CEDAR_HTTP_SESSION_IDLE_TTL_MS) || DEFAULT_SESSION_IDLE_TTL_MS);
51
+ const reaperIntervalMs = options.reaperIntervalMs ?? DEFAULT_REAPER_INTERVAL_MS;
52
+ // Load deployer-configured roots before the server starts accepting traffic.
53
+ // Shared by all sessions via the storeManager singleton.
54
+ if (options.roots && options.roots.length > 0) {
55
+ storeManager.loadFromRoots(options.roots.map((r) => ({ uri: `file://${r.path}`, name: r.name })));
56
+ }
57
+ const sessions = new Map();
58
+ async function createSession() {
59
+ const transport = new StreamableHTTPServerTransport({
60
+ sessionIdGenerator: () => randomUUID(),
61
+ });
62
+ const server = createServer();
63
+ await server.connect(transport);
64
+ transport.onclose = () => {
65
+ // Only drop the session-map entry here. Do NOT call server.close():
66
+ // server.close() triggers transport close, which fires this handler
67
+ // again. The McpServer becomes unreferenced and gets GC'd. The shared
68
+ // WASM module lives at process scope, not per-session.
69
+ const sid = transport.sessionId;
70
+ if (sid && sessions.has(sid)) {
71
+ sessions.delete(sid);
72
+ }
73
+ };
74
+ return { transport, server, lastActiveAt: Date.now() };
75
+ }
76
+ // Reaper: evict sessions whose lastActiveAt is older than the idle TTL.
77
+ // Collect-then-delete pattern avoids mid-iteration map mutation when the
78
+ // transport.close() callback re-enters the sessions map.
79
+ const reaper = setInterval(() => {
80
+ const cutoff = Date.now() - sessionIdleTtlMs;
81
+ const toEvict = [];
82
+ for (const [sid, sess] of sessions.entries()) {
83
+ if (sess.lastActiveAt < cutoff)
84
+ toEvict.push(sid);
85
+ }
86
+ for (const sid of toEvict) {
87
+ const sess = sessions.get(sid);
88
+ if (sess) {
89
+ sessions.delete(sid);
90
+ void sess.transport.close().catch(() => { });
91
+ }
92
+ }
93
+ }, reaperIntervalMs);
94
+ // Don't keep the Node event loop alive just for the reaper — let the
95
+ // process exit cleanly when the HTTP server closes.
96
+ reaper.unref();
97
+ const app = createMcpExpressApp({ host });
98
+ app.use(express.json({ limit: "10mb" }));
99
+ app.post("/mcp", async (req, res) => {
100
+ try {
101
+ const sessionIdHeader = req.headers["mcp-session-id"];
102
+ const sessionId = Array.isArray(sessionIdHeader) ? sessionIdHeader[0] : sessionIdHeader;
103
+ let session;
104
+ if (sessionId && sessions.has(sessionId)) {
105
+ session = sessions.get(sessionId);
106
+ if (session)
107
+ session.lastActiveAt = Date.now();
108
+ }
109
+ else {
110
+ // New session — apply backpressure if at cap. We do this BEFORE
111
+ // creating the transport/server pair to avoid leaking resources
112
+ // on rejection.
113
+ if (sessions.size >= maxSessions) {
114
+ res.status(503).json({
115
+ error: "Too many concurrent MCP sessions",
116
+ message: `Server is at the max-session limit of ${maxSessions}. Try again later, or run multiple processes for higher capacity.`,
117
+ active_sessions: sessions.size,
118
+ max_sessions: maxSessions,
119
+ });
120
+ return;
121
+ }
122
+ session = await createSession();
123
+ }
124
+ if (!session) {
125
+ res.status(400).json({ error: "Could not establish MCP session" });
126
+ return;
127
+ }
128
+ await session.transport.handleRequest(req, res, req.body);
129
+ // After the initialize request completes the transport will have set
130
+ // its sessionId. Register the session under that ID so subsequent
131
+ // requests find it.
132
+ const sidAfter = session.transport.sessionId;
133
+ if (sidAfter && !sessions.has(sidAfter)) {
134
+ sessions.set(sidAfter, session);
135
+ }
136
+ }
137
+ catch (e) {
138
+ if (!res.headersSent) {
139
+ res.status(500).json({
140
+ error: "Internal MCP transport error",
141
+ message: e instanceof Error ? e.message : String(e),
142
+ });
143
+ }
144
+ }
145
+ });
146
+ app.get("/health", (_req, res) => {
147
+ res.json({
148
+ status: "ok",
149
+ transport: "streamable-http",
150
+ mode: "stateful",
151
+ active_sessions: sessions.size,
152
+ max_sessions: maxSessions,
153
+ session_idle_ttl_ms: sessionIdleTtlMs,
154
+ });
155
+ });
156
+ const httpServer = createHttpServer(app);
157
+ await new Promise((resolve, reject) => {
158
+ httpServer.once("error", reject);
159
+ httpServer.listen(options.port, host, () => {
160
+ httpServer.off("error", reject);
161
+ resolve();
162
+ });
163
+ });
164
+ // eslint-disable-next-line no-console
165
+ console.error(`[cedar-mcp-server] Streamable HTTP listening on http://${host}:${options.port}/mcp (max_sessions=${maxSessions}, idle_ttl=${Math.round(sessionIdleTtlMs / 1000)}s)`);
166
+ if (options.roots && options.roots.length > 0) {
167
+ // eslint-disable-next-line no-console
168
+ console.error(`[cedar-mcp-server] Loaded ${options.roots.length} root(s): ${options.roots.map((r) => r.name).join(", ")}`);
169
+ }
170
+ else {
171
+ // eslint-disable-next-line no-console
172
+ console.error("[cedar-mcp-server] WARNING: no --root flags supplied; tools that depend on a configured store will error.");
173
+ }
174
+ return {
175
+ httpServer,
176
+ port: options.port,
177
+ host,
178
+ async close() {
179
+ clearInterval(reaper);
180
+ // Close all sessions first so each McpServer cleans up its WASM state
181
+ for (const session of sessions.values()) {
182
+ try {
183
+ await session.server.close();
184
+ }
185
+ catch { /* ignore */ }
186
+ }
187
+ sessions.clear();
188
+ await new Promise((resolve, reject) => {
189
+ httpServer.close((err) => (err ? reject(err) : resolve()));
190
+ });
191
+ },
192
+ };
193
+ }
194
+ //# sourceMappingURL=http-server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http-server.js","sourceRoot":"","sources":["../src/http-server.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,32 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Synchronously populate StoreManager with the cwd-fallback store, if the
4
+ * cwd looks like a Cedar workspace. Returns the loaded root descriptor when
5
+ * a store was loaded, or null otherwise.
6
+ *
7
+ * Round 5 dogfood (Scenario E) found that emitting `notifications/resources/list_changed`
8
+ * AFTER an async cwd-fallback (kickoff-11 11a) was insufficient: Claude Code's
9
+ * `listMcpResources` does not honor `list_changed` for cache invalidation, so a
10
+ * client that snapshots `resources/list` once on the initialize response stays
11
+ * stuck on the empty pre-fallback snapshot regardless of any later notification.
12
+ *
13
+ * The fix is structural: populate StoreManager BEFORE the transport accepts
14
+ * any client requests. `process.cwd()` is available at startup; there is no
15
+ * need to wait for the transport. By the time the client can send any
16
+ * request, the store already exists.
17
+ *
18
+ * Security: rejects filesystem-root cwds (`/`) and any cwd whose basename is
19
+ * empty after normalization. Without this guard, the cwd-fallback would push
20
+ * an empty-path root into StoreManager, and the per-store path sandbox
21
+ * (`isPathAllowed`, which uses `startsWith(store.path)`) would return true
22
+ * for every filesystem path. StoreManager.loadFromRoots also refuses
23
+ * empty-path roots as a second layer of defense, but rejecting here keeps
24
+ * the cwd-fallback's intent narrow (workspace-shaped cwds only).
25
+ *
26
+ * Exported so unit tests can exercise it without spawning a stdio process.
27
+ */
28
+ export declare function populateCwdFallback(cwd: string): {
29
+ uri: string;
30
+ name: string;
31
+ } | null;
32
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAyBA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAMrF"}
package/dist/index.js ADDED
@@ -0,0 +1,270 @@
1
+ #!/usr/bin/env node
2
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
+ import { RootsListChangedNotificationSchema } from "@modelcontextprotocol/sdk/types.js";
4
+ import { existsSync } from "node:fs";
5
+ import { join, basename } from "node:path";
6
+ import { createServer } from "./server.js";
7
+ import { storeManager } from "./resources/store-manager.js";
8
+ import { startHttpServer } from "./http-server.js";
9
+ /**
10
+ * A directory "looks like a Cedar workspace" if it has at least one of:
11
+ * - schema.cedarschema (preferred)
12
+ * - schema.json (fallback)
13
+ * - policies/ (per-file policies layout)
14
+ *
15
+ * This is the same convention StoreManager uses to read a loaded root.
16
+ */
17
+ function looksLikeCedarWorkspace(path) {
18
+ return (existsSync(join(path, "schema.cedarschema")) ||
19
+ existsSync(join(path, "schema.json")) ||
20
+ existsSync(join(path, "policies")));
21
+ }
22
+ /**
23
+ * Synchronously populate StoreManager with the cwd-fallback store, if the
24
+ * cwd looks like a Cedar workspace. Returns the loaded root descriptor when
25
+ * a store was loaded, or null otherwise.
26
+ *
27
+ * Round 5 dogfood (Scenario E) found that emitting `notifications/resources/list_changed`
28
+ * AFTER an async cwd-fallback (kickoff-11 11a) was insufficient: Claude Code's
29
+ * `listMcpResources` does not honor `list_changed` for cache invalidation, so a
30
+ * client that snapshots `resources/list` once on the initialize response stays
31
+ * stuck on the empty pre-fallback snapshot regardless of any later notification.
32
+ *
33
+ * The fix is structural: populate StoreManager BEFORE the transport accepts
34
+ * any client requests. `process.cwd()` is available at startup; there is no
35
+ * need to wait for the transport. By the time the client can send any
36
+ * request, the store already exists.
37
+ *
38
+ * Security: rejects filesystem-root cwds (`/`) and any cwd whose basename is
39
+ * empty after normalization. Without this guard, the cwd-fallback would push
40
+ * an empty-path root into StoreManager, and the per-store path sandbox
41
+ * (`isPathAllowed`, which uses `startsWith(store.path)`) would return true
42
+ * for every filesystem path. StoreManager.loadFromRoots also refuses
43
+ * empty-path roots as a second layer of defense, but rejecting here keeps
44
+ * the cwd-fallback's intent narrow (workspace-shaped cwds only).
45
+ *
46
+ * Exported so unit tests can exercise it without spawning a stdio process.
47
+ */
48
+ export function populateCwdFallback(cwd) {
49
+ if (cwd === "/" || basename(cwd).length === 0)
50
+ return null;
51
+ if (!looksLikeCedarWorkspace(cwd))
52
+ return null;
53
+ const cwdRoot = { uri: `file://${cwd}`, name: basename(cwd) };
54
+ storeManager.loadFromRoots([cwdRoot]);
55
+ return cwdRoot;
56
+ }
57
+ function parseArgs(argv) {
58
+ const args = argv.slice(2);
59
+ const out = { mode: "stdio", roots: [] };
60
+ for (let i = 0; i < args.length; i++) {
61
+ const a = args[i];
62
+ if (a === "--help" || a === "-h") {
63
+ return { mode: "help", roots: [] };
64
+ }
65
+ else if (a === "--http") {
66
+ const portArg = args[i + 1];
67
+ if (!portArg || portArg.startsWith("--")) {
68
+ return { mode: "help", roots: [], error: "--http requires a port number (e.g. --http 3000)" };
69
+ }
70
+ const port = Number(portArg);
71
+ if (!Number.isInteger(port) || port < 1 || port > 65535) {
72
+ return { mode: "help", roots: [], error: `--http port must be an integer between 1 and 65535 (got '${portArg}')` };
73
+ }
74
+ out.mode = "http";
75
+ out.port = port;
76
+ i++;
77
+ }
78
+ else if (a === "--host") {
79
+ const hostArg = args[i + 1];
80
+ if (!hostArg || hostArg.startsWith("--")) {
81
+ return { mode: "help", roots: [], error: "--host requires a hostname (e.g. --host 0.0.0.0)" };
82
+ }
83
+ out.host = hostArg;
84
+ i++;
85
+ }
86
+ else if (a === "--root") {
87
+ const rootArg = args[i + 1];
88
+ if (!rootArg || rootArg.startsWith("--")) {
89
+ return { mode: "help", roots: [], error: "--root requires a name=path value (e.g. --root production=/etc/cedar/prod)" };
90
+ }
91
+ const eq = rootArg.indexOf("=");
92
+ if (eq <= 0 || eq === rootArg.length - 1) {
93
+ return { mode: "help", roots: [], error: `--root must be name=path (got '${rootArg}')` };
94
+ }
95
+ const name = rootArg.slice(0, eq);
96
+ const path = rootArg.slice(eq + 1);
97
+ out.roots.push({ name, path });
98
+ i++;
99
+ }
100
+ else {
101
+ return { mode: "help", roots: [], error: `Unknown argument: ${a}` };
102
+ }
103
+ }
104
+ if (out.mode === "stdio" && out.roots.length > 0) {
105
+ return { mode: "help", roots: [], error: "--root flags are only used with --http mode; in stdio mode roots come from the MCP client" };
106
+ }
107
+ return out;
108
+ }
109
+ function printUsage(error) {
110
+ if (error) {
111
+ // eslint-disable-next-line no-console
112
+ console.error(`Error: ${error}\n`);
113
+ }
114
+ // eslint-disable-next-line no-console
115
+ console.error(`cedar-mcp-server — MCP server for Cedar policy language
116
+
117
+ Usage:
118
+ cedar-mcp-server Start in stdio mode (default; for npx/Claude Code)
119
+ cedar-mcp-server --http <port> [options] Start in Streamable HTTP mode for shared team deployment
120
+
121
+ HTTP options:
122
+ --http <port> Listen port (1-65535)
123
+ --host <host> Bind host (default: 127.0.0.1; use 0.0.0.0 for non-localhost; you handle auth via reverse proxy)
124
+ --root <name>=<path> Repeatable; deployer-configured policy store ("production=/etc/cedar/prod")
125
+
126
+ Notes:
127
+ - Stdio mode: roots are negotiated with the MCP client via listRoots(); --root flags are not allowed.
128
+ - HTTP mode: stateful Streamable HTTP transport with session IDs. ALL clients share the same roots and policy stores (deployment model: one server per policy-store set). For per-tenant isolation, run multiple processes.
129
+ - HTTP mode default-binds to localhost with DNS-rebinding protection. Non-localhost binding is on you to secure (reverse proxy + auth).
130
+
131
+ Examples:
132
+ cedar-mcp-server
133
+ cedar-mcp-server --http 3000 --root production=/etc/cedar/production --root staging=/etc/cedar/staging
134
+ cedar-mcp-server --http 3000 --host 0.0.0.0 --root prod=/etc/cedar/prod
135
+ `);
136
+ }
137
+ /**
138
+ * Reconcile StoreManager state with whatever the MCP client advertises via
139
+ * `listRoots()`. Called from `oninitialized` AND on every
140
+ * `notifications/roots/list_changed` from the client.
141
+ *
142
+ * Precedence rules:
143
+ * - Client-advertised roots REPLACE any sync-loaded cwd-fallback. A client
144
+ * that explicitly advertises roots is stating authoritative intent; the
145
+ * cwd-fallback was an "if you didn't tell me anything" default.
146
+ * - Client returns ZERO roots (or doesn't support listRoots): preserve the
147
+ * sync-loaded cwd-fallback. We do NOT call `loadFromRoots([])` here
148
+ * because StoreManager.loadFromRoots clears the store as its first step,
149
+ * which would wipe the cwd-fallback populated synchronously at startup.
150
+ *
151
+ * `sendResourceListChanged` always fires at the end: cache-aware clients use
152
+ * it to refetch when the store membership changes. Idempotent if nothing
153
+ * actually changed.
154
+ */
155
+ async function loadRootsStdio(server) {
156
+ let clientRoots = [];
157
+ let clientSupportsRoots = true;
158
+ try {
159
+ const result = await server.server.listRoots();
160
+ clientRoots = result.roots;
161
+ }
162
+ catch {
163
+ clientSupportsRoots = false;
164
+ }
165
+ // Reconcile StoreManager state from the current (clientRoots, cwd) tuple.
166
+ // Stateless re-derivation: each call to loadRootsStdio computes the right
167
+ // state from scratch rather than mutating the previous state. This matters
168
+ // when a client advertised roots earlier and then retracts them via
169
+ // `roots/list_changed`; without re-derivation the stale advertised roots
170
+ // would leak forward instead of falling back to cwd.
171
+ if (clientRoots.length > 0) {
172
+ storeManager.loadFromRoots(clientRoots);
173
+ console.error(`[cedar-mcp-server] Loaded ${clientRoots.length} root(s) from MCP client: ${clientRoots.map((r) => r.uri).join(", ")} (replaces any sync-loaded cwd-fallback).`);
174
+ }
175
+ else {
176
+ const fallback = populateCwdFallback(process.cwd());
177
+ if (fallback) {
178
+ console.error(`[cedar-mcp-server] MCP client advertised 0 roots; using cwd-fallback store "${fallback.name}" (re-derived).`);
179
+ }
180
+ else {
181
+ storeManager.loadFromRoots([]);
182
+ if (clientSupportsRoots) {
183
+ console.error("[cedar-mcp-server] MCP client returned 0 roots and cwd does not look like a Cedar workspace (no schema.cedarschema, schema.json, or policies/ dir). Cedar tools will require inline inputs.");
184
+ }
185
+ else {
186
+ console.error("[cedar-mcp-server] MCP client does not support roots/list and cwd does not look like a Cedar workspace. Cedar tools will require inline inputs.");
187
+ }
188
+ }
189
+ }
190
+ // kickoff-11 11a notification: cache-aware clients refetch on this. The
191
+ // synchronous cwd-fallback from runStdio means the FIRST resources/list
192
+ // already sees the populated store (this is the Round 5 fix), so this
193
+ // notification is most useful for the late-arriving-roots case (client
194
+ // sends roots/list_changed mid-session, swapping the store set). McpServer
195
+ // makes it a no-op when not connected, so it's safe to call from any path.
196
+ server.sendResourceListChanged();
197
+ }
198
+ async function runStdio() {
199
+ const server = createServer();
200
+ // Round 5 fix: populate StoreManager BEFORE the transport accepts any
201
+ // client requests. The previous shape (cwd-fallback inside `oninitialized`
202
+ // → kickoff-11 sendResourceListChanged) was contract-correct against the
203
+ // MCP spec but did not hold against Claude Code, which snapshots
204
+ // resources/list once on the initialize response and does not honor
205
+ // list_changed for the `listMcpResources` cache. Synchronous population
206
+ // before connect closes the window entirely.
207
+ //
208
+ // Edge case (kickoff-10 audit Probe C): if process.cwd() is the
209
+ // cedar-mcp-server repo itself, it currently has none of schema.cedarschema,
210
+ // schema.json, or policies/ — looksLikeCedarWorkspace returns false. If a
211
+ // future commit adds a top-level policies/ directory (for examples or
212
+ // similar) the fallback would self-load. Flag in CHANGELOG if that
213
+ // ever happens.
214
+ const loaded = populateCwdFallback(process.cwd());
215
+ if (loaded) {
216
+ console.error(`[cedar-mcp-server] Synchronously auto-loaded cwd as workspace store: "${loaded.name}" (${loaded.uri}). StoreManager populated before transport accepts requests.`);
217
+ }
218
+ const transport = new StdioServerTransport();
219
+ await server.connect(transport);
220
+ server.server.oninitialized = async () => {
221
+ await loadRootsStdio(server);
222
+ };
223
+ server.server.setNotificationHandler(RootsListChangedNotificationSchema, async () => {
224
+ await loadRootsStdio(server);
225
+ });
226
+ process.on("SIGINT", async () => {
227
+ await server.close();
228
+ process.exit(0);
229
+ });
230
+ process.on("SIGTERM", async () => {
231
+ await server.close();
232
+ process.exit(0);
233
+ });
234
+ }
235
+ async function runHttp(parsed) {
236
+ const running = await startHttpServer({
237
+ port: parsed.port,
238
+ host: parsed.host,
239
+ roots: parsed.roots,
240
+ });
241
+ const shutdown = async () => {
242
+ try {
243
+ await running.close();
244
+ }
245
+ finally {
246
+ process.exit(0);
247
+ }
248
+ };
249
+ process.on("SIGINT", shutdown);
250
+ process.on("SIGTERM", shutdown);
251
+ }
252
+ async function main() {
253
+ const parsed = parseArgs(process.argv);
254
+ if (parsed.mode === "help") {
255
+ printUsage(parsed.error);
256
+ process.exit(parsed.error ? 1 : 0);
257
+ }
258
+ if (parsed.mode === "http") {
259
+ await runHttp(parsed);
260
+ }
261
+ else {
262
+ await runStdio();
263
+ }
264
+ }
265
+ main().catch((err) => {
266
+ // eslint-disable-next-line no-console
267
+ console.error("Fatal error:", err);
268
+ process.exit(1);
269
+ });
270
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}