How to Create an MCPServer Aspect
For LLMs and Developers — This guide explains how to build a new MCP server tool by creating an “aspect” — a single .cpp file that plugs into the MCPServer framework.
What is an Aspect?
The MCPServer is a C++ implementation of the Model Context Protocol. Each aspect is a single .cpp file that defines the server’s personality: its name, tools, prompts, and handlers. Each aspect compiles into its own executable (e.g., MCPServer_helloWorld.exe, MCPServer_portScan.exe).
The framework (MCPServer.cpp) handles all JSON-RPC protocol mechanics — parsing requests, dispatching methods, validating output, stdin/stdout I/O. Your aspect only implements the what, never the how of the protocol.
Reference Templates
When building a new aspect, start by copying one of our baseline templates depending on the architecture you need:
-
Baseline Tool (
MCPServer_helloWorld.cpp): A minimal, working aspect for standard tool execution. Start here if your tool just needs to execute local C code and return text sequentially. -
Asynchronous Background Processing (
MCPServer_setReminder.cpp): Start here if your tool requires non-blocking execution (like long-running jobs or delayed events). It demonstrates how to setMCPServer_Asynchronous = true, spawn background threads, and safely send notifications back to the client long after the initial request has finished. -
Bi-directional LLM-code integration aspect (
MCPServer_wordArt.cpp): Start here if you are building tools that need to natively query the client’s AI. It demonstrates how to use the MCP Sampling flow (sampling/createMessage) to delegate tasks (like cognitive reasoning or complex formatting) back to an LLM.
Required Sections (in order)
1. Includes and Identity
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <LevelTrace.h>
#include <MEMprintf.h>
#include <MCPServerCore.h>
const char *MCPServer_Name = "MCPServer_myAspect"; // Executable identity.
const char *MCPServer_Version = "0.1.0";
const char *MCPServer_Capabilities = "\"tools\":{}"; // MCP capabilities.
bool MCPServer_Asynchronous = false; // true for async tools.
MCPServer_Name— used in trace filenames andserverInfosent to the client.MCPServer_Capabilities— typically"\"tools\":{}". Add"prompts","resources", etc. as needed.MCPServer_Asynchronous— settrueif any tool needs deferred responses (worker threads).
2. Test Cases
static const char* MCPInputTestRequest_MyCall =
"{"
" \"jsonrpc\": \"2.0\","
" \"id\": 3,"
" \"method\": \"tools/call\","
" \"params\": {"
" \"name\": \"myTool\","
" \"arguments\": { \"key\": \"value\" }"
" }"
"}";
const char* MCPInputTestRequests[] =
{
MCPInputTestRequest_MyCall,
NULL // NULL-terminated array.
};
Run tests with: MCPServer_myAspect.exe Test
Note: A successful test will output the raw JSON-RPC response directly to the console.
3. Init / Shutdown
bool MCPServer_OnInitialize(MCPInputRequest &MCPRequest)
{
// Called once when the MCP client sends "initialize".
return true;
}
bool MCPServer_OnShutdown()
{
// Called when stdin closes (client disconnect).
return true;
}
4. Tool Directory
Register tools via the MCPToolInfo array. The framework auto-generates the tools/list response from this.
const unsigned MCPToolInfoCount = 1;
MCPToolInfo_t MCPToolInfo[] =
{
{
// Name
"myTool",
// Description (one line, no newlines — token-efficient)
"Does something useful. Returns result as text.",
1, // nProperties
// InputSchema {Properties}
{
"\"key\":{\"type\":\"string\","
"\"description\":\"The input key\"}"
},
// [Required]
"\"key\""
}
};
5. Tool Handlers — The Two-Layer Pattern
Every tool follows this pattern:
MCPtool_xxx() — Parse arguments, validate, call FormMCPResponse_xxx()
FormMCPResponse_xxx() — Build result text, call FormMCPResponse_Text()
Memory Management Note: You do not need to manage the memory of the char* buffer returned by these handlers. Function handlers returning an MCPOutput JSON buffer must assume it will be automatically freed by the caller (MCPServer.cpp) after being sent.
Response builder — builds the text and wraps it:
char* FormMCPResponse_myTool(JSON_Value &idRequest, const char *Key)
{
MemoryPrintf Summary;
Summary.printf("Result for key: %s", Key);
return FormMCPResponse_Text(&idRequest, Summary.GetBuffer());
}
Tool handler — parses and validates the request:
char* MCPtool_myTool(JSON_Value &idRequest, JSON_Object &Arguments)
{
const char *ProcName = "MCPtool_myTool";
const char *Key = Arguments.Find_member_string("key");
if (!Key)
{
TERROR(("%s: Missing Argument: 'key'",ProcName));
return FormMCPResponse_ERROR(&idRequest,
MCPErrorCode_InvalidParms,
"Invalid Params",
"Missing parameter: key");
}
return FormMCPResponse_myTool(idRequest, Key);
}
6. Tool Dispatch
Route tool names to handlers:
char* Handle_tools_call(JSON_Value &idRequest,
const char *ToolName,
JSON_Object &Arguments)
{
char *MCPOutput = NULL;
if (strcmp(ToolName,"myTool") == 0)
{
MCPOutput = MCPtool_myTool(idRequest, Arguments);
}
return MCPOutput; // NULL = "unknown tool" (framework handles).
}
7. Dormant Handlers
These are required by the linker. Copy them verbatim from MCPServer_helloWorld.cpp and activate as needed:
void Handle_notification(MCPInputRequest &MCPRequest) { return; }
char* Handle_sampling_response(MCPInputRequest &MCPRequest) { return NULL; }
char* Handle_notify_action(void *UserPtr) { return NULL; }
void Discard_notify_action(void *UserPtr) { return; }
char* Handle_completion_complete(JSON_Value &idRequest, const char *RefType, const char *RefName, const char *ArgName, const char *ArgValue) { return NULL; }
const unsigned MCPPromptInfoCount = 0;
MCPPromptInfo_t MCPPromptInfo[] = { {0} };
char* Handle_prompts_get(JSON_Value &idRequest, const char *PromptName, JSON_Object &Arguments) { return NULL; }
char* Handle_method(MCPInputRequest &MCPRequest) { return NULL; }
Build System
To add a new aspect, add it to both Makefiles in the mcp/ directory:
GNU Make (Makefile) — Linux:
# Named target:
myAspect: _makedirs $(OUTDIR)/MCPServer_myAspect
# Link rule:
$(OUTDIR)/MCPServer_myAspect: $(SHARED_OBJS) $(O)/MCPServer_myAspect.o
$(CXX) $(LDFLAGS) $^ -o $@ $(LIBS)
# Compile rule:
$(O)/%.o: $(MCPSRV_DIR)/myAspect/%.cpp
$(CXX) $(CXXFLAGS) $(INC_CORE) -c $< -o $@
Build: make myAspect (or make myAspect CFG=Debug)
NMAKE (Makefile.nmake) — Windows:
# Named target:
myAspect: _makedirs $(OUTDIR)\MCPServer_myAspect.exe
# Link rule:
$(OUTDIR)\MCPServer_myAspect.exe: $(SHARED_OBJS) $(O)\MCPServer_myAspect.obj
$(LINK) $(LDFLAGS) /OUT:$@ $** $(LIBS)
# Compile rule:
{$(MCPSRV_DIR)\myAspect\}.cpp{$(O)\}.obj:
$(CC) $(CFLAGS) $(INC_CORE) /Fo$@ $<
Build: nmake myAspect (or nmake myAspect CFG=Debug)
LLM Registration
VSCode mcp.json:
{
"servers": {
"myAspect": {
"type": "stdio",
"command": "path/to/MCPServer_myAspect.exe"
}
}
}
See also: MCPServer Wiring & Transport Guide
Advanced Native Features
The core framework (MCPServer.cpp) inherently supports:
- Async Tools: Set
MCPServer_Asynchronous = trueand returnReturn_MCPOutput_Pending(). UsePostMCPOutput()orPostNotifyAction()from your worker thread. - Sampling: Send
sampling/createMessagerequests to the LLM and catch the result inHandle_sampling_response(). UseFormMCPRequest_sampling()to build a sampling request. - Prompts & Agentic Polyfill: Populate
MCPPromptInfo[]and implementHandle_prompts_get(). UseFormMCPResponse_prompt()to respond to a prompt request. By default, the framework automatically polyfills these prompts as agentic services (listPromptsandgetPrompttools), allowing LLMs to seamlessly invoke them even if the client application lacks native prompt support. This behavior can be disabled via the-DIncludePromptTools=0compiler flag.
Checklist for a New Aspect
- Copy
MCPServer_helloWorld.cpp→MCPServer_myAspect.cpp - Set Identity (
MCPServer_Name,MCPServer_Version) - Define
MCPToolInfo[] - Implement
FormMCPResponse_xxx()andMCPtool_xxx() - Wire dispatch in
Handle_tools_call() - Add test cases and verify via
MCPServer_myAspect.exe Test - Add Makefile target & LLM Registration