-
Notifications
You must be signed in to change notification settings - Fork 324
[MCP] Fix for inconsistent MCP enabled check #3303
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -50,7 +50,7 @@ private static bool TryGetMcpOptions(RuntimeConfigProvider runtimeConfigProvider | |
| return false; | ||
| } | ||
|
|
||
| mcpOptions = runtimeConfig?.Runtime?.Mcp; | ||
| mcpOptions = runtimeConfig?.Runtime?.Mcp ?? new McpRuntimeOptions(); | ||
| return mcpOptions != null; | ||
| } | ||
|
Comment on lines
+53
to
55
|
||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -410,7 +410,8 @@ public string GetRouteAfterPathBase(string route) | |
| // forward slash '/'. | ||
| configuredRestPathBase = configuredRestPathBase.Substring(1); | ||
|
|
||
| if (route.Equals(_runtimeConfigProvider.GetConfig().McpPath.Substring(1))) | ||
| if (route.Equals(_runtimeConfigProvider.GetConfig().McpPath.Substring(1)) | ||
| && !_runtimeConfigProvider.GetConfig().IsMcpEnabled) | ||
|
Comment on lines
+413
to
+414
|
||
| { | ||
| throw new DataApiBuilderException( | ||
| message: $"Route {route} was not found.", | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||
|---|---|---|---|---|
|
|
@@ -167,6 +167,46 @@ public void SinglePathMatchingTest() | |||
|
|
||||
| #endregion | ||||
|
|
||||
| #region MCP Path Guard Tests | ||||
|
|
||||
| /// <summary> | ||||
| /// When MCP is explicitly disabled and the route matches the MCP path (default or custom), | ||||
| /// GetRouteAfterPathBase should throw GlobalMcpEndpointDisabled. | ||||
| /// </summary> | ||||
| [DataTestMethod] | ||||
| [DataRow("/mcp", "mcp", DisplayName = "MCP disabled with default path")] | ||||
| [DataRow("/custom-mcp", "custom-mcp", DisplayName = "MCP disabled with custom path")] | ||||
| public void McpPathThrowsWhenMcpDisabled(string mcpPath, string route) | ||||
| { | ||||
| InitializeTestWithMcpConfig("/api", new McpRuntimeOptions(Enabled: false, Path: mcpPath)); | ||||
| DataApiBuilderException ex = Assert.ThrowsException<DataApiBuilderException>( | ||||
| () => _restService.GetRouteAfterPathBase(route)); | ||||
| Assert.AreEqual(HttpStatusCode.NotFound, ex.StatusCode); | ||||
| Assert.AreEqual(DataApiBuilderException.SubStatusCodes.GlobalMcpEndpointDisabled, ex.SubStatusCode); | ||||
| } | ||||
|
|
||||
| /// <summary> | ||||
| /// When MCP is enabled (explicitly or by default when config is absent), | ||||
| /// the MCP path route should NOT throw GlobalMcpEndpointDisabled. | ||||
| /// It falls through to the normal path-base check. | ||||
| /// </summary> | ||||
| [DataTestMethod] | ||||
| [DataRow(true, DisplayName = "MCP explicitly enabled")] | ||||
| [DataRow(null, DisplayName = "MCP config absent (defaults to enabled)")] | ||||
| public void McpPathDoesNotThrowGlobalMcpEndpointDisabledWhenMcpEnabled(bool? mcpEnabled) | ||||
| { | ||||
| McpRuntimeOptions mcpOptions = mcpEnabled.HasValue ? new McpRuntimeOptions(Enabled: mcpEnabled.Value) : null; | ||||
| InitializeTestWithMcpConfig("/api", mcpOptions); | ||||
| // "mcp" doesn't start with "api", so it should throw BadRequest (invalid path), | ||||
| // NOT GlobalMcpEndpointDisabled. | ||||
| DataApiBuilderException ex = Assert.ThrowsException<DataApiBuilderException>( | ||||
| () => _restService.GetRouteAfterPathBase("mcp")); | ||||
| Assert.AreEqual(HttpStatusCode.BadRequest, ex.StatusCode); | ||||
| Assert.AreEqual(DataApiBuilderException.SubStatusCodes.BadRequest, ex.SubStatusCode); | ||||
| } | ||||
|
|
||||
| #endregion | ||||
|
|
||||
| #region Helper Functions | ||||
|
|
||||
| /// <summary> | ||||
|
|
@@ -306,6 +346,93 @@ private static void InitializeTestWithEntityPaths(string restRoutePrefix, Dictio | |||
| provider, | ||||
| requestValidator); | ||||
| } | ||||
|
|
||||
| /// <summary> | ||||
| /// Initializes RestService with a specific MCP configuration for testing MCP path guard behavior. | ||||
| /// </summary> | ||||
| /// <param name="restRoutePrefix">REST path prefix (e.g., "/api").</param> | ||||
| /// <param name="mcpOptions">MCP options, or null to simulate absent mcp config block.</param> | ||||
| private static void InitializeTestWithMcpConfig(string restRoutePrefix, McpRuntimeOptions mcpOptions) | ||||
| { | ||||
| RuntimeConfig mockConfig = new( | ||||
| Schema: "", | ||||
| DataSource: new(DatabaseType.PostgreSQL, "", new()), | ||||
| Runtime: new( | ||||
| Rest: new(Path: restRoutePrefix), | ||||
| GraphQL: new(), | ||||
| Mcp: mcpOptions, | ||||
| Host: new(null, null) | ||||
| ), | ||||
| Entities: new(new Dictionary<string, Entity>()) | ||||
| ); | ||||
|
|
||||
| MockFileSystem fileSystem = new(); | ||||
| fileSystem.AddFile(FileSystemRuntimeConfigLoader.DEFAULT_CONFIG_FILE_NAME, new MockFileData(mockConfig.ToJson())); | ||||
| FileSystemRuntimeConfigLoader loader = new(fileSystem); | ||||
| RuntimeConfigProvider provider = new(loader); | ||||
| MsSqlQueryBuilder queryBuilder = new(); | ||||
| Mock<DbExceptionParser> dbExceptionParser = new(provider); | ||||
| Mock<ILogger<QueryExecutor<SqlConnection>>> queryExecutorLogger = new(); | ||||
| Mock<ILogger<IQueryEngine>> queryEngineLogger = new(); | ||||
| Mock<IHttpContextAccessor> httpContextAccessor = new(); | ||||
| Mock<IMetadataProviderFactory> metadataProviderFactory = new(); | ||||
| Mock<IAbstractQueryManagerFactory> queryManagerFactory = new(); | ||||
| Mock<IQueryEngineFactory> queryEngineFactory = new(); | ||||
|
|
||||
| MsSqlQueryExecutor queryExecutor = new( | ||||
| provider, | ||||
| dbExceptionParser.Object, | ||||
| queryExecutorLogger.Object, | ||||
| httpContextAccessor.Object); | ||||
|
|
||||
| queryManagerFactory.Setup(x => x.GetQueryBuilder(It.IsAny<DatabaseType>())).Returns(queryBuilder); | ||||
| queryManagerFactory.Setup(x => x.GetQueryExecutor(It.IsAny<DatabaseType>())).Returns(queryExecutor); | ||||
|
|
||||
| Mock<ISqlMetadataProvider> sqlMetadataProvider = new(); | ||||
|
||||
| Mock<ISqlMetadataProvider> sqlMetadataProvider = new(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The change makes MCP map by default when
runtime.mcpis absent, but there’s no test exercising actual endpoint registration (e.g., aTestServerrequest to/mcpwith a config omittingruntime/mcp). Adding an integration test would prevent regressions of #3284.