代码执行MCP服务器SDK

Yith是一个基于Odin的服务器端MCP SDK,提供沙盒化Lua代码执行功能,专为LLM设计的轻量级API交互工具。

  • by rlyeh-dev
  • Odin
  • 本地部署
  • 1 星标
  • 更新于
在 GitHub 查看 rlyeh-dev/miskatonic-mcp

使用说明

Yith:代码执行MCP服务器SDK

这是一个仅限奥丁服务器的MCP SDK,其行为与中列出的传统/官方MCP-SDK不同 MCP的官方文件,支持Sandboxed Code Exec风格 Anthropic 和 云耀 2025年底。

与上述提倡LLM在Docker或云容器沙箱中运行TypeScript代码的博客文章不同,该项目选择了一种使用进程内lua沙箱的更简化的方法。您的api有luadoc注释记录,并在Odin procs中实现(参见 天气 和 天气.喧闹 从 基本示例服务器)

仅向LLM提供四种工具:

  • 评估:LLM提供了一个lua代码块,可以在lua沙箱中执行
  • 帮助:列出关于如何使用该系统的散文帮助概述。
  • 搜索:基于luadoc-api文档的内容,对沙箱中可用的所有lua-api函数进行tf-idf驱动的搜索
  • 列表:仅列出每个可用功能的名称和描述
  • 文档:在luadoc文档中查找单个api函数

此外 help, search, list,以及 docs 工具在lua中有等价物 api_help(), api_search(), api_list(),以及 api_docs() 分别。 api_docs() 和 api_help() 不返回任何结果,但会打印到lua沙盒控制台,该控制台会被捕获并作为工具响应的一部分返回给LLM。 api_search() 和 api_list() 返回结构化结果。

计划支持的唯一其他MCP-SDK功能是用户发起的提示(斜线命令),尽管目前客户端对这些功能的支持非常有限,因此这不是优先事项。

用法


import mcp "path/to/yith/mcp"
import lua "vendor:lua/5.4"

main :: proc() {
  server := mcp.make_server("CodeExecMCP", "Code Execution MCP utilizing Yith MCP Framework", "1.2.3")
  // Register the api docs for the function. 
  // do not need to store these in constants, but it's wise to do 
  // so for the name since it's replicated in both places. The 
  // api docs registry happens outside of the sandbox, before lua
  // is ever booted up, while function registration happens within 
  // the sandbox
  mcp.add_documentation(server, NAME, SIG, DESC, DOCS) 

  // Register the sandbox setup to add the function to each sandbox, 
  // as the lua environment is recreated on every evaluate call
  mcp.setup(server, proc(state: ^lua.State) {
    mcp.add_function(state, NAME, do_something)
  })
  
  // Start the MCP stdio server. no http server is provided at this time.
  mcp.start_stdio(server)
}

NAME :: "do_something"
// the signature is VERY helpful to llms to get a basic idea of the api 
// without needing to pull full docs
SIG :: `do_something({ str = "string to do something with" })`
DESC :: "it does... something"
Input :: struct { str: string }
Output :: struct { str: string }
// it is possible to write your own lua wrapper `proc "c" ()` style handlers, 
// and register them with `lua.register()`, but for simple calls and printing
// output, this style is easiest and most ergonomic.
do_something :: proc(params: Input, state: ^lua.State) -> (result: Output) {
  // this gets printed as output in the tool call response to the LLM
  mcp.lua_print(state, "do_something was called with input:", params.str)
  // printf style works too, supports all 4 of print/printf/println/printfln
  mcp.lua_printf(state, "do_something likes your input: %s,", params.str)
  
  if params.str == "BAD_INPUT" {
    // this treats the result as a fatal error, and this call will be
    // reported back as an error to the LLM. as with lua_print* the 
    // lua_eprint also supports all 4 variants
    mcp.lua_eprintln(state, "do not send me bad input :(")
    // immediately after return, before marshaling output params, the 
    // wrapper function will know that we errored because we used a
    // `mcp.lua_eprint*` call, and it will trigger a lua.error()
    return
  }
  
  if params.str == "OTHER_BAD_INPUT" {
    // can also do this, which is the same as doing
    // `lua.pushstring(state, "msg"); lua.error(state)` except in one call
    // since we're calling abort() directly, its best to return our own
    // lua function name here (normally when we use the `mcp.lua_eprint*` procs, 
    // the lua_wrapper that mcp.add_function() wraps us in will handle this,
    // but the lua.error() longjmp will bypass that, so this will let the llm 
    // know what function call errored)
    mcp.lua_abort(state, "[do_something()] your input sucks")
    // technically not needed b/c lua.error() will longjmp us out of the 
    // entire lua.L_dostring() call, but its nice to put for readability
    return 
  }
  
  // by default this proc is run within a dynamic arena allocator, so allocate 
  // whatever you want and it'll get cleaned up automatically at the end of the 
  // `evaluate` tool call, after your output has been marshaled into a lua table.
  // see examples/basic/manual.odin for a comparison of both memory management 
  // strategies as well as the Input/Output auto-marshaling to/from lua tables.
  result.str = strings.concatenate({"You said: ", params.str})
  return
}

// docs in luadoc format
DOCS: string: `
---@class DoSomethingParams
---@field str string The input string to process

---@class DoSomethingResult
---@field str string The processed output string

---Returns AND prints its input
---@param params DoSomethingParams
---@return DoSomethingResult
function do_something(params) 
  -- implemented in native code
end 
`