21. MCP Server
MCP (Model Context Protocol) lets an AI host — Claude Desktop, Cursor, ChatGPT, or a MALDA MCPClient — launch your process and talk JSON-RPC 2.0 over STDIO (stdin/stdout). Decorate ordinary functions with @MCPTool, call new MCPServer().start(), and the host can list and call them. Same file: Examples/MCP/mcp_tools_server.malda.
@MCPTool plus MCPServer.start() exposes functions to a host over STDIO. Smallest file: Examples/MCP/mcp_schema_tool.malda. Course: Learn MALDA — After the core path.
21.1 A minimal server
@MCPTool("add", "Adds two numbers together")
function add(a, b) {
return int(a) + int(b);
}
@MCPTool("reverse_string", "Reverses a string")
function reverseString(text) {
var reversed = "";
var i = length(text) - 1;
while (i >= 0) {
reversed = reversed + substring(text, i, 1);
i = i - 1;
}
return reversed;
}
var server = new MCPServer();
server.start();
while (server.isRunning) {
sleep(1000);
}
Run it as a normal MALDA program. The host, not you, should start that process when it wants the tools:
malda Examples/MCP/mcp_tools_server.malda
An MCP host config (Claude Desktop claude_desktop_config.json, Cursor mcp.json) looks like this — point command at a malda executable on PATH, or a full path:
{
"mcpServers": {
"malda-tools": {
"command": "malda",
"args": ["C:/path/to/Examples/MCP/mcp_tools_server.malda"]
}
}
}
start() if a host is connected: STDIO is the protocol. Log to stderr or a file. The example server in Examples/MCP/ prints before the host handshake in a demo loop; a production server should stay quiet on stdout.
21.2 @MCPTool and schemas
schema AddArgs {
a: int;
b: int;
}
@MCPTool("add", "Adds two numbers together", "AddArgs")
function add(a, b) {
return int(a) + int(b);
}
If you omit a schema, MALDA builds JSON Schema from the parameter names: every property is type "string" and required. The optional third argument may be:
- A registered
schemaor sum-type name — a string"AddArgs"or a bare identifierAddArgs. That name is resolved the same way asvalidate("AddArgs", …). - A JSON schema object string, when you need enums, defaults, or other JSON Schema keywords the MALDA
schemasyntax does not express.
@MCPTool("get_weather", "Gets weather for a location", "{\"type\":\"object\",\"properties\":{\"location\":{\"type\":\"string\"},\"unit\":{\"type\":\"string\",\"enum\":[\"celsius\",\"fahrenheit\"]}},\"required\":[\"location\"]}")
function getWeather(location, unit) {
// ...
}
When that third argument is present, the host validates incoming arguments against the schema before the function body (same rules as validate()). server.callTool("add", { a: 1, b: 2 }) returns { ok, data } or { ok: false, error } without starting STDIO. MCP tools/call returns isError: true on failure. Omitting the schema still advertises all-string properties and does not check args. Direct calls such as add(1, 2) are unchanged.
server.getTools() returns { name, description, inputSchema } for each @MCPTool without starting STDIO — useful to inspect the resolved schema. Same file: Examples/MCP/mcp_schema_tool.malda. @Tool accepts the same third argument and host-validates tool.execute / agent invoke (see the malda new agent scaffold).
21.3 MCPServer
var server = new MCPServer(); // STDIO transport (default)
var server = new MCPServer("stdio"); // Explicit STDIO transport
start(): discover@MCPToolfunctions and serve JSON-RPCstop(): stop the servergetTools(): list{ name, description, inputSchema }(works beforestart())callTool(name, args?): invoke one tool in-process;{ ok, data }or{ ok: false, error }isRunning: loop until the host disconnects
JSON-RPC methods implemented: initialize, tools/list, tools/call, notifications/initialized.
21.4 MCPClient: call another server
MCPClient starts a child process and speaks the same STDIO protocol. Same file: Examples/MCP/mcp_client_example.malda. Run the client from the repo root so malda can find the server script.
var client = new MCPClient("example-server");
var connected = client.connect("malda", ["Examples/MCP/mcp_tools_server.malda"]);
if (!connected) {
print("Failed to connect");
return;
}
var tools = client.getTools();
print("Tool count: " + string(tools.length));
var sum = client.callTool("add", {"a": 5, "b": 3});
print(string(sum));
client.disconnect();
connect(command, args?, env?) returns true on success. callTool(name, arguments?) converts the JSON result to MALDA values. Each tool from getTools() has name, description, and schema.
var client = new MCPClient("my-server");
var connected = client.connect("python", ["-m", "mcp_server"], {"API_KEY": "secret"});
createTool(toolName, registeredName?, register?)wraps one tool as anMCPToolInstanceyou canagent.addTool(...)(see 19. Agent Orchestration)refreshTools()rediscovers tools after the server has changedserverName/isConnectedare properties
21.5 Wrapper agent (optional)
createWrapperAgent builds an Agent with every tool from the connected server already attached. If you omit llmClient, MALDA uses a local GGUF model (downloaded on first use). Prefer the callTool example above until you need an LLM in the loop.
var mcpClient = new MCPClient("example-server");
mcpClient.connect("malda", ["Examples/MCP/mcp_tools_server.malda"]);
var agent = mcpClient.createWrapperAgent(
"Helper",
"assistant",
"You use MCP tools to answer questions."
);
var response = agent.think("Add 5 and 3");
print(response.content);
mcpClient.disconnect();
21.6 Advanced: generate a script
createMcpAgentScript(agentName, agentRole, agentInstructions, tools, outputPath, model?) writes a .malda file that exposes an agent as MCP tools. That is a code generator, not a live STDIO session — prefer callTool or createWrapperAgent until you need to emit a file. Pair it with MALDACodingAgent in 19. Agent Orchestration and compileMALDA in 13. Built-in Functions only when an agent should generate and compile MALDA.
See Also
- 9. Functions - Function decorators
- 19. Agent Orchestration - Agents, tools, and
agent.think - 22. ACP (Agent Communication Protocol) - Agent-to-agent, not tools