MCP Toolset headers are static — impossible to pass per-user JWT in multi-tenant scenarios
Maintainer thường phản hồi trong vòng 1 ngày
Đánh giá
Issue này chưa được đánh giá.
Mô tả
** Please make sure you read the contribution guide and file the issues in the right place. **
Contribution guide.
🔴 Required Information
Please ensure all items in this section are completed to allow for efficient
triaging. Requests without complete information may be rejected / deprioritized.
If an item is not applicable to you - please mark it as N/A
Is your feature request related to a specific problem?
In a multi-user application (e.g. Spring Boot serving multiple authenticated users), each user has their own JWT token that must be forwarded to an MCP server requiring authentication.
Currently, SseServerParameters.headers() returns an ImmutableMap<String, Object> and StreamableHttpServerParameters.headers is a final Map<String, String>. Both are set at construction time and frozen for the lifetime of the toolset.
This means in a typical Spring Boot setup:
// At app startup — no user context available
SseServerParameters params = SseServerParameters.builder()
.url("https://mcp-server.example.com")
.headers(Map.of("Authorization", "Bearer ???")) // No token yet!
.build();
McpToolset toolset = new McpToolset(params);
When User A (with JWT-A) and User B (with JWT-B) both call the agent, the MCP server always receives the same frozen token (or none). There is no way to inject per-request authentication headers.
Describe the Solution You'd Like
-
Root cause
In DefaultMcpTransportBuilder, the asyncHttpRequestCustomizer is called per-request but always reads from the same frozen parameters object:
.asyncHttpRequestCustomizer(
(requestBuilder, method, uri, body, context) -> {
streamableParams.headers() // ← always the same frozen map
.forEach((key, value) -> requestBuilder.header(key, value));
return Mono.just(requestBuilder);
});The context parameter is available but never used for dynamic header resolution.
-
Proposed solution
Support a Supplier<Map<String, String>> (or similar functional interface) for headers, evaluated at each request:
SseServerParameters params = SseServerParameters.builder()
.url("https://mcp-server.example.com")
.headersProvider(() -> {
String jwt = SecurityContextHolder.getContext()
.getAuthentication().getCredentials().toString();
return Map.of("Authorization", "Bearer " + jwt);
})
.build();This would allow frameworks like Spring Security to inject the current user's token on every MCP call, without creating a new toolset per request.
Impact on your work
We are building a multi-tenant Spring Boot application where multiple authenticated users interact with ADK agents concurrently. Each user's request must be forwarded to MCP servers that enforce JWT-based authorization.
Currently, we have to create a new McpToolset instance per request to inject the user's token, which is inefficient (new connection per call) and defeats the purpose of connection pooling and session reuse. This is a blocker for any production multi-user deployment that relies on authenticated MCP servers.
Timeline: We are targeting a production release in Q4 2026. A fix or accepted workaround by then would be ideal.
Willingness to contribute
Are you interested in implementing this feature yourself or submitting a PR?
Yes — we are willing to submit a PR for this. We have already identified the affected files and a viable approach (see Proposed API below).
🟡 Recommended Information
Describe Alternatives You've Considered
Create a new McpToolset per request — Works but creates a new transport/connection for every call. No session reuse, poor performance under load
Proposed API / Implementation
Add a headersProvider option alongside the existing static headers:
// SseServerParameters / StreamableHttpServerParameters
@Nullable
public abstract Supplier<Map<String, String>> headersProvider();
// DefaultMcpTransportBuilder — evaluate per-request instead of once
.asyncHttpRequestCustomizer(
(requestBuilder, method, uri, body, context) -> {
Supplier<Map<String, String>> provider = params.headersProvider();
if (provider != null) {
provider.get().forEach(requestBuilder::header);
}
return Mono.just(requestBuilder);
});
Usage with Spring Security:
SseServerParameters params = SseServerParameters.builder()
.url("https://mcp-server.example.com")
.headersProvider(() -> {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
return Map.of("Authorization", "Bearer " + auth.getCredentials());
})
.build();
This is backward-compatible — existing headers(Map) usage continues to work unchanged.
Additional Context
The asyncHttpRequestCustomizer in DefaultMcpTransportBuilder already receives a context parameter per-request but currently ignores it — the infrastructure for dynamic behavior is partially in place.
- Ngôn ngữ chính
- Java
- Star
- 1.7k
- Fork
- 431
- Merge trung bình
- 2 ngày 20 giờ
- Pull request đã merge (30 ngày)
- 42
Chuẩn bị môi trường
Khởi chạy dev container của dự án ngay trên trình duyệt, bằng tài khoản GitHub của bạn.
- Không có Dockerfile hay tệp Docker Compose
- Có mẫu pull request
- Đọc hướng dẫn đóng góp
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Issue khác của google/adk-java
-
[spring-ai] ToolConverter silently drops enum and items from tool parameter schemasCó thể đã có người làm Có pull request liên kết đang mở hoặc đã được merge. Đang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 76/100
google/adk-java#1609 · 1 bình luận · 1 người được giao ·
Maintainer thường phản hồi trong vòng 1 ngày
-
[spring-ai] Streaming responses ending with CJK punctuation (。!?) are misclassified as partial and never persisted to the sessionCó thể đã có người làm Có pull request liên kết đang mở hoặc đã được merge. Đang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 84/100
google/adk-java#1608 · 1 bình luận · 1 người được giao ·
Maintainer thường phản hồi trong vòng 1 ngày
-
[spring-ai] Bridge drops reasoning_content (thinking) — surface it as partial events and/or persist itCó thể đã có người làm @hemasekhar-p đã nhận 1 ngày trước. Đang mởneeds review
google/adk-java#1616 · 1 bình luận · 1 người được giao ·
Maintainer thường phản hồi trong vòng 1 ngày
-
needs review
Độ khó 5/5 Hơn một tuần Mức phù hợp với người mới 35/100
google/adk-java#1598 · 1 bình luận · 1 người được giao ·
Maintainer thường phản hồi trong vòng 1 ngày
-
BaseLlmFlow nests each step inside the previous one and overflows the stack after a few hundred LLM callsCó thể đã có người làm @hemasekhar-p đã nhận 8 ngày trước. Đang mởneeds review
Độ khó 4/5 3-5 ngày Mức phù hợp với người mới 68/100
google/adk-java#1564 · 1 bình luận · 1 người được giao ·
Maintainer thường phản hồi trong vòng 1 ngày
Tất cả issue của google/adk-java
Issue tương tự
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 70/100
commonmark/commonmark-java#460 ·
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 86/100
-
1.21.1见幽匿感测体就崩溃Đang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 75/100
-
Make branch and label autocomplete matching locale-independentCó thể đã có người làm Có pull request liên kết đang mở hoặc đã được merge. Đang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 83/100
jenkinsci/gitlab-plugin#1950 ·
-
Place type search does not workĐang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 65/100
commons-app/apps-android-commons#6984 ·
Maintainer thường phản hồi trong vòng 2 ngày