After a short break, I'm back with my series about the GitHub Copilot SDK. So far, we have covered:
- You don't need to build your own agent harness
- Getting started with the GitHub Copilot SDK in .NET
- Sessions in the GitHub Copilot SDK: What they are and how to manage them
- Deploying and scaling
- Authentication and observability
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