From 924bdaf6104d5005757b811856927545ec3d6312 Mon Sep 17 00:00:00 2001 From: Aditya kumar singh <143548997+Adityakk9031@users.noreply.github.com> Date: Sat, 29 Aug 2026 15:38:12 +0530 Subject: [PATCH 1/2] feat(mcp): add agent-facing updateServer and configureAuth tools --- packages/plugins/mcp/src/api/handlers.test.ts | 1 + packages/plugins/mcp/src/sdk/index.ts | 1 + packages/plugins/mcp/src/sdk/plugin.test.ts | 70 +++++++ packages/plugins/mcp/src/sdk/plugin.ts | 184 ++++++++++++++++++ 4 files changed, 256 insertions(+) diff --git a/packages/plugins/mcp/src/api/handlers.test.ts b/packages/plugins/mcp/src/api/handlers.test.ts index 6d9048878..cecc33e5d 100644 --- a/packages/plugins/mcp/src/api/handlers.test.ts +++ b/packages/plugins/mcp/src/api/handlers.test.ts @@ -25,6 +25,7 @@ const failingExtension: McpPluginExtension = { // oxlint-disable-next-line executor/no-error-constructor -- boundary: test injects a defect to verify opaque handler error responses probeEndpoint: () => Effect.die(new Error("Not implemented")), addServer: () => unused, + updateServer: () => unused, removeServer: () => unused, reconcileStdioConnections: () => unused, getServer: () => Effect.succeed(null), diff --git a/packages/plugins/mcp/src/sdk/index.ts b/packages/plugins/mcp/src/sdk/index.ts index 9dbb9d3d0..456a0ad0f 100644 --- a/packages/plugins/mcp/src/sdk/index.ts +++ b/packages/plugins/mcp/src/sdk/index.ts @@ -4,6 +4,7 @@ export { type McpPluginExtension, type McpPluginOptions, type McpServerInput, + type McpUpdateServerInput, type McpRemoteServerInput, type McpStdioServerInput, type McpProbeResult, diff --git a/packages/plugins/mcp/src/sdk/plugin.test.ts b/packages/plugins/mcp/src/sdk/plugin.test.ts index fc8ce7f12..fe4efbe56 100644 --- a/packages/plugins/mcp/src/sdk/plugin.test.ts +++ b/packages/plugins/mcp/src/sdk/plugin.test.ts @@ -956,6 +956,76 @@ describe("mcpPlugin", () => { }), ); + it.effect("agent tool updateServer updates MCP server config and auth template", () => + Effect.gen(function* () { + const config = makeTestConfig({ plugins: [mcpPlugin()] as const }); + const executor = yield* createExecutor(config); + + yield* executor.execute(ToolAddress.make("executor.mcp.addServer"), { + name: "Initial MCP", + endpoint: "https://mcp1.example.com/mcp", + slug: "test_update_mcp", + }); + + const updated = yield* executor.execute(ToolAddress.make("executor.mcp.updateServer"), { + slug: "test_update_mcp", + name: "Updated MCP Display", + endpoint: "https://mcp2.example.com/mcp", + authenticationTemplate: [ + { + type: "apiKey", + headers: { Authorization: ["Bearer ", { type: "variable", name: "token" }] }, + }, + ], + }); + expect(updated).toMatchObject({ + ok: true, + data: { slug: "test_update_mcp" }, + }); + + const integration = yield* executor.integrations.get(IntegrationSlug.make("test_update_mcp")); + expect(integration?.name).toBe("Updated MCP Display"); + expect(integration?.authMethods.map((m) => m.kind)).toEqual(["apikey"]); + + yield* executor.close(); + yield* Effect.promise(() => config.testDb.close()); + }), + ); + + it.effect("agent tool configureAuth updates authentication templates on remote server", () => + Effect.gen(function* () { + const config = makeTestConfig({ plugins: [mcpPlugin()] as const }); + const executor = yield* createExecutor(config); + + yield* executor.execute(ToolAddress.make("executor.mcp.addServer"), { + name: "Auth MCP", + endpoint: "https://auth.example.com/mcp", + slug: "test_auth_mcp", + }); + + const configured = yield* executor.execute(ToolAddress.make("executor.mcp.configureAuth"), { + slug: "test_auth_mcp", + authenticationTemplate: [ + { + type: "apiKey", + headers: { "X-API-Key": [{ type: "variable", name: "apiKey" }] }, + }, + ], + mode: "replace", + }); + expect(configured).toMatchObject({ + ok: true, + data: { slug: "test_auth_mcp" }, + }); + + const integration = yield* executor.integrations.get(IntegrationSlug.make("test_auth_mcp")); + expect(integration?.authMethods.map((m) => m.kind)).toEqual(["apikey"]); + + yield* executor.close(); + yield* Effect.promise(() => config.testDb.close()); + }), + ); + for (const status of [401, 403] as const) { it.effect(`returns an auth tool failure when tools/call responds HTTP ${status}`, () => Effect.scoped( diff --git a/packages/plugins/mcp/src/sdk/plugin.ts b/packages/plugins/mcp/src/sdk/plugin.ts index ec8b92a77..1f3ba06d3 100644 --- a/packages/plugins/mcp/src/sdk/plugin.ts +++ b/packages/plugins/mcp/src/sdk/plugin.ts @@ -253,6 +253,54 @@ const McpAddServerOutputSchema = Schema.Struct({ slug: Schema.String, }); +const McpUpdateRemoteServerInputSchema = Schema.Struct({ + slug: Schema.String, + transport: Schema.optional(Schema.Literal("remote")), + name: Schema.optional(Schema.String), + family: Schema.optional(Schema.String), + description: Schema.optional(Schema.String), + endpoint: Schema.optional(Schema.String), + remoteTransport: Schema.optional(McpRemoteTransport), + headers: Schema.optional(Schema.Record(Schema.String, Schema.String)), + queryParams: Schema.optional(Schema.Record(Schema.String, Schema.String)), + authenticationTemplate: Schema.optional(Schema.Array(McpAuthMethodInput)), + auth: Schema.optional(McpAuthShorthand), +}); + +const McpUpdateStdioServerInputSchema = Schema.Struct({ + slug: Schema.String, + transport: Schema.Literal("stdio"), + name: Schema.optional(Schema.String), + family: Schema.optional(Schema.String), + description: Schema.optional(Schema.String), + command: Schema.optional(Schema.String), + args: Schema.optional(Schema.Array(Schema.String)), + envVars: Schema.optional(Schema.Array(Schema.String)), + env: Schema.optional(Schema.Record(Schema.String, Schema.String)), + cwd: Schema.optional(Schema.String), + versionNegotiation: Schema.optional(McpStdioVersionNegotiation), +}); + +const McpUpdateServerInputSchema = Schema.Union([ + McpUpdateRemoteServerInputSchema, + McpUpdateStdioServerInputSchema, +]); + +const McpUpdateServerOutputSchema = Schema.Struct({ + slug: Schema.String, +}); + +const McpConfigureAuthToolInputSchema = Schema.Struct({ + slug: Schema.String, + authenticationTemplate: Schema.Array(McpAuthMethodInput), + mode: Schema.optional(Schema.Literals(["merge", "replace"])), +}); + +const McpConfigureAuthToolOutputSchema = Schema.Struct({ + slug: Schema.String, + authenticationTemplate: Schema.Array(Schema.Unknown), +}); + /** Input for the custom-method-create flow. `merge` (default) appends onto the * integration's existing `authenticationTemplate`; `replace` swaps the whole * declared set. Mirrors the OpenAPI/GraphQL `configureAuth` inputs. */ @@ -292,6 +340,7 @@ const McpProbeEndpointOutputSchema = Schema.Struct({ export type McpRemoteServerInput = typeof McpRemoteServerInputSchema.Type; export type McpStdioServerInput = typeof McpStdioServerInputSchema.Type; export type McpServerInput = typeof McpAddServerInputSchema.Type; +export type McpUpdateServerInput = typeof McpUpdateServerInputSchema.Type; export type McpProbeResult = typeof McpProbeEndpointOutputSchema.Type; export type McpProbeEndpointInput = typeof McpProbeEndpointInputSchema.Type; @@ -311,6 +360,14 @@ const schemaToStaticToolSchema = (schema: Schema.Decoder): StaticToo const McpAddServerInputStandardSchema = schemaToStaticToolSchema(McpAddServerInputSchema); const McpAddServerOutputStandardSchema = schemaToStaticToolSchema(McpAddServerOutputSchema); +const McpUpdateServerInputStandardSchema = schemaToStaticToolSchema(McpUpdateServerInputSchema); +const McpUpdateServerOutputStandardSchema = schemaToStaticToolSchema(McpUpdateServerOutputSchema); +const McpConfigureAuthToolInputStandardSchema = schemaToStaticToolSchema( + McpConfigureAuthToolInputSchema, +); +const McpConfigureAuthToolOutputStandardSchema = schemaToStaticToolSchema( + McpConfigureAuthToolOutputSchema, +); const McpProbeEndpointInputStandardSchema = schemaToStaticToolSchema(McpProbeEndpointInputSchema); const McpProbeEndpointOutputStandardSchema = schemaToStaticToolSchema(McpProbeEndpointOutputSchema); const McpGetServerInputStandardSchema = schemaToStaticToolSchema(McpGetServerInputSchema); @@ -1248,6 +1305,82 @@ export const mcpPlugin = definePlugin((options?: McpPluginOptions) => { }), ); + const updateServer = (input: McpUpdateServerInput) => + Effect.gen(function* () { + const slug = slugFrom(input.slug); + const record = yield* ctx.core.integrations.get(slug); + if (!record) { + return yield* new McpConnectionError({ + message: `MCP server not found: ${input.slug}`, + transport: "remote", + }); + } + const current = parseMcpIntegrationConfig(record.config); + if (!current) { + return yield* new McpConnectionError({ + message: `Invalid configuration for MCP server: ${input.slug}`, + transport: "remote", + }); + } + + let updatedConfig: McpIntegrationConfigType; + if (current.transport === "stdio") { + const stdioInput = input as typeof McpUpdateStdioServerInputSchema.Type; + let authenticationTemplate = current.authenticationTemplate; + if (stdioInput.envVars !== undefined) { + authenticationTemplate = + stdioInput.envVars.length > 0 + ? [{ slug: "stdio_env", kind: "stdio_env", vars: stdioInput.envVars }] + : [{ slug: "none", kind: "none" }]; + } + updatedConfig = { + transport: "stdio", + family: stdioInput.family ?? current.family, + command: stdioInput.command ?? current.command, + args: stdioInput.args ?? current.args, + cwd: stdioInput.cwd !== undefined ? stdioInput.cwd : current.cwd, + ...(stdioInput.versionNegotiation !== undefined + ? { versionNegotiation: stdioInput.versionNegotiation } + : current.versionNegotiation !== undefined + ? { versionNegotiation: current.versionNegotiation } + : {}), + ...(authenticationTemplate !== undefined ? { authenticationTemplate } : {}), + }; + } else { + const remoteInput = input as typeof McpUpdateRemoteServerInputSchema.Type; + let authenticationTemplate = current.authenticationTemplate; + if (remoteInput.authenticationTemplate !== undefined) { + authenticationTemplate = normalizeMcpAuthMethods( + remoteInput.authenticationTemplate, + ); + } else if (remoteInput.auth !== undefined) { + authenticationTemplate = [mcpAuthMethodFromShorthand(remoteInput.auth)]; + } + + updatedConfig = { + transport: "remote", + family: remoteInput.family ?? current.family, + endpoint: remoteInput.endpoint ?? current.endpoint, + remoteTransport: remoteInput.remoteTransport ?? current.remoteTransport, + headers: remoteInput.headers ?? current.headers, + queryParams: remoteInput.queryParams ?? current.queryParams, + authenticationTemplate, + }; + } + + yield* ctx.core.integrations.update(slug, { + ...(input.name !== undefined ? { name: input.name } : {}), + ...(input.description !== undefined ? { description: input.description } : {}), + config: updatedConfig, + }); + + return { slug: String(input.slug) }; + }).pipe( + Effect.withSpan("mcp.plugin.update_server", { + attributes: { "mcp.integration.slug": input.slug }, + }), + ); + /** Merge-append auth methods onto the integration's existing * `authenticationTemplate` (custom-method-create flow), mirroring the * OpenAPI/GraphQL `configureAuth`. Returns the merged array. A no-op @@ -1289,6 +1422,7 @@ export const mcpPlugin = definePlugin((options?: McpPluginOptions) => { return { probeEndpoint, addServer, + updateServer, removeServer, reconcileStdioConnections, getServer, @@ -1814,6 +1948,53 @@ export const mcpPlugin = definePlugin((options?: McpPluginOptions) => { ); }, }), + tool({ + name: "updateServer", + description: + "Update the configuration of an existing registered MCP server (transport settings, endpoint/command, headers, query params, or auth templates).", + annotations: { + requiresApproval: true, + approvalDescription: "Update an MCP server", + }, + inputSchema: McpUpdateServerInputStandardSchema, + outputSchema: McpUpdateServerOutputStandardSchema, + execute: (rawInput) => { + const input = rawInput as typeof McpUpdateServerInputSchema.Type; + return self.updateServer(input).pipe( + Effect.map(ToolResult.ok), + Effect.catchTag("McpConnectionError", ({ message, transport }) => + Effect.succeed(mcpToolFailure("mcp_connection_failed", message, { transport })), + ), + ); + }, + }), + tool({ + name: "configureAuth", + description: + "Configure or update the authentication templates on a registered remote MCP server. In 'merge' mode (default), new auth methods are appended to existing ones; in 'replace' mode, the entire authentication template is replaced.", + annotations: { + requiresApproval: true, + approvalDescription: "Configure MCP server authentication", + }, + inputSchema: McpConfigureAuthToolInputStandardSchema, + outputSchema: McpConfigureAuthToolOutputStandardSchema, + execute: (rawInput) => { + const input = rawInput as typeof McpConfigureAuthToolInputSchema.Type; + return self + .configureAuth(input.slug, { + authenticationTemplate: input.authenticationTemplate, + mode: input.mode ?? "merge", + }) + .pipe( + Effect.map((authenticationTemplate) => + ToolResult.ok({ + slug: input.slug, + authenticationTemplate, + }), + ), + ); + }, + }), ], }, ], @@ -1836,6 +2017,9 @@ export interface McpPluginExtension { { readonly slug: string }, McpExtensionFailure | IntegrationAlreadyExistsError >; + readonly updateServer: ( + input: McpUpdateServerInput, + ) => Effect.Effect<{ readonly slug: string }, McpExtensionFailure>; readonly removeServer: (slug: string) => Effect.Effect; /** Ensure every stdio integration has its default connection (migrating any * legacy inline env into the secret store). Idempotent; safe to run at boot. */ From 4f5a7b9cd146331bae316643d7e2daf9da8dd388 Mon Sep 17 00:00:00 2001 From: Aditya kumar singh <143548997+Adityakk9031@users.noreply.github.com> Date: Sat, 29 Aug 2026 15:57:30 +0530 Subject: [PATCH 2/2] style: format plugin.ts --- packages/plugins/mcp/src/sdk/plugin.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/plugins/mcp/src/sdk/plugin.ts b/packages/plugins/mcp/src/sdk/plugin.ts index 1f3ba06d3..dba57ab4c 100644 --- a/packages/plugins/mcp/src/sdk/plugin.ts +++ b/packages/plugins/mcp/src/sdk/plugin.ts @@ -1350,9 +1350,7 @@ export const mcpPlugin = definePlugin((options?: McpPluginOptions) => { const remoteInput = input as typeof McpUpdateRemoteServerInputSchema.Type; let authenticationTemplate = current.authenticationTemplate; if (remoteInput.authenticationTemplate !== undefined) { - authenticationTemplate = normalizeMcpAuthMethods( - remoteInput.authenticationTemplate, - ); + authenticationTemplate = normalizeMcpAuthMethods(remoteInput.authenticationTemplate); } else if (remoteInput.auth !== undefined) { authenticationTemplate = [mcpAuthMethodFromShorthand(remoteInput.auth)]; }