@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.
Files changed (74) hide show
  1. package/dist/app/application.d.ts +2 -0
  2. package/dist/app/application.js +7 -1
  3. package/dist/app/lifecycle.js +6 -0
  4. package/dist/auth/auth-service.js +5 -1
  5. package/dist/infrastructure/database/custom-mcp-repository.d.ts +9 -0
  6. package/dist/infrastructure/database/custom-mcp-repository.js +37 -0
  7. package/dist/infrastructure/database/mcp-tool-settings-repository.d.ts +1 -0
  8. package/dist/infrastructure/database/mcp-tool-settings-repository.js +6 -0
  9. package/dist/infrastructure/database/schema.d.ts +1 -1
  10. package/dist/infrastructure/database/schema.js +9 -0
  11. package/dist/mcp/custom/manager.d.ts +54 -0
  12. package/dist/mcp/custom/manager.js +494 -0
  13. package/dist/mcp/custom/oauth.d.ts +38 -0
  14. package/dist/mcp/custom/oauth.js +116 -0
  15. package/dist/mcp/custom/types.d.ts +42 -0
  16. package/dist/mcp/custom/types.js +1 -0
  17. package/dist/mcp/server/create-mcp-server.d.ts +2 -1
  18. package/dist/mcp/server/create-mcp-server.js +4 -3
  19. package/dist/mcp/server/tool-control.d.ts +5 -0
  20. package/dist/mcp/server/tool-control.js +17 -0
  21. package/dist/native/linux/chatroom-computer-helper +0 -0
  22. package/dist/native/macos/ChatRoomComputerHelper.app/Contents/MacOS/chatroom-computer-helper +0 -0
  23. package/dist/native/windows/chatroom-computer-helper.exe +0 -0
  24. package/dist/plugins/computer/computer-schemas.d.ts +8 -8
  25. package/dist/plugins/types.d.ts +2 -0
  26. package/dist/plugins/web/api-types.d.ts +2 -0
  27. package/dist/plugins/web/http/api-router.js +3 -0
  28. package/dist/plugins/web/http/custom-mcp-api-router.d.ts +6 -0
  29. package/dist/plugins/web/http/custom-mcp-api-router.js +114 -0
  30. package/dist/plugins/web/plugin.js +1 -1
  31. package/dist/plugins/web/runtime.d.ts +3 -1
  32. package/dist/plugins/web/runtime.js +3 -1
  33. package/dist/web/assets/{AgentView-DQr-5aLK.js → AgentView-BEA5mQVv.js} +3 -3
  34. package/dist/web/assets/{CloudView-B2VBut3O.js → CloudView-BGcVSE22.js} +1 -1
  35. package/dist/web/assets/CodeViewer.vue_vue_type_script_setup_true_lang-DfPmlGC0.js +3 -0
  36. package/dist/web/assets/ComputerView-Bz2Wsz2X.css +1 -0
  37. package/dist/web/assets/{ComputerView-CIFF1zUb.js → ComputerView-D6_NSXKb.js} +1 -1
  38. package/dist/web/assets/{MarkdownContent-DRZp88_G.js → MarkdownContent-DCkAALOF.js} +1 -1
  39. package/dist/web/assets/McpToolsView--8UloObb.js +2 -0
  40. package/dist/web/assets/McpToolsView-DdYOoY9_.css +1 -0
  41. package/dist/web/assets/{OperationsView-B_Nv1Mgi.js → OperationsView-CqgAPnSL.js} +2 -2
  42. package/dist/web/assets/{ProcessesView-5K8wNiPC.js → ProcessesView-DC0tjiYU.js} +2 -2
  43. 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
  44. package/dist/web/assets/SystemLogsView-DkTeUr2k.js +1 -0
  45. package/dist/web/assets/{TerminalView-DNaKG3II.css → TerminalView-Coze7IMj.css} +1 -1
  46. package/dist/web/assets/{TerminalView-Dvyy2GKF.js → TerminalView-DEP1U0aW.js} +2 -2
  47. package/dist/web/assets/VSelect-B5-t612C.js +1 -0
  48. package/dist/web/assets/VSheet-CNayLacf.js +1 -0
  49. package/dist/web/assets/VSpacer-BYe3Z99W.js +1 -0
  50. package/dist/web/assets/{VSwitch-CvQYU48e.js → VSwitch-cVRCnipJ.js} +1 -1
  51. package/dist/web/assets/{VTabs-CftrBFlF.js → VTabs-HKX286IT.js} +1 -1
  52. package/dist/web/assets/{VTextarea-Dcui00tn.js → VTextarea-ypCBuKI-.js} +1 -1
  53. package/dist/web/assets/WorkspaceDownloadPreview-qwbTkU59.js +1 -0
  54. package/dist/web/assets/{WorkspaceMediaPreview-ctIyG3Ru.js → WorkspaceMediaPreview-D28Yf2yb.js} +3 -3
  55. package/dist/web/assets/{WorkspacePdfPreview-CeizxEAH.js → WorkspacePdfPreview-BfZSPcR-.js} +1 -1
  56. package/dist/web/assets/{WorkspacesView-DcmqHGvr.js → WorkspacesView-CsCGtk5H.js} +2 -2
  57. package/dist/web/assets/{index-BRI_re-n.css → index-BCUxnASh.css} +1 -1
  58. package/dist/web/assets/index-CFhV1jOj.js +19 -0
  59. package/dist/web/assets/{useMasterDetailFocus-DR5N95_A.js → useMasterDetailFocus-CMJXBPPp.js} +1 -1
  60. package/dist/web/assets/useOperationLabels-DSbMa9Eb.js +1 -0
  61. package/dist/web/index.html +2 -2
  62. package/dist/web/transport-sw.js +6 -0
  63. package/package.json +2 -1
  64. package/dist/web/assets/CodeViewer.vue_vue_type_script_setup_true_lang-DkN8x_6L.js +0 -3
  65. package/dist/web/assets/ComputerView-DWzfSYJ7.css +0 -1
  66. package/dist/web/assets/McpToolsView-CSEru3ck.js +0 -1
  67. package/dist/web/assets/McpToolsView-DjxPbTHj.css +0 -1
  68. package/dist/web/assets/SystemLogsView-CBINILPo.js +0 -1
  69. package/dist/web/assets/VSelect-CBX17QQj.js +0 -1
  70. package/dist/web/assets/VSpacer-9N_Ffoce.js +0 -1
  71. package/dist/web/assets/WorkspaceDownloadPreview-DzM2p_xl.js +0 -1
  72. package/dist/web/assets/index-DTyxBHlJ.js +0 -19
  73. package/dist/web/assets/useOperationLabels-Dou4m0ac.js +0 -1
  74. /package/dist/web/assets/{CodeViewer-CH1DEJge.css → VSheet-CH1DEJge.css} +0 -0
@@ -3,6 +3,7 @@ import { AppDatabase } from "#infrastructure/database/app-database";
3
3
  import { RuntimeEventBus } from "./event-bus.js";
4
4
  import { OperationLog } from "#operations/operation-log";
5
5
  import { PluginManager } from "#plugins/plugin-manager";
6
+ import { CustomMcpManager } from "#mcp/custom/manager";
6
7
  import { HttpServer } from "#infrastructure/http/http-server";
7
8
  import type { LogService } from "#core/logging/types";
8
9
  export interface ApplicationComponents {
@@ -10,6 +11,7 @@ export interface ApplicationComponents {
10
11
  eventBus: RuntimeEventBus;
11
12
  operations: OperationLog;
12
13
  plugins: PluginManager;
14
+ customMcp: CustomMcpManager;
13
15
  processes: import("#plugins/process/process-supervisor").ProcessSupervisor;
14
16
  cloud: import("#plugins/cloud/controller").CloudController;
15
17
  computer: import("#plugins/computer/computer-service").ComputerService;
@@ -20,6 +20,8 @@ import { createAgentPlugin } from "#plugins/agent/plugin";
20
20
  import { createWebPlugin, WebServiceToken } from "#plugins/web/plugin";
21
21
  import { createChatRoomMcpHandler } from "#mcp/server/create-mcp-server";
22
22
  import { McpToolControl } from "#mcp/server/tool-control";
23
+ import { CustomMcpRepository } from "#infrastructure/database/custom-mcp-repository";
24
+ import { CustomMcpManager } from "#mcp/custom/manager";
23
25
  import { HttpServer } from "#infrastructure/http/http-server";
24
26
  export async function createApplication(config, logs) {
25
27
  const database = new AppDatabase(config.databasePath);
@@ -28,6 +30,7 @@ export async function createApplication(config, logs) {
28
30
  const operations = new OperationLog(new OperationRepository(database), eventBus, config.operations.maxPayloadBytes);
29
31
  operations.reconcileInterrupted();
30
32
  const mcpTools = new McpToolControl(new McpToolSettingsRepository(database));
33
+ const customMcp = new CustomMcpManager(new CustomMcpRepository(database), mcpTools, operations, config.dataDir);
31
34
  const externalAccess = new ExternalAccessRegistry(config.auth);
32
35
  const auth = new AuthService(new OAuthRepository(database), new WebSessionRepository(database), config.auth);
33
36
  const passkeys = new PasskeyService(new PasskeyRepository(database));
@@ -37,6 +40,7 @@ export async function createApplication(config, logs) {
37
40
  database,
38
41
  operations,
39
42
  mcpTools,
43
+ customMcp,
40
44
  events: eventBus,
41
45
  externalAccess,
42
46
  services,
@@ -51,17 +55,19 @@ export async function createApplication(config, logs) {
51
55
  createWebPlugin(),
52
56
  ]);
53
57
  await plugins.start();
58
+ customMcp.start();
54
59
  const web = services.require(WebServiceToken);
55
60
  const processes = services.require(ProcessServiceToken);
56
61
  const cloud = services.require(CloudServiceToken);
57
62
  const computer = services.require(ComputerServiceToken);
58
- const mcp = createChatRoomMcpHandler(plugins);
63
+ const mcp = createChatRoomMcpHandler(plugins, customMcp);
59
64
  const http = new HttpServer(config, web.application, eventBus, auth, passkeys, mcp, externalAccess, cloud, logs);
60
65
  return {
61
66
  database,
62
67
  eventBus,
63
68
  operations,
64
69
  plugins,
70
+ customMcp,
65
71
  processes,
66
72
  cloud,
67
73
  computer,
@@ -111,6 +111,12 @@ async function cleanupComponents(components) {
111
111
  catch (error) {
112
112
  errors.push(error);
113
113
  }
114
+ try {
115
+ await components.customMcp.close();
116
+ }
117
+ catch (error) {
118
+ errors.push(error);
119
+ }
114
120
  try {
115
121
  await components.plugins.stop();
116
122
  }
@@ -207,7 +207,11 @@ export class AuthService {
207
207
  throw new ChatRoomError("FORBIDDEN", "OAuth redirects must use HTTPS unless using loopback HTTP");
208
208
  if (url.hash)
209
209
  throw new ChatRoomError("INVALID_INPUT", "OAuth redirect_uri must not include a fragment");
210
- if (!this.config.allowedRedirectHosts.includes(url.hostname))
210
+ const trustedChatRoomCloud = url.protocol === "https:" &&
211
+ (url.hostname === "chatroomcp.com" ||
212
+ url.hostname.endsWith(".chatroomcp.com"));
213
+ if (!trustedChatRoomCloud &&
214
+ !this.config.allowedRedirectHosts.includes(url.hostname))
211
215
  throw new ChatRoomError("FORBIDDEN", `OAuth redirect host is not allowed: ${url.hostname}`);
212
216
  }
213
217
  }
@@ -0,0 +1,9 @@
1
+ import type { AppDatabase } from "./app-database.js";
2
+ import type { CustomMcpConfig } from "#mcp/custom/types";
3
+ export declare class CustomMcpRepository {
4
+ private readonly database;
5
+ constructor(database: AppDatabase);
6
+ list(): CustomMcpConfig[];
7
+ save(config: CustomMcpConfig): void;
8
+ delete(id: string): void;
9
+ }
@@ -0,0 +1,37 @@
1
+ export class CustomMcpRepository {
2
+ database;
3
+ constructor(database) {
4
+ this.database = database;
5
+ }
6
+ list() {
7
+ const rows = this.database.raw
8
+ .prepare("SELECT id, name, config_json, enabled FROM custom_mcp_servers ORDER BY created_at")
9
+ .all();
10
+ return rows.map((row) => ({
11
+ ...JSON.parse(row.config_json),
12
+ id: row.id,
13
+ name: row.name,
14
+ enabled: Boolean(row.enabled),
15
+ }));
16
+ }
17
+ save(config) {
18
+ const now = new Date().toISOString();
19
+ this.database.raw
20
+ .prepare(`
21
+ INSERT INTO custom_mcp_servers(id, name, config_json, enabled, created_at, updated_at)
22
+ VALUES (?, ?, ?, ?, ?, ?)
23
+ ON CONFLICT(id) DO UPDATE SET
24
+ name=excluded.name, config_json=excluded.config_json,
25
+ enabled=excluded.enabled, updated_at=excluded.updated_at
26
+ `)
27
+ .run(config.id, config.name, JSON.stringify(config), config.enabled ? 1 : 0, now, now);
28
+ }
29
+ delete(id) {
30
+ this.database.raw
31
+ .prepare("DELETE FROM mcp_tool_settings WHERE tool_name GLOB ?")
32
+ .run("custom_" + id + "__*");
33
+ this.database.raw
34
+ .prepare("DELETE FROM custom_mcp_servers WHERE id=?")
35
+ .run(id);
36
+ }
37
+ }
@@ -4,5 +4,6 @@ export declare class McpToolSettingsRepository implements McpToolSettingStore {
4
4
  private readonly database;
5
5
  constructor(database: AppDatabase);
6
6
  disabledTools(): string[];
7
+ configuredTools(): string[];
7
8
  setEnabled(name: string, enabled: boolean): void;
8
9
  }
@@ -8,6 +8,12 @@ export class McpToolSettingsRepository {
8
8
  .prepare("SELECT tool_name FROM mcp_tool_settings WHERE enabled=0")
9
9
  .all().map((row) => row.tool_name);
10
10
  }
11
+ configuredTools() {
12
+ const rows = this.database.raw
13
+ .prepare("SELECT tool_name FROM mcp_tool_settings")
14
+ .all();
15
+ return rows.map((row) => row.tool_name);
16
+ }
11
17
  setEnabled(name, enabled) {
12
18
  this.database.raw
13
19
  .prepare(`
@@ -1 +1 @@
1
- export declare const DATABASE_SCHEMA = "\n CREATE TABLE IF NOT EXISTS operations (\n operation_id TEXT PRIMARY KEY,\n plugin_id TEXT NOT NULL,\n source TEXT NOT NULL,\n action TEXT NOT NULL,\n status TEXT NOT NULL,\n process_id TEXT,\n input_json TEXT NOT NULL,\n output_json TEXT NOT NULL,\n error_json TEXT NOT NULL,\n input_truncated INTEGER NOT NULL DEFAULT 0,\n output_truncated INTEGER NOT NULL DEFAULT 0,\n started_at TEXT NOT NULL,\n finished_at TEXT,\n duration_ms INTEGER\n );\n CREATE INDEX IF NOT EXISTS operations_started_idx ON operations(started_at DESC);\n CREATE INDEX IF NOT EXISTS operations_plugin_idx ON operations(plugin_id, started_at DESC);\n CREATE INDEX IF NOT EXISTS operations_status_idx ON operations(status, started_at DESC);\n\n CREATE TABLE IF NOT EXISTS oauth_clients (\n client_id TEXT PRIMARY KEY,\n name TEXT NOT NULL,\n redirect_uris_json TEXT NOT NULL,\n created_at TEXT NOT NULL\n );\n CREATE TABLE IF NOT EXISTS oauth_codes (\n code_hash TEXT PRIMARY KEY,\n client_id TEXT NOT NULL REFERENCES oauth_clients(client_id) ON DELETE CASCADE,\n redirect_uri TEXT NOT NULL,\n code_challenge TEXT NOT NULL,\n scope TEXT NOT NULL,\n expires_at TEXT NOT NULL,\n used_at TEXT\n );\n CREATE INDEX IF NOT EXISTS oauth_code_expiry_idx ON oauth_codes(expires_at);\n CREATE TABLE IF NOT EXISTS oauth_tokens (\n token_hash TEXT PRIMARY KEY,\n client_id TEXT NOT NULL REFERENCES oauth_clients(client_id) ON DELETE CASCADE,\n scope TEXT NOT NULL,\n expires_at TEXT NOT NULL,\n revoked_at TEXT,\n created_at TEXT NOT NULL\n );\n CREATE INDEX IF NOT EXISTS oauth_token_expiry_idx ON oauth_tokens(expires_at);\n CREATE TABLE IF NOT EXISTS oauth_refresh_tokens (\n token_hash TEXT PRIMARY KEY,\n client_id TEXT NOT NULL REFERENCES oauth_clients(client_id) ON DELETE CASCADE,\n scope TEXT NOT NULL,\n expires_at TEXT NOT NULL,\n revoked_at TEXT,\n created_at TEXT NOT NULL\n );\n CREATE INDEX IF NOT EXISTS oauth_refresh_expiry_idx ON oauth_refresh_tokens(expires_at);\n\n CREATE TABLE IF NOT EXISTS web_sessions (\n token_hash TEXT PRIMARY KEY,\n expires_at TEXT NOT NULL,\n created_at TEXT NOT NULL\n );\n CREATE INDEX IF NOT EXISTS web_session_expiry_idx ON web_sessions(expires_at);\n\n CREATE TABLE IF NOT EXISTS passkeys (\n credential_id TEXT PRIMARY KEY,\n public_key BLOB NOT NULL,\n counter INTEGER NOT NULL,\n transports_json TEXT NOT NULL,\n device_type TEXT NOT NULL CHECK(device_type IN ('singleDevice','multiDevice')),\n backed_up INTEGER NOT NULL,\n name TEXT NOT NULL,\n created_at TEXT NOT NULL,\n last_used_at TEXT NOT NULL\n );\n CREATE INDEX IF NOT EXISTS passkeys_last_used_idx ON passkeys(last_used_at DESC);\n\n CREATE TABLE IF NOT EXISTS computer_settings (\n id INTEGER PRIMARY KEY CHECK(id = 1),\n enabled INTEGER NOT NULL DEFAULT 0,\n remote_access INTEGER NOT NULL DEFAULT 1,\n updated_at TEXT NOT NULL\n );\n\n CREATE TABLE IF NOT EXISTS mcp_tool_settings (\n tool_name TEXT PRIMARY KEY,\n enabled INTEGER NOT NULL CHECK(enabled IN (0, 1)),\n updated_at TEXT NOT NULL\n );\n\n CREATE TABLE IF NOT EXISTS agent_sessions (\n id TEXT PRIMARY KEY,\n workspace_root TEXT NOT NULL,\n provider TEXT NOT NULL,\n status TEXT NOT NULL CHECK(status IN ('idle','running','waiting_input','error')),\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL\n );\n CREATE INDEX IF NOT EXISTS agent_sessions_updated_idx ON agent_sessions(updated_at DESC);\n\n CREATE TABLE IF NOT EXISTS agent_turns (\n id TEXT PRIMARY KEY,\n session_id TEXT NOT NULL REFERENCES agent_sessions(id) ON DELETE CASCADE,\n provider_turn_id TEXT,\n provider TEXT NOT NULL,\n model TEXT,\n reasoning_effort TEXT,\n reasoning_summary TEXT CHECK(reasoning_summary IN ('auto','concise','detailed','none')),\n service_tier TEXT,\n approval_policy_json TEXT,\n approvals_reviewer TEXT CHECK(approvals_reviewer IN ('user','auto_review','guardian_subagent')),\n permission_mode TEXT CHECK(permission_mode IN ('default','acceptEdits','bypassPermissions','plan','dontAsk','auto','read-only','workspace-write','danger-full-access')),\n usage_json TEXT,\n origin INTEGER NOT NULL CHECK(origin IN (0,1)),\n status TEXT NOT NULL CHECK(status IN ('running','completed','interrupted','failed')),\n error_json TEXT NOT NULL,\n started_at TEXT NOT NULL,\n completed_at TEXT\n );\n CREATE INDEX IF NOT EXISTS agent_turns_session_idx ON agent_turns(session_id, started_at ASC);\n\n CREATE TABLE IF NOT EXISTS agent_items (\n id TEXT PRIMARY KEY,\n turn_id TEXT NOT NULL REFERENCES agent_turns(id) ON DELETE CASCADE,\n provider_item_id TEXT,\n type TEXT NOT NULL,\n sequence INTEGER NOT NULL,\n status TEXT NOT NULL CHECK(status IN ('running','completed','waiting','interrupted','failed')),\n payload_json TEXT NOT NULL,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL\n );\n CREATE INDEX IF NOT EXISTS agent_items_turn_idx ON agent_items(turn_id, sequence ASC);\n\n CREATE TABLE IF NOT EXISTS agent_session_providers (\n session_id TEXT NOT NULL REFERENCES agent_sessions(id) ON DELETE CASCADE,\n provider TEXT NOT NULL,\n provider_session_id TEXT NOT NULL,\n model TEXT,\n reasoning_effort TEXT,\n reasoning_summary TEXT CHECK(reasoning_summary IN ('auto','concise','detailed','none')),\n service_tier TEXT,\n approval_policy_json TEXT,\n approvals_reviewer TEXT CHECK(approvals_reviewer IN ('user','auto_review','guardian_subagent')),\n permission_mode TEXT CHECK(permission_mode IN ('default','acceptEdits','bypassPermissions','plan','dontAsk','auto','read-only','workspace-write','danger-full-access')),\n token_usage_json TEXT,\n continuity_context TEXT NOT NULL DEFAULT '',\n synced_through_turn_id TEXT REFERENCES agent_turns(id) ON DELETE SET NULL,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL,\n PRIMARY KEY(session_id, provider)\n );\n";
1
+ export declare const DATABASE_SCHEMA = "\n CREATE TABLE IF NOT EXISTS operations (\n operation_id TEXT PRIMARY KEY,\n plugin_id TEXT NOT NULL,\n source TEXT NOT NULL,\n action TEXT NOT NULL,\n status TEXT NOT NULL,\n process_id TEXT,\n input_json TEXT NOT NULL,\n output_json TEXT NOT NULL,\n error_json TEXT NOT NULL,\n input_truncated INTEGER NOT NULL DEFAULT 0,\n output_truncated INTEGER NOT NULL DEFAULT 0,\n started_at TEXT NOT NULL,\n finished_at TEXT,\n duration_ms INTEGER\n );\n CREATE INDEX IF NOT EXISTS operations_started_idx ON operations(started_at DESC);\n CREATE INDEX IF NOT EXISTS operations_plugin_idx ON operations(plugin_id, started_at DESC);\n CREATE INDEX IF NOT EXISTS operations_status_idx ON operations(status, started_at DESC);\n\n CREATE TABLE IF NOT EXISTS oauth_clients (\n client_id TEXT PRIMARY KEY,\n name TEXT NOT NULL,\n redirect_uris_json TEXT NOT NULL,\n created_at TEXT NOT NULL\n );\n CREATE TABLE IF NOT EXISTS oauth_codes (\n code_hash TEXT PRIMARY KEY,\n client_id TEXT NOT NULL REFERENCES oauth_clients(client_id) ON DELETE CASCADE,\n redirect_uri TEXT NOT NULL,\n code_challenge TEXT NOT NULL,\n scope TEXT NOT NULL,\n expires_at TEXT NOT NULL,\n used_at TEXT\n );\n CREATE INDEX IF NOT EXISTS oauth_code_expiry_idx ON oauth_codes(expires_at);\n CREATE TABLE IF NOT EXISTS oauth_tokens (\n token_hash TEXT PRIMARY KEY,\n client_id TEXT NOT NULL REFERENCES oauth_clients(client_id) ON DELETE CASCADE,\n scope TEXT NOT NULL,\n expires_at TEXT NOT NULL,\n revoked_at TEXT,\n created_at TEXT NOT NULL\n );\n CREATE INDEX IF NOT EXISTS oauth_token_expiry_idx ON oauth_tokens(expires_at);\n CREATE TABLE IF NOT EXISTS oauth_refresh_tokens (\n token_hash TEXT PRIMARY KEY,\n client_id TEXT NOT NULL REFERENCES oauth_clients(client_id) ON DELETE CASCADE,\n scope TEXT NOT NULL,\n expires_at TEXT NOT NULL,\n revoked_at TEXT,\n created_at TEXT NOT NULL\n );\n CREATE INDEX IF NOT EXISTS oauth_refresh_expiry_idx ON oauth_refresh_tokens(expires_at);\n\n CREATE TABLE IF NOT EXISTS web_sessions (\n token_hash TEXT PRIMARY KEY,\n expires_at TEXT NOT NULL,\n created_at TEXT NOT NULL\n );\n CREATE INDEX IF NOT EXISTS web_session_expiry_idx ON web_sessions(expires_at);\n\n CREATE TABLE IF NOT EXISTS passkeys (\n credential_id TEXT PRIMARY KEY,\n public_key BLOB NOT NULL,\n counter INTEGER NOT NULL,\n transports_json TEXT NOT NULL,\n device_type TEXT NOT NULL CHECK(device_type IN ('singleDevice','multiDevice')),\n backed_up INTEGER NOT NULL,\n name TEXT NOT NULL,\n created_at TEXT NOT NULL,\n last_used_at TEXT NOT NULL\n );\n CREATE INDEX IF NOT EXISTS passkeys_last_used_idx ON passkeys(last_used_at DESC);\n\n CREATE TABLE IF NOT EXISTS computer_settings (\n id INTEGER PRIMARY KEY CHECK(id = 1),\n enabled INTEGER NOT NULL DEFAULT 0,\n remote_access INTEGER NOT NULL DEFAULT 1,\n updated_at TEXT NOT NULL\n );\n\n CREATE TABLE IF NOT EXISTS mcp_tool_settings (\n tool_name TEXT PRIMARY KEY,\n enabled INTEGER NOT NULL CHECK(enabled IN (0, 1)),\n updated_at TEXT NOT NULL\n );\n\n CREATE TABLE IF NOT EXISTS custom_mcp_servers (\n id TEXT PRIMARY KEY,\n name TEXT NOT NULL,\n config_json TEXT NOT NULL,\n enabled INTEGER NOT NULL CHECK(enabled IN (0, 1)),\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL\n );\n\n CREATE TABLE IF NOT EXISTS agent_sessions (\n id TEXT PRIMARY KEY,\n workspace_root TEXT NOT NULL,\n provider TEXT NOT NULL,\n status TEXT NOT NULL CHECK(status IN ('idle','running','waiting_input','error')),\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL\n );\n CREATE INDEX IF NOT EXISTS agent_sessions_updated_idx ON agent_sessions(updated_at DESC);\n\n CREATE TABLE IF NOT EXISTS agent_turns (\n id TEXT PRIMARY KEY,\n session_id TEXT NOT NULL REFERENCES agent_sessions(id) ON DELETE CASCADE,\n provider_turn_id TEXT,\n provider TEXT NOT NULL,\n model TEXT,\n reasoning_effort TEXT,\n reasoning_summary TEXT CHECK(reasoning_summary IN ('auto','concise','detailed','none')),\n service_tier TEXT,\n approval_policy_json TEXT,\n approvals_reviewer TEXT CHECK(approvals_reviewer IN ('user','auto_review','guardian_subagent')),\n permission_mode TEXT CHECK(permission_mode IN ('default','acceptEdits','bypassPermissions','plan','dontAsk','auto','read-only','workspace-write','danger-full-access')),\n usage_json TEXT,\n origin INTEGER NOT NULL CHECK(origin IN (0,1)),\n status TEXT NOT NULL CHECK(status IN ('running','completed','interrupted','failed')),\n error_json TEXT NOT NULL,\n started_at TEXT NOT NULL,\n completed_at TEXT\n );\n CREATE INDEX IF NOT EXISTS agent_turns_session_idx ON agent_turns(session_id, started_at ASC);\n\n CREATE TABLE IF NOT EXISTS agent_items (\n id TEXT PRIMARY KEY,\n turn_id TEXT NOT NULL REFERENCES agent_turns(id) ON DELETE CASCADE,\n provider_item_id TEXT,\n type TEXT NOT NULL,\n sequence INTEGER NOT NULL,\n status TEXT NOT NULL CHECK(status IN ('running','completed','waiting','interrupted','failed')),\n payload_json TEXT NOT NULL,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL\n );\n CREATE INDEX IF NOT EXISTS agent_items_turn_idx ON agent_items(turn_id, sequence ASC);\n\n CREATE TABLE IF NOT EXISTS agent_session_providers (\n session_id TEXT NOT NULL REFERENCES agent_sessions(id) ON DELETE CASCADE,\n provider TEXT NOT NULL,\n provider_session_id TEXT NOT NULL,\n model TEXT,\n reasoning_effort TEXT,\n reasoning_summary TEXT CHECK(reasoning_summary IN ('auto','concise','detailed','none')),\n service_tier TEXT,\n approval_policy_json TEXT,\n approvals_reviewer TEXT CHECK(approvals_reviewer IN ('user','auto_review','guardian_subagent')),\n permission_mode TEXT CHECK(permission_mode IN ('default','acceptEdits','bypassPermissions','plan','dontAsk','auto','read-only','workspace-write','danger-full-access')),\n token_usage_json TEXT,\n continuity_context TEXT NOT NULL DEFAULT '',\n synced_through_turn_id TEXT REFERENCES agent_turns(id) ON DELETE SET NULL,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL,\n PRIMARY KEY(session_id, provider)\n );\n";
@@ -87,6 +87,15 @@ export const DATABASE_SCHEMA = `
87
87
  updated_at TEXT NOT NULL
88
88
  );
89
89
 
90
+ CREATE TABLE IF NOT EXISTS custom_mcp_servers (
91
+ id TEXT PRIMARY KEY,
92
+ name TEXT NOT NULL,
93
+ config_json TEXT NOT NULL,
94
+ enabled INTEGER NOT NULL CHECK(enabled IN (0, 1)),
95
+ created_at TEXT NOT NULL,
96
+ updated_at TEXT NOT NULL
97
+ );
98
+
90
99
  CREATE TABLE IF NOT EXISTS agent_sessions (
91
100
  id TEXT PRIMARY KEY,
92
101
  workspace_root TEXT NOT NULL,
@@ -0,0 +1,54 @@
1
+ import { type McpServer } from "@modelcontextprotocol/server";
2
+ import type { OperationLog } from "#operations/operation-log";
3
+ import type { McpToolControl } from "#mcp/server/tool-control";
4
+ import type { CustomMcpConfig, CustomMcpServerSummary, CustomMcpStore } from "./types.js";
5
+ export declare class CustomMcpManager {
6
+ private readonly repository;
7
+ private readonly control;
8
+ private readonly operations;
9
+ private readonly oauth;
10
+ private readonly authorizationFlows;
11
+ private readonly entries;
12
+ private closed;
13
+ constructor(repository: CustomMcpStore, control: McpToolControl, operations: OperationLog, dataDir?: string);
14
+ start(): void;
15
+ list(): CustomMcpServerSummary[];
16
+ create(input: Omit<CustomMcpConfig, "id">): Promise<CustomMcpServerSummary>;
17
+ update(id: string, input: Partial<Omit<CustomMcpConfig, "id">>): Promise<CustomMcpServerSummary>;
18
+ remove(id: string): Promise<void>;
19
+ refresh(id: string): Promise<CustomMcpServerSummary>;
20
+ test(input: Omit<CustomMcpConfig, "id">): Promise<{
21
+ tools: number;
22
+ authRequired?: boolean;
23
+ }>;
24
+ testStored(id: string): Promise<{
25
+ tools: number;
26
+ authRequired?: boolean;
27
+ }>;
28
+ private probe;
29
+ /**
30
+ * Start an interactive OAuth flow for an existing HTTP MCP server.
31
+ * The callback state is single-use and never exposed to the browser.
32
+ */
33
+ authorize(id: string, redirectUrl: string): Promise<{
34
+ authorizationUrl: string;
35
+ }>;
36
+ completeAuthorization(query: URLSearchParams): Promise<void>;
37
+ revokeAuthorization(id: string): Promise<void>;
38
+ private dropPendingAuthorization;
39
+ private authMode;
40
+ registerMcp(server: McpServer): void;
41
+ private call;
42
+ private connect;
43
+ private open;
44
+ private define;
45
+ private refreshTools;
46
+ private transport;
47
+ private scheduleRetry;
48
+ private forgetTools;
49
+ private disconnect;
50
+ private dispose;
51
+ private require;
52
+ private summary;
53
+ close(): Promise<void>;
54
+ }