Python SDK for building MCP tools that compile to WebAssembly
Project description
ftl-sdk (Python)
Python SDK for building Model Context Protocol (MCP) tools that compile to WebAssembly.
Installation
Latest Stable Version
pip install ftl-sdk
Specific Version
pip install ftl-sdk==0.1.0
Development Version
pip install git+https://github.com/fastertools/ftl-cli.git#subdirectory=sdk/python
From TestPyPI (Pre-releases)
pip install --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple/ ftl-sdk
Overview
This SDK provides:
- Decorator-based API for easy tool creation
- Automatic JSON Schema generation from type hints
- Support for both sync and async functions
- Automatic return value conversion to MCP format
- Zero-dependency implementation (only requires
spin-sdk) - Full compatibility with Spin WebAssembly components
- Seamless deployment to Fermyon Cloud
Requirements
- Python 3.10 or later
componentize-pyfor building WebAssembly componentsspin-sdkfor Spin runtime integration
Quick Start
1. Create a new Python tool
from ftl_sdk import FTL
# Create FTL application instance
ftl = FTL()
@ftl.tool
def echo(message: str) -> str:
"""Echo back the input."""
return f"Echo: {message}"
# Create the Spin handler
Handler = ftl.create_handler()
3. Build and Deploy
# Create virtual environment
python3 -m venv venv
source venv/bin/activate
# Install dependencies
pip install componentize-py spin-sdk ftl-sdk
# Build
ftl build
# Deploy to Fermyon Cloud
ftl deploy
API Reference
Decorator-based API
FTL Class
ftl = FTL()
Creates an FTL application instance for registering tools.
@ftl.tool Decorator
@ftl.tool
def my_tool(param: str) -> str:
"""Tool description."""
return result
# With custom name and annotations
@ftl.tool(name="custom_name", annotations={"priority": "high"})
def another_tool(data: dict) -> dict:
return {"processed": data}
The decorator:
- Automatically generates JSON Schema from type hints
- Extracts docstring as tool description
- Handles both sync and async functions
- Validates output against return type annotation
ftl.create_handler()
Creates a Spin HTTP handler that:
- Returns tool metadata on GET / requests
- Routes to specific tools on POST /{tool_name} requests
- Handles async/await for async tool functions
- Automatically converts return values to MCP format
ToolResponse Helper Methods
# Simple text response
ToolResponse.text("Hello, world!")
# Error response
ToolResponse.error("Something went wrong")
# Response with structured content
ToolResponse.with_structured("Operation complete", {"result": 42})
ToolContent Helper Methods
# Text content
ToolContent.text("Some text", {"priority": 0.8})
# Image content
ToolContent.image(base64_data, "image/png")
# Audio content
ToolContent.audio(base64_data, "audio/wav")
# Resource reference
ToolContent.resource({"uri": "file:///example.txt"})
Type Guards
# Check content types
if is_text_content(content):
print(content["text"])
Examples
Basic Tools
from ftl_sdk import FTL
ftl = FTL()
@ftl.tool
def echo(message: str) -> str:
"""Echo the input."""
return f"Echo: {message}"
@ftl.tool
def reverse_text(text: str) -> str:
"""Reverse the input text."""
return text[::-1]
@ftl.tool
def word_count(text: str) -> dict:
"""Count words in text."""
count = len(text.split())
return {"text": text, "word_count": count}
Handler = ftl.create_handler()
Async Tools
import asyncio
from ftl_sdk import FTL
ftl = FTL()
@ftl.tool
async def fetch_data(url: str) -> dict:
"""Fetch data from URL asynchronously."""
# Simulate async HTTP request
await asyncio.sleep(0.1)
return {
"url": url,
"status": "success",
"data": {"example": "data"}
}
@ftl.tool
async def process_items(items: list[str]) -> dict:
"""Process items with async operations."""
results = []
for item in items:
# Simulate async processing
await asyncio.sleep(0.01)
results.append(item.upper())
return {
"original": items,
"processed": results,
"count": len(results)
}
# Mix sync and async tools
@ftl.tool
def sync_add(a: int, b: int) -> int:
"""Add two numbers synchronously."""
return a + b
Handler = ftl.create_handler()
Error Handling
from ftl_sdk import FTL
ftl = FTL()
@ftl.tool
def divide(a: float, b: float) -> float:
"""Divide two numbers."""
if b == 0:
raise ValueError("Cannot divide by zero")
return a / b
# Async error handling
@ftl.tool
async def validate_data(data: dict) -> dict:
"""Validate data asynchronously."""
if "required_field" not in data:
raise KeyError("Missing required field: required_field")
# Simulate async validation
await asyncio.sleep(0.05)
if not isinstance(data["required_field"], str):
raise TypeError("required_field must be a string")
return {"status": "valid", "data": data}
Handler = ftl.create_handler()
The FTL framework automatically catches exceptions and returns them as error responses.
Development
Setting Up Development Environment
-
Clone the repository:
git clone https://github.com/fastertools/ftl-cli.git cd ftl-cli/sdk/python
-
Create virtual environment:
python -m venv venv source venv/bin/activate # On Windows: venv\Scripts\activate
-
Install development dependencies:
make install-dev # or manually: pip install -e ".[dev]" pip install componentize-py
Running Tests
# Run all tests
make test
# Run tests with coverage
make test-cov
# Run specific test
pytest tests/test_ftl_sdk.py::test_tool_response_text
Code Quality
# Format code
make format
# Run linting
make lint
# Type checking
make type-check
# Run all quality checks
make quality
Available Make Commands
make help # Show all available commands
make install # Install SDK
make install-dev # Install with dev dependencies
make format # Format code with black
make lint # Run linting with ruff
make type-check # Run type checking with mypy
make test # Run tests
make test-cov # Run tests with coverage
make clean # Clean build artifacts
make build # Build distribution packages
make publish # Publish to PyPI
Building to WebAssembly
Tools must be compiled to WebAssembly to run on Spin:
-
Install dependencies:
pip install componentize-py spin-sdk ftl-sdk
-
Build with componentize-py:
componentize-py -w spin-http componentize app -o app.wasm
-
Or use FTL CLIs build command:
ftl build
Best Practices
Type Hints
Always use type hints for better code clarity and IDE support:
from typing import Dict, Any
from ftl_sdk import ToolResponse
def my_handler(input_data: Dict[str, Any]) -> Dict[str, Any]:
message: str = input_data.get("message", "")
return ToolResponse.text(f"Received: {message}")
Error Handling
Handle errors gracefully and return informative error messages:
def safe_handler(input_data: Dict[str, Any]) -> Dict[str, Any]:
try:
# Validate required fields
if "required_field" not in input_data:
return ToolResponse.error("Missing required field: required_field")
# Process input
result = process_data(input_data["required_field"])
return ToolResponse.text(f"Success: {result}")
except ValueError as e:
return ToolResponse.error(f"Invalid value: {e}")
except Exception as e:
return ToolResponse.error(f"Unexpected error: {e}")
Testing Your Tools
Write comprehensive tests for your tools:
import pytest
from your_module import your_handler
def test_handler_success():
result = your_handler({"message": "test"})
assert result["content"][0]["text"] == "Expected output"
def test_handler_missing_field():
result = your_handler({})
assert result.get("isError") is True
assert "Missing required field" in result["content"][0]["text"]
Important Notes
-
Python Version: Requires Python 3.10 or later. Python 3.11+ recommended.
-
Zero Dependencies: This SDK has no external dependencies beyond
spin-sdk, keeping the WASM bundle size minimal. -
Input Validation: The FTL gateway handles input validation against your JSON Schema. Your handler can assume inputs are valid.
-
Virtual Environments: Always use a virtual environment to ensure consistent builds.
-
WASM Size: Python WASM components are larger than TypeScript/Rust equivalents (~37MB), but this is acceptable for cloud deployment.
-
Type Safety: Use type hints and mypy for better code quality and fewer runtime errors.
-
Code Quality: The SDK includes development tools (black, ruff, mypy, pytest) to maintain high code quality standards.
Deployment
Deploy to FTL:
# Deploy with auto-generated name
ftl deploy
Development
Contributions are welcome! Please feel free to submit a Pull Request.
Setting Up Development Environment
# Clone the repository
git clone https://github.com/fastertools/ftl-cli.git
cd ftl-cli/sdk/python
# Create virtual environment
python -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activate
# Install development dependencies
pip install -e ".[dev]"
pip install tox
Running Tests
# Run tests for all Python versions
tox
# Run tests for specific Python version
tox -e py311
# Run linting and type checking
tox -e lint,type
# Run tests with coverage
tox -e py311 -- --cov-report=html
Code Quality
# Format code
black src tests
# Run linter
ruff check src tests
# Type checking
mypy src
Changelog
See CHANGELOG.md for a list of changes in each release.
License
Apache-2.0 - see LICENSE for details.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file ftl_sdk-0.1.0.tar.gz.
File metadata
- Download URL: ftl_sdk-0.1.0.tar.gz
- Upload date:
- Size: 22.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.12.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
beec6090e1cf5940be808ef8552458a679a17beb692f783977adbbec59c799a4
|
|
| MD5 |
870d4d59e1951157d0202f472959f135
|
|
| BLAKE2b-256 |
a938de3cf931134c01d5dc4ff34890503faf2ad3d9c393a3fb82453dceca2f99
|
Provenance
The following attestation bundles were made for ftl_sdk-0.1.0.tar.gz:
Publisher:
release-sdk-python.yml on fastertools/ftl-cli
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ftl_sdk-0.1.0.tar.gz -
Subject digest:
beec6090e1cf5940be808ef8552458a679a17beb692f783977adbbec59c799a4 - Sigstore transparency entry: 347064023
- Sigstore integration time:
-
Permalink:
fastertools/ftl-cli@4d35a82d4b7762ad2fa36ab26f5fb1ff1612409c -
Branch / Tag:
refs/tags/sdk-python-v0.1.0 - Owner: https://github.com/fastertools
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release-sdk-python.yml@4d35a82d4b7762ad2fa36ab26f5fb1ff1612409c -
Trigger Event:
push
-
Statement type:
File details
Details for the file ftl_sdk-0.1.0-py3-none-any.whl.
File metadata
- Download URL: ftl_sdk-0.1.0-py3-none-any.whl
- Upload date:
- Size: 14.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.12.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
849d020e988542b17dc4007a83b3a491cc497d8b7b33ea0b0feeb97aff959670
|
|
| MD5 |
30623a6745e1218ad3608c2023075e51
|
|
| BLAKE2b-256 |
73ffdd6383c48ab71f043b3084c03eab6d8d8086183f59ea32e0d440b50782d8
|
Provenance
The following attestation bundles were made for ftl_sdk-0.1.0-py3-none-any.whl:
Publisher:
release-sdk-python.yml on fastertools/ftl-cli
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ftl_sdk-0.1.0-py3-none-any.whl -
Subject digest:
849d020e988542b17dc4007a83b3a491cc497d8b7b33ea0b0feeb97aff959670 - Sigstore transparency entry: 347064028
- Sigstore integration time:
-
Permalink:
fastertools/ftl-cli@4d35a82d4b7762ad2fa36ab26f5fb1ff1612409c -
Branch / Tag:
refs/tags/sdk-python-v0.1.0 - Owner: https://github.com/fastertools
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release-sdk-python.yml@4d35a82d4b7762ad2fa36ab26f5fb1ff1612409c -
Trigger Event:
push
-
Statement type: