Skip to main content

MCP — Model Context Protocol

The Model Context Protocol is a standard protocol for exposing tools to LLMs. Anything you can do from Claude Desktop or VS Code MCP — file system access, GitHub, Slack, databases — works inside a LogicGrid agent.

LogicGrid.Mcp serves two clients — stdio and HTTP — and a single adapter that wraps every server tool as a LogicGrid ToolBase.

Install

dotnet add package LogicGrid.Mcp

Use it in three steps

using LogicGrid.Core.Agents;
using LogicGrid.Core.Tools;
using LogicGrid.Mcp;

// 1. Connect to a server (stdio or http)
using var mcp = new McpStdioClient(
command: "npx",
args: new[] { "-y", "@modelcontextprotocol/server-filesystem", "/repo" });
await mcp.ConnectAsync();

// 2. Wrap the server's tools as LogicGrid tools
IList<ToolBase> tools = await McpToolAdapter.GetAllAsync(mcp);

// 3. Hand them to any agent
IAgent fileBot = new Agent<string>(
"FileBot",
"Reads and edits files in the repo.",
"Use the file tools to answer the user's question.",
llm,
tools: tools);

IMcpClient contract

public interface IMcpClient : IDisposable
{
Task ConnectAsync(CancellationToken ct = default);
Task<IList<McpToolInfo>> GetToolsAsync(CancellationToken ct = default);
Task<string> CallToolAsync(string toolName, string argsJson, CancellationToken ct = default);
McpServerInfo? ServerInfo { get; }
}

Two implementations:

ClientUse it forPage
McpStdioClientLocal processes — npx, python, any CLI MCP server.Stdio client
McpHttpClientRemote MCP services exposed over HTTP.HTTP client

McpServerInfo

Returned from IMcpClient.ServerInfo after ConnectAsync completes. Captures the handshake the server reported during MCP initialization.

public sealed class McpServerInfo
{
public string Name { get; } // server's self-reported name
public string Version { get; } // server's self-reported version
}

null until the client has connected. Useful for logging, diagnostics, or version-gating behaviour against a specific server build:

await mcp.ConnectAsync();
logger.LogInformation(
"Connected to MCP server {Name} v{Version}",
mcp.ServerInfo?.Name, mcp.ServerInfo?.Version);

McpToolInfo

Returned from IMcpClient.GetToolsAsync() — one entry per tool the server advertises. This is the raw protocol-level shape; in most code you'll let McpToolAdapter wrap these into ToolBase instances for you.

public sealed class McpToolInfo
{
public string Name { get; } // tool name passed to CallToolAsync
public string Description { get; } // shown to the LLM as tool docs
public string InputSchema { get; } // raw JSON Schema for arguments
}

Reach for GetToolsAsync directly when you want to inspect or filter on the schema before adapting — e.g. logging the available tools at startup:

foreach (var tool in await mcp.GetToolsAsync())
Console.WriteLine($"{tool.Name}: {tool.Description}");

McpToolAdapter

public static class McpToolAdapter
{
public static Task<IList<ToolBase>> GetAllAsync(
IMcpClient client, CancellationToken ct = default);
}

Calls client.GetToolsAsync(), wraps each result as a ToolBase, and returns the list. The wrapped tools forward calls back to the MCP server through client.CallToolAsync.

Filtering tools

A typical filesystem MCP server exposes ~20 tools. Most agents only need a few. Filter before passing to the agent:

IList<ToolBase> availableTools = await McpToolAdapter.GetAllAsync(mcp);

ToolBase[] readOnly = availableTools
.Where(t => t.Name is "read_file" or "list_directory")
.ToArray();

IAgent reader = new Agent<string>(
"Reader", "Reads files only.",
"Use the read tools.", llm,
tools: readOnly);

Mixing MCP tools with built-ins

The agent doesn't care where a tool came from. Mix freely:

var tools = new List<ToolBase>(await McpToolAdapter.GetAllAsync(mcp))
{
new CalculatorTool(),
new MyCustomTool(),
};

Lifecycle and disposal

Both clients are IDisposable. Use using so the stdio client kills its child process and the HTTP client closes its session. If the agent runs across many requests, dispose the client when the application shuts down — not on every request.

Where next