waypoi 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (260) hide show
  1. package/.github/instructions/ui.instructions.md +42 -0
  2. package/.github/workflows/ci.yml +35 -0
  3. package/.github/workflows/publish.yml +71 -0
  4. package/.github/workflows/release.yml +48 -0
  5. package/.playwright-mcp/console-2026-04-04T01-41-10-746Z.log +2 -0
  6. package/.playwright-mcp/console-2026-04-04T01-41-28-799Z.log +3 -0
  7. package/.playwright-mcp/console-2026-04-05T02-26-51-909Z.log +76 -0
  8. package/.playwright-mcp/page-2026-04-04T01-41-10-816Z.yml +1 -0
  9. package/.playwright-mcp/page-2026-04-04T01-41-29-141Z.yml +77 -0
  10. package/.playwright-mcp/page-2026-04-04T01-41-42-633Z.yml +190 -0
  11. package/.playwright-mcp/page-2026-04-04T01-42-03-929Z.yml +262 -0
  12. package/.playwright-mcp/page-2026-04-04T02-12-54-813Z.yml +6 -0
  13. package/.playwright-mcp/page-2026-04-04T02-14-58-600Z.yml +190 -0
  14. package/.playwright-mcp/page-2026-04-04T02-15-03-923Z.yml +190 -0
  15. package/.playwright-mcp/page-2026-04-04T02-15-07-426Z.yml +190 -0
  16. package/.playwright-mcp/page-2026-04-04T02-15-25-729Z.yml +262 -0
  17. package/.playwright-mcp/page-2026-04-04T02-16-22-984Z.yml +262 -0
  18. package/.playwright-mcp/page-2026-04-04T02-17-00-599Z.yml +190 -0
  19. package/.playwright-mcp/page-2026-04-04T02-17-50-874Z.yml +190 -0
  20. package/.playwright-mcp/page-2026-04-05T02-26-55-570Z.yml +6 -0
  21. package/AGENTS.md +48 -0
  22. package/CHANGELOG.md +131 -0
  23. package/README.md +552 -0
  24. package/assets/agent-mode.png +0 -0
  25. package/assets/categorize.png +0 -0
  26. package/assets/dashboard.png +0 -0
  27. package/assets/endpoint-proxy.png +0 -0
  28. package/assets/icon.png +0 -0
  29. package/assets/mcp-generate-image.png +0 -0
  30. package/assets/mcp-understand-image.png +0 -0
  31. package/assets/peek-token-flow.png +0 -0
  32. package/assets/playground.png +0 -0
  33. package/assets/sankey.png +0 -0
  34. package/cli/index.ts +2805 -0
  35. package/cli/legacyRewrite.ts +108 -0
  36. package/cli/modelRef.ts +24 -0
  37. package/dist/cli/index.js +2536 -0
  38. package/dist/cli/legacyRewrite.js +92 -0
  39. package/dist/cli/modelRef.js +20 -0
  40. package/dist/src/benchmark/artifacts.js +131 -0
  41. package/dist/src/benchmark/capabilityClassifier.js +81 -0
  42. package/dist/src/benchmark/capabilityStore.js +144 -0
  43. package/dist/src/benchmark/config.js +238 -0
  44. package/dist/src/benchmark/gates.js +118 -0
  45. package/dist/src/benchmark/jobs.js +252 -0
  46. package/dist/src/benchmark/runner.js +1847 -0
  47. package/dist/src/benchmark/schema.js +353 -0
  48. package/dist/src/benchmark/suites.js +314 -0
  49. package/dist/src/benchmark/tinyQaDataset.js +422 -0
  50. package/dist/src/benchmark/types.js +25 -0
  51. package/dist/src/config.js +47 -0
  52. package/dist/src/index.js +178 -0
  53. package/dist/src/mcp/client.js +215 -0
  54. package/dist/src/mcp/discovery.js +226 -0
  55. package/dist/src/mcp/policy.js +65 -0
  56. package/dist/src/mcp/registry.js +129 -0
  57. package/dist/src/mcp/service.js +460 -0
  58. package/dist/src/middleware/auth.js +179 -0
  59. package/dist/src/middleware/requestCapture.js +192 -0
  60. package/dist/src/middleware/requestStats.js +118 -0
  61. package/dist/src/pools/builder.js +132 -0
  62. package/dist/src/pools/repository.js +69 -0
  63. package/dist/src/pools/scheduler.js +360 -0
  64. package/dist/src/pools/types.js +2 -0
  65. package/dist/src/protocols/adapters/dashscope.js +267 -0
  66. package/dist/src/protocols/adapters/inferenceV2.js +346 -0
  67. package/dist/src/protocols/adapters/openai.js +27 -0
  68. package/dist/src/protocols/registry.js +99 -0
  69. package/dist/src/protocols/types.js +2 -0
  70. package/dist/src/providers/health.js +153 -0
  71. package/dist/src/providers/importer.js +289 -0
  72. package/dist/src/providers/modelRegistry.js +313 -0
  73. package/dist/src/providers/repository.js +361 -0
  74. package/dist/src/providers/types.js +2 -0
  75. package/dist/src/routes/admin.js +531 -0
  76. package/dist/src/routes/audio.js +295 -0
  77. package/dist/src/routes/chat.js +240 -0
  78. package/dist/src/routes/embeddings.js +157 -0
  79. package/dist/src/routes/images.js +288 -0
  80. package/dist/src/routes/mcp.js +256 -0
  81. package/dist/src/routes/mcpService.js +100 -0
  82. package/dist/src/routes/models.js +48 -0
  83. package/dist/src/routes/responses.js +711 -0
  84. package/dist/src/routes/sessions.js +450 -0
  85. package/dist/src/routes/stats.js +270 -0
  86. package/dist/src/routes/ui.js +97 -0
  87. package/dist/src/routes/videos.js +107 -0
  88. package/dist/src/routing/router.js +338 -0
  89. package/dist/src/services/imageGeneration.js +280 -0
  90. package/dist/src/services/imageUnderstanding.js +352 -0
  91. package/dist/src/services/videoGeneration.js +79 -0
  92. package/dist/src/storage/captureRepository.js +1591 -0
  93. package/dist/src/storage/files.js +157 -0
  94. package/dist/src/storage/imageCache.js +346 -0
  95. package/dist/src/storage/repositories.js +388 -0
  96. package/dist/src/storage/sessionRepository.js +370 -0
  97. package/dist/src/storage/statsRepository.js +204 -0
  98. package/dist/src/transport/httpClient.js +126 -0
  99. package/dist/src/types.js +2 -0
  100. package/dist/src/utils/messageMedia.js +285 -0
  101. package/dist/src/utils/modelCapabilities.js +108 -0
  102. package/dist/src/utils/modelDiscovery.js +170 -0
  103. package/dist/src/version.js +5 -0
  104. package/dist/src/workers/captureRetention.js +25 -0
  105. package/dist/src/workers/configWatcher.js +91 -0
  106. package/dist/src/workers/healthChecker.js +21 -0
  107. package/dist/src/workers/statsRotation.js +41 -0
  108. package/docs/LLM/output_schema.md +312 -0
  109. package/docs/benchmark.md +208 -0
  110. package/docs/mcp-guidelines.md +125 -0
  111. package/docs/mcp-service.md +178 -0
  112. package/docs/opencode.md +86 -0
  113. package/docs/providers.md +79 -0
  114. package/examples/benchmark.config.yaml +28 -0
  115. package/examples/providers/alibaba-dashscope.yaml +88 -0
  116. package/examples/providers/alibaba-llm.yaml +64 -0
  117. package/examples/providers/alibaba-registry.yaml +7 -0
  118. package/examples/providers/inference-v2-ray.yaml +29 -0
  119. package/examples/scenarios/assets/omni-call-sample.wav +0 -0
  120. package/examples/scenarios/custom.jsonl +5 -0
  121. package/examples/scenarios/custom.yaml +40 -0
  122. package/model-form-v2.png +0 -0
  123. package/package.json +66 -0
  124. package/provider-form-v2.png +0 -0
  125. package/provider-form.png +0 -0
  126. package/scripts/manual-test.sh +11 -0
  127. package/scripts/version-from-git.js +23 -0
  128. package/src/benchmark/artifacts.ts +149 -0
  129. package/src/benchmark/capabilityClassifier.ts +99 -0
  130. package/src/benchmark/capabilityStore.ts +174 -0
  131. package/src/benchmark/config.ts +337 -0
  132. package/src/benchmark/gates.ts +164 -0
  133. package/src/benchmark/jobs.ts +312 -0
  134. package/src/benchmark/runner.ts +2519 -0
  135. package/src/benchmark/schema.ts +443 -0
  136. package/src/benchmark/suites.ts +323 -0
  137. package/src/benchmark/tinyQaDataset.ts +428 -0
  138. package/src/benchmark/types.ts +442 -0
  139. package/src/config.ts +44 -0
  140. package/src/index.ts +195 -0
  141. package/src/mcp/client.ts +305 -0
  142. package/src/mcp/discovery.ts +266 -0
  143. package/src/mcp/policy.ts +105 -0
  144. package/src/mcp/registry.ts +164 -0
  145. package/src/mcp/service.ts +611 -0
  146. package/src/middleware/auth.ts +251 -0
  147. package/src/middleware/requestCapture.ts +245 -0
  148. package/src/middleware/requestStats.ts +163 -0
  149. package/src/pools/builder.ts +159 -0
  150. package/src/pools/repository.ts +71 -0
  151. package/src/pools/scheduler.ts +425 -0
  152. package/src/pools/types.ts +117 -0
  153. package/src/protocols/adapters/dashscope.ts +335 -0
  154. package/src/protocols/adapters/inferenceV2.ts +428 -0
  155. package/src/protocols/adapters/openai.ts +32 -0
  156. package/src/protocols/registry.ts +117 -0
  157. package/src/protocols/types.ts +81 -0
  158. package/src/providers/health.ts +207 -0
  159. package/src/providers/importer.ts +402 -0
  160. package/src/providers/modelRegistry.ts +415 -0
  161. package/src/providers/repository.ts +439 -0
  162. package/src/providers/types.ts +113 -0
  163. package/src/routes/admin.ts +666 -0
  164. package/src/routes/audio.ts +372 -0
  165. package/src/routes/chat.ts +301 -0
  166. package/src/routes/embeddings.ts +197 -0
  167. package/src/routes/images.ts +356 -0
  168. package/src/routes/mcp.ts +320 -0
  169. package/src/routes/mcpService.ts +114 -0
  170. package/src/routes/models.ts +50 -0
  171. package/src/routes/responses.ts +872 -0
  172. package/src/routes/sessions.ts +558 -0
  173. package/src/routes/stats.ts +312 -0
  174. package/src/routes/ui.ts +96 -0
  175. package/src/routes/videos.ts +132 -0
  176. package/src/routing/router.ts +501 -0
  177. package/src/services/imageGeneration.ts +396 -0
  178. package/src/services/imageUnderstanding.ts +449 -0
  179. package/src/services/videoGeneration.ts +127 -0
  180. package/src/storage/captureRepository.ts +1835 -0
  181. package/src/storage/files.ts +178 -0
  182. package/src/storage/imageCache.ts +405 -0
  183. package/src/storage/repositories.ts +494 -0
  184. package/src/storage/sessionRepository.ts +419 -0
  185. package/src/storage/statsRepository.ts +238 -0
  186. package/src/transport/httpClient.ts +145 -0
  187. package/src/types.ts +322 -0
  188. package/src/utils/messageMedia.ts +293 -0
  189. package/src/utils/modelCapabilities.ts +161 -0
  190. package/src/utils/modelDiscovery.ts +203 -0
  191. package/src/workers/captureRetention.ts +25 -0
  192. package/src/workers/configWatcher.ts +115 -0
  193. package/src/workers/healthChecker.ts +22 -0
  194. package/src/workers/statsRotation.ts +49 -0
  195. package/tests/benchmarkAdminRoutes.test.ts +82 -0
  196. package/tests/benchmarkBasics.test.ts +116 -0
  197. package/tests/captureAdminRoutes.test.ts +420 -0
  198. package/tests/captureRepository.test.ts +797 -0
  199. package/tests/cliLegacyRewrite.test.ts +45 -0
  200. package/tests/imageGeneration.service.test.ts +107 -0
  201. package/tests/imageUnderstanding.service.test.ts +123 -0
  202. package/tests/mcpPolicy.test.ts +105 -0
  203. package/tests/mcpService.test.ts +1245 -0
  204. package/tests/modelRef.test.ts +23 -0
  205. package/tests/modelsRoutes.test.ts +154 -0
  206. package/tests/sessionMediaCache.test.ts +167 -0
  207. package/tests/statsRoutes.test.ts +323 -0
  208. package/tsconfig.json +15 -0
  209. package/ui/index.html +16 -0
  210. package/ui/package-lock.json +8521 -0
  211. package/ui/package.json +52 -0
  212. package/ui/postcss.config.js +6 -0
  213. package/ui/public/assets/apple-touch-icon.png +0 -0
  214. package/ui/public/assets/favicon-16.png +0 -0
  215. package/ui/public/assets/favicon-32.png +0 -0
  216. package/ui/public/assets/icon-192.png +0 -0
  217. package/ui/public/assets/icon-512.png +0 -0
  218. package/ui/src/App.tsx +27 -0
  219. package/ui/src/api/client.ts +1503 -0
  220. package/ui/src/components/EndpointUsageGuide.tsx +361 -0
  221. package/ui/src/components/Layout.tsx +124 -0
  222. package/ui/src/components/MessageContent.tsx +365 -0
  223. package/ui/src/components/ToolCallMessage.tsx +179 -0
  224. package/ui/src/components/ToolPicker.tsx +442 -0
  225. package/ui/src/components/messageContentParser.test.ts +41 -0
  226. package/ui/src/components/messageContentParser.ts +73 -0
  227. package/ui/src/components/thinkingPreview.test.ts +27 -0
  228. package/ui/src/components/thinkingPreview.ts +15 -0
  229. package/ui/src/components/toMermaidSankey.test.ts +78 -0
  230. package/ui/src/components/toMermaidSankey.ts +56 -0
  231. package/ui/src/components/ui/button.tsx +58 -0
  232. package/ui/src/components/ui/input.tsx +21 -0
  233. package/ui/src/components/ui/textarea.tsx +21 -0
  234. package/ui/src/lib/utils.ts +6 -0
  235. package/ui/src/main.tsx +9 -0
  236. package/ui/src/pages/AgentPlayground.tsx +2010 -0
  237. package/ui/src/pages/Benchmark.tsx +988 -0
  238. package/ui/src/pages/Dashboard.tsx +581 -0
  239. package/ui/src/pages/Peek.tsx +962 -0
  240. package/ui/src/pages/Settings.tsx +2013 -0
  241. package/ui/src/pages/agentPlaygroundPayload.test.ts +109 -0
  242. package/ui/src/pages/agentPlaygroundPayload.ts +97 -0
  243. package/ui/src/pages/agentThinkingContent.test.ts +50 -0
  244. package/ui/src/pages/agentThinkingContent.ts +57 -0
  245. package/ui/src/pages/dashboardTokenUsage.test.ts +66 -0
  246. package/ui/src/pages/dashboardTokenUsage.ts +36 -0
  247. package/ui/src/pages/imageUpload.test.ts +39 -0
  248. package/ui/src/pages/imageUpload.ts +71 -0
  249. package/ui/src/pages/peekFilters.test.ts +29 -0
  250. package/ui/src/pages/peekFilters.ts +13 -0
  251. package/ui/src/pages/peekMedia.test.ts +58 -0
  252. package/ui/src/pages/peekMedia.ts +148 -0
  253. package/ui/src/pages/sessionAutoTitle.test.ts +128 -0
  254. package/ui/src/pages/sessionAutoTitle.ts +106 -0
  255. package/ui/src/stores/settings.ts +58 -0
  256. package/ui/src/styles/globals.css +223 -0
  257. package/ui/src/vite-env.d.ts +8 -0
  258. package/ui/tailwind.config.js +106 -0
  259. package/ui/tsconfig.json +32 -0
  260. package/ui/vite.config.ts +37 -0
@@ -0,0 +1,320 @@
1
+ import { FastifyInstance, FastifyRequest, FastifyReply } from "fastify";
2
+ import { StoragePaths } from "../storage/files";
3
+ import { appConfig } from "../config";
4
+ import {
5
+ listMcpServers,
6
+ getMcpServer,
7
+ addMcpServer,
8
+ updateMcpServer,
9
+ removeMcpServer,
10
+ } from "../mcp/registry";
11
+ import {
12
+ discoverAllTools,
13
+ discoverServerTools,
14
+ discoverBuiltinTools,
15
+ getBuiltinVirtualServer,
16
+ getCachedTools,
17
+ getCachedToolsForServer,
18
+ executeTool,
19
+ disconnectServer,
20
+ isServerConnected,
21
+ BUILTIN_SERVER_ID,
22
+ } from "../mcp/discovery";
23
+
24
+ /**
25
+ * MCP Routes
26
+ *
27
+ * Admin API for managing MCP servers and tools.
28
+ *
29
+ * Endpoints:
30
+ * GET /admin/mcp/servers - List all MCP servers
31
+ * POST /admin/mcp/servers - Add a new MCP server
32
+ * GET /admin/mcp/servers/:id - Get server by ID
33
+ * PUT /admin/mcp/servers/:id - Update server
34
+ * DELETE /admin/mcp/servers/:id - Remove server
35
+ * POST /admin/mcp/servers/:id/connect - Connect and discover tools
36
+ * POST /admin/mcp/servers/:id/disconnect - Disconnect from server
37
+ *
38
+ * GET /admin/mcp/tools - List all discovered tools
39
+ * POST /admin/mcp/tools/discover - Discover tools from all servers
40
+ * POST /admin/mcp/tools/execute - Execute a tool
41
+ */
42
+
43
+ export async function registerMcpRoutes(
44
+ app: FastifyInstance,
45
+ paths: StoragePaths
46
+ ): Promise<void> {
47
+ // ─────────────────────────────────────────────────────────────────────────────
48
+ // Server management
49
+ // ─────────────────────────────────────────────────────────────────────────────
50
+
51
+ app.get("/admin/mcp/servers", async (_req: FastifyRequest, reply: FastifyReply) => {
52
+ try {
53
+ const servers = await listMcpServers(paths);
54
+ const builtin = getBuiltinVirtualServer();
55
+ return reply.send({
56
+ object: "list",
57
+ data: [
58
+ ...(builtin ? [builtin] : []),
59
+ ...servers.map((s) => ({
60
+ ...s,
61
+ connected: isServerConnected(s.id),
62
+ })),
63
+ ],
64
+ });
65
+ } catch (error) {
66
+ app.log.error(error, "Failed to list MCP servers");
67
+ return reply.status(500).send({
68
+ error: { message: "Failed to list servers", type: "internal_error" },
69
+ });
70
+ }
71
+ });
72
+
73
+ app.post(
74
+ "/admin/mcp/servers",
75
+ async (
76
+ req: FastifyRequest<{ Body: { name: string; url: string; enabled?: boolean } }>,
77
+ reply: FastifyReply
78
+ ) => {
79
+ try {
80
+ if (!req.body?.name || !req.body?.url) {
81
+ return reply.status(400).send({
82
+ error: { message: "name and url are required", type: "invalid_request" },
83
+ });
84
+ }
85
+
86
+ const server = await addMcpServer(paths, req.body);
87
+ return reply.status(201).send(server);
88
+ } catch (error) {
89
+ if ((error as Error).message.includes("already exists")) {
90
+ return reply.status(409).send({
91
+ error: { message: (error as Error).message, type: "conflict" },
92
+ });
93
+ }
94
+ app.log.error(error, "Failed to add MCP server");
95
+ return reply.status(500).send({
96
+ error: { message: "Failed to add server", type: "internal_error" },
97
+ });
98
+ }
99
+ }
100
+ );
101
+
102
+ app.get(
103
+ "/admin/mcp/servers/:id",
104
+ async (req: FastifyRequest<{ Params: { id: string } }>, reply: FastifyReply) => {
105
+ try {
106
+ if (req.params.id === BUILTIN_SERVER_ID) {
107
+ const builtin = getBuiltinVirtualServer();
108
+ if (!builtin) {
109
+ return reply.status(404).send({
110
+ error: { message: "Built-in server not yet initialized", type: "not_found" },
111
+ });
112
+ }
113
+ return reply.send({
114
+ ...builtin,
115
+ tools: getCachedToolsForServer(BUILTIN_SERVER_ID),
116
+ });
117
+ }
118
+
119
+ const server = await getMcpServer(paths, req.params.id);
120
+ if (!server) {
121
+ return reply.status(404).send({
122
+ error: { message: "Server not found", type: "not_found" },
123
+ });
124
+ }
125
+ return reply.send({
126
+ ...server,
127
+ connected: isServerConnected(server.id),
128
+ tools: getCachedToolsForServer(server.id),
129
+ });
130
+ } catch (error) {
131
+ app.log.error(error, "Failed to get MCP server");
132
+ return reply.status(500).send({
133
+ error: { message: "Failed to get server", type: "internal_error" },
134
+ });
135
+ }
136
+ }
137
+ );
138
+
139
+ app.put(
140
+ "/admin/mcp/servers/:id",
141
+ async (
142
+ req: FastifyRequest<{
143
+ Params: { id: string };
144
+ Body: { name?: string; url?: string; enabled?: boolean };
145
+ }>,
146
+ reply: FastifyReply
147
+ ) => {
148
+ try {
149
+ const server = await updateMcpServer(paths, req.params.id, req.body || {});
150
+ if (!server) {
151
+ return reply.status(404).send({
152
+ error: { message: "Server not found", type: "not_found" },
153
+ });
154
+ }
155
+ return reply.send(server);
156
+ } catch (error) {
157
+ app.log.error(error, "Failed to update MCP server");
158
+ return reply.status(500).send({
159
+ error: { message: "Failed to update server", type: "internal_error" },
160
+ });
161
+ }
162
+ }
163
+ );
164
+
165
+ app.delete(
166
+ "/admin/mcp/servers/:id",
167
+ async (req: FastifyRequest<{ Params: { id: string } }>, reply: FastifyReply) => {
168
+ try {
169
+ if (req.params.id === BUILTIN_SERVER_ID) {
170
+ return reply.status(403).send({
171
+ error: { message: "Cannot delete the built-in waypoi server", type: "forbidden" },
172
+ });
173
+ }
174
+
175
+ // Disconnect first
176
+ await disconnectServer(req.params.id);
177
+
178
+ const deleted = await removeMcpServer(paths, req.params.id);
179
+ if (!deleted) {
180
+ return reply.status(404).send({
181
+ error: { message: "Server not found", type: "not_found" },
182
+ });
183
+ }
184
+ return reply.status(204).send();
185
+ } catch (error) {
186
+ app.log.error(error, "Failed to remove MCP server");
187
+ return reply.status(500).send({
188
+ error: { message: "Failed to remove server", type: "internal_error" },
189
+ });
190
+ }
191
+ }
192
+ );
193
+
194
+ app.post(
195
+ "/admin/mcp/servers/:id/connect",
196
+ async (req: FastifyRequest<{ Params: { id: string } }>, reply: FastifyReply) => {
197
+ try {
198
+ if (req.params.id === BUILTIN_SERVER_ID) {
199
+ const port = appConfig.port;
200
+ const tools = await discoverBuiltinTools(paths, `http://localhost:${port}/mcp`);
201
+ return reply.send({
202
+ connected: true,
203
+ toolCount: tools.length,
204
+ tools: tools.map((t) => ({ name: t.name, description: t.description })),
205
+ });
206
+ }
207
+
208
+ const server = await getMcpServer(paths, req.params.id);
209
+ if (!server) {
210
+ return reply.status(404).send({
211
+ error: { message: "Server not found", type: "not_found" },
212
+ });
213
+ }
214
+
215
+ const tools = await discoverServerTools(paths, server);
216
+ return reply.send({
217
+ connected: true,
218
+ toolCount: tools.length,
219
+ tools: tools.map((t) => ({ name: t.name, description: t.description })),
220
+ });
221
+ } catch (error) {
222
+ app.log.error(error, "Failed to connect to MCP server");
223
+ return reply.status(500).send({
224
+ error: { message: "Failed to connect", type: "connection_error" },
225
+ });
226
+ }
227
+ }
228
+ );
229
+
230
+ app.post(
231
+ "/admin/mcp/servers/:id/disconnect",
232
+ async (req: FastifyRequest<{ Params: { id: string } }>, reply: FastifyReply) => {
233
+ try {
234
+ if (req.params.id === BUILTIN_SERVER_ID) {
235
+ return reply.status(403).send({
236
+ error: { message: "Cannot disconnect the built-in waypoi server", type: "forbidden" },
237
+ });
238
+ }
239
+ await disconnectServer(req.params.id);
240
+ return reply.send({ disconnected: true });
241
+ } catch (error) {
242
+ app.log.error(error, "Failed to disconnect from MCP server");
243
+ return reply.status(500).send({
244
+ error: { message: "Failed to disconnect", type: "internal_error" },
245
+ });
246
+ }
247
+ }
248
+ );
249
+
250
+ // ─────────────────────────────────────────────────────────────────────────────
251
+ // Tool management
252
+ // ─────────────────────────────────────────────────────────────────────────────
253
+
254
+ app.get("/admin/mcp/tools", async (_req: FastifyRequest, reply: FastifyReply) => {
255
+ try {
256
+ const tools = getCachedTools();
257
+ return reply.send({
258
+ object: "list",
259
+ data: tools,
260
+ });
261
+ } catch (error) {
262
+ app.log.error(error, "Failed to list tools");
263
+ return reply.status(500).send({
264
+ error: { message: "Failed to list tools", type: "internal_error" },
265
+ });
266
+ }
267
+ });
268
+
269
+ app.post("/admin/mcp/tools/discover", async (_req: FastifyRequest, reply: FastifyReply) => {
270
+ try {
271
+ const tools = await discoverAllTools(paths);
272
+ return reply.send({
273
+ discovered: tools.length,
274
+ tools: tools.map((t) => ({
275
+ name: t.name,
276
+ description: t.description,
277
+ serverName: t.serverName,
278
+ })),
279
+ });
280
+ } catch (error) {
281
+ app.log.error(error, "Failed to discover tools");
282
+ return reply.status(500).send({
283
+ error: { message: "Failed to discover tools", type: "internal_error" },
284
+ });
285
+ }
286
+ });
287
+
288
+ app.post(
289
+ "/admin/mcp/tools/execute",
290
+ async (
291
+ req: FastifyRequest<{ Body: { name: string; arguments: Record<string, unknown> } }>,
292
+ reply: FastifyReply
293
+ ) => {
294
+ try {
295
+ if (!req.body?.name) {
296
+ return reply.status(400).send({
297
+ error: { message: "Tool name is required", type: "invalid_request" },
298
+ });
299
+ }
300
+
301
+ const result = await executeTool(req.body.name, req.body.arguments ?? {});
302
+
303
+ if (result.isError) {
304
+ return reply.status(500).send({
305
+ error: { message: result.content, type: "tool_error" },
306
+ });
307
+ }
308
+
309
+ return reply.send({
310
+ result: result.content,
311
+ });
312
+ } catch (error) {
313
+ app.log.error(error, "Failed to execute tool");
314
+ return reply.status(500).send({
315
+ error: { message: "Failed to execute tool", type: "internal_error" },
316
+ });
317
+ }
318
+ }
319
+ );
320
+ }
@@ -0,0 +1,114 @@
1
+ import { FastifyInstance, FastifyReply, FastifyRequest } from "fastify";
2
+ import { StoragePaths } from "../storage/files";
3
+ import {
4
+ createMcpService,
5
+ McpService,
6
+ McpServiceDependencyOverrides,
7
+ } from "../mcp/service";
8
+
9
+ let activeMcpService: McpService | null = null;
10
+
11
+ export async function registerMcpServiceRoutes(
12
+ app: FastifyInstance,
13
+ paths: StoragePaths,
14
+ deps?: McpServiceDependencyOverrides
15
+ ): Promise<void> {
16
+ const mcpService = createMcpService(paths, deps);
17
+ activeMcpService = mcpService;
18
+
19
+ app.route({
20
+ method: ["GET", "POST", "DELETE"],
21
+ url: "/mcp",
22
+ handler: async (req: FastifyRequest, reply: FastifyReply) => {
23
+ if (!isLocalRequest(req)) {
24
+ reply.code(403).send({
25
+ error: {
26
+ message: "Forbidden: /mcp is restricted to localhost",
27
+ type: "forbidden",
28
+ },
29
+ });
30
+ return;
31
+ }
32
+
33
+ if (req.method === "GET") {
34
+ reply.code(405).header("Allow", "POST, DELETE").send("Method Not Allowed");
35
+ return;
36
+ }
37
+
38
+ await mcpService.handleRequest(req.raw, reply.raw, req.body);
39
+ reply.hijack();
40
+ },
41
+ });
42
+ }
43
+
44
+ export async function closeMcpServiceRoutes(): Promise<void> {
45
+ if (!activeMcpService) {
46
+ return;
47
+ }
48
+ await activeMcpService.close();
49
+ activeMcpService = null;
50
+ }
51
+
52
+ function isLocalRequest(req: FastifyRequest): boolean {
53
+ const address = normalizeAddress(req.ip ?? req.socket.remoteAddress);
54
+ if (address && !isLocalHost(address)) {
55
+ return false;
56
+ }
57
+
58
+ const hostCandidates = [
59
+ firstHeaderValue(req.headers.host),
60
+ firstHeaderValue(req.headers["x-forwarded-host"]),
61
+ extractHostFromOrigin(firstHeaderValue(req.headers.origin)),
62
+ ]
63
+ .map((value) => normalizeHost(value))
64
+ .filter((value): value is string => Boolean(value));
65
+
66
+ return hostCandidates.every((host) => isLocalHost(host));
67
+ }
68
+
69
+ function firstHeaderValue(header: string | string[] | undefined): string | undefined {
70
+ if (typeof header === "string") {
71
+ return header;
72
+ }
73
+ if (Array.isArray(header) && header.length > 0) {
74
+ return header[0];
75
+ }
76
+ return undefined;
77
+ }
78
+
79
+ function extractHostFromOrigin(origin: string | undefined): string | undefined {
80
+ if (!origin) return undefined;
81
+ try {
82
+ const parsed = new URL(origin);
83
+ return parsed.host;
84
+ } catch {
85
+ return undefined;
86
+ }
87
+ }
88
+
89
+ function normalizeHost(value: string | undefined): string | null {
90
+ if (!value) return null;
91
+ const host = value.split(",")[0].trim().toLowerCase();
92
+ if (!host) return null;
93
+ if (host.startsWith("[")) {
94
+ const end = host.indexOf("]");
95
+ if (end > -1) {
96
+ return host.slice(1, end);
97
+ }
98
+ }
99
+ return host.split(":")[0];
100
+ }
101
+
102
+ function normalizeAddress(value: string | undefined): string | null {
103
+ if (!value) return null;
104
+ const trimmed = value.trim().toLowerCase();
105
+ if (!trimmed) return null;
106
+ if (trimmed.startsWith("::ffff:")) {
107
+ return trimmed.replace("::ffff:", "");
108
+ }
109
+ return trimmed;
110
+ }
111
+
112
+ function isLocalHost(host: string): boolean {
113
+ return host === "localhost" || host === "127.0.0.1" || host === "::1";
114
+ }
@@ -0,0 +1,50 @@
1
+ import { FastifyInstance } from "fastify";
2
+ import { StoragePaths } from "../storage/files";
3
+ import { EndpointType, ModelModality } from "../types";
4
+ import { getAvailableSmartPool, listModelsForApi } from "../providers/modelRegistry";
5
+
6
+ export async function registerModelsRoutes(app: FastifyInstance, paths: StoragePaths): Promise<void> {
7
+ app.get("/v1/models", async (req, reply) => {
8
+ const query = (req.query ?? {}) as { available_only?: string | boolean };
9
+ const availableOnly = query.available_only === true || query.available_only === "true";
10
+ const concrete = await listModelsForApi(paths, { availableOnly });
11
+ const smart = await getAvailableSmartPool(paths);
12
+ const poolEntries = smart
13
+ ? [
14
+ {
15
+ id: smart.alias,
16
+ object: "model" as const,
17
+ owned_by: "waypoi",
18
+ endpoint_type: inferEndpointType(smart.capabilities.output),
19
+ capabilities: smart.capabilities,
20
+ slug: smart.alias,
21
+ waypoi_pool: {
22
+ id: smart.id,
23
+ strategy: smart.strategy,
24
+ candidateCount: smart.candidateCount,
25
+ scoreSource: "benchmark.livebench",
26
+ },
27
+ },
28
+ ]
29
+ : [];
30
+ const data = [...concrete, ...poolEntries];
31
+ // Include both `data` and `models` for broader client compatibility.
32
+ reply.send({ object: "list", data, models: data });
33
+ });
34
+ }
35
+
36
+ function inferEndpointType(output: ModelModality[]): EndpointType {
37
+ if (output.includes("text")) {
38
+ return "llm";
39
+ }
40
+ if (output.includes("embedding")) {
41
+ return "embedding";
42
+ }
43
+ if (output.includes("image")) {
44
+ return "diffusion";
45
+ }
46
+ if (output.includes("audio")) {
47
+ return "audio";
48
+ }
49
+ return "llm";
50
+ }