Skip to content

feat: Support the MCP client and dynamically fetch the model list#298

Open
lhpqaq wants to merge 7 commits intoapache:mainfrom
lhpqaq:agent
Open

feat: Support the MCP client and dynamically fetch the model list#298
lhpqaq wants to merge 7 commits intoapache:mainfrom
lhpqaq:agent

Conversation

@lhpqaq
Copy link
Copy Markdown
Member

@lhpqaq lhpqaq commented Mar 26, 2026

  • Support adding MCP servers of the local, SSE, and streamHttp types
  • Support dynamically fetching the model list
  • Support displaying the tool invocation process
image image

lhpqaq added 7 commits March 26, 2026 20:18
- Deleted ClusterFunctions, HostFunctions, StackFunctions, AIServiceToolsProvider, and InfoToolsProvider classes as they are no longer needed.
- Removed associated test classes: ClusterFunctionsTest, HostFunctionsTest, StackFunctionsTest, and AIServiceToolsProviderTest.
- Updated application.yml to include new client configuration.
- Added new API methods in llm-config for fetching platform models.
- Enhanced UI components to support model fetching with loading states and error handling.
- Updated localization files for new UI strings related to model fetching.
- Added repository for Spring milestones in pom.xml.
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR expands the AI assistant feature set by adding MCP client support (local/SSE/streamable HTTP), enabling dynamic model discovery from providers, and surfacing tool invocation progress in the UI during streaming responses.

Changes:

  • Add MCP client configuration/initialization and wire MCP tool callbacks into Spring AI assistants with tool-execution event streaming.
  • Add a backend API to fetch models dynamically and update UI to refresh model options on demand.
  • Update AI assistant UI/store to display tool execution steps alongside the streaming assistant message.

Reviewed changes

Copilot reviewed 42 out of 42 changed files in this pull request and generated 11 comments.

Show a summary per file
File Description
pom.xml Adds Spring milestone repository configuration.
bigtop-manager-ui/src/store/llm-config/index.ts Adds store action to refresh platform models.
bigtop-manager-ui/src/store/ai-assistant/index.ts Tracks tool executions + pending streaming AI record.
bigtop-manager-ui/src/pages/system-manage/llm-config/components/add-llm-item.vue Adds “Fetch Models” button to model selector.
bigtop-manager-ui/src/locales/zh_CN/llm-config.ts Adds new i18n strings for model fetching.
bigtop-manager-ui/src/locales/en_US/llm-config.ts Adds new i18n strings for model fetching.
bigtop-manager-ui/src/features/ai-assistant/index.vue Renders streaming tool/AI records during SSE.
bigtop-manager-ui/src/features/ai-assistant/chat-message.vue Adds UI rendering for tool execution records.
bigtop-manager-ui/src/api/llm-config/types.ts Adds request type for platform model fetch.
bigtop-manager-ui/src/api/llm-config/index.ts Adds API call for platform model list endpoint.
bigtop-manager-ui/src/api/ai-assistant/types.ts Extends message/event typing for tool execution fields.
bigtop-manager-server/src/test/java/org/apache/bigtop/manager/server/tools/provider/AIServiceToolsProviderTest.java Removes tests for removed langchain4j tool provider.
bigtop-manager-server/src/test/java/org/apache/bigtop/manager/server/tools/functions/StackFunctionsTest.java Removes tests for removed langchain4j stack functions.
bigtop-manager-server/src/test/java/org/apache/bigtop/manager/server/tools/functions/HostFunctionsTest.java Removes tests for removed langchain4j host functions.
bigtop-manager-server/src/test/java/org/apache/bigtop/manager/server/tools/functions/ClusterFunctionsTest.java Removes tests for removed langchain4j cluster functions.
bigtop-manager-server/src/main/resources/application.yml Adds MCP client configuration + defaults.
bigtop-manager-server/src/main/java/org/apache/bigtop/manager/server/tools/provider/InfoToolsProvider.java Removes langchain4j ToolProvider implementation.
bigtop-manager-server/src/main/java/org/apache/bigtop/manager/server/tools/provider/AIServiceToolsProvider.java Removes tool provider selector used previously by chatbot.
bigtop-manager-server/src/main/java/org/apache/bigtop/manager/server/tools/functions/StackFunctions.java Removes langchain4j tool functions.
bigtop-manager-server/src/main/java/org/apache/bigtop/manager/server/tools/functions/HostFunctions.java Removes langchain4j tool functions.
bigtop-manager-server/src/main/java/org/apache/bigtop/manager/server/tools/functions/ClusterFunctions.java Removes langchain4j tool functions.
bigtop-manager-server/src/main/java/org/apache/bigtop/manager/server/service/impl/LLMConfigServiceImpl.java Adds dynamic model fetch + uses it in model validation.
bigtop-manager-server/src/main/java/org/apache/bigtop/manager/server/service/impl/ChatbotServiceImpl.java Streams tool execution events via SSE and improves emitter lifecycle handling.
bigtop-manager-server/src/main/java/org/apache/bigtop/manager/server/service/LLMConfigService.java Adds platformModels service API.
bigtop-manager-server/src/main/java/org/apache/bigtop/manager/server/model/vo/TalkVO.java Extends SSE payload with tool execution fields.
bigtop-manager-server/src/main/java/org/apache/bigtop/manager/server/model/converter/AuthPlatformConverter.java Filters null credential keys before toMap conversion.
bigtop-manager-server/src/main/java/org/apache/bigtop/manager/server/mcp/tool/StackMcpTool.java Adds logging when MCP tool is invoked.
bigtop-manager-server/src/main/java/org/apache/bigtop/manager/server/controller/LLMConfigController.java Adds /platforms/{platformId}/models endpoint.
bigtop-manager-server/pom.xml Removes direct langchain4j dependency from server module.
bigtop-manager-bom/pom.xml Bumps Spring AI version; adds MCP client starter to BOM.
bigtop-manager-ai/src/test/java/org/apache/bigtop/manager/ai/config/McpAsyncClientManagerTest.java Adds tests for MCP connection parsing behavior.
bigtop-manager-ai/src/test/java/assistant/GeneralAssistantFactoryTest.java Updates tests for revised factory method signatures.
bigtop-manager-ai/src/main/java/org/apache/bigtop/manager/ai/platform/QianFanAssistant.java Adds dynamic model fetch + MCP tool callback observation.
bigtop-manager-ai/src/main/java/org/apache/bigtop/manager/ai/platform/OpenAIAssistant.java Adds MCP tool callback observation + base URL/model streaming tweaks.
bigtop-manager-ai/src/main/java/org/apache/bigtop/manager/ai/platform/DeepSeekAssistant.java Adds MCP tool callback observation + streaming safety tweaks.
bigtop-manager-ai/src/main/java/org/apache/bigtop/manager/ai/platform/DashScopeAssistant.java Adds MCP tool callback observation + streaming safety tweaks.
bigtop-manager-ai/src/main/java/org/apache/bigtop/manager/ai/core/factory/AIAssistantFactory.java Adjusts factory API; adds model discovery and tool-event hook.
bigtop-manager-ai/src/main/java/org/apache/bigtop/manager/ai/core/factory/AIAssistant.java Adds MCP client + tool-event listener support and event record type.
bigtop-manager-ai/src/main/java/org/apache/bigtop/manager/ai/core/AbstractAIAssistant.java Implements model discovery helper and tool-execution event emission wiring.
bigtop-manager-ai/src/main/java/org/apache/bigtop/manager/ai/config/McpAsyncClientManager.java Adds MCP async client manager with connection parsing and initialization.
bigtop-manager-ai/src/main/java/org/apache/bigtop/manager/ai/assistant/GeneralAssistantFactory.java Wires MCP clients + tool execution listener; adds model discovery method.
bigtop-manager-ai/pom.xml Adds MCP client starter dependency.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +164 to +173
<repositories>
<repository>
<snapshots>
<enabled>false</enabled>
</snapshots>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
</repository>
</repositories>
Copy link

Copilot AI Mar 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The added Spring milestone repository appears unnecessary for Spring AI 1.1.3 / spring-ai-starter-mcp-client-webflux (both are published to Maven Central). Keeping an extra repository can make builds less reproducible and increases supply-chain surface; consider removing it (or gating it behind a profile only when needed).

Copilot uses AI. Check for mistakes.
Comment on lines +73 to +80
@Operation(summary = "list platform models", description = "List models from /v1/models")
@PostMapping("/platforms/{platformId}/models")
public ResponseEntity<List<String>> platformModels(
@PathVariable(name = "platformId") Long platformId, @RequestBody AuthPlatformReq authPlatformReq) {
AuthPlatformDTO authPlatformDTO = AuthPlatformConverter.INSTANCE.fromReq2DTO(authPlatformReq);
Map<String, String> authCredentials = authPlatformDTO.getAuthCredentials();
return ResponseEntity.success(llmConfigService.platformModels(platformId, authCredentials));
}
Copy link

Copilot AI Mar 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new /platforms/{platformId}/models endpoint accepts AuthPlatformReq, but platformId is already provided in the path and the request type contains many unrelated fields. Consider introducing a dedicated request DTO (e.g., { authCredentials: [...] }) to keep the API contract clear and avoid implying that other fields are required/used.

Copilot uses AI. Check for mistakes.
AIAssistant aiAssistant = prepareTalk(threadId, command, emitter, emitterClosed);

Flux<String> stringFlux =
(command == null) ? aiAssistant.streamAsk(message) : Flux.just(aiAssistant.ask(message));
Copy link

Copilot AI Mar 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Flux.just(aiAssistant.ask(message)) evaluates ask() eagerly (before subscription). With tool-execution events now emitted during ask(), this can send SSE events before the emitter lifecycle handlers are registered / before the client is fully connected. Use a deferred publisher (e.g., Flux.defer/Mono.fromCallable) for the non-streaming path so execution starts on subscribe and can be cancelled/cleaned up consistently.

Suggested change
(command == null) ? aiAssistant.streamAsk(message) : Flux.just(aiAssistant.ask(message));
(command == null)
? aiAssistant.streamAsk(message)
: Flux.defer(() -> Flux.just(aiAssistant.ask(message)));

Copilot uses AI. Check for mistakes.
Comment on lines +94 to +104
OpenAiApi openAiApi = OpenAiApi.builder()
.baseUrl(resolveDefaultBaseUrl())
.apiKey(apiKey)
.build();
OpenAiChatOptions.Builder optionsBuilder =
OpenAiChatOptions.builder().model(model);
List<io.modelcontextprotocol.client.McpAsyncClient> mcpClients = getMcpAsyncClients();
if (!mcpClients.isEmpty()) {
optionsBuilder.toolCallbacks(buildObservedToolCallbacks(mcpClients));
}
OpenAiChatOptions options = optionsBuilder.build();
Copy link

Copilot AI Mar 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OpenAIAssistant supports a credential-provided baseUrl for model discovery, but chat requests still always use resolveDefaultBaseUrl() (ignoring that credential). This can make model listing succeed against a custom endpoint while chat requests go to a different host. Consider using the same base-url resolution for both chat and model listing to keep behavior consistent.

Copilot uses AI. Check for mistakes.
if (supportModels == null || supportModels.isBlank()) {
return Collections.emptyList();
}
return List.of(supportModels.split(","));
Copy link

Copilot AI Mar 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getDefaultModels returns List.of(supportModels.split(",")) without trimming/filtering blanks. If supportModels contains spaces or trailing commas, callers may see invalid model IDs (e.g., " gpt-4" or ""). Consider trimming each entry and filtering empty strings before returning.

Suggested change
return List.of(supportModels.split(","));
String[] rawModels = supportModels.split(",");
List<String> models = new ArrayList<>();
for (String rawModel : rawModels) {
if (rawModel == null) {
continue;
}
String trimmed = rawModel.trim();
if (!trimmed.isEmpty()) {
models.add(trimmed);
}
}
if (models.isEmpty()) {
return Collections.emptyList();
}
return models;

Copilot uses AI. Check for mistakes.
Comment on lines +201 to +214
const prevRecord = toolExecutions.value.find((execution) => execution.executionId === executionId)
const record = {
sender: 'AI' as const,
message: '',
messageType: 'tool' as const,
executionId,
toolName,
toolStatus,
toolPayload,
toolInput: toolStatus === 'started' ? toolPayload || '' : prevRecord?.toolInput || '',
toolOutput: toolStatus === 'completed' ? toolPayload || '' : prevRecord?.toolOutput || '',
toolError: toolStatus === 'failed' ? toolPayload || '' : prevRecord?.toolError || '',
createTime: dayjs().format()
}
Copy link

Copilot AI Mar 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updateToolExecution overwrites createTime on every tool status update. This can reorder tool records / show misleading timestamps (e.g., completed time instead of started time). Preserve the original createTime from prevRecord when present and only set it when creating a new execution record.

Copilot uses AI. Check for mistakes.
Comment on lines +69 to +79
<div class="tool-section">
<span class="tool-section-title">Input</span>
<markdown-view :mark-raw="toolInput" />
</div>
<div v-if="props.record.toolStatus === 'completed'" class="tool-section">
<span class="tool-section-title">Output</span>
<markdown-view :mark-raw="toolOutput" />
</div>
<div v-if="props.record.toolStatus === 'failed'" class="tool-section">
<span class="tool-section-title">Error</span>
<markdown-view :mark-raw="toolError" />
Copy link

Copilot AI Mar 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This component introduces new user-visible labels ("Input", "Output", "Error") and displays raw status strings (started/completed/failed) without i18n. Other ai-assistant components use useI18n(), so these should be translated and status values mapped to localized labels.

Copilot uses AI. Check for mistakes.
Comment on lines +57 to 58
.filter(item -> item != null && item.getKey() != null)
.collect(Collectors.toMap(AuthCredentialReq::getKey, AuthCredentialReq::getValue));
Copy link

Copilot AI Mar 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Collectors.toMap(AuthCredentialReq::getKey, AuthCredentialReq::getValue) will throw a NullPointerException if any credential has a null value (e.g., frontend sends {key: "apiKey"} when value is undefined). Filter out null values (and ideally handle duplicate keys via a merge function) to avoid 500s on the new models endpoint and existing auth-platform endpoints.

Suggested change
.filter(item -> item != null && item.getKey() != null)
.collect(Collectors.toMap(AuthCredentialReq::getKey, AuthCredentialReq::getValue));
.filter(item -> item != null && item.getKey() != null && item.getValue() != null)
.collect(Collectors.toMap(AuthCredentialReq::getKey, AuthCredentialReq::getValue, (existing, replacement) -> replacement));

Copilot uses AI. Check for mistakes.
Comment on lines +28 to +45
client:
enabled: true
request-timeout-seconds: 120
init-timeout-seconds: 20
# Optional multi-connection definition.
# Format:
# name=<id>,type=<sse|streamable-http|local>,baseUrl=<url>,endpoint=<path>,command=<cmd>,args=<a|b>,env=<K:V|K2:V2>,requestTimeoutSeconds=<n>,initTimeoutSeconds=<n>;
# Notes:
# - sse / streamable-http use baseUrl (+ optional endpoint)
# - local uses command/args/env
# - requestTimeoutSeconds/initTimeoutSeconds are optional per-connection overrides
# Example:
# connections: >
# name=embedded,type=sse,baseUrl=http://localhost:8080,endpoint=/mcp/sse;
# name=remote,type=streamable-http,baseUrl=http://127.0.0.1:9000,endpoint=/mcp,requestTimeoutSeconds=180;
# name=memory,type=local,command=npx,args=-y|@modelcontextprotocol/server-memory,env=NODE_ENV:production
connections: >
name=bm,type=sse,baseUrl=http://localhost:8080,endpoint=/mcp/sse
Copy link

Copilot AI Mar 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MCP client is enabled by default and ships with a default connection to http://localhost:8080. This can lead to unexpected outbound connections/timeouts in real deployments and makes the feature non-optional by default. Consider defaulting spring.ai.mcp.client.enabled to false and leaving connections empty (keep examples commented) so operators opt in explicitly.

Copilot uses AI. Check for mistakes.
stackVOList.add(stackVO);
}

log.info("ListStacks tool called, total stacks: {}", stackVOList.size());
Copy link

Copilot AI Mar 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Logging every tool invocation at INFO (ListStacks tool called...) may be noisy if tools are invoked frequently during chats. Consider lowering this to DEBUG (or sampling) to avoid log volume growth in production.

Suggested change
log.info("ListStacks tool called, total stacks: {}", stackVOList.size());
log.debug("ListStacks tool called, total stacks: {}", stackVOList.size());

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants