arcway 0.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 (274) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +711 -0
  3. package/client/env.js +55 -0
  4. package/client/fetcher.js +50 -0
  5. package/client/graphql.js +35 -0
  6. package/client/head.js +140 -0
  7. package/client/hooks/use-api.js +80 -0
  8. package/client/hooks/use-debounce.js +12 -0
  9. package/client/hooks/use-form.js +86 -0
  10. package/client/hooks/use-graphql.js +30 -0
  11. package/client/hooks/use-interval.js +12 -0
  12. package/client/hooks/use-mutation.js +27 -0
  13. package/client/hooks/use-query.js +45 -0
  14. package/client/hooks/web/use-click-outside.js +22 -0
  15. package/client/hooks/web/use-local-storage.js +42 -0
  16. package/client/index.js +62 -0
  17. package/client/page-loader.js +155 -0
  18. package/client/provider.js +53 -0
  19. package/client/query.js +13 -0
  20. package/client/router.jsx +303 -0
  21. package/client/ui/accordion.jsx +65 -0
  22. package/client/ui/accordion.stories.jsx +48 -0
  23. package/client/ui/alert-dialog.jsx +122 -0
  24. package/client/ui/alert-dialog.stories.jsx +44 -0
  25. package/client/ui/alert.jsx +52 -0
  26. package/client/ui/alert.stories.jsx +31 -0
  27. package/client/ui/app-shell.jsx +39 -0
  28. package/client/ui/app-shell.stories.jsx +51 -0
  29. package/client/ui/aspect-ratio.jsx +6 -0
  30. package/client/ui/aspect-ratio.stories.jsx +69 -0
  31. package/client/ui/avatar.jsx +78 -0
  32. package/client/ui/avatar.stories.jsx +62 -0
  33. package/client/ui/badge.jsx +34 -0
  34. package/client/ui/badge.stories.js +32 -0
  35. package/client/ui/breadcrumb.jsx +86 -0
  36. package/client/ui/breadcrumb.stories.jsx +43 -0
  37. package/client/ui/button-group.jsx +58 -0
  38. package/client/ui/button-group.stories.jsx +67 -0
  39. package/client/ui/button.jsx +46 -0
  40. package/client/ui/button.stories.js +72 -0
  41. package/client/ui/calendar.jsx +172 -0
  42. package/client/ui/card.jsx +57 -0
  43. package/client/ui/card.stories.jsx +33 -0
  44. package/client/ui/carousel.jsx +167 -0
  45. package/client/ui/chart.jsx +244 -0
  46. package/client/ui/checkbox.jsx +24 -0
  47. package/client/ui/checkbox.stories.js +33 -0
  48. package/client/ui/collapsible.jsx +12 -0
  49. package/client/ui/collapsible.stories.jsx +42 -0
  50. package/client/ui/combobox.jsx +223 -0
  51. package/client/ui/command.jsx +128 -0
  52. package/client/ui/context-menu.jsx +170 -0
  53. package/client/ui/context-menu.stories.jsx +35 -0
  54. package/client/ui/dialog.jsx +109 -0
  55. package/client/ui/dialog.stories.jsx +37 -0
  56. package/client/ui/direction.jsx +9 -0
  57. package/client/ui/drawer.jsx +87 -0
  58. package/client/ui/dropdown-menu.jsx +172 -0
  59. package/client/ui/dropdown-menu.stories.jsx +34 -0
  60. package/client/ui/empty.jsx +76 -0
  61. package/client/ui/empty.stories.jsx +64 -0
  62. package/client/ui/field.jsx +174 -0
  63. package/client/ui/field.stories.jsx +118 -0
  64. package/client/ui/form.jsx +17 -0
  65. package/client/ui/hooks/use-mobile.js +16 -0
  66. package/client/ui/hover-card.jsx +26 -0
  67. package/client/ui/hover-card.stories.jsx +28 -0
  68. package/client/ui/index.js +649 -0
  69. package/client/ui/input-group.jsx +116 -0
  70. package/client/ui/input-group.stories.jsx +65 -0
  71. package/client/ui/input-otp.jsx +62 -0
  72. package/client/ui/input.jsx +16 -0
  73. package/client/ui/input.stories.js +27 -0
  74. package/client/ui/item.jsx +155 -0
  75. package/client/ui/item.stories.jsx +118 -0
  76. package/client/ui/kbd.jsx +24 -0
  77. package/client/ui/kbd.stories.jsx +32 -0
  78. package/client/ui/label.jsx +16 -0
  79. package/client/ui/label.stories.js +25 -0
  80. package/client/ui/lib/utils.js +6 -0
  81. package/client/ui/main-content.jsx +30 -0
  82. package/client/ui/menubar.jsx +189 -0
  83. package/client/ui/menubar.stories.jsx +43 -0
  84. package/client/ui/native-select.jsx +34 -0
  85. package/client/ui/native-select.stories.jsx +67 -0
  86. package/client/ui/navigation-menu.jsx +120 -0
  87. package/client/ui/navigation-menu.stories.jsx +45 -0
  88. package/client/ui/pagination.jsx +92 -0
  89. package/client/ui/pagination.stories.jsx +52 -0
  90. package/client/ui/panel.jsx +66 -0
  91. package/client/ui/popover.jsx +54 -0
  92. package/client/ui/popover.stories.jsx +27 -0
  93. package/client/ui/progress.jsx +19 -0
  94. package/client/ui/progress.stories.js +34 -0
  95. package/client/ui/radio-group.jsx +33 -0
  96. package/client/ui/radio-group.stories.jsx +49 -0
  97. package/client/ui/resizable.jsx +33 -0
  98. package/client/ui/scroll-area.jsx +41 -0
  99. package/client/ui/scroll-area.stories.jsx +43 -0
  100. package/client/ui/select.jsx +145 -0
  101. package/client/ui/select.stories.jsx +80 -0
  102. package/client/ui/separator.jsx +18 -0
  103. package/client/ui/separator.stories.jsx +37 -0
  104. package/client/ui/sheet.jsx +95 -0
  105. package/client/ui/sheet.stories.jsx +56 -0
  106. package/client/ui/sidebar.jsx +544 -0
  107. package/client/ui/skeleton.jsx +8 -0
  108. package/client/ui/skeleton.stories.js +23 -0
  109. package/client/ui/slider.jsx +41 -0
  110. package/client/ui/slider.stories.js +31 -0
  111. package/client/ui/sonner.jsx +37 -0
  112. package/client/ui/spinner.jsx +14 -0
  113. package/client/ui/spinner.stories.js +16 -0
  114. package/client/ui/style-mira.css +1316 -0
  115. package/client/ui/switch.jsx +22 -0
  116. package/client/ui/switch.stories.js +44 -0
  117. package/client/ui/table.jsx +33 -0
  118. package/client/ui/table.stories.jsx +42 -0
  119. package/client/ui/tabs.jsx +63 -0
  120. package/client/ui/tabs.stories.jsx +45 -0
  121. package/client/ui/textarea.jsx +15 -0
  122. package/client/ui/textarea.stories.js +33 -0
  123. package/client/ui/theme.css +459 -0
  124. package/client/ui/toggle-group.jsx +62 -0
  125. package/client/ui/toggle-group.stories.jsx +68 -0
  126. package/client/ui/toggle.jsx +34 -0
  127. package/client/ui/toggle.stories.js +46 -0
  128. package/client/ui/tooltip.jsx +37 -0
  129. package/client/ui/tooltip.stories.jsx +32 -0
  130. package/client/ui/use-transition.js +35 -0
  131. package/client/ws.js +132 -0
  132. package/package.json +134 -0
  133. package/server/bin/cli.js +42 -0
  134. package/server/bin/commands/build.js +23 -0
  135. package/server/bin/commands/dev.js +57 -0
  136. package/server/bin/commands/docs.js +30 -0
  137. package/server/bin/commands/graphql-schema.js +32 -0
  138. package/server/bin/commands/lint.js +35 -0
  139. package/server/bin/commands/mcp.js +26 -0
  140. package/server/bin/commands/migrate.js +82 -0
  141. package/server/bin/commands/schema.js +41 -0
  142. package/server/bin/commands/seed.js +36 -0
  143. package/server/bin/commands/start.js +31 -0
  144. package/server/bin/commands/test.js +20 -0
  145. package/server/bin/solo.js +4 -0
  146. package/server/boot/index.js +150 -0
  147. package/server/boot.js +2 -0
  148. package/server/build.js +23 -0
  149. package/server/cache/drivers/memory.js +23 -0
  150. package/server/cache/drivers/redis.js +28 -0
  151. package/server/cache/index.js +69 -0
  152. package/server/config/loader.js +89 -0
  153. package/server/config/modules/api.js +17 -0
  154. package/server/config/modules/build.js +9 -0
  155. package/server/config/modules/cache.js +10 -0
  156. package/server/config/modules/database.js +29 -0
  157. package/server/config/modules/events.js +15 -0
  158. package/server/config/modules/files.js +15 -0
  159. package/server/config/modules/jobs.js +20 -0
  160. package/server/config/modules/logger.js +9 -0
  161. package/server/config/modules/mail.js +11 -0
  162. package/server/config/modules/mcp.js +9 -0
  163. package/server/config/modules/pages.js +20 -0
  164. package/server/config/modules/queue.js +10 -0
  165. package/server/config/modules/redis.js +9 -0
  166. package/server/config/modules/server.js +30 -0
  167. package/server/config/modules/session.js +9 -0
  168. package/server/config/modules/websocket.js +11 -0
  169. package/server/constants.js +67 -0
  170. package/server/context.js +15 -0
  171. package/server/db/index.js +87 -0
  172. package/server/db/schema/drivers/mysql.js +28 -0
  173. package/server/db/schema/drivers/pg.js +34 -0
  174. package/server/db/schema/drivers/sqlite.js +22 -0
  175. package/server/db/schema/index.js +78 -0
  176. package/server/db/seeds.js +22 -0
  177. package/server/discovery.js +67 -0
  178. package/server/docs/openapi.js +153 -0
  179. package/server/env.js +17 -0
  180. package/server/events/drivers/memory.js +45 -0
  181. package/server/events/drivers/redis.js +64 -0
  182. package/server/events/handler.js +67 -0
  183. package/server/events/index.js +35 -0
  184. package/server/events/pattern.js +5 -0
  185. package/server/files/drivers/local.js +83 -0
  186. package/server/files/drivers/s3.js +113 -0
  187. package/server/files/index.js +57 -0
  188. package/server/filewatcher/index.js +156 -0
  189. package/server/glob.js +6 -0
  190. package/server/graphql/discovery.js +70 -0
  191. package/server/graphql/handler.js +41 -0
  192. package/server/graphql/index.js +13 -0
  193. package/server/graphql/loaders.js +19 -0
  194. package/server/graphql/merge.js +48 -0
  195. package/server/graphql/subscriptions.js +43 -0
  196. package/server/health.js +34 -0
  197. package/server/helpers.js +9 -0
  198. package/server/index.js +55 -0
  199. package/server/internals.js +139 -0
  200. package/server/jobs/cron.js +10 -0
  201. package/server/jobs/drivers/knex-queue.js +207 -0
  202. package/server/jobs/drivers/lease.js +148 -0
  203. package/server/jobs/drivers/memory-queue.js +134 -0
  204. package/server/jobs/queue.js +27 -0
  205. package/server/jobs/runner.js +197 -0
  206. package/server/jobs/throughput.js +63 -0
  207. package/server/lib/vault/encrypt.js +40 -0
  208. package/server/lib/vault/ids.js +9 -0
  209. package/server/lib/vault/index.js +14 -0
  210. package/server/lib/vault/jwt.js +55 -0
  211. package/server/lib/vault/password.js +10 -0
  212. package/server/lint/boundaries.js +77 -0
  213. package/server/logger/index.js +130 -0
  214. package/server/mail/drivers/console.js +31 -0
  215. package/server/mail/drivers/smtp.js +34 -0
  216. package/server/mail/imap.js +105 -0
  217. package/server/mail/inbound-store.js +58 -0
  218. package/server/mail/inbound.js +79 -0
  219. package/server/mail/index.js +112 -0
  220. package/server/mcp/debug-api.js +137 -0
  221. package/server/mcp/helpers.js +30 -0
  222. package/server/mcp/index.js +77 -0
  223. package/server/mcp/runtime.js +7 -0
  224. package/server/mcp/server.js +19 -0
  225. package/server/mcp/tools/debugging.js +133 -0
  226. package/server/mcp/tools/introspection.js +87 -0
  227. package/server/middlewares/cors.js +30 -0
  228. package/server/middlewares/index.js +3 -0
  229. package/server/middlewares/require-session.js +15 -0
  230. package/server/module-loader.js +9 -0
  231. package/server/pages/build-client.js +187 -0
  232. package/server/pages/build-css.js +47 -0
  233. package/server/pages/build-manifest.js +55 -0
  234. package/server/pages/build-plugins.js +75 -0
  235. package/server/pages/build-server.js +115 -0
  236. package/server/pages/build.js +116 -0
  237. package/server/pages/discovery.js +120 -0
  238. package/server/pages/fonts.js +128 -0
  239. package/server/pages/handler.js +276 -0
  240. package/server/pages/hmr.js +176 -0
  241. package/server/pages/pages-router.js +78 -0
  242. package/server/pages/ssr.js +276 -0
  243. package/server/pages/static.js +92 -0
  244. package/server/pages/watcher.js +90 -0
  245. package/server/queue/drivers/knex.js +67 -0
  246. package/server/queue/drivers/redis.js +91 -0
  247. package/server/queue/index.js +61 -0
  248. package/server/rate-limit/consume.js +21 -0
  249. package/server/rate-limit/drivers/memory.js +24 -0
  250. package/server/rate-limit/drivers/redis.js +32 -0
  251. package/server/rate-limit/index.js +33 -0
  252. package/server/redis/index.js +67 -0
  253. package/server/ring-buffer.js +44 -0
  254. package/server/route.js +4 -0
  255. package/server/router/api-router.js +317 -0
  256. package/server/router/cors.js +31 -0
  257. package/server/router/middleware.js +91 -0
  258. package/server/router/routes.js +132 -0
  259. package/server/server.js +35 -0
  260. package/server/session/helpers.js +21 -0
  261. package/server/session/index.js +89 -0
  262. package/server/static/index.js +36 -0
  263. package/server/system-jobs/index.js +50 -0
  264. package/server/system-routes/index.js +84 -0
  265. package/server/testing/index.js +263 -0
  266. package/server/validation.js +41 -0
  267. package/server/watcher.js +34 -0
  268. package/server/web-server.js +231 -0
  269. package/server/ws/discovery.js +54 -0
  270. package/server/ws/index.js +14 -0
  271. package/server/ws/realtime.js +318 -0
  272. package/server/ws/registry.js +17 -0
  273. package/server/ws/server.js +152 -0
  274. package/server/ws/ws-router.js +335 -0
@@ -0,0 +1,137 @@
1
+ import { toErrorMessage } from '../helpers.js';
2
+ const DEBUG_PREFIX = '/_mcp';
3
+ function isDebugRequest(url) {
4
+ return url.startsWith(DEBUG_PREFIX);
5
+ }
6
+ function createDebugHandler(runtime, deps, secret, logger) {
7
+ const { jobRunner, apiRouter, db, mode } = deps;
8
+ let port = deps.port ?? 0;
9
+
10
+ function numParam(params, key, fallback) {
11
+ return params.has(key) ? Number(params.get(key)) : fallback;
12
+ }
13
+ function strParam(params, key) {
14
+ return params.get(key) ?? void 0;
15
+ }
16
+
17
+ const routes = {
18
+ '/logs': (params, json) => {
19
+ const entries = logger.query({
20
+ level: strParam(params, 'level'),
21
+ logger: strParam(params, 'logger'),
22
+ since: strParam(params, 'since'),
23
+ limit: numParam(params, 'limit', 100),
24
+ });
25
+ json(200, { entries, total: entries.length });
26
+ },
27
+ '/errors': (params, json) => {
28
+ const entries = logger.errors(numParam(params, 'limit', 50), strParam(params, 'since'));
29
+ json(200, { entries, total: entries.length });
30
+ },
31
+ '/traces': (params, json) => {
32
+ const traces = logger.query({
33
+ message: 'request',
34
+ method: strParam(params, 'method'),
35
+ path: strParam(params, 'path'),
36
+ status: params.has('status') ? Number(params.get('status')) : void 0,
37
+ minDurationMs: params.has('minDurationMs') ? Number(params.get('minDurationMs')) : void 0,
38
+ since: strParam(params, 'since'),
39
+ limit: numParam(params, 'limit', 50),
40
+ });
41
+ json(200, { traces, total: traces.length });
42
+ },
43
+ '/traces/get': (params, json) => {
44
+ const id = params.get('id');
45
+ if (!id) {
46
+ json(400, { error: 'Missing id parameter' });
47
+ return;
48
+ }
49
+ const entries = logger.query({ requestId: id });
50
+ if (entries.length === 0) {
51
+ json(404, { error: 'Trace not found' });
52
+ return;
53
+ }
54
+ json(200, { requestId: id, entries });
55
+ },
56
+ '/events': (params, json) => {
57
+ const events = logger.query({
58
+ message: 'event',
59
+ eventName: strParam(params, 'eventName'),
60
+ since: strParam(params, 'since'),
61
+ limit: numParam(params, 'limit', 50),
62
+ });
63
+ json(200, { events, total: events.length });
64
+ },
65
+ '/jobs': (_params, json) => {
66
+ json(200, {
67
+ queueSize: jobRunner.dispatcher.size,
68
+ runnerActive: jobRunner.running,
69
+ });
70
+ },
71
+ '/queue': (_params, json) => {
72
+ json(200, { queueSize: jobRunner.dispatcher.size });
73
+ },
74
+ '/health': (_params, json) => {
75
+ json(200, {
76
+ status: 'running',
77
+ bootedAt: runtime.bootedAt,
78
+ uptime: Math.floor((Date.now() - new Date(runtime.bootedAt).getTime()) / 1e3),
79
+ port,
80
+ mode,
81
+ domains: [],
82
+ routes: apiRouter?.routes.length ?? 0,
83
+ });
84
+ },
85
+ '/schema': (params, json, res) => {
86
+ const format = params.get('format') ?? 'json';
87
+ db.introspectSchema()
88
+ .then((schema) => {
89
+ if (format === 'markdown') {
90
+ res.writeHead(200, { 'Content-Type': 'text/markdown' });
91
+ res.end(db.generateSchemaMarkdown(schema));
92
+ } else {
93
+ json(200, schema);
94
+ }
95
+ })
96
+ .catch((err) => {
97
+ json(500, { error: toErrorMessage(err) });
98
+ });
99
+ },
100
+ };
101
+
102
+ const handler = (req, res) => {
103
+ const url = req.url ?? '';
104
+ if (!url.startsWith(DEBUG_PREFIX)) return false;
105
+ const route = url.slice(DEBUG_PREFIX.length).split('?')[0];
106
+ const params = new URL(url, 'http://localhost').searchParams;
107
+ const json = (status, data) => {
108
+ res.writeHead(status, { 'Content-Type': 'application/json' });
109
+ res.end(JSON.stringify(data));
110
+ };
111
+ if (secret) {
112
+ const authHeader = req.headers['authorization'];
113
+ const bearerToken =
114
+ typeof authHeader === 'string' && authHeader.startsWith('Bearer ')
115
+ ? authHeader.slice(7)
116
+ : void 0;
117
+ const queryToken = params.get('token') ?? void 0;
118
+ const providedToken = bearerToken ?? queryToken;
119
+ if (providedToken !== secret) {
120
+ json(401, { error: 'Unauthorized \u2014 invalid or missing debug API token' });
121
+ return true;
122
+ }
123
+ }
124
+ const routeHandler = routes[route];
125
+ if (routeHandler) {
126
+ routeHandler(params, json, res);
127
+ } else {
128
+ json(404, { error: `Unknown debug route: ${route}` });
129
+ }
130
+ return true;
131
+ };
132
+ handler.setPort = (p) => {
133
+ port = p;
134
+ };
135
+ return handler;
136
+ }
137
+ export { createDebugHandler, isDebugRequest };
@@ -0,0 +1,30 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ const BUILD_DIR = '.build';
4
+ const DEV_INFO_FILE = 'dev.json';
5
+ async function readDevInfo(rootDir) {
6
+ try {
7
+ const raw = await fs.readFile(path.join(rootDir, BUILD_DIR, DEV_INFO_FILE), 'utf-8');
8
+ return JSON.parse(raw);
9
+ } catch {
10
+ return null;
11
+ }
12
+ }
13
+ async function fetchDebug(port, endpoint, params) {
14
+ const url = new URL(`http://localhost:${port}/_mcp${endpoint}`);
15
+ if (params) {
16
+ for (const [k, v] of Object.entries(params)) {
17
+ if (v !== void 0 && v !== '') url.searchParams.set(k, v);
18
+ }
19
+ }
20
+ const res = await fetch(url.toString());
21
+ if (!res.ok) {
22
+ const text = await res.text();
23
+ throw new Error(`Debug API error (${res.status}): ${text}`);
24
+ }
25
+ return res.json();
26
+ }
27
+ function textResult(text) {
28
+ return { content: [{ type: 'text', text }] };
29
+ }
30
+ export { fetchDebug, readDevInfo, textResult };
@@ -0,0 +1,77 @@
1
+ import path from 'node:path';
2
+ import { createMcpRuntime } from './runtime.js';
3
+ import { createDebugHandler, isDebugRequest } from './debug-api.js';
4
+ import { startMcpServer } from './server.js';
5
+
6
+ const DEBUG_PREFIX = '/_mcp';
7
+
8
+ class McpRouter {
9
+ _config;
10
+ _log;
11
+ _runtime;
12
+ _handler = null;
13
+
14
+ constructor(config, { log } = {}) {
15
+ this._config = config;
16
+ this._log = log;
17
+ this._runtime = config.enabled !== false ? createMcpRuntime() : null;
18
+ }
19
+
20
+ get enabled() {
21
+ return this._runtime !== null;
22
+ }
23
+
24
+ get bootedAt() {
25
+ return this._runtime?.bootedAt;
26
+ }
27
+
28
+ get runtime() {
29
+ return this._runtime;
30
+ }
31
+
32
+ init({ jobRunner, apiRouter, db, mode } = {}) {
33
+ if (!this._runtime) return;
34
+ this._handler = createDebugHandler(
35
+ this._runtime,
36
+ { jobRunner, apiRouter, db, mode },
37
+ this._config.secret,
38
+ this._log,
39
+ );
40
+ this._log.info('MCP debug API enabled at /_mcp/*');
41
+ }
42
+
43
+ async handle(req, res) {
44
+ if (!this._handler) return false;
45
+ const url = req.url ?? '';
46
+ if (!url.startsWith(DEBUG_PREFIX)) return false;
47
+ return this._handler(req, res);
48
+ }
49
+
50
+ setPort(port) {
51
+ if (this._handler) this._handler.setPort(port);
52
+ }
53
+
54
+ async writeDevMetadata(rootDir, port) {
55
+ if (!this._runtime) return;
56
+ this.setPort(port);
57
+ const buildDir = path.join(rootDir, '.build');
58
+ try {
59
+ const { mkdir, writeFile } = await import('node:fs/promises');
60
+ await mkdir(buildDir, { recursive: true });
61
+ await writeFile(
62
+ path.join(buildDir, 'dev.json'),
63
+ JSON.stringify({ port, pid: process.pid, bootedAt: this._runtime.bootedAt }),
64
+ );
65
+ } catch {}
66
+ }
67
+
68
+ async cleanup(rootDir) {
69
+ if (!this._runtime) return;
70
+ try {
71
+ const { unlink } = await import('node:fs/promises');
72
+ await unlink(path.join(rootDir, '.build', 'dev.json'));
73
+ } catch {}
74
+ }
75
+ }
76
+
77
+ export { McpRouter, createDebugHandler, createMcpRuntime, isDebugRequest, startMcpServer };
@@ -0,0 +1,7 @@
1
+ function createMcpRuntime() {
2
+ return {
3
+ bootedAt: new Date().toISOString(),
4
+ };
5
+ }
6
+
7
+ export { createMcpRuntime };
@@ -0,0 +1,19 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
3
+ import { registerIntrospectionTools } from './tools/introspection.js';
4
+ import { registerDebuggingTools } from './tools/debugging.js';
5
+ async function startMcpServer(rootDir) {
6
+ const server = new McpServer(
7
+ { name: 'arcway-mcp', version: '1.0.0' },
8
+ {
9
+ capabilities: { tools: {} },
10
+ instructions:
11
+ 'Arcway framework MCP server. Provides debugging, logging, and project introspection tools for AI coding agents.',
12
+ },
13
+ );
14
+ registerIntrospectionTools(server, rootDir);
15
+ registerDebuggingTools(server, rootDir);
16
+ const transport = new StdioServerTransport();
17
+ await server.connect(transport);
18
+ }
19
+ export { startMcpServer };
@@ -0,0 +1,133 @@
1
+ import { z } from 'zod';
2
+ import { readDevInfo, fetchDebug, textResult } from '../helpers.js';
3
+ import { toErrorMessage } from '../../helpers.js';
4
+
5
+ const NOT_RUNNING = 'Dev server is not running. Start it with `arcway dev`.';
6
+
7
+ function registerDebugTool(server, rootDir, name, meta, endpoint, mapArgs) {
8
+ server.registerTool(
9
+ name,
10
+ { ...meta, annotations: { readOnlyHint: true } },
11
+ async (args) => {
12
+ try {
13
+ const devInfo = await readDevInfo(rootDir);
14
+ if (!devInfo) return textResult(NOT_RUNNING);
15
+ const params = mapArgs ? mapArgs(args) : {};
16
+ const data = await fetchDebug(devInfo.port, endpoint, params);
17
+ return textResult(JSON.stringify(data, null, 2));
18
+ } catch (err) {
19
+ return textResult(`Error: ${toErrorMessage(err)}`);
20
+ }
21
+ },
22
+ );
23
+ }
24
+
25
+ function registerDebuggingTools(server, rootDir) {
26
+ registerDebugTool(server, rootDir, 'arcway_logs', {
27
+ title: 'Query Logs',
28
+ description:
29
+ 'Query recent log entries from the running dev server. Filter by level (debug/info/warn/error), domain, and time range.',
30
+ inputSchema: {
31
+ level: z.enum(['debug', 'info', 'warn', 'error']).optional().describe('Filter by log level'),
32
+ domain: z.string().optional().describe('Filter by domain name'),
33
+ since: z.string().optional().describe('ISO timestamp to filter entries after'),
34
+ limit: z.number().optional().describe('Max entries to return (default: 100)'),
35
+ },
36
+ }, '/logs', (args) => {
37
+ const params = {};
38
+ if (args.level) params.level = args.level;
39
+ if (args.domain) params.logger = args.domain;
40
+ if (args.since) params.since = args.since;
41
+ if (args.limit) params.limit = String(args.limit);
42
+ return params;
43
+ });
44
+
45
+ registerDebugTool(server, rootDir, 'arcway_errors', {
46
+ title: 'Recent Errors',
47
+ description: 'Get recent error log entries from the running dev server.',
48
+ inputSchema: {
49
+ limit: z.number().optional().describe('Max errors to return (default: 50)'),
50
+ since: z.string().optional().describe('ISO timestamp to filter errors after'),
51
+ },
52
+ }, '/errors', (args) => {
53
+ const params = {};
54
+ if (args.limit) params.limit = String(args.limit);
55
+ if (args.since) params.since = args.since;
56
+ return params;
57
+ });
58
+
59
+ // Request trace has special id-lookup logic
60
+ server.registerTool(
61
+ 'arcway_request_trace',
62
+ {
63
+ title: 'Request Traces',
64
+ description:
65
+ 'Query recent HTTP request traces. When given an id, returns all correlated log entries for that request across every subsystem.',
66
+ inputSchema: {
67
+ id: z.string().optional().describe('Specific trace ID to look up'),
68
+ method: z.string().optional().describe('Filter by HTTP method (GET, POST, etc.)'),
69
+ path: z.string().optional().describe('Filter by path (substring match)'),
70
+ status: z.number().optional().describe('Filter by status code'),
71
+ minDurationMs: z.number().optional().describe('Filter by minimum duration'),
72
+ limit: z.number().optional().describe('Max traces to return (default: 50)'),
73
+ },
74
+ annotations: { readOnlyHint: true },
75
+ },
76
+ async (args) => {
77
+ try {
78
+ const devInfo = await readDevInfo(rootDir);
79
+ if (!devInfo) return textResult(NOT_RUNNING);
80
+ if (args.id) {
81
+ const data = await fetchDebug(devInfo.port, '/traces/get', { id: args.id });
82
+ return textResult(JSON.stringify(data, null, 2));
83
+ }
84
+ const params = {};
85
+ if (args.method) params.method = args.method;
86
+ if (args.path) params.path = args.path;
87
+ if (args.status) params.status = String(args.status);
88
+ if (args.minDurationMs) params.minDurationMs = String(args.minDurationMs);
89
+ if (args.limit) params.limit = String(args.limit);
90
+ const data = await fetchDebug(devInfo.port, '/traces', params);
91
+ return textResult(JSON.stringify(data, null, 2));
92
+ } catch (err) {
93
+ return textResult(`Error: ${toErrorMessage(err)}`);
94
+ }
95
+ },
96
+ );
97
+
98
+ registerDebugTool(server, rootDir, 'arcway_event_trace', {
99
+ title: 'Event Traces',
100
+ description:
101
+ 'Query recent event emission traces. Shows which listeners fired (immediate + async), their results/errors, and timing for each event.',
102
+ inputSchema: {
103
+ eventName: z.string().optional().describe('Filter by event name (substring match)'),
104
+ domain: z.string().optional().describe('Filter by emitter domain'),
105
+ limit: z.number().optional().describe('Max traces to return (default: 50)'),
106
+ since: z.string().optional().describe('ISO timestamp to filter after'),
107
+ },
108
+ }, '/events', (args) => {
109
+ const params = {};
110
+ if (args.eventName) params.eventName = args.eventName;
111
+ if (args.domain) params.domain = args.domain;
112
+ if (args.limit) params.limit = String(args.limit);
113
+ if (args.since) params.since = args.since;
114
+ return params;
115
+ });
116
+
117
+ registerDebugTool(server, rootDir, 'arcway_job_status', {
118
+ title: 'Job Status',
119
+ description: 'Get the status of the job queue and runner from the running dev server.',
120
+ }, '/jobs');
121
+
122
+ registerDebugTool(server, rootDir, 'arcway_queue_inspect', {
123
+ title: 'Queue Inspect',
124
+ description: 'Inspect domain queues from the running dev server.',
125
+ }, '/queue');
126
+
127
+ registerDebugTool(server, rootDir, 'arcway_health', {
128
+ title: 'Server Health',
129
+ description:
130
+ 'Get dev server health status: uptime, port, mode, domain count, route count, log/trace buffer sizes.',
131
+ }, '/health');
132
+ }
133
+ export { registerDebuggingTools };
@@ -0,0 +1,87 @@
1
+ import path from 'node:path';
2
+ import { z } from 'zod';
3
+ import { makeConfig } from '#server/config/loader.js';
4
+ import { discoverRoutes } from '#server/router/routes.js';
5
+ import { createDB } from '#server/db/index.js';
6
+ import { loadEnvFiles } from '#server/env.js';
7
+ import { textResult } from '../helpers.js';
8
+ function registerIntrospectionTools(server, rootDir) {
9
+ server.registerTool(
10
+ 'arcway_project_overview',
11
+ {
12
+ title: 'Project Overview',
13
+ description:
14
+ 'Returns the full project topology: all routes (with methods, patterns, schemas), config overview. This is the single most useful tool for understanding an Arcway project.',
15
+ annotations: { readOnlyHint: true },
16
+ },
17
+ async () => {
18
+ try {
19
+ const config = await makeConfig(rootDir);
20
+ const routes = await discoverRoutes(config.api.dir);
21
+ const routeSummaries = routes.map((r) => ({
22
+ method: r.method,
23
+ pattern: r.pattern,
24
+ hasSchema: !!(r.config.schema?.query || r.config.schema?.body),
25
+ hasResponseSchema: !!r.config.schema?.response,
26
+ meta: r.config.meta,
27
+ }));
28
+ const overview = {
29
+ config: {
30
+ database: config.database.client,
31
+ server: {
32
+ port: config.server?.port ?? 3e3,
33
+ host: config.server?.host ?? '0.0.0.0',
34
+ },
35
+ hasSession: !!config.session,
36
+ hasRedis: !!(config.cache?.driver === 'redis' || config.events?.driver === 'redis'),
37
+ mcp: config.mcp,
38
+ },
39
+ routes: routeSummaries,
40
+ totals: {
41
+ routes: routeSummaries.length,
42
+ },
43
+ };
44
+ return textResult(JSON.stringify(overview, null, 2));
45
+ } catch (err) {
46
+ return textResult(`Error: ${err.message}`);
47
+ }
48
+ },
49
+ );
50
+ server.registerTool(
51
+ 'arcway_db_schema',
52
+ {
53
+ title: 'Database Schema',
54
+ description:
55
+ 'Introspect the database schema: column types, nullability, defaults, and foreign key references. Returns structured data or markdown.',
56
+ inputSchema: {
57
+ format: z
58
+ .enum(['json', 'markdown'])
59
+ .optional()
60
+ .describe('Output format: json (default) or markdown.'),
61
+ },
62
+ annotations: { readOnlyHint: true },
63
+ },
64
+ async ({ format }) => {
65
+ try {
66
+ loadEnvFiles(rootDir, 'development');
67
+ const config = await makeConfig(rootDir);
68
+ const db = await createDB(config.database);
69
+ try {
70
+ await db.runMigrations();
71
+ const schema = await db.introspectSchema();
72
+ await db.destroy();
73
+ if (format === 'markdown') {
74
+ return textResult(db.generateSchemaMarkdown(schema));
75
+ }
76
+ return textResult(JSON.stringify(schema, null, 2));
77
+ } catch (err) {
78
+ await db.destroy();
79
+ throw err;
80
+ }
81
+ } catch (err) {
82
+ return textResult(`Error: ${err.message}`);
83
+ }
84
+ },
85
+ );
86
+ }
87
+ export { registerIntrospectionTools };
@@ -0,0 +1,30 @@
1
+ function corsMiddleware(options) {
2
+ const methods = options.methods ?? ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'];
3
+ const allowedHeaders = options.allowedHeaders ?? ['Content-Type', 'Authorization'];
4
+ const maxAge = options.maxAge ?? 86400;
5
+ return (ctx) => {
6
+ const requestOrigin = ctx.req.headers['origin'];
7
+ if (!requestOrigin) return;
8
+ let allowOrigin;
9
+ if (options.origin === '*') {
10
+ allowOrigin = options.credentials ? requestOrigin : '*';
11
+ } else {
12
+ const origins = Array.isArray(options.origin) ? options.origin : [options.origin];
13
+ if (origins.includes(requestOrigin)) {
14
+ allowOrigin = requestOrigin;
15
+ }
16
+ }
17
+ if (!allowOrigin) return;
18
+ ctx.req.headers['access-control-allow-origin'] = allowOrigin;
19
+ ctx.req.headers['access-control-allow-methods'] = methods.join(', ');
20
+ ctx.req.headers['access-control-allow-headers'] = allowedHeaders.join(', ');
21
+ if (options.credentials) {
22
+ ctx.req.headers['access-control-allow-credentials'] = 'true';
23
+ }
24
+ if (options.exposedHeaders?.length) {
25
+ ctx.req.headers['access-control-expose-headers'] = options.exposedHeaders.join(', ');
26
+ }
27
+ ctx.req.headers['access-control-max-age'] = String(maxAge);
28
+ };
29
+ }
30
+ export { corsMiddleware };
@@ -0,0 +1,3 @@
1
+ import { requireSession } from './require-session.js';
2
+ import { corsMiddleware } from './cors.js';
3
+ export { corsMiddleware, requireSession };
@@ -0,0 +1,15 @@
1
+ function requireSession(options) {
2
+ const sessionKey = options?.sessionKey ?? 'userId';
3
+ const errorCode = options?.errorCode ?? 'UNAUTHORIZED';
4
+ const message = options?.message ?? 'Authentication required';
5
+ const status = options?.status ?? 401;
6
+ return (ctx) => {
7
+ if (!ctx.req.session || !ctx.req.session[sessionKey]) {
8
+ return {
9
+ status,
10
+ error: { code: errorCode, message },
11
+ };
12
+ }
13
+ };
14
+ }
15
+ export { requireSession };
@@ -0,0 +1,9 @@
1
+ import { pathToFileURL } from 'node:url';
2
+
3
+ let importCounter = 0;
4
+ async function loadModule(filePath) {
5
+ const cacheBuster = `?v=${++importCounter}`;
6
+ return import(pathToFileURL(filePath).href + cacheBuster);
7
+ }
8
+
9
+ export { loadModule };