Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

package com.microsoft.copilot.eclipse.core.lsp.protocol;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

import com.google.gson.Gson;
import com.google.gson.JsonObject;
import org.junit.jupiter.api.Test;

class CopilotLanguageServerSettingsTests {

private final Gson gson = new Gson();

@Test
void testSetMcpServers_copiesTopLevelHeadersToRequestInitHeaders() {
CopilotLanguageServerSettings settings = new CopilotLanguageServerSettings();
String preference = """
{
"servers": {
"remote": {
"type": "http",
"url": "https://example.com/mcp",
"headers": {
"X-Username": "myuser",
"X-Api-Token": "my-token"
}
}
}
}
""";

settings.setMcpServers(preference);

JsonObject mcpServers = gson.fromJson(settings.getGithubSettings().getCopilotSettings().getMcpServers(),
JsonObject.class);
JsonObject remoteServer = mcpServers.getAsJsonObject("remote");
assertTrue(remoteServer.has("requestInit"));
JsonObject requestHeaders = remoteServer.getAsJsonObject("requestInit").getAsJsonObject("headers");
assertEquals("myuser", requestHeaders.get("X-Username").getAsString());
assertEquals("my-token", requestHeaders.get("X-Api-Token").getAsString());
assertTrue(remoteServer.has("headers"));
}

@Test
void testSetMcpServers_copiesTopLevelHeadersWithoutServersWrapper() {
CopilotLanguageServerSettings settings = new CopilotLanguageServerSettings();
String preference = """
{
"remote": {
"type": "http",
"url": "https://example.com/mcp",
"headers": {
"X-Api-Token": "my-token"
}
}
}
""";

settings.setMcpServers(preference);

JsonObject mcpServers = gson.fromJson(settings.getGithubSettings().getCopilotSettings().getMcpServers(),
JsonObject.class);
JsonObject requestHeaders = mcpServers.getAsJsonObject("remote").getAsJsonObject("requestInit")
.getAsJsonObject("headers");
assertEquals("my-token", requestHeaders.get("X-Api-Token").getAsString());
}

@Test
void testSetMcpServers_preservesExistingRequestInitHeaders() {
CopilotLanguageServerSettings settings = new CopilotLanguageServerSettings();
String preference = """
{
"servers": {
"remote": {
"type": "http",
"url": "https://example.com/mcp",
"headers": {
"Authorization": "Bearer top-level-token",
"X-Workspace": "demo"
},
"requestInit": {
"headers": {
"Authorization": "Bearer request-init-token"
}
}
}
}
}
""";

settings.setMcpServers(preference);

JsonObject mcpServers = gson.fromJson(settings.getGithubSettings().getCopilotSettings().getMcpServers(),
JsonObject.class);
JsonObject requestHeaders = mcpServers.getAsJsonObject("remote").getAsJsonObject("requestInit")
.getAsJsonObject("headers");
assertEquals("Bearer request-init-token", requestHeaders.get("Authorization").getAsString());
assertEquals("demo", requestHeaders.get("X-Workspace").getAsString());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.annotations.SerializedName;
import com.google.gson.reflect.TypeToken;
Expand Down Expand Up @@ -507,21 +509,76 @@ private String parseMcpServers(String mcpServersPreference) {

try {
Gson gson = new GsonBuilder().disableHtmlEscaping().create();
Map<String, Object> jsonMap = gson.fromJson(mcpServersPreference, new TypeToken<Map<String, Object>>() {
}.getType());
JsonObject jsonObject = gson.fromJson(mcpServersPreference, JsonObject.class);

if (jsonMap != null && jsonMap.containsKey("servers")) {
Object serversObj = jsonMap.get("servers");
if (jsonObject != null && jsonObject.has("servers")) {
JsonElement serversObj = jsonObject.get("servers");
normalizeMcpServerHeaders(serversObj);
return gson.toJson(serversObj);
}

if (normalizeMcpServerHeaders(jsonObject)) {
return gson.toJson(jsonObject);
}
return mcpServersPreference;
} catch (JsonParseException e) {
CopilotCore.LOGGER.error("Failed to parse MCP servers JSON", e);
return null;
}
}

private boolean normalizeMcpServerHeaders(JsonElement serversElement) {
if (serversElement == null || !serversElement.isJsonObject()) {
return false;
}

boolean changed = false;
JsonObject servers = serversElement.getAsJsonObject();
for (Map.Entry<String, JsonElement> serverEntry : servers.entrySet()) {
JsonElement serverElement = serverEntry.getValue();
if (!serverElement.isJsonObject()) {
continue;
}

JsonObject server = serverElement.getAsJsonObject();
if (!server.has("headers") || !server.get("headers").isJsonObject()) {
continue;
}

JsonObject topLevelHeaders = server.getAsJsonObject("headers");
if (topLevelHeaders.isEmpty()) {
continue;
}

JsonObject requestInit = getOrCreateObject(server, "requestInit");
if (requestInit == null) {
continue;
}
JsonObject requestHeaders = getOrCreateObject(requestInit, "headers");
if (requestHeaders == null) {
continue;
}
for (Map.Entry<String, JsonElement> headerEntry : topLevelHeaders.entrySet()) {
if (!requestHeaders.has(headerEntry.getKey())) {
requestHeaders.add(headerEntry.getKey(), headerEntry.getValue());
changed = true;
}
}
}
return changed;
}

private JsonObject getOrCreateObject(JsonObject parent, String memberName) {
JsonElement existing = parent.get(memberName);
if (existing != null) {
return existing.isJsonObject() ? existing.getAsJsonObject() : null;
}

JsonObject created = new JsonObject();
parent.add(memberName, created);
return created;
}

@Override
public String toString() {
ToStringBuilder builder = new ToStringBuilder(this);
Expand Down Expand Up @@ -555,4 +612,4 @@ public boolean equals(Object obj) {
&& showEditorCompletions == other.showEditorCompletions;
}

}
}