@xopcai/xopc 0.0.224 → 0.0.225

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 (183) hide show
  1. package/dist/browser-ext/manifest.json +1 -1
  2. package/dist/extensions/demo-memory/src/index.js +10 -1
  3. package/dist/extensions/feishu/src/outbound/media-load.js +1 -1
  4. package/dist/extensions/telegram/xopc.extension.json +1 -1
  5. package/dist/gateway/static/root/assets/{activity-store--GBciH10.js → activity-store-CtTYnku4.js} +1 -1
  6. package/dist/gateway/static/root/assets/{agent-avatar-dicebear-K7V4XXt7.js → agent-avatar-dicebear-dizuqu_4.js} +1 -1
  7. package/dist/gateway/static/root/assets/{agents-BsxTpXyq.js → agents-DeEEFZJ4.js} +1 -1
  8. package/dist/gateway/static/root/assets/{api-HoIz_ija.js → api-CLRMz2BT.js} +1 -1
  9. package/dist/gateway/static/root/assets/{app-management-settings-panel-xQCmkAHh.js → app-management-settings-panel-Cnk-DtQh.js} +1 -1
  10. package/dist/gateway/static/root/assets/{appearance-settings-BRoeA4kU.js → appearance-settings-wP0rogVI.js} +1 -1
  11. package/dist/gateway/static/root/assets/{apps-page-BXgpBA8w.js → apps-page-Db5YNHVM.js} +1 -1
  12. package/dist/gateway/static/root/assets/{archive-plugin-CteCVsBP.js → archive-plugin-Cpzsm-DM.js} +1 -1
  13. package/dist/gateway/static/root/assets/{attachment-load-D7xNA2jB.js → attachment-load-Bd2OOoEK.js} +1 -1
  14. package/dist/gateway/static/root/assets/{automations-page-BD1vK7r5.js → automations-page-DNqhguyk.js} +1 -1
  15. package/dist/gateway/static/root/assets/{automations-workspace-wrgf-z7D.js → automations-workspace-CNT05YTb.js} +1 -1
  16. package/dist/gateway/static/root/assets/{binary-plugins-C3DaJa0W.js → binary-plugins-Dcma5HdI.js} +1 -1
  17. package/dist/gateway/static/root/assets/{block-editor-lTkcHTve.js → block-editor-D-B8ThwK.js} +1 -1
  18. package/dist/gateway/static/root/assets/{browser-settings-page-zhzNkeVG.js → browser-settings-page-UbDj3LIq.js} +1 -1
  19. package/dist/gateway/static/root/assets/{browser-workflow-inputs-1UZ0vnIy.js → browser-workflow-inputs-BAPZIXrs.js} +1 -1
  20. package/dist/gateway/static/root/assets/{browser-workflows-page-BHuJOF4M.js → browser-workflows-page-DHtaSKEh.js} +1 -1
  21. package/dist/gateway/static/root/assets/{capabilities-settings-panel-DuCmg2Ej.js → capabilities-settings-panel-CiO152QS.js} +1 -1
  22. package/dist/gateway/static/root/assets/{capability-presets-api-yNqTIuyd.js → capability-presets-api-zsZQ7im3.js} +1 -1
  23. package/dist/gateway/static/root/assets/{capability-presets-settings-panel-DjiP3mQQ.js → capability-presets-settings-panel-Bc_hRwfL.js} +1 -1
  24. package/dist/gateway/static/root/assets/{channel-recipient-api-krezGsKc.js → channel-recipient-api-Cp8iaaXp.js} +1 -1
  25. package/dist/gateway/static/root/assets/{channels-settings-B4AtvQHk.js → channels-settings-CFW7zBoT.js} +1 -1
  26. package/dist/gateway/static/root/assets/channels-status-swr-5rNVq7iI.js +1 -0
  27. package/dist/gateway/static/root/assets/{chat-terminal-dock-BRrL2iHA.js → chat-terminal-dock-Dr6Rq2ln.js} +1 -1
  28. package/dist/gateway/static/root/assets/connectors-page-DLBr5nre.js +2 -0
  29. package/dist/gateway/static/root/assets/{cron-expression-Bl-2DzBQ.js → cron-expression-BiXrpzmo.js} +1 -1
  30. package/dist/gateway/static/root/assets/{date-picker-CxTl6Wlz.js → date-picker-BPuRtwKt.js} +1 -1
  31. package/dist/gateway/static/root/assets/{dependency-picker-DSl0mOPa.js → dependency-picker-BNC_W7wI.js} +1 -1
  32. package/dist/gateway/static/root/assets/{desktop-pet-DTeCmCdb.js → desktop-pet-DzOpt5nI.js} +1 -1
  33. package/dist/gateway/static/root/assets/{desktop-pet-settings-BNebxg0V.js → desktop-pet-settings-BW0brCvH.js} +1 -1
  34. package/dist/gateway/static/root/assets/{directory-picker-path-field-Dn9XpmXY.js → directory-picker-path-field-Baml0uTS.js} +1 -1
  35. package/dist/gateway/static/root/assets/{extension-debug-page-D-J-eOTb.js → extension-debug-page-CBXyKmfS.js} +1 -1
  36. package/dist/gateway/static/root/assets/{extension-page-CpST24nU.js → extension-page-BBhcr_SZ.js} +1 -1
  37. package/dist/gateway/static/root/assets/{extension-settings-page-4nQXFnFq.js → extension-settings-page-4rwWpN9_.js} +1 -1
  38. package/dist/gateway/static/root/assets/{gateway-config-api-BHWHduDi.js → gateway-config-api-Dg-BlBe4.js} +1 -1
  39. package/dist/gateway/static/root/assets/{gateway-config-swr-yUxMMVjK.js → gateway-config-swr-Du0MvK9s.js} +1 -1
  40. package/dist/gateway/static/root/assets/{gateway-settings-BimMKmkM.js → gateway-settings-BOuYZOYe.js} +1 -1
  41. package/dist/gateway/static/root/assets/{gateway-startup-retry-9mrsL84j.js → gateway-startup-retry-JbjcHCaN.js} +1 -1
  42. package/dist/gateway/static/root/assets/{heartbeat-settings-MUJ3mEC9.js → heartbeat-settings-Dq4YAlSx.js} +1 -1
  43. package/dist/gateway/static/root/assets/{home-page-DSM6aPHF.js → home-page-BIXkO0Ck.js} +1 -1
  44. package/dist/gateway/static/root/assets/{image-generation-api-CgFAGDi8.js → image-generation-api-G2tfPvKP.js} +1 -1
  45. package/dist/gateway/static/root/assets/{index-B3zYJBhK.js → index-Csccu8sa.js} +11 -11
  46. package/dist/gateway/static/root/assets/{keyboard-shortcuts-settings-6Ar5rfX4.js → keyboard-shortcuts-settings-L1Vtxgp9.js} +1 -1
  47. package/dist/gateway/static/root/assets/{local-app-workbench-page-tzM0wTL8.js → local-app-workbench-page-D-eacBcO.js} +1 -1
  48. package/dist/gateway/static/root/assets/{local-apps-page-B5n-x544.js → local-apps-page--thlkBLS.js} +1 -1
  49. package/dist/gateway/static/root/assets/{locale-store-CPylQjH9.js → locale-store-DqJz-cOR.js} +1 -1
  50. package/dist/gateway/static/root/assets/{logs-page-CgoQlera.js → logs-page-BRCglHvd.js} +1 -1
  51. package/dist/gateway/static/root/assets/{management-settings-CXBLyU-0.js → management-settings-p55fR4XO.js} +1 -1
  52. package/dist/gateway/static/root/assets/{markdown-split-CtmQemJi.js → markdown-split-DwMcRlBR.js} +1 -1
  53. package/dist/gateway/static/root/assets/{markdown-view-CUsob4Pz.js → markdown-view-CTB9u15e.js} +1 -1
  54. package/dist/gateway/static/root/assets/{media-plugins-Dsd-IEOW.js → media-plugins-O6H3GZn7.js} +1 -1
  55. package/dist/gateway/static/root/assets/{messages-B82nsi9k.js → messages-CeydamsI.js} +3 -3
  56. package/dist/gateway/static/root/assets/{notes-api-CfKZYrBC.js → notes-api-CrjQbmk1.js} +1 -1
  57. package/dist/gateway/static/root/assets/{notes-page-BKbOTE2n.js → notes-page-miO9ig9C.js} +1 -1
  58. package/dist/gateway/static/root/assets/{notes-workbench-BScELo01.js → notes-workbench-_13lmvmF.js} +2 -2
  59. package/dist/gateway/static/root/assets/{product-open-page-DqB9bsDB.js → product-open-page-BOu07nXf.js} +1 -1
  60. package/dist/gateway/static/root/assets/{project-detail-page-DUoyty8X.js → project-detail-page-BStRkOQr.js} +1 -1
  61. package/dist/gateway/static/root/assets/{projects-page-PeXUdGdO.js → projects-page-CY03RxVr.js} +1 -1
  62. package/dist/gateway/static/root/assets/{registry-api-B_5D8epF.js → registry-api-Bf_HDLUJ.js} +1 -1
  63. package/dist/gateway/static/root/assets/{remote-access-hub-sHho-uXP.js → remote-access-hub-B19llkld.js} +1 -1
  64. package/dist/gateway/static/root/assets/{runtime-tools-settings-panel-DBTH_t0O.js → runtime-tools-settings-panel-DthuRqUH.js} +1 -1
  65. package/dist/gateway/static/root/assets/{schema-form-CqMKAwV0.js → schema-form-8W8PhXxa.js} +1 -1
  66. package/dist/gateway/static/root/assets/{sessions-page-DyvNgaP9.js → sessions-page-BDA2qbTd.js} +1 -1
  67. package/dist/gateway/static/root/assets/settings-advanced-gate-BONIDX4v.js +1 -0
  68. package/dist/gateway/static/root/assets/{settings-form-section-dPriuuYq.js → settings-form-section-CrsKGYiG.js} +1 -1
  69. package/dist/gateway/static/root/assets/{settings-loading-skeleton-B-Mbm8Va.js → settings-loading-skeleton-WJtI0AAq.js} +1 -1
  70. package/dist/gateway/static/root/assets/{settings-page-Dyj3w-Ml.js → settings-page-PCjwRF47.js} +1 -1
  71. package/dist/gateway/static/root/assets/{setup-status-panel-C4FxY5J3.js → setup-status-panel-D65CwlNC.js} +1 -1
  72. package/dist/gateway/static/root/assets/{share-preview-page-emEdx7YL.js → share-preview-page-BGtvLLeZ.js} +1 -1
  73. package/dist/gateway/static/root/assets/{shares-settings-BQBVq-sN.js → shares-settings-CdzVKrBl.js} +1 -1
  74. package/dist/gateway/static/root/assets/skill-reload-api-CsRWid5h.js +1 -0
  75. package/dist/gateway/static/root/assets/{skills-page-BNtYnrRT.js → skills-page-C_npyryI.js} +1 -1
  76. package/dist/gateway/static/root/assets/{skills-page.utils-DjnbNFs5.js → skills-page.utils-C46Sd78k.js} +1 -1
  77. package/dist/gateway/static/root/assets/{system-settings-panel-DDStf-jZ.js → system-settings-panel-DomTLwki.js} +1 -1
  78. package/dist/gateway/static/root/assets/{task-detail-page-ZVwr8KbS.js → task-detail-page-B6YFX7kK.js} +1 -1
  79. package/dist/gateway/static/root/assets/{theme-store-BN-32Fz5.js → theme-store-Ce7wq__X.js} +1 -1
  80. package/dist/gateway/static/root/assets/{use-autosave-C2mKZ-ua.js → use-autosave-Do9mCRcY.js} +1 -1
  81. package/dist/gateway/static/root/assets/{user-context-page-CHjIinCW.js → user-context-page-CiCQA40E.js} +1 -1
  82. package/dist/gateway/static/root/assets/{voice-api-key-field-D7OHeQby.js → voice-api-key-field-Bc5p-0tX.js} +1 -1
  83. package/dist/gateway/static/root/assets/{work-discovery-overlay-CIiXie7F.js → work-discovery-overlay-BieqsmT1.js} +1 -1
  84. package/dist/gateway/static/root/assets/{workflow-run-setup-panel-9_dAGW3_.js → workflow-run-setup-panel-DVj8iYvm.js} +1 -1
  85. package/dist/gateway/static/root/assets/{workflows-page-DXpYJxao.js → workflows-page-AG04TTuH.js} +1 -1
  86. package/dist/gateway/static/root/index.html +10 -10
  87. package/dist/package.js +1 -1
  88. package/dist/src/agent/embedded/run-turn.js +2 -0
  89. package/dist/src/agent/mcp/bundle-mcp-config.d.ts +9 -1
  90. package/dist/src/agent/mcp/bundle-mcp-config.js +11 -15
  91. package/dist/src/agent/mcp/embedded-mcp.d.ts +1 -1
  92. package/dist/src/agent/mcp/index.d.ts +1 -0
  93. package/dist/src/agent/mcp/index.js +7 -1
  94. package/dist/src/agent/mcp/mcp-http.d.ts +4 -0
  95. package/dist/src/agent/mcp/mcp-http.js +25 -1
  96. package/dist/src/agent/mcp/mcp-transport-config.d.ts +4 -0
  97. package/dist/src/agent/mcp/mcp-transport-config.js +1 -0
  98. package/dist/src/agent/mcp/mcp-transport.d.ts +7 -1
  99. package/dist/src/agent/mcp/mcp-transport.js +38 -11
  100. package/dist/src/agent/mcp/oauth/index.d.ts +6 -0
  101. package/dist/src/agent/mcp/oauth/index.js +6 -0
  102. package/dist/src/agent/mcp/oauth/mcp-oauth-errors.d.ts +5 -0
  103. package/dist/src/agent/mcp/oauth/mcp-oauth-errors.js +14 -0
  104. package/dist/src/agent/mcp/oauth/mcp-oauth-manager.d.ts +25 -0
  105. package/dist/src/agent/mcp/oauth/mcp-oauth-manager.js +219 -0
  106. package/dist/src/agent/mcp/oauth/mcp-oauth-provider.d.ts +33 -0
  107. package/dist/src/agent/mcp/oauth/mcp-oauth-provider.js +103 -0
  108. package/dist/src/agent/mcp/oauth/mcp-oauth-session.d.ts +29 -0
  109. package/dist/src/agent/mcp/oauth/mcp-oauth-session.js +169 -0
  110. package/dist/src/agent/mcp/oauth/mcp-oauth-store.d.ts +9 -0
  111. package/dist/src/agent/mcp/oauth/mcp-oauth-store.js +79 -0
  112. package/dist/src/agent/mcp/oauth/mcp-oauth-types.d.ts +22 -0
  113. package/dist/src/agent/mcp/oauth/mcp-oauth-types.js +1 -0
  114. package/dist/src/agent/memory/builtin-provider.js +23 -4
  115. package/dist/src/agent/memory/compaction-promotion.d.ts +18 -0
  116. package/dist/src/agent/memory/compaction-promotion.js +211 -0
  117. package/dist/src/agent/memory/context/types.d.ts +5 -0
  118. package/dist/src/agent/memory/manager.d.ts +3 -0
  119. package/dist/src/agent/memory/manager.js +12 -2
  120. package/dist/src/agent/memory/trusted-recall.d.ts +7 -0
  121. package/dist/src/agent/memory/trusted-recall.js +53 -0
  122. package/dist/src/agent/memory/turn-provenance.d.ts +14 -0
  123. package/dist/src/agent/memory/turn-provenance.js +60 -0
  124. package/dist/src/agent/memory/types.d.ts +18 -3
  125. package/dist/src/agent/memory/user-context-coordinator.js +57 -6
  126. package/dist/src/agent/skills/marketplace/adapters/store/store-api-client.d.ts +2 -0
  127. package/dist/src/agent/tools/memory-tool.js +5 -1
  128. package/dist/src/capabilities/store-connector.js +99 -10
  129. package/dist/src/cli/commands/channels.js +1 -1
  130. package/dist/src/cli/commands/connectors.js +1 -1
  131. package/dist/src/cli/commands/doctor/checks/database-schema.js +1 -1
  132. package/dist/src/cli/commands/extension-marketplace.js +1 -1
  133. package/dist/src/cli/commands/gateway/lifecycle.js +1 -1
  134. package/dist/src/cli/commands/models.js +1 -1
  135. package/dist/src/cli/commands/onboard.js +1 -1
  136. package/dist/src/config/config-mutation.d.ts +14 -0
  137. package/dist/src/config/config-mutation.js +33 -0
  138. package/dist/src/config/index.js +3 -3
  139. package/dist/src/config/paths.d.ts +2 -0
  140. package/dist/src/config/paths.js +5 -1
  141. package/dist/src/config/schema.d.ts +17 -0
  142. package/dist/src/config/schema.js +26 -2
  143. package/dist/src/connectors/auth-provider-registry.d.ts +1 -1
  144. package/dist/src/connectors/auth-provider-registry.js +23 -2
  145. package/dist/src/connectors/install.js +3 -3
  146. package/dist/src/connectors/instances.d.ts +2 -1
  147. package/dist/src/connectors/instances.js +15 -5
  148. package/dist/src/connectors/materialize.d.ts +1 -0
  149. package/dist/src/connectors/materialize.js +9 -1
  150. package/dist/src/connectors/runtime-adapter-registry.js +1 -0
  151. package/dist/src/connectors/types.d.ts +4 -1
  152. package/dist/src/extensions/connector-dependencies.d.ts +10 -0
  153. package/dist/src/extensions/connector-dependencies.js +65 -0
  154. package/dist/src/extensions/install.js +27 -0
  155. package/dist/src/extensions/normalize-manifest.js +1 -0
  156. package/dist/src/extensions/sdk/index.js +1 -1
  157. package/dist/src/extensions/types/manifest.d.ts +2 -0
  158. package/dist/src/gateway/heartbeat/service.js +1 -1
  159. package/dist/src/gateway/hono/routes/capabilities.js +8 -8
  160. package/dist/src/gateway/hono/routes/connectors.js +87 -55
  161. package/dist/src/gateway/hono/routes/mcp.js +77 -5
  162. package/dist/src/gateway/hono/routes/voice.js +1 -1
  163. package/dist/src/gateway/service.js +8 -8
  164. package/dist/src/heartbeat/index.js +1 -1
  165. package/dist/src/infra/update-restart.js +1 -1
  166. package/dist/src/knowledge/connected-knowledge-pipeline.js +26 -4
  167. package/dist/src/session/store.js +27 -0
  168. package/dist/src/storage/sqlite/memory-records-repository.d.ts +9 -1
  169. package/dist/src/storage/sqlite/memory-records-repository.js +40 -6
  170. package/dist/src/storage/sqlite/migrations/134_memory_provenance.sql +18 -0
  171. package/dist/src/storage/sqlite/migrations/runner.d.ts +1 -1
  172. package/dist/src/storage/sqlite/migrations/runner.js +3 -3
  173. package/dist/src/storage/sqlite/migrations/runner.ts +1 -1
  174. package/dist/src/storage/sqlite/schema.js +1 -1
  175. package/dist/src/tui/backends/embedded-backend.js +1 -1
  176. package/dist/src/user-context/planner.js +8 -3
  177. package/package.json +2 -2
  178. package/dist/gateway/static/root/assets/channels-status-swr-DMxB7slL.js +0 -1
  179. package/dist/gateway/static/root/assets/connectors-page-fmda2NEP.js +0 -2
  180. package/dist/gateway/static/root/assets/settings-advanced-gate-CnYf1R7z.js +0 -1
  181. package/dist/gateway/static/root/assets/skill-reload-api-BFoEpQ4I.js +0 -1
  182. package/dist/src/extensions/bundle-mcp.d.ts +0 -18
  183. package/dist/src/extensions/bundle-mcp.js +0 -69
@@ -4,6 +4,7 @@ export declare function materializeConnectorMcpServer(definition: ConnectorDefin
4
4
  serverId: string;
5
5
  server: Record<string, unknown>;
6
6
  };
7
+ export declare function connectorDefinitionFromManagedMarker(marker: unknown): ConnectorDefinition | undefined;
7
8
  export declare function isManagedConnectorServer(server: unknown): server is Record<string, unknown> & {
8
9
  xopcConnector: ManagedConnectorMarker;
9
10
  };
@@ -56,6 +56,7 @@ function materializeConnectorMcpServer(definition, input) {
56
56
  displayName: definition.displayName,
57
57
  source: definition.source,
58
58
  artifactSha256: definition.provenance?.sha256,
59
+ definition,
59
60
  ...input.config && Object.keys(input.config).length > 0 ? { config: input.config } : {}
60
61
  };
61
62
  const server = {
@@ -69,10 +70,17 @@ function materializeConnectorMcpServer(definition, input) {
69
70
  server
70
71
  };
71
72
  }
73
+ function connectorDefinitionFromManagedMarker(marker) {
74
+ if (!marker || typeof marker !== "object" || Array.isArray(marker)) return void 0;
75
+ const definition = marker.definition;
76
+ if (!definition || typeof definition !== "object" || Array.isArray(definition)) return void 0;
77
+ const record = definition;
78
+ return typeof record.id === "string" && typeof record.version === "string" && record.runtime ? record : void 0;
79
+ }
72
80
  function isManagedConnectorServer(server) {
73
81
  if (!server || typeof server !== "object" || Array.isArray(server)) return false;
74
82
  const marker = server.xopcConnector;
75
83
  return Boolean(marker && typeof marker === "object" && !Array.isArray(marker) && marker.managed === true && typeof marker.connectorId === "string");
76
84
  }
77
85
  //#endregion
78
- export { assertValidConnectorServerId, isManagedConnectorServer, materializeConnectorMcpServer };
86
+ export { assertValidConnectorServerId, connectorDefinitionFromManagedMarker, isManagedConnectorServer, materializeConnectorMcpServer };
@@ -32,6 +32,7 @@ function installRecord(config, definition, extra = {}, markerExtra = {}) {
32
32
  displayName: definition.displayName,
33
33
  source: definition.source,
34
34
  artifactSha256: definition.provenance?.sha256,
35
+ definition,
35
36
  ...markerExtra
36
37
  },
37
38
  runtime: definition.runtime,
@@ -8,7 +8,8 @@ export type ConnectorAuthDefinition = {
8
8
  mode: 'apiKey';
9
9
  } | {
10
10
  mode: 'oauth';
11
- provider: string;
11
+ provider?: string;
12
+ clientId?: string;
12
13
  };
13
14
  export type ConnectorScope = 'read' | 'write' | 'admin';
14
15
  export type ConnectorVerificationLevel = 'verified' | 'beta' | 'experimental';
@@ -289,4 +290,6 @@ export type ManagedConnectorMarker = {
289
290
  config?: Record<string, unknown>;
290
291
  source?: ConnectorDefinition['source'];
291
292
  artifactSha256?: string;
293
+ /** Immutable install-time snapshot used for update, health, and uninstall after restart. */
294
+ definition?: ConnectorDefinition;
292
295
  };
@@ -0,0 +1,10 @@
1
+ import type { Config } from '../config/schema.js';
2
+ export type ExtensionConnectorDiagnostic = {
3
+ extensionId: string;
4
+ connectorId: string;
5
+ message: string;
6
+ };
7
+ export declare function inspectExtensionConnectorDependencies(params: {
8
+ cfg?: Config;
9
+ roots?: string[];
10
+ }): ExtensionConnectorDiagnostic[];
@@ -0,0 +1,65 @@
1
+ import { init_paths, resolveBundledExtensionsDir, resolveExtensionsDir } from "../config/paths.js";
2
+ import { isRecord } from "../utils/is-record.js";
3
+ import { join } from "node:path";
4
+ import { existsSync, readFileSync, readdirSync } from "node:fs";
5
+ //#region src/extensions/connector-dependencies.ts
6
+ init_paths();
7
+ function isExtensionEnabled(extensionId, cfg) {
8
+ const disabled = cfg?.extensions?.disabled;
9
+ if (Array.isArray(disabled) && disabled.includes(extensionId)) return false;
10
+ const enabled = cfg?.extensions?.enabled;
11
+ return !Array.isArray(enabled) || enabled.length === 0 || enabled.includes(extensionId);
12
+ }
13
+ function enabledConnectorIds(cfg) {
14
+ const ids = /* @__PURE__ */ new Set();
15
+ for (const server of Object.values(cfg?.mcp?.servers ?? {})) {
16
+ if (!isRecord(server) || !isRecord(server.xopcConnector)) continue;
17
+ if (server.xopcConnector.managed === true && server.xopcConnector.enabled !== false && typeof server.xopcConnector.connectorId === "string") ids.add(server.xopcConnector.connectorId);
18
+ }
19
+ for (const instance of Object.values(cfg?.connectors?.instances ?? {})) {
20
+ if (!isRecord(instance) || !isRecord(instance.xopcConnector)) continue;
21
+ if (instance.xopcConnector.managed === true && instance.xopcConnector.enabled !== false && typeof instance.xopcConnector.connectorId === "string") ids.add(instance.xopcConnector.connectorId);
22
+ }
23
+ return ids;
24
+ }
25
+ function scanRoot(params) {
26
+ if (!existsSync(params.root)) return;
27
+ for (const entry of readdirSync(params.root, { withFileTypes: true })) {
28
+ if (!entry.isDirectory() || params.seenExtensions.has(entry.name) || !isExtensionEnabled(entry.name, params.cfg)) continue;
29
+ const manifestPath = join(params.root, entry.name, "xopc.extension.json");
30
+ if (!existsSync(manifestPath)) continue;
31
+ params.seenExtensions.add(entry.name);
32
+ let manifest;
33
+ try {
34
+ manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
35
+ } catch {
36
+ continue;
37
+ }
38
+ if (!isRecord(manifest) || !Array.isArray(manifest.connectorDependencies)) continue;
39
+ const dependencies = [...new Set(manifest.connectorDependencies.filter((value) => typeof value === "string").map((value) => value.trim()).filter(Boolean))];
40
+ for (const connectorId of dependencies) {
41
+ if (params.installed.has(connectorId)) continue;
42
+ params.diagnostics.push({
43
+ extensionId: entry.name,
44
+ connectorId,
45
+ message: `Extension "${entry.name}" requires enabled Connector "${connectorId}". Install and connect it from the Connector Store.`
46
+ });
47
+ }
48
+ }
49
+ }
50
+ function inspectExtensionConnectorDependencies(params) {
51
+ const diagnostics = [];
52
+ const installed = enabledConnectorIds(params.cfg);
53
+ const seenExtensions = /* @__PURE__ */ new Set();
54
+ const roots = params.roots ?? [resolveExtensionsDir(), resolveBundledExtensionsDir()].filter((root) => Boolean(root));
55
+ for (const root of roots) scanRoot({
56
+ root,
57
+ cfg: params.cfg,
58
+ installed,
59
+ seenExtensions,
60
+ diagnostics
61
+ });
62
+ return diagnostics;
63
+ }
64
+ //#endregion
65
+ export { inspectExtensionConnectorDependencies };
@@ -172,6 +172,24 @@ function isSafeZipPath(name) {
172
172
  for (const p of normalized.split("/")) if (p === "..") return false;
173
173
  return true;
174
174
  }
175
+ function findEmbeddedMcpConfig(root) {
176
+ for (const entry of readdirSync(root, { withFileTypes: true })) {
177
+ if (entry.isSymbolicLink()) continue;
178
+ const path = join(root, entry.name);
179
+ if (entry.isFile() && entry.name.toLowerCase() === ".mcp.json") return path;
180
+ if (entry.isDirectory()) {
181
+ const nested = findEmbeddedMcpConfig(path);
182
+ if (nested) return nested;
183
+ }
184
+ }
185
+ }
186
+ function validateConnectorDependencies(value) {
187
+ if (value === void 0) return void 0;
188
+ if (!Array.isArray(value) || value.some((item) => typeof item !== "string")) return "Extension connectorDependencies must be an array of unique Connector ids";
189
+ const dependencies = value;
190
+ const validId = /^[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$/;
191
+ if (dependencies.some((id) => !validId.test(id)) || new Set(dependencies).size !== dependencies.length) return "Extension connectorDependencies must be an array of unique Connector ids";
192
+ }
175
193
  function isIgnorableZipEntry(name) {
176
194
  const n = name.replace(/\\/g, "/");
177
195
  if (n.startsWith("__MACOSX/")) return true;
@@ -448,6 +466,15 @@ async function installFromDirectory(sourceDir, extensionsDir) {
448
466
  ok: false,
449
467
  error: "Extension must include xopc.extension.json"
450
468
  };
469
+ if (findEmbeddedMcpConfig(sourceDir)) return {
470
+ ok: false,
471
+ error: "Extensions must declare connectorDependencies instead of embedding .mcp.json"
472
+ };
473
+ const connectorDependencyError = validateConnectorDependencies(manifest.connectorDependencies);
474
+ if (connectorDependencyError) return {
475
+ ok: false,
476
+ error: connectorDependencyError
477
+ };
451
478
  if (!manifest.engines?.xopc) return {
452
479
  ok: false,
453
480
  error: "Extension manifest must declare engines.xopc"
@@ -130,6 +130,7 @@ function normalizeExtensionManifest(raw) {
130
130
  main: typeof raw.main === "string" ? raw.main : void 0,
131
131
  configSchema: isRecord(raw.configSchema) ? raw.configSchema : void 0,
132
132
  dependencies: isRecord(raw.dependencies) ? raw.dependencies : void 0,
133
+ connectorDependencies: Array.isArray(raw.connectorDependencies) ? [...new Set(raw.connectorDependencies.filter((value) => typeof value === "string").map((value) => value.trim()).filter(Boolean))] : void 0,
133
134
  enabledByDefault: typeof raw.enabledByDefault === "boolean" ? raw.enabledByDefault : void 0,
134
135
  providers: Array.isArray(raw.providers) ? raw.providers.filter((x) => typeof x === "string") : void 0,
135
136
  speechProviders: Array.isArray(raw.speechProviders) ? raw.speechProviders.filter((x) => typeof x === "string") : void 0,
@@ -7,8 +7,8 @@ import { SlotRegistry, getSlotRegistry, registerSlotType } from "../slots.js";
7
7
  import { ExtensionDiagnostics, ExtensionLoaderCacheImpl, getExtensionCache, getExtensionDiagnostics } from "../diagnostics.js";
8
8
  import "../loader.js";
9
9
  import { ExtensionHookRunner, createHookContext, isHookEvent } from "../hooks.js";
10
- import { registerExtensionCliProgram } from "./channel-helpers.js";
11
10
  import { defineChannelPluginEntry } from "./channel-entry.js";
11
+ import { registerExtensionCliProgram } from "./channel-helpers.js";
12
12
  import { lazyFunction, lazyModule } from "./lazy.js";
13
13
  import { createMockExtensionApi, createTestGateway } from "./testing.js";
14
14
  //#region src/extensions/sdk/index.ts
@@ -20,6 +20,8 @@ export interface ExtensionManifest {
20
20
  main?: string;
21
21
  configSchema?: Record<string, unknown>;
22
22
  dependencies?: Record<string, string>;
23
+ /** Connector ids that must already be installed and enabled. Extensions never embed MCP server configuration. */
24
+ connectorDependencies?: string[];
23
25
  enabledByDefault?: boolean;
24
26
  providers?: string[];
25
27
  /** Speech/TTS provider ids implemented by this extension. */
@@ -1,9 +1,9 @@
1
1
  import { createLogger } from "../../utils/logger/index.js";
2
2
  import { init_logger } from "../../utils/logger.js";
3
3
  import { shouldSilence, stripHeartbeatToken } from "../../heartbeat/tokens.js";
4
+ import { appendCronEventLines } from "../../heartbeat/event-prompt.js";
4
5
  import { isWithinActiveHours } from "../../heartbeat/active-hours.js";
5
6
  import { isHeartbeatContentEmpty } from "../../heartbeat/content-check.js";
6
- import { appendCronEventLines } from "../../heartbeat/event-prompt.js";
7
7
  import { createHeartbeatWake } from "../../heartbeat/wake.js";
8
8
  import { resolveHeartbeatMdPath } from "../workspace-heartbeat-path.js";
9
9
  import { readFile } from "fs/promises";
@@ -1,4 +1,5 @@
1
1
  import { getStoreConnectorInstallPlan, installStoreConnector, listStoreConnectors } from "../../../capabilities/store-connector.js";
2
+ import { ConfigPersistenceError, persistConfigMutation } from "../../../config/config-mutation.js";
2
3
  //#region src/gateway/hono/routes/capabilities.ts
3
4
  function errorMessage(error) {
4
5
  return error instanceof Error ? error.message : String(error);
@@ -50,14 +51,13 @@ function registerCapabilityRoutes(authenticated, deps) {
50
51
  authenticated.post("/api/capabilities/connectors/:name/install", strictRateLimitMiddleware, async (c) => {
51
52
  const body = await c.req.json().catch(() => ({}));
52
53
  const input = body && typeof body === "object" && !Array.isArray(body) ? body : {};
54
+ const config = service.currentConfig;
53
55
  try {
54
- const config = service.currentConfig;
55
- const { instance, plan } = await installStoreConnector(config, c.req.param("name"), input, optionalVersion(body));
56
- const saved = await service.saveConfig(config);
57
- if (!saved.saved) return c.json({
58
- ok: false,
59
- error: saved.error ?? "Failed to save connector configuration."
60
- }, 500);
56
+ const { instance, plan } = await persistConfigMutation({
57
+ config,
58
+ mutate: () => installStoreConnector(config, c.req.param("name"), input, optionalVersion(body)),
59
+ save: () => service.saveConfig(config)
60
+ });
61
61
  return c.json({
62
62
  ok: true,
63
63
  payload: {
@@ -69,7 +69,7 @@ function registerCapabilityRoutes(authenticated, deps) {
69
69
  return c.json({
70
70
  ok: false,
71
71
  error: errorMessage(error)
72
- }, 400);
72
+ }, error instanceof ConfigPersistenceError ? 500 : 400);
73
73
  }
74
74
  });
75
75
  }
@@ -13,8 +13,9 @@ import { listConnectorProviders } from "../../../connectors/providers.js";
13
13
  import { getConnectorDefinition, listConnectorCatalog } from "../../../connectors/catalog.js";
14
14
  import { listUnderstandingSourceGrants, revokeUnderstandingSourceGrant } from "../../../user-context/sources/repository.js";
15
15
  import { recordConnectorHealthUsage } from "../../../connectors/usage.js";
16
- import { getConnectorInstance, listConnectorInstances } from "../../../connectors/instances.js";
16
+ import { getConnectorInstance, getInstalledConnectorDefinition, listConnectorInstances } from "../../../connectors/instances.js";
17
17
  import { installConnector, installConnectorDefinition, uninstallConnector, updateConnectorConfig } from "../../../connectors/install.js";
18
+ import { getMcpOAuthManager } from "../../../agent/mcp/oauth/mcp-oauth-manager.js";
18
19
  import { startConnectorAuthorization } from "../../../connectors/auth-provider-registry.js";
19
20
  import { composioLogoResponse } from "../../../connectors/composio-logo.js";
20
21
  import { inspectManagedComposioStatus } from "../../../connectors/composio-managed-client.js";
@@ -25,6 +26,7 @@ import { ingestLocalFolderSource } from "../../../connectors/connected-source-in
25
26
  import { previewConnectorDefinition, testConnectorInstance } from "../../../connectors/health.js";
26
27
  import { setConnectorEnabled } from "../../../connectors/lifecycle.js";
27
28
  import { projectComposioConnectionStatus } from "../../../connectors/runtime-status.js";
29
+ import { ConfigPersistenceError, persistConfigMutation } from "../../../config/config-mutation.js";
28
30
  //#region src/gateway/hono/routes/connectors.ts
29
31
  init_agent_scope();
30
32
  function errorMessage(error) {
@@ -48,9 +50,39 @@ function registerConnectorRoutes(authenticated, deps) {
48
50
  }
49
51
  });
50
52
  });
51
- authenticated.get("/api/connectors/installed", (c) => {
53
+ authenticated.get("/api/connectors/installed", async (c) => {
52
54
  const config = service.currentConfig;
53
- const instances = listConnectorInstances(config);
55
+ const instances = await Promise.all(listConnectorInstances(config).map(async (instance) => {
56
+ if (!instance.enabled || instance.materialized.type !== "mcp") return instance;
57
+ const server = config.mcp?.servers?.[instance.materialized.serverId];
58
+ if (!server) return instance;
59
+ const oauth = await getMcpOAuthManager().status(instance.materialized.serverId, server);
60
+ if (!oauth.configured) return instance;
61
+ if (oauth.status === "connected") return {
62
+ ...instance,
63
+ authStatus: "connected",
64
+ connectionStatus: "connected"
65
+ };
66
+ if (oauth.status === "authorizing") return {
67
+ ...instance,
68
+ status: "connecting",
69
+ authStatus: "unknown",
70
+ connectionStatus: "connecting"
71
+ };
72
+ if (oauth.status === "error") return {
73
+ ...instance,
74
+ status: "failed",
75
+ authStatus: "unauthorized",
76
+ connectionStatus: "error",
77
+ lastError: oauth.session?.error ?? instance.lastError
78
+ };
79
+ return {
80
+ ...instance,
81
+ status: "unauthorized",
82
+ authStatus: "missing",
83
+ connectionStatus: "disconnected"
84
+ };
85
+ }));
54
86
  const connections = listConnectorConnections({ principalId: "local-owner" });
55
87
  return c.json({
56
88
  ok: true,
@@ -348,12 +380,11 @@ function registerConnectorRoutes(authenticated, deps) {
348
380
  }, 400);
349
381
  const config = service.currentConfig;
350
382
  try {
351
- setComposioToolkitScope(config, toolkit, scope);
352
- const saved = await service.saveConfig(config);
353
- if (!saved.saved) return c.json({
354
- ok: false,
355
- error: saved.error
356
- }, 500);
383
+ await persistConfigMutation({
384
+ config,
385
+ mutate: () => setComposioToolkitScope(config, toolkit, scope),
386
+ save: () => service.saveConfig(config)
387
+ });
357
388
  return c.json({
358
389
  ok: true,
359
390
  payload: {
@@ -365,7 +396,7 @@ function registerConnectorRoutes(authenticated, deps) {
365
396
  return c.json({
366
397
  ok: false,
367
398
  error: errorMessage(error)
368
- }, 400);
399
+ }, error instanceof ConfigPersistenceError ? 500 : 400);
369
400
  }
370
401
  });
371
402
  authenticated.post("/api/connectors/composio/tools/:slug/execute", strictRateLimitMiddleware, async (c) => {
@@ -573,14 +604,16 @@ function registerConnectorRoutes(authenticated, deps) {
573
604
  });
574
605
  });
575
606
  authenticated.post("/api/connectors/:id/auth/start", strictRateLimitMiddleware, async (c) => {
576
- const connectorId = c.req.param("id");
577
- const connector = getConnectorDefinition(connectorId);
607
+ const connectorOrInstanceId = c.req.param("id");
608
+ const config = service.currentConfig;
609
+ const instance = getConnectorInstance(config, connectorOrInstanceId);
610
+ const connector = instance ? getInstalledConnectorDefinition(config, instance.instanceId) : getConnectorDefinition(connectorOrInstanceId);
578
611
  if (!connector) return c.json({
579
612
  ok: false,
580
- error: `Unknown connector: ${connectorId}`
613
+ error: `Unknown connector: ${connectorOrInstanceId}`
581
614
  }, 404);
582
615
  try {
583
- const authorization = await startConnectorAuthorization(connector, service.currentConfig);
616
+ const authorization = await startConnectorAuthorization(connector, config, instance?.instanceId);
584
617
  return c.json({
585
618
  ok: true,
586
619
  payload: { authorization }
@@ -628,12 +661,11 @@ function registerConnectorRoutes(authenticated, deps) {
628
661
  const registryDefinition = body && typeof body === "object" && !Array.isArray(body) && body.definition && typeof body.definition === "object" && !Array.isArray(body.definition) ? body.definition : void 0;
629
662
  const config = service.currentConfig;
630
663
  try {
631
- const instance = registryDefinition?.id === connectorId ? await installConnectorDefinition(config, registryDefinition, input) : await installConnector(config, connectorId, input);
632
- const saved = await service.saveConfig(config);
633
- if (!saved.saved) return c.json({
634
- ok: false,
635
- error: saved.error
636
- }, 500);
664
+ const instance = await persistConfigMutation({
665
+ config,
666
+ mutate: () => registryDefinition?.id === connectorId ? installConnectorDefinition(config, registryDefinition, input) : installConnector(config, connectorId, input),
667
+ save: () => service.saveConfig(config)
668
+ });
637
669
  return c.json({
638
670
  ok: true,
639
671
  payload: { instance }
@@ -642,7 +674,7 @@ function registerConnectorRoutes(authenticated, deps) {
642
674
  return c.json({
643
675
  ok: false,
644
676
  error: errorMessage(error)
645
- }, 400);
677
+ }, error instanceof ConfigPersistenceError ? 500 : 400);
646
678
  }
647
679
  });
648
680
  authenticated.post("/api/connectors/:id/test", async (c) => {
@@ -659,12 +691,11 @@ function registerConnectorRoutes(authenticated, deps) {
659
691
  }, 400);
660
692
  try {
661
693
  const result = await testConnectorInstance(config, instance.materialized.serverId);
662
- recordConnectorHealthUsage(config, instance.instanceId, result);
663
- const saved = await service.saveConfig(config);
664
- if (!saved.saved) return c.json({
665
- ok: false,
666
- error: saved.error
667
- }, 500);
694
+ await persistConfigMutation({
695
+ config,
696
+ mutate: () => recordConnectorHealthUsage(config, instance.instanceId, result),
697
+ save: () => service.saveConfig(config)
698
+ });
668
699
  return c.json({
669
700
  ok: true,
670
701
  payload: result
@@ -701,12 +732,11 @@ function registerConnectorRoutes(authenticated, deps) {
701
732
  const input = body && typeof body === "object" && !Array.isArray(body) ? body : {};
702
733
  const config = service.currentConfig;
703
734
  try {
704
- const instance = updateConnectorConfig(config, instanceId, input);
705
- const saved = await service.saveConfig(config);
706
- if (!saved.saved) return c.json({
707
- ok: false,
708
- error: saved.error
709
- }, 500);
735
+ const instance = await persistConfigMutation({
736
+ config,
737
+ mutate: () => updateConnectorConfig(config, instanceId, input),
738
+ save: () => service.saveConfig(config)
739
+ });
710
740
  return c.json({
711
741
  ok: true,
712
742
  payload: { instance }
@@ -715,19 +745,18 @@ function registerConnectorRoutes(authenticated, deps) {
715
745
  return c.json({
716
746
  ok: false,
717
747
  error: errorMessage(error)
718
- }, 400);
748
+ }, error instanceof ConfigPersistenceError ? 500 : 400);
719
749
  }
720
750
  });
721
751
  authenticated.post("/api/connectors/:id/enable", strictRateLimitMiddleware, async (c) => {
722
752
  const instanceId = c.req.param("id");
723
753
  const config = service.currentConfig;
724
754
  try {
725
- const instance = setConnectorEnabled(config, instanceId, true);
726
- const saved = await service.saveConfig(config);
727
- if (!saved.saved) return c.json({
728
- ok: false,
729
- error: saved.error
730
- }, 500);
755
+ const instance = await persistConfigMutation({
756
+ config,
757
+ mutate: () => setConnectorEnabled(config, instanceId, true),
758
+ save: () => service.saveConfig(config)
759
+ });
731
760
  return c.json({
732
761
  ok: true,
733
762
  payload: { instance }
@@ -736,19 +765,18 @@ function registerConnectorRoutes(authenticated, deps) {
736
765
  return c.json({
737
766
  ok: false,
738
767
  error: errorMessage(error)
739
- }, 400);
768
+ }, error instanceof ConfigPersistenceError ? 500 : 400);
740
769
  }
741
770
  });
742
771
  authenticated.post("/api/connectors/:id/disable", strictRateLimitMiddleware, async (c) => {
743
772
  const instanceId = c.req.param("id");
744
773
  const config = service.currentConfig;
745
774
  try {
746
- const instance = setConnectorEnabled(config, instanceId, false);
747
- const saved = await service.saveConfig(config);
748
- if (!saved.saved) return c.json({
749
- ok: false,
750
- error: saved.error
751
- }, 500);
775
+ const instance = await persistConfigMutation({
776
+ config,
777
+ mutate: () => setConnectorEnabled(config, instanceId, false),
778
+ save: () => service.saveConfig(config)
779
+ });
752
780
  return c.json({
753
781
  ok: true,
754
782
  payload: { instance }
@@ -757,19 +785,23 @@ function registerConnectorRoutes(authenticated, deps) {
757
785
  return c.json({
758
786
  ok: false,
759
787
  error: errorMessage(error)
760
- }, 400);
788
+ }, error instanceof ConfigPersistenceError ? 500 : 400);
761
789
  }
762
790
  });
763
791
  authenticated.delete("/api/connectors/:id", strictRateLimitMiddleware, async (c) => {
764
792
  const instanceId = c.req.param("id");
765
793
  const config = service.currentConfig;
766
794
  try {
767
- const instance = uninstallConnector(config, instanceId);
768
- const saved = await service.saveConfig(config);
769
- if (!saved.saved) return c.json({
770
- ok: false,
771
- error: saved.error
772
- }, 500);
795
+ const definition = getInstalledConnectorDefinition(config, instanceId);
796
+ const installed = getConnectorInstance(config, instanceId);
797
+ const serverId = installed?.materialized.type === "mcp" ? installed.materialized.serverId : void 0;
798
+ const rawServer = serverId ? config.mcp?.servers?.[serverId] : void 0;
799
+ if (definition?.auth.mode === "oauth" && definition.runtime.type === "mcp" && rawServer) await getMcpOAuthManager().disconnect(serverId, rawServer);
800
+ const instance = await persistConfigMutation({
801
+ config,
802
+ mutate: () => uninstallConnector(config, instanceId),
803
+ save: () => service.saveConfig(config)
804
+ });
773
805
  return c.json({
774
806
  ok: true,
775
807
  payload: { instance }
@@ -778,7 +810,7 @@ function registerConnectorRoutes(authenticated, deps) {
778
810
  return c.json({
779
811
  ok: false,
780
812
  error: errorMessage(error)
781
- }, 400);
813
+ }, error instanceof ConfigPersistenceError ? 500 : 400);
782
814
  }
783
815
  });
784
816
  }
@@ -2,21 +2,31 @@ import { canonicalizeConfiguredMcpServer, normalizeConfiguredMcpServers } from "
2
2
  import { loadMergedBundleMcpConfig } from "../../../agent/mcp/bundle-mcp-config.js";
3
3
  import { isManagedConnectorServer } from "../../../connectors/materialize.js";
4
4
  import { getWorkspacePath } from "../../../config/workspace-path-helpers.js";
5
+ import { getMcpOAuthManager } from "../../../agent/mcp/oauth/mcp-oauth-manager.js";
5
6
  import { listBundleMcpServerCapabilitiesForGateway, listBundleMcpServerToolsForGateway } from "../../../agent/mcp/bundle-mcp-gateway.js";
6
7
  //#region src/gateway/hono/routes/mcp.ts
7
8
  function registerMcpRoutes(authenticated, deps) {
8
- authenticated.get("/api/mcp/servers", (c) => {
9
+ const resolveServer = (id) => {
10
+ const cfg = deps.service.currentConfig;
11
+ return loadMergedBundleMcpConfig({
12
+ workspaceDir: getWorkspacePath(cfg) || "./workspace",
13
+ cfg
14
+ }).config.mcpServers[id];
15
+ };
16
+ authenticated.get("/api/mcp/servers", async (c) => {
9
17
  const cfg = deps.service.currentConfig;
10
18
  const merged = loadMergedBundleMcpConfig({
11
19
  workspaceDir: getWorkspacePath(cfg) || "./workspace",
12
20
  cfg
13
21
  });
14
22
  const configured = normalizeConfiguredMcpServers(cfg.mcp?.servers);
15
- const servers = Object.entries(configured).map(([id, server]) => ({
23
+ const oauthManager = getMcpOAuthManager();
24
+ const servers = await Promise.all(Object.entries(configured).map(async ([id, server]) => ({
16
25
  id,
17
26
  managed: isManagedConnectorServer(server),
18
- connectorId: isManagedConnectorServer(server) ? server.xopcConnector.connectorId : void 0
19
- }));
27
+ connectorId: isManagedConnectorServer(server) ? server.xopcConnector.connectorId : void 0,
28
+ oauth: await oauthManager.status(id, server)
29
+ })));
20
30
  return c.json({
21
31
  ok: true,
22
32
  payload: {
@@ -26,6 +36,62 @@ function registerMcpRoutes(authenticated, deps) {
26
36
  }
27
37
  });
28
38
  });
39
+ authenticated.get("/api/mcp/servers/:id/oauth", async (c) => {
40
+ const id = c.req.param("id");
41
+ const server = resolveServer(id);
42
+ if (!server) return c.json({
43
+ ok: false,
44
+ error: `Unknown MCP server: ${id}`
45
+ }, 404);
46
+ return c.json({
47
+ ok: true,
48
+ payload: await getMcpOAuthManager().status(id, server)
49
+ });
50
+ });
51
+ authenticated.post("/api/mcp/servers/:id/oauth/start", deps.strictRateLimitMiddleware, async (c) => {
52
+ const id = c.req.param("id");
53
+ const server = resolveServer(id);
54
+ if (!server) return c.json({
55
+ ok: false,
56
+ error: `Unknown MCP server: ${id}`
57
+ }, 404);
58
+ try {
59
+ const payload = await getMcpOAuthManager().start({
60
+ serverId: id,
61
+ rawServer: server,
62
+ cfg: deps.service.currentConfig
63
+ });
64
+ return c.json({
65
+ ok: true,
66
+ payload
67
+ });
68
+ } catch (err) {
69
+ return c.json({
70
+ ok: false,
71
+ error: err instanceof Error ? err.message : String(err)
72
+ }, 400);
73
+ }
74
+ });
75
+ authenticated.delete("/api/mcp/servers/:id/oauth", deps.strictRateLimitMiddleware, async (c) => {
76
+ const id = c.req.param("id");
77
+ const server = resolveServer(id);
78
+ if (!server) return c.json({
79
+ ok: false,
80
+ error: `Unknown MCP server: ${id}`
81
+ }, 404);
82
+ try {
83
+ const payload = await getMcpOAuthManager().disconnect(id, server);
84
+ return c.json({
85
+ ok: true,
86
+ payload
87
+ });
88
+ } catch (err) {
89
+ return c.json({
90
+ ok: false,
91
+ error: err instanceof Error ? err.message : String(err)
92
+ }, 400);
93
+ }
94
+ });
29
95
  authenticated.get("/api/mcp/servers/:id/tools", async (c) => {
30
96
  const id = c.req.param("id");
31
97
  const cfg = deps.service.currentConfig;
@@ -79,11 +145,17 @@ function registerMcpRoutes(authenticated, deps) {
79
145
  workspaceDir,
80
146
  cfg
81
147
  }).config.mcpServers;
82
- if (!(inlineServer ?? servers[id] ?? mergedServers[id])) return c.json({
148
+ const knownServer = inlineServer ?? servers[id] ?? mergedServers[id];
149
+ if (!knownServer) return c.json({
83
150
  ok: false,
84
151
  error: `Unknown MCP server: ${id}`
85
152
  }, 404);
86
153
  try {
154
+ const oauth = await getMcpOAuthManager().status(id, knownServer);
155
+ if (oauth.configured && oauth.status !== "connected") return c.json({
156
+ ok: false,
157
+ error: oauth.session?.error ?? "MCP server authorization is required"
158
+ }, 409);
87
159
  const capabilities = await listBundleMcpServerCapabilitiesForGateway({
88
160
  workspaceDir,
89
161
  cfg: inlineServer ? {
@@ -14,8 +14,8 @@ import "../../../voice/stt/index.js";
14
14
  import { listSttProvidersForApi } from "../../../voice/stt/list-providers.js";
15
15
  import "../../../voice/tts/index.js";
16
16
  import { createGatewayRouteLogger } from "../lib/route-logger.js";
17
- import { listLocalVoiceModelStatuses, removeLocalVoiceModel, startLocalVoiceModelInstall } from "../../../voice/local/model-manager.js";
18
17
  import { listTtsProvidersForApi } from "../../../voice/tts/list-providers.js";
18
+ import { listLocalVoiceModelStatuses, removeLocalVoiceModel, startLocalVoiceModelInstall } from "../../../voice/local/model-manager.js";
19
19
  //#region src/gateway/hono/routes/voice.ts
20
20
  init_providers();
21
21
  const log = createGatewayRouteLogger("Voice");