Skip to content

Commit 90b6602

Browse files
committed
MCP-324 Remove namespace from proxied servers
1 parent b39ee6a commit 90b6602

File tree

15 files changed

+74
-185
lines changed

15 files changed

+74
-185
lines changed

docs/proxied-mcp-servers.md

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ Proxied MCP servers are defined in `/proxied-mcp-servers.json` (bundled in the J
3232
[
3333
{
3434
"name": "my-server",
35-
"namespace": "myserver",
3635
"command": "node",
3736
"args": ["path/to/mcp-server.js"],
3837
"env": {},
@@ -43,20 +42,16 @@ Proxied MCP servers are defined in `/proxied-mcp-servers.json` (bundled in the J
4342

4443
**Fields:**
4544
- `name` (required): Human-readable server name (for logging)
46-
- `namespace` (required): Tool name prefix (e.g., tool `analyze` becomes `context/analyze`)
4745
- `command` (required): Executable command to start the MCP server
4846
- `args` (optional): Command-line arguments
4947
- `env` (optional): Environment variables with explicit values. These take precedence over inherited variables.
5048
- `inherits` (optional): Array of environment variable names to inherit from the parent process. Only variables listed here will be inherited.
5149
- `supportedTransports` (required): Array of transport modes supported by this provider. Valid values: `"stdio"`, `"http"`. Providers are only loaded if they support the server's current transport mode.
5250
- `instructions` (optional): Brief instructions to help AI assistants use this provider's tools effectively. These are automatically appended to the server's base instructions.
5351

54-
### Tool Namespacing
52+
### Tool Names
5553

56-
Proxied tools are prefixed with their namespace to avoid conflicts with the main server's tools, following the [MCP SEP-986 naming convention](https://modelcontextprotocol.io/community/seps/986-specify-format-for-tool-names):
57-
- Server namespace: `context`
58-
- Original tool name: `analyze_code`
59-
- Exposed name: `context/analyze_code`
54+
Proxied tools are exposed with their original names from the proxied MCP server.
6055

6156
**Tool Name Validation:**
6257

@@ -102,7 +97,6 @@ Edit `src/main/resources/proxied-mcp-servers.json`:
10297
[
10398
{
10499
"name": "my-server",
105-
"namespace": "myserver",
106100
"command": "node",
107101
"args": ["path/to/mcp-server.js"],
108102
"env": {
@@ -141,7 +135,6 @@ This provides security through explicit allowlisting while maintaining flexibili
141135
```json
142136
{
143137
"name": "my-server",
144-
"namespace": "myserver",
145138
"command": "python",
146139
"args": ["-m", "my_mcp_server"],
147140
"env": {
@@ -159,7 +152,6 @@ The proxied server receives:
159152
```json
160153
{
161154
"name": "my-server",
162-
"namespace": "myserver",
163155
"command": "python",
164156
"args": ["-m", "my_mcp_server"],
165157
"env": {
@@ -178,7 +170,6 @@ If the parent process has `SONARQUBE_TOKEN=secret123` and `SONARQUBE_URL=https:/
178170
```json
179171
{
180172
"name": "my-server",
181-
"namespace": "myserver",
182173
"command": "python",
183174
"args": ["-m", "my_mcp_server"],
184175
"env": {

its/src/test/java/org/sonarsource/sonarqube/mcp/its/ProxiedServerITest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ void should_successfully_connect_to_cag_proxied_server_when_given_enough_time()
5252
.contains("Loading proxied MCP servers configuration")
5353
.contains("Successfully loaded 1 proxied MCP server(s)")
5454
.contains("Initializing 1 proxied MCP server(s)")
55-
.contains("Connecting to 'caas' (namespace: context)")
55+
.contains("Connecting to 'caas'")
5656
.contains("Connected to 'caas' - discovered 1 tool(s)")
5757
.contains("MCP client manager initialization completed. 1/1 server(s) connected")
5858
.contains("Loaded 1 proxied tool(s) from 1/1 server(s)");

its/src/test/resources/proxied-mcp-servers-its.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
[
22
{
33
"name": "caas",
4-
"namespace": "context",
54
"command": "/app/binaries/sonar-code-context-mcp",
65
"args": [],
76
"env": {},

src/main/java/org/sonarsource/sonarqube/mcp/client/McpClientManager.java

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ public void initialize() {
6565
private void initializeClient(ProxiedMcpServerConfig config) {
6666
var serverName = config.name();
6767
try {
68-
LOG.info("Connecting to '" + config.name() + "' (namespace: " + config.namespace() + ")");
68+
LOG.info("Connecting to '" + config.name() + "'");
6969

7070
// Build server parameters for STDIO transport
7171
var serverParamsBuilder = ServerParameters.builder(config.command());
@@ -99,7 +99,7 @@ private void initializeClient(ProxiedMcpServerConfig config) {
9999
LOG.info("Connected to '" + config.name() + "' - discovered " + tools.size() + " tool(s)");
100100
clients.put(serverName, client);
101101
serverTools.put(serverName, tools);
102-
tools.forEach(tool -> LOG.debug(" - " + config.namespace() + "/" + tool.name()));
102+
tools.forEach(tool -> LOG.debug(" - " + tool.name()));
103103
} catch (Exception e) {
104104
LOG.error("Failed to initialize '" + config.name() + "': " + e.getMessage(), e);
105105
serverErrors.put(serverName, e.getMessage());
@@ -110,13 +110,11 @@ public Map<String, ToolMapping> getAllProxiedTools() {
110110
var allTools = new HashMap<String, ToolMapping>();
111111
for (var config : serverConfigs) {
112112
var serverId = config.name();
113-
var namespace = config.namespace();
114113
var tools = serverTools.getOrDefault(serverId, List.of());
115114
tools.forEach(tool -> {
116-
var namespacedToolName = namespace + "/" + tool.name();
117115
try {
118-
ToolNameValidator.validate(namespacedToolName);
119-
allTools.put(namespacedToolName, new ToolMapping(serverId, namespace, tool.name(), tool));
116+
ToolNameValidator.validate(tool.name());
117+
allTools.put(tool.name(), new ToolMapping(serverId, tool.name(), tool));
120118
} catch (IllegalArgumentException e) {
121119
LOG.error("Skipping tool with invalid name from server '" + serverId + "': " + e.getMessage(), e);
122120
}
@@ -206,6 +204,6 @@ Map<String, String> buildEnvironmentVariables(ProxiedMcpServerConfig config, Map
206204
return filteredEnv;
207205
}
208206

209-
public record ToolMapping(String serverId, String namespace, String originalToolName, McpSchema.Tool tool) {}
207+
public record ToolMapping(String serverId, String originalToolName, McpSchema.Tool tool) {}
210208

211209
}

src/main/java/org/sonarsource/sonarqube/mcp/client/ProxiedMcpServerConfig.java

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,16 +21,13 @@
2121
import java.util.Set;
2222
import javax.annotation.Nullable;
2323

24-
public record ProxiedMcpServerConfig(String name, String namespace, String command, List<String> args, Map<String, String> env,
24+
public record ProxiedMcpServerConfig(String name, String command, List<String> args, Map<String, String> env,
2525
List<String> inherits, Set<TransportMode> supportedTransports, @Nullable String instructions) {
2626

2727
public ProxiedMcpServerConfig {
2828
if (name.isBlank()) {
2929
throw new IllegalArgumentException("Proxied MCP server name cannot be null or blank");
3030
}
31-
if (namespace.isBlank()) {
32-
throw new IllegalArgumentException("Proxied MCP server namespace cannot be null or blank");
33-
}
3431
if (command.isBlank()) {
3532
throw new IllegalArgumentException("Proxied MCP server command cannot be null or blank");
3633
}

src/main/java/org/sonarsource/sonarqube/mcp/client/ProxiedServerConfigParser.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,6 @@ static ParseResult parseAndValidateProxiedConfig(String json) {
9898
private static ProxiedMcpServerConfig toProxiedMcpServerConfig(JsonServerConfig jsonConfig) {
9999
return new ProxiedMcpServerConfig(
100100
jsonConfig.name,
101-
jsonConfig.namespace,
102101
jsonConfig.command,
103102
jsonConfig.args != null ? jsonConfig.args : Collections.emptyList(),
104103
jsonConfig.env != null ? jsonConfig.env : Collections.emptyMap(),
@@ -116,7 +115,6 @@ private static ProxiedMcpServerConfig toProxiedMcpServerConfig(JsonServerConfig
116115
@JsonIgnoreProperties(ignoreUnknown = true)
117116
record JsonServerConfig(
118117
@JsonProperty("name") String name,
119-
@JsonProperty("namespace") String namespace,
120118
@JsonProperty("command") String command,
121119
@JsonProperty("args") @Nullable List<String> args,
122120
@JsonProperty("env") @Nullable Map<String, String> env,

src/main/java/org/sonarsource/sonarqube/mcp/client/ProxiedToolsLoader.java

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -75,12 +75,11 @@ public List<Tool> loadProxiedTools(TransportMode currentTransportMode) {
7575
mcpClientManager = new McpClientManager(compatibleConfigs);
7676
mcpClientManager.initialize();
7777

78-
var tools = mcpClientManager.getAllProxiedTools().entrySet().stream()
79-
.map(e -> (Tool) new ProxiedMcpTool(
80-
e.getKey(),
81-
e.getValue().serverId(),
82-
e.getValue().originalToolName(),
83-
e.getValue().tool(),
78+
var tools = mcpClientManager.getAllProxiedTools().values().stream()
79+
.map(toolMapping -> (Tool) new ProxiedMcpTool(
80+
toolMapping.serverId(),
81+
toolMapping.originalToolName(),
82+
toolMapping.tool(),
8483
mcpClientManager
8584
))
8685
.toList();

src/main/java/org/sonarsource/sonarqube/mcp/tools/proxied/ProxiedMcpTool.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,20 +31,20 @@ public class ProxiedMcpTool extends Tool {
3131
private final String serverId;
3232
private final String originalToolName;
3333

34-
public ProxiedMcpTool(String prefixedName, String serverId, String originalToolName, McpSchema.Tool originalTool,
34+
public ProxiedMcpTool(String serverId, String originalToolName, McpSchema.Tool originalTool,
3535
McpClientManager clientManager) {
36-
super(createProxyDefinition(prefixedName, originalTool), ToolCategory.EXTERNAL);
36+
super(createProxyDefinition(originalToolName, originalTool), ToolCategory.EXTERNAL);
3737
this.serverId = serverId;
3838
this.originalToolName = originalToolName;
3939
this.clientManager = clientManager;
4040
}
4141

42-
private static McpSchema.Tool createProxyDefinition(String prefixedName, McpSchema.Tool originalTool) {
42+
private static McpSchema.Tool createProxyDefinition(String toolName, McpSchema.Tool originalTool) {
4343
var title = originalTool.title() != null ? originalTool.title() : originalTool.name();
4444
var description = originalTool.description() != null ? originalTool.description() : "Proxied MCP tool";
4545

4646
return new McpSchema.Tool(
47-
prefixedName,
47+
toolName,
4848
title,
4949
description,
5050
originalTool.inputSchema(),

src/test/java/org/sonarsource/sonarqube/mcp/client/McpClientManagerTest.java

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,8 @@ void initialize_should_be_idempotent() {
4949
@Test
5050
void getTotalCount_should_match_config_count() {
5151
var configs = List.of(
52-
new ProxiedMcpServerConfig("server1", "server1", "npx", List.of(), Map.of(), List.of(), Set.of(TransportMode.STDIO), null),
53-
new ProxiedMcpServerConfig("server2", "server2", "uv", List.of(), Map.of(), List.of(), Set.of(TransportMode.STDIO), null)
52+
new ProxiedMcpServerConfig("server1", "npx", List.of(), Map.of(), List.of(), Set.of(TransportMode.STDIO), null),
53+
new ProxiedMcpServerConfig("server2", "uv", List.of(), Map.of(), List.of(), Set.of(TransportMode.STDIO), null)
5454
);
5555

5656
var manager = new McpClientManager(configs);
@@ -60,7 +60,7 @@ void getTotalCount_should_match_config_count() {
6060

6161
@Test
6262
void getAllExternalTools_should_return_empty_map_before_initialization() {
63-
var configs = List.of(new ProxiedMcpServerConfig("server1", "server1", "npx", List.of(), Map.of(), List.of(), Set.of(TransportMode.STDIO), null));
63+
var configs = List.of(new ProxiedMcpServerConfig("server1", "npx", List.of(), Map.of(), List.of(), Set.of(TransportMode.STDIO), null));
6464

6565
var manager = new McpClientManager(configs);
6666

@@ -102,7 +102,7 @@ void shutdown_should_clear_state() {
102102

103103
@Test
104104
void executeTool_should_include_error_message_for_failed_server() {
105-
var configs = List.of(new ProxiedMcpServerConfig("failing-server", "failing-server", "/non/existent/command", List.of(), Map.of(), List.of(), Set.of(TransportMode.STDIO), null));
105+
var configs = List.of(new ProxiedMcpServerConfig("failing-server", "/non/existent/command", List.of(), Map.of(), List.of(), Set.of(TransportMode.STDIO), null));
106106
var manager = new McpClientManager(configs);
107107
var emptyMap = Map.<String, Object>of();
108108

@@ -116,7 +116,7 @@ void executeTool_should_include_error_message_for_failed_server() {
116116
@Test
117117
void buildEnvironmentVariables_should_include_explicit_values_from_config() {
118118
var manager = new McpClientManager(List.of());
119-
var config = new ProxiedMcpServerConfig("server", "ns", "cmd", List.of(),
119+
var config = new ProxiedMcpServerConfig("server", "cmd", List.of(),
120120
Map.of("EXPLICIT_VAR1", "value1", "EXPLICIT_VAR2", "value2"),
121121
List.of(), Set.of(TransportMode.STDIO), null);
122122
var parentEnv = Map.of("PARENT_VAR", "parent_value");
@@ -133,7 +133,7 @@ void buildEnvironmentVariables_should_include_explicit_values_from_config() {
133133
@Test
134134
void buildEnvironmentVariables_should_inherit_variables_from_parent() {
135135
var manager = new McpClientManager(List.of());
136-
var config = new ProxiedMcpServerConfig("server", "ns", "cmd", List.of(),
136+
var config = new ProxiedMcpServerConfig("server", "cmd", List.of(),
137137
Map.of(),
138138
List.of("INHERITED_VAR1", "INHERITED_VAR2"),
139139
Set.of(TransportMode.STDIO), null);
@@ -155,7 +155,7 @@ void buildEnvironmentVariables_should_inherit_variables_from_parent() {
155155
@Test
156156
void buildEnvironmentVariables_should_prioritize_explicit_values_over_inherited() {
157157
var manager = new McpClientManager(List.of());
158-
var config = new ProxiedMcpServerConfig("server", "ns", "cmd", List.of(),
158+
var config = new ProxiedMcpServerConfig("server", "cmd", List.of(),
159159
Map.of("OVERRIDE_VAR", "explicit_value"),
160160
List.of("OVERRIDE_VAR"),
161161
Set.of(TransportMode.STDIO), null);
@@ -171,7 +171,7 @@ void buildEnvironmentVariables_should_prioritize_explicit_values_over_inherited(
171171
@Test
172172
void buildEnvironmentVariables_should_skip_inherited_var_not_in_parent() {
173173
var manager = new McpClientManager(List.of());
174-
var config = new ProxiedMcpServerConfig("server", "ns", "cmd", List.of(),
174+
var config = new ProxiedMcpServerConfig("server", "cmd", List.of(),
175175
Map.of(),
176176
List.of("MISSING_VAR", "EXISTING_VAR"),
177177
Set.of(TransportMode.STDIO), null);

src/test/java/org/sonarsource/sonarqube/mcp/client/ProxiedMcpServerConfigTest.java

Lines changed: 7 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -31,27 +31,17 @@ void constructor_should_throw_when_name_is_blank() {
3131
var emptyList = List.<String>of();
3232
var emptyMap = Map.<String, String>of();
3333
var defaultTransports = Set.of(TransportMode.STDIO);
34-
assertThatThrownBy(() -> new ProxiedMcpServerConfig(" ", "namespace", "npx", emptyList, emptyMap, emptyList, defaultTransports, "the instructions"))
34+
assertThatThrownBy(() -> new ProxiedMcpServerConfig(" ", "npx", emptyList, emptyMap, emptyList, defaultTransports, "the instructions"))
3535
.isInstanceOf(IllegalArgumentException.class)
3636
.hasMessage("Proxied MCP server name cannot be null or blank");
3737
}
3838

39-
@Test
40-
void constructor_should_throw_when_namespace_is_blank() {
41-
var emptyList = List.<String>of();
42-
var emptyMap = Map.<String, String>of();
43-
var defaultTransports = Set.of(TransportMode.STDIO);
44-
assertThatThrownBy(() -> new ProxiedMcpServerConfig("server", " ", "npx", emptyList, emptyMap, emptyList, defaultTransports, "the instructions"))
45-
.isInstanceOf(IllegalArgumentException.class)
46-
.hasMessage("Proxied MCP server namespace cannot be null or blank");
47-
}
48-
4939
@Test
5040
void constructor_should_throw_when_command_is_blank() {
5141
var emptyList = List.<String>of();
5242
var emptyMap = Map.<String, String>of();
5343
var defaultTransports = Set.of(TransportMode.STDIO);
54-
assertThatThrownBy(() -> new ProxiedMcpServerConfig("server", "namespace", " ", emptyList, emptyMap, emptyList, defaultTransports, null))
44+
assertThatThrownBy(() -> new ProxiedMcpServerConfig("server", " ", emptyList, emptyMap, emptyList, defaultTransports, null))
5545
.isInstanceOf(IllegalArgumentException.class)
5646
.hasMessage("Proxied MCP server command cannot be null or blank");
5747
}
@@ -62,36 +52,36 @@ void constructor_should_throw_when_supported_transports_is_empty() {
6252
var emptyMap = Map.<String, String>of();
6353
var emptyTransports = Set.<TransportMode>of();
6454

65-
assertThatThrownBy(() -> new ProxiedMcpServerConfig("server", "namespace", "npx", emptyList, emptyMap, emptyList, emptyTransports, "the instructions"))
55+
assertThatThrownBy(() -> new ProxiedMcpServerConfig("server", "npx", emptyList, emptyMap, emptyList, emptyTransports, "the instructions"))
6656
.isInstanceOf(IllegalArgumentException.class)
6757
.hasMessage("Proxied MCP server must support at least one transport mode");
6858
}
6959

7060
@Test
7161
void supportsTransport_should_return_true_for_supported_transport() {
72-
var config = new ProxiedMcpServerConfig("server", "namespace", "npx", List.of(), Map.of(), List.of(), Set.of(TransportMode.STDIO, TransportMode.HTTP), "the instructions");
62+
var config = new ProxiedMcpServerConfig("server", "npx", List.of(), Map.of(), List.of(), Set.of(TransportMode.STDIO, TransportMode.HTTP), "the instructions");
7363

7464
assertThat(config.supportsTransport(TransportMode.STDIO)).isTrue();
7565
assertThat(config.supportsTransport(TransportMode.HTTP)).isTrue();
7666
}
7767

7868
@Test
7969
void supportsTransport_should_return_false_for_unsupported_transport() {
80-
var config = new ProxiedMcpServerConfig("server", "namespace", "npx", List.of(), Map.of(), List.of(), Set.of(TransportMode.STDIO), null);
70+
var config = new ProxiedMcpServerConfig("server", "npx", List.of(), Map.of(), List.of(), Set.of(TransportMode.STDIO), null);
8171

8272
assertThat(config.supportsTransport(TransportMode.HTTP)).isFalse();
8373
}
8474

8575
@Test
8676
void instructions_should_return_blank_description_when_empty() {
87-
var config = new ProxiedMcpServerConfig("server", "namespace", "npx", List.of(), Map.of(), List.of(), Set.of(TransportMode.STDIO), null);
77+
var config = new ProxiedMcpServerConfig("server", "npx", List.of(), Map.of(), List.of(), Set.of(TransportMode.STDIO), null);
8878

8979
assertThat(config.instructions()).isBlank();
9080
}
9181

9282
@Test
9383
void instructions_should_return_description() {
94-
var config = new ProxiedMcpServerConfig("server", "namespace", "npx", List.of(), Map.of(), List.of(), Set.of(TransportMode.STDIO), "the instructions");
84+
var config = new ProxiedMcpServerConfig("server", "npx", List.of(), Map.of(), List.of(), Set.of(TransportMode.STDIO), "the instructions");
9585

9686
assertThat(config.instructions()).isEqualTo("the instructions");
9787
}

0 commit comments

Comments
 (0)