Skip to main content
Protocol Revision: 2025-11-25

Expose Tools as MCP Server

PraisonAI allows you to expose your Python functions as MCP (Model Context Protocol) tools that can be consumed by any MCP client, including Claude Desktop, Cursor, and other AI assistants.

Quick Start

1

Install Dependencies

pip install "praisonaiagents[mcp]"
2

Create Your Tools

Define Python functions with type hints and docstrings:
def search_web(query: str, max_results: int = 5) -> dict:
    """Search the web for information.
    
    Args:
        query: The search query string
        max_results: Maximum number of results to return
    """
    return {"results": [f"Result for {query}"]}
3

Create and Run MCP Server

from praisonaiagents.mcp import ToolsMCPServer

server = ToolsMCPServer(name="my-tools")
server.register_tool(search_web)
server.run()  # Starts stdio server

Basic Usage

Simple MCP Server

from praisonaiagents.mcp import ToolsMCPServer

# Define your tools as regular Python functions
def search_web(query: str, max_results: int = 5) -> dict:
    """Search the web for information."""
    return {
        "query": query,
        "results": [
            {"title": f"Result {i+1}", "url": f"https://example.com/{i}"}
            for i in range(max_results)
        ]
    }

def calculate(expression: str) -> dict:
    """Evaluate a mathematical expression."""
    try:
        result = eval(expression)  # Use safer parser in production
        return {"expression": expression, "result": result}
    except Exception as e:
        return {"error": str(e)}

def get_weather(city: str, units: str = "celsius") -> dict:
    """Get current weather for a city."""
    return {
        "city": city,
        "temperature": 22 if units == "celsius" else 72,
        "condition": "Sunny"
    }

# Create and run MCP server
server = ToolsMCPServer(name="my-tools")
server.register_tools([search_web, calculate, get_weather])
server.run()  # Starts stdio server (for Claude Desktop)

Using the Convenience Function

from praisonaiagents.mcp import launch_tools_mcp_server

def my_tool(query: str) -> str:
    """Process a query."""
    return f"Processed: {query}"

# Quick launch
launch_tools_mcp_server(
    tools=[my_tool],
    name="quick-server"
)

Transport Options

stdio Transport (Default)

Best for local integrations like Claude Desktop:
server = ToolsMCPServer(name="my-tools")
server.register_tools([search_web, calculate])
server.run()  # or server.run_stdio()

SSE Transport

For web-based clients and remote access:
server = ToolsMCPServer(name="my-tools")
server.register_tools([search_web, calculate])
server.run_sse(host="0.0.0.0", port=8080)
Access at: http://localhost:8080/sse

Tool Schema Generation

PraisonAI automatically generates MCP-compatible schemas from your Python functions:
from praisonaiagents.mcp import function_to_mcp_schema

def search(query: str, max_results: int = 10, include_images: bool = False) -> dict:
    """Search the web for information.
    
    Args:
        query: The search query
        max_results: Maximum results to return
        include_images: Whether to include images
    """
    return {}

schema = function_to_mcp_schema(search)
print(schema)
Output:
{
    "name": "search",
    "description": "Search the web for information.",
    "inputSchema": {
        "type": "object",
        "properties": {
            "query": {"type": "string"},
            "max_results": {"type": "integer"},
            "include_images": {"type": "boolean"}
        },
        "required": ["query"]
    }
}

Supported Type Mappings

Python TypeJSON Schema Type
strstring
intinteger
floatnumber
boolboolean
List[T]array with items
Dict[str, Any]object
Optional[T]Type T (not required)

Custom Tool Metadata

Override default name and description using special attributes:
def my_internal_function(x: str) -> str:
    """Internal docstring."""
    return x

# Custom MCP metadata
my_internal_function.__mcp_name__ = "public_tool_name"
my_internal_function.__mcp_description__ = "Public description for MCP clients"

server.register_tool(my_internal_function)

Async Tools

Async functions are fully supported:
import asyncio

async def async_search(query: str) -> dict:
    """Async search function."""
    await asyncio.sleep(0.1)  # Simulate async operation
    return {"query": query, "results": ["Result 1", "Result 2"]}

server = ToolsMCPServer(name="async-tools")
server.register_tool(async_search)
server.run()

Claude Desktop Integration

1

Create Your Server Script

Save as my_mcp_server.py:
from praisonaiagents.mcp import ToolsMCPServer

def search(query: str) -> dict:
    """Search for information."""
    return {"results": [f"Result for {query}"]}

def calculate(expr: str) -> dict:
    """Calculate expression."""
    return {"result": eval(expr)}

if __name__ == "__main__":
    server = ToolsMCPServer(name="my-tools")
    server.register_tools([search, calculate])
    server.run()
2

Configure Claude Desktop

Add to Claude Desktop config (~/Library/Application Support/Claude/claude_desktop_config.json on macOS):
{
    "mcpServers": {
        "my-tools": {
            "command": "python",
            "args": ["/path/to/my_mcp_server.py"]
        }
    }
}
3

Restart Claude Desktop

Restart Claude Desktop to load your new MCP server. Your tools will be available in conversations.

Cursor Integration

1

Create Server Script

Same as Claude Desktop setup above.
2

Configure Cursor

Add to Cursor’s MCP configuration:
{
    "mcpServers": {
        "praisonai-tools": {
            "command": "python",
            "args": ["/path/to/my_mcp_server.py"]
        }
    }
}

Exposing Built-in PraisonAI Tools

You can expose PraisonAI’s built-in tools as an MCP server:
from praisonaiagents.mcp import launch_tools_mcp_server

# Expose built-in tools by name
launch_tools_mcp_server(
    tool_names=["tavily_search", "duckduckgo_search"],
    name="search-tools-server"
)

Complete Example

"""Complete MCP Server Example

Run with: python mcp_tools_server.py
Or with SSE: python mcp_tools_server.py --sse --port 8080
"""

import argparse
from typing import List, Dict, Any

def search_web(query: str, max_results: int = 5) -> Dict[str, Any]:
    """Search the web for information.
    
    Args:
        query: The search query string
        max_results: Maximum number of results (default: 5)
    """
    return {
        "query": query,
        "results": [
            {"title": f"Result {i+1} for '{query}'", "url": f"https://example.com/{i+1}"}
            for i in range(max_results)
        ]
    }

def calculate(expression: str) -> Dict[str, Any]:
    """Evaluate a mathematical expression.
    
    Args:
        expression: Math expression to evaluate (e.g., "2 + 2 * 3")
    """
    try:
        allowed = set("0123456789+-*/.() ")
        if not all(c in allowed for c in expression):
            return {"error": "Invalid characters"}
        return {"expression": expression, "result": eval(expression)}
    except Exception as e:
        return {"error": str(e)}

def get_weather(city: str, units: str = "celsius") -> Dict[str, Any]:
    """Get current weather for a city.
    
    Args:
        city: Name of the city
        units: Temperature units - "celsius" or "fahrenheit"
    """
    return {
        "city": city,
        "temperature": 22 if units == "celsius" else 72,
        "units": units,
        "condition": "Sunny",
        "humidity": 45
    }

def list_files(directory: str = ".", pattern: str = "*") -> List[str]:
    """List files in a directory.
    
    Args:
        directory: Directory path (default: current)
        pattern: Glob pattern to filter files
    """
    import glob
    import os
    try:
        return [os.path.basename(f) for f in glob.glob(os.path.join(directory, pattern))]
    except Exception as e:
        return [f"Error: {e}"]

def main():
    parser = argparse.ArgumentParser(description="PraisonAI Tools MCP Server")
    parser.add_argument("--sse", action="store_true", help="Use SSE transport")
    parser.add_argument("--port", type=int, default=8080, help="Port for SSE")
    parser.add_argument("--host", type=str, default="0.0.0.0", help="Host for SSE")
    parser.add_argument("--debug", action="store_true", help="Enable debug logging")
    args = parser.parse_args()
    
    from praisonaiagents.mcp import ToolsMCPServer
    
    server = ToolsMCPServer(name="praisonai-tools", debug=args.debug)
    server.register_tools([search_web, calculate, get_weather, list_files])
    
    print(f"📦 Registered {len(server.tools)} tools: {', '.join(server.get_tool_names())}")
    
    if args.sse:
        server.run_sse(host=args.host, port=args.port)
    else:
        server.run_stdio()

if __name__ == "__main__":
    main()

API Reference

ToolsMCPServer

MethodDescription
__init__(name, tools, debug)Initialize server with optional tools
register_tool(func)Register a single tool function
register_tools(funcs)Register multiple tool functions
get_tool_schemas()Get MCP schemas for all tools
get_tool_names()Get list of registered tool names
execute_tool(name, args)Execute a tool synchronously
execute_tool_async(name, args)Execute a tool asynchronously
run(transport)Run server with specified transport
run_stdio()Run with stdio transport
run_sse(host, port)Run with SSE transport

launch_tools_mcp_server

launch_tools_mcp_server(
    tools=None,           # List of tool functions
    tool_names=None,      # List of built-in tool names
    name="praisonai-tools",
    transport="stdio",    # "stdio" or "sse"
    host="0.0.0.0",
    port=8080,
    debug=False
)

Best Practices

Type Hints

Always use type hints for proper schema generation

Docstrings

Write clear docstrings - they become tool descriptions

Error Handling

Return error information in response dict, don’t raise exceptions

Security

Validate inputs, especially for file/system operations

Next Steps