Skip to main content

MCP integration in the GitHub Copilot SDK

After a short break, I'm back with my series about the GitHub Copilot SDK. So far, we have covered:

Today, we cover MCP integration — connecting your agent to external context and services via the Model Context Protocol, so your agent can reach databases, APIs, and cloud services without you having to build the glue.

The agent we've built so far knows how to reason and plan, and can call C# tools you define directly. But there's a whole ecosystem of pre-built capabilities out there. Servers that talk to Azure, GitHub, databases, Slack, internal systems — all speaking the same open standard. MCP is how your agent connects to that ecosystem. Instead of writing tool integrations from scratch, you point the SDK at an MCP server and the agent gains its entire tool catalogue instantly.

What MCP actually is

Model Context Protocol is an open standard for connecting AI assistants to external tools and data sources. An MCP server is a process that exposes a set of named tools via a standardised protocol. The client, in this case the Copilot SDK, discovers what tools the server offers, and the agent can invoke them the same way it invokes any other tool: by deciding it needs one, forming a call, getting a result, and incorporating that into its reasoning.

The key point is that the agent doesn't care whether a tool is defined in your C# code or served by an external MCP process. From the agent's perspective, tools are tools. MCP just dramatically expands how many you can have without writing any integration code.

Two flavours

The SDK supports two types of MCP server connection: local servers that run as a subprocess communicating over stdio, and remote servers accessible over HTTP.

  • Local (stdio) — the SDK spawns the MCP server as a child process and communicates with it over standard input/output. This is the most common pattern for servers distributed as npm packages or executables. The server starts with your application and stops with it.
  • Remote (HTTP) — the SDK connects to an MCP server running at a URL. Useful for shared internal servers, SaaS-hosted MCP endpoints, or any server you don't want to run locally. Authentication is typically handled via headers.

Both are configured the same way in .NET: through a McpServers dictionary on SessionConfig.

Connecting a local MCP server

Here's the minimal setup to connect a local MCP server — in this case one distributed as an npm package, started with npx:

var session = await client.CreateSessionAsync(new SessionConfig
{
    Model = "gpt-4.1",
    McpServers = new Dictionary<string, McpServerConfig>
    {
        ["my-server"] = new McpStdioServerConfig
        {
            Command = "npx",
            Args = ["-y", "@my-org/my-mcp-server@latest"],
            Tools = ["*"]  // "*" = all tools, [] = none, or list specific tool names
        }
    }
});

The tools: ["*"] parameter is essential — it enables all tools from the MCP server for the session. If you want the agent to only have access to a subset of what the server exposes, list the tool names explicitly:

Tools = ["read_file", "search_files", "list_directory"]

You can also pass environment variables and set the working directory for the spawned process:

["my-server"] = new McpStdioServerConfig
{
    Command = "node",
    Args = ["./mcp-server.js"],
    Env = new Dictionary<string, string>
    {
        ["DEBUG"] = "true",
        ["DB_CONNECTION"] = Environment.GetEnvironmentVariable("DB_CONNECTION")!
    },
    WorkingDirectory = "./servers",
    Tools = ["*"],
    Timeout = 30000
}

Connecting a remote MCP server

For remote servers, provide a URL instead of a command. Authentication is passed via headers:

["github"] = new McpHttpServerConfig
{
    Url = "https://api.githubcopilot.com/mcp/",
    Headers = new Dictionary<string, string>
    {
        ["Authorization"] = $"Bearer {Environment.GetEnvironmentVariable("GITHUB_TOKEN")}"
    },
    Tools = ["*"]
}

Remote MCP servers are useful for any capability you want to centralise — a shared internal knowledge base, a company-wide data access layer, or a third-party SaaS integration that's already running at a stable URL.

Multiple MCP servers in one session

A session can have multiple MCP servers attached simultaneously. The agent picks the right tool from whichever server provides it, based on the tool descriptions:

var session = await client.CreateSessionAsync(new SessionConfig
{
    Model = "gpt-4.1",
    McpServers = new Dictionary<string, McpServerConfig>
    {
        ["azure-mcp"] = new McpStdioServerConfig
        {
            Command = "npx",
            Args = ["-y", "@azure/mcp@latest", "server", "start"],
            Tools = ["*"]
        },
        ["github"] = new McpHttpServerConfig
        {
            Url = "https://api.githubcopilot.com/mcp/",
            Headers = new Dictionary<string, string>
            {
                ["Authorization"] = $"Bearer {Environment.GetEnvironmentVariable("GITHUB_TOKEN")}"
            },
            Tools = ["*"]
        },
        ["internal-kb"] = new McpHttpServerConfig
        {
            Url = "https://mcp.internal.yourcompany.com/knowledge-base",
            Headers = new Dictionary<string, string>
            {
                ["X-API-Key"] = Environment.GetEnvironmentVariable("KB_API_KEY")!
            },
            Tools = ["search_docs", "get_runbook"]
        }
    }
});

With these three servers attached, the agent can cross-reference Azure resource state, GitHub issues, and internal runbooks in a single conversation — without you writing any routing logic between them.

Tool filtering: Granting minimal capability

One of the more useful aspects of MCP integration is fine-grained tool control. Rather than granting "*" (all tools), you can specify exactly which tools from a server the agent is allowed to call. This is the principle of least privilege applied to your agent.

// Only allow read operations from the Azure MCP server
["azure-mcp"] = new McpStdioServerConfig
{
    Command = "npx",
    Args = ["-y", "@azure/mcp@latest", "server", "start"],
    Tools =
    [
        "azure_resource_group_list",
        "azure_storage_account_list",
        "azure_subscription_list"
        // Deliberately not including write or delete operations
    ]
}

This matters in production. An agent that can only read from Azure can't accidentally delete a resource group, even if it's prompted to try. Match the tool access to what the agent's task actually requires.

What's next

You now have an agent that can reach well beyond your application boundary — into Azure, GitHub, internal systems, and any MCP-compatible service — without writing the integration code yourself.

In the next post, we take a look at Skills. Skills are about what the agent knows — and more specifically, how to apply that knowledge in a repeatable, composable way

More information

Using MCP servers with the GitHub Copilot SDK - GitHub Docs

Popular posts from this blog

Podman– Command execution failed with exit code 125

After updating WSL on one of the developer machines, Podman failed to work. When we took a look through Podman Desktop, we noticed that Podman had stopped running and returned the following error message: Error: Command execution failed with exit code 125 Here are the steps we tried to fix the issue: We started by running podman info to get some extra details on what could be wrong: >podman info OS: windows/amd64 provider: wsl version: 5.3.1 Cannot connect to Podman. Please verify your connection to the Linux system using `podman system connection list`, or try `podman machine init` and `podman machine start` to manage a new Linux VM Error: unable to connect to Podman socket: failed to connect: dial tcp 127.0.0.1:2655: connectex: No connection could be made because the target machine actively refused it. That makes sense as the podman VM was not running. Let’s check the VM: >podman machine list NAME         ...

Azure DevOps/ GitHub emoji

I’m really bad at remembering emoji’s. So here is cheat sheet with all emoji’s that can be used in tools that support the github emoji markdown markup: All credits go to rcaviers who created this list.

VS Code Planning mode

After the introduction of Plan mode in Visual Studio , it now also found its way into VS Code. Planning mode, or as I like to call it 'Hannibal mode', extends GitHub Copilot's Agent Mode capabilities to handle larger, multi-step coding tasks with a structured approach. Instead of jumping straight into code generation, Planning mode creates a detailed execution plan. If you want more details, have a look at my previous post . Putting plan mode into action VS Code takes a different approach compared to Visual Studio when using plan mode. Instead of a configuration setting that you can activate but have limited control over, planning is available as a separate chat mode/agent: I like this approach better than how Visual Studio does it as you have explicit control when plan mode is activated. Instead of immediately diving into execution, the plan agent creates a plan and asks some follow up questions: You can further edit the plan by clicking on ‘Open in Editor’: ...