@imgly/codesign-mcp 0.1.1 → 0.1.2

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 (115) hide show
  1. package/dist/cli/stdio.d.ts.map +1 -1
  2. package/dist/cli/stdio.js +54 -21
  3. package/dist/cli/stdio.js.map +1 -1
  4. package/dist/engine/cesdk-adapter.d.ts +7 -1
  5. package/dist/engine/cesdk-adapter.d.ts.map +1 -1
  6. package/dist/engine/cesdk-adapter.js +7 -1
  7. package/dist/engine/cesdk-adapter.js.map +1 -1
  8. package/dist/http/server.d.ts.map +1 -1
  9. package/dist/http/server.js +0 -1
  10. package/dist/http/server.js.map +1 -1
  11. package/dist/server.d.ts +0 -2
  12. package/dist/server.d.ts.map +1 -1
  13. package/dist/server.js.map +1 -1
  14. package/dist/test/test/cli/redirect-console.test.d.ts +2 -0
  15. package/dist/test/test/cli/redirect-console.test.d.ts.map +1 -0
  16. package/dist/test/test/cli/redirect-console.test.js +47 -0
  17. package/dist/test/test/cli/redirect-console.test.js.map +1 -0
  18. package/dist/test/test/cli/resolve-license.test.d.ts +2 -0
  19. package/dist/test/test/cli/resolve-license.test.d.ts.map +1 -0
  20. package/dist/test/test/cli/resolve-license.test.js +20 -0
  21. package/dist/test/test/cli/resolve-license.test.js.map +1 -0
  22. package/dist/test/test/cli/stdio-license.test.d.ts +2 -0
  23. package/dist/test/test/cli/stdio-license.test.d.ts.map +1 -0
  24. package/dist/test/test/cli/stdio-license.test.js +19 -0
  25. package/dist/test/test/cli/stdio-license.test.js.map +1 -0
  26. package/dist/test/test/config.test.d.ts +2 -0
  27. package/dist/test/test/config.test.d.ts.map +1 -0
  28. package/dist/test/test/config.test.js +91 -0
  29. package/dist/test/test/config.test.js.map +1 -0
  30. package/dist/test/test/engine/asset-sources.test.d.ts +2 -0
  31. package/dist/test/test/engine/asset-sources.test.d.ts.map +1 -0
  32. package/dist/test/test/engine/asset-sources.test.js +100 -0
  33. package/dist/test/test/engine/asset-sources.test.js.map +1 -0
  34. package/dist/test/test/engine/mutex.test.d.ts +2 -0
  35. package/dist/test/test/engine/mutex.test.d.ts.map +1 -0
  36. package/dist/test/test/engine/mutex.test.js +63 -0
  37. package/dist/test/test/engine/mutex.test.js.map +1 -0
  38. package/dist/test/test/engine/session.test.js +15 -0
  39. package/dist/test/test/engine/session.test.js.map +1 -1
  40. package/dist/test/test/engine/unsplash-source.test.d.ts +2 -0
  41. package/dist/test/test/engine/unsplash-source.test.d.ts.map +1 -0
  42. package/dist/test/test/engine/unsplash-source.test.js +129 -0
  43. package/dist/test/test/engine/unsplash-source.test.js.map +1 -0
  44. package/dist/test/test/handbook-contract.test.js +8 -4
  45. package/dist/test/test/handbook-contract.test.js.map +1 -1
  46. package/dist/test/test/http/server.test.js +129 -0
  47. package/dist/test/test/http/server.test.js.map +1 -1
  48. package/dist/test/test/http/session-id-header.test.d.ts +2 -0
  49. package/dist/test/test/http/session-id-header.test.d.ts.map +1 -0
  50. package/dist/test/test/http/session-id-header.test.js +76 -0
  51. package/dist/test/test/http/session-id-header.test.js.map +1 -0
  52. package/dist/test/test/http/session-manager.test.js +14 -0
  53. package/dist/test/test/http/session-manager.test.js.map +1 -1
  54. package/dist/test/test/http/workspace-auth.test.d.ts +2 -0
  55. package/dist/test/test/http/workspace-auth.test.d.ts.map +1 -0
  56. package/dist/test/test/http/workspace-auth.test.js +41 -0
  57. package/dist/test/test/http/workspace-auth.test.js.map +1 -0
  58. package/dist/test/test/metrics/mount.test.d.ts +2 -0
  59. package/dist/test/test/metrics/mount.test.d.ts.map +1 -0
  60. package/dist/test/test/metrics/mount.test.js +59 -0
  61. package/dist/test/test/metrics/mount.test.js.map +1 -0
  62. package/dist/test/test/metrics/recorder.test.d.ts +2 -0
  63. package/dist/test/test/metrics/recorder.test.d.ts.map +1 -0
  64. package/dist/test/test/metrics/recorder.test.js +118 -0
  65. package/dist/test/test/metrics/recorder.test.js.map +1 -0
  66. package/dist/test/test/revisions/store.test.js +81 -11
  67. package/dist/test/test/revisions/store.test.js.map +1 -1
  68. package/dist/test/test/server.metrics.test.d.ts +2 -0
  69. package/dist/test/test/server.metrics.test.d.ts.map +1 -0
  70. package/dist/test/test/server.metrics.test.js +87 -0
  71. package/dist/test/test/server.metrics.test.js.map +1 -0
  72. package/dist/test/test/server.test.js +33 -4
  73. package/dist/test/test/server.test.js.map +1 -1
  74. package/dist/test/test/tools/asset-search.test.d.ts +2 -0
  75. package/dist/test/test/tools/asset-search.test.d.ts.map +1 -0
  76. package/dist/test/test/tools/asset-search.test.js +121 -0
  77. package/dist/test/test/tools/asset-search.test.js.map +1 -0
  78. package/dist/test/test/tools/edit.test.js +71 -9
  79. package/dist/test/test/tools/edit.test.js.map +1 -1
  80. package/dist/test/test/tools/export.test.js +60 -30
  81. package/dist/test/test/tools/export.test.js.map +1 -1
  82. package/dist/test/test/tools/index.test.d.ts +2 -0
  83. package/dist/test/test/tools/index.test.d.ts.map +1 -0
  84. package/dist/test/test/tools/index.test.js +55 -0
  85. package/dist/test/test/tools/index.test.js.map +1 -0
  86. package/dist/test/test/tools/metrics.test.d.ts +2 -0
  87. package/dist/test/test/tools/metrics.test.d.ts.map +1 -0
  88. package/dist/test/test/tools/metrics.test.js +29 -0
  89. package/dist/test/test/tools/metrics.test.js.map +1 -0
  90. package/dist/test/test/tools/preview.test.js +6 -4
  91. package/dist/test/test/tools/preview.test.js.map +1 -1
  92. package/dist/test/test/tools/rename.test.js +1 -2
  93. package/dist/test/test/tools/rename.test.js.map +1 -1
  94. package/dist/test/test/tools/save.test.d.ts +2 -0
  95. package/dist/test/test/tools/save.test.d.ts.map +1 -0
  96. package/dist/test/test/tools/save.test.js +137 -0
  97. package/dist/test/test/tools/save.test.js.map +1 -0
  98. package/dist/test/test/viewer/router.test.js +56 -0
  99. package/dist/test/test/viewer/router.test.js.map +1 -1
  100. package/dist/test/test/viewer/session-mount.test.d.ts +2 -0
  101. package/dist/test/test/viewer/session-mount.test.d.ts.map +1 -0
  102. package/dist/test/test/viewer/session-mount.test.js +52 -0
  103. package/dist/test/test/viewer/session-mount.test.js.map +1 -0
  104. package/dist/test/test/viewer/url-builder.test.js +76 -9
  105. package/dist/test/test/viewer/url-builder.test.js.map +1 -1
  106. package/dist/test/test/workspace/s3.test.d.ts +2 -0
  107. package/dist/test/test/workspace/s3.test.d.ts.map +1 -0
  108. package/dist/test/test/workspace/s3.test.js +153 -0
  109. package/dist/test/test/workspace/s3.test.js.map +1 -0
  110. package/dist/test/test/workspace/scoped.test.d.ts +2 -0
  111. package/dist/test/test/workspace/scoped.test.d.ts.map +1 -0
  112. package/dist/test/test/workspace/scoped.test.js +59 -0
  113. package/dist/test/test/workspace/scoped.test.js.map +1 -0
  114. package/dist/test/tsconfig.test.tsbuildinfo +1 -1
  115. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"stdio.d.ts","sourceRoot":"","sources":["../../src/cli/stdio.ts"],"names":[],"mappings":"AA8CA,wBAAsB,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,CAsG9C"}
1
+ {"version":3,"file":"stdio.d.ts","sourceRoot":"","sources":["../../src/cli/stdio.ts"],"names":[],"mappings":"AAqDA,wBAAsB,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,CA0G9C"}
package/dist/cli/stdio.js CHANGED
@@ -10,6 +10,13 @@
10
10
  // stdio's single-engine, single-workspace model is the simplest possible
11
11
  // wiring; HTTP transport (F2) reuses the same primitives per-session.
12
12
  //
13
+ // Startup order matters: everything cheap (workspace, store, viewer listener,
14
+ // tool wiring) runs before `server.connect()` so the MCP handshake replies
15
+ // immediately. The expensive CE.SDK engine init + CDN asset-source fetch are
16
+ // deferred to a background promise (bootstrapEngine) that the first tool call
17
+ // awaits. Doing this work before connect can blow the host's MCP startup
18
+ // timeout on a cold boot — seen as "fails on add/update, works after Reconnect".
19
+ //
13
20
  // Optional env:
14
21
  // CESDK_LICENSE — overrides the build-time-bundled trial license. Mandatory
15
22
  // at GA / when no trial is baked in (see resolve-license.ts).
@@ -51,29 +58,15 @@ export async function runStdio() {
51
58
  const workspaceRoot = process.env.CODESIGN_WORKSPACE ?? DEFAULT_WORKSPACE;
52
59
  const fs = new NodeWorkspaceFS(workspaceRoot);
53
60
  const store = new RevisionStore(fs);
54
- let engine;
55
- try {
56
- engine = await ensureHeadlessEngine(license);
57
- }
58
- catch (err) {
59
- if (usedTrial) {
60
- process.stderr.write('codesign-mcp stdio: the bundled trial license was rejected (it may have expired). Set CESDK_LICENSE to a valid CE.SDK node license string.\n');
61
- }
62
- throw err;
63
- }
64
- // Default + demo asset sources so `asset_search` has content (CDN fetch,
65
- // best-effort — a failure logs to stderr and the server still starts).
66
- await ensureEngineAssetSources(engine);
67
- // Demo `unsplash` custom source (synchronous, idempotent) so asset_search has
68
- // a real remote source alongside the CDN ones. Public IMG.LY demo proxy.
69
- registerUnsplashSource(engine);
70
- const session = new EngineSession({ size: 1, adapter: cesdkAdapter(engine) });
71
61
  // One engine, one lock. stdio is single-session so there is no contention
72
62
  // today, but the engine tools require the lock and it keeps wiring uniform
73
63
  // with the HTTP transport.
74
64
  const engineLock = new Mutex();
75
65
  const broadcaster = new SseBroadcaster();
76
66
  const gateway = new LocalGatewayClient({ apiKey: process.env.API_KEY });
67
+ // The viewer listener binds an ephemeral localhost port — cheap and local, so
68
+ // it stays on the startup path. urlBuilder must exist before we build the tools
69
+ // and before the engine's workspace:// URI resolver is wired.
77
70
  const viewerRouter = createViewerRouter({
78
71
  store,
79
72
  fs,
@@ -84,9 +77,22 @@ export async function runStdio() {
84
77
  const listener = await startViewerListener({ router: viewerRouter });
85
78
  process.stderr.write(`codesign-mcp stdio: viewer at ${listener.base}\n`);
86
79
  const urlBuilder = new LocalViewerUrlBuilder({ base: listener.base });
87
- // Resolve workspace:// asset URIs to the listener's /r/ route so the
88
- // headless engine can fetch generated assets embedded in scenes.
89
- registerWorkspaceUriResolver(engine, urlBuilder);
80
+ // Kick off the heavy engine bootstrap WITHOUT awaiting it see the file
81
+ // header. server.connect() below answers the handshake while this runs.
82
+ const enginePromise = bootstrapEngine({ license, urlBuilder, usedTrial });
83
+ // Nothing awaits enginePromise until the first tool call. Attach a logger so a
84
+ // background init failure surfaces on stderr instead of crashing the process
85
+ // on an unhandled rejection; the awaiting tool call still sees the real error.
86
+ void enginePromise.catch((err) => {
87
+ process.stderr.write(`codesign-mcp stdio: engine initialization failed — ${err instanceof Error ? (err.stack ?? err.message) : String(err)}\n`);
88
+ });
89
+ // size=1, single shared engine resolved lazily from enginePromise.
90
+ // EngineSession only calls adapter.create() (awaiting the engine) on the first
91
+ // edit, by which point the bootstrap is usually already settled.
92
+ const session = new EngineSession({
93
+ size: 1,
94
+ adapter: cesdkAdapter(enginePromise)
95
+ });
90
96
  const renderThumbnail = async ({ engine: eng }) => {
91
97
  // Thumbnail = the scene's first page rendered to a 256-px webp. Best-effort.
92
98
  // We use page 0 of the scene; if the scene has no pages, throw → swallowed.
@@ -111,7 +117,6 @@ export async function runStdio() {
111
117
  });
112
118
  const assets = loadNodeAssets();
113
119
  const server = createCodesignMcpServer({
114
- engine,
115
120
  assets,
116
121
  tools
117
122
  });
@@ -125,4 +130,32 @@ export async function runStdio() {
125
130
  process.on('SIGINT', () => void shutdown());
126
131
  process.on('SIGTERM', () => void shutdown());
127
132
  }
133
+ // Heavy engine bootstrap, run in the background by runStdio (off the MCP
134
+ // handshake path). Resolves to a fully-wired engine: design-unit-patched (by
135
+ // ensureHeadlessEngine), workspace:// URI resolver installed, and — best-effort
136
+ // — the default + demo + unsplash asset sources registered so asset_search has
137
+ // content. The asset-source step hits the CDN; failures log to stderr and leave
138
+ // the engine usable (asset_search just sees fewer sources).
139
+ async function bootstrapEngine({ license, urlBuilder, usedTrial }) {
140
+ let engine;
141
+ try {
142
+ engine = await ensureHeadlessEngine(license);
143
+ }
144
+ catch (err) {
145
+ if (usedTrial) {
146
+ process.stderr.write('codesign-mcp stdio: the bundled trial license was rejected (it may have expired). Set CESDK_LICENSE to a valid CE.SDK node license string.\n');
147
+ }
148
+ throw err;
149
+ }
150
+ // Resolve workspace:// asset URIs to the listener's /r/ route so the headless
151
+ // engine can fetch generated assets embedded in scenes.
152
+ registerWorkspaceUriResolver(engine, urlBuilder);
153
+ // Default + demo asset sources so `asset_search` has content (CDN fetch,
154
+ // best-effort — a failure logs to stderr and the engine stays usable).
155
+ await ensureEngineAssetSources(engine);
156
+ // Demo `unsplash` custom source (synchronous, idempotent) so asset_search has
157
+ // a real remote source alongside the CDN ones. Public IMG.LY demo proxy.
158
+ registerUnsplashSource(engine);
159
+ return engine;
160
+ }
128
161
  //# sourceMappingURL=stdio.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"stdio.js","sourceRoot":"","sources":["../../src/cli/stdio.ts"],"names":[],"mappings":"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","sourcesContent":["// MCP server over stdin/stdout. Bootstraps:\n// - Workspace (NodeWorkspaceFS rooted at $CODESIGN_WORKSPACE or ~/.codesign/workspace)\n// - RevisionStore over the workspace\n// - EngineSession (size=1) over the headless CE.SDK engine\n// - SseBroadcaster for the viewer\n// - Viewer router + ephemeral localhost listener\n// - design + workspace tool registrations\n// - LocalGatewayClient (reads $API_KEY)\n//\n// stdio's single-engine, single-workspace model is the simplest possible\n// wiring; HTTP transport (F2) reuses the same primitives per-session.\n//\n// Optional env:\n// CESDK_LICENSE — overrides the build-time-bundled trial license. Mandatory\n// at GA / when no trial is baked in (see resolve-license.ts).\n//\n// Logging note: stdout is reserved for MCP protocol traffic. Everything\n// human-facing (errors, progress) goes to stderr.\nimport { homedir } from 'node:os';\nimport { join as joinPath } from 'node:path';\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\nimport { loadNodeAssets } from '../assets/load-node.js';\nimport { ensureEngineAssetSources } from '../engine/asset-sources.js';\nimport { cesdkAdapter } from '../engine/cesdk-adapter.js';\nimport { Mutex } from '../engine/mutex.js';\nimport { EngineSession } from '../engine/session.js';\nimport { registerUnsplashSource } from '../engine/unsplash-source.js';\nimport { registerWorkspaceUriResolver } from '../engine/uri-resolver.js';\nimport { LocalGatewayClient } from '../gateway/local.js';\nimport { RevisionStore } from '../revisions/store.js';\nimport { createCodesignMcpServer } from '../server.js';\nimport { ensureHeadlessEngine } from '../tools/engine.js';\nimport { buildDesignToolRegistrations } from '../tools/index.js';\nimport {\n createViewerRouter,\n LocalViewerUrlBuilder,\n SseBroadcaster,\n startViewerListener\n} from '../viewer/index.js';\nimport { NodeWorkspaceFS } from '../workspace/node-fs.js';\nimport { redirectConsoleToStderr } from './redirect-console.js';\nimport { resolveLicense } from './resolve-license.js';\nimport { TRIAL_CESDK_LICENSE } from './trial-license.generated.js';\n\nconst DEFAULT_WORKSPACE = joinPath(homedir(), '.codesign', 'workspace');\n\nexport async function runStdio(): Promise<void> {\n // stdout is the JSON-RPC channel in stdio mode; keep engine console.* noise off it.\n redirectConsoleToStderr();\n\n const license = resolveLicense(process.env, TRIAL_CESDK_LICENSE);\n if (!license) {\n process.stderr.write(\n 'codesign-mcp stdio: no CESDK_LICENSE set and no bundled trial license — set CESDK_LICENSE to a CE.SDK node license string.\\n'\n );\n process.exit(1);\n }\n // True when the license came from the bundled trial (no explicit env override).\n // Used only to turn an engine-init rejection into a trial-aware hint below.\n const usedTrial = !process.env.CESDK_LICENSE?.trim();\n\n const workspaceRoot = process.env.CODESIGN_WORKSPACE ?? DEFAULT_WORKSPACE;\n const fs = new NodeWorkspaceFS(workspaceRoot);\n const store = new RevisionStore(fs);\n let engine: Awaited<ReturnType<typeof ensureHeadlessEngine>>;\n try {\n engine = await ensureHeadlessEngine(license);\n } catch (err) {\n if (usedTrial) {\n process.stderr.write(\n 'codesign-mcp stdio: the bundled trial license was rejected (it may have expired). Set CESDK_LICENSE to a valid CE.SDK node license string.\\n'\n );\n }\n throw err;\n }\n // Default + demo asset sources so `asset_search` has content (CDN fetch,\n // best-effort — a failure logs to stderr and the server still starts).\n await ensureEngineAssetSources(engine);\n // Demo `unsplash` custom source (synchronous, idempotent) so asset_search has\n // a real remote source alongside the CDN ones. Public IMG.LY demo proxy.\n registerUnsplashSource(engine);\n const session = new EngineSession({ size: 1, adapter: cesdkAdapter(engine) });\n // One engine, one lock. stdio is single-session so there is no contention\n // today, but the engine tools require the lock and it keeps wiring uniform\n // with the HTTP transport.\n const engineLock = new Mutex();\n const broadcaster = new SseBroadcaster();\n const gateway = new LocalGatewayClient({ apiKey: process.env.API_KEY });\n const viewerRouter = createViewerRouter({\n store,\n fs,\n broadcaster,\n cesdkLicense: license,\n workspaceRoot\n });\n const listener = await startViewerListener({ router: viewerRouter });\n process.stderr.write(`codesign-mcp stdio: viewer at ${listener.base}\\n`);\n\n const urlBuilder = new LocalViewerUrlBuilder({ base: listener.base });\n // Resolve workspace:// asset URIs to the listener's /r/ route so the\n // headless engine can fetch generated assets embedded in scenes.\n registerWorkspaceUriResolver(engine, urlBuilder);\n\n const renderThumbnail = async ({\n engine: eng\n }: {\n revision: string;\n engine: import('@cesdk/engine').default;\n }): Promise<Uint8Array> => {\n // Thumbnail = the scene's first page rendered to a 256-px webp. Best-effort.\n // We use page 0 of the scene; if the scene has no pages, throw → swallowed.\n const pages = eng.scene.getPages();\n if (!pages || pages.length === 0) throw new Error('no pages');\n const blob = await eng.block.export(pages[0], 'image/webp', {\n targetWidth: 256\n });\n return new Uint8Array(await blob.arrayBuffer());\n };\n\n const tools = buildDesignToolRegistrations({\n fs,\n store,\n session,\n engineLock,\n gateway,\n urlBuilder,\n allowOutPath: true,\n onLeafWritten: (e) => broadcaster.broadcast(e),\n renderThumbnail: renderThumbnail as never // adapter's E generic is `unknown`; cesdk engine type fits\n });\n\n const assets = loadNodeAssets();\n const server = createCodesignMcpServer({\n engine,\n assets,\n tools\n });\n\n const transport = new StdioServerTransport();\n await server.connect(transport);\n\n const shutdown = async () => {\n await listener.close();\n await server.close();\n process.exit(0);\n };\n process.on('SIGINT', () => void shutdown());\n process.on('SIGTERM', () => void shutdown());\n}\n"]}
1
+ {"version":3,"file":"stdio.js","sourceRoot":"","sources":["../../src/cli/stdio.ts"],"names":[],"mappings":"AAAA,4CAA4C;AAC5C,yFAAyF;AACzF,uCAAuC;AACvC,6DAA6D;AAC7D,oCAAoC;AACpC,mDAAmD;AACnD,4CAA4C;AAC5C,0CAA0C;AAC1C,EAAE;AACF,yEAAyE;AACzE,sEAAsE;AACtE,EAAE;AACF,8EAA8E;AAC9E,2EAA2E;AAC3E,6EAA6E;AAC7E,8EAA8E;AAC9E,yEAAyE;AACzE,iFAAiF;AACjF,EAAE;AACF,gBAAgB;AAChB,8EAA8E;AAC9E,gEAAgE;AAChE,EAAE;AACF,wEAAwE;AACxE,kDAAkD;AAClD,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,IAAI,QAAQ,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,EAAE,wBAAwB,EAAE,MAAM,4BAA4B,CAAC;AACtE,OAAO,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAC1D,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAC3C,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,sBAAsB,EAAE,MAAM,8BAA8B,CAAC;AACtE,OAAO,EAAE,4BAA4B,EAAE,MAAM,2BAA2B,CAAC;AACzE,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AACtD,OAAO,EAAE,uBAAuB,EAAE,MAAM,cAAc,CAAC;AACvD,OAAO,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAC1D,OAAO,EAAE,4BAA4B,EAAE,MAAM,mBAAmB,CAAC;AACjE,OAAO,EACL,kBAAkB,EAClB,qBAAqB,EACrB,cAAc,EACd,mBAAmB,EACpB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC1D,OAAO,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,mBAAmB,EAAE,MAAM,8BAA8B,CAAC;AAEnE,MAAM,iBAAiB,GAAG,QAAQ,CAAC,OAAO,EAAE,EAAE,WAAW,EAAE,WAAW,CAAC,CAAC;AAExE,MAAM,CAAC,KAAK,UAAU,QAAQ;IAC5B,oFAAoF;IACpF,uBAAuB,EAAE,CAAC;IAE1B,MAAM,OAAO,GAAG,cAAc,CAAC,OAAO,CAAC,GAAG,EAAE,mBAAmB,CAAC,CAAC;IACjE,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,8HAA8H,CAC/H,CAAC;QACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IACD,gFAAgF;IAChF,4EAA4E;IAC5E,MAAM,SAAS,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,aAAa,EAAE,IAAI,EAAE,CAAC;IAErD,MAAM,aAAa,GAAG,OAAO,CAAC,GAAG,CAAC,kBAAkB,IAAI,iBAAiB,CAAC;IAC1E,MAAM,EAAE,GAAG,IAAI,eAAe,CAAC,aAAa,CAAC,CAAC;IAC9C,MAAM,KAAK,GAAG,IAAI,aAAa,CAAC,EAAE,CAAC,CAAC;IAEpC,0EAA0E;IAC1E,2EAA2E;IAC3E,2BAA2B;IAC3B,MAAM,UAAU,GAAG,IAAI,KAAK,EAAE,CAAC;IAC/B,MAAM,WAAW,GAAG,IAAI,cAAc,EAAE,CAAC;IACzC,MAAM,OAAO,GAAG,IAAI,kBAAkB,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;IAExE,8EAA8E;IAC9E,gFAAgF;IAChF,8DAA8D;IAC9D,MAAM,YAAY,GAAG,kBAAkB,CAAC;QACtC,KAAK;QACL,EAAE;QACF,WAAW;QACX,YAAY,EAAE,OAAO;QACrB,aAAa;KACd,CAAC,CAAC;IACH,MAAM,QAAQ,GAAG,MAAM,mBAAmB,CAAC,EAAE,MAAM,EAAE,YAAY,EAAE,CAAC,CAAC;IACrE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,iCAAiC,QAAQ,CAAC,IAAI,IAAI,CAAC,CAAC;IACzE,MAAM,UAAU,GAAG,IAAI,qBAAqB,CAAC,EAAE,IAAI,EAAE,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;IAEtE,yEAAyE;IACzE,wEAAwE;IACxE,MAAM,aAAa,GAAG,eAAe,CAAC,EAAE,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,CAAC,CAAC;IAC1E,+EAA+E;IAC/E,6EAA6E;IAC7E,+EAA+E;IAC/E,KAAK,aAAa,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;QAC/B,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,sDACE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAChE,IAAI,CACL,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,mEAAmE;IACnE,+EAA+E;IAC/E,iEAAiE;IACjE,MAAM,OAAO,GAAG,IAAI,aAAa,CAAC;QAChC,IAAI,EAAE,CAAC;QACP,OAAO,EAAE,YAAY,CAAC,aAAa,CAAC;KACrC,CAAC,CAAC;IAEH,MAAM,eAAe,GAAG,KAAK,EAAE,EAC7B,MAAM,EAAE,GAAG,EAIZ,EAAuB,EAAE;QACxB,6EAA6E;QAC7E,4EAA4E;QAC5E,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC;QACnC,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,UAAU,CAAC,CAAC;QAC9D,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE;YAC1D,WAAW,EAAE,GAAG;SACjB,CAAC,CAAC;QACH,OAAO,IAAI,UAAU,CAAC,MAAM,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC;IAClD,CAAC,CAAC;IAEF,MAAM,KAAK,GAAG,4BAA4B,CAAC;QACzC,EAAE;QACF,KAAK;QACL,OAAO;QACP,UAAU;QACV,OAAO;QACP,UAAU;QACV,YAAY,EAAE,IAAI;QAClB,aAAa,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,WAAW,CAAC,SAAS,CAAC,CAAC,CAAC;QAC9C,eAAe,EAAE,eAAwB,CAAC,2DAA2D;KACtG,CAAC,CAAC;IAEH,MAAM,MAAM,GAAG,cAAc,EAAE,CAAC;IAChC,MAAM,MAAM,GAAG,uBAAuB,CAAC;QACrC,MAAM;QACN,KAAK;KACN,CAAC,CAAC;IAEH,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAEhC,MAAM,QAAQ,GAAG,KAAK,IAAI,EAAE;QAC1B,MAAM,QAAQ,CAAC,KAAK,EAAE,CAAC;QACvB,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC;QACrB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC;IACF,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,EAAE,CAAC,KAAK,QAAQ,EAAE,CAAC,CAAC;IAC5C,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,GAAG,EAAE,CAAC,KAAK,QAAQ,EAAE,CAAC,CAAC;AAC/C,CAAC;AAED,yEAAyE;AACzE,6EAA6E;AAC7E,gFAAgF;AAChF,+EAA+E;AAC/E,gFAAgF;AAChF,4DAA4D;AAC5D,KAAK,UAAU,eAAe,CAAC,EAC7B,OAAO,EACP,UAAU,EACV,SAAS,EAMV;IACC,IAAI,MAAwD,CAAC;IAC7D,IAAI,CAAC;QACH,MAAM,GAAG,MAAM,oBAAoB,CAAC,OAAO,CAAC,CAAC;IAC/C,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,SAAS,EAAE,CAAC;YACd,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,8IAA8I,CAC/I,CAAC;QACJ,CAAC;QACD,MAAM,GAAG,CAAC;IACZ,CAAC;IACD,8EAA8E;IAC9E,wDAAwD;IACxD,4BAA4B,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IACjD,yEAAyE;IACzE,uEAAuE;IACvE,MAAM,wBAAwB,CAAC,MAAM,CAAC,CAAC;IACvC,8EAA8E;IAC9E,yEAAyE;IACzE,sBAAsB,CAAC,MAAM,CAAC,CAAC;IAC/B,OAAO,MAAM,CAAC;AAChB,CAAC","sourcesContent":["// MCP server over stdin/stdout. Bootstraps:\n// - Workspace (NodeWorkspaceFS rooted at $CODESIGN_WORKSPACE or ~/.codesign/workspace)\n// - RevisionStore over the workspace\n// - EngineSession (size=1) over the headless CE.SDK engine\n// - SseBroadcaster for the viewer\n// - Viewer router + ephemeral localhost listener\n// - design + workspace tool registrations\n// - LocalGatewayClient (reads $API_KEY)\n//\n// stdio's single-engine, single-workspace model is the simplest possible\n// wiring; HTTP transport (F2) reuses the same primitives per-session.\n//\n// Startup order matters: everything cheap (workspace, store, viewer listener,\n// tool wiring) runs before `server.connect()` so the MCP handshake replies\n// immediately. The expensive CE.SDK engine init + CDN asset-source fetch are\n// deferred to a background promise (bootstrapEngine) that the first tool call\n// awaits. Doing this work before connect can blow the host's MCP startup\n// timeout on a cold boot — seen as \"fails on add/update, works after Reconnect\".\n//\n// Optional env:\n// CESDK_LICENSE — overrides the build-time-bundled trial license. Mandatory\n// at GA / when no trial is baked in (see resolve-license.ts).\n//\n// Logging note: stdout is reserved for MCP protocol traffic. Everything\n// human-facing (errors, progress) goes to stderr.\nimport { homedir } from 'node:os';\nimport { join as joinPath } from 'node:path';\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\nimport { loadNodeAssets } from '../assets/load-node.js';\nimport { ensureEngineAssetSources } from '../engine/asset-sources.js';\nimport { cesdkAdapter } from '../engine/cesdk-adapter.js';\nimport { Mutex } from '../engine/mutex.js';\nimport { EngineSession } from '../engine/session.js';\nimport { registerUnsplashSource } from '../engine/unsplash-source.js';\nimport { registerWorkspaceUriResolver } from '../engine/uri-resolver.js';\nimport { LocalGatewayClient } from '../gateway/local.js';\nimport { RevisionStore } from '../revisions/store.js';\nimport { createCodesignMcpServer } from '../server.js';\nimport { ensureHeadlessEngine } from '../tools/engine.js';\nimport { buildDesignToolRegistrations } from '../tools/index.js';\nimport {\n createViewerRouter,\n LocalViewerUrlBuilder,\n SseBroadcaster,\n startViewerListener\n} from '../viewer/index.js';\nimport { NodeWorkspaceFS } from '../workspace/node-fs.js';\nimport { redirectConsoleToStderr } from './redirect-console.js';\nimport { resolveLicense } from './resolve-license.js';\nimport { TRIAL_CESDK_LICENSE } from './trial-license.generated.js';\n\nconst DEFAULT_WORKSPACE = joinPath(homedir(), '.codesign', 'workspace');\n\nexport async function runStdio(): Promise<void> {\n // stdout is the JSON-RPC channel in stdio mode; keep engine console.* noise off it.\n redirectConsoleToStderr();\n\n const license = resolveLicense(process.env, TRIAL_CESDK_LICENSE);\n if (!license) {\n process.stderr.write(\n 'codesign-mcp stdio: no CESDK_LICENSE set and no bundled trial license — set CESDK_LICENSE to a CE.SDK node license string.\\n'\n );\n process.exit(1);\n }\n // True when the license came from the bundled trial (no explicit env override).\n // Used only to turn an engine-init rejection into a trial-aware hint below.\n const usedTrial = !process.env.CESDK_LICENSE?.trim();\n\n const workspaceRoot = process.env.CODESIGN_WORKSPACE ?? DEFAULT_WORKSPACE;\n const fs = new NodeWorkspaceFS(workspaceRoot);\n const store = new RevisionStore(fs);\n\n // One engine, one lock. stdio is single-session so there is no contention\n // today, but the engine tools require the lock and it keeps wiring uniform\n // with the HTTP transport.\n const engineLock = new Mutex();\n const broadcaster = new SseBroadcaster();\n const gateway = new LocalGatewayClient({ apiKey: process.env.API_KEY });\n\n // The viewer listener binds an ephemeral localhost port — cheap and local, so\n // it stays on the startup path. urlBuilder must exist before we build the tools\n // and before the engine's workspace:// URI resolver is wired.\n const viewerRouter = createViewerRouter({\n store,\n fs,\n broadcaster,\n cesdkLicense: license,\n workspaceRoot\n });\n const listener = await startViewerListener({ router: viewerRouter });\n process.stderr.write(`codesign-mcp stdio: viewer at ${listener.base}\\n`);\n const urlBuilder = new LocalViewerUrlBuilder({ base: listener.base });\n\n // Kick off the heavy engine bootstrap WITHOUT awaiting it — see the file\n // header. server.connect() below answers the handshake while this runs.\n const enginePromise = bootstrapEngine({ license, urlBuilder, usedTrial });\n // Nothing awaits enginePromise until the first tool call. Attach a logger so a\n // background init failure surfaces on stderr instead of crashing the process\n // on an unhandled rejection; the awaiting tool call still sees the real error.\n void enginePromise.catch((err) => {\n process.stderr.write(\n `codesign-mcp stdio: engine initialization failed — ${\n err instanceof Error ? (err.stack ?? err.message) : String(err)\n }\\n`\n );\n });\n\n // size=1, single shared engine resolved lazily from enginePromise.\n // EngineSession only calls adapter.create() (awaiting the engine) on the first\n // edit, by which point the bootstrap is usually already settled.\n const session = new EngineSession({\n size: 1,\n adapter: cesdkAdapter(enginePromise)\n });\n\n const renderThumbnail = async ({\n engine: eng\n }: {\n revision: string;\n engine: import('@cesdk/engine').default;\n }): Promise<Uint8Array> => {\n // Thumbnail = the scene's first page rendered to a 256-px webp. Best-effort.\n // We use page 0 of the scene; if the scene has no pages, throw → swallowed.\n const pages = eng.scene.getPages();\n if (!pages || pages.length === 0) throw new Error('no pages');\n const blob = await eng.block.export(pages[0], 'image/webp', {\n targetWidth: 256\n });\n return new Uint8Array(await blob.arrayBuffer());\n };\n\n const tools = buildDesignToolRegistrations({\n fs,\n store,\n session,\n engineLock,\n gateway,\n urlBuilder,\n allowOutPath: true,\n onLeafWritten: (e) => broadcaster.broadcast(e),\n renderThumbnail: renderThumbnail as never // adapter's E generic is `unknown`; cesdk engine type fits\n });\n\n const assets = loadNodeAssets();\n const server = createCodesignMcpServer({\n assets,\n tools\n });\n\n const transport = new StdioServerTransport();\n await server.connect(transport);\n\n const shutdown = async () => {\n await listener.close();\n await server.close();\n process.exit(0);\n };\n process.on('SIGINT', () => void shutdown());\n process.on('SIGTERM', () => void shutdown());\n}\n\n// Heavy engine bootstrap, run in the background by runStdio (off the MCP\n// handshake path). Resolves to a fully-wired engine: design-unit-patched (by\n// ensureHeadlessEngine), workspace:// URI resolver installed, and — best-effort\n// — the default + demo + unsplash asset sources registered so asset_search has\n// content. The asset-source step hits the CDN; failures log to stderr and leave\n// the engine usable (asset_search just sees fewer sources).\nasync function bootstrapEngine({\n license,\n urlBuilder,\n usedTrial\n}: {\n license: string;\n urlBuilder: LocalViewerUrlBuilder;\n /** License came from the bundled trial — turns an init rejection into a hint. */\n usedTrial: boolean;\n}): Promise<Awaited<ReturnType<typeof ensureHeadlessEngine>>> {\n let engine: Awaited<ReturnType<typeof ensureHeadlessEngine>>;\n try {\n engine = await ensureHeadlessEngine(license);\n } catch (err) {\n if (usedTrial) {\n process.stderr.write(\n 'codesign-mcp stdio: the bundled trial license was rejected (it may have expired). Set CESDK_LICENSE to a valid CE.SDK node license string.\\n'\n );\n }\n throw err;\n }\n // Resolve workspace:// asset URIs to the listener's /r/ route so the headless\n // engine can fetch generated assets embedded in scenes.\n registerWorkspaceUriResolver(engine, urlBuilder);\n // Default + demo asset sources so `asset_search` has content (CDN fetch,\n // best-effort — a failure logs to stderr and the engine stays usable).\n await ensureEngineAssetSources(engine);\n // Demo `unsplash` custom source (synchronous, idempotent) so asset_search has\n // a real remote source alongside the CDN ones. Public IMG.LY demo proxy.\n registerUnsplashSource(engine);\n return engine;\n}\n"]}
@@ -5,6 +5,12 @@ import type { EngineAdapter } from './session.js';
5
5
  * generic EngineAdapter shape. One-to-one mapping; the only quirk is `reset`
6
6
  * (CE.SDK has no `reset()` — we wipe by creating a new scene via the same
7
7
  * patched create path `ensureHeadlessEngine` installed for the design-unit fix).
8
+ *
9
+ * `engine` may be a Promise: the stdio transport defers engine init off the
10
+ * MCP-handshake path and hands the in-flight promise here. EngineSession only
11
+ * calls `create()` on the first slot (first edit), awaiting it then — by which
12
+ * point init is usually already settled. The other methods receive the resolved
13
+ * engine from EngineSession, so they never touch the promise.
8
14
  */
9
- export declare function cesdkAdapter(engine: CreativeEngine): EngineAdapter<CreativeEngine>;
15
+ export declare function cesdkAdapter(engine: CreativeEngine | Promise<CreativeEngine>): EngineAdapter<CreativeEngine>;
10
16
  //# sourceMappingURL=cesdk-adapter.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"cesdk-adapter.d.ts","sourceRoot":"","sources":["../../src/engine/cesdk-adapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,cAAc,MAAM,eAAe,CAAC;AAChD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAElD;;;;;GAKG;AACH,wBAAgB,YAAY,CAC1B,MAAM,EAAE,cAAc,GACrB,aAAa,CAAC,cAAc,CAAC,CAW/B"}
1
+ {"version":3,"file":"cesdk-adapter.d.ts","sourceRoot":"","sources":["../../src/engine/cesdk-adapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,cAAc,MAAM,eAAe,CAAC;AAChD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAElD;;;;;;;;;;;GAWG;AACH,wBAAgB,YAAY,CAC1B,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,cAAc,CAAC,GAC/C,aAAa,CAAC,cAAc,CAAC,CAW/B"}
@@ -3,10 +3,16 @@
3
3
  * generic EngineAdapter shape. One-to-one mapping; the only quirk is `reset`
4
4
  * (CE.SDK has no `reset()` — we wipe by creating a new scene via the same
5
5
  * patched create path `ensureHeadlessEngine` installed for the design-unit fix).
6
+ *
7
+ * `engine` may be a Promise: the stdio transport defers engine init off the
8
+ * MCP-handshake path and hands the in-flight promise here. EngineSession only
9
+ * calls `create()` on the first slot (first edit), awaiting it then — by which
10
+ * point init is usually already settled. The other methods receive the resolved
11
+ * engine from EngineSession, so they never touch the promise.
6
12
  */
7
13
  export function cesdkAdapter(engine) {
8
14
  return {
9
- create: () => engine, // single-engine adapter: always the same instance
15
+ create: () => engine, // single-engine adapter: always the same instance (awaited lazily if a promise)
10
16
  reset: async (eng) => {
11
17
  eng.scene.create(); // wipes the active scene; design-unit patch from tools/engine.ts re-applies
12
18
  },
@@ -1 +1 @@
1
- {"version":3,"file":"cesdk-adapter.js","sourceRoot":"","sources":["../../src/engine/cesdk-adapter.ts"],"names":[],"mappings":"AAGA;;;;;GAKG;AACH,MAAM,UAAU,YAAY,CAC1B,MAAsB;IAEtB,OAAO;QACL,MAAM,EAAE,GAAG,EAAE,CAAC,MAAM,EAAE,kDAAkD;QACxE,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE;YACnB,GAAG,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC,4EAA4E;QAClG,CAAC;QACD,cAAc,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,EAAE;YACnC,MAAM,GAAG,CAAC,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC;QACxC,CAAC;QACD,YAAY,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,YAAY,EAAE;KACtD,CAAC;AACJ,CAAC","sourcesContent":["import type CreativeEngine from '@cesdk/engine';\nimport type { EngineAdapter } from './session.js';\n\n/**\n * Adapts a `@cesdk/engine` CreativeEngine instance to the EngineSession's\n * generic EngineAdapter shape. One-to-one mapping; the only quirk is `reset`\n * (CE.SDK has no `reset()` — we wipe by creating a new scene via the same\n * patched create path `ensureHeadlessEngine` installed for the design-unit fix).\n */\nexport function cesdkAdapter(\n engine: CreativeEngine\n): EngineAdapter<CreativeEngine> {\n return {\n create: () => engine, // single-engine adapter: always the same instance\n reset: async (eng) => {\n eng.scene.create(); // wipes the active scene; design-unit patch from tools/engine.ts re-applies\n },\n loadFromString: async (eng, scene) => {\n await eng.scene.loadFromString(scene);\n },\n saveToString: async (eng) => eng.scene.saveToString()\n };\n}\n"]}
1
+ {"version":3,"file":"cesdk-adapter.js","sourceRoot":"","sources":["../../src/engine/cesdk-adapter.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,YAAY,CAC1B,MAAgD;IAEhD,OAAO;QACL,MAAM,EAAE,GAAG,EAAE,CAAC,MAAM,EAAE,gFAAgF;QACtG,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE;YACnB,GAAG,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC,4EAA4E;QAClG,CAAC;QACD,cAAc,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,EAAE;YACnC,MAAM,GAAG,CAAC,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC;QACxC,CAAC;QACD,YAAY,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,YAAY,EAAE;KACtD,CAAC;AACJ,CAAC","sourcesContent":["import type CreativeEngine from '@cesdk/engine';\nimport type { EngineAdapter } from './session.js';\n\n/**\n * Adapts a `@cesdk/engine` CreativeEngine instance to the EngineSession's\n * generic EngineAdapter shape. One-to-one mapping; the only quirk is `reset`\n * (CE.SDK has no `reset()` — we wipe by creating a new scene via the same\n * patched create path `ensureHeadlessEngine` installed for the design-unit fix).\n *\n * `engine` may be a Promise: the stdio transport defers engine init off the\n * MCP-handshake path and hands the in-flight promise here. EngineSession only\n * calls `create()` on the first slot (first edit), awaiting it then — by which\n * point init is usually already settled. The other methods receive the resolved\n * engine from EngineSession, so they never touch the promise.\n */\nexport function cesdkAdapter(\n engine: CreativeEngine | Promise<CreativeEngine>\n): EngineAdapter<CreativeEngine> {\n return {\n create: () => engine, // single-engine adapter: always the same instance (awaited lazily if a promise)\n reset: async (eng) => {\n eng.scene.create(); // wipes the active scene; design-unit patch from tools/engine.ts re-applies\n },\n loadFromString: async (eng, scene) => {\n await eng.scene.loadFromString(scene);\n },\n saveToString: async (eng) => eng.scene.saveToString()\n };\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/http/server.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,6BAA6B,EAAE,MAAM,oDAAoD,CAAC;AACnG,OAAa,EAAE,KAAK,WAAW,EAAE,MAAM,MAAM,CAAC;AAC9C,OAAgB,EACd,KAAK,OAAO,EACZ,KAAK,OAAO,EACZ,KAAK,cAAc,EAEpB,MAAM,SAAS,CAAC;AACjB,OAAO,EACL,KAAK,2BAA2B,EAChC,uBAAuB,EACxB,MAAM,cAAc,CAAC;AACtB,OAAO,EAA2B,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAE/E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAE9D;;;;GAIG;AACH,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,OAAO,CAAC;IAChB,MAAM,EAAE,UAAU,CAAC,OAAO,uBAAuB,CAAC,CAAC;IACnD,SAAS,EAAE,6BAA6B,CAAC;IACzC,OAAO,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC/B;AAED,MAAM,WAAW,+BAA+B;IAC9C,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB;;;;OAIG;IACH,kBAAkB,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,KAAK,MAAM,CAAC;IACjE;;;;;;OAMG;IACH,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,yDAAyD;IACzD,aAAa,EAAE,CAAC,GAAG,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC;QAC1E,MAAM,EAAE,OAAO,CAAC;QAChB,KAAK,EAAE,2BAA2B,EAAE,CAAC;QACrC,OAAO,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;KAC/B,CAAC,CAAC;IACH,gFAAgF;IAChF,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,CAAC;IACrC;;;OAGG;IACH,OAAO,CAAC,EAAE,eAAe,CAAC;IAC1B,wEAAwE;IACxE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,0EAA0E;IAC1E,IAAI,CAAC,EAAE,cAAc,CAAC;IACtB,gDAAgD;IAChD,IAAI,CAAC,EAAE,WAAW,GAAG,KAAK,CAAC;CAC5B;AAED;;;;;;;;;;GAUG;AACH,MAAM,WAAW,kBAAmB,SAAQ,OAAO;IACjD,QAAQ,EAAE,cAAc,CACtB,mBAAmB,EACnB;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,CAC3C,CAAC;IACF,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3B;AAED,wBAAsB,wBAAwB,CAC5C,IAAI,EAAE,+BAA+B,GACpC,OAAO,CAAC,kBAAkB,CAAC,CAyL7B"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/http/server.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,6BAA6B,EAAE,MAAM,oDAAoD,CAAC;AACnG,OAAa,EAAE,KAAK,WAAW,EAAE,MAAM,MAAM,CAAC;AAC9C,OAAgB,EACd,KAAK,OAAO,EACZ,KAAK,OAAO,EACZ,KAAK,cAAc,EAEpB,MAAM,SAAS,CAAC;AACjB,OAAO,EACL,KAAK,2BAA2B,EAChC,uBAAuB,EACxB,MAAM,cAAc,CAAC;AACtB,OAAO,EAA2B,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAE/E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAE9D;;;;GAIG;AACH,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,OAAO,CAAC;IAChB,MAAM,EAAE,UAAU,CAAC,OAAO,uBAAuB,CAAC,CAAC;IACnD,SAAS,EAAE,6BAA6B,CAAC;IACzC,OAAO,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC/B;AAED,MAAM,WAAW,+BAA+B;IAC9C,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB;;;;OAIG;IACH,kBAAkB,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,KAAK,MAAM,CAAC;IACjE;;;;;;OAMG;IACH,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,yDAAyD;IACzD,aAAa,EAAE,CAAC,GAAG,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC;QAC1E,MAAM,EAAE,OAAO,CAAC;QAChB,KAAK,EAAE,2BAA2B,EAAE,CAAC;QACrC,OAAO,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;KAC/B,CAAC,CAAC;IACH,gFAAgF;IAChF,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,CAAC;IACrC;;;OAGG;IACH,OAAO,CAAC,EAAE,eAAe,CAAC;IAC1B,wEAAwE;IACxE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,0EAA0E;IAC1E,IAAI,CAAC,EAAE,cAAc,CAAC;IACtB,gDAAgD;IAChD,IAAI,CAAC,EAAE,WAAW,GAAG,KAAK,CAAC;CAC5B;AAED;;;;;;;;;;GAUG;AACH,MAAM,WAAW,kBAAmB,SAAQ,OAAO;IACjD,QAAQ,EAAE,cAAc,CACtB,mBAAmB,EACnB;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,CAC3C,CAAC;IACF,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3B;AAED,wBAAsB,wBAAwB,CAC5C,IAAI,EAAE,+BAA+B,GACpC,OAAO,CAAC,kBAAkB,CAAC,CAwL7B"}
@@ -19,7 +19,6 @@ export async function createCodesignHttpServer(opts) {
19
19
  workspaceId: ctx.workspaceId
20
20
  });
21
21
  const server = createCodesignMcpServer({
22
- engine,
23
22
  assets: opts.assets,
24
23
  resourceScheme: opts.resourceScheme,
25
24
  tools,
@@ -1 +1 @@
1
- {"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/http/server.ts"],"names":[],"mappings":"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","sourcesContent":["import { randomUUID } from 'node:crypto';\nimport { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';\nimport cors, { type CorsOptions } from 'cors';\nimport express, {\n type Express,\n type Request,\n type RequestHandler,\n type Response\n} from 'express';\nimport {\n type CodesignMcpToolRegistration,\n createCodesignMcpServer\n} from '../server.js';\nimport { SessionCapExceededError, SessionManager } from './session-manager.js';\nimport { WorkspaceAuthError } from './workspace-auth.js';\nimport type { MetricsRecorder } from '../metrics/recorder.js';\n\n/**\n * Per-session state held by the SessionManager: the engine, the MCP server,\n * and the per-session HTTP transport. Every incoming request with this\n * session's id is routed to `transport.handleRequest`.\n */\nexport interface CodesignHttpSession {\n id: string;\n engine: unknown;\n server: ReturnType<typeof createCodesignMcpServer>;\n transport: StreamableHTTPServerTransport;\n dispose?: () => Promise<void>;\n}\n\nexport interface CreateCodesignHttpServerOptions {\n assets: Record<string, string>;\n resourceScheme?: string;\n maxSessions: number;\n idleTimeoutMs: number;\n /**\n * Resolve the front-asserted workspaceId for a new MCP session. Receives the\n * freshly-minted session id (so `session` mode can derive `s/<id>`). May throw\n * WorkspaceAuthError → 401. Default: a fixed \"default\" workspace.\n */\n resolveWorkspaceId?: (req: Request, sessionId: string) => string;\n /**\n * When set, an `initialize` request carrying this (case-insensitive) header uses\n * its value as the MCP session id instead of minting one. The Worker/front\n * mints the id so it can route follow-ups to the same warm instance (spec §3).\n * The front MUST strip any client-supplied value of this header — same trust\n * posture as the workspace header. Absent header → mint as normal.\n */\n trustedSessionIdHeader?: string;\n /** Build everything needed for one session in one go. */\n createSession: (ctx: { sessionId: string; workspaceId: string }) => Promise<{\n engine: unknown;\n tools: CodesignMcpToolRegistration[];\n dispose?: () => Promise<void>;\n }>;\n /** Optional: mount additional routes (viewer router, dashboards) before MCP. */\n mountExtras?: (app: Express) => void;\n /**\n * Optional metrics recorder, DI'd into every session's MCP server so tool\n * calls are timed at the single dispatch chokepoint. Absent → no timing.\n */\n metrics?: MetricsRecorder;\n /** Build identity reported as `serverInfo.version`. Default `'dev'`. */\n buildId?: string;\n /** Optional auth middleware — runs before MCP routing. Default: no-op. */\n auth?: RequestHandler;\n /** Optional CORS options. Default: disabled. */\n cors?: CorsOptions | false;\n}\n\n/**\n * Express app exposing MCP over Streamable HTTP at /mcp. Stateful sessions:\n * each MCP `initialize` call spawns a fresh session (engine + server +\n * transport); follow-up requests carry `Mcp-Session-Id` to route back. The\n * underlying SessionManager enforces idle eviction and a session cap.\n *\n * The returned object exposes:\n * .listen / .use / etc. — Express's surface (composes into larger apps)\n * .sessions — the SessionManager (testing + ops insight)\n * .shutdown() — destroy every active session, dispose engines\n */\nexport interface CodesignHttpServer extends Express {\n sessions: SessionManager<\n CodesignHttpSession,\n { sessionId: string; workspaceId: string }\n >;\n shutdown(): Promise<void>;\n}\n\nexport async function createCodesignHttpServer(\n opts: CreateCodesignHttpServerOptions\n): Promise<CodesignHttpServer> {\n const resolveWorkspaceId = opts.resolveWorkspaceId ?? (() => 'default');\n\n const sessionMgr = new SessionManager<\n CodesignHttpSession,\n { sessionId: string; workspaceId: string }\n >({\n maxSessions: opts.maxSessions,\n idleTimeoutMs: opts.idleTimeoutMs,\n create: async (ctx) => {\n if (!ctx)\n throw new Error(\n 'createCodesignHttpServer: session context is required'\n );\n const id = ctx.sessionId;\n const { engine, tools, dispose } = await opts.createSession({\n sessionId: id,\n workspaceId: ctx.workspaceId\n });\n const server = createCodesignMcpServer({\n engine,\n assets: opts.assets,\n resourceScheme: opts.resourceScheme,\n tools,\n ...(opts.buildId ? { buildId: opts.buildId } : {}),\n ...(opts.metrics ? { metrics: opts.metrics } : {})\n });\n const transport = new StreamableHTTPServerTransport({\n sessionIdGenerator: () => id,\n // A client `DELETE /mcp` (graceful termination) fires this before the\n // transport closes itself. Bridge it to the SessionManager so the slot\n // and its engine are reclaimed immediately instead of squatting until\n // idle eviction. Only fires on the DELETE path — not from transport\n // `close()` during `destroy` — so there is no re-entrancy: `delete`\n // removes the map entry first, then runs `destroy` (a redundant,\n // idempotent second `transport.close()`).\n onsessionclosed: (sid) => {\n void sessionMgr.delete(sid).catch(() => {\n // Already gone (idle-evicted, or a duplicate close) — nothing to do.\n });\n }\n });\n await server.connect(transport);\n return { id, engine, server, transport, dispose };\n },\n destroy: async (s) => {\n try {\n await s.transport.close();\n } catch {\n // ignore — best-effort\n }\n try {\n await s.server.close();\n } catch {\n // ignore\n }\n // Call the session's dispose callback if provided (e.g. engine teardown).\n try {\n await s.dispose?.();\n } catch {\n // ignore\n }\n }\n });\n\n const app = express() as CodesignHttpServer;\n app.sessions = sessionMgr;\n\n if (opts.cors !== false) {\n app.use(cors(opts.cors));\n }\n if (opts.auth) {\n app.use(opts.auth);\n }\n app.use(express.json({ limit: '16mb' }));\n\n /** Narrow `code` out of an unknown thrown value without `as any`. */\n function errCode(e: unknown): string | undefined {\n return typeof e === 'object' && e !== null && 'code' in e\n ? ((e as { code?: unknown }).code as string | undefined)\n : undefined;\n }\n\n const handle = async (req: Request, res: Response): Promise<void> => {\n const sessionId = req.header('mcp-session-id');\n\n // Initialization (POST without session id) → spawn a new session.\n if (req.method === 'POST' && !sessionId) {\n const injected = opts.trustedSessionIdHeader\n ? req.header(opts.trustedSessionIdHeader)?.trim() || undefined\n : undefined;\n const newSessionId = injected ?? randomUUID();\n let workspaceId: string;\n try {\n workspaceId = resolveWorkspaceId(req, newSessionId);\n } catch (err) {\n if (err instanceof WorkspaceAuthError) {\n res.status(401).json({\n jsonrpc: '2.0',\n error: { code: -32002, message: err.message },\n id: null\n });\n return;\n }\n res.status(500).json({\n jsonrpc: '2.0',\n error: { code: -32603, message: 'internal error' },\n id: null\n });\n return;\n }\n let session: CodesignHttpSession;\n try {\n session = await sessionMgr.create({\n sessionId: newSessionId,\n workspaceId\n });\n } catch (err) {\n if (err instanceof SessionCapExceededError) {\n res\n .status(503)\n .set('Retry-After', '30')\n .json({\n jsonrpc: '2.0',\n error: { code: -32000, message: err.message },\n id: null\n });\n return;\n }\n if (errCode(err) === 'EINVAL') {\n res.status(400).json({\n jsonrpc: '2.0',\n error: {\n code: -32602,\n message:\n err instanceof Error ? err.message : 'invalid workspaceId'\n },\n id: null\n });\n return;\n }\n res.status(500).json({\n jsonrpc: '2.0',\n error: { code: -32603, message: 'internal error' },\n id: null\n });\n return;\n }\n await session.transport.handleRequest(req, res, req.body);\n return;\n }\n\n // Subsequent requests must carry a known session id.\n if (!sessionId) {\n res.status(400).json({\n jsonrpc: '2.0',\n error: { code: -32600, message: 'Mcp-Session-Id header required' },\n id: null\n });\n return;\n }\n const session = sessionMgr.get(sessionId);\n if (!session) {\n res.status(404).json({\n jsonrpc: '2.0',\n error: { code: -32001, message: `unknown session: ${sessionId}` },\n id: null\n });\n return;\n }\n sessionMgr.touch(sessionId);\n await session.transport.handleRequest(req, res, req.body);\n };\n\n if (opts.mountExtras) opts.mountExtras(app);\n\n app.post('/mcp', handle);\n app.get('/mcp', handle);\n app.delete('/mcp', handle);\n\n app.shutdown = async () => {\n await sessionMgr.destroyAll();\n };\n\n return app;\n}\n"]}
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/http/server.ts"],"names":[],"mappings":"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","sourcesContent":["import { randomUUID } from 'node:crypto';\nimport { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';\nimport cors, { type CorsOptions } from 'cors';\nimport express, {\n type Express,\n type Request,\n type RequestHandler,\n type Response\n} from 'express';\nimport {\n type CodesignMcpToolRegistration,\n createCodesignMcpServer\n} from '../server.js';\nimport { SessionCapExceededError, SessionManager } from './session-manager.js';\nimport { WorkspaceAuthError } from './workspace-auth.js';\nimport type { MetricsRecorder } from '../metrics/recorder.js';\n\n/**\n * Per-session state held by the SessionManager: the engine, the MCP server,\n * and the per-session HTTP transport. Every incoming request with this\n * session's id is routed to `transport.handleRequest`.\n */\nexport interface CodesignHttpSession {\n id: string;\n engine: unknown;\n server: ReturnType<typeof createCodesignMcpServer>;\n transport: StreamableHTTPServerTransport;\n dispose?: () => Promise<void>;\n}\n\nexport interface CreateCodesignHttpServerOptions {\n assets: Record<string, string>;\n resourceScheme?: string;\n maxSessions: number;\n idleTimeoutMs: number;\n /**\n * Resolve the front-asserted workspaceId for a new MCP session. Receives the\n * freshly-minted session id (so `session` mode can derive `s/<id>`). May throw\n * WorkspaceAuthError → 401. Default: a fixed \"default\" workspace.\n */\n resolveWorkspaceId?: (req: Request, sessionId: string) => string;\n /**\n * When set, an `initialize` request carrying this (case-insensitive) header uses\n * its value as the MCP session id instead of minting one. The Worker/front\n * mints the id so it can route follow-ups to the same warm instance (spec §3).\n * The front MUST strip any client-supplied value of this header — same trust\n * posture as the workspace header. Absent header → mint as normal.\n */\n trustedSessionIdHeader?: string;\n /** Build everything needed for one session in one go. */\n createSession: (ctx: { sessionId: string; workspaceId: string }) => Promise<{\n engine: unknown;\n tools: CodesignMcpToolRegistration[];\n dispose?: () => Promise<void>;\n }>;\n /** Optional: mount additional routes (viewer router, dashboards) before MCP. */\n mountExtras?: (app: Express) => void;\n /**\n * Optional metrics recorder, DI'd into every session's MCP server so tool\n * calls are timed at the single dispatch chokepoint. Absent → no timing.\n */\n metrics?: MetricsRecorder;\n /** Build identity reported as `serverInfo.version`. Default `'dev'`. */\n buildId?: string;\n /** Optional auth middleware — runs before MCP routing. Default: no-op. */\n auth?: RequestHandler;\n /** Optional CORS options. Default: disabled. */\n cors?: CorsOptions | false;\n}\n\n/**\n * Express app exposing MCP over Streamable HTTP at /mcp. Stateful sessions:\n * each MCP `initialize` call spawns a fresh session (engine + server +\n * transport); follow-up requests carry `Mcp-Session-Id` to route back. The\n * underlying SessionManager enforces idle eviction and a session cap.\n *\n * The returned object exposes:\n * .listen / .use / etc. — Express's surface (composes into larger apps)\n * .sessions — the SessionManager (testing + ops insight)\n * .shutdown() — destroy every active session, dispose engines\n */\nexport interface CodesignHttpServer extends Express {\n sessions: SessionManager<\n CodesignHttpSession,\n { sessionId: string; workspaceId: string }\n >;\n shutdown(): Promise<void>;\n}\n\nexport async function createCodesignHttpServer(\n opts: CreateCodesignHttpServerOptions\n): Promise<CodesignHttpServer> {\n const resolveWorkspaceId = opts.resolveWorkspaceId ?? (() => 'default');\n\n const sessionMgr = new SessionManager<\n CodesignHttpSession,\n { sessionId: string; workspaceId: string }\n >({\n maxSessions: opts.maxSessions,\n idleTimeoutMs: opts.idleTimeoutMs,\n create: async (ctx) => {\n if (!ctx)\n throw new Error(\n 'createCodesignHttpServer: session context is required'\n );\n const id = ctx.sessionId;\n const { engine, tools, dispose } = await opts.createSession({\n sessionId: id,\n workspaceId: ctx.workspaceId\n });\n const server = createCodesignMcpServer({\n assets: opts.assets,\n resourceScheme: opts.resourceScheme,\n tools,\n ...(opts.buildId ? { buildId: opts.buildId } : {}),\n ...(opts.metrics ? { metrics: opts.metrics } : {})\n });\n const transport = new StreamableHTTPServerTransport({\n sessionIdGenerator: () => id,\n // A client `DELETE /mcp` (graceful termination) fires this before the\n // transport closes itself. Bridge it to the SessionManager so the slot\n // and its engine are reclaimed immediately instead of squatting until\n // idle eviction. Only fires on the DELETE path — not from transport\n // `close()` during `destroy` — so there is no re-entrancy: `delete`\n // removes the map entry first, then runs `destroy` (a redundant,\n // idempotent second `transport.close()`).\n onsessionclosed: (sid) => {\n void sessionMgr.delete(sid).catch(() => {\n // Already gone (idle-evicted, or a duplicate close) — nothing to do.\n });\n }\n });\n await server.connect(transport);\n return { id, engine, server, transport, dispose };\n },\n destroy: async (s) => {\n try {\n await s.transport.close();\n } catch {\n // ignore — best-effort\n }\n try {\n await s.server.close();\n } catch {\n // ignore\n }\n // Call the session's dispose callback if provided (e.g. engine teardown).\n try {\n await s.dispose?.();\n } catch {\n // ignore\n }\n }\n });\n\n const app = express() as CodesignHttpServer;\n app.sessions = sessionMgr;\n\n if (opts.cors !== false) {\n app.use(cors(opts.cors));\n }\n if (opts.auth) {\n app.use(opts.auth);\n }\n app.use(express.json({ limit: '16mb' }));\n\n /** Narrow `code` out of an unknown thrown value without `as any`. */\n function errCode(e: unknown): string | undefined {\n return typeof e === 'object' && e !== null && 'code' in e\n ? ((e as { code?: unknown }).code as string | undefined)\n : undefined;\n }\n\n const handle = async (req: Request, res: Response): Promise<void> => {\n const sessionId = req.header('mcp-session-id');\n\n // Initialization (POST without session id) → spawn a new session.\n if (req.method === 'POST' && !sessionId) {\n const injected = opts.trustedSessionIdHeader\n ? req.header(opts.trustedSessionIdHeader)?.trim() || undefined\n : undefined;\n const newSessionId = injected ?? randomUUID();\n let workspaceId: string;\n try {\n workspaceId = resolveWorkspaceId(req, newSessionId);\n } catch (err) {\n if (err instanceof WorkspaceAuthError) {\n res.status(401).json({\n jsonrpc: '2.0',\n error: { code: -32002, message: err.message },\n id: null\n });\n return;\n }\n res.status(500).json({\n jsonrpc: '2.0',\n error: { code: -32603, message: 'internal error' },\n id: null\n });\n return;\n }\n let session: CodesignHttpSession;\n try {\n session = await sessionMgr.create({\n sessionId: newSessionId,\n workspaceId\n });\n } catch (err) {\n if (err instanceof SessionCapExceededError) {\n res\n .status(503)\n .set('Retry-After', '30')\n .json({\n jsonrpc: '2.0',\n error: { code: -32000, message: err.message },\n id: null\n });\n return;\n }\n if (errCode(err) === 'EINVAL') {\n res.status(400).json({\n jsonrpc: '2.0',\n error: {\n code: -32602,\n message:\n err instanceof Error ? err.message : 'invalid workspaceId'\n },\n id: null\n });\n return;\n }\n res.status(500).json({\n jsonrpc: '2.0',\n error: { code: -32603, message: 'internal error' },\n id: null\n });\n return;\n }\n await session.transport.handleRequest(req, res, req.body);\n return;\n }\n\n // Subsequent requests must carry a known session id.\n if (!sessionId) {\n res.status(400).json({\n jsonrpc: '2.0',\n error: { code: -32600, message: 'Mcp-Session-Id header required' },\n id: null\n });\n return;\n }\n const session = sessionMgr.get(sessionId);\n if (!session) {\n res.status(404).json({\n jsonrpc: '2.0',\n error: { code: -32001, message: `unknown session: ${sessionId}` },\n id: null\n });\n return;\n }\n sessionMgr.touch(sessionId);\n await session.transport.handleRequest(req, res, req.body);\n };\n\n if (opts.mountExtras) opts.mountExtras(app);\n\n app.post('/mcp', handle);\n app.get('/mcp', handle);\n app.delete('/mcp', handle);\n\n app.shutdown = async () => {\n await sessionMgr.destroyAll();\n };\n\n return app;\n}\n"]}
package/dist/server.d.ts CHANGED
@@ -24,8 +24,6 @@ export interface CodesignMcpToolRegistration {
24
24
  }>;
25
25
  }
26
26
  export interface CodesignMcpServerOptions {
27
- /** CreativeEngine instance, DI'd by caller. Pass-through to design tools. */
28
- engine: unknown;
29
27
  /**
30
28
  * Asset bundle. Keys are forward-slash paths under one of:
31
29
  * "docs/<doc>/SKILL.md" — entry point for handbook / api / guide
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AAenE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAE7D;;;;;GAKG;AACH,MAAM,WAAW,2BAA2B;IAC1C,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,yDAAyD;IACzD,WAAW,EAAE,OAAO,CAAC;IACrB,IAAI,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,OAAO,CAAC;QAC/B,OAAO,EAAE,KAAK,CACV;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,IAAI,EAAE,MAAM,CAAA;SAAE,GAC9B;YAAE,IAAI,EAAE,OAAO,CAAC;YAAC,IAAI,EAAE,MAAM,CAAC;YAAC,QAAQ,EAAE,MAAM,CAAA;SAAE,CACpD,CAAC;QACF,OAAO,CAAC,EAAE,OAAO,CAAC;KACnB,CAAC,CAAC;CACJ;AAED,MAAM,WAAW,wBAAwB;IACvC,6EAA6E;IAC7E,MAAM,EAAE,OAAO,CAAC;IAChB;;;;;;OAMG;IACH,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,qDAAqD;IACrD,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,mDAAmD;IACnD,KAAK,CAAC,EAAE,2BAA2B,EAAE,CAAC;IACtC,wEAAwE;IACxE,OAAO,CAAC,EAAE,eAAe,CAAC;IAC1B;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAKD,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,wBAAwB,GAC7B,MAAM,CA2LR"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AAenE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAE7D;;;;;GAKG;AACH,MAAM,WAAW,2BAA2B;IAC1C,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,yDAAyD;IACzD,WAAW,EAAE,OAAO,CAAC;IACrB,IAAI,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,OAAO,CAAC;QAC/B,OAAO,EAAE,KAAK,CACV;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,IAAI,EAAE,MAAM,CAAA;SAAE,GAC9B;YAAE,IAAI,EAAE,OAAO,CAAC;YAAC,IAAI,EAAE,MAAM,CAAC;YAAC,QAAQ,EAAE,MAAM,CAAA;SAAE,CACpD,CAAC;QACF,OAAO,CAAC,EAAE,OAAO,CAAC;KACnB,CAAC,CAAC;CACJ;AAED,MAAM,WAAW,wBAAwB;IACvC;;;;;;OAMG;IACH,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,qDAAqD;IACrD,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,mDAAmD;IACnD,KAAK,CAAC,EAAE,2BAA2B,EAAE,CAAC;IACtC,wEAAwE;IACxE,OAAO,CAAC,EAAE,eAAe,CAAC;IAC1B;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAKD,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,wBAAwB,GAC7B,MAAM,CA2LR"}
@@ -1 +1 @@
1
- {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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","sourcesContent":["import { Server } from '@modelcontextprotocol/sdk/server/index.js';\nimport {\n CallToolRequestSchema,\n ErrorCode,\n GetPromptRequestSchema,\n ListPromptsRequestSchema,\n ListResourcesRequestSchema,\n ListToolsRequestSchema,\n McpError,\n ReadResourceRequestSchema\n} from '@modelcontextprotocol/sdk/types.js';\nimport { rewriteSkillLinks } from './cross-ref.js';\nimport { parseFrontmatter } from './frontmatter.js';\nimport { buildSkillReadTools } from './skill-tools.js';\nimport { performance } from 'node:perf_hooks';\nimport type { MetricsRecorder } from './metrics/recorder.js';\n\n/**\n * One tool the server exposes via tools/list + tools/call. Caller-supplied so\n * the engine-bound implementations live near the surface that owns the engine\n * (the demo passes its existing `createDesignTools` output adapted to this\n * shape; an external bootstrap could pass server-side equivalents).\n */\nexport interface CodesignMcpToolRegistration {\n name: string;\n description?: string;\n /** JSON Schema describing the tool's input arguments. */\n inputSchema: unknown;\n call: (args: unknown) => Promise<{\n content: Array<\n | { type: 'text'; text: string }\n | { type: 'image'; data: string; mimeType: string }\n >;\n isError?: boolean;\n }>;\n}\n\nexport interface CodesignMcpServerOptions {\n /** CreativeEngine instance, DI'd by caller. Pass-through to design tools. */\n engine: unknown;\n /**\n * Asset bundle. Keys are forward-slash paths under one of:\n * \"docs/<doc>/SKILL.md\" — entry point for handbook / api / guide\n * \"docs/<doc>/<file>\" — sub-files reachable via the doc tools\n * \"prompts/<name>.md\" — slash-command prompts\n * Values are file contents.\n */\n assets: Record<string, string>;\n /** URI scheme for resources. Default: \"codesign\". */\n resourceScheme?: string;\n /** Tools to expose via tools/list + tools/call. */\n tools?: CodesignMcpToolRegistration[];\n /** Optional metrics sink. Absent → tool calls are not timed (no-op). */\n metrics?: MetricsRecorder;\n /**\n * Build identity reported as `serverInfo.version` on `initialize`. A client\n * (the deploy smoketest) can assert it to confirm it's talking to the\n * just-deployed build, not a stale container. Default `'dev'`.\n */\n buildId?: string;\n}\n\nconst DOC_FILE = /^docs\\/([^/]+)\\/(.+)$/;\nconst PROMPT_FILE = /^prompts\\/([^/]+)\\.md$/;\n\nexport function createCodesignMcpServer(\n opts: CodesignMcpServerOptions\n): Server {\n const scheme = opts.resourceScheme ?? 'codesign';\n const assets = opts.assets;\n\n // `instructions` is intentionally short — it tells the model what the\n // server is and STRONGLY directs it to call `codesign_read_codesign_system`\n // before its first design_exec. Skills are now first-class tools (not MCP\n // resources buried in an XML block), so discovery happens via tools/list\n // on every host without needing ReadMcpResourceTool support.\n //\n // Why short: Claude Code wraps MCP `instructions` in a <system-reminder>\n // and truncates long ones mid-stream. Keeping `instructions` under ~1.5 KB\n // ensures the whole pointer survives any sane truncation.\n //\n // The asset-generation hint is included only when an `asset_generate` tool\n // is actually registered (Local hides it without an API_KEY), so we never\n // advertise a tool the model can't see in tools/list.\n const hasAssetGenerate = (opts.tools ?? []).some(\n (t) => t.name === 'asset_generate'\n );\n const instructions = [\n 'This is the **codesign** MCP server. It hosts a CE.SDK CreativeEngine and a persistent design workspace (revision DAG) for vector design + print-quality PDF output.',\n '',\n '**Required first step** — before any `edit`, call `handbook` (no args). It teaches the mandatory `parent` argument, dirty-on-throw safety, rewind recovery via `list`/`history`, the typeface schema setFont requires, and the runtime quirks that bite first attempts. Skipping it produces broken output.',\n '',\n 'Reference docs, on demand:',\n '- `api({ path?, offset?, limit? })` — CE.SDK TypeScript signatures.',\n '- `guide({ path? })` — CE.SDK Vanilla JS reference docs / topic guides.',\n '',\n 'Design tools (after reading the handbook):',\n '- `edit({ parent, code, title?, note? })` — mutate the canvas; `parent` is REQUIRED (revision id, or null for a new design).',\n '- `preview({ blockId, revision? })` — render a block to an inline image.',\n '- `export({ target, format, revision, blockId })` — render a block to pdf/image and write it into the workspace.',\n '- `save({ revision, outPath? })` — write a self-contained `.zip` archive (scene + assets) of a revision into the workspace.',\n '',\n 'Workspace tools (for resuming + navigating across designs):',\n '- `list()` — every design in the workspace, newest first.',\n '- `history({ revision })` — walk back to the root; how you re-ground after a rewind.',\n '- `inspect({ revision })` — read one revision in full, including the code that produced it (patterns, not block ids).',\n '- `rename({ revision, title })` — rename a design.',\n '- `open_editor({ revision? })` — URL the human can open to view the design.',\n '- `asset_search({ sourceId?, query? })` — browse asset sources; apply results in `edit`.'\n ]\n .concat(\n hasAssetGenerate\n ? [\n '',\n 'Asset generation:',\n \"- `asset_generate({ kind: 'image', prompt, width?, height? })` — opt-in (Local needs API_KEY); Hosted is cost-gated.\"\n ]\n : []\n )\n .join('\\n');\n\n // Build the resource map up-front: URI → rewritten markdown body. Cross-refs\n // inside doc content are rewritten to absolute MCP URIs so the model never\n // has to resolve a relative path against a doc dir.\n const rewrittenContent = new Map<string, string>();\n for (const [key, content] of Object.entries(assets)) {\n const m = key.match(DOC_FILE);\n if (!m) continue;\n const [, doc, rest] = m;\n const uri = `${scheme}://docs/${doc}/${rest}`;\n rewrittenContent.set(uri, rewriteSkillLinks(content, doc, scheme));\n }\n\n // Index prompts by name (frontmatter `name`, falling back to filename).\n const prompts = new Map<string, { description: string; body: string }>();\n for (const [key, content] of Object.entries(assets)) {\n const m = key.match(PROMPT_FILE);\n if (!m) continue;\n const front = parseFrontmatter(content);\n const name = front.name ?? m[1];\n prompts.set(name, {\n description: front.description ?? '',\n body: front.body\n });\n }\n\n const server = new Server(\n { name: 'codesign', version: opts.buildId ?? 'dev' },\n {\n capabilities: { resources: {}, prompts: {}, tools: {} },\n instructions\n }\n );\n\n // `resources/list` advertises only the per-skill SKILL.md (the entry\n // point), not the 392 nested cesdk-guide files. Sub-files stay readable\n // via `resources/read` against any cached URI, but skill content discovery\n // happens through the codesign_read_* tools — those are the universal\n // surface across MCP hosts.\n server.setRequestHandler(ListResourcesRequestSchema, async () => ({\n resources: Array.from(rewrittenContent.keys())\n .filter((uri) => uri.endsWith('/SKILL.md'))\n .map((uri) => ({\n uri,\n mimeType: 'text/markdown',\n name: uri.replace(`${scheme}://`, '')\n }))\n }));\n\n server.setRequestHandler(ReadResourceRequestSchema, async (req) => {\n const text = rewrittenContent.get(req.params.uri);\n if (text === undefined) {\n throw new McpError(\n ErrorCode.InvalidParams,\n `Unknown resource: ${req.params.uri}`\n );\n }\n return {\n contents: [{ uri: req.params.uri, mimeType: 'text/markdown', text }]\n };\n });\n\n server.setRequestHandler(ListPromptsRequestSchema, async () => ({\n prompts: Array.from(prompts.entries()).map(([name, p]) => ({\n name,\n description: p.description\n }))\n }));\n\n server.setRequestHandler(GetPromptRequestSchema, async (req) => {\n const p = prompts.get(req.params.name);\n if (!p) {\n throw new McpError(\n ErrorCode.InvalidParams,\n `Unknown prompt: ${req.params.name}`\n );\n }\n return {\n messages: [\n {\n role: 'user',\n content: { type: 'text', text: p.body }\n }\n ]\n };\n });\n\n // Tools registered with the server: caller-supplied design tools + the\n // auto-generated skill-read tools (one per skill in the asset bundle).\n // The skill tools always come first so Level 1 wording in the design tool\n // descriptions (\"call codesign_read_codesign_system first\") points at\n // something the model already sees in tools/list.\n const tools = new Map<string, CodesignMcpToolRegistration>();\n for (const t of buildSkillReadTools(assets)) tools.set(t.name, t);\n for (const t of opts.tools ?? []) tools.set(t.name, t);\n\n server.setRequestHandler(ListToolsRequestSchema, async () => ({\n tools: Array.from(tools.values()).map((t) => ({\n name: t.name,\n description: t.description,\n inputSchema: t.inputSchema as never\n }))\n }));\n\n server.setRequestHandler(CallToolRequestSchema, async (req) => {\n const t = tools.get(req.params.name);\n if (!t) {\n throw new McpError(\n ErrorCode.InvalidParams,\n `Unknown tool: ${req.params.name}`\n );\n }\n // No recorder → byte-for-byte the original behaviour (the off-switch).\n if (!opts.metrics) return await t.call(req.params.arguments ?? {});\n\n const startedAt = performance.now();\n let ok = true;\n try {\n const res = await t.call(req.params.arguments ?? {});\n ok = res.isError !== true;\n return res;\n } catch (err) {\n ok = false;\n throw err;\n } finally {\n opts.metrics.record({\n tool: req.params.name,\n durationMs: performance.now() - startedAt,\n ok\n });\n }\n });\n\n return server;\n}\n"]}
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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","sourcesContent":["import { Server } from '@modelcontextprotocol/sdk/server/index.js';\nimport {\n CallToolRequestSchema,\n ErrorCode,\n GetPromptRequestSchema,\n ListPromptsRequestSchema,\n ListResourcesRequestSchema,\n ListToolsRequestSchema,\n McpError,\n ReadResourceRequestSchema\n} from '@modelcontextprotocol/sdk/types.js';\nimport { rewriteSkillLinks } from './cross-ref.js';\nimport { parseFrontmatter } from './frontmatter.js';\nimport { buildSkillReadTools } from './skill-tools.js';\nimport { performance } from 'node:perf_hooks';\nimport type { MetricsRecorder } from './metrics/recorder.js';\n\n/**\n * One tool the server exposes via tools/list + tools/call. Caller-supplied so\n * the engine-bound implementations live near the surface that owns the engine\n * (the demo passes its existing `createDesignTools` output adapted to this\n * shape; an external bootstrap could pass server-side equivalents).\n */\nexport interface CodesignMcpToolRegistration {\n name: string;\n description?: string;\n /** JSON Schema describing the tool's input arguments. */\n inputSchema: unknown;\n call: (args: unknown) => Promise<{\n content: Array<\n | { type: 'text'; text: string }\n | { type: 'image'; data: string; mimeType: string }\n >;\n isError?: boolean;\n }>;\n}\n\nexport interface CodesignMcpServerOptions {\n /**\n * Asset bundle. Keys are forward-slash paths under one of:\n * \"docs/<doc>/SKILL.md\" — entry point for handbook / api / guide\n * \"docs/<doc>/<file>\" — sub-files reachable via the doc tools\n * \"prompts/<name>.md\" — slash-command prompts\n * Values are file contents.\n */\n assets: Record<string, string>;\n /** URI scheme for resources. Default: \"codesign\". */\n resourceScheme?: string;\n /** Tools to expose via tools/list + tools/call. */\n tools?: CodesignMcpToolRegistration[];\n /** Optional metrics sink. Absent → tool calls are not timed (no-op). */\n metrics?: MetricsRecorder;\n /**\n * Build identity reported as `serverInfo.version` on `initialize`. A client\n * (the deploy smoketest) can assert it to confirm it's talking to the\n * just-deployed build, not a stale container. Default `'dev'`.\n */\n buildId?: string;\n}\n\nconst DOC_FILE = /^docs\\/([^/]+)\\/(.+)$/;\nconst PROMPT_FILE = /^prompts\\/([^/]+)\\.md$/;\n\nexport function createCodesignMcpServer(\n opts: CodesignMcpServerOptions\n): Server {\n const scheme = opts.resourceScheme ?? 'codesign';\n const assets = opts.assets;\n\n // `instructions` is intentionally short — it tells the model what the\n // server is and STRONGLY directs it to call `codesign_read_codesign_system`\n // before its first design_exec. Skills are now first-class tools (not MCP\n // resources buried in an XML block), so discovery happens via tools/list\n // on every host without needing ReadMcpResourceTool support.\n //\n // Why short: Claude Code wraps MCP `instructions` in a <system-reminder>\n // and truncates long ones mid-stream. Keeping `instructions` under ~1.5 KB\n // ensures the whole pointer survives any sane truncation.\n //\n // The asset-generation hint is included only when an `asset_generate` tool\n // is actually registered (Local hides it without an API_KEY), so we never\n // advertise a tool the model can't see in tools/list.\n const hasAssetGenerate = (opts.tools ?? []).some(\n (t) => t.name === 'asset_generate'\n );\n const instructions = [\n 'This is the **codesign** MCP server. It hosts a CE.SDK CreativeEngine and a persistent design workspace (revision DAG) for vector design + print-quality PDF output.',\n '',\n '**Required first step** — before any `edit`, call `handbook` (no args). It teaches the mandatory `parent` argument, dirty-on-throw safety, rewind recovery via `list`/`history`, the typeface schema setFont requires, and the runtime quirks that bite first attempts. Skipping it produces broken output.',\n '',\n 'Reference docs, on demand:',\n '- `api({ path?, offset?, limit? })` — CE.SDK TypeScript signatures.',\n '- `guide({ path? })` — CE.SDK Vanilla JS reference docs / topic guides.',\n '',\n 'Design tools (after reading the handbook):',\n '- `edit({ parent, code, title?, note? })` — mutate the canvas; `parent` is REQUIRED (revision id, or null for a new design).',\n '- `preview({ blockId, revision? })` — render a block to an inline image.',\n '- `export({ target, format, revision, blockId })` — render a block to pdf/image and write it into the workspace.',\n '- `save({ revision, outPath? })` — write a self-contained `.zip` archive (scene + assets) of a revision into the workspace.',\n '',\n 'Workspace tools (for resuming + navigating across designs):',\n '- `list()` — every design in the workspace, newest first.',\n '- `history({ revision })` — walk back to the root; how you re-ground after a rewind.',\n '- `inspect({ revision })` — read one revision in full, including the code that produced it (patterns, not block ids).',\n '- `rename({ revision, title })` — rename a design.',\n '- `open_editor({ revision? })` — URL the human can open to view the design.',\n '- `asset_search({ sourceId?, query? })` — browse asset sources; apply results in `edit`.'\n ]\n .concat(\n hasAssetGenerate\n ? [\n '',\n 'Asset generation:',\n \"- `asset_generate({ kind: 'image', prompt, width?, height? })` — opt-in (Local needs API_KEY); Hosted is cost-gated.\"\n ]\n : []\n )\n .join('\\n');\n\n // Build the resource map up-front: URI → rewritten markdown body. Cross-refs\n // inside doc content are rewritten to absolute MCP URIs so the model never\n // has to resolve a relative path against a doc dir.\n const rewrittenContent = new Map<string, string>();\n for (const [key, content] of Object.entries(assets)) {\n const m = key.match(DOC_FILE);\n if (!m) continue;\n const [, doc, rest] = m;\n const uri = `${scheme}://docs/${doc}/${rest}`;\n rewrittenContent.set(uri, rewriteSkillLinks(content, doc, scheme));\n }\n\n // Index prompts by name (frontmatter `name`, falling back to filename).\n const prompts = new Map<string, { description: string; body: string }>();\n for (const [key, content] of Object.entries(assets)) {\n const m = key.match(PROMPT_FILE);\n if (!m) continue;\n const front = parseFrontmatter(content);\n const name = front.name ?? m[1];\n prompts.set(name, {\n description: front.description ?? '',\n body: front.body\n });\n }\n\n const server = new Server(\n { name: 'codesign', version: opts.buildId ?? 'dev' },\n {\n capabilities: { resources: {}, prompts: {}, tools: {} },\n instructions\n }\n );\n\n // `resources/list` advertises only the per-skill SKILL.md (the entry\n // point), not the 392 nested cesdk-guide files. Sub-files stay readable\n // via `resources/read` against any cached URI, but skill content discovery\n // happens through the codesign_read_* tools — those are the universal\n // surface across MCP hosts.\n server.setRequestHandler(ListResourcesRequestSchema, async () => ({\n resources: Array.from(rewrittenContent.keys())\n .filter((uri) => uri.endsWith('/SKILL.md'))\n .map((uri) => ({\n uri,\n mimeType: 'text/markdown',\n name: uri.replace(`${scheme}://`, '')\n }))\n }));\n\n server.setRequestHandler(ReadResourceRequestSchema, async (req) => {\n const text = rewrittenContent.get(req.params.uri);\n if (text === undefined) {\n throw new McpError(\n ErrorCode.InvalidParams,\n `Unknown resource: ${req.params.uri}`\n );\n }\n return {\n contents: [{ uri: req.params.uri, mimeType: 'text/markdown', text }]\n };\n });\n\n server.setRequestHandler(ListPromptsRequestSchema, async () => ({\n prompts: Array.from(prompts.entries()).map(([name, p]) => ({\n name,\n description: p.description\n }))\n }));\n\n server.setRequestHandler(GetPromptRequestSchema, async (req) => {\n const p = prompts.get(req.params.name);\n if (!p) {\n throw new McpError(\n ErrorCode.InvalidParams,\n `Unknown prompt: ${req.params.name}`\n );\n }\n return {\n messages: [\n {\n role: 'user',\n content: { type: 'text', text: p.body }\n }\n ]\n };\n });\n\n // Tools registered with the server: caller-supplied design tools + the\n // auto-generated skill-read tools (one per skill in the asset bundle).\n // The skill tools always come first so Level 1 wording in the design tool\n // descriptions (\"call codesign_read_codesign_system first\") points at\n // something the model already sees in tools/list.\n const tools = new Map<string, CodesignMcpToolRegistration>();\n for (const t of buildSkillReadTools(assets)) tools.set(t.name, t);\n for (const t of opts.tools ?? []) tools.set(t.name, t);\n\n server.setRequestHandler(ListToolsRequestSchema, async () => ({\n tools: Array.from(tools.values()).map((t) => ({\n name: t.name,\n description: t.description,\n inputSchema: t.inputSchema as never\n }))\n }));\n\n server.setRequestHandler(CallToolRequestSchema, async (req) => {\n const t = tools.get(req.params.name);\n if (!t) {\n throw new McpError(\n ErrorCode.InvalidParams,\n `Unknown tool: ${req.params.name}`\n );\n }\n // No recorder → byte-for-byte the original behaviour (the off-switch).\n if (!opts.metrics) return await t.call(req.params.arguments ?? {});\n\n const startedAt = performance.now();\n let ok = true;\n try {\n const res = await t.call(req.params.arguments ?? {});\n ok = res.isError !== true;\n return res;\n } catch (err) {\n ok = false;\n throw err;\n } finally {\n opts.metrics.record({\n tool: req.params.name,\n durationMs: performance.now() - startedAt,\n ok\n });\n }\n });\n\n return server;\n}\n"]}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=redirect-console.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redirect-console.test.d.ts","sourceRoot":"","sources":["../../../../test/cli/redirect-console.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,47 @@
1
+ import { afterEach, describe, expect, it, vi } from 'vitest';
2
+ import { redirectConsoleToStderr } from '../../src/cli/redirect-console.js';
3
+ describe('redirectConsoleToStderr', () => {
4
+ // Snapshot the real console methods so each test starts from a clean slate
5
+ // and the redirect can't leak into later tests.
6
+ const original = {
7
+ log: console.log,
8
+ info: console.info,
9
+ warn: console.warn,
10
+ debug: console.debug,
11
+ trace: console.trace
12
+ };
13
+ // Spy on the fd-1/fd-2 sinks. Inference (rather than an explicit annotation)
14
+ // keeps vitest's overloaded `write` signature happy.
15
+ function spyOnStreams() {
16
+ return {
17
+ stdout: vi.spyOn(process.stdout, 'write').mockReturnValue(true),
18
+ stderr: vi.spyOn(process.stderr, 'write').mockReturnValue(true)
19
+ };
20
+ }
21
+ afterEach(() => {
22
+ Object.assign(console, original);
23
+ vi.restoreAllMocks();
24
+ });
25
+ it('routes the engine log line to stderr, never stdout', () => {
26
+ const { stdout, stderr } = spyOnStreams();
27
+ redirectConsoleToStderr();
28
+ console.log('[UBQ] Engine has been initialized.');
29
+ expect(stdout).not.toHaveBeenCalled();
30
+ expect(stderr).toHaveBeenCalledWith('[UBQ] Engine has been initialized.\n');
31
+ });
32
+ it.each(['info', 'warn', 'debug', 'trace'])('redirects console.%s to stderr', (method) => {
33
+ const { stdout, stderr } = spyOnStreams();
34
+ redirectConsoleToStderr();
35
+ console[method](`[CreativeEngine] via ${method}`);
36
+ expect(stdout).not.toHaveBeenCalled();
37
+ expect(stderr).toHaveBeenCalledWith(`[CreativeEngine] via ${method}\n`);
38
+ });
39
+ it('serializes non-string args instead of hitting stdout', () => {
40
+ const { stdout, stderr } = spyOnStreams();
41
+ redirectConsoleToStderr();
42
+ console.log('boot', { revision: 'dc8dd08ac3' }, 42);
43
+ expect(stdout).not.toHaveBeenCalled();
44
+ expect(stderr).toHaveBeenCalledWith('boot {"revision":"dc8dd08ac3"} 42\n');
45
+ });
46
+ });
47
+ //# sourceMappingURL=redirect-console.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redirect-console.test.js","sourceRoot":"","sources":["../../../../test/cli/redirect-console.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAC7D,OAAO,EAAE,uBAAuB,EAAE,MAAM,mCAAmC,CAAC;AAE5E,QAAQ,CAAC,yBAAyB,EAAE,GAAG,EAAE;IACvC,2EAA2E;IAC3E,gDAAgD;IAChD,MAAM,QAAQ,GAAG;QACf,GAAG,EAAE,OAAO,CAAC,GAAG;QAChB,IAAI,EAAE,OAAO,CAAC,IAAI;QAClB,IAAI,EAAE,OAAO,CAAC,IAAI;QAClB,KAAK,EAAE,OAAO,CAAC,KAAK;QACpB,KAAK,EAAE,OAAO,CAAC,KAAK;KACrB,CAAC;IAEF,6EAA6E;IAC7E,qDAAqD;IACrD,SAAS,YAAY;QACnB,OAAO;YACL,MAAM,EAAE,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,eAAe,CAAC,IAAI,CAAC;YAC/D,MAAM,EAAE,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,eAAe,CAAC,IAAI,CAAC;SAChE,CAAC;IACJ,CAAC;IAED,SAAS,CAAC,GAAG,EAAE;QACb,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;QACjC,EAAE,CAAC,eAAe,EAAE,CAAC;IACvB,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oDAAoD,EAAE,GAAG,EAAE;QAC5D,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,YAAY,EAAE,CAAC;QAC1C,uBAAuB,EAAE,CAAC;QAE1B,OAAO,CAAC,GAAG,CAAC,oCAAoC,CAAC,CAAC;QAElD,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,gBAAgB,EAAE,CAAC;QACtC,MAAM,CAAC,MAAM,CAAC,CAAC,oBAAoB,CAAC,sCAAsC,CAAC,CAAC;IAC9E,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,CAAU,CAAC,CAClD,gCAAgC,EAChC,CAAC,MAAM,EAAE,EAAE;QACT,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,YAAY,EAAE,CAAC;QAC1C,uBAAuB,EAAE,CAAC;QAE1B,OAAO,CAAC,MAAM,CAAC,CAAC,wBAAwB,MAAM,EAAE,CAAC,CAAC;QAElD,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,gBAAgB,EAAE,CAAC;QACtC,MAAM,CAAC,MAAM,CAAC,CAAC,oBAAoB,CAAC,wBAAwB,MAAM,IAAI,CAAC,CAAC;IAC1E,CAAC,CACF,CAAC;IAEF,EAAE,CAAC,sDAAsD,EAAE,GAAG,EAAE;QAC9D,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,YAAY,EAAE,CAAC;QAC1C,uBAAuB,EAAE,CAAC;QAE1B,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,EAAE,QAAQ,EAAE,YAAY,EAAE,EAAE,EAAE,CAAC,CAAC;QAEpD,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,gBAAgB,EAAE,CAAC;QACtC,MAAM,CAAC,MAAM,CAAC,CAAC,oBAAoB,CAAC,qCAAqC,CAAC,CAAC;IAC7E,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC","sourcesContent":["import { afterEach, describe, expect, it, vi } from 'vitest';\nimport { redirectConsoleToStderr } from '../../src/cli/redirect-console.js';\n\ndescribe('redirectConsoleToStderr', () => {\n // Snapshot the real console methods so each test starts from a clean slate\n // and the redirect can't leak into later tests.\n const original = {\n log: console.log,\n info: console.info,\n warn: console.warn,\n debug: console.debug,\n trace: console.trace\n };\n\n // Spy on the fd-1/fd-2 sinks. Inference (rather than an explicit annotation)\n // keeps vitest's overloaded `write` signature happy.\n function spyOnStreams() {\n return {\n stdout: vi.spyOn(process.stdout, 'write').mockReturnValue(true),\n stderr: vi.spyOn(process.stderr, 'write').mockReturnValue(true)\n };\n }\n\n afterEach(() => {\n Object.assign(console, original);\n vi.restoreAllMocks();\n });\n\n it('routes the engine log line to stderr, never stdout', () => {\n const { stdout, stderr } = spyOnStreams();\n redirectConsoleToStderr();\n\n console.log('[UBQ] Engine has been initialized.');\n\n expect(stdout).not.toHaveBeenCalled();\n expect(stderr).toHaveBeenCalledWith('[UBQ] Engine has been initialized.\\n');\n });\n\n it.each(['info', 'warn', 'debug', 'trace'] as const)(\n 'redirects console.%s to stderr',\n (method) => {\n const { stdout, stderr } = spyOnStreams();\n redirectConsoleToStderr();\n\n console[method](`[CreativeEngine] via ${method}`);\n\n expect(stdout).not.toHaveBeenCalled();\n expect(stderr).toHaveBeenCalledWith(`[CreativeEngine] via ${method}\\n`);\n }\n );\n\n it('serializes non-string args instead of hitting stdout', () => {\n const { stdout, stderr } = spyOnStreams();\n redirectConsoleToStderr();\n\n console.log('boot', { revision: 'dc8dd08ac3' }, 42);\n\n expect(stdout).not.toHaveBeenCalled();\n expect(stderr).toHaveBeenCalledWith('boot {\"revision\":\"dc8dd08ac3\"} 42\\n');\n });\n});\n"]}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=resolve-license.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve-license.test.d.ts","sourceRoot":"","sources":["../../../../test/cli/resolve-license.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,20 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { resolveLicense } from '../../src/cli/resolve-license.js';
3
+ describe('resolveLicense', () => {
4
+ it('prefers CESDK_LICENSE over the trial', () => {
5
+ expect(resolveLicense({ CESDK_LICENSE: 'real' }, 'trial')).toBe('real');
6
+ });
7
+ it('falls back to the trial when CESDK_LICENSE is unset', () => {
8
+ expect(resolveLicense({}, 'trial')).toBe('trial');
9
+ });
10
+ it('treats a blank CESDK_LICENSE as unset and uses the trial', () => {
11
+ expect(resolveLicense({ CESDK_LICENSE: ' ' }, 'trial')).toBe('trial');
12
+ });
13
+ it('returns undefined when neither is present', () => {
14
+ expect(resolveLicense({}, '')).toBeUndefined();
15
+ });
16
+ it('trims surrounding whitespace from the env license', () => {
17
+ expect(resolveLicense({ CESDK_LICENSE: ' real ' }, '')).toBe('real');
18
+ });
19
+ });
20
+ //# sourceMappingURL=resolve-license.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve-license.test.js","sourceRoot":"","sources":["../../../../test/cli/resolve-license.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,cAAc,EAAE,MAAM,kCAAkC,CAAC;AAElE,QAAQ,CAAC,gBAAgB,EAAE,GAAG,EAAE;IAC9B,EAAE,CAAC,sCAAsC,EAAE,GAAG,EAAE;QAC9C,MAAM,CAAC,cAAc,CAAC,EAAE,aAAa,EAAE,MAAM,EAAE,EAAE,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAC1E,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qDAAqD,EAAE,GAAG,EAAE;QAC7D,MAAM,CAAC,cAAc,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACpD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,0DAA0D,EAAE,GAAG,EAAE;QAClE,MAAM,CAAC,cAAc,CAAC,EAAE,aAAa,EAAE,KAAK,EAAE,EAAE,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAC1E,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,2CAA2C,EAAE,GAAG,EAAE;QACnD,MAAM,CAAC,cAAc,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC;IACjD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,mDAAmD,EAAE,GAAG,EAAE;QAC3D,MAAM,CAAC,cAAc,CAAC,EAAE,aAAa,EAAE,UAAU,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACzE,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC","sourcesContent":["import { describe, expect, it } from 'vitest';\nimport { resolveLicense } from '../../src/cli/resolve-license.js';\n\ndescribe('resolveLicense', () => {\n it('prefers CESDK_LICENSE over the trial', () => {\n expect(resolveLicense({ CESDK_LICENSE: 'real' }, 'trial')).toBe('real');\n });\n\n it('falls back to the trial when CESDK_LICENSE is unset', () => {\n expect(resolveLicense({}, 'trial')).toBe('trial');\n });\n\n it('treats a blank CESDK_LICENSE as unset and uses the trial', () => {\n expect(resolveLicense({ CESDK_LICENSE: ' ' }, 'trial')).toBe('trial');\n });\n\n it('returns undefined when neither is present', () => {\n expect(resolveLicense({}, '')).toBeUndefined();\n });\n\n it('trims surrounding whitespace from the env license', () => {\n expect(resolveLicense({ CESDK_LICENSE: ' real ' }, '')).toBe('real');\n });\n});\n"]}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=stdio-license.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stdio-license.test.d.ts","sourceRoot":"","sources":["../../../../test/cli/stdio-license.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,19 @@
1
+ import { spawnSync } from 'node:child_process';
2
+ import path from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { describe, expect, it } from 'vitest';
5
+ const here = path.dirname(fileURLToPath(import.meta.url));
6
+ const CLI = path.resolve(here, '..', '..', 'dist', 'cli.js');
7
+ describe('codesign-mcp stdio license resolution', () => {
8
+ it('exits 1 with a helpful message when no license and no bundled trial', () => {
9
+ const r = spawnSync('node', [CLI, 'stdio'], {
10
+ encoding: 'utf8',
11
+ // No CESDK_LICENSE; dev build has an empty trial → must refuse to start.
12
+ env: { PATH: process.env.PATH ?? '' },
13
+ timeout: 5000
14
+ });
15
+ expect(r.status).toBe(1);
16
+ expect(r.stderr).toContain('no CESDK_LICENSE set and no bundled trial');
17
+ });
18
+ });
19
+ //# sourceMappingURL=stdio-license.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stdio-license.test.js","sourceRoot":"","sources":["../../../../test/cli/stdio-license.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC/C,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAE9C,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAC1D,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC;AAE7D,QAAQ,CAAC,uCAAuC,EAAE,GAAG,EAAE;IACrD,EAAE,CAAC,qEAAqE,EAAE,GAAG,EAAE;QAC7E,MAAM,CAAC,GAAG,SAAS,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,OAAO,CAAC,EAAE;YAC1C,QAAQ,EAAE,MAAM;YAChB,yEAAyE;YACzE,GAAG,EAAE,EAAE,IAAI,EAAE,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,EAAE,EAAE;YACrC,OAAO,EAAE,IAAI;SACd,CAAC,CAAC;QACH,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACzB,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,2CAA2C,CAAC,CAAC;IAC1E,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC","sourcesContent":["import { spawnSync } from 'node:child_process';\nimport path from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { describe, expect, it } from 'vitest';\n\nconst here = path.dirname(fileURLToPath(import.meta.url));\nconst CLI = path.resolve(here, '..', '..', 'dist', 'cli.js');\n\ndescribe('codesign-mcp stdio license resolution', () => {\n it('exits 1 with a helpful message when no license and no bundled trial', () => {\n const r = spawnSync('node', [CLI, 'stdio'], {\n encoding: 'utf8',\n // No CESDK_LICENSE; dev build has an empty trial → must refuse to start.\n env: { PATH: process.env.PATH ?? '' },\n timeout: 5000\n });\n expect(r.status).toBe(1);\n expect(r.stderr).toContain('no CESDK_LICENSE set and no bundled trial');\n });\n});\n"]}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=config.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.test.d.ts","sourceRoot":"","sources":["../../../test/config.test.ts"],"names":[],"mappings":""}