Developing a Servlet
If you want to start with some working servlets, see our official servlet repo: dylibso/mcp.run-servlets
Register the Servlet
Before you create and publish a servlet, you must register it at https://mcp.run/publish
- Provide a descriptive name, so that the servlet is recognizable.
Note: this name is used during the Publish step below. It MUST match.
- Add a description, which will help make your servlet discoverable via search. This supports Markdown.
- Add requirements if your servlet needs configuration or access to resources
- Register config variables if your servlet needs configuration
- Register domain permissions if your servlet needs to make a request
- Register filesystem permissions if your servlet needs to read or write to disk
Note: When another user installs your servlet, they will be asked to fill in their own values for your defined configuration. The values they add will only be available to their installation of the servlet.
Configuration values are encrypted at rest in mcp.run, however they are delivered to installed servlets unencrypted in plaintext. Please be aware of which client you're accessing these values from, as untrusted devices may be able to inspect them.
Generate the Servlet Project
Generate a servlet
starter project:
-
After you register the servlet, you will get instructions like the ones below.
-
The
mcp.run
system uses the XTP Platform to manage code. Install thextp
CLI to begin.
# Install the CLI
curl https://static.dylibso.com/cli/install.sh -s | bash
# Generate a new servlet project
xtp plugin init \
--extension-point ext_01je4jj1tteaktf0zd0anm8854 \
--feature stub-with-code-samples \
--path <YOUR-NAME-HERE> \
--name <YOUR-NAME-HERE>
- Select your preferred language when prompted.
- Navigate to your project directory and create your servlet by implementing the functions in the main file:
- TypeScript
src/main.ts
- Go
main.go
- Rust
src/lib.rs
- Python
plugin/plugin.py
- C#
Program.cs
- Zig
src/main.zig
- C++
impl.cpp
You will see two functions you need to implement: a call
and describe
function.
What's in a servlet?
Currently, there are two functions that make up a servlet:
call(CallToolRequest) -> CallToolResult
describe() -> ListToolsResult
The following sections cover what these are. Refer to the repository linked above to see examples of them.
Call
This function is the logic executed when Claude Desktop or another MCP Client
actually uses your servlet. It is passed input data based on the inputSchema
you declare in the "describe" function.
Describe
This function is only called by the mcp.run
service to store your servlet
description, and by the mcpx
MCP Server a user/agent uses to install the
servlet.
The servlet description is an array of JSON objects with name
(string),
description
(string), and inputSchema
JSON Schema object.
You can implement any number of tools within your servlet, and encourage you to bundle multiple related synergetic tools within the same servelet. Doing so will give clients more understanding of when to call your servlet, will make your servlet more discoverable, and it will reduce the amount of disk and memory resources consumed by each servlet.
{
"tools": [
{
"name": "analyze_csv",
"description": "Analyze a CSV file",
"inputSchema": {
"type": "object",
"properties": {
"filepath": { "type": "string" },
"operations": {
"type": "array",
"items": {
"enum": ["sum", "average", "count"]
}
}
},
"required": ["filepath", "operations"]
}
}
]
}
In the describe()
function, our helper libraries expect you to return a
well-typed version of this JSON object, so you will notice that the function
signature in your generated code (from the step above) will look like:
/**
* Called by mcpx to understand how and why to use this tool.
* Note: Your servlet configs will not be set when this function is called,
* so do not rely on config in this function
*
* @returns {ListToolsResult} The tools' descriptions, supporting multiple tools from a single servlet.
*/
export function describeImpl(): ListToolsResult {
// TODO: fill out your implementation here
throw new Error("Function not implemented.");
}
Compiling and Testing
Regardless of language, servlets are compiled to WebAssembly using the Extism framework.
Compiling to WebAssembly means there are some features of your language or dependencies that will not work. While this is a challenge during development, it is a massive benefit to the security and portability of your servlet. If you're overwhelmed or stuck, come join our Discord and we'll personally help you out!
Run xtp plugin build
to compile your servlet. Depending on your language,
should see something like this:
$ xtp plugin build
🔨 Building dist/plugin.wasm
Running build script: sh prepare.sh && npm run build
> [email protected] build
> npx tsc --noEmit && node esbuild.js && extism-js dist/index.js -i src/index.d.ts -o dist/plugin.wasm
✅ Successfully built dist/plugin.wasm
If all is well, your servlet will compile into a .wasm
module. This is the
language agnostic code / artifact representing your servlet.
You can invoke call
or describe
on this module to test it out using
xtp plugin call [path-to-wasm-module] [func-name] [flags]
:
xtp plugin call dist/plugin.wasm call --input='{
"params": {
"name": "greet",
"arguments": {
"name": "benjamin"
}
}
}' --wasi
This should return some output that conforms to the CallToolResult
object:
{
"content": [
{
"type": "text",
"text": "Hello benjamin!!!"
}
]
}
Note: There are some more flags you might need if you want to pass configs
or give the servlet permissions. Run xtp plugin call --help
for all the
options. Here are some common ones:
xtp plugin call dist/plugin.wasm call --input='{
"params": {
"name": "greet",
"arguments": {
"name": "benjamin"
}
}
}' --wasi \
--allow-host api.google.com \
--allow-path "/Users/ben:/home/ben" \
--config "MY_API_KEY=abcd1234" \
--log-level info
Library Support
Each language comes with a standard library of helpers from Extism. These are called "PDKs". Remember, because it's a WebAssembly sandbox, some things you take for granted (like HTTP calls or environment variables) might not work the same way as they do in a runtime like Node.js or Go.
See the README of your chosen language's PDK for more info:
Publishing
Once you've registered, built, and tested your servlet. It's time to publish. In
the root directory of your servlet project (where you should have an xtp.toml
file), you can now run:
xtp plugin push
This will compile your code and push it to mcp.run. Please take note of any errors reported, which may indicate that you need additional servlets/compilers/libraries installed for the compilation to work.
After the code is compiled, xtp plugin push
will upload your servlet and make
it available via mcp.run
.
You MUST use the same name
as you used when registering the servlet initially.
To check, see the xtp.toml
file generated inside your project directory and
see that the name
field matches the one you used to register with mcp.run
.
In Early Access we expect you to have questions and run into some rough edges. Please reach out via any support channel you'd like and get some help!