f62b8f14e709903085dc25755e03fd94b9b53774
YiemAgent
Julia framework for building agents with tool use and MCP (Model Context Protocol) support.
Getting Started
- Install dependencies:
]add JSON, DataStructures, UUIDs, Dates, NATS, DataFrames - Create a callable
mcpServerstruct that communicates with an MCP server via NATS - Create a
yiemAgentwith an LLM callable and MCP server:agent = YiemAgent.yiemAgent(llmCall; mcpServer=mcpServer, agentEventSink=yourSink) - Call
runAgent(agent, message)thentakeResponse(agent)
Architecture
src/
├── YiemAgent.jl # Module entry point
├── type.jl # Core types (messages, tools, agent state)
├── utils.jl # Message formatting, context preparation, validation
├── agentCore.jl # Agent loop, tool execution pipeline
├── api.jl # Public API (runAgent, takeResponse, followUp, stopAgent)
└── toolRegistry.jl # Tool store, MCP discovery, listTools registration
MCP Protocol
YiemAgent uses JSON-RPC 2.0 for MCP communication. The mcpServer callable struct must implement:
# tools/list — returns tool definitions
mcpServer("tools/list") # → Dict("jsonrpc"=>"2.0", "id"=>1, "result"=>Dict("tools"=>[...], "nextCursor"=>...))
# tools/call — executes a tool
mcpServer("tools/call", toolName, arguments) # → Dict("jsonrpc"=>"2.0", "id"=>2, "result"=>Dict("content"=>[...], "isError"=>...))
Protocol error responses include "error" instead of "result":
Dict("jsonrpc"=>"2.0", "id"=>2, "error"=>Dict("code"=>-32602, "message"=>"..."))
Implementing the MCP Server Client
You must provide a callable struct that communicates with your MCP server. Example using NATS:
struct mcpServer
natsConn::NATS.Connection
topic::String
senderID::String
fileserver_url::String
end
# tools/list implementation
function (c::mcpServer)(method::String)
if method != "tools/list"
error("mcpServer: unexpected method '$method' (expected 'tools/list')")
end
payload = Dict("jsonrpc" => "2.0", "id" => 1, "method" => method, "params" => Dict{String, Any}())
payloads = [("payload", payload, "dictionary")]
_, msg_envelope_json_str = msghandler.smartpack(
c.topic, payloads;
sender_id=c.senderID,
msg_purpose="mcp_tools_list",
fileserver_url=c.fileserver_url)
reply = NATS.request(c.natsConn, c.topic, msg_envelope_json_str, timeout=180)
incoming_env = msghandler.smartunpack(String(reply.payload))
return incoming_env["payloads"][1][2]
end
# tools/call implementation
function (c::mcpServer)(method::String, toolName::String, arguments::Dict{String, Any})
if method != "tools/call"
error("mcpServer: unexpected method '$method' (expected 'tools/call')")
end
payload = Dict(
"jsonrpc" => "2.0",
"id" => 2,
"method" => method,
"params" => Dict(
"name" => toolName,
"arguments" => arguments
)
)
payloads = [("payload", payload, "dictionary")]
_, msg_envelope_json_str = msghandler.smartpack(
c.topic, payloads;
sender_id=c.senderID,
msg_purpose="mcp_tool_call",
fileserver_url=c.fileserver_url)
reply = NATS.request(c.natsConn, c.topic, msg_envelope_json_str, timeout=180)
incoming_env = msghandler.smartunpack(String(reply.payload))
return incoming_env["payloads"][1][2]
end
See test/runtest.jl for the complete working example.
Tool Discovery
Tools are discovered dynamically via MCP server:
listToolis the only pre-registered tool- When the LLM calls
listTools(), tools are discovered from the MCP server and registered at runtime - Supports pagination via
nextCursorfor large tool sets
Agent API
| Function | Description |
|---|---|
runAgent(agent, msg) |
Send a message to the agent's input channel |
takeResponse(agent) |
Block and take the agent's response from output channel |
followUp(agent, msg) |
Send a follow-up message while agent is still processing |
stopAgent(agent) |
Gracefully stop the agent and close channels |
Agent Lifecycle
yiemAgent()spawns a background task (_agentLoop) listening oninputChannelandfollowUpChannel- User messages enter via
runAgent()orfollowUp() _processMessage()handles LLM calls, tool execution, and conversation history- Tool execution follows three phases:
prepareToolCall→executePreparedToolCall→finalizeExecutedToolCall beforeToolCall/afterToolCallhooks allow pre/post-processing of tool calls- Tool
executefunctions can setterminate=trueto stop the agent loop
Hooks
| Hook | Signature | Purpose |
|---|---|---|
prepareContext |
(state, sink, llmCall) -> ctx |
Transform messages/context before LLM call |
formatMsgForLLM |
(ctx, sink) -> dict |
Convert agent context to LLM API format |
beforeToolCall |
(context, signal) -> result |
Block/allow tool execution |
afterToolCall |
(context, signal) -> result |
Post-process tool results |
agentEventSink |
(msg) -> nothing |
Callback for agent events/debug messages |
Description