Skip to main content

Developing a Servlet

Just looking for code examples?

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.

Register Servlet Screenshot

Secrets

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:

  1. After you register the servlet, you will get instructions like the ones below.

  2. The mcp.run system uses the XTP Platform to manage code. Install the xtp 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>
  1. Select your preferred language when prompted.
  2. 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

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.

Name Matching

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!