Skip to content

Conversation

@kseysh
Copy link
Owner

@kseysh kseysh commented Dec 8, 2025

Related issue 🛠

Work Description ✏️

  • Tool Refresh API를 추가하였습니다.
  • SyncMcpToolCallbackProvider를 활용하여 캐싱해둔 Tool List를 invalid하게 만들어 tool을 mcp server로부터 Tool List를 재호출하도록 구현했습니다.

@kseysh kseysh self-assigned this Dec 8, 2025
@coderabbitai
Copy link

coderabbitai bot commented Dec 8, 2025

Caution

Review failed

The pull request is closed.

Summary by CodeRabbit

릴리스 노트

  • 새로운 기능

    • 도구 캐시를 새로고침하는 새로운 API 엔드포인트 추가
    • 도구 캐시 관리를 위한 새로운 서비스 계층 도입
  • 테스트

    • 새로운 엔드포인트에 대한 테스트 커버리지 추가

✏️ Tip: You can customize this high-level summary in your review settings.

Walkthrough

POST /v1/mcp/tools/refresh 엔드포인트가 ChatController에 추가되어 McpToolService.refreshTools()를 호출하고 204 No Content를 반환합니다. 신규 McpToolService는 SyncMcpToolCallbackProvider.invalidateCache()를 호출해 MCP 도구 캐시를 무효화합니다.

Changes

Cohort / File(s) Change Summary
Chat Controller Endpoint
chat/src/main/java/core/chat/controller/ChatController.java
McpToolService 의존성 주입 추가 및 POST /v1/mcp/tools/refresh 엔드포인트 추가. 엔드포인트는 mcpToolService.refreshTools() 호출 후 HTTP 204 No Content 반환.
MCP Tool Service
mcp-client/src/main/java/core/mcpclient/service/McpToolService.java
신규 @Service 클래스 추가. SyncMcpToolCallbackProvider 주입(생성자) 및 refreshTools()에서 syncMcpToolCallbackProvider.invalidateCache() 호출로 캐시 무효화 구현.
Tests
chat/src/test/java/core/chat/controller/ChatControllerTest.java
테스트에 @MockitoBean으로 McpToolService 목(mock) 추가(임포트 및 필드 선언). 기존 테스트 로직 변경 없음.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant Client
    participant ChatController
    participant McpToolService
    participant SyncMcpToolCallbackProvider

    Note over Client,ChatController: POST /v1/mcp/tools/refresh
    Client->>ChatController: POST /v1/mcp/tools/refresh
    ChatController->>McpToolService: refreshTools()
    McpToolService->>SyncMcpToolCallbackProvider: invalidateCache()
    SyncMcpToolCallbackProvider-->>McpToolService: cache invalidated
    McpToolService-->>ChatController: return
    ChatController-->>Client: 204 No Content
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • 확인할 주요 파일:
    • chat/src/main/java/core/chat/controller/ChatController.java — 엔드포인트 매핑, 응답 코드(204) 검증
    • mcp-client/src/main/java/core/mcpclient/service/McpToolService.java — 생성자 주입, invalidateCache() 호출 위치 및 예외 처리 여부
    • chat/src/test/java/core/chat/controller/ChatControllerTest.java — 목 객체 추가로 인한 테스트 설정/초기화 영향 검토

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목이 변경 사항의 핵심을 명확하게 요약하고 있습니다. Tool Refresh API 추가라는 주요 변경 사항을 직접적으로 설명하고 있습니다.
Description check ✅ Passed PR 설명이 변경 사항과 관련이 있으며, Tool Refresh API 추가 및 구현 방식을 설명하고 있습니다.
Linked Issues check ✅ Passed PR은 #36 이슈의 'Tool refresh 로직 추가' 요구사항을 충족합니다. McpToolService와 REST 엔드포인트를 통해 Tool List 캐시 무효화 기능을 구현했습니다.
Out of Scope Changes check ✅ Passed 모든 변경 사항이 #36 이슈의 Tool Refresh API 추가 요구사항과 관련이 있습니다. 범위를 벗어난 변경 사항이 없습니다.

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 67cefee and 3f415df.

📒 Files selected for processing (2)
  • chat/src/main/java/core/chat/controller/ChatController.java (3 hunks)
  • chat/src/test/java/core/chat/controller/ChatControllerTest.java (2 hunks)

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9c15d42 and 67cefee.

📒 Files selected for processing (2)
  • chat/src/main/java/core/chat/controller/ChatController.java (3 hunks)
  • mcp-client/src/main/java/core/mcpclient/service/McpToolService.java (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-11-20T07:10:22.381Z
Learnt from: kseysh
Repo: kseysh/factory-chatbot PR: 24
File: chat/src/main/java/core/chat/service/ChatFacade.java:96-100
Timestamp: 2025-11-20T07:10:22.381Z
Learning: In chat/src/main/java/core/chat/service/ChatFacade.java, the `checkUserCanDeleteRoom` method returns `orElse(true)` when a chat room doesn't exist. This is intentional to ensure idempotency of the DELETE operation - allowing deletion of non-existent rooms to succeed silently, treating DELETE as a declaration of desired end state rather than an action.

Applied to files:

  • chat/src/main/java/core/chat/controller/ChatController.java
🔇 Additional comments (5)
chat/src/main/java/core/chat/controller/ChatController.java (2)

16-16: LGTM!

필요한 의존성 import가 올바르게 추가되었습니다.


30-30: LGTM!

@requiredargsconstructor를 통한 생성자 주입이 올바르게 적용되었습니다.

mcp-client/src/main/java/core/mcpclient/service/McpToolService.java (3)

1-5: LGTM!

패키지 선언과 import 문이 적절합니다. 필요한 의존성만 포함되어 있습니다.


7-9: LGTM!

Spring @service 어노테이션과 Lombok @requiredargsconstructor를 적절히 사용하여 생성자 기반 의존성 주입을 구현했습니다.


11-11: No action needed - invalidateCache() is the correct Spring AI MCP API

The SyncMcpToolCallbackProvider.invalidateCache() method is the documented and intended API for invalidating cached tool callbacks and forcing re-discovery on the next request. This is the correct approach for refreshing the tool list.

Comment on lines 127 to 131
@PostMapping("/v1/mcp/tools/refresh")
public ResponseEntity<Void> refreshTools() {
mcpToolService.refreshTools();
return ResponseEntity.ok().build();
}
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

응답 코드 일관성 개선 고려

본문이 없는 작업에 대해 deleteChatRoom 엔드포인트(Line 124)는 ResponseEntity.noContent().build() (204)를 사용하는 반면, 이 엔드포인트는 ResponseEntity.ok().build() (200)를 사용합니다. 일관성을 위해 동일한 접근 방식을 사용하는 것을 고려하세요.

일관성을 위한 diff:

 @PostMapping("/v1/mcp/tools/refresh")
 public ResponseEntity<Void> refreshTools(@UserId String userId) {
     mcpToolService.refreshTools();
-    return ResponseEntity.ok().build();
+    return ResponseEntity.noContent().build();
 }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In chat/src/main/java/core/chat/controller/ChatController.java around lines 127
to 131, the POST /v1/mcp/tools/refresh endpoint currently returns
ResponseEntity.ok().build() (200) for a bodyless operation while similar
bodyless endpoints use ResponseEntity.noContent().build() (204); change the
return to ResponseEntity.noContent().build() so the response code is consistent
with other bodyless operations.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

cat -n chat/src/main/java/core/chat/controller/ChatController.java | head -160

Repository: kseysh/factory-chatbot

Length of output: 6854


🏁 Script executed:

rg -n --type=java '@PreAuthorize|@Secured|@RolesAllowed|hasRole|ROLE_' -A 2 | head -80

Repository: kseysh/factory-chatbot

Length of output: 48


🏁 Script executed:

rg -n --type=java '@UserId' -A 2 -B 2 | head -30

Repository: kseysh/factory-chatbot

Length of output: 2792


🏁 Script executed:

find . -type f -name "*Security*" -o -name "*Auth*" -o -name "*Filter*" | grep -E "\.(java|xml|properties|yml)$" | head -20

Repository: kseysh/factory-chatbot

Length of output: 48


🏁 Script executed:

find . -type f -name "*.java" -path "*global/auth*" | head -20

Repository: kseysh/factory-chatbot

Length of output: 181


🏁 Script executed:

rg -n "class UserId|interface UserId|@interface UserId" --type=java

Repository: kseysh/factory-chatbot

Length of output: 289


🏁 Script executed:

cat -n ./chat/src/main/java/core/global/auth/UserId.java

Repository: kseysh/factory-chatbot

Length of output: 427


🏁 Script executed:

cat -n ./chat/src/main/java/core/global/auth/UserIdArgumentResolver.java

Repository: kseysh/factory-chatbot

Length of output: 1483


🏁 Script executed:

rg -n "class McpToolService|def refreshTools|void refreshTools" --type=java -A 5 | head -50

Repository: kseysh/factory-chatbot

Length of output: 835


🏁 Script executed:

find . -type f -name "McpToolService.java"

Repository: kseysh/factory-chatbot

Length of output: 135


Missing authentication on admin endpoint

The refreshTools() endpoint lacks the @UserId annotation required by all other business logic endpoints in this controller. Since mcpToolService.refreshTools() invalidates the tool cache for all users, this is a system-level admin operation that must require authentication.

Without @UserId, this endpoint accepts unauthenticated requests, allowing anyone to:

  • Invalidate the MCP tool cache
  • Force unnecessary re-syncs with MCP servers
  • Potentially cause DoS via repeated calls

Add the @UserId annotation to restrict access to authenticated users:

 @PostMapping("/v1/mcp/tools/refresh")
-public ResponseEntity<Void> refreshTools() {
+public ResponseEntity<Void> refreshTools(@UserId String userId) {
     mcpToolService.refreshTools();
     return ResponseEntity.ok().build();
 }

Additionally, consider whether this operation should be further restricted to admin users via role-based access control if the codebase adopts such mechanisms in the future.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@PostMapping("/v1/mcp/tools/refresh")
public ResponseEntity<Void> refreshTools() {
mcpToolService.refreshTools();
return ResponseEntity.ok().build();
}
@PostMapping("/v1/mcp/tools/refresh")
public ResponseEntity<Void> refreshTools(@UserId String userId) {
mcpToolService.refreshTools();
return ResponseEntity.ok().build();
}
🤖 Prompt for AI Agents
In chat/src/main/java/core/chat/controller/ChatController.java around lines
127-131, the refreshTools() admin endpoint is missing the @UserId annotation so
it accepts unauthenticated calls; add a @UserId-annotated parameter (e.g. add a
method param like @UserId String userId) to the controller method signature so
the framework enforces authentication before calling
mcpToolService.refreshTools(), and if the service API requires it, forward the
userId to the service; optionally annotate or guard the method with any existing
role-based check (admin role) if such RBAC is available in the codebase.

Comment on lines +7 to +15
@RequiredArgsConstructor
@Service
public class McpToolService {

private final SyncMcpToolCallbackProvider syncMcpToolCallbackProvider;

public void refreshTools() {
syncMcpToolCallbackProvider.invalidateCache();
}
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

JavaDoc 추가 고려

클래스와 메서드에 대한 문서화를 추가하면 코드 가독성과 유지보수성이 향상됩니다.

+/**
+ * MCP 도구 관련 서비스
+ * MCP 서버로부터 도구 목록을 갱신하는 기능을 제공합니다.
+ */
 @RequiredArgsConstructor
 @Service
 public class McpToolService {
 
     private final SyncMcpToolCallbackProvider syncMcpToolCallbackProvider;
 
+    /**
+     * MCP 도구 캐시를 무효화하여 다음 요청 시 서버로부터 도구 목록을 재조회하도록 합니다.
+     */
     public void refreshTools() {
         syncMcpToolCallbackProvider.invalidateCache();
     }
 }
🤖 Prompt for AI Agents
In mcp-client/src/main/java/core/mcpclient/service/McpToolService.java around
lines 7 to 15, the class and its public method lack JavaDoc; add concise JavaDoc
above the class describing its responsibility (a Spring @Service that refreshes
MCP tools by invalidating the SyncMcpToolCallbackProvider cache) and add JavaDoc
above refreshTools() describing the action it performs and side-effects
(invalidates provider cache), include @author or @since tags if your project
uses them, and document that the method has no parameters and returns void.

Comment on lines +13 to +15
public void refreshTools() {
syncMcpToolCallbackProvider.invalidateCache();
}
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

에러 처리와 로깅 추가 권장

refreshTools() 메서드에 다음 개선사항을 적용하는 것을 권장합니다:

  1. 에러 처리: invalidateCache() 호출 시 발생할 수 있는 예외를 처리하지 않아, 예외 발생 시 호출자에게 그대로 전파됩니다.
  2. 로깅: 도구 목록 갱신 시점을 추적할 수 있는 로그가 없어 모니터링 및 디버깅이 어렵습니다.
  3. 피드백: void 반환 타입으로 인해 작업 성공 여부를 호출자가 알 수 없습니다.

다음과 같이 개선할 수 있습니다:

+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
 @RequiredArgsConstructor
 @Service
 public class McpToolService {
 
     private final SyncMcpToolCallbackProvider syncMcpToolCallbackProvider;
 
     public void refreshTools() {
+        try {
+            log.info("MCP 도구 캐시 무효화 시작");
             syncMcpToolCallbackProvider.invalidateCache();
+            log.info("MCP 도구 캐시 무효화 완료");
+        } catch (Exception e) {
+            log.error("MCP 도구 캐시 무효화 실패", e);
+            throw e;
+        }
     }
 }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In mcp-client/src/main/java/core/mcpclient/service/McpToolService.java around
lines 13 to 15, refreshTools() currently calls
syncMcpToolCallbackProvider.invalidateCache() with no try/catch, no logging, and
returns void; change it to catch and handle exceptions thrown by
invalidateCache(), log start/success/failure with contextual messages (including
exception details on failure), and change the method to return a boolean (or an
appropriate status) indicating success/failure so callers can react; ensure the
catch logs the exception and returns false, successful path logs and returns
true.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Find SyncMcpToolCallbackProvider and understand its invalidateCache implementation
fd -t f "SyncMcpToolCallbackProvider" --type f

# Also search for the class definition
rg -l "class SyncMcpToolCallbackProvider" -t java

Repository: kseysh/factory-chatbot

Length of output: 48


🏁 Script executed:

# List the structure of mcp-client
find mcp-client -type f -name "*.java" | head -20

# Search for invalidateCache method
rg -n "invalidateCache" -t java

# Search for refreshTools usage
rg -n "refreshTools" -t java

Repository: kseysh/factory-chatbot

Length of output: 1447


🏁 Script executed:

# Read McpToolService.java
cat -n mcp-client/src/main/java/core/mcpclient/service/McpToolService.java

# Read ChatController.java to see the REST endpoint
cat -n chat/src/main/java/core/chat/controller/ChatController.java | head -150

Repository: kseysh/factory-chatbot

Length of output: 7003


🏁 Script executed:

# Find the build configuration to see Spring AI version
find . -name "pom.xml" -o -name "build.gradle" -o -name "build.gradle.kts" | head -5

# Check if there's a dependency file that shows Spring AI version
rg -n "spring-ai|SyncMcpToolCallbackProvider" --type xml --type gradle

Repository: kseysh/factory-chatbot

Length of output: 714


🏁 Script executed:

# Check root build.gradle for Spring AI version
cat -n build.gradle | grep -A 5 -B 5 "springAiVersion"

# Also check for gradle.properties
cat -n gradle.properties 2>/dev/null || echo "No gradle.properties"

Repository: kseysh/factory-chatbot

Length of output: 634


🌐 Web query:

Spring AI 1.1.0 SyncMcpToolCallbackProvider invalidateCache thread-safe

💡 Result:

Short answer: invalidateCache() on SyncMcpToolCallbackProvider is not documented as thread‑safe. The Javadoc merely exposes invalidateCache() (no threading guarantees) — so treat it as not thread‑safe and synchronize externally (or call from a single thread / the ApplicationEvent thread). [1][2]

References

  • SyncMcpToolCallbackProvider API (1.1.x). [1]
  • SyncMcpToolCallbackProvider API (1.1.0‑M2 / 1.1.0 docs). [2]

Add synchronization to prevent concurrent cache invalidation issues

invalidateCache() in Spring AI 1.1.0's SyncMcpToolCallbackProvider is not documented as thread-safe. Since refreshTools() is exposed as a public REST endpoint at /v1/mcp/tools/refresh, concurrent HTTP requests can call this method simultaneously, potentially causing cache corruption or race conditions. Synchronize access to invalidateCache() using either a synchronized block, ReentrantLock, or a Spring-managed mechanism like @Synchronized.

🤖 Prompt for AI Agents
In mcp-client/src/main/java/core/mcpclient/service/McpToolService.java around
lines 13 to 15, refreshTools() directly calls
syncMcpToolCallbackProvider.invalidateCache() without any synchronization which
can lead to race conditions under concurrent HTTP requests; modify the class to
serialize access to invalidateCache() — for example add a private final
ReentrantLock cacheLock and wrap the call in cacheLock.lock()/try {
syncMcpToolCallbackProvider.invalidateCache(); } finally { cacheLock.unlock(); }
(alternatively mark the method synchronized or use a Spring @Synchronized on the
method) to ensure only one thread invalidates the cache at a time and always
release the lock in a finally block.

@kseysh kseysh merged commit 5d7a593 into main Dec 8, 2025
1 check passed
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.

[FEAT] TOOL Refresh api 추가

2 participants