Hot-Reload MCP tools - #3747
Hot-Reload MCP tools#3747aaronburtle wants to merge 26 commits into
Conversation
…registry-hot-reload # Conflicts: # src/Service.Tests/Mcp/McpMetadataHelperTests.cs
There was a problem hiding this comment.
Pull request overview
This PR adds robust hot-reload support for MCP tools by rebuilding and atomically publishing tool-registry snapshots whenever runtime configuration/metadata refreshes, keeping tools/list and tools/call aligned with the latest successfully applied generation across both HTTP and stdio transports.
Changes:
- Introduces an immutable, versioned MCP tool-registry snapshot model with ordered refresh tied into the hot-reload pipeline.
- Serializes initial runtime initialization and file-triggered hot reloads through a shared gate, and adds coordinated shutdown draining/cancellation.
- Propagates cancellation across metadata discovery and query execution paths, and adds stdio
notifications/tools/list_changed.
Reviewed changes
Copilot reviewed 58 out of 58 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| src/Service/Utilities/RuntimeInitializationHelper.cs | Centralizes serialized startup init path. |
| src/Service/Utilities/RuntimeConfigLoaderShutdownService.cs | Drains loader work on host shutdown. |
| src/Service/Utilities/McpStdioHelper.cs | Runs serialized init + loader drain. |
| src/Service/Telemetry/LogLevelInitializer.cs | Adds cancellation checks on reload. |
| src/Service/Startup.cs | Uses new init helper + shutdown service. |
| src/Service/Program.cs | Registers stdio tool-list notifier services. |
| src/Service.Tests/UnitTests/SqlQueryExecutorUnitTests.cs | Adds linked cancellation unit test. |
| src/Service.Tests/UnitTests/McpStdoutWriterTests.cs | Tests nonblocking dispose with blocked stdout. |
| src/Service.Tests/UnitTests/McpStdioToolListChangedNotifierTests.cs | Adds notifier behavior/unit coverage. |
| src/Service.Tests/UnitTests/McpStdioServerRunAsyncTests.cs | Verifies notifier initialization handshake behavior. |
| src/Service.Tests/UnitTests/McpStdioServerInitializeTests.cs | Ensures listChanged advertised only when supported. |
| src/Service.Tests/UnitTests/McpStdioHelperTests.cs | Ensures stdio init order: metadata then registry. |
| src/Service.Tests/UnitTests/McpServerConfigurationTests.cs | Verifies HTTP capabilities + snapshot discovery filtering. |
| src/Service.Tests/UnitTests/ConfigFileWatcherUnitTests.cs | Adds serialization/drain/shutdown tests for loader. |
| src/Service.Tests/Mcp/McpToolRegistryTests.cs | Updates tests for snapshot/ordering/semantic comparison. |
| src/Service.Tests/Mcp/McpStdioToolRegistryHotReloadIntegrationTests.cs | Adds stdio hot-reload notification integration test. |
| src/Service.Tests/Mcp/McpMetadataHelperTests.cs | Updates metadata helper tests + new overload coverage. |
| src/Service.Tests/Mcp/McpInitialHotReloadSerializationTests.cs | Tests startup vs reload serialization correctness. |
| src/Service.Tests/Mcp/McpHttpToolRegistryHotReloadIntegrationTests.cs | Adds HTTP hot-reload integration coverage. |
| src/Service.Tests/Mcp/DynamicCustomToolTests.cs | Updates tool metadata init to explicit factory/config. |
| src/Service.Tests/Mcp/DynamicCustomToolMsSqlIntegrationTests.cs | Updates integration tests for new init signature. |
| src/Core/Services/OpenAPI/OpenApiDocumentor.cs | Adds cancellation checks on config change. |
| src/Core/Services/MetadataProviders/SqlMetadataProvider.cs | Adds cancellation-aware metadata discovery APIs. |
| src/Core/Services/MetadataProviders/MySqlMetadataProvider.cs | Propagates cancellation to schema discovery. |
| src/Core/Services/MetadataProviders/MsSqlMetadataProvider.cs | Propagates cancellation to metadata + autoentities. |
| src/Core/Services/MetadataProviders/MetadataProviderFactory.cs | Runs init with cancellation during reload. |
| src/Core/Services/MetadataProviders/ISqlMetadataProvider.cs | Adds default cancellation-aware interface method. |
| src/Core/Services/MetadataProviders/IMetadataProviderFactory.cs | Adds default cancellation-aware interface method. |
| src/Core/Services/MetadataProviders/CosmosSqlMetadataProvider.cs | Adds cancellation-aware no-op init. |
| src/Core/Services/GraphQLSchemaCreator.cs | Adds cancellation checks on config change. |
| src/Core/Resolvers/QueryExecutor.cs | Adds linked cancellation + token-aware overloads. |
| src/Core/Resolvers/PostgreSqlExecutor.cs | Adds cancellation to MI token acquisition. |
| src/Core/Resolvers/MySqlQueryExecutor.cs | Adds cancellation to MI token acquisition. |
| src/Core/Resolvers/MsSqlQueryExecutor.cs | Adds cancellation to OBO/MI token acquisition. |
| src/Core/Resolvers/IQueryExecutor.cs | Adds default cancellation-aware interface methods. |
| src/Core/Resolvers/Factories/QueryManagerFactory.cs | Adds cancellation checks on config change. |
| src/Core/Resolvers/Factories/QueryEngineFactory.cs | Adds cancellation checks on config change. |
| src/Core/Resolvers/Factories/MutationEngineFactory.cs | Adds cancellation checks on config change. |
| src/Core/Authorization/AuthorizationResolver.cs | Adds cancellation checks on config change. |
| src/Config/RuntimeConfigLoader.cs | Adds cancellation-aware ordered event signaling. |
| src/Config/HotReloadEventHandler.cs | Adds MCP registry event + docs. |
| src/Config/HotReloadEventArgs.cs | Adds CancellationToken to hot-reload args. |
| src/Config/FileSystemRuntimeConfigLoader.cs | Serializes reload + adds StopAsync drain/cancel. |
| src/Config/DabConfigEvents.cs | Adds MCP tool registry ordered event name. |
| src/Config/ConfigFileWatcher.cs | Adds stoppable watcher abstraction + lifecycle fixes. |
| src/Azure.DataApiBuilder.Mcp/Utils/McpMetadataHelper.cs | Adds overload accepting explicit metadata factory. |
| src/Azure.DataApiBuilder.Mcp/Core/McpToolRegistryRefreshService.cs | Adds refresh service + notifier integration. |
| src/Azure.DataApiBuilder.Mcp/Core/McpToolRegistryInitializer.cs | Removes startup-only hosted initializer. |
| src/Azure.DataApiBuilder.Mcp/Core/McpToolRegistry.cs | Implements immutable snapshot registry + canonical compare. |
| src/Azure.DataApiBuilder.Mcp/Core/McpStdoutWriter.cs | Makes dispose nonblocking under blocked stdout. |
| src/Azure.DataApiBuilder.Mcp/Core/McpStdioToolListChangedNotifier.cs | Implements stdio list-changed notifications. |
| src/Azure.DataApiBuilder.Mcp/Core/McpStdioServer.cs | Adds handshake gating + uses advertised snapshot. |
| src/Azure.DataApiBuilder.Mcp/Core/McpServiceCollectionExtensions.cs | Registers refresh service + removes config tool DI. |
| src/Azure.DataApiBuilder.Mcp/Core/McpServerConfiguration.cs | Uses advertised snapshot; disables HTTP listChanged. |
| src/Azure.DataApiBuilder.Mcp/Core/DynamicCustomTool.cs | Regenerates per snapshot + explicit metadata init. |
| src/Azure.DataApiBuilder.Mcp/Core/CustomMcpToolFactory.cs | Builds dynamic tools per generation (fail-fast). |
…registry-hot-reload # Conflicts: # src/Service.Tests/UnitTests/McpStdioServerRunAsyncTests.cs
…registry-hot-reload # Conflicts: # src/Service.Tests/UnitTests/McpStdioHelperTests.cs # src/Service/Utilities/McpStdioHelper.cs
| @@ -0,0 +1,1141 @@ | |||
| # Design Document: MCP Tool Registry Hot-Reload | |||
There was a problem hiding this comment.
please git ignore this file from checkin
| } | ||
| } | ||
|
|
||
| private static bool TryWriteOrderInsensitiveJsonSchemaStringArray( |
There was a problem hiding this comment.
Once isWithinJsonSchema becomes true, it remains true inside default, const, and examples values. Consequently, changing an object-valued default from {"enum":["a","b"]} to {"enum":["b","a"]} produces identical canonical JSON. Here, enum is an ordinary data property, so array order matters. The registry publishes the changed metadata but suppresses notifications/tools/list_changed, leaving clients with stale metadata.
Suggestion: Distinguish schema nodes from instance-data nodes, or conservatively preserve array order. Add tests for reordered arrays inside object-valued default, const, and examples, including properties named enum, type, and required.
Why make this change?
Closes #3066
DAB previously constructed the MCP tool registry only once during startup. Runtime configuration hot-reload could therefore leave MCP discovery stale when custom tools were added, removed, renamed, enabled, disabled, or given updated descriptions or stored-procedure parameters.
This change keeps MCP discovery and execution aligned with the latest successfully refreshed configuration and metadata generation.
What is this change?
tools/listandtools/callobserve either the complete previous generation or the complete new generation—never a partially rebuilt registry.IMcpToolimplementations remain available across generations.notifications/tools/list_changedsupport.HostOptions.ShutdownTimeout; synchronous disposal remains nonblocking.HttpContext.RequestAborted, so either source can stop database work.How was this tested?
notifications/tools/list_changednotification.HttpContext.RequestAbortedboth cancel the same operation.Sample Request(s)
No REST or GraphQL wire shape changes are introduced.
Start DAB in MCP stdio mode using a development-mode configuration:
Initialize the MCP session and request the initial tool list:
{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"sample-client","version":"1.0.0"}}} {"jsonrpc":"2.0","method":"notifications/initialized","params":{}} {"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}While DAB remains running, enable a stored-procedure entity as a custom MCP tool:
{ "entities": { "GetBook": { "source": { "object": "dbo.GetBook", "type": "stored-procedure" }, "mcp": { "custom-tool": true } } } }After the configuration reload succeeds, an initialized stdio client receives:
{"jsonrpc":"2.0","method":"notifications/tools/list_changed","params":{}}Request the tool list again to retrieve the newly published snapshot:
{"jsonrpc":"2.0","id":3,"method":"tools/list","params":{}}