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.
- package/.github/instructions/ui.instructions.md +42 -0
- package/.github/workflows/ci.yml +35 -0
- package/.github/workflows/publish.yml +71 -0
- package/.github/workflows/release.yml +48 -0
- package/.playwright-mcp/console-2026-04-04T01-41-10-746Z.log +2 -0
- package/.playwright-mcp/console-2026-04-04T01-41-28-799Z.log +3 -0
- package/.playwright-mcp/console-2026-04-05T02-26-51-909Z.log +76 -0
- package/.playwright-mcp/page-2026-04-04T01-41-10-816Z.yml +1 -0
- package/.playwright-mcp/page-2026-04-04T01-41-29-141Z.yml +77 -0
- package/.playwright-mcp/page-2026-04-04T01-41-42-633Z.yml +190 -0
- package/.playwright-mcp/page-2026-04-04T01-42-03-929Z.yml +262 -0
- package/.playwright-mcp/page-2026-04-04T02-12-54-813Z.yml +6 -0
- package/.playwright-mcp/page-2026-04-04T02-14-58-600Z.yml +190 -0
- package/.playwright-mcp/page-2026-04-04T02-15-03-923Z.yml +190 -0
- package/.playwright-mcp/page-2026-04-04T02-15-07-426Z.yml +190 -0
- package/.playwright-mcp/page-2026-04-04T02-15-25-729Z.yml +262 -0
- package/.playwright-mcp/page-2026-04-04T02-16-22-984Z.yml +262 -0
- package/.playwright-mcp/page-2026-04-04T02-17-00-599Z.yml +190 -0
- package/.playwright-mcp/page-2026-04-04T02-17-50-874Z.yml +190 -0
- package/.playwright-mcp/page-2026-04-05T02-26-55-570Z.yml +6 -0
- package/AGENTS.md +48 -0
- package/CHANGELOG.md +131 -0
- package/README.md +552 -0
- package/assets/agent-mode.png +0 -0
- package/assets/categorize.png +0 -0
- package/assets/dashboard.png +0 -0
- package/assets/endpoint-proxy.png +0 -0
- package/assets/icon.png +0 -0
- package/assets/mcp-generate-image.png +0 -0
- package/assets/mcp-understand-image.png +0 -0
- package/assets/peek-token-flow.png +0 -0
- package/assets/playground.png +0 -0
- package/assets/sankey.png +0 -0
- package/cli/index.ts +2805 -0
- package/cli/legacyRewrite.ts +108 -0
- package/cli/modelRef.ts +24 -0
- package/dist/cli/index.js +2536 -0
- package/dist/cli/legacyRewrite.js +92 -0
- package/dist/cli/modelRef.js +20 -0
- package/dist/src/benchmark/artifacts.js +131 -0
- package/dist/src/benchmark/capabilityClassifier.js +81 -0
- package/dist/src/benchmark/capabilityStore.js +144 -0
- package/dist/src/benchmark/config.js +238 -0
- package/dist/src/benchmark/gates.js +118 -0
- package/dist/src/benchmark/jobs.js +252 -0
- package/dist/src/benchmark/runner.js +1847 -0
- package/dist/src/benchmark/schema.js +353 -0
- package/dist/src/benchmark/suites.js +314 -0
- package/dist/src/benchmark/tinyQaDataset.js +422 -0
- package/dist/src/benchmark/types.js +25 -0
- package/dist/src/config.js +47 -0
- package/dist/src/index.js +178 -0
- package/dist/src/mcp/client.js +215 -0
- package/dist/src/mcp/discovery.js +226 -0
- package/dist/src/mcp/policy.js +65 -0
- package/dist/src/mcp/registry.js +129 -0
- package/dist/src/mcp/service.js +460 -0
- package/dist/src/middleware/auth.js +179 -0
- package/dist/src/middleware/requestCapture.js +192 -0
- package/dist/src/middleware/requestStats.js +118 -0
- package/dist/src/pools/builder.js +132 -0
- package/dist/src/pools/repository.js +69 -0
- package/dist/src/pools/scheduler.js +360 -0
- package/dist/src/pools/types.js +2 -0
- package/dist/src/protocols/adapters/dashscope.js +267 -0
- package/dist/src/protocols/adapters/inferenceV2.js +346 -0
- package/dist/src/protocols/adapters/openai.js +27 -0
- package/dist/src/protocols/registry.js +99 -0
- package/dist/src/protocols/types.js +2 -0
- package/dist/src/providers/health.js +153 -0
- package/dist/src/providers/importer.js +289 -0
- package/dist/src/providers/modelRegistry.js +313 -0
- package/dist/src/providers/repository.js +361 -0
- package/dist/src/providers/types.js +2 -0
- package/dist/src/routes/admin.js +531 -0
- package/dist/src/routes/audio.js +295 -0
- package/dist/src/routes/chat.js +240 -0
- package/dist/src/routes/embeddings.js +157 -0
- package/dist/src/routes/images.js +288 -0
- package/dist/src/routes/mcp.js +256 -0
- package/dist/src/routes/mcpService.js +100 -0
- package/dist/src/routes/models.js +48 -0
- package/dist/src/routes/responses.js +711 -0
- package/dist/src/routes/sessions.js +450 -0
- package/dist/src/routes/stats.js +270 -0
- package/dist/src/routes/ui.js +97 -0
- package/dist/src/routes/videos.js +107 -0
- package/dist/src/routing/router.js +338 -0
- package/dist/src/services/imageGeneration.js +280 -0
- package/dist/src/services/imageUnderstanding.js +352 -0
- package/dist/src/services/videoGeneration.js +79 -0
- package/dist/src/storage/captureRepository.js +1591 -0
- package/dist/src/storage/files.js +157 -0
- package/dist/src/storage/imageCache.js +346 -0
- package/dist/src/storage/repositories.js +388 -0
- package/dist/src/storage/sessionRepository.js +370 -0
- package/dist/src/storage/statsRepository.js +204 -0
- package/dist/src/transport/httpClient.js +126 -0
- package/dist/src/types.js +2 -0
- package/dist/src/utils/messageMedia.js +285 -0
- package/dist/src/utils/modelCapabilities.js +108 -0
- package/dist/src/utils/modelDiscovery.js +170 -0
- package/dist/src/version.js +5 -0
- package/dist/src/workers/captureRetention.js +25 -0
- package/dist/src/workers/configWatcher.js +91 -0
- package/dist/src/workers/healthChecker.js +21 -0
- package/dist/src/workers/statsRotation.js +41 -0
- package/docs/LLM/output_schema.md +312 -0
- package/docs/benchmark.md +208 -0
- package/docs/mcp-guidelines.md +125 -0
- package/docs/mcp-service.md +178 -0
- package/docs/opencode.md +86 -0
- package/docs/providers.md +79 -0
- package/examples/benchmark.config.yaml +28 -0
- package/examples/providers/alibaba-dashscope.yaml +88 -0
- package/examples/providers/alibaba-llm.yaml +64 -0
- package/examples/providers/alibaba-registry.yaml +7 -0
- package/examples/providers/inference-v2-ray.yaml +29 -0
- package/examples/scenarios/assets/omni-call-sample.wav +0 -0
- package/examples/scenarios/custom.jsonl +5 -0
- package/examples/scenarios/custom.yaml +40 -0
- package/model-form-v2.png +0 -0
- package/package.json +66 -0
- package/provider-form-v2.png +0 -0
- package/provider-form.png +0 -0
- package/scripts/manual-test.sh +11 -0
- package/scripts/version-from-git.js +23 -0
- package/src/benchmark/artifacts.ts +149 -0
- package/src/benchmark/capabilityClassifier.ts +99 -0
- package/src/benchmark/capabilityStore.ts +174 -0
- package/src/benchmark/config.ts +337 -0
- package/src/benchmark/gates.ts +164 -0
- package/src/benchmark/jobs.ts +312 -0
- package/src/benchmark/runner.ts +2519 -0
- package/src/benchmark/schema.ts +443 -0
- package/src/benchmark/suites.ts +323 -0
- package/src/benchmark/tinyQaDataset.ts +428 -0
- package/src/benchmark/types.ts +442 -0
- package/src/config.ts +44 -0
- package/src/index.ts +195 -0
- package/src/mcp/client.ts +305 -0
- package/src/mcp/discovery.ts +266 -0
- package/src/mcp/policy.ts +105 -0
- package/src/mcp/registry.ts +164 -0
- package/src/mcp/service.ts +611 -0
- package/src/middleware/auth.ts +251 -0
- package/src/middleware/requestCapture.ts +245 -0
- package/src/middleware/requestStats.ts +163 -0
- package/src/pools/builder.ts +159 -0
- package/src/pools/repository.ts +71 -0
- package/src/pools/scheduler.ts +425 -0
- package/src/pools/types.ts +117 -0
- package/src/protocols/adapters/dashscope.ts +335 -0
- package/src/protocols/adapters/inferenceV2.ts +428 -0
- package/src/protocols/adapters/openai.ts +32 -0
- package/src/protocols/registry.ts +117 -0
- package/src/protocols/types.ts +81 -0
- package/src/providers/health.ts +207 -0
- package/src/providers/importer.ts +402 -0
- package/src/providers/modelRegistry.ts +415 -0
- package/src/providers/repository.ts +439 -0
- package/src/providers/types.ts +113 -0
- package/src/routes/admin.ts +666 -0
- package/src/routes/audio.ts +372 -0
- package/src/routes/chat.ts +301 -0
- package/src/routes/embeddings.ts +197 -0
- package/src/routes/images.ts +356 -0
- package/src/routes/mcp.ts +320 -0
- package/src/routes/mcpService.ts +114 -0
- package/src/routes/models.ts +50 -0
- package/src/routes/responses.ts +872 -0
- package/src/routes/sessions.ts +558 -0
- package/src/routes/stats.ts +312 -0
- package/src/routes/ui.ts +96 -0
- package/src/routes/videos.ts +132 -0
- package/src/routing/router.ts +501 -0
- package/src/services/imageGeneration.ts +396 -0
- package/src/services/imageUnderstanding.ts +449 -0
- package/src/services/videoGeneration.ts +127 -0
- package/src/storage/captureRepository.ts +1835 -0
- package/src/storage/files.ts +178 -0
- package/src/storage/imageCache.ts +405 -0
- package/src/storage/repositories.ts +494 -0
- package/src/storage/sessionRepository.ts +419 -0
- package/src/storage/statsRepository.ts +238 -0
- package/src/transport/httpClient.ts +145 -0
- package/src/types.ts +322 -0
- package/src/utils/messageMedia.ts +293 -0
- package/src/utils/modelCapabilities.ts +161 -0
- package/src/utils/modelDiscovery.ts +203 -0
- package/src/workers/captureRetention.ts +25 -0
- package/src/workers/configWatcher.ts +115 -0
- package/src/workers/healthChecker.ts +22 -0
- package/src/workers/statsRotation.ts +49 -0
- package/tests/benchmarkAdminRoutes.test.ts +82 -0
- package/tests/benchmarkBasics.test.ts +116 -0
- package/tests/captureAdminRoutes.test.ts +420 -0
- package/tests/captureRepository.test.ts +797 -0
- package/tests/cliLegacyRewrite.test.ts +45 -0
- package/tests/imageGeneration.service.test.ts +107 -0
- package/tests/imageUnderstanding.service.test.ts +123 -0
- package/tests/mcpPolicy.test.ts +105 -0
- package/tests/mcpService.test.ts +1245 -0
- package/tests/modelRef.test.ts +23 -0
- package/tests/modelsRoutes.test.ts +154 -0
- package/tests/sessionMediaCache.test.ts +167 -0
- package/tests/statsRoutes.test.ts +323 -0
- package/tsconfig.json +15 -0
- package/ui/index.html +16 -0
- package/ui/package-lock.json +8521 -0
- package/ui/package.json +52 -0
- package/ui/postcss.config.js +6 -0
- package/ui/public/assets/apple-touch-icon.png +0 -0
- package/ui/public/assets/favicon-16.png +0 -0
- package/ui/public/assets/favicon-32.png +0 -0
- package/ui/public/assets/icon-192.png +0 -0
- package/ui/public/assets/icon-512.png +0 -0
- package/ui/src/App.tsx +27 -0
- package/ui/src/api/client.ts +1503 -0
- package/ui/src/components/EndpointUsageGuide.tsx +361 -0
- package/ui/src/components/Layout.tsx +124 -0
- package/ui/src/components/MessageContent.tsx +365 -0
- package/ui/src/components/ToolCallMessage.tsx +179 -0
- package/ui/src/components/ToolPicker.tsx +442 -0
- package/ui/src/components/messageContentParser.test.ts +41 -0
- package/ui/src/components/messageContentParser.ts +73 -0
- package/ui/src/components/thinkingPreview.test.ts +27 -0
- package/ui/src/components/thinkingPreview.ts +15 -0
- package/ui/src/components/toMermaidSankey.test.ts +78 -0
- package/ui/src/components/toMermaidSankey.ts +56 -0
- package/ui/src/components/ui/button.tsx +58 -0
- package/ui/src/components/ui/input.tsx +21 -0
- package/ui/src/components/ui/textarea.tsx +21 -0
- package/ui/src/lib/utils.ts +6 -0
- package/ui/src/main.tsx +9 -0
- package/ui/src/pages/AgentPlayground.tsx +2010 -0
- package/ui/src/pages/Benchmark.tsx +988 -0
- package/ui/src/pages/Dashboard.tsx +581 -0
- package/ui/src/pages/Peek.tsx +962 -0
- package/ui/src/pages/Settings.tsx +2013 -0
- package/ui/src/pages/agentPlaygroundPayload.test.ts +109 -0
- package/ui/src/pages/agentPlaygroundPayload.ts +97 -0
- package/ui/src/pages/agentThinkingContent.test.ts +50 -0
- package/ui/src/pages/agentThinkingContent.ts +57 -0
- package/ui/src/pages/dashboardTokenUsage.test.ts +66 -0
- package/ui/src/pages/dashboardTokenUsage.ts +36 -0
- package/ui/src/pages/imageUpload.test.ts +39 -0
- package/ui/src/pages/imageUpload.ts +71 -0
- package/ui/src/pages/peekFilters.test.ts +29 -0
- package/ui/src/pages/peekFilters.ts +13 -0
- package/ui/src/pages/peekMedia.test.ts +58 -0
- package/ui/src/pages/peekMedia.ts +148 -0
- package/ui/src/pages/sessionAutoTitle.test.ts +128 -0
- package/ui/src/pages/sessionAutoTitle.ts +106 -0
- package/ui/src/stores/settings.ts +58 -0
- package/ui/src/styles/globals.css +223 -0
- package/ui/src/vite-env.d.ts +8 -0
- package/ui/tailwind.config.js +106 -0
- package/ui/tsconfig.json +32 -0
- 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
|
+
}
|