upfynai-code 3.0.3 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (243) hide show
  1. package/README.md +66 -91
  2. package/bin/cli.js +191 -0
  3. package/{client/dist → dist/client}/api-docs.html +838 -838
  4. package/{client/dist/assets/AppContent-Bvg0CPCO.js → dist/client/assets/AppContent-BofJquUs.js} +43 -43
  5. package/dist/client/assets/BrowserPanel-CSvD4jOX.js +2 -0
  6. package/dist/client/assets/CanvasFullScreen-onRfarpc.js +1 -0
  7. package/dist/client/assets/CanvasWorkspace-DvGKdL-k.js +259 -0
  8. package/dist/client/assets/DashboardPanel-DqAHbXDO.js +1 -0
  9. package/dist/client/assets/FileTree-BE0h-9M9.js +1 -0
  10. package/{client/dist/assets/GitPanel-RtyZUIWS.js → dist/client/assets/GitPanel-DdeJ0bp5.js} +2 -2
  11. package/{client/dist/assets/LoginModal-BWep8a6g.js → dist/client/assets/LoginModal-BP0pCTrH.js} +3 -3
  12. package/{client/dist/assets/MarkdownPreview-DHmk3qzu.js → dist/client/assets/MarkdownPreview-CESjI261.js} +1 -1
  13. package/dist/client/assets/MermaidBlock-D0rfEhrT.js +2 -0
  14. package/dist/client/assets/Onboarding-B2zQy-_6.js +1 -0
  15. package/dist/client/assets/SetupForm-Be7-WBe-.js +1 -0
  16. package/dist/client/assets/WorkflowsPanel-CusLbVJ6.js +1 -0
  17. package/{client/dist/assets/index-C5ptjuTl.js → dist/client/assets/index-BQy15irW.js} +25 -25
  18. package/dist/client/assets/index-CS0fDqEC.js +1 -0
  19. package/dist/client/assets/index-DYLSCCCp.css +1 -0
  20. package/dist/client/assets/vendor-canvas-QWTduIvM.js +23 -0
  21. package/{client/dist/assets/vendor-codemirror-CbtmxxaB.js → dist/client/assets/vendor-codemirror-D2ALgpaX.js} +1 -1
  22. package/{client/dist/assets/vendor-icons-BaD0x9SL.js → dist/client/assets/vendor-icons-kix3Gb31.js} +178 -138
  23. package/{client/dist/assets/vendor-mermaid-CH7SGc99.js → dist/client/assets/vendor-mermaid-CS3J4_Bz.js} +329 -326
  24. package/{client/dist/assets/vendor-syntax-DuHI9Ok6.js → dist/client/assets/vendor-syntax-LS_Nt30I.js} +1 -1
  25. package/{client/dist → dist/client}/clear-cache.html +85 -85
  26. package/dist/client/favicon.png +0 -0
  27. package/dist/client/favicon.svg +15 -0
  28. package/{client/dist → dist/client}/index.html +17 -17
  29. package/{client/dist → dist/client}/manifest.json +15 -15
  30. package/{client/dist → dist/client}/mcp-docs.html +108 -108
  31. package/{client/dist → dist/client}/offline.html +84 -84
  32. package/{client/dist → dist/client}/sw.js +82 -82
  33. package/package.json +55 -104
  34. package/scripts/postinstall.js +9 -0
  35. package/scripts/prepublish.js +77 -0
  36. package/src/animation.js +228 -0
  37. package/src/auth.js +142 -0
  38. package/src/config.js +40 -0
  39. package/src/connect.js +416 -0
  40. package/src/launch.js +81 -0
  41. package/src/mcp.js +57 -0
  42. package/src/permissions.js +140 -0
  43. package/src/persistent-shell.js +261 -0
  44. package/src/server.js +54 -0
  45. package/client/dist/assets/CanvasFullScreen-BdiJ35aq.js +0 -1
  46. package/client/dist/assets/CanvasWorkspace-Bk9R9_e0.js +0 -163
  47. package/client/dist/assets/DashboardPanel-CblJfTGi.js +0 -1
  48. package/client/dist/assets/FileTree-BDUnBheV.js +0 -1
  49. package/client/dist/assets/MermaidBlock-BuBc_G-F.js +0 -2
  50. package/client/dist/assets/Onboarding-Drnlt75a.js +0 -1
  51. package/client/dist/assets/SetupForm-CtCKitZG.js +0 -1
  52. package/client/dist/assets/WorkflowsPanel-B2mIXDvD.js +0 -1
  53. package/client/dist/assets/index-BFuqS0tY.css +0 -1
  54. package/client/dist/assets/vendor-canvas-D39yWul6.js +0 -49
  55. package/client/dist/favicon.png +0 -0
  56. package/client/dist/favicon.svg +0 -5
  57. package/commands/upfynai-connect.md +0 -59
  58. package/commands/upfynai-disconnect.md +0 -31
  59. package/commands/upfynai-doctor.md +0 -99
  60. package/commands/upfynai-export.md +0 -49
  61. package/commands/upfynai-local.md +0 -82
  62. package/commands/upfynai-status.md +0 -75
  63. package/commands/upfynai-stop.md +0 -49
  64. package/commands/upfynai-uninstall.md +0 -58
  65. package/commands/upfynai.md +0 -69
  66. package/scripts/build-client.js +0 -17
  67. package/scripts/fix-node-pty.js +0 -67
  68. package/scripts/install-commands.js +0 -78
  69. package/server/agent-loop.js +0 -242
  70. package/server/auto-compact.js +0 -99
  71. package/server/claude-sdk.js +0 -797
  72. package/server/cli-ui.js +0 -798
  73. package/server/cli.js +0 -751
  74. package/server/constants/config.js +0 -31
  75. package/server/cursor-cli.js +0 -270
  76. package/server/database/auth.db +0 -0
  77. package/server/database/db.js +0 -1451
  78. package/server/database/init.sql +0 -70
  79. package/server/index.js +0 -3814
  80. package/server/load-env.js +0 -26
  81. package/server/mcp-server.js +0 -621
  82. package/server/middleware/auth.js +0 -181
  83. package/server/middleware/relayHelpers.js +0 -44
  84. package/server/middleware/sandboxRouter.js +0 -174
  85. package/server/openai-codex.js +0 -403
  86. package/server/openrouter.js +0 -137
  87. package/server/projects.js +0 -1807
  88. package/server/provider-factory.js +0 -174
  89. package/server/relay-client.js +0 -390
  90. package/server/routes/agent.js +0 -1234
  91. package/server/routes/auth.js +0 -559
  92. package/server/routes/canvas.js +0 -53
  93. package/server/routes/cli-auth.js +0 -263
  94. package/server/routes/codex.js +0 -396
  95. package/server/routes/commands.js +0 -707
  96. package/server/routes/composio.js +0 -176
  97. package/server/routes/cursor.js +0 -770
  98. package/server/routes/dashboard.js +0 -295
  99. package/server/routes/git.js +0 -1208
  100. package/server/routes/keys.js +0 -34
  101. package/server/routes/mcp-utils.js +0 -48
  102. package/server/routes/mcp.js +0 -661
  103. package/server/routes/payments.js +0 -227
  104. package/server/routes/projects.js +0 -655
  105. package/server/routes/sessions.js +0 -146
  106. package/server/routes/settings.js +0 -261
  107. package/server/routes/taskmaster.js +0 -1928
  108. package/server/routes/user.js +0 -106
  109. package/server/routes/vapi-chat.js +0 -624
  110. package/server/routes/voice.js +0 -235
  111. package/server/routes/webhooks.js +0 -166
  112. package/server/routes/workflows.js +0 -312
  113. package/server/sandbox.js +0 -120
  114. package/server/services/composio.js +0 -204
  115. package/server/services/sessionRegistry.js +0 -139
  116. package/server/services/whisperService.js +0 -84
  117. package/server/services/workflowScheduler.js +0 -211
  118. package/server/tests/relay-flow.test.js +0 -570
  119. package/server/tests/sessions.test.js +0 -259
  120. package/server/utils/commandParser.js +0 -303
  121. package/server/utils/email.js +0 -66
  122. package/server/utils/gitConfig.js +0 -24
  123. package/server/utils/mcp-detector.js +0 -198
  124. package/server/utils/taskmaster-websocket.js +0 -129
  125. package/shared/integrationCatalog.d.ts +0 -12
  126. package/shared/integrationCatalog.js +0 -172
  127. package/shared/modelConstants.js +0 -96
  128. /package/{shared → dist}/agents/claude.js +0 -0
  129. /package/{shared → dist}/agents/codex.js +0 -0
  130. /package/{shared → dist}/agents/cursor.js +0 -0
  131. /package/{shared → dist}/agents/detect.js +0 -0
  132. /package/{shared → dist}/agents/exec.js +0 -0
  133. /package/{shared → dist}/agents/files.js +0 -0
  134. /package/{shared → dist}/agents/git.js +0 -0
  135. /package/{shared → dist}/agents/gitagent.js +0 -0
  136. /package/{shared → dist}/agents/index.js +0 -0
  137. /package/{shared → dist}/agents/shell.js +0 -0
  138. /package/{shared → dist}/agents/utils.js +0 -0
  139. /package/{client/dist → dist/client}/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  140. /package/{client/dist → dist/client}/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  141. /package/{client/dist → dist/client}/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  142. /package/{client/dist → dist/client}/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  143. /package/{client/dist → dist/client}/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  144. /package/{client/dist → dist/client}/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  145. /package/{client/dist → dist/client}/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  146. /package/{client/dist → dist/client}/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  147. /package/{client/dist → dist/client}/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  148. /package/{client/dist → dist/client}/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  149. /package/{client/dist → dist/client}/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  150. /package/{client/dist → dist/client}/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  151. /package/{client/dist → dist/client}/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  152. /package/{client/dist → dist/client}/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  153. /package/{client/dist → dist/client}/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  154. /package/{client/dist → dist/client}/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  155. /package/{client/dist → dist/client}/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  156. /package/{client/dist → dist/client}/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  157. /package/{client/dist → dist/client}/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  158. /package/{client/dist → dist/client}/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  159. /package/{client/dist → dist/client}/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  160. /package/{client/dist → dist/client}/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  161. /package/{client/dist → dist/client}/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  162. /package/{client/dist → dist/client}/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  163. /package/{client/dist → dist/client}/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  164. /package/{client/dist → dist/client}/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  165. /package/{client/dist → dist/client}/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  166. /package/{client/dist → dist/client}/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  167. /package/{client/dist → dist/client}/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  168. /package/{client/dist → dist/client}/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  169. /package/{client/dist → dist/client}/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  170. /package/{client/dist → dist/client}/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  171. /package/{client/dist → dist/client}/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  172. /package/{client/dist → dist/client}/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  173. /package/{client/dist → dist/client}/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  174. /package/{client/dist → dist/client}/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  175. /package/{client/dist → dist/client}/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  176. /package/{client/dist → dist/client}/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  177. /package/{client/dist → dist/client}/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  178. /package/{client/dist → dist/client}/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  179. /package/{client/dist → dist/client}/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  180. /package/{client/dist → dist/client}/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  181. /package/{client/dist → dist/client}/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  182. /package/{client/dist → dist/client}/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  183. /package/{client/dist → dist/client}/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  184. /package/{client/dist → dist/client}/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  185. /package/{client/dist → dist/client}/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  186. /package/{client/dist → dist/client}/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  187. /package/{client/dist → dist/client}/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  188. /package/{client/dist → dist/client}/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  189. /package/{client/dist → dist/client}/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  190. /package/{client/dist → dist/client}/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  191. /package/{client/dist → dist/client}/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  192. /package/{client/dist → dist/client}/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  193. /package/{client/dist → dist/client}/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  194. /package/{client/dist → dist/client}/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  195. /package/{client/dist → dist/client}/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  196. /package/{client/dist → dist/client}/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  197. /package/{client/dist → dist/client}/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  198. /package/{client/dist → dist/client}/assets/PreviewPanel-CqCa92Tf.js +0 -0
  199. /package/{client/dist → dist/client}/assets/pdf-CE_K4jFx.js +0 -0
  200. /package/{client/dist → dist/client}/assets/vendor-canvas-BZV40eAE.css +0 -0
  201. /package/{client/dist → dist/client}/assets/vendor-diff-DNQpbhrT.js +0 -0
  202. /package/{client/dist → dist/client}/assets/vendor-i18n-DCFGyhQR.js +0 -0
  203. /package/{client/dist → dist/client}/assets/vendor-markdown-CimbIo6Y.js +0 -0
  204. /package/{client/dist → dist/client}/assets/vendor-react-96lCPsRK.js +0 -0
  205. /package/{client/dist → dist/client}/assets/vendor-xterm-CZq1hqo1.js +0 -0
  206. /package/{client/dist → dist/client}/assets/vendor-xterm-qxJ8_QYu.css +0 -0
  207. /package/{client/dist → dist/client}/convert-icons.md +0 -0
  208. /package/{client/dist → dist/client}/generate-icons.js +0 -0
  209. /package/{client/dist → dist/client}/icons/claude-ai-icon.svg +0 -0
  210. /package/{client/dist → dist/client}/icons/codex-white.svg +0 -0
  211. /package/{client/dist → dist/client}/icons/codex.svg +0 -0
  212. /package/{client/dist → dist/client}/icons/cursor-white.svg +0 -0
  213. /package/{client/dist → dist/client}/icons/cursor.svg +0 -0
  214. /package/{client/dist → dist/client}/icons/icon-128x128.png +0 -0
  215. /package/{client/dist → dist/client}/icons/icon-128x128.svg +0 -0
  216. /package/{client/dist → dist/client}/icons/icon-144x144.png +0 -0
  217. /package/{client/dist → dist/client}/icons/icon-144x144.svg +0 -0
  218. /package/{client/dist → dist/client}/icons/icon-152x152.png +0 -0
  219. /package/{client/dist → dist/client}/icons/icon-152x152.svg +0 -0
  220. /package/{client/dist → dist/client}/icons/icon-192x192.png +0 -0
  221. /package/{client/dist → dist/client}/icons/icon-192x192.svg +0 -0
  222. /package/{client/dist → dist/client}/icons/icon-384x384.png +0 -0
  223. /package/{client/dist → dist/client}/icons/icon-384x384.svg +0 -0
  224. /package/{client/dist → dist/client}/icons/icon-512x512.png +0 -0
  225. /package/{client/dist → dist/client}/icons/icon-512x512.svg +0 -0
  226. /package/{client/dist → dist/client}/icons/icon-72x72.png +0 -0
  227. /package/{client/dist → dist/client}/icons/icon-72x72.svg +0 -0
  228. /package/{client/dist → dist/client}/icons/icon-96x96.png +0 -0
  229. /package/{client/dist → dist/client}/icons/icon-96x96.svg +0 -0
  230. /package/{client/dist → dist/client}/icons/icon-template.svg +0 -0
  231. /package/{client/dist → dist/client}/logo-128.png +0 -0
  232. /package/{client/dist → dist/client}/logo-256.png +0 -0
  233. /package/{client/dist → dist/client}/logo-32.png +0 -0
  234. /package/{client/dist → dist/client}/logo-512.png +0 -0
  235. /package/{client/dist → dist/client}/logo-64.png +0 -0
  236. /package/{client/dist → dist/client}/logo.svg +0 -0
  237. /package/{client/dist → dist/client}/screenshots/cli-selection.png +0 -0
  238. /package/{client/dist → dist/client}/screenshots/desktop-main.png +0 -0
  239. /package/{client/dist → dist/client}/screenshots/mobile-chat.png +0 -0
  240. /package/{client/dist → dist/client}/screenshots/tools-modal.png +0 -0
  241. /package/{shared → dist}/gitagent/index.js +0 -0
  242. /package/{shared → dist}/gitagent/parser.js +0 -0
  243. /package/{shared → dist}/gitagent/prompt-builder.js +0 -0
@@ -1,26 +0,0 @@
1
- // Load environment variables from .env before other imports execute.
2
- import fs from 'fs';
3
- import path from 'path';
4
- import { fileURLToPath } from 'url';
5
- import { dirname } from 'path';
6
-
7
- const __filename = fileURLToPath(import.meta.url);
8
- const __dirname = dirname(__filename);
9
-
10
- try {
11
- const envPath = path.join(__dirname, '../.env');
12
- const envFile = fs.readFileSync(envPath, 'utf8');
13
- envFile.split('\n').forEach(line => {
14
- const trimmedLine = line.trim();
15
- if (trimmedLine && !trimmedLine.startsWith('#')) {
16
- const [key, ...valueParts] = trimmedLine.split('=');
17
- if (key && valueParts.length > 0 && !process.env[key]) {
18
- process.env[key] = valueParts.join('=').trim();
19
- }
20
- }
21
- });
22
- } catch (e) {
23
- if (!process.env.VERCEL) {
24
- console.log('No .env file found or error reading it:', e.message);
25
- }
26
- }
@@ -1,621 +0,0 @@
1
- /**
2
- * Upfyn-Code MCP Server
3
- *
4
- * Exposes the app's capabilities as MCP tools and resources so any MCP-compatible
5
- * client (ChatGPT, Claude Desktop, Cursor, etc.) can control the app.
6
- *
7
- * Transport: Streamable HTTP mounted at /mcp on the Express server
8
- *
9
- * Tools:
10
- * - send-prompt: Send a message to Claude and get a response
11
- * - list-projects: List all available projects
12
- * - list-sessions: List sessions for a project
13
- * - get-session-messages: Get messages from a session
14
- * - get-canvas-state: Get current canvas nodes
15
- * - add-canvas-node: Add a node to the canvas
16
- * - clear-canvas: Clear all canvas nodes
17
- * - create-session: Start a new Claude session
18
- * - abort-session: Stop an active session
19
- * - read-file: Read a file from a project
20
- * - list-files: List files in a project directory
21
- *
22
- * Resources:
23
- * - upfynai://canvas/state: Current canvas state
24
- * - upfynai://sessions/active: Active sessions list
25
- */
26
-
27
- import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
28
- import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
29
- import { z } from 'zod';
30
- import crypto from 'crypto';
31
- import jwt from 'jsonwebtoken';
32
- import { userDb, apiKeysDb, relayTokensDb } from './database/db.js';
33
-
34
- import { IS_PLATFORM } from './constants/config.js';
35
- const JWT_SECRET = process.env.JWT_SECRET?.trim() || (IS_PLATFORM ? crypto.randomBytes(32).toString('hex') : (() => { throw new Error('JWT_SECRET required'); })());
36
-
37
- // In-memory canvas state (Excalidraw elements, synced via WebSocket with browser clients)
38
- let canvasElements = [];
39
- const canvasListeners = new Set();
40
-
41
- export function getCanvasElements() {
42
- return canvasElements;
43
- }
44
-
45
- export function setCanvasElements(elements) {
46
- canvasElements = elements;
47
- notifyCanvasListeners();
48
- }
49
-
50
- export function addCanvasElement(element) {
51
- canvasElements.push(element);
52
- notifyCanvasListeners();
53
- }
54
-
55
- export function clearCanvas() {
56
- canvasElements = [];
57
- notifyCanvasListeners();
58
- }
59
-
60
- export function onCanvasChange(listener) {
61
- canvasListeners.add(listener);
62
- return () => canvasListeners.delete(listener);
63
- }
64
-
65
- function notifyCanvasListeners() {
66
- for (const listener of canvasListeners) {
67
- try { listener(canvasElements); } catch (e) { /* ignore */ }
68
- }
69
- }
70
-
71
- // Helper: create an Excalidraw rectangle + text element pair
72
- function createExcalidrawNote(text, { x = 100, y = 100, width = 300, height = 100, label = '' } = {}) {
73
- const id = `el-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
74
- const textId = `${id}-text`;
75
- const fullText = label ? `${label}\n\n${text}` : text;
76
-
77
- const rect = {
78
- id,
79
- type: 'rectangle',
80
- x,
81
- y,
82
- width,
83
- height,
84
- strokeColor: '#a855f7',
85
- backgroundColor: '#1a1a2e',
86
- fillStyle: 'solid',
87
- strokeWidth: 2,
88
- roughness: 0,
89
- opacity: 100,
90
- angle: 0,
91
- groupIds: [],
92
- roundness: { type: 3 },
93
- boundElements: [{ id: textId, type: 'text' }],
94
- isDeleted: false,
95
- version: 1,
96
- versionNonce: Math.floor(Math.random() * 1e9),
97
- };
98
-
99
- const textEl = {
100
- id: textId,
101
- type: 'text',
102
- x: x + 10,
103
- y: y + 10,
104
- width: width - 20,
105
- height: height - 20,
106
- text: fullText,
107
- fontSize: 16,
108
- fontFamily: 1,
109
- textAlign: 'left',
110
- verticalAlign: 'top',
111
- containerId: id,
112
- originalText: fullText,
113
- isDeleted: false,
114
- version: 1,
115
- versionNonce: Math.floor(Math.random() * 1e9),
116
- };
117
-
118
- return [rect, textEl];
119
- }
120
-
121
- /**
122
- * Create and configure the MCP server with all tools and resources
123
- */
124
- export function createMcpServer({ getProjects, getSessions, getSessionMessages, queryClaudeSDK, abortClaudeSDKSession, getActiveClaudeSDKSessions, connectedClients }) {
125
- const server = new McpServer({
126
- name: 'upfynai-code',
127
- version: '2.0.0',
128
- });
129
-
130
- // ═══════════════════════════════════════════
131
- // TOOLS
132
- // ═══════════════════════════════════════════
133
-
134
- // Send a prompt to Claude
135
- server.tool(
136
- 'send-prompt',
137
- 'Send a message to Claude and get a streaming response. The response will appear on the canvas and in chat.',
138
- {
139
- prompt: z.string().describe('The message to send to Claude'),
140
- projectPath: z.string().optional().describe('Project directory path for context'),
141
- sessionId: z.string().optional().describe('Session ID to resume, or omit for new session'),
142
- model: z.string().optional().describe('Model to use (e.g. claude-sonnet-4-5-20250929)'),
143
- },
144
- async ({ prompt, projectPath, sessionId, model }) => {
145
- return new Promise((resolve) => {
146
- const responseChunks = [];
147
- let resolved = false;
148
-
149
- // Create a mock WebSocket writer that collects the response
150
- const mockWs = {
151
- readyState: 1, // WebSocket.OPEN
152
- send: (data) => {
153
- try {
154
- const msg = typeof data === 'string' ? JSON.parse(data) : data;
155
- if (msg.type === 'claude-response' && msg.data?.text) {
156
- responseChunks.push(msg.data.text);
157
- }
158
- if (msg.type === 'claude-complete' && !resolved) {
159
- resolved = true;
160
- const fullText = responseChunks.join('');
161
- // Add to canvas as Excalidraw elements
162
- const yOffset = canvasElements.length * 60;
163
- const els = createExcalidrawNote(fullText, { y: 100 + yOffset, label: 'Claude' });
164
- els.forEach(el => addCanvasElement(el));
165
- // Broadcast canvas update to browser clients
166
- broadcastToClients(connectedClients, {
167
- type: 'canvas-update',
168
- elements: canvasElements,
169
- });
170
- resolve({
171
- content: [{ type: 'text', text: fullText || 'No response received.' }],
172
- });
173
- }
174
- if (msg.type === 'error' && !resolved) {
175
- resolved = true;
176
- resolve({
177
- content: [{ type: 'text', text: `Error: ${msg.error || 'Unknown error'}` }],
178
- isError: true,
179
- });
180
- }
181
- } catch (e) { /* ignore parse errors */ }
182
- },
183
- };
184
-
185
- // Add user prompt to canvas as Excalidraw elements
186
- const userYOffset = canvasElements.length * 60;
187
- const userEls = createExcalidrawNote(prompt, { y: 100 + userYOffset, label: 'You (MCP)' });
188
- userEls.forEach(el => addCanvasElement(el));
189
-
190
- // Broadcast the user elements
191
- broadcastToClients(connectedClients, {
192
- type: 'canvas-update',
193
- elements: canvasElements,
194
- });
195
-
196
- const options = {
197
- projectPath: projectPath || process.cwd(),
198
- cwd: projectPath || process.cwd(),
199
- sessionId: sessionId || undefined,
200
- resume: Boolean(sessionId),
201
- model: model || undefined,
202
- };
203
-
204
- queryClaudeSDK(prompt, options, mockWs).catch((err) => {
205
- if (!resolved) {
206
- resolved = true;
207
- resolve({
208
- content: [{ type: 'text', text: `SDK Error: ${err.message}` }],
209
- isError: true,
210
- });
211
- }
212
- });
213
-
214
- // Safety timeout
215
- setTimeout(() => {
216
- if (!resolved) {
217
- resolved = true;
218
- const partial = responseChunks.join('');
219
- resolve({
220
- content: [{ type: 'text', text: partial || 'Response timed out after 5 minutes.' }],
221
- });
222
- }
223
- }, 300000);
224
- });
225
- }
226
- );
227
-
228
- // List projects
229
- server.tool(
230
- 'list-projects',
231
- 'List all available projects that Claude can work on',
232
- {},
233
- async () => {
234
- try {
235
- const projects = await getProjects();
236
- const summary = projects.map((p) => ({
237
- name: p.name,
238
- displayName: p.displayName || p.name,
239
- path: p.fullPath || p.path,
240
- sessions: (p.sessions || []).length,
241
- }));
242
- return {
243
- content: [{ type: 'text', text: JSON.stringify(summary, null, 2) }],
244
- };
245
- } catch (err) {
246
- return {
247
- content: [{ type: 'text', text: `Error listing projects: ${err.message}` }],
248
- isError: true,
249
- };
250
- }
251
- }
252
- );
253
-
254
- // List sessions
255
- server.tool(
256
- 'list-sessions',
257
- 'List sessions for a project',
258
- {
259
- projectName: z.string().describe('Project name to list sessions for'),
260
- limit: z.number().optional().describe('Max sessions to return (default 10)'),
261
- },
262
- async ({ projectName, limit }) => {
263
- try {
264
- const sessions = await getSessions(projectName, limit || 10, 0);
265
- return {
266
- content: [{ type: 'text', text: JSON.stringify(sessions, null, 2) }],
267
- };
268
- } catch (err) {
269
- return {
270
- content: [{ type: 'text', text: `Error: ${err.message}` }],
271
- isError: true,
272
- };
273
- }
274
- }
275
- );
276
-
277
- // Get session messages
278
- server.tool(
279
- 'get-session-messages',
280
- 'Get message history from a specific session',
281
- {
282
- projectName: z.string().describe('Project name'),
283
- sessionId: z.string().describe('Session ID'),
284
- limit: z.number().optional().describe('Max messages (default 50)'),
285
- },
286
- async ({ projectName, sessionId, limit }) => {
287
- try {
288
- const messages = await getSessionMessages(projectName, sessionId, limit || 50, 0);
289
- return {
290
- content: [{ type: 'text', text: JSON.stringify(messages, null, 2) }],
291
- };
292
- } catch (err) {
293
- return {
294
- content: [{ type: 'text', text: `Error: ${err.message}` }],
295
- isError: true,
296
- };
297
- }
298
- }
299
- );
300
-
301
- // Get canvas state
302
- server.tool(
303
- 'get-canvas-state',
304
- 'Get the current Upfyn-Canvas state including all elements',
305
- {},
306
- async () => {
307
- return {
308
- content: [{
309
- type: 'text',
310
- text: JSON.stringify({
311
- elementCount: canvasElements.length,
312
- elements: canvasElements,
313
- }, null, 2),
314
- }],
315
- };
316
- }
317
- );
318
-
319
- // Add canvas node (creates Upfyn-Canvas rectangle + text)
320
- server.tool(
321
- 'add-canvas-node',
322
- 'Add a visual note to the Upfyn-Canvas whiteboard (rectangle with text)',
323
- {
324
- text: z.string().describe('Text content for the note'),
325
- label: z.string().optional().describe('Label for the note (default: "MCP Note")'),
326
- x: z.number().optional().describe('X position (default: 100)'),
327
- y: z.number().optional().describe('Y position (default: auto)'),
328
- },
329
- async ({ text, label, x, y }) => {
330
- const yPos = y ?? (100 + canvasElements.length * 60);
331
- const els = createExcalidrawNote(text, { x: x ?? 100, y: yPos, label: label || 'MCP Note' });
332
- els.forEach(el => addCanvasElement(el));
333
- broadcastToClients(connectedClients, {
334
- type: 'canvas-update',
335
- elements: canvasElements,
336
- });
337
- return {
338
- content: [{ type: 'text', text: `Note added to canvas: ${els[0].id}` }],
339
- };
340
- }
341
- );
342
-
343
- // Clear canvas
344
- server.tool(
345
- 'clear-canvas',
346
- 'Clear all elements from the Upfyn-Canvas whiteboard',
347
- {},
348
- async () => {
349
- clearCanvas();
350
- broadcastToClients(connectedClients, {
351
- type: 'canvas-update',
352
- elements: [],
353
- });
354
- return {
355
- content: [{ type: 'text', text: 'Canvas cleared.' }],
356
- };
357
- }
358
- );
359
-
360
- // Update full canvas scene
361
- server.tool(
362
- 'update-canvas-scene',
363
- 'Replace the entire Upfyn-Canvas with new elements',
364
- {
365
- elements: z.array(z.object({}).passthrough()).describe('Array of canvas elements'),
366
- },
367
- async ({ elements }) => {
368
- setCanvasElements(elements);
369
- broadcastToClients(connectedClients, {
370
- type: 'canvas-update',
371
- elements: canvasElements,
372
- });
373
- return {
374
- content: [{ type: 'text', text: `Canvas updated with ${elements.length} elements.` }],
375
- };
376
- }
377
- );
378
-
379
- // Get active sessions
380
- server.tool(
381
- 'get-active-sessions',
382
- 'Get list of currently active Claude sessions',
383
- {},
384
- async () => {
385
- try {
386
- const sessions = getActiveClaudeSDKSessions();
387
- return {
388
- content: [{ type: 'text', text: JSON.stringify(sessions, null, 2) }],
389
- };
390
- } catch (err) {
391
- return {
392
- content: [{ type: 'text', text: `Error: ${err.message}` }],
393
- isError: true,
394
- };
395
- }
396
- }
397
- );
398
-
399
- // Abort session
400
- server.tool(
401
- 'abort-session',
402
- 'Stop an active Claude session',
403
- {
404
- sessionId: z.string().describe('Session ID to abort'),
405
- },
406
- async ({ sessionId }) => {
407
- try {
408
- const result = await abortClaudeSDKSession(sessionId);
409
- return {
410
- content: [{ type: 'text', text: result ? `Session ${sessionId} aborted.` : `Session ${sessionId} not found or already stopped.` }],
411
- };
412
- } catch (err) {
413
- return {
414
- content: [{ type: 'text', text: `Error: ${err.message}` }],
415
- isError: true,
416
- };
417
- }
418
- }
419
- );
420
-
421
- // ═══════════════════════════════════════════
422
- // RESOURCES
423
- // ═══════════════════════════════════════════
424
-
425
- server.resource(
426
- 'canvas-state',
427
- 'upfynai://canvas/state',
428
- {
429
- description: 'Current Upfyn-Canvas state with all elements',
430
- mimeType: 'application/json',
431
- },
432
- async (uri) => ({
433
- contents: [{
434
- uri: uri.href,
435
- text: JSON.stringify({ elementCount: canvasElements.length, elements: canvasElements }, null, 2),
436
- }],
437
- })
438
- );
439
-
440
- server.resource(
441
- 'active-sessions',
442
- 'upfynai://sessions/active',
443
- {
444
- description: 'Currently active Claude sessions',
445
- mimeType: 'application/json',
446
- },
447
- async (uri) => ({
448
- contents: [{
449
- uri: uri.href,
450
- text: JSON.stringify(getActiveClaudeSDKSessions(), null, 2),
451
- }],
452
- })
453
- );
454
-
455
- return server;
456
- }
457
-
458
- /**
459
- * Mount the MCP server on an Express app at /mcp
460
- */
461
- export async function mountMcpServer(app, mcpServer, mcpServerFactory) {
462
- // On Vercel serverless, in-memory session state is lost between invocations.
463
- // Use per-request stateless MCP servers on Vercel; session-based on local.
464
- const isServerless = !!process.env.VERCEL;
465
- const transports = new Map();
466
-
467
- // MCP authentication middleware — cookie → Bearer → API key → query param
468
- const authenticateMcp = async (req, res, next) => {
469
- // 1. Try httpOnly cookie (browser sessions)
470
- if (req.cookies?.session) {
471
- try {
472
- const decoded = jwt.verify(req.cookies.session, JWT_SECRET);
473
- const user = await userDb.getUserById(decoded.userId);
474
- if (user) { req.user = user; return next(); }
475
- } catch (e) { /* fall through */ }
476
- }
477
-
478
- // 2. Try Bearer token — supports JWT, relay token (upfyn_/rt_), or API key (up-cli-)
479
- const authHeader = req.headers['authorization'];
480
- const token = authHeader && authHeader.split(' ')[1];
481
- if (token) {
482
- // 2a. Relay token (upfyn_xxx or legacy rt_xxx) — same token used for CLI connect
483
- if (token.startsWith('upfyn_') || token.startsWith('rt_')) {
484
- try {
485
- const tokenData = await relayTokensDb.validateToken(token);
486
- if (tokenData) {
487
- const user = await userDb.getUserById(tokenData.user_id);
488
- if (user) { req.user = user; return next(); }
489
- }
490
- } catch (e) { /* fall through */ }
491
- }
492
- // 2b. API key (up-cli-xxx)
493
- if (token.startsWith('up-cli-')) {
494
- try {
495
- const user = await apiKeysDb.validateApiKey(token);
496
- if (user) { req.user = user; return next(); }
497
- } catch (e) { /* fall through */ }
498
- }
499
- // 2c. JWT token
500
- try {
501
- const decoded = jwt.verify(token, JWT_SECRET);
502
- const user = await userDb.getUserById(decoded.userId);
503
- if (user) { req.user = user; return next(); }
504
- } catch (e) { /* fall through */ }
505
- }
506
-
507
- // 3. Try API key header (MCP clients: ChatGPT, Claude Desktop, Cursor, etc.)
508
- const apiKey = req.headers['x-api-key'];
509
- if (apiKey) {
510
- try {
511
- const user = await apiKeysDb.validateApiKey(apiKey);
512
- if (user) { req.user = user; return next(); }
513
- } catch (e) { /* ignore */ }
514
- }
515
-
516
- // 4. Try query param token (SSE EventSource fallback)
517
- if (req.query.token) {
518
- try {
519
- const decoded = jwt.verify(req.query.token, JWT_SECRET);
520
- const user = await userDb.getUserById(decoded.userId);
521
- if (user) { req.user = user; return next(); }
522
- } catch (e) { /* ignore */ }
523
- }
524
-
525
- res.status(401).json({ error: 'Authentication required. Use Authorization: Bearer <jwt>, x-api-key header, or up-cli- API key.' });
526
- };
527
-
528
- // Handle MCP requests at /mcp endpoint
529
- app.post('/mcp', authenticateMcp, async (req, res) => {
530
- try {
531
- const sessionId = req.headers['mcp-session-id'];
532
- let transport;
533
-
534
- if (!isServerless && sessionId && transports.has(sessionId)) {
535
- // Local: reuse existing session transport
536
- transport = transports.get(sessionId);
537
- } else {
538
- // Create a fresh MCP server + transport per request on serverless,
539
- // or a new session on local
540
- const server = isServerless && mcpServerFactory ? mcpServerFactory() : mcpServer;
541
-
542
- // On serverless: no sessionIdGenerator → disables session validation
543
- // entirely, so requests don't need the initialize handshake.
544
- // On local: sessionIdGenerator enables session tracking.
545
- transport = new StreamableHTTPServerTransport({
546
- sessionIdGenerator: isServerless ? undefined : (() => crypto.randomUUID()),
547
- stateless: isServerless,
548
- });
549
-
550
- transport.onclose = () => {
551
- const sid = transport.sessionId;
552
- if (sid) transports.delete(sid);
553
- };
554
-
555
- await server.connect(transport);
556
-
557
- if (!isServerless && transport.sessionId) {
558
- transports.set(transport.sessionId, transport);
559
- }
560
- }
561
-
562
- // Express already consumed the raw body stream, so we must pass the
563
- // parsed body as the 3rd arg to avoid "Parse error: Invalid JSON"
564
- await transport.handleRequest(req, res, req.body);
565
- } catch (err) {
566
- console.error('[MCP] Error handling POST:', err.message);
567
- if (!res.headersSent) {
568
- res.status(500).json({ error: 'MCP server error', details: err.message });
569
- }
570
- }
571
- });
572
-
573
- // SSE stream for server-initiated messages (local only — serverless is stateless)
574
- app.get('/mcp', authenticateMcp, async (req, res) => {
575
- if (isServerless) {
576
- return res.status(405).json({ error: 'SSE not supported on serverless. Use stateless POST requests.' });
577
- }
578
- const sessionId = req.headers['mcp-session-id'];
579
- if (!sessionId || !transports.has(sessionId)) {
580
- res.status(400).json({ error: 'Invalid or missing session ID' });
581
- return;
582
- }
583
- const transport = transports.get(sessionId);
584
- await transport.handleRequest(req, res);
585
- });
586
-
587
- // Session termination
588
- app.delete('/mcp', authenticateMcp, async (req, res) => {
589
- if (isServerless) {
590
- return res.json({ ok: true });
591
- }
592
- const sessionId = req.headers['mcp-session-id'];
593
- if (sessionId && transports.has(sessionId)) {
594
- const transport = transports.get(sessionId);
595
- await transport.handleRequest(req, res);
596
- transports.delete(sessionId);
597
- } else {
598
- res.status(404).json({ error: 'Session not found' });
599
- }
600
- });
601
-
602
- console.log(`${c_info('[MCP]')} MCP server mounted at /mcp (${isServerless ? 'stateless/serverless' : 'session-based'})`);
603
- console.log(`${c_info('[MCP]')} Tools: send-prompt, list-projects, list-sessions, get-session-messages, get-canvas-state, add-canvas-node, clear-canvas, update-canvas-scene, get-active-sessions, abort-session`);
604
- }
605
-
606
- // Simple color helper (avoids importing from index.js)
607
- function c_info(text) {
608
- return `\x1b[36m${text}\x1b[0m`;
609
- }
610
-
611
- function broadcastToClients(clients, message) {
612
- if (!clients || clients.size === 0) return;
613
- const data = JSON.stringify(message);
614
- for (const client of clients) {
615
- try {
616
- if (client.readyState === 1) {
617
- client.send(data);
618
- }
619
- } catch (e) { /* ignore */ }
620
- }
621
- }