Skip to content

Commit 5028654

Browse files
committed
Explain: Improve configuration using --additional-mcp-config.
The upstream Copilot project has added support for explicitly specifying additional MCP configuration files (see github/copilot-cli#51). This changeset tidies up our Copilot integration further by using it. As a result, existing MCP configuration should also be retained when running under `explain`.
1 parent b2bca36 commit 5028654

File tree

1 file changed

+16
-14
lines changed

1 file changed

+16
-14
lines changed

explain/copilot_cli_agent.py

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,15 @@
33
"""
44

55
import asyncio
6+
import contextlib
67
import itertools
78
import json
89
import os
910
from dataclasses import dataclass
1011
from pathlib import Path
1112
from typing import ClassVar
1213

13-
from src.udbpy.fileutil import mkdtemp
14+
from src.udbpy.fileutil import mkdtemp, mkstemp
1415

1516
from .agents import BaseAgent
1617
from .assets import FRAMING_PROMPT, SYSTEM_PROMPT
@@ -78,23 +79,23 @@ async def ask(self, question: str, port: int, tools: list[str]) -> str:
7879
if self.log_level == "DEBUG":
7980
print(f"Connecting Copilot CLI to MCP server on port {port}")
8081

81-
copilot_config = {
82+
mcp_config = {
8283
"mcpServers": {
8384
"UDB_Server": {"type": "sse", "url": f"http://localhost:{port}/sse", "tools": ["*"]}
8485
}
8586
}
8687

88+
# We always need to re-create the MCP config as the dynamically allocated port may change
89+
# between invocations of the tool.
90+
mcp_config_fd, mcp_config_path = mkstemp(prefix="mcp_config", suffix=".json")
91+
with contextlib.closing(os.fdopen(mcp_config_fd, "w")) as mcp_config_file:
92+
json.dump(mcp_config, mcp_config_file)
93+
8794
# We run Copilot CLI with a temporary "home directory" so that we can apply a temporary MCP
8895
# configuration and rely on "--resume" finding our previous session automatically.
8996
if not self._tempdir:
9097
self._tempdir = mkdtemp(prefix="udb_explain_copilot_home")
9198

92-
# We always need to re-create the MCP config as the dynamically allocated port may change
93-
# between invocations of the tool.
94-
config_dir = self._tempdir / ".copilot"
95-
config_dir.mkdir(exist_ok=True)
96-
(config_dir / "mcp-config.json").write_text(json.dumps(copilot_config) + "\n")
97-
9899
result = ""
99100
copilot = None
100101

@@ -105,15 +106,12 @@ async def ask(self, question: str, port: int, tools: list[str]) -> str:
105106
prompt = question
106107

107108
allowed_tools = ["UDB_Server", "shell(grep)", "shell(find)", "shell(cat)", "shell(xargs)"]
108-
env = {
109-
**os.environ,
110-
"XDG_CONFIG_HOME": str(self._tempdir),
111-
"XDG_STATE_HOME": str(self._tempdir),
112-
}
113109

114110
try:
115111
copilot = await asyncio.create_subprocess_exec(
116112
str(self.agent_bin),
113+
"--additional-mcp-config",
114+
f"@{mcp_config_path}",
117115
# We can resume unambiguously without specifying a session ID because we're using a
118116
# temporary home directory for the state generated in this session.
119117
*(["--resume"] if self._resume else []),
@@ -125,7 +123,11 @@ async def ask(self, question: str, port: int, tools: list[str]) -> str:
125123
"claude-sonnet-4.5",
126124
"-p",
127125
prompt,
128-
env=env,
126+
env={
127+
**os.environ,
128+
# Ensure state files are stored in our temporary home, so that resuming works.
129+
"XDG_STATE_HOME": str(self._tempdir),
130+
},
129131
stdin=asyncio.subprocess.PIPE,
130132
stdout=asyncio.subprocess.PIPE,
131133
stderr=asyncio.subprocess.PIPE,

0 commit comments

Comments
 (0)