scriptling.runtime.mcp
The scriptling.runtime.mcp sub-library provides decorator-based registration for MCP (Model Context Protocol) tools. It is the recommended way to define tools when using Scriptling as an MCP server.
Instead of maintaining separate .toml metadata and .py script files, you define the tool’s description, parameters, and implementation in one place.
Importing
import scriptling.runtime.mcp as mcpOr via the parent library:
import scriptling.runtime as runtime
# use as runtime.mcp.tool(...)@mcp.tool() Decorator
Registers a function as an MCP tool. The function’s parameters become the tool’s input schema; the return value becomes the tool response.
Signature
@mcp.tool(description, params=None, keywords=None, discoverable=False)
def tool_name(param1, param2=default):
...Arguments
| Argument | Type | Required | Description |
|---|---|---|---|
description |
str | Yes | Tool description shown to the AI |
params |
dict | No | Parameter metadata keyed by name (see below) |
keywords |
list | No | Keywords for tool search/discovery |
discoverable |
bool | No | If True, hidden from tools/list, searchable via tool_search (default: False) |
Parameter Metadata (params)
Each key in the params dict must match a function parameter name. The value can be:
- A string — treated as the description; type is inferred from the default value or defaults to
"string". - A dict — full control over
type,description, and optionallyrequired.
@mcp.tool("Example", params={
"name": "Person's name", # shorthand: description only
"count": {"type": "int", "description": "How many times"}, # explicit type
"force": {"type": "boolean", "description": "Force mode", "required": True},
})
def example(name, count=1, force=False):
...Parameter Resolution Rules
| Source | Determines |
|---|---|
| Function signature | Parameter names, required (no default → required) |
params dict value (str) |
Description; type inferred or defaults to "string" |
params dict value (dict) |
type, description, optional required override |
| Default value type | Inferred type when not explicit: int → "integer", float → "number", bool → "boolean" |
A params key that doesn’t match any function parameter produces a registration error at startup.
Parameter Types
| Type | Aliases | Description |
|---|---|---|
string |
str |
Text values |
integer |
int |
Whole numbers |
number |
float |
Integer or float |
boolean |
bool |
True/false values |
array:string |
Array of strings | |
array:integer |
array:int |
Array of whole numbers |
array:number |
array:float |
Array of numbers |
array:boolean |
array:bool |
Array of booleans |
Return Value Semantics
The function’s return value is mapped to the MCP tool response:
| Return Type | MCP Response |
|---|---|
str |
Text content |
dict or list |
JSON content |
None (or no return) |
Empty text content |
| Exception raised | Error response |
Complete Example
import scriptling.runtime.mcp as mcp
@mcp.tool(
"Calculate a mathematical expression",
params={"expr": "Expression to evaluate (e.g. 2+3*4)"},
)
def calc(expr):
allowed = "0123456789+-*/.() "
if not all(c in allowed for c in expr):
raise ValueError("invalid characters in expression")
result = eval(expr)
return f"{expr} = {result}"Multiple Tools per File
A single .py file can define multiple tools:
import scriptling.runtime.mcp as mcp
@mcp.tool("Encode text to base64", params={"text": "Text to encode"})
def encode_base64(text):
import base64
return base64.b64encode(text.encode()).decode()
@mcp.tool("Decode base64 to text", params={"data": "Base64 string to decode"})
def decode_base64(data):
import base64
return base64.b64decode(data).decode()Both tools are discovered and registered when the file is scanned.
File Layout
Place decorated tool files in the tools/ directory without a .toml sibling:
tools/
├── calc.py # Decorated tool (no .toml needed)
├── multi.py # Multiple tools in one file
├── greet.toml # Legacy format (still supported)
└── greet.py # Legacy format scriptThe server auto-detects the format:
.pywith a sibling.toml→ legacy format (usestool.get_*/tool.return_*).pywithout a sibling.toml→ decorator format (scanned for@mcp.tool)
Both formats work in the same folder and can coexist indefinitely.
Comparison with Legacy Format
| Aspect | Decorator (@mcp.tool) |
Legacy (.toml + .py) |
|---|---|---|
| Files per tool | 1 | 2 |
| Parameter source | Function signature + decorator | .toml [[parameters]] section |
| Implementation style | def fn(params) -> result |
Top-level script with tool.get_*/tool.return_* |
| Type safety | Params cross-checked at startup | Drift possible between .toml and .py |
| Multiple tools/file | Yes | No (one pair per tool) |
| Discovery | Requires evaluating the file | Static TOML parse |
Migration from Legacy Format
Converting a .toml + .py tool:
Before (greet.toml + greet.py):
# greet.toml
description = "Greet a person"
[[parameters]]
name = "name"
type = "string"
description = "Person name"
required = true
[[parameters]]
name = "times"
type = "int"
description = "Repeat count"# greet.py
import scriptling.mcp.tool as tool
name = tool.get_string("name", "World")
times = tool.get_int("times", 1)
greetings = "\n".join(f"Hello, {name}!" for _ in range(times))
tool.return_string(greetings)After (single greet.py, delete greet.toml):
import scriptling.runtime.mcp as mcp
@mcp.tool("Greet a person", params={
"name": "Person name",
"times": {"type": "int", "description": "Repeat count"},
})
def greet(name, times=1):
return "\n".join(f"Hello, {name}!" for _ in range(times))Usage in App Bundles
Decorated tools work in app bundles exactly the same way — place them in the bundle’s tools/ directory:
myapp/
├── manifest.toml # serve = ["mcp"]
├── setup.py
├── tools/
│ └── calc.py # @mcp.tool decorated
└── lib/
└── ...See Also
- Writing MCP Tools (legacy format) — The
.toml+.pyformat reference - Decorators — General decorator syntax and patterns
- MCP Server Mode — Running Scriptling as an MCP server
- App Bundles — Packaging tools, HTTP routes, and assets together