Skip to main content

intpot

IntPot

CI PyPI version Python versions License: MIT

Write once, serve as CLI, API, or MCP. Plus convert between all three.

intpot bridges three popular Python frameworks:

  • Typer — CLI applications
  • FastMCP — Model Context Protocol servers
  • FastAPI — REST API applications

Define your tools once with @app.tool() and serve them as any framework — or convert existing code between all three.

Features

  • Write once, serve everywhereintpot.App lets you define tools once and serve as CLI, API, or MCP with a single command
  • 6 conversion directions — CLI to MCP, CLI to API, MCP to CLI, MCP to API, API to CLI, API to MCP
  • Eject to standalone codeintpot eject exports your universal app as standalone Typer, FastAPI, or FastMCP code
  • Python APIintpot.load() accepts file paths or live app instances for programmatic conversion
  • Directory auto-discovery — scan an entire directory and convert all found apps at once
  • Auto-detection — automatically identifies the source framework by analyzing imports and patterns
  • HTTP method preservation — API routes keep their GET/POST/PUT/DELETE methods through conversion
  • Parameter source preservation — FastAPI Query, Header, Path, and Body parameters stay where they were, instead of collapsing into a request body
  • Project scaffoldingintpot init creates new CLI, MCP, or API projects from templates
  • Jinja2 templates — clean, readable generated code with proper type hints
  • Fully typed — PEP 561 compatible with py.typed marker
  • AI agent skillsintpot add skills installs skills/rules for Claude Code, Cursor, Windsurf, Copilot, Cline, and Codex
  • Zero config — just point at a Python file and specify the target

Installation

pip install intpot            # core: init, inspect, add skills, and Typer CLI output
pip install intpot[mcp]       # + FastMCP support
pip install intpot[api]       # + FastAPI support
pip install intpot[all]       # everything

The extras are only needed for frameworks you actually touch: reading a FastMCP server or emitting one requires [mcp], and the same goes for [api] and FastAPI.

Quick Start

Write once, serve everywhere

Define your tools once, serve as CLI, API, or MCP:

from intpot import App

app = App("my-app")

@app.tool()
def add(a: int, b: int) -> int:
    """Add two numbers together."""
    return a + b

@app.tool()
def greet(name: str, greeting: str = "Hello") -> str:
    """Greet someone by name."""
    return f"{greeting}, {name}!"

The tool name and description default to the function name and its docstring. Override either when the two audiences want different things — the docstring explains the code to whoever maintains it, the description tells an agent when to call the tool:

@app.tool(name="lookup", description="Look up a customer by their account number.")
def fetch_customer_record(account_id: str) -> dict:
    """Hit the accounts table. Assumes the caller already validated account_id."""
    ...

Then serve in any mode:

intpot serve app.py --cli          # Run as Typer CLI
intpot serve app.py --api          # Run as FastAPI on port 8000
intpot serve app.py --mcp          # Run as MCP server for AI agents

Or eject to standalone framework code:

intpot eject app.py --to api       # Export as standalone FastAPI app
intpot eject app.py --to cli       # Export as standalone Typer CLI
intpot eject app.py --to mcp       # Export as standalone FastMCP server

Scaffold a new project

intpot init my-server --type mcp
intpot init my-app --type cli
intpot init my-api --type api

Convert between frameworks

# MCP server -> Typer CLI
intpot to cli server.py

# CLI app -> FastMCP server
intpot to mcp app.py

# CLI app -> FastAPI app
intpot to api app.py

# Write output to a file
intpot to cli server.py --output cli_app.py

# Convert all apps in a directory
intpot to cli ./myproject/
intpot to mcp ./myproject/ --output ./converted/

Install AI agent skills

# Auto-detect agents in your project
intpot add skills

# Target a specific agent
intpot add skills --agent claude
intpot add skills --agent cursor
intpot add skills --agent windsurf
intpot add skills --agent copilot
intpot add skills --agent cline
intpot add skills --agent codex

# Specify a project directory
intpot add skills --path ./myproject/

Python API

Universal App (write once, serve everywhere)

from intpot import App

app = App("my-app")

@app.tool()
def greet(name: str, greeting: str = "Hello") -> str:
    """Greet someone."""
    return f"{greeting}, {name}!"

# Serve as any framework
app.serve(mode="cli")                         # Run as Typer CLI
app.serve(mode="api", port=8000)              # Run as FastAPI on 127.0.0.1
app.serve(mode="mcp")                         # Run as MCP server

# Eject to standalone code
cli_code = app.eject("cli")                   # Returns Typer code string
api_code = app.eject("api")                   # Returns FastAPI code string

# Access normalized tool definitions
for tool in app.tools:
    print(tool.name, tool.parameters)

Conversion API (convert existing framework code)

import intpot

# From a file
app = intpot.load("mcp_server.py")
cli_code = app.to_cli()
api_code = app.to_api()

# From a live instance
from fastmcp import FastMCP

mcp = FastMCP("my-server")

@mcp.tool()
def greet(name: str) -> str:
    return f"Hello, {name}!"

app = intpot.load(mcp)
print(app.to_cli())

# Write directly to a file
app.write("output/cli_app.py", "cli")
app.write("output/api_app.py", "api")

App (universal runtime):

  • .tool(name=None, description=None) — decorator to register functions as tools; both arguments override the defaults taken from the function name and docstring
  • .serve(mode, host, port) — serve as CLI, API, or MCP
  • .eject(target) — generate standalone framework code
  • .tools — list of normalized ToolInfo objects

IntpotApp (conversion wrapper, returned by intpot.load()):

  • .to_cli(), .to_mcp(), .to_api() — return generated code as strings
  • .write(path, target) — generate and write to a file in one step
  • .tools — list of normalized ToolInfo objects
  • .source_type — detected framework type

Architecture

Both halves of intpot meet at one normalized schema, ToolInfo. Everything upstream produces it; everything downstream consumes it.

   @app.tool()                        source .py file
   (intpot.App)                    (Typer / FastMCP / FastAPI)
        |                                    |
        |                              1. DETECT
        |                              2. INSPECT
        |                                    |
        +--------------> ToolInfo[] <--------+
                              |
              +---------------+---------------+
              |                               |
        build a live                    3. GENERATE
      framework instance            (render a template)
              |                               |
       serve --cli/--api/--mcp        .py output on disk
                                    (to cli/mcp/api, eject)

The conversion side is a three-stage pipeline:

                    +-----------+
                    |  SOURCE   |
                    | (.py file)|
                    +-----+-----+
                          |
                    1. DETECT
                    (identify framework)
                          |
                    +-----v-----+
                    | SourceType|
                    | cli/mcp/api|
                    +-----+-----+
                          |
                    2. INSPECT
                    (extract functions)
                          |
                    +-----v-----+
                    | ToolInfo[] |
                    | (normalized|
                    |  schema)  |
                    +-----+-----+
                          |
                    3. GENERATE
                    (render template)
                          |
                    +-----v-----+
                    |  OUTPUT   |
                    | (.py code)|
                    +-----------+
  1. DETECTcore/detector.py imports the source file and identifies whether it's a Typer app, FastMCP server, or FastAPI app
  2. INSPECT — Framework-specific inspectors (core/inspectors/) extract function signatures, parameters, types, defaults, and docstrings into a normalized ToolInfo schema
  3. GENERATE — Framework-specific generators (core/generators/) render the normalized schema into target code using Jinja2 templates

The runtime side skips detection and inspection: @app.tool() builds ToolInfo directly from the function signature, then either constructs a live framework instance (serve) or hands the same schema to the same generators (eject).

Detection imports your source file. intpot to ... and intpot inspect execute the module to find the app instance, so any module-level code in it runs. Point them at code you trust.

Examples

MCP server to CLI app

Input (mcp_server.py):

from fastmcp import FastMCP

mcp = FastMCP("example-server")

@mcp.tool()
def greet(name: str, greeting: str = "Hello") -> str:
    """Greet someone by name."""
    return f"{greeting}, {name}!"

Command: intpot to cli mcp_server.py

Output:

import typer

app = typer.Typer()


def _greet_impl(
    name: str,
    greeting: str,
) -> None:
    """Greet someone by name."""
    typer.echo(f'{greeting}, {name}!')


@app.command()
def greet(
    name: str = typer.Argument(..., help=""),
    greeting: str = typer.Option('Hello', help=""),
) -> None:
    """Greet someone by name."""
    result = _greet_impl(name, greeting)
    if result is not None:
        typer.echo(result)

The body lives in a separate function so the command can print what it returns — Typer discards return values, so the generated command has to echo explicitly.

CLI app to FastAPI

Input (cli_app.py):

import typer

app = typer.Typer()

@app.command()
def add(
    a: int = typer.Argument(..., help="First number"),
    b: int = typer.Argument(..., help="Second number"),
) -> None:
    """Add two numbers together."""
    typer.echo(a + b)

Command: intpot to api cli_app.py

Output:

from fastapi import FastAPI, Body

app = FastAPI()


@app.post("/add")
def add(
    a: int = Body(..., description="First number"),
    b: int = Body(..., description="Second number"),
) -> dict:
    """Add two numbers together."""

    return {'result': a + b}

typer.echo(a + b) becomes a return, wrapped so the response matches the dict annotation FastAPI validates against.

API app to MCP server

Input (api_app.py):

from fastapi import FastAPI

app = FastAPI()

@app.post("/greet")
def greet(name: str, greeting: str = "Hello") -> dict:
    """Greet someone by name."""
    return {"message": f"{greeting}, {name}!"}

Command: intpot to mcp api_app.py

Output:

from fastmcp import FastMCP

mcp = FastMCP("generated-server")


@mcp.tool()
def greet(
    name: str,
    greeting: str = 'Hello',
) -> dict:
    """Greet someone by name."""

    return {"message": f"{greeting}, {name}!"}

Bodies carry over where the two frameworks agree on conventions. Where they don't — a Typer command echoing instead of returning — the body is rewritten to match the target. Tools whose body can't be recovered generate a # TODO: implement stub.

See the examples/ directory for all conversion outputs, including advanced examples with import json, Body(...), Depends(), async tools, and more. Run bash scripts/demo.sh to regenerate them all.

CLI Reference

intpot serve

Serve an intpot App as CLI, API, or MCP server.

intpot serve <source> --cli|--api|--mcp [--host <host>] [--port <port>]
Argument/Option Description
source Path to a Python file containing an intpot.App
--cli Serve as a Typer CLI
--api Serve as a FastAPI app
--mcp Serve as a FastMCP server
--host API server host (default: 127.0.0.1 — pass 0.0.0.0 to expose it on the network)
--port API server port (default: 8000)

intpot eject

Export an intpot App as standalone framework code.

intpot eject <source> --to <cli|mcp|api> [--output <path>]
Argument/Option Description
source Path to a Python file containing an intpot.App
--to, -t Target framework: cli, mcp, api (required)
--output, -o Output file path (prints to stdout if omitted)

intpot init

Scaffold a new project from a template.

intpot init <name> --type <mcp|cli|api>
Argument/Option Description
name Project name (creates a directory)
--type, -t Project type: mcp, cli, or api (required)

intpot to cli

Convert an MCP or API source file to a Typer CLI app.

intpot to cli <source> [--output <path>]

intpot to mcp

Convert a CLI or API source file to a FastMCP server.

intpot to mcp <source> [--output <path>]

intpot to api

Convert a CLI or MCP source file to a FastAPI app.

intpot to api <source> [--output <path>]
Argument/Option Description
source Path to a source Python file or directory
--output, -o Output file/directory path (prints to stdout if omitted)

intpot add skills

Install intpot skills/rules for AI coding agents. Auto-detects which agents are configured in the project, or specify one explicitly.

intpot add skills [--agent <name>] [--path <dir>]
Option Description
--agent, -a Target agent: claude, cursor, windsurf, copilot, cline, codex
--path, -p Project root directory (defaults to current directory)

Supported agents and output locations:

Agent Files created
Claude Code .claude/skills/intpot-cli.md, .claude/skills/intpot-python.md
Cursor .cursor/rules/intpot-cli.mdc, .cursor/rules/intpot-python.mdc
Windsurf .windsurf/rules/intpot-cli.md, .windsurf/rules/intpot-python.md
GitHub Copilot .github/copilot-instructions.md (appended)
Cline .clinerules/intpot-cli.md, .clinerules/intpot-python.md
OpenAI Codex AGENTS.md (appended)

Development

Requires uv.

git clone https://github.com/tugrulguner/intpot.git
cd intpot
uv sync --all-extras
uv run pre-commit install

Run the full check suite:

make check   # lint + typecheck + test

Individual targets:

make lint        # ruff check + format check
make typecheck   # pyright
make test        # pytest
make format      # auto-format code

See CONTRIBUTING.md for more details.

Roadmap

See ROADMAP.md for what's planned for v2 (full AST transform pipeline).

License

MIT

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

intpot-0.5.0.tar.gz (2.9 MB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

intpot-0.5.0-py3-none-any.whl (47.8 kB view details)

Uploaded Python 3

File details

Details for the file intpot-0.5.0.tar.gz.

File metadata

  • Download URL: intpot-0.5.0.tar.gz
  • Upload date:
  • Size: 2.9 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for intpot-0.5.0.tar.gz
Algorithm Hash digest
SHA256 820860576b8e73d77b2c72feaa4181f315565cae072ce26502f6a2047b63b129
MD5 7aca5026a08d0a9ea6a18ffacaef1f71
BLAKE2b-256 2d6bb132bd5511d3debf7ef6f7ac19ae7ccd83358fc875e9fdcfe880c01eb3e0

See more details on using hashes here.

Provenance

The following attestation bundles were made for intpot-0.5.0.tar.gz:

Publisher: release.yml on tugrulguner/intpot

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file intpot-0.5.0-py3-none-any.whl.

File metadata

  • Download URL: intpot-0.5.0-py3-none-any.whl
  • Upload date:
  • Size: 47.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for intpot-0.5.0-py3-none-any.whl
Algorithm Hash digest
SHA256 58223cc94ae5846dbfa31f2e859d5d07c00092d709612ec23a7b14b23581b0ea
MD5 4f4e50e71918805ab0c4bd3abbefc49e
BLAKE2b-256 f825f4a3a50b77d7d4e67de807cd787b0ebdfc3f3f66cdc66bc606f7b08ea543

See more details on using hashes here.

Provenance

The following attestation bundles were made for intpot-0.5.0-py3-none-any.whl:

Publisher: release.yml on tugrulguner/intpot

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

0.8.0

2 files

0.7.2

2 files

0.7.1

2 files

0.7.0

2 files

0.6.0

2 files

0.5.1

2 files

This release

0.5.0 This release

2 files

0.4.2

2 files

0.4.1

2 files

0.4.0

2 files

0.3.0

2 files

0.2.6

2 files

0.2.4

2 files

0.2.2

2 files

0.2.1

2 files

0.2.0

2 files

0.1.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page