@zhanguym/chatroom 1.2.0-beta.0 → 1.2.0-beta.1
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/dist/app/application.d.ts +2 -0
- package/dist/app/application.js +7 -1
- package/dist/app/lifecycle.js +6 -0
- package/dist/auth/auth-service.js +5 -1
- package/dist/infrastructure/database/custom-mcp-repository.d.ts +9 -0
- package/dist/infrastructure/database/custom-mcp-repository.js +37 -0
- package/dist/infrastructure/database/mcp-tool-settings-repository.d.ts +1 -0
- package/dist/infrastructure/database/mcp-tool-settings-repository.js +6 -0
- package/dist/infrastructure/database/schema.d.ts +1 -1
- package/dist/infrastructure/database/schema.js +9 -0
- package/dist/mcp/custom/manager.d.ts +54 -0
- package/dist/mcp/custom/manager.js +494 -0
- package/dist/mcp/custom/oauth.d.ts +38 -0
- package/dist/mcp/custom/oauth.js +116 -0
- package/dist/mcp/custom/types.d.ts +42 -0
- package/dist/mcp/custom/types.js +1 -0
- package/dist/mcp/server/create-mcp-server.d.ts +2 -1
- package/dist/mcp/server/create-mcp-server.js +4 -3
- package/dist/mcp/server/tool-control.d.ts +5 -0
- package/dist/mcp/server/tool-control.js +17 -0
- package/dist/native/linux/chatroom-computer-helper +0 -0
- package/dist/native/macos/ChatRoomComputerHelper.app/Contents/MacOS/chatroom-computer-helper +0 -0
- package/dist/native/windows/chatroom-computer-helper.exe +0 -0
- package/dist/plugins/computer/computer-schemas.d.ts +8 -8
- package/dist/plugins/types.d.ts +2 -0
- package/dist/plugins/web/api-types.d.ts +2 -0
- package/dist/plugins/web/http/api-router.js +3 -0
- package/dist/plugins/web/http/custom-mcp-api-router.d.ts +6 -0
- package/dist/plugins/web/http/custom-mcp-api-router.js +114 -0
- package/dist/plugins/web/plugin.js +1 -1
- package/dist/plugins/web/runtime.d.ts +3 -1
- package/dist/plugins/web/runtime.js +3 -1
- package/dist/web/assets/{AgentView-DQr-5aLK.js → AgentView-BEA5mQVv.js} +3 -3
- package/dist/web/assets/{CloudView-B2VBut3O.js → CloudView-BGcVSE22.js} +1 -1
- package/dist/web/assets/CodeViewer.vue_vue_type_script_setup_true_lang-DfPmlGC0.js +3 -0
- package/dist/web/assets/ComputerView-Bz2Wsz2X.css +1 -0
- package/dist/web/assets/{ComputerView-CIFF1zUb.js → ComputerView-D6_NSXKb.js} +1 -1
- package/dist/web/assets/{MarkdownContent-DRZp88_G.js → MarkdownContent-DCkAALOF.js} +1 -1
- package/dist/web/assets/McpToolsView--8UloObb.js +2 -0
- package/dist/web/assets/McpToolsView-DdYOoY9_.css +1 -0
- package/dist/web/assets/{OperationsView-B_Nv1Mgi.js → OperationsView-CqgAPnSL.js} +2 -2
- package/dist/web/assets/{ProcessesView-5K8wNiPC.js → ProcessesView-DC0tjiYU.js} +2 -2
- package/dist/web/assets/{StateChip.vue_vue_type_script_setup_true_lang-CQEj-YRM.js → StateChip.vue_vue_type_script_setup_true_lang-D_LKEtqT.js} +1 -1
- package/dist/web/assets/SystemLogsView-DkTeUr2k.js +1 -0
- package/dist/web/assets/{TerminalView-DNaKG3II.css → TerminalView-Coze7IMj.css} +1 -1
- package/dist/web/assets/{TerminalView-Dvyy2GKF.js → TerminalView-DEP1U0aW.js} +2 -2
- package/dist/web/assets/VSelect-B5-t612C.js +1 -0
- package/dist/web/assets/VSheet-CNayLacf.js +1 -0
- package/dist/web/assets/VSpacer-BYe3Z99W.js +1 -0
- package/dist/web/assets/{VSwitch-CvQYU48e.js → VSwitch-cVRCnipJ.js} +1 -1
- package/dist/web/assets/{VTabs-CftrBFlF.js → VTabs-HKX286IT.js} +1 -1
- package/dist/web/assets/{VTextarea-Dcui00tn.js → VTextarea-ypCBuKI-.js} +1 -1
- package/dist/web/assets/WorkspaceDownloadPreview-qwbTkU59.js +1 -0
- package/dist/web/assets/{WorkspaceMediaPreview-ctIyG3Ru.js → WorkspaceMediaPreview-D28Yf2yb.js} +3 -3
- package/dist/web/assets/{WorkspacePdfPreview-CeizxEAH.js → WorkspacePdfPreview-BfZSPcR-.js} +1 -1
- package/dist/web/assets/{WorkspacesView-DcmqHGvr.js → WorkspacesView-CsCGtk5H.js} +2 -2
- package/dist/web/assets/{index-BRI_re-n.css → index-BCUxnASh.css} +1 -1
- package/dist/web/assets/index-CFhV1jOj.js +19 -0
- package/dist/web/assets/{useMasterDetailFocus-DR5N95_A.js → useMasterDetailFocus-CMJXBPPp.js} +1 -1
- package/dist/web/assets/useOperationLabels-DSbMa9Eb.js +1 -0
- package/dist/web/index.html +2 -2
- package/dist/web/transport-sw.js +6 -0
- package/package.json +2 -1
- package/dist/web/assets/CodeViewer.vue_vue_type_script_setup_true_lang-DkN8x_6L.js +0 -3
- package/dist/web/assets/ComputerView-DWzfSYJ7.css +0 -1
- package/dist/web/assets/McpToolsView-CSEru3ck.js +0 -1
- package/dist/web/assets/McpToolsView-DjxPbTHj.css +0 -1
- package/dist/web/assets/SystemLogsView-CBINILPo.js +0 -1
- package/dist/web/assets/VSelect-CBX17QQj.js +0 -1
- package/dist/web/assets/VSpacer-9N_Ffoce.js +0 -1
- package/dist/web/assets/WorkspaceDownloadPreview-DzM2p_xl.js +0 -1
- package/dist/web/assets/index-DTyxBHlJ.js +0 -19
- package/dist/web/assets/useOperationLabels-Dou4m0ac.js +0 -1
- /package/dist/web/assets/{CodeViewer-CH1DEJge.css → VSheet-CH1DEJge.css} +0 -0
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
import { type McpHttpHandler } from "@modelcontextprotocol/server";
|
|
2
|
+
import type { CustomMcpManager } from "#mcp/custom/manager";
|
|
2
3
|
import type { PluginManager } from "#plugins/plugin-manager";
|
|
3
|
-
export declare function createChatRoomMcpHandler(plugins: PluginManager): McpHttpHandler;
|
|
4
|
+
export declare function createChatRoomMcpHandler(plugins: PluginManager, custom: CustomMcpManager): McpHttpHandler;
|
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
import { createMcpHandler, McpServer, } from "@modelcontextprotocol/server";
|
|
2
2
|
import { CHATROOM_NAME, CHATROOM_VERSION } from "#core/runtime/identity";
|
|
3
|
-
export function createChatRoomMcpHandler(plugins) {
|
|
4
|
-
return createMcpHandler(() => createServer(plugins), {
|
|
3
|
+
export function createChatRoomMcpHandler(plugins, custom) {
|
|
4
|
+
return createMcpHandler(() => createServer(plugins, custom), {
|
|
5
5
|
legacy: "stateless",
|
|
6
6
|
responseMode: "auto",
|
|
7
7
|
});
|
|
8
8
|
}
|
|
9
|
-
function createServer(plugins) {
|
|
9
|
+
function createServer(plugins, custom) {
|
|
10
10
|
const server = new McpServer({
|
|
11
11
|
name: CHATROOM_NAME,
|
|
12
12
|
version: CHATROOM_VERSION,
|
|
13
13
|
});
|
|
14
14
|
plugins.registerMcp(server);
|
|
15
|
+
custom.registerMcp(server);
|
|
15
16
|
return server;
|
|
16
17
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { RegisteredTool } from "@modelcontextprotocol/server";
|
|
2
2
|
export interface McpToolSettingStore {
|
|
3
3
|
disabledTools(): string[];
|
|
4
|
+
configuredTools?(): string[];
|
|
4
5
|
setEnabled(name: string, enabled: boolean): void;
|
|
5
6
|
}
|
|
6
7
|
export interface McpToolSummary {
|
|
@@ -15,14 +16,18 @@ interface McpToolDefinition {
|
|
|
15
16
|
pluginId: string;
|
|
16
17
|
title: string;
|
|
17
18
|
description: string;
|
|
19
|
+
defaultEnabled?: boolean;
|
|
18
20
|
}
|
|
19
21
|
export declare class McpToolControl {
|
|
20
22
|
private readonly store;
|
|
21
23
|
private readonly disabled;
|
|
24
|
+
private readonly configured;
|
|
22
25
|
private readonly definitions;
|
|
23
26
|
private readonly handles;
|
|
24
27
|
constructor(store: McpToolSettingStore);
|
|
25
28
|
define(definition: McpToolDefinition): void;
|
|
29
|
+
undefine(name: string): void;
|
|
30
|
+
isEnabled(name: string): boolean;
|
|
26
31
|
attach(name: string, tool: RegisteredTool): void;
|
|
27
32
|
list(): McpToolSummary[];
|
|
28
33
|
setEnabled(name: string, enabled: boolean): McpToolSummary;
|
|
@@ -2,18 +2,34 @@ import { ChatRoomError } from "#core/errors/chatroom-error";
|
|
|
2
2
|
export class McpToolControl {
|
|
3
3
|
store;
|
|
4
4
|
disabled = new Set();
|
|
5
|
+
configured = new Set();
|
|
5
6
|
definitions = new Map();
|
|
6
7
|
handles = new Map();
|
|
7
8
|
constructor(store) {
|
|
8
9
|
this.store = store;
|
|
9
10
|
for (const name of store.disabledTools())
|
|
10
11
|
this.disabled.add(name);
|
|
12
|
+
for (const name of store.configuredTools?.() ?? [])
|
|
13
|
+
this.configured.add(name);
|
|
11
14
|
}
|
|
12
15
|
define(definition) {
|
|
13
16
|
const existing = this.definitions.get(definition.name);
|
|
14
17
|
if (existing && existing.pluginId !== definition.pluginId)
|
|
15
18
|
throw new Error(`MCP tool already belongs to another plugin: ${definition.name}`);
|
|
16
19
|
this.definitions.set(definition.name, definition);
|
|
20
|
+
if (definition.defaultEnabled === false &&
|
|
21
|
+
!this.configured.has(definition.name))
|
|
22
|
+
this.setEnabled(definition.name, false);
|
|
23
|
+
}
|
|
24
|
+
undefine(name) {
|
|
25
|
+
this.definitions.delete(name);
|
|
26
|
+
for (const reference of this.handles.get(name) ?? []) {
|
|
27
|
+
reference.deref()?.remove();
|
|
28
|
+
}
|
|
29
|
+
this.handles.delete(name);
|
|
30
|
+
}
|
|
31
|
+
isEnabled(name) {
|
|
32
|
+
return this.definitions.has(name) && !this.disabled.has(name);
|
|
17
33
|
}
|
|
18
34
|
attach(name, tool) {
|
|
19
35
|
let tools = this.handles.get(name);
|
|
@@ -46,6 +62,7 @@ export class McpToolControl {
|
|
|
46
62
|
if (enabled === !this.disabled.has(name))
|
|
47
63
|
return { ...definition, enabled };
|
|
48
64
|
this.store.setEnabled(name, enabled);
|
|
65
|
+
this.configured.add(name);
|
|
49
66
|
if (enabled)
|
|
50
67
|
this.disabled.delete(name);
|
|
51
68
|
else
|
|
Binary file
|
package/dist/native/macos/ChatRoomComputerHelper.app/Contents/MacOS/chatroom-computer-helper
CHANGED
|
Binary file
|
|
Binary file
|
|
@@ -72,6 +72,10 @@ export declare const computerSnapshotSchema: z.ZodObject<{
|
|
|
72
72
|
}, z.core.$strip>>;
|
|
73
73
|
}, z.core.$strip>;
|
|
74
74
|
export declare const computerSnapshotMetadataSchema: z.ZodObject<{
|
|
75
|
+
cursor: z.ZodNullable<z.ZodObject<{
|
|
76
|
+
x: z.ZodNumber;
|
|
77
|
+
y: z.ZodNumber;
|
|
78
|
+
}, z.core.$strip>>;
|
|
75
79
|
snapshotId: z.ZodString;
|
|
76
80
|
revision: z.ZodNumber;
|
|
77
81
|
display: z.ZodNullable<z.ZodObject<{
|
|
@@ -84,10 +88,6 @@ export declare const computerSnapshotMetadataSchema: z.ZodObject<{
|
|
|
84
88
|
}, z.core.$strip>>;
|
|
85
89
|
activeApp: z.ZodNullable<z.ZodString>;
|
|
86
90
|
activeWindow: z.ZodNullable<z.ZodString>;
|
|
87
|
-
cursor: z.ZodNullable<z.ZodObject<{
|
|
88
|
-
x: z.ZodNumber;
|
|
89
|
-
y: z.ZodNumber;
|
|
90
|
-
}, z.core.$strip>>;
|
|
91
91
|
elements: z.ZodArray<z.ZodObject<{
|
|
92
92
|
id: z.ZodNumber;
|
|
93
93
|
role: z.ZodString;
|
|
@@ -234,6 +234,10 @@ export declare const computerActionMetadataSchema: z.ZodObject<{
|
|
|
234
234
|
}>;
|
|
235
235
|
focusChanged: z.ZodBoolean;
|
|
236
236
|
snapshot: z.ZodOptional<z.ZodObject<{
|
|
237
|
+
cursor: z.ZodNullable<z.ZodObject<{
|
|
238
|
+
x: z.ZodNumber;
|
|
239
|
+
y: z.ZodNumber;
|
|
240
|
+
}, z.core.$strip>>;
|
|
237
241
|
snapshotId: z.ZodString;
|
|
238
242
|
revision: z.ZodNumber;
|
|
239
243
|
display: z.ZodNullable<z.ZodObject<{
|
|
@@ -246,10 +250,6 @@ export declare const computerActionMetadataSchema: z.ZodObject<{
|
|
|
246
250
|
}, z.core.$strip>>;
|
|
247
251
|
activeApp: z.ZodNullable<z.ZodString>;
|
|
248
252
|
activeWindow: z.ZodNullable<z.ZodString>;
|
|
249
|
-
cursor: z.ZodNullable<z.ZodObject<{
|
|
250
|
-
x: z.ZodNumber;
|
|
251
|
-
y: z.ZodNumber;
|
|
252
|
-
}, z.core.$strip>>;
|
|
253
253
|
elements: z.ZodArray<z.ZodObject<{
|
|
254
254
|
id: z.ZodNumber;
|
|
255
255
|
role: z.ZodString;
|
package/dist/plugins/types.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import type { AppDatabase } from "#infrastructure/database/app-database";
|
|
|
3
3
|
import type { PluginMcpRegistrar } from "#mcp/server/plugin-mcp-registrar";
|
|
4
4
|
import type { OperationLog } from "#operations/operation-log";
|
|
5
5
|
import type { McpToolControl } from "#mcp/server/tool-control";
|
|
6
|
+
import type { CustomMcpManager } from "#mcp/custom/manager";
|
|
6
7
|
import type { RuntimeEventBus } from "#app/event-bus";
|
|
7
8
|
import type { ExternalAccessRegistry } from "#app/external-access-registry";
|
|
8
9
|
import type { LogWriter } from "#core/logging/types";
|
|
@@ -12,6 +13,7 @@ export interface PluginContext {
|
|
|
12
13
|
database: AppDatabase;
|
|
13
14
|
operations: OperationLog;
|
|
14
15
|
mcpTools: McpToolControl;
|
|
16
|
+
customMcp: CustomMcpManager;
|
|
15
17
|
events: RuntimeEventBus;
|
|
16
18
|
externalAccess: ExternalAccessRegistry;
|
|
17
19
|
services: ServiceRegistry;
|
|
@@ -2,6 +2,7 @@ import type { Operation as DomainOperation } from "#core/operations/types";
|
|
|
2
2
|
import type { GitBranch, GitChange, GitCommit, GitDiff, GitStatus } from "#app/git-access";
|
|
3
3
|
import type { ProcessSnapshot, ProcessSummary } from "#plugins/process/types";
|
|
4
4
|
import type { AgentAttachment, AgentHistory, AgentHistoryUpdate, AgentInteractionResponse, AgentItem, AgentModel, AgentProviderId, AgentProviderDetails, AgentReviewTarget, AgentProviderStatus, AgentSession, AgentTurn, AgentTurnSource } from "#plugins/agent/types";
|
|
5
|
+
import type { CustomMcpServerSummary } from "#mcp/custom/types";
|
|
5
6
|
import type { McpToolSummary as DomainMcpToolSummary } from "#mcp/server/tool-control";
|
|
6
7
|
import type { CloudServiceId, CloudStatus as DomainCloudStatus } from "#plugins/cloud/types";
|
|
7
8
|
import type { ComputerDisplay, ComputerPermission, ComputerStatus } from "#plugins/computer/types";
|
|
@@ -46,6 +47,7 @@ export interface UpdateStatus {
|
|
|
46
47
|
releaseUrl: string | null;
|
|
47
48
|
}
|
|
48
49
|
export type McpToolSummary = DomainMcpToolSummary;
|
|
50
|
+
export type { CustomMcpServerSummary };
|
|
49
51
|
export type CloudService = CloudServiceId;
|
|
50
52
|
export type CloudStatus = DomainCloudStatus;
|
|
51
53
|
export interface WorkspaceFileContent {
|
|
@@ -8,6 +8,7 @@ import { createGitApiRouter } from "./git-api-router.js";
|
|
|
8
8
|
import { createCloudApiRouter } from "./cloud-api-router.js";
|
|
9
9
|
import { createComputerApiRouter } from "./computer-api-router.js";
|
|
10
10
|
import { apiAuthentication, createPrivateAuthApiRouter, createPublicAuthApiRouter, } from "./auth-api-router.js";
|
|
11
|
+
import { createCustomMcpApiRouter, createCustomMcpOAuthCallbackRouter, } from "./custom-mcp-api-router.js";
|
|
11
12
|
import { createRuntimeApiRouter } from "./runtime-api-router.js";
|
|
12
13
|
import { createLogsApiRouter } from "./logs-api-router.js";
|
|
13
14
|
import { createAgentApiRouter } from "./agent-api-router.js";
|
|
@@ -15,10 +16,12 @@ import { createRtcApiRouter } from "./rtc-api-router.js";
|
|
|
15
16
|
export function createApiRouter(application, eventBus, auth, passkeys, ingress, cloud, logs, config, runtimeStatus) {
|
|
16
17
|
const router = Router();
|
|
17
18
|
router.use(createPublicAuthApiRouter(auth, passkeys, ingress, logs));
|
|
19
|
+
router.use(createCustomMcpOAuthCallbackRouter(application.customMcp));
|
|
18
20
|
router.use(apiAuthentication(auth, ingress));
|
|
19
21
|
router.use(createPrivateAuthApiRouter(passkeys, ingress, logs));
|
|
20
22
|
router.use(createRtcApiRouter(config));
|
|
21
23
|
router.use(createRuntimeApiRouter(application, eventBus, runtimeStatus));
|
|
24
|
+
router.use(createCustomMcpApiRouter(application.customMcp, ingress));
|
|
22
25
|
router.use(createLogsApiRouter(logs));
|
|
23
26
|
router.get("/operations", (req, res) => {
|
|
24
27
|
res.json(application.operations.list({
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { Router } from "express";
|
|
2
|
+
import type { IngressPolicy } from "#auth/ingress-policy";
|
|
3
|
+
import type { CustomMcpManager } from "#mcp/custom/manager";
|
|
4
|
+
export declare function createCustomMcpApiRouter(manager: CustomMcpManager, ingress: IngressPolicy): Router;
|
|
5
|
+
/** Public OAuth callback: the random state is single-use and scoped to an initiated login. */
|
|
6
|
+
export declare function createCustomMcpOAuthCallbackRouter(manager: CustomMcpManager): Router;
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { Router } from "express";
|
|
2
|
+
import { ChatRoomError } from "#core/errors/chatroom-error";
|
|
3
|
+
import { bodyRecord, requireString } from "#presentation/http/http-utils";
|
|
4
|
+
function settings(body, partial = false) {
|
|
5
|
+
const config = {};
|
|
6
|
+
const str = (key) => {
|
|
7
|
+
if (typeof body[key] !== "string")
|
|
8
|
+
throw new ChatRoomError("INVALID_INPUT", key + " must be a string");
|
|
9
|
+
return body[key];
|
|
10
|
+
};
|
|
11
|
+
if (!partial || "name" in body)
|
|
12
|
+
config.name = str("name").trim();
|
|
13
|
+
if (!partial || "type" in body) {
|
|
14
|
+
const type = str("type");
|
|
15
|
+
if (type !== "stdio" && type !== "http")
|
|
16
|
+
throw new ChatRoomError("INVALID_INPUT", "Unsupported MCP transport");
|
|
17
|
+
config.type = type;
|
|
18
|
+
}
|
|
19
|
+
for (const key of ["command", "cwd", "url"])
|
|
20
|
+
if (key in body)
|
|
21
|
+
config[key] = str(key);
|
|
22
|
+
if ("args" in body) {
|
|
23
|
+
if (!Array.isArray(body.args) ||
|
|
24
|
+
body.args.some((value) => typeof value !== "string"))
|
|
25
|
+
throw new ChatRoomError("INVALID_INPUT", "args must be an array of strings");
|
|
26
|
+
config.args = body.args;
|
|
27
|
+
}
|
|
28
|
+
for (const key of ["env", "headers"]) {
|
|
29
|
+
if (!(key in body))
|
|
30
|
+
continue;
|
|
31
|
+
const data = body[key];
|
|
32
|
+
if (!data ||
|
|
33
|
+
typeof data !== "object" ||
|
|
34
|
+
Array.isArray(data) ||
|
|
35
|
+
Object.entries(data).some(([name, value]) => !name.trim() || typeof value !== "string"))
|
|
36
|
+
throw new ChatRoomError("INVALID_INPUT", key + " must be a string map");
|
|
37
|
+
config[key] = data;
|
|
38
|
+
}
|
|
39
|
+
if ("authMode" in body) {
|
|
40
|
+
const value = str("authMode");
|
|
41
|
+
if (!["auto", "none", "headers"].includes(value))
|
|
42
|
+
throw new ChatRoomError("INVALID_INPUT", "Unsupported MCP authentication");
|
|
43
|
+
config.authMode = value;
|
|
44
|
+
}
|
|
45
|
+
if (!partial || "enabled" in body) {
|
|
46
|
+
if (typeof body.enabled !== "boolean")
|
|
47
|
+
throw new ChatRoomError("INVALID_INPUT", "enabled must be a boolean");
|
|
48
|
+
config.enabled = body.enabled;
|
|
49
|
+
}
|
|
50
|
+
return config;
|
|
51
|
+
}
|
|
52
|
+
export function createCustomMcpApiRouter(manager, ingress) {
|
|
53
|
+
const router = Router();
|
|
54
|
+
router.get("/mcp/servers", (_req, res) => res.json(manager.list()));
|
|
55
|
+
router.post("/mcp/servers", async (req, res) => {
|
|
56
|
+
const config = settings(bodyRecord(req.body));
|
|
57
|
+
res.status(201).json(await manager.create(config));
|
|
58
|
+
});
|
|
59
|
+
router.post("/mcp/servers/test", async (req, res) => {
|
|
60
|
+
const config = settings(bodyRecord(req.body));
|
|
61
|
+
res.json(await manager.test(config));
|
|
62
|
+
});
|
|
63
|
+
router.post("/mcp/servers/:id/test", async (req, res) => {
|
|
64
|
+
res.json(await manager.testStored(requireString(req.params.id, "id")));
|
|
65
|
+
});
|
|
66
|
+
router.patch("/mcp/servers/:id", async (req, res) => {
|
|
67
|
+
const id = requireString(req.params.id, "id");
|
|
68
|
+
res.json(await manager.update(id, settings(bodyRecord(req.body), true)));
|
|
69
|
+
});
|
|
70
|
+
router.delete("/mcp/servers/:id", async (req, res) => {
|
|
71
|
+
await manager.remove(requireString(req.params.id, "id"));
|
|
72
|
+
res.json({ ok: true });
|
|
73
|
+
});
|
|
74
|
+
router.post("/mcp/servers/:id/authorize", async (req, res) => {
|
|
75
|
+
const id = requireString(req.params.id, "id");
|
|
76
|
+
const origin = ingress.expectedWebOrigin(req) ??
|
|
77
|
+
`http://${req.get("host") ?? "localhost:8765"}`;
|
|
78
|
+
res.json(await manager.authorize(id, origin + "/api/mcp/oauth/callback"));
|
|
79
|
+
});
|
|
80
|
+
router.post("/mcp/servers/:id/revoke", async (req, res) => {
|
|
81
|
+
await manager.revokeAuthorization(requireString(req.params.id, "id"));
|
|
82
|
+
res.json({ ok: true });
|
|
83
|
+
});
|
|
84
|
+
router.post("/mcp/servers/:id/refresh", async (req, res) => {
|
|
85
|
+
res.json(await manager.refresh(requireString(req.params.id, "id")));
|
|
86
|
+
});
|
|
87
|
+
return router;
|
|
88
|
+
}
|
|
89
|
+
/** Public OAuth callback: the random state is single-use and scoped to an initiated login. */
|
|
90
|
+
export function createCustomMcpOAuthCallbackRouter(manager) {
|
|
91
|
+
const router = Router();
|
|
92
|
+
router.get("/mcp/oauth/callback", async (req, res) => {
|
|
93
|
+
const query = new URLSearchParams();
|
|
94
|
+
for (const key of ["state", "code", "iss", "error"])
|
|
95
|
+
if (typeof req.query[key] === "string")
|
|
96
|
+
query.set(key, req.query[key]);
|
|
97
|
+
let success = false;
|
|
98
|
+
try {
|
|
99
|
+
await manager.completeAuthorization(query);
|
|
100
|
+
success = true;
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
// Never expose OAuth codes, provider errors, or credentials in this page.
|
|
104
|
+
}
|
|
105
|
+
res.setHeader("Cache-Control", "no-store");
|
|
106
|
+
res.setHeader("Content-Security-Policy", "default-src 'none'; script-src 'unsafe-inline'; frame-ancestors 'none'");
|
|
107
|
+
res.status(success ? 200 : 400).type("html")
|
|
108
|
+
.send(`<!doctype html><html lang="en"><meta charset="utf-8"><title>ChatRoom MCP</title>
|
|
109
|
+
<body><p>${success ? "Authorization completed. You may close this window." : "Authorization failed. Return to ChatRoom and retry."}</p>
|
|
110
|
+
<script>window.opener?.postMessage({type:"chatroom:mcp-oauth-completed",success:${success}},window.location.origin);window.close();</script>
|
|
111
|
+
</body></html>`);
|
|
112
|
+
});
|
|
113
|
+
return router;
|
|
114
|
+
}
|
|
@@ -11,7 +11,7 @@ export function createWebPlugin() {
|
|
|
11
11
|
id: "web",
|
|
12
12
|
activate(context) {
|
|
13
13
|
context.services.provide(WebServiceToken, {
|
|
14
|
-
application: new WebRuntime(context.services.require(WorkspaceServiceToken), context.services.require(GitAccessToken), context.operations, context.services.require(ProcessServiceToken), context.services.require(ComputerServiceToken), context.services.require(AgentServiceToken), context.mcpTools),
|
|
14
|
+
application: new WebRuntime(context.services.require(WorkspaceServiceToken), context.services.require(GitAccessToken), context.operations, context.services.require(ProcessServiceToken), context.services.require(ComputerServiceToken), context.services.require(AgentServiceToken), context.mcpTools, context.customMcp),
|
|
15
15
|
});
|
|
16
16
|
},
|
|
17
17
|
};
|
|
@@ -4,6 +4,7 @@ import type { GitAccess } from "#app/git-access";
|
|
|
4
4
|
import type { ProcessSupervisor } from "#plugins/process/process-supervisor";
|
|
5
5
|
import type { WorkspaceService } from "#plugins/workspace/workspace-service";
|
|
6
6
|
import type { AgentRuntime } from "#plugins/agent/runtime";
|
|
7
|
+
import type { CustomMcpManager } from "#mcp/custom/manager";
|
|
7
8
|
import type { McpToolControl } from "#mcp/server/tool-control";
|
|
8
9
|
export declare class WebRuntime {
|
|
9
10
|
readonly workspaces: WorkspaceService;
|
|
@@ -13,7 +14,8 @@ export declare class WebRuntime {
|
|
|
13
14
|
readonly computer: ComputerService;
|
|
14
15
|
readonly agents: AgentRuntime;
|
|
15
16
|
readonly mcpTools: McpToolControl;
|
|
16
|
-
|
|
17
|
+
readonly customMcp: CustomMcpManager;
|
|
18
|
+
constructor(workspaces: WorkspaceService, git: GitAccess, operations: OperationLog, processes: ProcessSupervisor, computer: ComputerService, agents: AgentRuntime, mcpTools: McpToolControl, customMcp: CustomMcpManager);
|
|
17
19
|
processKill(processId: string, force?: boolean): Promise<import("./api-types.js").ProcessSnapshot>;
|
|
18
20
|
listProcesses(): import("./api-types.js").ProcessSummary[];
|
|
19
21
|
getProcess(processId: string): import("./api-types.js").ProcessSnapshot;
|
|
@@ -6,7 +6,8 @@ export class WebRuntime {
|
|
|
6
6
|
computer;
|
|
7
7
|
agents;
|
|
8
8
|
mcpTools;
|
|
9
|
-
|
|
9
|
+
customMcp;
|
|
10
|
+
constructor(workspaces, git, operations, processes, computer, agents, mcpTools, customMcp) {
|
|
10
11
|
this.workspaces = workspaces;
|
|
11
12
|
this.git = git;
|
|
12
13
|
this.operations = operations;
|
|
@@ -14,6 +15,7 @@ export class WebRuntime {
|
|
|
14
15
|
this.computer = computer;
|
|
15
16
|
this.agents = agents;
|
|
16
17
|
this.mcpTools = mcpTools;
|
|
18
|
+
this.customMcp = customMcp;
|
|
17
19
|
}
|
|
18
20
|
processKill(processId, force = false) {
|
|
19
21
|
return this.operations.run({
|