- Explore MCP Servers
- lua-resty-mcp
Lua Resty Mcp
What is Lua Resty Mcp
lua-resty-mcp is a Model Context Protocol SDK implemented in Lua for OpenResty, designed to facilitate communication with MCP servers.
Use cases
Use cases include creating interactive applications that leverage prompts and resources from MCP servers, subscribing to resource updates, and utilizing tools for various tasks such as data processing and command execution.
How to use
To use lua-resty-mcp, you need to import the module, create an MCP client session with the desired transport, initialize the session, and then interact with the MCP server using high-level client APIs to list prompts, resources, and tools.
Key features
Key features include support for stdio transport, lifecycle management, prompts, resources, tools, roots, and sampling. Some utilities like pagination are also included, although others are still in development.
Where to use
lua-resty-mcp can be used in web applications that require interaction with Model Context Protocol servers, particularly in environments powered by OpenResty.
Clients Supporting MCP
The following are the main client software that supports the Model Context Protocol. Click the link to visit the official website for more information.
Overview
What is Lua Resty Mcp
lua-resty-mcp is a Model Context Protocol SDK implemented in Lua for OpenResty, designed to facilitate communication with MCP servers.
Use cases
Use cases include creating interactive applications that leverage prompts and resources from MCP servers, subscribing to resource updates, and utilizing tools for various tasks such as data processing and command execution.
How to use
To use lua-resty-mcp, you need to import the module, create an MCP client session with the desired transport, initialize the session, and then interact with the MCP server using high-level client APIs to list prompts, resources, and tools.
Key features
Key features include support for stdio transport, lifecycle management, prompts, resources, tools, roots, and sampling. Some utilities like pagination are also included, although others are still in development.
Where to use
lua-resty-mcp can be used in web applications that require interaction with Model Context Protocol servers, particularly in environments powered by OpenResty.
Clients Supporting MCP
The following are the main client software that supports the Model Context Protocol. Click the link to visit the official website for more information.
Content
lua-resty-mcp
Model Context Protocol (MCP) SDK implemented in Lua for OpenResty.
Status
Production ready.
Table of Contents
- Features
- Quickstart
- Server APIs
- Writing MCP Clients
- Client APIs
- mcp.client
- client:initialize
- client:wait_background_tasks
- client:shutdown
- client:expose_roots
- client:list_prompts
- client:get_prompt
- client:list_resources
- client:list_resource_templates
- client:read_resource
- client:subscribe_resource
- client:unsubscribe_resource
- client:list_tools
- client:call_tool
- client:prompt_complete
- client:resource_complete
- client:set_log_level
- client:ping
- Known Issues
- License
Features
- [x] Transports
- [x] stdio
- [x] Streamable HTTP
- [x] WebSocket (non-specification)
- [x] Protocols
- [x] Lifecycle
- [x] Prompts
- [x] Resources
- [x] Tools
- [x] Roots
- [x] Sampling
- [x] Utilities
- [x] Pagination
- [x] Progress
- [x] Cancellation
- [x] Completion
- [x] Logging
- [x] Ping
Quickstart
A simple server demonstrating prompts, resources, tools:
-- Import lua-resty-mcp module
local mcp = require("resty.mcp")
-- Create an MCP server session with stdio transport
local server = assert(mcp.server(mcp.transport.stdio, {}))
-- Register a prompt
assert(server:register(mcp.prompt("echo", function(args)
return "Please process this message: "..args.message
end, "Create an echo prompt", {message = {required = true}})))
-- Register a resource template
assert(server:register(mcp.resource_template("echo://{message}", "echo", function(uri, vars)
return true, "Resource echo: "..ngx.unescape_uri(vars.message)
end, "Echo a message as a resource", "text/plain")))
-- Register a tool
assert(server:register(mcp.tool("echo", function(args)
return "Tool echo: "..args.message
end, "Echo a message as a tool", {
type = "object",
properties = {
message = {type = "string"}
},
required = {"message"}
})))
-- Launch the server session
server:run()
Server APIs
mcp.server
syntax: server, err = mcp.server(transport[, options])
Create an MCP server session with the specific transport (currently only mcp.transport.stdio is available here) and options.
A successful call returns an MCP server session. Otherwise, it returns nil and a string describing the error.
Available transports:
mcp.transport.stdiomcp.transport.websocket
Available options:
{
-- Common options
name = "lua-resty-mcp", -- Name of this session (optional)
version = "1.0", -- Version of this session (optional)
-- Options for WebSocket connections (optional)
websocket_opts = {
...
},
-- You can also put your other options here and access them via `options` field of the session instance
}
[!NOTE]
- Available options for WebSocket connections can be viewed here;
- WebSocket transport for the server should only be used in
content_by_lua*directives.
A simple echo demo server configuration that uses WebSocket transport:
worker_processes auto;
events {
}
http {
server {
listen 80;
location = /mcp {
content_by_lua_block {
local mcp = require("resty.mcp")
local server = assert(mcp.server(mcp.transport.websocket))
assert(server:register(mcp.tool("echo", function(args)
return "Tool echo: "..args.message
end, "Echo a message as a tool", {
type = "object",
properties = {
message = {type = "string"}
},
required = {"message"}
})))
server:run()
}
}
}
}
mcp.transport.streamable_http.endpoint
syntax: mcp.transport.streamable_http.endpoint(custom_fn[, options])
Create an MCP Streamable HTTP endpoint.
[!NOTE]
This method should only be called fromcontent_by_lua*directives.
The 1st argument of this method custom_fn, will be called when the clients initiate the initialization phase; the reference to resty.mcp module and the instance of the server session will be passed into this callback. You should configure the contexts for the server and launch the server session in this callback. It could be defined as follows:
function custom_fn(mcp, server)
assert(server:register(mcp.tool("echo", function(args)
return "Tool echo: "..args.message
end, "Echo a message as a tool", {
type = "object",
properties = {
message = {type = "string"}
},
required = {"message"}
})))
server:run()
end
The optional 2nd argument of this method options, should be a dict-like Lua table that contains the configuration options of the endpoint and server session. It includes the following optional fields:
{
-- Configure the message bus of this endpoint
message_bus = {
-- Type of the message bus, available types: "builtin", "redis"
-- It's implemented using the shared memory zone of OpenResty
type = "builtin",
mark_ttl = 10, -- TTL of the session mark (seconds)
cache_ttl = 90, -- TTL of the cached messages (seconds)
-- Options for "builtin" message bus
shm_zone = "mcp_message_bus", -- name of the shared memory zone
spin_opts = {
-- Spin waiting arguments for "builtin" message bus
step = 0.001,
ratio = 2,
max_step = 0.5
},
-- Options for "redis" message bus
redis = {
host = "127.0.0.1", -- Host of the Redis server
port = 6379, -- Port of the Redis server
password = "foobared", -- Password of the Redis server
db = 0, -- Index of the Redis logical database
-- Options for Redis connections
options = {
...
}
},
-- The maximum number of events that can be cached for replay
event_capacity = 1024
},
-- Whether to enable the resumability and redelivery mechanism
enable_resumability = false,
-- Longest standby in seconds, default: 600
-- If no request is received from the client for longer than this duration, the session will be terminated
-- If long inactivity is desired, please send a ping request periodically to avoid the auto-termination
longest_standby = 600,
-- Read timeout threshold, in seconds, default: 10
-- Best to be much smaller than longest_standby, and NOT exceed it
read_timeout = 10,
-- Common options are the same as `mcp.server` API
...
}
[!NOTE]
Available options for Redis connections can be viewed here.
[!TIP]
- It is recommended to use different shared memory zones or Redis logical databases for different endpoints;
- Hard-coding the Redis password is not secure, it is recommended to pass it through environment variables.
A simple echo demo server configuration that uses Streamable HTTP transport:
worker_processes auto;
events {
}
http {
lua_shared_dict mcp_message_bus 64m;
server {
listen 80;
location = /mcp {
content_by_lua_block {
require("resty.mcp").transport.streamable_http.endpoint(function(mcp, server)
assert(server:register(mcp.tool("echo", function(args)
return "Tool echo: "..args.message
end, "Echo a message as a tool", {
type = "object",
properties = {
message = {type = "string"}
},
required = {"message"}
})))
server:run()
end)
}
}
}
}
[!IMPORTANT]
Endpoint callbacks use a different request context than outside the callback, so DO NOT access variables outside the callback through upvalues of the closure, which will result in undefined behavior.
server:run
syntax: server:run([options])
Launch the server session.
Available options:
{
-- Configure server capabilities, which are enabled by default
-- Explicitly set the field to `false` to disable the corresponding capability
-- (optional)
capabilities = {
completions = {},
logging = {},
prompts = {
listChanged = true
},
resources = {
subscribe = true,
listChanged = true
},
tools = {
listChanged = true
}
},
-- Configure the page size of the corresponding list, 0 disables pagination
-- The default value for these fields is 0
-- (optional)
pagination = {
prompts = 0,
resources = 0,
tools = 0
},
-- Instructions describing how to use the server and its features (optional)
instructions = "Hello, MCP!",
-- Configure the event handlers of the server session (optional)
event_handlers = {
initialized = function(params, ctx)
-- Will be called after `initialized` notification (optional)
local current_session = ctx.session
-- Interact with the current session or other services
end,
["roots/list_changed"] = function(params, ctx)
-- Will be called after `roots/list_changed` notification (optional)
local current_session = ctx.session
-- Interact with the current session or other services
end,
["resources/subscribe"] = function(params, ctx)
-- Will be called after successfully subscribing to a resource (optional)
local current_session = ctx.session
-- Interact with the current session or other services
end,
["resources/unsubscribe"] = function(params, ctx)
-- Will be called after successfully unsubscribing to a resource (optional)
local current_session = ctx.session
-- Interact with the current session or other services
end
}
}
[!NOTE]
Before launching a server session, maybe you should register some context components.
server:register
syntax: ok, err = server:register(component)
Register a context component to the server session.
A successful call returns true. Otherwise, it returns nil and a string describing the error.
Available context components:
mcp.prompt
syntax: component = mcp.prompt(name, callback[, desc[, args]])
syntax: component = mcp.prompt(name, callback[, desc[, args]]):complete(callbacks)
Create a prompt or prompt template.
callback will be called when a client requests to get this prompt, and it could be defined as follows:
function callback(args, ctx)
local meta_field = ctx._meta -- `_meta` field of current request
local current_session = ctx.session
-- Interact with the current session or other services
local ok, err = ctx.push_progress(0.1, 1, "getting prompt")
-- The 3 arguments stand for "progress", "total", and "message"
-- "progress" is required and the other 2 are optional
if not ok then
if err == "cancelled" then
-- Or you can also use `ctx.cancelled()` to check whether the current request is cancelled
return
end
error(err)
end
-- Continue interacting with the current session or other services
if error_occurred then
return nil, "an error occured"
end
return "content of this prompt" or {
{role = "user", content = {type = "text", text = "text content"}},
{role = "assistant", content = {type = "image", data = "...", mimeType = "image/jpeg"}},
...
}
end
The 4th argument of this method args, is used to declare the expected arguments that will be passed into callback. It should be a table and could be defined as follows:
{
arg_name = {
description = "What is this argument.",
required = true
},
...
}
The argument of complete method, callbacks should be a dict-like Lua table that contains the completion callbacks of corresponding prompt arguments. The keys of the items in this table should match the argument names declared in args. This table and the callbacks within it could be defined as follows:
{
arg_name = function(value)
-- Query the suggested values based on `value`
return values, total, has_more -- All of these are optional
end,
...
}
mcp.resource
syntax: component = mcp.resource(uri, name, callback[, desc[, mime[, annos[, size]]]])
Create a resource.
callback will be called when a client requests to read this resource, and it could be defined as follows:
function callback(uri, ctx)
local meta_field = ctx._meta -- `_meta` field of current request
local current_session = ctx.session
-- Interact with the current session or other services
local ok, err = ctx.push_progress(0.1, 1, "reading resource")
-- The 3 arguments stand for "progress", "total", and "message"
-- "progress" is required and the other 2 are optional
if not ok then
if err == "cancelled" then
-- Or you can also use `ctx.cancelled()` to check whether the current request is cancelled
return
end
error(err)
end
-- Continue interacting with the current session or other services
if error_occurred then
return nil, "an error occured"
end
return "content of this resource" or {
{text = "content of "..uri},
{uri = uri.."/bin", blob = "SGVsbG8sIHdvcmxkIQ==", mimeType = "application/octet-stream"},
...
}
end
The 6th argument of this method annos, is optional annotations for the client. It should be a table and could be defined as follows:
{
-- Describes who the intended customer of this object or data is
audience = {"user", "assistant"},
-- Describes how important this data is for operating the server
-- The value will be clipped to the range [0, 1]
priority = 0.42
}
mcp.resource_template
syntax: component = mcp.resource_template(pattern, name, callback[, desc[, mime[, annos]]])
syntax: component = mcp.resource_template(pattern, name, callback[, desc[, mime[, annos]]]):complete(callbacks)
Create a resource template.
callback will be called when a client requests to read a resource that matches this template, and it could be defined as follows:
function callback(uri, vars, ctx)
-- `vars` is a table that holds variables extracted from the URI according to the template pattern
local meta_field = ctx._meta -- `_meta` field of current request
local current_session = ctx.session
-- Interact with the current session or other services
local ok, err = ctx.push_progress(0.1, 1, "reading resource")
-- The 3 arguments stand for "progress", "total", and "message"
-- "progress" is required and the other 2 are optional
if not ok then
if err == "cancelled" then
-- Or you can also use `ctx.cancelled()` to check whether the current request is cancelled
return
end
error(err)
end
-- Continue interacting with the current session or other services
if resource_not_found then
return false
end
if error_occurred then
return true, nil, "an error occured"
end
return true, "content of this resource" or {
{text = string.format("content of %s, foo=%s", uri, vars.foo)},
{uri = uri.."/bin", blob = "SGVsbG8sIHdvcmxkIQ==", mimeType = "application/octet-stream"},
...
}
end
The argument annos is the same as in mcp.resource.
The argument of complete method, callbacks should be a dict-like Lua table that contains the completion callbacks of corresponding resource template arguments. The keys of the items in this table should match the argument names defined in the URI pattern of this resource template. The structure of the table and the definition of the callbacks in it are the same as in mcp.prompt.
mcp.tool
syntax: component = mcp.tool(name, callback[, desc[, input_schema[, annos]]])
Create a tool.
callback will be called when a client requests to call this tool, and it could be defined as follows:
function callback(args, ctx)
local meta_field = ctx._meta -- `_meta` field of current request
local current_session = ctx.session
-- Interact with the current session or other services
local ok, err = ctx.push_progress(0.1, 1, "calling tool")
-- The 3 arguments stand for "progress", "total", and "message"
-- "progress" is required and the other 2 are optional
if not ok then
if err == "cancelled" then
-- Or you can also use `ctx.cancelled()` to check whether the current request is cancelled
return
end
error(err)
end
-- Continue interacting with the current session or other services
if error_occurred then
return nil, "an error occured" or {
-- multi-content error information
{type = "text", text = "an error occured"},
{type = "audio", data = "...", mimeType = "audio/mpeg"},
...
}
end
return "result of this tool calling" or {
{type = "text", text = "result of this tool calling"},
{type = "image", data = "...", mimeType = "image/jpeg"},
...
}
end
The 4th argument input_schema, is a JSON Schema object defining the expected arguments for the tool.
The 5th argument annos, is optional additional tool information. It should be a table and could be defined as follows:
{
-- A human-readable title for the tool
title = "Foobar",
-- If true, the tool does not modify its environment
-- Default: false
readOnlyHint = false,
-- If true, the tool may perform destructive updates to its environment
-- If false, the tool performs only additive updates
-- This property is meaningful only when `readOnlyHint == false`
-- Default: true
destructiveHint = true,
-- If true, calling the tool repeatedly with the same arguments will have no additional effect on the its environment
-- This property is meaningful only when `readOnlyHint == false`
-- Default: false
idempotentHint = false,
-- If true, this tool may interact with an "open world" of external entities
-- If false, the tool's domain of interaction is closed
-- Default: true
openWorldHint = true
}
[!NOTE]
All of the above properties are hints. They are not guaranteed to provide a faithful description of tool behavior (including descriptive properties liketitle).
[!IMPORTANT]
- When you need to call server methods from callbacks (event handlers and context component callbacks), you MUST call them via the
ctx.sessionfield instead of via the server instance outside of the callback using closure upvalues. Becausectx.sessionis a wrapper of the server instance that contains the context required by the backend components, calling the server methods via the server instance outside of the callback will result in undefined behavior.- The fields in
ctxargument of callbacks (event handlers and context component callbacks) are ONLY available before the callback returns; accessing them after the callback returns results in undefined behavior.
server:unregister_*
syntax: ok, err = server:unregister_prompt(name)
syntax: ok, err = server:unregister_resource(uri)
syntax: ok, err = server:unregister_resource_template(pattern)
syntax: ok, err = server:unregister_tool(name)
Unregister the corresponding component.
A successful call returns true. Otherwise, it returns nil and a string describing the error.
server:resource_updated
syntax: ok, err = server:resource_updated(uri)
Trigger the resource updated event.
A successful call returns true. Otherwise, it returns nil and a string describing the error.
server:replace_*
syntax: ok, err = server:replace_prompts(prompts)
syntax: ok, err = server:replace_resources(resources, templates)
syntax: ok, err = server:replace_tools(tools)
Replace the corresponding components.
A successful call returns true. Otherwise, it returns nil and a string describing the error.
server:list_roots
syntax: roots, err, rpc_err = server:list_roots([timeout])
Request a list of root URIs from the client.
A successful call returns an array-like Lua table that contains the roots. Otherwise, it returns nil and a string describing the error, along with an additional RPC error object if the error originated from the peer responding to the RPC request.
The returned roots may have the following structure:
{
{uri = "file:///path/to/project", name = "Project"},
{uri = "file:///path/to/foobar"},
...
}
server:create_message
syntax: res, err, rpc_err = server:create_message(messages, max_tokens[, options[, timeout[, progress_cb]]])
Request to sample an LLM via the client.
A successful call returns a dict-like Lua table that contains the sampled message from the client. Otherwise, it returns nil and a string describing the error, along with an additional RPC error object if the error originated from the peer responding to the RPC request.
The 1st argument of this method messages, should be an array-like Lua table that contains a list of structured conversation messages. It could be defined as follows:
{
{
role = "user" or "assistant",
content = {type = "text", text = "text message"}
},
{
role = "user" or "assistant",
content = {type = "image" or "audio", data = "...", mimeType = "..."}
},
...
}
The 3rd argument of this method options, should be a dict-like Lua table that contains the additional sampling options. It includes the following optional fields:
{
-- Preferences for which model to select
modelPreferences = {
-- Hints to use for model selection
hints = {
{name = "gemma"},
{name = "llama"},
...
},
-- How much to prioritize cost when selecting a model
-- 0 means cost, 1 means cost is the most important factor
costPriority = 0.5,
-- How much to prioritize sampling speed (latency) when selecting a model
-- 0 means speed is not important, 1 means speed is the most important factor
speedPriority = 0.5,
-- How much to prioritize intelligence and capabilities when selecting a model
-- 0 means intelligence is not important, 1 means intelligence is the most important factor
intelligencePriority = 0.5
},
-- System prompt you wants to use for sampling
systemPrompt = "You are a helpful assistant."
-- A request to include context from one or more MCP servers (including the caller), to be attached to the prompt
includeContext = "none" or "thisServer" or "allServers",
temperature = 0.4,
stopSequences = {"foo", "bar", ...},
metadata = {...}
}
[!NOTE]
All of the above properties are hints.
The 5th argument of this method progress_cb, is the callback to receive the progress of this request. It could be defined as follows:
function progress_cb(progress, total, message)
-- If you want to cancel this request, return a conditional false value and an optional string describing the reason
-- Otherwise, return `true` to continue with the request
end
The returned message of this method is similar to the list elements passed in the messages argument, but has an additional model field containing the name of the model that generated the message, and an optional stopReason field containing the reason why sampling stopped, if known.
server:log
syntax: ok, err = server:log(level, data[, logger])
Send a log message to the client.
A successful call returns true. Otherwise, it returns nil and a string describing the error.
Available log levels: "debug", "info", "notice", "warning", "error", "critical", "alert", "emergency".
The 2nd argument data could be any JSON serializable type, and the optional logger should be the name of the logger issuing this message.
server:ping
syntax: ok, err, rpc_err = server:ping([timeout])
Send a ping request to the client.
A successful call returns a conditional true value. Otherwise, it returns nil and a string describing the error, along with an additional RPC error object if the error originated from the peer responding to the RPC request.
server:wait_background_tasks
syntax: ok, err = server:wait_background_tasks([timeout])
Wait on the background tasks of the server session.
A successful call returns true. Otherwise, it returns nil and a string describing the error.
server:shutdown
syntax: server:shutdown()
Shutdown the server session.
Writing MCP Clients
The following code demonstrates how to use the high-level client APIs to interact with an MCP server:
-- Import lua-resty-mcp module
local mcp = require("resty.mcp")
-- Create an MCP client session with stdio transport
local client, err = mcp.client(mcp.transport.stdio, {
command = {"npx", "-y", "@modelcontextprotocol/server-everything"}
})
-- Initialize this session with roots (optional) and sampling callback (optional)
assert(client:initialize({
roots = {
{path = "/path/to/project", name = "Project"} -- Expose a directory named `Project` to server
},
sampling_callback = function(params)
-- Sampling callback
return "Mock sampling text."
end
}))
-- Discover available prompts
local prompts = assert(client:list_prompts())
-- Get a specific prompt
local res = assert(client:get_prompt("complex_prompt", {temperature = "0.4", style = "json"}))
-- Discover available resources
local resources = assert(client:list_resources())
-- Read a specific resource
local res = assert(client:read_resource("test://static/resource/1"))
-- Subscribe to a specific resource
assert(client:subscribe_resource("test://static/resource/42", function(uri)
-- Resource updated callback
end))
-- Unsubscribe from a specific resource
assert(client:unsubscribe_resource("test://static/resource/42"))
-- Discover available tools
local tools = assert(client:list_tools())
-- Call a specific tool
local res = assert(client:call_tool("echo", {message = "Hello, world!"}))
-- Shutdown this client session
client:shutdown()
Client APIs
mcp.client
syntax: client, err = mcp.client(transport, options)
Create an MCP client session with the specific transport and options.
A successful call returns an MCP client session. Otherwise, it returns nil and a string describing the error.
Available transports:
mcp.transport.stdiomcp.transport.streamable_httpmcp.transport.websocket
Available options:
{
-- Common options
name = "lua-resty-mcp", -- Name of this session (optional)
version = "1.0", -- Version of this session (optional)
-- Options for stdio transport
-- Command and arguments for starting server (required)
command = "npx -y @modelcontextprotocol/server-everything" or {"npx", "-y", "@modelcontextprotocol/server-everything"},
-- Options for pipe connected to server (optional)
pipe_opts = {
-- Buffer size used by reading operations, in bytes
-- Default: 4096
buffer_size = 4096,
-- Environment variables for server
environ = {"PATH=/tmp/bin", "CWD=/tmp/work"},
-- Write timeout threshold, in milliseconds
-- Default: 10000, 0 for never timeout
write_timeout = 10000,
-- STDOUT read timeout threshold, in milliseconds
-- Default: 10000, 0 for never timeout
stdout_read_timeout = 10000,
-- Wait timeout threshold, in milliseconds
-- Default: 10000, 0 for never timeout
wait_timeout = 10000
},
-- Options for Streamable HTTP transport
-- URL of the MCP endpoint (required)
endpoint_url = "http://127.0.0.1/mcp",
-- Content of the HTTP Authorization header (optional)
endpoint_auth = "Bearer TOKEN",
-- Read timeout threshold, in seconds
-- Default: 10
read_timeout = 10,
-- Whether to enable the standalone GET SSE stream
-- Default: false
enable_get_sse = false,
-- Options for HTTP connections
http_opts = {
...
},
-- Options for WebSocket transport
-- URL of the WebSocket MCP endpoint (required)
endpoint_url = "ws://127.0.0.1/mcp",
-- Options for WebSocket connections
websocket_opts = {
...
},
-- You can also put your other options here and access them via `options` field of the session instance
}
[!NOTE]
- Available options for HTTP connections can be viewed here. Note that
scheme,host, andportwill be parsed according toendpoint_urlautomatically, so setting them inhttp_optswill be ignored;- Available options for WebSocket connections can be viewed here, it combines the available options for
client:newandclient:connectmethods. Note that the subprotocol will always be set to"mcp", so settingprotocolsinwebsocket_optswill be ignored.
client:initialize
syntax: ok, err, rpc_err = client:initialize([options[, timeout]])
Initialize the client session.
A successful call returns true. Otherwise, it returns nil and a string describing the error, along with an additional RPC error object if the error originated from the peer responding to the RPC request.
Available options:
{
-- List of directories that will be exposed to the server
roots = {
{path = "/path/to/first", name = "First Directory"},
{path = "/path/to/second"},
...
},
-- This callback will be called when the MCP server requests sampling LLM
sampling_callback = function(params, ctx)
local current_session = ctx.session
-- Interact with the current session or other services
local ok, err = ctx.push_progress(0.1, 1, "sampling")
-- The 3 arguments stand for "progress", "total", and "message"
-- "progress" is required and the other 2 are optional
if not ok then
if err == "cancelled" then
-- Or you can also use `ctx.cancelled()` to check whether the current request is cancelled
return
end
error(err)
end
-- Continue interacting with the current session or other services
if error_occurred then
return nil, -1, "an error occured", opt_extra_err_info
end
return "Mock sampling text." or {
role = "assistant",
content = {
type = "text",
text = "Mock sampling text."
},
model = "gemma3-4b",
stopReason = "endTurn"
}
end,
event_handlers = {
["prompts/list_changed"] = function(params, ctx)
-- Will be called after `prompts/list_changed` notification (optional)
local current_session = ctx.session
-- Interact with the current session or other services
end,
["resources/list_changed"] = function(params, ctx)
-- Will be called after `resources/list_changed` notification (optional)
local current_session = ctx.session
-- Interact with the current session or other services
end,
["tools/list_changed"] = function(params, ctx)
-- Will be called after `tools/list_changed` notification (optional)
local current_session = ctx.session
-- Interact with the current session or other services
end,
message = function(params, ctx)
-- Will be called when a log message notification is received (optional)
local current_session = ctx.session
-- Interact with the current session or other services
end
}
}
The sampling callback’s argument params may have the following structure:
{
messages = {
{
role = "user",
content = {type = "text", text = "What is the capital of France?"}
},
...
},
modelPreferences = {
hints = {
{name = "claude-3-sonnet"},
...
},
intelligencePriority = 0.8,
speedPriority = 0.5
},
systemPrompt = "You are a helpful assistant.",
maxTokens = 100
}
client:wait_background_tasks
syntax: ok, err = client:wait_background_tasks([timeout])
Wait on the background tasks of the client session.
A successful call returns true. Otherwise, it returns nil and a string describing the error.
client:shutdown
syntax: client:shutdown()
Shutdown the client session.
client:expose_roots
syntax: ok, err = client:expose_roots(roots)
Expose a new set of directories to the server.
A successful call returns true. Otherwise, it returns nil and a string describing the error.
The argument roots is the same as in client:initialize, pass nil or {} to expose no directories to the server.
[!NOTE]
Calling this method will triggernotifications/roots/list_changed.
client:list_prompts
syntax: prompts, err, rpc_err = client:list_prompts([timeout])
List the available prompts of the MCP server.
A successful call returns an array-like Lua table that contains the prompts. Otherwise, it returns nil and a string describing the error, along with an additional RPC error object if the error originated from the peer responding to the RPC request.
The returned prompts may have the following structure:
{
{
name = "code_review",
description = "Asks the LLM to analyze code quality and suggest improvements",
arguments = {
{
name = "code",
description = "The code to review",
required = true
},
...
}
},
...
}
client:get_prompt
syntax: res, err, rpc_err = client:get_prompt(name, args[, timeout[, progress_cb]])
Get a specific prompt from the MCP server.
A successful call returns a dict-like Lua table that contains the content of the prompt. Otherwise, it returns nil and a string describing the error, along with an additional RPC error object if the error originated from the peer responding to the RPC request.
The 4th argument of this method progress_cb, is the callback to receive the progress of this request. It could be defined as follows:
function progress_cb(progress, total, message)
-- If you want to cancel this request, return a conditional false value and an optional string describing the reason
-- Otherwise, return `true` to continue with the request
end
The content of the prompt may have the following structure:
{
description = "Code review prompt",
messages = {
{
role = "user",
content = {
type = "text",
text = "Please review this Python code:\ndef hello():\n print('world')"
}
},
...
}
}
client:list_resources
syntax: resources, err, rpc_err = client:list_resources([timeout])
List the available resources of the MCP server.
A successful call returns an array-like Lua table that contains the resources. Otherwise, it returns nil and a string describing the error, along with an additional RPC error object if the error originated from the peer responding to the RPC request.
The returned resources may have the following structure:
{
{
uri = "file:///project/src/main.rs",
name = "main.rs",
description = "Primary application entry point",
mimeType = "text/x-rust"
},
...
}
client:list_resource_templates
syntax: templates, err, rpc_err = client:list_resource_templates([timeout])
List the available resource templates of the MCP server.
A successful call returns an array-like Lua table that contains the resource templates. Otherwise, it returns nil and a string describing the error, along with an additional RPC error object if the error originated from the peer responding to the RPC request.
The returned templates may have the following structure:
{
{
uriTemplate = "file://{+path}",
name = "Project Files",
description = "Access files in the project directory",
mimeType = "application/octet-stream"
},
...
}
client:read_resource
syntax: res, err, rpc_err = client:read_resource(uri[, timeout[, progress_cb]])
Read a specific resource from the MCP server.
A successful call returns a dict-like Lua table that contains the content of the resource. Otherwise, it returns nil and a string describing the error, along with an additional RPC error object if the error originated from the peer responding to the RPC request.
The 3rd argument of this method progress_cb, is the callback to receive the progress of this request. It could be defined as follows:
function progress_cb(progress, total, message)
-- If you want to cancel this request, return a conditional false value and an optional string describing the reason
-- Otherwise, return `true` to continue with the request
end
The content of the resource may have the following structure:
{
contents = {
{
uri = "file:///project/src/main.rs",
mimeType = "text/x-rust",
text = "fn main() {\n println!(\"Hello world!\");\n}"
},
...
}
}
client:subscribe_resource
syntax: ok, err, rpc_err = client:subscribe_resource(uri, callback[, timeout])
Subscribe to a specific resource of the MCP server.
A successful call returns true. Otherwise, it returns nil and a string describing the error, along with an additional RPC error object if the error originated from the peer responding to the RPC request.
callback will be called when the MCP server triggers notifications/resources/updated, and it could be defined as follows:
function updated_callback(uri, ctx)
local current_session = ctx.session
-- Interact with the current session or other services
-- Read this resource from the MCP server to get the latest content
end
client:unsubscribe_resource
syntax: ok, err, rpc_err = client:unsubscribe_resource(uri[, timeout])
Unsubscribe from a subscribed resource of the MCP server.
A successful call returns true. Otherwise, it returns nil and a string describing the error, along with an additional RPC error object if the error originated from the peer responding to the RPC request.
client:list_tools
syntax: tools, err, rpc_err = client:list_tools([timeout])
List the available tools of the MCP server.
A successful call returns an array-like Lua table that contains the tools. Otherwise, it returns nil and a string describing the error, along with an additional RPC error object if the error originated from the peer responding to the RPC request.
The returned tools may have the following structure:
{
{
name = "get_weather",
description = "Get current weather information for a location",
inputSchema = {
type = "object",
properties = {
location = {
type = "string",
description = "City name or zip code"
}
},
required = {"location"}
}
},
...
}
client:call_tool
syntax: res, err, rpc_err = client:call_tool(name, args[, timeout[, progress_cb]])
Call a specific tool in the MCP server.
A successful call returns a dict-like Lua table that contains the result of the tool call. Otherwise, it returns nil and a string describing the error, along with an additional RPC error object if the error originated from the peer responding to the RPC request.
The 4th argument of this method progress_cb, is the callback to receive the progress of this request. It could be defined as follows:
function progress_cb(progress, total, message)
-- If you want to cancel this request, return a conditional false value and an optional string describing the reason
-- Otherwise, return `true` to continue with the request
end
The result of the tool calling may have the following structure:
{
content = {
{
type = "text",
text = "Current weather in New York:\nTemperature: 72°F\nConditions: Partly cloudy"
},
...
},
isError = false
}
client:prompt_complete
syntax: res, err, rpc_err = client:prompt_complete(name, arg_name, arg_value)
Request to complete an argument of a prompt.
A successful call returns a dict-like Lua table that contains the result of the argument completion. Otherwise, it returns nil and a string describing the error, along with an additional RPC error object if the error originated from the peer responding to the RPC request.
The result of the argument completion may have the following structure:
{
completion = {
-- Returned suggested values of this argument (max 100 items)
values = {...},
-- Number of total suggested values of this argument (optional)
total = 123,
-- Indicates whether there are more suggested values (optional)
hasMore = true
}
}
client:resource_complete
syntax: res, err, rpc_err = client:resource_complete(uri, arg_name, arg_value)
Request to complete an argument of a resource template.
A successful call returns a dict-like Lua table that contains the result of the argument completion. Otherwise, it returns nil and a string describing the error, along with an additional RPC error object if the error originated from the peer responding to the RPC request.
The 1st argument uri should be the URI pattern of the resource template, and the structure of the result is the same as in client:prompt_complete.
client:set_log_level
syntax: res, err, rpc_err = client:set_log_level(level[, timeout])
Configure the minimum log level.
A successful call returns a conditional true value. Otherwise, it returns nil and a string describing the error, along with an additional RPC error object if the error originated from the peer responding to the RPC request.
Available log levels: "debug", "info", "notice", "warning", "error", "critical", "alert", "emergency".
client:ping
syntax: ok, err, rpc_err = client:ping([timeout])
Send a ping request to the server.
A successful call returns a conditional true value. Otherwise, it returns nil and a string describing the error, along with an additional RPC error object if the error originated from the peer responding to the RPC request.
Known Issues
Cancel request on server that uses stdio transport
It is currently not possible to cancel requests on servers that use stdio transport. This is because the server module of the stdio transport is implemented using the Lua I/O library, and the APIs in it are all blocking. Therefore, it is not possible to yield the execution of the current request handler and handle the cancellation notification.
License
BSD-3-Clause license. See LICENSE for details.
Dev Tools Supporting MCP
The following are the main code editors that support the Model Context Protocol. Click the link to visit the official website for more information.










