2026-08-20 18:45:39 +07:00
2024-12-09 20:48:45 +07:00
2026-08-20 18:45:39 +07:00
2026-08-16 17:24:29 +07:00
2026-07-14 18:08:36 +07:00
2026-07-14 18:08:36 +07:00
2026-08-20 09:58:55 +07:00
2025-05-01 08:04:01 +07:00
2026-07-16 22:31:40 +07:00
2026-07-04 13:23:46 +07:00
2026-08-20 09:58:55 +07:00
2026-08-20 09:58:55 +07:00
2026-08-15 16:50:28 +07:00
2026-08-20 18:45:39 +07:00
2026-08-15 16:50:28 +07:00

YiemAgent

Julia framework for building agents with tool use and MCP (Model Context Protocol) support.

Getting Started

  1. Install dependencies: ]add JSON, DataStructures, UUIDs, Dates, NATS, DataFrames
  2. Create a callable mcpServer struct that communicates with an MCP server via NATS
  3. Create a yiemAgent with an LLM callable and MCP server:
    agent = YiemAgent.yiemAgent(llmCall; mcpServer=mcpServer, agentEventSink=yourSink)
    
  4. Call runAgent(agent, message) then takeResponse(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:

  1. listTool is the only pre-registered tool
  2. When the LLM calls listTools(), tools are discovered from the MCP server and registered at runtime
  3. Supports pagination via nextCursor for 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

  1. yiemAgent() spawns a background task (_agentLoop) listening on inputChannel and followUpChannel
  2. User messages enter via runAgent() or followUp()
  3. _processMessage() handles LLM calls, tool execution, and conversation history
  4. Tool execution follows three phases: prepareToolCallexecutePreparedToolCallfinalizeExecutedToolCall
  5. beforeToolCall/afterToolCall hooks allow pre/post-processing of tool calls
  6. Tool execute functions can set terminate=true to 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
S
Description
No description provided
Readme 18 MiB
v0.7.2 Latest
2026-07-17 05:26:11 +00:00
Languages
Julia 75.7%
Python 24.3%