How to Extend ChocolateLMLite with External Tools Using MCP
Enable EnableMcpTools in the general settings, create an mcp.json file defining your servers, and ChocolateLMLite automatically discovers and registers those external tools via the Tools class.
ChocolateLMLite supports the Model Context Protocol (MCP) to integrate external programs and services directly into the LLM's execution context. By toggling a single configuration flag and providing a JSON server manifest, you can extend ChocolateLMLite with external tools using MCP without modifying the core C# engine. This guide explains the bootstrap flow, transport options, and tool discovery mechanism based on the source code in gpsnmeajp/chocolatelite.
Enable MCP Support in Settings
Before the runtime initializes external connections, you must activate the MCP subsystem globally. In src/Persona.cs (lines 245‑246), the framework deserializes the user-editable flag EnableMcpTools from the persona settings JSON.
When the application starts, Tools.InitToolsAsync() checks this flag before bootstrapping the MCP infrastructure:
// Tools.cs – MCP bootstrap gate
if (_fileManager.generalSettings.EnableMcpTools) // 【line 47】
{
await InitMcpToolsAsync(); // 【line 52】
}
Set EnableMcpTools to true in your settings.json or via the Settings UI to trigger the initialization sequence.
Configure the mcp.json Server Manifest
The runtime expects a server manifest named mcp.json in the working directory. The FileManager class handles persistence; if the file is missing, it writes a blank template (see src/FileManager.cs, lines 1538‑1558).
The configuration schema defines a McpServers object where each key represents a named server:
{
"McpServers": {
"my-local-tool": {
"Transport": "Stdio",
"Command": "python my_tool.py"
},
"remote-service": {
"Transport": "Http",
"Url": "http://localhost:8000/mcp"
}
}
}
- Stdio: Launches a local command and communicates over standard input/output.
- Http: Connects to a remote endpoint exposing the MCP protocol over HTTP.
Initialize MCP Clients and Transports
Inside Tools.InitMcpToolsAsync() (src/Tools.cs), the framework deserializes the JSON manifest (lines 314‑319) and iterates over each entry in McpServers (line 356). For each server, it instantiates the appropriate transport:
if (serverConfig.Transport == "Stdio")
{
clientTransport = new StdioMcpTransport(serverConfig.Command);
MyLog.LogWrite($"MCPサーバーを起動中(Stdio): {serverName} コマンド={serverConfig.Command}"); // 【line 365】
}
else if (serverConfig.Transport == "Http")
{
clientTransport = new HttpMcpTransport(serverConfig.Url);
MyLog.LogWrite($"MCPサーバーに接続中(HTTP): {serverName} URL={serverConfig.Url}"); // 【line 384】
}
An McpClient is then created asynchronously and stored for lifecycle management:
client = await McpClient.CreateAsync(clientTransport); // 【line 379】 or 【line 392】
_mcpClients.Add(client);
Discover and Register Tools
After establishing connections, InitMcpToolsAsync() queries each client for its advertised capabilities. It calls ListToolsAsync() and aggregates the results into the internal _mcpTools collection:
var mcpTools = await client.ListToolsAsync();
foreach (var tool in mcpTools)
{
lock (_mcpTools)
{
_mcpTools.Add(tool); // 【line 63】
}
}
MyLog.LogWrite($"MCPツールを追加: {string.Join(",", mcpTools.Select(t => t.Name))}"); // 【line 404】
When the LLM requests available functions, Tools.GetAvailableTools() merges native utilities with the MCP-derived tools:
// Combine native and external tools
if (_fileManager.generalSettings.EnableMcpTools)
{
tools.AddRange(_mcpTools); // 【lines 114‑115】
}
Each AITool object contains the name, description, parameter schema, and a handler that forwards execution to the remote server.
Resource Lifecycle and Cleanup
The Tools class implements IAsyncDisposable to ensure graceful shutdown. When the application exits, it disposes every McpClient instance (lines 28‑34), terminating child processes and closing HTTP connections:
public async ValueTask DisposeAsync()
{
foreach (var client in _mcpClients)
{
await client.DisposeAsync();
}
}
Complete Implementation Example
The following example demonstrates wiring a local Python calculator and a remote weather service into ChocolateLMLite.
1. Create mcp.json
Place this file next to the executable:
{
"McpServers": {
"calc": {
"Transport": "Stdio",
"Command": "python tools/calc_tool.py"
},
"weather": {
"Transport": "Http",
"Url": "http://localhost:5000/mcp"
}
}
}
2. Enable MCP in settings.json
{
"EnableMcpTools": true
}
3. Implement a Stdio MCP Server (Python)
Create tools/calc_tool.py:
import sys, json
def add(a, b):
return a + b
def handle(request):
data = json.loads(request)
if data["name"] == "add":
result = add(**data["arguments"])
return json.dumps({"result": result})
return json.dumps({"error": "unknown tool"})
for line in sys.stdin:
print(handle(line))
sys.stdout.flush()
For HTTP servers, expose a POST endpoint at /mcp returning the same JSON schema.
4. Use Tools in a Prompt
When you call the LLM, the framework automatically includes the discovered tools in the context:
var prompt = @"You have access to:
- calc.add(a:int, b:int)
- weather.get(city:string)
What is 7 + 5? Also, what is the weather in Tokyo?";
var response = await llm.ChatAsync(prompt);
The LLM selects the appropriate tool, and ChocolateLMLite routes the call to the corresponding MCP client.
Summary
- Enable the feature by setting
EnableMcpToolstotruein your settings; the flag is parsed insrc/Persona.cs(lines 245‑246) and checked insrc/Tools.cs(line 47). - Define servers in
mcp.jsonusing eitherStdiofor local commands orHttpfor remote endpoints;FileManagerhandles I/O (lines 1538‑1558). - Bootstrap clients via
Tools.InitMcpToolsAsync(), which createsStdioMcpTransportorHttpMcpTransportinstances and initializesMcpClientobjects (lines 314‑392). - Aggregate tools by calling
ListToolsAsync()on each client and merging results into_mcpTools(lines 56‑66), exposed throughGetAvailableTools()(lines 114‑115). - Cleanup resources through
IAsyncDisposabledisposal of client connections (lines 28‑34).
Frequently Asked Questions
What file format does ChocolateLMLite use for MCP configuration?
ChocolateLMLite expects a JSON file named mcp.json in the application working directory. The root object must contain a McpServers dictionary where each server specifies a Transport (Stdio or Http) and the corresponding connection details (Command or Url).
Can I use both local command-line tools and HTTP APIs simultaneously?
Yes. The Tools.InitMcpToolsAsync() method iterates over all entries in the McpServers configuration (line 356) and creates the appropriate transport for each. You can mix Stdio servers for local scripts and Http servers for remote microservices in the same mcp.json file.
Where does ChocolateLMLite store the MCP enable flag?
The boolean flag EnableMcpTools is stored in the general settings object managed by FileManager and deserialized in src/Persona.cs (lines 245‑246). The Tools class checks this flag at line 47 before initializing any MCP clients.
How does the LLM know which external tools are available?
When MCP is enabled, Tools.GetAvailableTools() (lines 114‑115) merges the native toolset with the _mcpTools collection populated during InitMcpToolsAsync(). Each discovered tool exposes its name, description, and JSON schema parameter definition, allowing the LLM to generate valid invocations that the framework routes to the correct McpClient.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →