feat: add MCP server for AI agent access to pull requests and issues by lmilbaum · Pull Request #9071 · microsoft/vscode-pull-request-github · GitHub
Skip to content

feat: add MCP server for AI agent access to pull requests and issues - #9071

Open
Liora Milbaum (lmilbaum) wants to merge 1 commit into
microsoft:mainfrom
lmilbaum:feat/mcp-server
Open

Liora Milbaum (lmilbaum) wants to merge 1 commit into
microsoft:mainfrom
lmilbaum:feat/mcp-server

Conversation

@lmilbaum

@lmilbaum Liora Milbaum (lmilbaum) commented Oct 16, 2026 •

Copy link
Copy Markdown

Summary

Implements #9070.

  • Adds a localhost Streamable-HTTP MCP server that bridges 9 existing vscode.LanguageModelTool instances to any MCP-capable agent running in the integrated terminal (Claude Code, Copilot, etc.)
  • Server binds to 127.0.0.1 with a per-session 256-bit bearer token, exposed as $GITHUB_PR_MCP_URL and $GITHUB_PR_MCP_TOKEN in integrated terminals — agents spawned there connect automatically
  • Also registers via vscode.lm.registerMcpServerDefinitionProvider so VS Code's own MCP client (Copilot Agent) discovers the server automatically — no manual config needed inside the editor
  • Write tools (create_pull_request, resolve_review_thread) show an explicit Allow / Allow for session / Deny modal before executing
  • New command Copy MCP Server Configuration (pr.copyMcpServerConfig) copies a ready-to-paste JSON snippet plus a claude mcp add one-liner for external agents
  • Gated behind githubPullRequests.experimental.mcpServer (default false, application scope); a no-op browser stub keeps the web build clean

Approach: bridge over existing LM tools

src/lm/tools/ already has 9 vscode.LanguageModelTool classes. This PR adds a thin MCP adapter layer on top — descriptions and schemas come from contributes.languageModelTools in package.json (one source of truth). No new GitHub API calls.

Tools exposed (MVP)

MCP tool Kind
get_active_pull_request read
get_viewed_pull_request read
get_issue_or_pull_request read
get_pull_request_status_checks read
search_issues_and_pull_requests read
list_labels read
get_notification read
create_pull_request write (confirmed)
resolve_review_thread write (confirmed)

Security

  • Bound to 127.0.0.1 only; never externally reachable
  • 256-bit per-session bearer token, compared with crypto.timingSafeEqual
  • Host-header DNS-rebinding check + Origin-header check
  • 4 MB body size limit
  • Application-scope setting prevents a repo's .vscode/settings.json from enabling the server

How to test

External agent (Claude Code CLI)

  1. Open Extension Development Host with "githubPullRequests.experimental.mcpServer": true and a trusted workspace.
  2. Open a new integrated terminal; echo $GITHUB_PR_MCP_URL prints http://127.0.0.1:<port>/mcp.
  3. curl -s -X POST "$GITHUB_PR_MCP_URL" -H "Content-Type: application/json" -H "Authorization: Bearer $GITHUB_PR_MCP_TOKEN" -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' returns 9 tools.
  4. Without the Authorization header: 401. Bad Origin: 403.
  5. claude mcp add --transport http github-pr "$GITHUB_PR_MCP_URL" --header "Authorization: Bearer $GITHUB_PR_MCP_TOKEN", then /mcp shows 9 tools.
  6. Ask Claude Code "What needs to be done for this pull request?" — it calls get_active_pull_request and surfaces review threads and CI status.

Copilot Agent (VS Code-internal)

  1. With the server running, Copilot Agent should discover the server automatically via mcpServerDefinitionProviders — no manual config required.

Lifecycle

  1. Toggle the setting off; new terminals no longer have the env vars and VS Code unregisters the provider.

Open questions

  • @modelcontextprotocol/server vs hand-rolled? Currently hand-rolled to avoid touching package-lock.json. Happy to switch if preferred.
  • Stable port/token? Regenerated on each window reload. Persist token in context.secrets and/or add a port setting?
  • Tool duplication in Copilot Chat? With mcpServerDefinitionProviders wired up, Copilot Chat may show each tool twice (once as a native LM tool, once over MCP). Worth filtering one set out, or is the duplication acceptable?

Closes #9070

🤖 Generated with Claude Code

- Register a localhost Streamable-HTTP MCP server bridging 9 existing
  LanguageModelTool instances to external MCP-capable agents
- Server binds to 127.0.0.1 with a per-session bearer token; exposed as
  $GITHUB_PR_MCP_URL and $GITHUB_PR_MCP_TOKEN in integrated terminals
- Write tools show a Allow/Allow-for-session/Deny confirmation modal
- New command pr.copyMcpServerConfig copies a ready-to-paste config snippet
- Gated behind githubPullRequests.experimental.mcpServer (default off)
- Browser stub keeps web bundle clean

Closes microsoft#9070

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@lmilbaum
Liora Milbaum (lmilbaum) marked this pull request as ready for review October 16, 2026 07:30
Copilot AI balanced review requested due to automatic review settings October 16, 2026 07:30

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Unresolved write safeguards, lifecycle races, validation gaps, and browser test failures block approval.

8 open findings
What changed in this PR

Adds an opt-in localhost MCP server for #9070, exposing existing pull request and issue tools to external agents and VS Code’s MCP client.

Changes:

  • Bridges nine language-model tools with authenticated HTTP access and write confirmations.
  • Adds server lifecycle management, discovery, terminal credentials, and configuration copying.
  • Adds browser fallback support and MCP tests.
File Description
webpack.config.js Aliases the browser server stub.
src/​test/​lm/​mcpServer.test.ts Tests catalog, protocol, bridge, and HTTP behavior.
src/​lm/​tools/​tools.ts Extracts reusable tool creation.
src/​lm/​mcp/​mcpToolCatalog.ts Maps existing tools to MCP definitions.
src/​lm/​mcp/​mcpToolBridge.ts Adapts invocation and write confirmations.
src/​lm/​mcp/​mcpServerManager.ts Manages lifecycle, discovery, and configuration copying.
src/​lm/​mcp/​mcpProtocol.ts Handles MCP protocol requests.
src/​extension.ts Initializes the server manager.
src/​env/​node/​mcpHttpServer.ts Provides authenticated localhost HTTP transport.
src/​env/​browser/​mcpHttpServer.ts Provides a no-op browser implementation.
src/​common/​settingKeys.ts Adds the experimental setting key.
package.nls.json Adds setting and command text.
package.json Declares the setting, command, and provider.

🧠 Review effort: Balanced


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

});
});

describe('MCP HTTP server', () => {
let size = 0;
req.on('data', (chunk: Buffer) => {
size += chunk.length;
if (size > MAX_BODY_BYTES) { resolve(null); req.destroy(); }
Comment thread src/lm/mcp/mcpProtocol.ts
const protocolVersion = typeof requested === 'string' && SUPPORTED_PROTOCOL_VERSIONS.includes(requested)
? requested
: SUPPORTED_PROTOCOL_VERSIONS[0];
this.clientName = clientNameOf(p['clientInfo']);
const version: string = this.context.extension.packageJSON.version ?? '0.0.0';
const handler = new McpProtocolHandler(bridge, version);

this.serverHandle = await startMcpHttpServer(handler);
Comment on lines +129 to +130
await vscode.env.clipboard.writeText(`${config}\n\n# Claude Code:\n# ${claudeCmd}`);
vscode.window.showInformationMessage('MCP server configuration copied to clipboard.');
Comment on lines +51 to +55
const required = def.inputSchema.required ?? [];
const missing = required.filter(key => input[key] === undefined);
if (missing.length > 0) {
return toolError(`Missing required arguments: ${missing.join(', ')}`);
}

try {
// toolInvocationToken is intentionally undefined: no VS Code language model session exists in the MCP path
const result = await tool.invoke({ input, toolInvocationToken: undefined }, token);
private async confirmationMessage(tool: vscode.LanguageModelTool<unknown>, input: Record<string, unknown>, fallback: string, token: vscode.CancellationToken): Promise<string> {
try {
const prepared = await tool.prepareInvocation?.({ input }, token);
return prepared?.confirmationMessages?.title ?? fallback;
@alexr00

Copy link
Copy Markdown
Member

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature request: expose an MCP server so AI agents can interact with PRs and issues

3 participants