| @@ -1260,6 +1260,100 @@ const toMcpConfigEntry = ( · 1259 unmodified lines |
| 1260 | return entry; | 1260 | return entry; |
| 1261 | }; | 1261 | }; |
| 1262 | | 1262 | |
| | 1263 | const refreshSourceInternal = ( |
| | 1264 | ctx: PluginCtx<McpBindingStore>, |
| | 1265 | sourceId: string, |
| | 1266 | scope: string, |
| | 1267 | allowStdio: boolean, |
| | 1268 | ) => |
| | 1269 | Effect.gen(function* () { |
| | 1270 | const sd = yield* ctx.storage.getSourceConfig(sourceId, scope).pipe( |
| | 1271 | Effect.withSpan("mcp.plugin.load_source_config", { |
| | 1272 | attributes: { "mcp.source.namespace": sourceId }, |
| | 1273 | }), |
| | 1274 | ); |
| | 1275 | if (!sd) { |
| | 1276 | return yield* new McpConnectionError({ |
| | 1277 | transport: "remote", |
| | 1278 | message: `No stored config for MCP source "${sourceId}"`, |
| | 1279 | }); |
| | 1280 | } |
| | 1281 | |
| | 1282 | const ci = yield* resolveConnectorInput(sourceId, scope, sd, ctx, allowStdio).pipe( |
| | 1283 | Effect.catchTag("McpAuthRequiredError", ({ message }) => |
| | 1284 | Effect.fail(new McpConnectionError({ transport: sd.transport, message })), |
| | 1285 | ), |
| | 1286 | Effect.withSpan("mcp.plugin.resolve_connector", { |
| | 1287 | attributes: { |
| | 1288 | "mcp.source.namespace": sourceId, |
| | 1289 | "mcp.source.transport": sd.transport, |
| | 1290 | }, |
| | 1291 | }), |
| | 1292 | ); |
| | 1293 | const manifest = yield* discoverTools(createMcpConnector(ci)).pipe( |
| | 1294 | Effect.mapError( |
| | 1295 | ({ message }) => |
| | 1296 | new McpToolDiscoveryError({ |
| | 1297 | stage: "list_tools", |
| | 1298 | message: `MCP refresh failed: ${message}`, |
| | 1299 | }), |
| | 1300 | ), |
| | 1301 | Effect.withSpan("mcp.plugin.discover_tools", { |
| | 1302 | attributes: { "mcp.source.namespace": sourceId }, |
| | 1303 | }), |
| | 1304 | ); |
| | 1305 | |
| | 1306 | const existing = yield* ctx.storage.getSource(sourceId, scope); |
| | 1307 | const sourceName = manifest.server?.name ?? existing?.name ?? sourceId; |
| | 1308 | |
| | 1309 | yield* ctx |
| | 1310 | .transaction( |
| | 1311 | Effect.gen(function* () { |
| | 1312 | yield* ctx.storage.removeBindingsByNamespace(sourceId, scope); |
| | 1313 | yield* ctx.core.sources.unregister({ id: sourceId, targetScope: scope }); |
| | 1314 | |
| | 1315 | yield* ctx.storage.putBindings( |
| | 1316 | sourceId, |
| | 1317 | scope, |
| | 1318 | manifest.tools.map((e) => ({ |
| | 1319 | toolId: `${sourceId}.${e.toolId}`, |
| | 1320 | binding: toBinding(e), |
| | 1321 | })), |
| | 1322 | ); |
| | 1323 | yield* ctx.core.sources.register({ |
| | 1324 | id: sourceId, |
| | 1325 | scope, |
| | 1326 | kind: "mcp", |
| | 1327 | name: sourceName, |
| | 1328 | url: sd.transport === "remote" ? sd.endpoint : undefined, |
| | 1329 | canRemove: true, |
| | 1330 | canRefresh: true, |
| | 1331 | canEdit: sd.transport === "remote", |
| | 1332 | tools: manifest.tools.map((e) => ({ |
| | 1333 | name: e.toolId, |
| | 1334 | description: e.description ?? `MCP tool: ${e.toolName}`, |
| | 1335 | inputSchema: e.inputSchema, |
| | 1336 | outputSchema: mcpCallToolResultOutputSchema(e.outputSchema), |
| | 1337 | })), |
| | 1338 | }); |
| | 1339 | }), |
| | 1340 | ) |
| | 1341 | .pipe( |
| | 1342 | Effect.withSpan("mcp.plugin.persist_source", { |
| | 1343 | attributes: { |
| | 1344 | "mcp.source.namespace": sourceId, |
| | 1345 | "mcp.source.tool_count": manifest.tools.length, |
| | 1346 | }, |
| | 1347 | }), |
| | 1348 | ); |
| | 1349 | |
| | 1350 | return { toolCount: manifest.tools.length }; |
| | 1351 | }).pipe( |
| | 1352 | Effect.withSpan("mcp.plugin.refresh_source", { |
| | 1353 | attributes: { "mcp.source.namespace": sourceId }, |
| | 1354 | }), |
| | 1355 | ); |
| | 1356 | |
| 1263 | export const mcpPlugin = definePlugin((options?: McpPluginOptions) => { | 1357 | export const mcpPlugin = definePlugin((options?: McpPluginOptions) => { |
| 1264 | const allowStdio = options?.dangerouslyAllowStdioMCP ?? false; | 1358 | const allowStdio = options?.dangerouslyAllowStdioMCP ?? false; |
| 1265 | // Per-plugin-instance runtime holder. Captured by closures in | 1359 | // Per-plugin-instance runtime holder. Captured by closures in |
| @@ -1695,93 +1789,7 @@ export const mcpPlugin = definePlugin((options?: McpPluginOptions) => { · 429 unmodified lines |
| 1695 | ); | 1789 | ); |
| 1696 | | 1790 | |
| 1697 | const refreshSource = (namespace: string, scope: string) => | 1791 | const refreshSource = (namespace: string, scope: string) => |
| 1698 | Effect.gen(function* () { | 1792 | refreshSourceInternal(ctx, namespace, scope, allowStdio); |
| 1699 | const sd = yield* ctx.storage.getSourceConfig(namespace, scope).pipe( | | |
| 1700 | Effect.withSpan("mcp.plugin.load_source_config", { | | |
| 1701 | attributes: { "mcp.source.namespace": namespace }, | | |
| 1702 | }), | | |
| 1703 | ); | | |
| 1704 | if (!sd) { | | |
| 1705 | return yield* new McpConnectionError({ | | |
| 1706 | transport: "remote", | | |
| 1707 | message: `No stored config for MCP source "${namespace}"`, | | |
| 1708 | }); | | |
| 1709 | } | | |
| 1710 | | | |
| 1711 | const ci = yield* resolveConnectorInput(namespace, scope, sd, ctx, allowStdio).pipe( | | |
| 1712 | Effect.catchTag("McpAuthRequiredError", ({ message }) => | | |
| 1713 | Effect.fail(new McpConnectionError({ transport: sd.transport, message })), | | |
| 1714 | ), | | |
| 1715 | Effect.withSpan("mcp.plugin.resolve_connector", { | | |
| 1716 | attributes: { | | |
| 1717 | "mcp.source.namespace": namespace, | | |
| 1718 | "mcp.source.transport": sd.transport, | | |
| 1719 | }, | | |
| 1720 | }), | | |
| 1721 | ); | | |
| 1722 | const manifest = yield* discoverTools(createMcpConnector(ci)).pipe( | | |
| 1723 | Effect.mapError( | | |
| 1724 | ({ message }) => | | |
| 1725 | new McpToolDiscoveryError({ | | |
| 1726 | stage: "list_tools", | | |
| 1727 | message: `MCP refresh failed: ${message}`, | | |
| 1728 | }), | | |
| 1729 | ), | | |
| 1730 | Effect.withSpan("mcp.plugin.discover_tools", { | | |
| 1731 | attributes: { "mcp.source.namespace": namespace }, | | |
| 1732 | }), | | |
| 1733 | ); | | |
| 1734 | | | |
| 1735 | const existing = yield* ctx.storage.getSource(namespace, scope); | | |
| 1736 | const sourceName = manifest.server?.name ?? existing?.name ?? namespace; | | |
| 1737 | | | |
| 1738 | yield* ctx | | |
| 1739 | .transaction( | | |
| 1740 | Effect.gen(function* () { | | |
| 1741 | yield* ctx.storage.removeBindingsByNamespace(namespace, scope); | | |
| 1742 | yield* ctx.core.sources.unregister({ id: namespace, targetScope: scope }); | | |
| 1743 | | | |
| 1744 | yield* ctx.storage.putBindings( | | |
| 1745 | namespace, | | |
| 1746 | scope, | | |
| 1747 | manifest.tools.map((e) => ({ | | |
| 1748 | toolId: `${namespace}.${e.toolId}`, | | |
| 1749 | binding: toBinding(e), | | |
| 1750 | })), | | |
| 1751 | ); | | |
| 1752 | yield* ctx.core.sources.register({ | | |
| 1753 | id: namespace, | | |
| 1754 | scope, | | |
| 1755 | kind: "mcp", | | |
| 1756 | name: sourceName, | | |
| 1757 | url: sd.transport === "remote" ? sd.endpoint : undefined, | | |
| 1758 | canRemove: true, | | |
| 1759 | canRefresh: true, | | |
| 1760 | canEdit: sd.transport === "remote", | | |
| 1761 | tools: manifest.tools.map((e) => ({ | | |
| 1762 | name: e.toolId, | | |
| 1763 | description: e.description ?? `MCP tool: ${e.toolName}`, | | |
| 1764 | inputSchema: e.inputSchema, | | |
| 1765 | outputSchema: mcpCallToolResultOutputSchema(e.outputSchema), | | |
| 1766 | })), | | |
| 1767 | }); | | |
| 1768 | }), | | |
| 1769 | ) | | |
| 1770 | .pipe( | | |
| 1771 | Effect.withSpan("mcp.plugin.persist_source", { | | |
| 1772 | attributes: { | | |
| 1773 | "mcp.source.namespace": namespace, | | |
| 1774 | "mcp.source.tool_count": manifest.tools.length, | | |
| 1775 | }, | | |
| 1776 | }), | | |
| 1777 | ); | | |
| 1778 | | | |
| 1779 | return { toolCount: manifest.tools.length }; | | |
| 1780 | }).pipe( | | |
| 1781 | Effect.withSpan("mcp.plugin.refresh_source", { | | |
| 1782 | attributes: { "mcp.source.namespace": namespace }, | | |
| 1783 | }), | | |
| 1784 | ); | | |
| 1785 | | 1793 | |
| 1786 | const getSource = (namespace: string, scope: string) => | 1794 | const getSource = (namespace: string, scope: string) => |
| 1787 | ctx.storage.getSource(namespace, scope).pipe( | 1795 | ctx.storage.getSource(namespace, scope).pipe( |
| @@ -2286,7 +2294,8 @@ export const mcpPlugin = definePlugin((options?: McpPluginOptions) => { · 498 unmodified lines |
| 2286 | | 2294 | |
| 2287 | usagesForConnection: () => Effect.succeed([]), | 2295 | usagesForConnection: () => Effect.succeed([]), |
| 2288 | | 2296 | |
| 2289 | refreshSource: () => Effect.void, | 2297 | refreshSource: ({ ctx, sourceId, scope }) => |
| | 2298 | refreshSourceInternal(ctx, sourceId, scope, allowStdio).pipe(Effect.asVoid), |
| 2290 | | 2299 | |
| 2291 | // Connection refresh for oauth2-minted sources is owned by the | 2300 | // Connection refresh for oauth2-minted sources is owned by the |
| 2292 | // canonical `"oauth2"` ConnectionProvider that core registers via | 2301 | // canonical `"oauth2"` ConnectionProvider that core registers via |