Turn Existing C++ Functions into Claude-Callable MCP Tools
Most C++ teams already have valuable business logic: file processing, schedulers, rule engines, protocol handlers, and performance-critical modules.
When LLM integration starts, many teams add Python/Node wrappers around existing C++ code. That works for demos, but it creates long-term maintenance problems:
- Duplicate implementations across languages
- Behavioral drift between wrappers and native code
- Harder performance tuning and debugging
This article shows a cleaner path: keep logic in C++, expose it as Claude-callable tools through MCP, and harden the stack for production.
The Core Idea
Do not rewrite your backend.
Build a thin runtime layer that gives you:
- Function registration
- JSON invocation
- Tool schema export
- Stateful object lifecycle (
create -> method -> destroy) - Concurrency control
- MCP transport (stdio/HTTP)
30-Second Tool Contract Example
This is more than "JSON calling a function".
It shows the three things a Claude-compatible host needs:
- A callable tool name
- A JSON argument contract
- Exportable schema metadata
#include <iostream>
#include <json_invoke/json_invoke.hpp>
int main() {
json_invoke::JsonInvokeAdapterThreadSafe tools;
tools.registerFunction(
"add",
[](int left, int right) { return left + right; },
json_invoke::FunctionMetadata{{"left", "right"}, "Add two integers."}
);
int result = tools.invoke({
{"name", "add"},
{"args", {{"left", 2}, {"right", 3}}}
});
std::cout << "result: " << result << "\n\n";
std::cout << tools.getToolSchemaJson("add").dump(2) << "\n";
}
Output excerpt:
result: 5
{
"function": {
"name": "add",
"description": "Add two integers.",
"parameters": {
"type": "object",
"properties": {
"left": { "type": "integer" },
"right": { "type": "integer" }
},
"required": ["left", "right"]
}
},
"type": "function"
}
What this proves:
- Your C++ logic is callable as a named tool
- Invocation uses stable JSON arguments (not positional ad-hoc payloads)
- Schema is exported for host-side discovery and validation
How Claude Uses Exported Schema Through MCP
After registration and schema export, the runtime and host interaction typically looks like this:
+------------------------------+ +------------------------------+ +------------------------------+
| Native C++ Tool Runtime | | MCP Server | | LLM Host/Client |
| (your existing functions) | | (stdio or HTTP transport) | | (Claude Desktop / Agent) |
+------------------------------+ +------------------------------+ +------------------------------+
| | |
|--(0) register tools + export schema --------------------->| |
|<-(0) MCP tool surface ready (name/args/schema) ----------| |
| | |
| |<--(1) initialize + list_tools ----|
| |--(2) tool list + JSON schema ---->|
| | |
|<--(4) invoke(name,args) ----------|<--(3) tool call ------------------|
|--(5) run native C++ function ---->| |
|<-(6) typed result / error --------| |
| |--(7) tool result ---------------->|
| | |
| |<--(8) optional next tool call ----|
|<--(9) optional invoke ------------| |
| |--(10) final tool-backed output -->|
The important part is Step 0: how native C++ functions become MCP tools with exported JSON schema.
Steps 1+ are mostly standard MCP host behavior.
Move Beyond Toy Examples
My goal is: help you move existing native C++ functions into real LLM tool workflows with minimal rewriting.
With this framework, you can expose legacy or production C++ code through a consistent stack that already covers:
- Stateless calls (simple request/response tools)
- Stateful calls (session handle + lifecycle)
- Scheduling and queueing (safe concurrency under load)
- Parallel execution (maximize throughput where safe)
- Task cancellation (stop unnecessary work cleanly)
Why This Matters in Practice
For teams shipping real systems, these are not optional extras.
They are the difference between a demo and an operable platform.
Instead of building one-off wrappers, you can quickly take existing C++ functions and expose them to LLMs through a structured interface.
Layered Architecture You Can Adopt Incrementally
A key strength of this project is its layered design.
You do not need to adopt everything at once.
You can use lower layers independently when you only need part of the stack, then move upward as requirements grow.
Typical path:
- Start with function registration
- Add JSON invocation and schema export
- Add session/stateful capabilities
- Add scheduling + concurrency control
- Expose via MCP transport
This keeps integration cost low while preserving an upgrade path to production-grade behavior.
Demos That Map to Real Usage
The repository includes detailed demos that show how each layer is used in practice, from minimal usage to advanced scenarios.
This helps teams answer practical questions quickly:
- How do I expose an existing function with minimal change?
- How do I model multi-step stateful workflows?
- How do I avoid race conditions with concurrent tool calls?
- How do I evolve from local JSON calls to MCP-hosted tools?
Repository
Project repository:
writePerfectCode/llm_invoke_cpp: Provide a framework enabling LLMs to invoke C++ functions through JSON-based interfaces.
Final Takeaway
If your team already has valuable C++ logic, you do not need a rewrite strategy.
You need an exposure strategy.
This framework gives you that path:
- Expose native C++ functions quickly
- Keep architecture layered and composable
- Support stateless and stateful scenarios
- Handle scheduling, concurrency, and cancellation
- Connect to LLM hosts through MCP when ready
That is how you turn existing C++ assets into reliable LLM-callable capabilities.
Top comments (0)