Skip to content

Commit 8ee8ccb

Browse files
Make McpTransportContext.metadata immutable (modelcontextprotocol#1076)
Signed-off-by: Daniel Garnier-Moiroux <git@garnier.wf>
1 parent 27b9152 commit 8ee8ccb

2 files changed

Lines changed: 90 additions & 1 deletion

File tree

mcp-core/src/main/java/io/modelcontextprotocol/common/DefaultMcpTransportContext.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ class DefaultMcpTransportContext implements McpTransportContext {
2020

2121
DefaultMcpTransportContext(Map<String, Object> metadata) {
2222
Assert.notNull(metadata, "The metadata cannot be null");
23-
this.metadata = metadata;
23+
this.metadata = Map.copyOf(metadata);
2424
}
2525

2626
@Override
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
/*
2+
* Copyright 2026-2026 the original author or authors.
3+
*/
4+
5+
package io.modelcontextprotocol.common;
6+
7+
import java.util.HashMap;
8+
import java.util.Map;
9+
10+
import org.junit.jupiter.api.Test;
11+
12+
import static org.assertj.core.api.Assertions.assertThat;
13+
14+
/**
15+
* Tests for {@link McpTransportContext#create(Map)}, which is documented to return an
16+
* unmodifiable context.
17+
*/
18+
class McpTransportContextTests {
19+
20+
@Test
21+
void createdContextShouldNotSeeLaterWritesToTheSourceMap() {
22+
Map<String, Object> metadata = new HashMap<>();
23+
metadata.put("tenant", "acme");
24+
25+
McpTransportContext context = McpTransportContext.create(metadata);
26+
metadata.put("tenant", "other");
27+
28+
assertThat(context.get("tenant")).isEqualTo("acme");
29+
}
30+
31+
@Test
32+
void createdContextShouldNotSeeLaterAdditionsToTheSourceMap() {
33+
Map<String, Object> metadata = new HashMap<>();
34+
metadata.put("tenant", "acme");
35+
36+
McpTransportContext context = McpTransportContext.create(metadata);
37+
metadata.put("added-after-the-fact", "surprise");
38+
39+
assertThat(context.get("added-after-the-fact")).isNull();
40+
}
41+
42+
@Test
43+
void createdContextShouldNotBeEmptiedByClearingTheSourceMap() {
44+
Map<String, Object> metadata = new HashMap<>();
45+
metadata.put("tenant", "acme");
46+
47+
McpTransportContext context = McpTransportContext.create(metadata);
48+
metadata.clear();
49+
50+
assertThat(context.get("tenant")).isEqualTo("acme");
51+
}
52+
53+
@Test
54+
void createdContextShouldRemainUsableAsAMapKey() {
55+
Map<String, Object> metadata = new HashMap<>();
56+
metadata.put("tenant", "acme");
57+
McpTransportContext context = McpTransportContext.create(metadata);
58+
59+
Map<McpTransportContext, String> byContext = new HashMap<>();
60+
byContext.put(context, "value");
61+
metadata.put("tenant", "other");
62+
63+
assertThat(byContext.get(context)).isEqualTo("value");
64+
}
65+
66+
@Test
67+
void twoContextsCreatedFromEqualMapsShouldStayEqual() {
68+
Map<String, Object> first = new HashMap<>();
69+
first.put("tenant", "acme");
70+
Map<String, Object> second = new HashMap<>();
71+
second.put("tenant", "acme");
72+
73+
McpTransportContext firstContext = McpTransportContext.create(first);
74+
McpTransportContext secondContext = McpTransportContext.create(second);
75+
assertThat(firstContext).isEqualTo(secondContext);
76+
77+
first.put("tenant", "other");
78+
79+
assertThat(firstContext).isEqualTo(secondContext);
80+
}
81+
82+
@Test
83+
void createdContextFromAnImmutableMapIsAlreadyCorrect() {
84+
McpTransportContext context = McpTransportContext.create(Map.of("tenant", "acme"));
85+
86+
assertThat(context.get("tenant")).isEqualTo("acme");
87+
}
88+
89+
}

0 commit comments

Comments
 (0)