mioku-service-webui 4.12.0 → 4.13.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/index.ts +2 -0
- package/package.json +10 -3
- package/routes/agent.ts +4 -0
- package/routes/index.ts +1 -0
- package/routes/mcp.ts +136 -0
package/index.ts
CHANGED
|
@@ -37,6 +37,7 @@ import {
|
|
|
37
37
|
createDataManagementRoutes,
|
|
38
38
|
createStrangerRoutes,
|
|
39
39
|
createAccessControlRoutes,
|
|
40
|
+
createMcpRoutes,
|
|
40
41
|
} from "./routes";
|
|
41
42
|
import { freePort } from "./port-utils";
|
|
42
43
|
|
|
@@ -132,6 +133,7 @@ class WebUIRuntime {
|
|
|
132
133
|
this.app.route("/api/data-management", createDataManagementRoutes());
|
|
133
134
|
this.app.route("/api/stranger", createStrangerRoutes());
|
|
134
135
|
this.app.route("/api/access-control", createAccessControlRoutes());
|
|
136
|
+
this.app.route("/api/mcp", createMcpRoutes());
|
|
135
137
|
|
|
136
138
|
this.app.get(
|
|
137
139
|
"/api/ws/logs",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mioku-service-webui",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.13.0",
|
|
4
4
|
"description": "Mioku WebUI 服务",
|
|
5
5
|
"main": "index.ts",
|
|
6
6
|
"type": "module",
|
|
@@ -26,11 +26,18 @@
|
|
|
26
26
|
"systeminformation": "^5.31.6"
|
|
27
27
|
},
|
|
28
28
|
"peerDependencies": {
|
|
29
|
-
"mioku-service-ai": "^4.0.0"
|
|
29
|
+
"mioku-service-ai": "^4.0.0",
|
|
30
|
+
"mioku-service-mcp": "^0.1.0"
|
|
30
31
|
},
|
|
31
32
|
"devDependencies": {
|
|
32
33
|
"@types/bun": "^1.3.14",
|
|
33
34
|
"mioku": "workspace:*",
|
|
34
|
-
"mioku-service-ai": "workspace:*"
|
|
35
|
+
"mioku-service-ai": "workspace:*",
|
|
36
|
+
"mioku-service-mcp": "workspace:*"
|
|
37
|
+
},
|
|
38
|
+
"peerDependenciesMeta": {
|
|
39
|
+
"mioku-service-mcp": {
|
|
40
|
+
"optional": true
|
|
41
|
+
}
|
|
35
42
|
}
|
|
36
43
|
}
|
package/routes/agent.ts
CHANGED
|
@@ -98,6 +98,10 @@ function sanitizeSettingsForSave(body: any): any {
|
|
|
98
98
|
maxResourceFiles: positive(next.skills?.maxResourceFiles, 200),
|
|
99
99
|
};
|
|
100
100
|
next.dataCollection = { enabled: next.dataCollection?.enabled !== false };
|
|
101
|
+
next.mcp = {
|
|
102
|
+
enabled: next.mcp?.enabled !== false,
|
|
103
|
+
maxTools: nonNegative(next.mcp?.maxTools, 48),
|
|
104
|
+
};
|
|
101
105
|
next.debug = Boolean(next.debug);
|
|
102
106
|
return next;
|
|
103
107
|
}
|
package/routes/index.ts
CHANGED
package/routes/mcp.ts
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { Hono } from "hono";
|
|
2
|
+
import { logger, servicesRegistry } from "mioku";
|
|
3
|
+
import type { McpServerInput, McpServiceApi } from "mioku-service-mcp";
|
|
4
|
+
|
|
5
|
+
/** 服务可能没装或没起来,统一走注册表按名查,缺失时返回 503 */
|
|
6
|
+
function mcp(): McpServiceApi | undefined {
|
|
7
|
+
return servicesRegistry.mcp as McpServiceApi | undefined;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function unavailable(c: { json: (body: unknown, status: 503) => Response }): Response {
|
|
11
|
+
return c.json({ ok: false, error: "MCP_SERVICE_UNAVAILABLE" }, 503);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function badRequest(
|
|
15
|
+
c: { json: (body: unknown, status: 400) => Response },
|
|
16
|
+
error: string,
|
|
17
|
+
): Response {
|
|
18
|
+
return c.json({ ok: false, error }, 400);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function normalizeLimit(value: string | undefined, fallback: number): number {
|
|
22
|
+
const num = Math.floor(Number(value));
|
|
23
|
+
if (!Number.isFinite(num) || num <= 0) return fallback;
|
|
24
|
+
return Math.min(num, 500);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function createMcpRoutes() {
|
|
28
|
+
const app = new Hono();
|
|
29
|
+
|
|
30
|
+
app.get("/overview", (c) => {
|
|
31
|
+
const api = mcp();
|
|
32
|
+
if (!api) return unavailable(c);
|
|
33
|
+
const config = api.getConfig();
|
|
34
|
+
return c.json({
|
|
35
|
+
ok: true,
|
|
36
|
+
data: {
|
|
37
|
+
autoConnect: config.autoConnect,
|
|
38
|
+
reconnectDelayMs: config.reconnectDelayMs,
|
|
39
|
+
maxConcurrency: config.maxConcurrency,
|
|
40
|
+
maxTools: config.maxTools,
|
|
41
|
+
maxResultChars: config.maxResultChars,
|
|
42
|
+
maxResultImages: config.maxResultImages,
|
|
43
|
+
revision: api.revision(),
|
|
44
|
+
toolCount: api.listTools().length,
|
|
45
|
+
catalogOnlyCount: api.listAllTools().filter((tool) => tool.catalogOnly).length,
|
|
46
|
+
servers: api.listServers(),
|
|
47
|
+
configs: api.listServerConfigs(),
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
app.post("/servers", async (c) => {
|
|
53
|
+
const api = mcp();
|
|
54
|
+
if (!api) return unavailable(c);
|
|
55
|
+
const body = (await c.req.json().catch(() => ({}))) as McpServerInput;
|
|
56
|
+
const name = String(body?.name ?? "").trim().toLowerCase();
|
|
57
|
+
if (!name) return badRequest(c, "请填写服务器名称");
|
|
58
|
+
logger.info("[webui-action] mcp.server.upsert", { name });
|
|
59
|
+
const result = await api.upsertServer({ ...body, name });
|
|
60
|
+
if ("error" in result) return badRequest(c, result.error);
|
|
61
|
+
return c.json({ ok: true, data: result });
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
app.delete("/servers/:name", async (c) => {
|
|
65
|
+
const api = mcp();
|
|
66
|
+
if (!api) return unavailable(c);
|
|
67
|
+
const name = c.req.param("name");
|
|
68
|
+
const removed = await api.removeServer(name);
|
|
69
|
+
if (!removed) return badRequest(c, `服务器 "${name}" 不存在`);
|
|
70
|
+
logger.info("[webui-action] mcp.server.remove", { name });
|
|
71
|
+
return c.json({ ok: true, data: { name } });
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
app.put("/servers/:name/enabled", async (c) => {
|
|
75
|
+
const api = mcp();
|
|
76
|
+
if (!api) return unavailable(c);
|
|
77
|
+
const name = c.req.param("name");
|
|
78
|
+
const body = (await c.req.json().catch(() => ({}))) as { enabled?: unknown };
|
|
79
|
+
const enabled = body?.enabled !== false;
|
|
80
|
+
const result = await api.setServerEnabled(name, enabled);
|
|
81
|
+
if ("error" in result) return badRequest(c, result.error);
|
|
82
|
+
logger.info(`[webui-action] mcp.server.${enabled ? "enable" : "disable"}`, { name });
|
|
83
|
+
return c.json({ ok: true, data: result });
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
for (const action of ["connect", "disconnect", "reconnect"] as const) {
|
|
87
|
+
app.post(`/servers/:name/${action}`, async (c) => {
|
|
88
|
+
const api = mcp();
|
|
89
|
+
if (!api) return unavailable(c);
|
|
90
|
+
const name = c.req.param("name");
|
|
91
|
+
logger.info(`[webui-action] mcp.server.${action}`, { name });
|
|
92
|
+
const result = await api[action](name);
|
|
93
|
+
if ("error" in result) return badRequest(c, result.error);
|
|
94
|
+
return c.json({ ok: true, data: result });
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
app.get("/servers/:name/tools", (c) => {
|
|
99
|
+
const api = mcp();
|
|
100
|
+
if (!api) return unavailable(c);
|
|
101
|
+
const name = c.req.param("name");
|
|
102
|
+
if (!api.getServer(name)) return badRequest(c, `服务器 "${name}" 不存在`);
|
|
103
|
+
const tools = api
|
|
104
|
+
.listAllTools()
|
|
105
|
+
.filter((tool) => tool.server === name)
|
|
106
|
+
.map((tool) => ({
|
|
107
|
+
publicName: tool.publicName,
|
|
108
|
+
tool: tool.tool,
|
|
109
|
+
description: tool.description,
|
|
110
|
+
catalogOnly: tool.catalogOnly,
|
|
111
|
+
parameters: tool.parameters,
|
|
112
|
+
}));
|
|
113
|
+
return c.json({ ok: true, data: tools });
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
app.post("/probe", async (c) => {
|
|
117
|
+
const api = mcp();
|
|
118
|
+
if (!api) return unavailable(c);
|
|
119
|
+
const body = (await c.req.json().catch(() => ({}))) as McpServerInput;
|
|
120
|
+
const name = String(body?.name ?? "").trim().toLowerCase();
|
|
121
|
+
if (!name) return badRequest(c, "请填写服务器名称");
|
|
122
|
+
logger.info("[webui-action] mcp.probe", { name });
|
|
123
|
+
const result = await api.probe({ ...body, name });
|
|
124
|
+
return c.json({ ok: true, data: result });
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
app.get("/logs", (c) => {
|
|
128
|
+
const api = mcp();
|
|
129
|
+
if (!api) return unavailable(c);
|
|
130
|
+
const server = c.req.query("server") || undefined;
|
|
131
|
+
const limit = normalizeLimit(c.req.query("limit"), 100);
|
|
132
|
+
return c.json({ ok: true, data: api.recentLogs(server, limit) });
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
return app;
|
|
136
|
+
}
|