Project README
Source: README.md at
f0c3d3702a6cf77529a8ecd307103558484dffee. This is a repository snapshot, not a claim about the latest published release.
intpot
Section titled “intpot”
Part of ModePot. Project website Created by Tugrul Guner
One typed definition. Three interfaces.
Define typed tools once for CLI, HTTP, or MCP. Convert apps between Typer, FastAPI, and FastMCP.
Quick start · Playground · Deep docs · README quick start · Convert an app · Conversion scope · Architecture · Community · Contributing
Why intpot?
Section titled “Why intpot?”A useful Python function often needs three interfaces: a command for people, an HTTP endpoint for applications, and an MCP tool for AI agents. Maintaining three copies means three signatures, three sets of descriptions, and three places for behavior to drift.
intpot gives you two ways out:
| Starting point | What intpot does |
|---|---|
| Plain Python functions | Register them once with @app.tool(), then serve or eject CLI, API, and MCP interfaces |
| An existing Typer, FastAPI, or FastMCP app | Inspect it, normalize its tools, and generate either of the other two frameworks |
The output is ordinary Python. Ejected and converted apps do not depend on intpot.
Quick start
Section titled “Quick start”Install every runtime for the complete experience:
pip install "intpot[all]"Write once, serve everywhere
Section titled “Write once, serve everywhere”Save this as app.py:
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}!"Then serve in any mode:
intpot serve app.py --cli # Run as Typer CLIintpot serve app.py --api # Run as FastAPI on port 8000intpot serve app.py --mcp # Run as MCP server for AI agentsIn CLI mode, everything after the flags belongs to your app:
$ intpot serve app.py --cli add 2 35$ intpot serve app.py --cli greet World --greeting HiHi, World!With API mode running, call the same names as POST routes with JSON request bodies:
$ curl -s -X POST http://127.0.0.1:8000/add \ -H "Content-Type: application/json" \ -d '{"a": 2, "b": 3}'5MCP mode exposes add and greet as FastMCP tools with schemas derived from their type
annotations and defaults.
Or eject to standalone framework code:
intpot eject app.py --to api # Export as standalone FastAPI appintpot eject app.py --to cli # Export as standalone Typer CLIintpot eject app.py --to mcp # Export as standalone FastMCP serverScaffold a new project
Section titled “Scaffold a new project”intpot init my-server --type mcpintpot init my-app --type cliintpot init my-api --type apiInstall only the frameworks you need if you do not want the full extra:
pip install intpot # Core commands and Typer outputpip install "intpot[mcp]" # Add FastMCP supportpip install "intpot[api]" # Add FastAPI supportConvert an existing app
Section titled “Convert an existing app”intpot detects Typer, FastAPI, and FastMCP apps from a Python file and supports every conversion between them:
# MCP server -> Typer CLIintpot to cli server.py
# CLI app -> FastMCP serverintpot to mcp app.py
# CLI app -> FastAPI appintpot to api app.py
# Write output to a fileintpot to cli server.py --output cli_app.py
# Convert all apps in a directoryintpot to cli ./myproject/intpot to mcp ./myproject/ --output ./converted/Directory output mirrors the source tree, so myproject/alpha/tools.py becomes
converted/alpha/tools_mcp.py. Sources sharing a filename in different packages stay
separate files.
Use intpot inspect app.py first when you want to see the normalized tool definitions
before generating code. Converted output is readable Python that you can review, test,
and change.
Give your coding agent intpot context
Section titled “Give your coding agent intpot context”Install project-local instructions for Claude Code, Cursor, Windsurf, GitHub Copilot, Cline, or OpenAI Codex:
# Auto-detect agents in your projectintpot add skills
# Target a specific agentintpot add skills --agent claudeintpot add skills --agent cursorintpot add skills --agent windsurfintpot add skills --agent copilotintpot add skills --agent clineintpot add skills --agent codex
# Specify a project directoryintpot add skills --path ./myproject/What intpot handles
Section titled “What intpot handles”- One definition, three live interfaces: serve an
intpot.Appthrough Typer, FastAPI, or FastMCP. - Six conversion directions: move existing apps between all three frameworks.
- Standalone output: eject or convert to normal framework code with no intpot runtime dependency.
- Behavior-aware transforms: preserve recoverable function bodies and the direct
imports they use, and translate framework conventions such as
typer.echo()into return values. - Interface fidelity: carry types, defaults, and async functions through supported conversions, and apply FastAPI parameter sources when generating an API.
- Inspectable semantic core: use
intpot.load().schemafor an immutableApplicationSchema, inspect target projections before generation, or use detachedToolInfocompatibility objects through.tools. - Project tooling: scan directories, scaffold projects, and install agent guidance for six coding agents.
Python API
Section titled “Python API”Universal App (write once, serve everywhere)
Section titled “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 frameworkapp.serve(mode="cli") # Run as Typer CLIapp.serve(mode="api", port=8000) # Run as FastAPI on 127.0.0.1app.serve(mode="mcp") # Run as MCP server
# Eject to standalone codecli_code = app.eject("cli") # Returns Typer code stringapi_code = app.eject("api") # Returns FastAPI code string
# Inspect the canonical application schemaprint(app.schema.to_dict())for tool in app.schema.tools: print(tool.name, tool.parameters)Conversion API (convert existing framework code)
Section titled “Conversion API (convert existing framework code)”import intpot
# From a fileapp = intpot.load("mcp_server.py")api_schema = app.project("api") # Immutable target semanticscli_code = app.to_cli()api_code = app.to_api()
# From a live instancefrom 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 fileapp.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. Functions with*argsor**kwargsare rejected because those parameters have no consistent CLI, HTTP API, and MCP representation..serve(mode, host, port)— serve as CLI, API, or MCP.eject(target)— generate standalone framework code.schema— immutableApplicationSchemasnapshot shared with conversion.tools— detachedToolInfocompatibility objects
IntpotApp (conversion wrapper, returned by intpot.load()):
.to_cli(),.to_mcp(),.to_api()— return generated code as strings.schema— immutable sourceApplicationSchema, compiled once.project(target)— immutable target projection used by generation.write(path, target)— generate and write to a file in one step.tools— detachedToolInfocompatibility objects; mutating them does not alter the schema.source_type— detected framework type
What conversion preserves
Section titled “What conversion preserves”intpot is designed to produce code you can own, rather than hide conversion behind a
runtime adapter. ApplicationSchema is the application-level semantic boundary;
immutable ToolSchema and ParameterSchema records carry the parts that the three
frameworks share:
- tool names, descriptions, types, defaults, and async behavior;
- recoverable function bodies and the direct imports they reference;
- framework metadata that has a target equivalent, including supported
Query,Header,Path, andBodyparameter sources when generating FastAPI; - scalar returns translated into a shape the target framework can serve correctly.
Frameworks do not have one-to-one equivalents for every feature. FastAPI dependencies
(Depends, Security, nested dependencies, and route/router/app-level dependencies) are
preserved by inspection, but API-to-CLI/MCP conversion refuses them with
UnsupportedFastAPIDependencyError rather than emitting code with missing values. Full
dependency injection mapping remains tracked in
#20. Review generated code when a source
uses nested Typer command groups, repeatable CLI options, Annotated[..., Body(...)],
Pydantic model parameters, routes with multiple HTTP methods, streaming, background tasks,
or framework-specific error handling.
intpot carries direct import statements referenced by a tool body. It does not yet copy
same-module helpers, constants, classes, models, or closure values that the body
references, and it does not follow dependencies across imported modules. Factory-created
apps are not detected by the current AST pre-check. Loading a source file directly also
does not add its directory to sys.path, so a sibling import such as
from helpers import normalize may require installing the package or setting
PYTHONPATH. External services and configuration are not provisioned for you. If intpot
cannot recover a body, it emits a # TODO: implement stub instead of inventing behavior.
[!IMPORTANT] Detection imports the source module, so only inspect or convert code you trust. A source that cannot be parsed or imported is reported cleanly; during a directory conversion, intpot reports the bad file and continues with the rest. intpot is alpha software: compile the generated file, import it with its dependencies, and exercise a real CLI command, API request, or MCP tool before shipping it.
Architecture
Section titled “Architecture”Both halves of intpot meet at one canonical ApplicationSchema. It owns immutable
ToolSchema and ParameterSchema records. Existing ToolInfo and ParameterInfo
objects remain detached compatibility views at the edges.
@app.tool() source .py file (intpot.App) (Typer / FastMCP / FastAPI) | | | 1. DETECT | 2. INSPECT | | +-----------> ApplicationSchema <----+ ToolSchema[] | +---------------+---------------+ | | build a live 3. GENERATE framework instance (render a template) | | serve --cli/--api/--mcp .py output on disk (to cli/mcp/api, eject)Conversion follows four stages: detect the framework, inspect its tools, compile one
immutable ApplicationSchema, then project target-specific semantics before rendering.
The runtime side compiles @app.tool() registrations into the same schema. serve keeps
the registered callables for live execution; eject renders detached views of the stable
schema. Access .schema.to_dict() when a human, build tool, or agent needs to inspect
exactly what Intpot compiled.
to_dict() is directly JSON-serializable. Every non-JSON-native default uses an
unambiguous {"$intpot": {"type": ...}} envelope, preserving distinctions such as
list versus tuple and bytes versus an ordinary dictionary. Enum and opaque defaults, as
well as timezone-aware/folded temporal values, are rejected with TypeError because
standalone generated code cannot preserve their semantics reliably.
Conversion examples
Section titled “Conversion examples”The repository includes checked-in source and generated output for every direction:
| Source | Typer target | FastAPI target | FastMCP target |
|---|---|---|---|
| Typer | — | cli_to_api.py |
cli_to_mcp.py |
| FastAPI | api_to_cli.py |
— | api_to_mcp.py |
| FastMCP | mcp_to_cli.py |
mcp_to_api.py |
— |
examples/ also contains advanced inputs with direct imports, async tools,
request bodies, Depends(), and path parameters. The FastAPI dependency example remains
inspectable, but API-to-CLI/MCP conversion intentionally refuses it until issue #20 is
implemented. The FastAPI input also includes routes with multiple HTTP methods. See
semantic_schema.py for a runnable canonical-schema
inspection example.
CLI Reference
Section titled “CLI Reference”intpot --version (or -V) prints the installed version; intpot <command> --help
works for any command below.
intpot serve
Section titled “intpot serve”Serve an intpot App as CLI, API, or MCP server.
intpot serve <source> --cli|--api|--mcp [--host <host>] [--port <port>] [--] [args...]| 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) |
args... |
Passed straight to your app in --cli mode: intpot serve app.py --cli add 2 3 |
Anything intpot doesn’t recognise is forwarded, so your own options work as-is. Use --
when your app defines a flag intpot also defines:
intpot serve app.py --cli -- greet World --port 5intpot inspect
Section titled “intpot inspect”Show the tools intpot extracts from a source, without generating anything. Useful for checking what a conversion will see before you run it.
intpot inspect <source> [--json] [--verbose]| Argument/Option | Description |
|---|---|
source |
Path to a source Python file or directory |
--json |
Emit the canonical tool schema as JSON instead of a table |
--verbose, -v |
Print detection details to stderr |
$ intpot inspect mcp_server.py
Source: mcp_server.py (mcp)┏━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━┓┃ Name ┃ Description ┃ Parameters ┃ Return Type ┃ Async ┃┡━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━┩│ greet │ Greet someone by name. │ name: str, greeting: │ str │ No ││ │ │ str='Hello' │ │ │└───────┴────────────────────────┴───────────────────────┴─────────────┴───────┘intpot eject
Section titled “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). Missing parent directories are created |
intpot init
Section titled “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 / to mcp / to api
Section titled “intpot to cli / to mcp / to api”Convert a source file — or every app in a directory — to the target framework. For a directory, the output mirrors the source tree: each generated file keeps its source’s position, with only the filename changing.
intpot to cli <source> [--output <path>] [--dry-run] [--verbose]intpot to mcp <source> [--output <path>] [--dry-run] [--verbose]intpot to api <source> [--output <path>] [--dry-run] [--verbose]All three take the same arguments:
| Argument/Option | Description |
|---|---|
source |
Path to a source Python file or directory |
--output, -o |
Output file/directory path (prints to stdout if omitted). Missing directories are created |
--dry-run |
Print what would be generated, without writing any files |
--verbose, -v |
Print detection details to stderr |
to cli accepts MCP or API sources, to mcp accepts CLI or API, to api accepts CLI or
MCP. A source that already matches the target is skipped.
--dry-run shows generated output without writing generated files, but it is not a
sandbox: detection still imports the source and executes arbitrary module-level code.
Only point intpot at source you trust:
$ intpot to cli mcp_server.py --dry-run# --- Would generate: mcp_server_cli.py ---"""CLI app generated by intpot."""...intpot add skills
Section titled “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 | Detected by | Files created |
|---|---|---|
| Claude Code | .claude/ |
.claude/skills/intpot-cli/SKILL.md, .claude/skills/intpot-python/SKILL.md |
| Cursor | .cursor/ |
.cursor/rules/intpot-cli.mdc, .cursor/rules/intpot-python.mdc |
| Windsurf | .windsurf/ |
.windsurf/rules/intpot-cli.md, .windsurf/rules/intpot-python.md |
| GitHub Copilot | .github/copilot-instructions.md |
.github/copilot-instructions.md (managed block) |
| Cline | .clinerules/ |
.clinerules/intpot-cli.md, .clinerules/intpot-python.md |
| OpenAI Codex | never auto-detected | AGENTS.md (managed block) |
Codex has to be asked for by name — intpot add skills --agent codex. It reads
AGENTS.md, but so does nearly every other tool now, so the presence of that file says
nothing about whether you use Codex. Since installing appends to it, guessing wrong would
edit your own documentation. The same reasoning is why Copilot keys off
.github/copilot-instructions.md rather than .github/, which only means the project is
on GitHub.
Running with no --agent and no detected marker exits without writing anything.
Copilot and Codex installations use bounded intpot-managed blocks, so rerunning the
command updates or repairs intpot guidance while preserving surrounding project content.
For Codex, intpot warns when the resulting AGENTS.md exceeds Codex’s default 32 KiB
instruction limit.
Community
Section titled “Community”The ModePot Discord is the shared community for intpot, summonpot, dexpot, and the rest of the project family. Join to discuss use cases, ask implementation questions, and help shape declaration-first Python frameworks.
Use GitHub issues for reproducible bugs and scoped feature proposals. Use Discord for open-ended design discussion, early ideas, and help applying the frameworks to real projects.
Contributing
Section titled “Contributing”intpot has a small core with clear extension points: inspectors turn frameworks into
ToolInfo, generators turn ToolInfo into source, and runtime builders expose live
interfaces. Contributions can improve conversion fidelity, add real-world examples,
strengthen generated-code tests, or make the developer experience clearer.
- Start with a good first issue.
- Browse work where help is wanted.
- Read CONTRIBUTING.md for the pull request process and
docs/reviewing.mdfor the contracts changes must preserve.
Open an issue before substantial public-contract work; focused fixes, tests, documentation, and maintenance may be direct pull requests. User-facing work uses an issue-backed or Towncrier-generated orphan changelog fragment—never a pull-request-number fragment.
Development setup
Section titled “Development setup”Requires uv.
git clone https://github.com/tugrulguner/intpot.gitcd intpotuv sync --all-extrasuv run pre-commit installRun the full check suite:
make check # lint + typecheck + testIndividual targets:
make lint # ruff check + format checkmake typecheck # pyrightmake test # pytestmake format # auto-format codemake changelog-draft # preview the next release sectionmake changelog # assemble changelog.d/ into CHANGELOG.md (release only)Changelog entries are written as one fragment file per PR in
changelog.d/ rather than by editing CHANGELOG.md, and CI asks every
PR for one. See changelog.d/README.md — it’s short.
Roadmap
Section titled “Roadmap”See ROADMAP.md for the current polish backlog and the planned full AST transform pipeline.
Support intpot
Section titled “Support intpot”If intpot removes a duplicate interface from your project, consider starring the repository. Stars help other Python developers find the project, while issues and pull requests make it better.
License
Section titled “License”MIT