Skip to main content
Focus apps give your Truffle tools it can call on demand. There are two ways to add them:

Hook an Existing MCP

Connect your Truffle to any MCP server that supports streamable-http transport — no coding required.

Steps

  1. Open Settings in the TruffleOS Desktop app
  2. Go to the MCPs section in the sidebar
  3. Click Add New MCP
  4. Fill in the server details:
Add New MCP dialog in TruffleOS
FieldDescription
Server URLThe full MCP server URL (e.g., https://mcp.exa.ai/mcp)
PortServer port (443 for HTTPS, 80 for HTTP)
PathThe MCP endpoint path (usually mcp)
DescriptionOptional description of what the MCP provides

Example: Adding Exa AI

Exa AI provides powerful web search, code search, and research tools via MCP.
FieldValue
Server URLhttps://mcp.exa.ai/mcp
Port443
Pathmcp
DescriptionConnects Truffle to Exa’s search capabilities, including web search and code search
Once added, you can ask your Truffle things like:
  • “Search for recent developments in AI agents”
  • “Find Python examples for OAuth 2.0”
  • “Research Stripe and summarize their products”
More MCP servers: Browse MCP Market or Smithery for more servers you can connect to.

Build Your Own

Want custom tools? Build and deploy your own MCP server. In this guide, we’ll build Research — a Focus app with web search and content extraction tools.

Source Code

View the complete Research app on GitHub

What We’re Building

Research is an MCP server that exposes tools your Truffle can call when you ask questions. When you ask “What’s the latest news about AI?”, your Truffle can use these tools to search the web and return results. Tools we’ll create:
  • search_web — Search the web with DuckDuckGo
  • search_news — Search for news articles
  • fetch_url_content — Extract content from any URL

What is MCP?

Model Context Protocol (MCP) is a standard for AI tools. Focus apps are MCP servers that:
  1. Run on port 8000
  2. Expose tools with descriptions
  3. Use streamable-http transport
Your Truffle discovers your tools and calls them when relevant to your questions.

Project Structure

research/
├── research.py      # MCP server with tools
├── truffile.yaml    # App configuration
└── icon.png         # App icon (optional)

Step 1: Create the Config File

Create truffile.yaml:
metadata:
  name: Research
  type: foreground
  description: |
    Tools to help your Truffle research and gather information from the web.
  process:
    cmd:
      - python
      - research.py
    working_directory: /
    environment:
      PYTHONUNBUFFERED: "1"
  icon_file: ./icon.png

files:
  - source: ./research.py
    destination: ./research.py

run: |
  pip install mcp requests ddgs

Key Differences from Ambient Apps

FieldFocus AppAmbient App
typeforegroundbackground
default_scheduleNot neededRequired
ServerRuns MCP on port 8000Runs once per schedule

Step 2: Write the MCP Server

Create research.py:
from mcp.server.fastmcp import FastMCP

HOST = "0.0.0.0"
PORT = 8000

# Create the MCP server
mcp = FastMCP("research", stateless_http=True, host=HOST, port=PORT)


@mcp.tool("search_web", description="Search the web using DuckDuckGo. Returns titles and URLs for the query.")
async def search_web(query: str, num_results: int = 5) -> str:
    """Search the web for information."""
    from ddgs import DDGS
    
    results = DDGS().text(query, max_results=num_results)
    formatted = "\n".join([
        f"{i+1}. {r['title']}: {r['href']}" 
        for i, r in enumerate(results)
    ])
    return formatted


@mcp.tool("search_news", description="Search for recent news articles. Returns headlines with sources and URLs.")
async def search_news(query: str, num_results: int = 5) -> str:
    """Search for news articles."""
    from ddgs import DDGS
    
    results = DDGS().news(query, max_results=num_results)
    formatted = "\n".join([
        f"{i+1}. [{r['source']}] {r['title']}: {r['url']}" 
        for i, r in enumerate(results)
    ])
    return formatted


@mcp.tool("fetch_url_content", description="Fetches and extracts the main text content from a URL. Useful for reading articles.")
async def fetch_url_content(url: str) -> str:
    """Extract content from a webpage."""
    import requests
    
    try:
        resp = requests.get(url, timeout=10)
        resp.raise_for_status()
        # Simple extraction - in production, use a proper extractor
        return resp.text[:5000]  # First 5000 chars
    except Exception as e:
        return f"Error fetching URL: {e}"


def main():
    print(f"Starting Research MCP server on {HOST}:{PORT}")
    mcp.run(transport="streamable-http")


if __name__ == "__main__":
    main()

Step 3: Understanding the Code

The FastMCP Pattern

Every Focus app follows this pattern:
from mcp.server.fastmcp import FastMCP

# 1. Create the server
mcp = FastMCP("app-name", stateless_http=True, host="0.0.0.0", port=8000)

# 2. Define tools with @mcp.tool()
@mcp.tool("tool_name", description="What this tool does")
async def my_tool(param: str) -> str:
    # Tool logic here
    return result

# 3. Run the server
mcp.run(transport="streamable-http")

The @mcp.tool() Decorator

This is how you expose functions to your Truffle:
@mcp.tool("tool_name", description="Clear description of what this tool does")
async def tool_name(param1: str, param2: int = 5) -> str:
    ...
Descriptions are critical! Your Truffle uses the description to decide when to call each tool. A vague description means your tool might not be used when it should be.

Writing Good Tool Descriptions

BadGood
"Searches stuff""Search the web using DuckDuckGo. Returns titles and URLs."
"Gets data""Fetches and extracts the main text content from a URL."
"News""Search for recent news articles. Returns headlines with sources."
Tips for descriptions:
  • Say what the tool does
  • Mention what it returns
  • Note any limitations (e.g., “Some sites may block scraping”)

Parameter Types

Use Python type hints — they become the tool’s input schema:
@mcp.tool("example")
async def example(
    query: str,           # Required string
    limit: int = 10,      # Optional int with default
    include_images: bool = False  # Optional bool
) -> str:
    ...

API Reference

FastMCP

Create an MCP server instance:
from mcp.server.fastmcp import FastMCP

mcp = FastMCP(
    name="my-app",           # App identifier
    stateless_http=True,     # Required for Truffle
    host="0.0.0.0",          # Listen on all interfaces
    port=8000                # Must be 8000
)
ParameterValueDescription
namestrIdentifier for your app
stateless_httpTrueRequired for Truffle compatibility
host"0.0.0.0"Listen on all network interfaces
port8000Must be 8000 for Truffle

@mcp.tool()

Register a function as a tool:
@mcp.tool(name, description)
async def tool_function(params...) -> str:
    ...
ParameterTypeDescription
namestrTool name (used by Truffle to call it)
descriptionstrImportant! Describes when to use this tool

mcp.run()

Start the MCP server:
mcp.run(transport="streamable-http")
ParameterValueDescription
transport"streamable-http"Required for Truffle

Step 4: Deploy Your App

Once your app is ready, deploy it to your Truffle:
cd research
truffile deploy
Truffile will:
  1. Validate your truffile.yaml
  2. Check Python syntax
  3. Upload files to your Truffle
  4. Run the install commands
  5. Start your MCP server

CLI Reference

See all deploy options including interactive mode

Tips

Test locally before deploying:
python research.py
Then use an MCP inspector or curl to test your tools:
curl http://localhost:8000/mcp
Use async functions — all tool functions should be async def, even if they don’t await anything.
Port 8000 is required — Your Truffle expects the MCP server on this port. Don’t change it.

Next Steps


SDK Source Code

truffile SDK

View the complete SDK source code, including the CLI, client library, and inference proxy.