upfynai-code 3.0.4 → 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 (246) hide show
  1. package/README.md +66 -91
  2. package/bin/cli.js +191 -0
  3. package/{client/dist/assets/AppContent-CwrTP6TW.js → dist/client/assets/AppContent-BofJquUs.js} +4 -4
  4. package/{client/dist/assets/BrowserPanel-0TLEl-IC.js → dist/client/assets/BrowserPanel-CSvD4jOX.js} +2 -2
  5. package/dist/client/assets/CanvasFullScreen-onRfarpc.js +1 -0
  6. package/dist/client/assets/CanvasWorkspace-DvGKdL-k.js +259 -0
  7. package/dist/client/assets/DashboardPanel-DqAHbXDO.js +1 -0
  8. package/dist/client/assets/FileTree-BE0h-9M9.js +1 -0
  9. package/{client/dist/assets/GitPanel-C_xFM-N2.js → dist/client/assets/GitPanel-DdeJ0bp5.js} +2 -2
  10. package/{client/dist/assets/LoginModal-CImJHRjX.js → dist/client/assets/LoginModal-BP0pCTrH.js} +3 -3
  11. package/dist/client/assets/MermaidBlock-D0rfEhrT.js +2 -0
  12. package/dist/client/assets/Onboarding-B2zQy-_6.js +1 -0
  13. package/dist/client/assets/SetupForm-Be7-WBe-.js +1 -0
  14. package/dist/client/assets/WorkflowsPanel-CusLbVJ6.js +1 -0
  15. package/{client/dist/assets/index-HaY-3pK1.js → dist/client/assets/index-BQy15irW.js} +24 -24
  16. package/dist/client/assets/index-CS0fDqEC.js +1 -0
  17. package/dist/client/assets/index-DYLSCCCp.css +1 -0
  18. package/dist/client/assets/vendor-canvas-QWTduIvM.js +23 -0
  19. package/{client/dist/assets/vendor-icons-GyYE35HP.js → dist/client/assets/vendor-icons-kix3Gb31.js} +1 -1
  20. package/{client/dist/assets/vendor-mermaid-DucWyDEe.js → dist/client/assets/vendor-mermaid-CS3J4_Bz.js} +329 -326
  21. package/dist/client/favicon.png +0 -0
  22. package/dist/client/favicon.svg +15 -0
  23. package/{client/dist → dist/client}/index.html +3 -3
  24. package/{client/dist → dist/client}/manifest.json +12 -12
  25. package/package.json +55 -104
  26. package/scripts/postinstall.js +9 -0
  27. package/scripts/prepublish.js +77 -0
  28. package/src/animation.js +228 -0
  29. package/src/auth.js +142 -0
  30. package/src/config.js +40 -0
  31. package/src/connect.js +416 -0
  32. package/src/launch.js +81 -0
  33. package/src/mcp.js +57 -0
  34. package/src/permissions.js +140 -0
  35. package/src/persistent-shell.js +261 -0
  36. package/src/server.js +54 -0
  37. package/client/dist/assets/CanvasFullScreen-D1GWQsGL.js +0 -1
  38. package/client/dist/assets/CanvasWorkspace-D7ORj358.js +0 -163
  39. package/client/dist/assets/DashboardPanel-BV7ybUDe.js +0 -1
  40. package/client/dist/assets/FileTree-5qfhBqdE.js +0 -1
  41. package/client/dist/assets/MermaidBlock-BFM21cwe.js +0 -2
  42. package/client/dist/assets/Onboarding-B3cteLu2.js +0 -1
  43. package/client/dist/assets/SetupForm-P6dsYgHO.js +0 -1
  44. package/client/dist/assets/WorkflowsPanel-CBoN80kc.js +0 -1
  45. package/client/dist/assets/index-46kkVu2i.css +0 -1
  46. package/client/dist/assets/vendor-canvas-DvHJ_Pn2.js +0 -49
  47. package/client/dist/favicon.png +0 -0
  48. package/client/dist/favicon.svg +0 -5
  49. package/commands/upfynai-connect.md +0 -59
  50. package/commands/upfynai-disconnect.md +0 -31
  51. package/commands/upfynai-doctor.md +0 -99
  52. package/commands/upfynai-export.md +0 -49
  53. package/commands/upfynai-local.md +0 -82
  54. package/commands/upfynai-status.md +0 -75
  55. package/commands/upfynai-stop.md +0 -49
  56. package/commands/upfynai-uninstall.md +0 -58
  57. package/commands/upfynai.md +0 -69
  58. package/scripts/build-client.js +0 -17
  59. package/scripts/fix-node-pty.js +0 -67
  60. package/scripts/install-commands.js +0 -78
  61. package/server/agent-loop.js +0 -242
  62. package/server/auto-compact.js +0 -99
  63. package/server/browser.js +0 -131
  64. package/server/claude-sdk.js +0 -797
  65. package/server/cli-ui.js +0 -798
  66. package/server/cli.js +0 -751
  67. package/server/constants/config.js +0 -31
  68. package/server/cursor-cli.js +0 -270
  69. package/server/database/auth.db +0 -0
  70. package/server/database/db.js +0 -1547
  71. package/server/database/init.sql +0 -70
  72. package/server/index.js +0 -3813
  73. package/server/load-env.js +0 -26
  74. package/server/mcp-server.js +0 -621
  75. package/server/middleware/auth.js +0 -184
  76. package/server/middleware/relayHelpers.js +0 -44
  77. package/server/middleware/sandboxRouter.js +0 -174
  78. package/server/openai-codex.js +0 -403
  79. package/server/openrouter.js +0 -137
  80. package/server/projects.js +0 -1807
  81. package/server/provider-factory.js +0 -174
  82. package/server/relay-client.js +0 -390
  83. package/server/routes/agent.js +0 -1234
  84. package/server/routes/auth.js +0 -559
  85. package/server/routes/browser.js +0 -419
  86. package/server/routes/canvas.js +0 -53
  87. package/server/routes/cli-auth.js +0 -263
  88. package/server/routes/codex.js +0 -396
  89. package/server/routes/commands.js +0 -707
  90. package/server/routes/composio.js +0 -176
  91. package/server/routes/cursor.js +0 -770
  92. package/server/routes/dashboard.js +0 -295
  93. package/server/routes/git.js +0 -1208
  94. package/server/routes/keys.js +0 -34
  95. package/server/routes/mcp-utils.js +0 -48
  96. package/server/routes/mcp.js +0 -661
  97. package/server/routes/payments.js +0 -227
  98. package/server/routes/projects.js +0 -754
  99. package/server/routes/sessions.js +0 -146
  100. package/server/routes/settings.js +0 -261
  101. package/server/routes/taskmaster.js +0 -1928
  102. package/server/routes/user.js +0 -106
  103. package/server/routes/vapi-chat.js +0 -624
  104. package/server/routes/voice.js +0 -235
  105. package/server/routes/webhooks.js +0 -166
  106. package/server/routes/workflows.js +0 -312
  107. package/server/sandbox.js +0 -120
  108. package/server/services/browser-ai.js +0 -154
  109. package/server/services/composio.js +0 -204
  110. package/server/services/sessionRegistry.js +0 -139
  111. package/server/services/whisperService.js +0 -84
  112. package/server/services/workflowScheduler.js +0 -211
  113. package/server/tests/relay-flow.test.js +0 -570
  114. package/server/tests/sessions.test.js +0 -259
  115. package/server/utils/commandParser.js +0 -303
  116. package/server/utils/email.js +0 -66
  117. package/server/utils/gitConfig.js +0 -24
  118. package/server/utils/mcp-detector.js +0 -198
  119. package/server/utils/taskmaster-websocket.js +0 -129
  120. package/shared/integrationCatalog.d.ts +0 -12
  121. package/shared/integrationCatalog.js +0 -172
  122. package/shared/modelConstants.js +0 -96
  123. /package/{shared → dist}/agents/claude.js +0 -0
  124. /package/{shared → dist}/agents/codex.js +0 -0
  125. /package/{shared → dist}/agents/cursor.js +0 -0
  126. /package/{shared → dist}/agents/detect.js +0 -0
  127. /package/{shared → dist}/agents/exec.js +0 -0
  128. /package/{shared → dist}/agents/files.js +0 -0
  129. /package/{shared → dist}/agents/git.js +0 -0
  130. /package/{shared → dist}/agents/gitagent.js +0 -0
  131. /package/{shared → dist}/agents/index.js +0 -0
  132. /package/{shared → dist}/agents/shell.js +0 -0
  133. /package/{shared → dist}/agents/utils.js +0 -0
  134. /package/{client/dist → dist/client}/api-docs.html +0 -0
  135. /package/{client/dist → dist/client}/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  136. /package/{client/dist → dist/client}/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  137. /package/{client/dist → dist/client}/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  138. /package/{client/dist → dist/client}/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  139. /package/{client/dist → dist/client}/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  140. /package/{client/dist → dist/client}/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  141. /package/{client/dist → dist/client}/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  142. /package/{client/dist → dist/client}/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  143. /package/{client/dist → dist/client}/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  144. /package/{client/dist → dist/client}/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  145. /package/{client/dist → dist/client}/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  146. /package/{client/dist → dist/client}/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  147. /package/{client/dist → dist/client}/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  148. /package/{client/dist → dist/client}/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  149. /package/{client/dist → dist/client}/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  150. /package/{client/dist → dist/client}/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  151. /package/{client/dist → dist/client}/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  152. /package/{client/dist → dist/client}/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  153. /package/{client/dist → dist/client}/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  154. /package/{client/dist → dist/client}/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  155. /package/{client/dist → dist/client}/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  156. /package/{client/dist → dist/client}/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  157. /package/{client/dist → dist/client}/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  158. /package/{client/dist → dist/client}/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  159. /package/{client/dist → dist/client}/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  160. /package/{client/dist → dist/client}/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  161. /package/{client/dist → dist/client}/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  162. /package/{client/dist → dist/client}/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  163. /package/{client/dist → dist/client}/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  164. /package/{client/dist → dist/client}/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  165. /package/{client/dist → dist/client}/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  166. /package/{client/dist → dist/client}/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  167. /package/{client/dist → dist/client}/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  168. /package/{client/dist → dist/client}/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  169. /package/{client/dist → dist/client}/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  170. /package/{client/dist → dist/client}/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  171. /package/{client/dist → dist/client}/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  172. /package/{client/dist → dist/client}/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  173. /package/{client/dist → dist/client}/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  174. /package/{client/dist → dist/client}/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  175. /package/{client/dist → dist/client}/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  176. /package/{client/dist → dist/client}/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  177. /package/{client/dist → dist/client}/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  178. /package/{client/dist → dist/client}/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  179. /package/{client/dist → dist/client}/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  180. /package/{client/dist → dist/client}/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  181. /package/{client/dist → dist/client}/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  182. /package/{client/dist → dist/client}/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  183. /package/{client/dist → dist/client}/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  184. /package/{client/dist → dist/client}/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  185. /package/{client/dist → dist/client}/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  186. /package/{client/dist → dist/client}/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  187. /package/{client/dist → dist/client}/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  188. /package/{client/dist → dist/client}/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  189. /package/{client/dist → dist/client}/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  190. /package/{client/dist → dist/client}/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  191. /package/{client/dist → dist/client}/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  192. /package/{client/dist → dist/client}/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  193. /package/{client/dist → dist/client}/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  194. /package/{client/dist → dist/client}/assets/MarkdownPreview-CESjI261.js +0 -0
  195. /package/{client/dist → dist/client}/assets/PreviewPanel-CqCa92Tf.js +0 -0
  196. /package/{client/dist → dist/client}/assets/pdf-CE_K4jFx.js +0 -0
  197. /package/{client/dist → dist/client}/assets/vendor-canvas-BZV40eAE.css +0 -0
  198. /package/{client/dist → dist/client}/assets/vendor-codemirror-D2ALgpaX.js +0 -0
  199. /package/{client/dist → dist/client}/assets/vendor-diff-DNQpbhrT.js +0 -0
  200. /package/{client/dist → dist/client}/assets/vendor-i18n-DCFGyhQR.js +0 -0
  201. /package/{client/dist → dist/client}/assets/vendor-markdown-CimbIo6Y.js +0 -0
  202. /package/{client/dist → dist/client}/assets/vendor-react-96lCPsRK.js +0 -0
  203. /package/{client/dist → dist/client}/assets/vendor-syntax-LS_Nt30I.js +0 -0
  204. /package/{client/dist → dist/client}/assets/vendor-xterm-CZq1hqo1.js +0 -0
  205. /package/{client/dist → dist/client}/assets/vendor-xterm-qxJ8_QYu.css +0 -0
  206. /package/{client/dist → dist/client}/clear-cache.html +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}/mcp-docs.html +0 -0
  238. /package/{client/dist → dist/client}/offline.html +0 -0
  239. /package/{client/dist → dist/client}/screenshots/cli-selection.png +0 -0
  240. /package/{client/dist → dist/client}/screenshots/desktop-main.png +0 -0
  241. /package/{client/dist → dist/client}/screenshots/mobile-chat.png +0 -0
  242. /package/{client/dist → dist/client}/screenshots/tools-modal.png +0 -0
  243. /package/{client/dist → dist/client}/sw.js +0 -0
  244. /package/{shared → dist}/gitagent/index.js +0 -0
  245. /package/{shared → dist}/gitagent/parser.js +0 -0
  246. /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
- }