A Python SDK for building AI agents with LLM, tools, and MCP support
Project description
minicode
A Python SDK for building AI agents with LLM, tools, skills, and MCP support.
Overview
minicode is a clean, extensible framework for building AI agents in Python. Minicode provides a simple yet powerful abstraction layer for:
- ๐ค LLM Integration - Support for any LLM provider through a common interface
- ๐ ๏ธ Tool System - Extensible tool framework with JSON Schema validation
- ๐ MCP Support - Connect to Model Context Protocol servers for additional capabilities
- ๐ Skills - Load and use skills from skill directories
- โก Async First - Built with async/await for efficient I/O operations
- ๐ฏ Type Safe - Full type annotations for better IDE support
Installation
pip install minicode
For OpenAI support:
pip install minicode[openai]
For development:
pip install minicode[dev]
Quick Start
Basic Agent
import asyncio
from minicode import Agent
from minicode.llm import OpenAILLM
from minicode.tools import ReadTool, WriteTool
async def main():
# Create an agent
agent = Agent(
name="assistant",
llm=OpenAILLM(api_key="your-api-key"),
tools=[ReadTool(), WriteTool()],
prompt="You are a helpful coding assistant."
)
# Stream responses
async for chunk in agent.stream("Read the README.md file"):
if chunk.get("type") == "content":
print(chunk.get("content", ""), end="", flush=True)
asyncio.run(main())
Core Concepts
1. Agent
The Agent class is the core of minicode. It combines an LLM, tools, and session management:
from minicode import Agent
agent = Agent(
name="my-agent",
llm=my_llm,
tools=[tool1, tool2],
prompt="System prompt for the agent",
temperature=0.7,
top_p=1.0,
mode="primary", # or "subagent" or "all"
)
Key Methods:
stream(message)- Stream responses from the agentgenerate(message)- Get a complete response (non-streaming)add_tool(tool)- Add a tool to the agentreset_session()- Clear conversation history
2. LLM Abstraction
minicode provides a clean abstraction for LLM providers:
from minicode.llm import BaseLLM
class MyCustomLLM(BaseLLM):
async def stream(self, messages, tools=None, **kwargs):
# Implement streaming logic
yield {"type": "content", "content": "Hello"}
yield {"type": "done", "finish_reason": "stop"}
async def generate(self, messages, **kwargs):
# Implement non-streaming logic
return {"content": "Hello", "finish_reason": "stop"}
Built-in Implementations:
OpenAILLM- OpenAI API integration (GPT-4, GPT-3.5, etc.)
3. Tool System
Tools allow agents to interact with the environment:
from minicode import BaseTool, ToolContext
from typing import Dict, Any
class MyTool(BaseTool):
@property
def name(self) -> str:
return "my_tool"
@property
def description(self) -> str:
return "What this tool does"
@property
def parameters_schema(self) -> Dict[str, Any]:
return {
"type": "object",
"properties": {
"input": {"type": "string", "description": "Input text"}
},
"required": ["input"]
}
async def execute(self, params: Dict[str, Any], context: ToolContext) -> Dict[str, Any]:
return {
"success": True,
"data": f"Processed: {params['input']}"
}
Built-in Tools:
AskUserQuestionTool- Ask questions to users and wait for answers with timeout supportBashTool- Execute bash commands with timeout support and background executionBashOutputTool- Monitor output from background bash processesKillShellTool- Terminate background bash processesReadTool- Read file contentsWriteTool- Write content to filesEditTool- Precise string replacement in filesGlobTool- File pattern matching (e.g.,**/*.py)GrepTool- Code search with regex (ripgrep + Python fallback)WebFetchTool- Fetch web content with HTML to Markdown/text conversionWebSearchTool- Web search with configurable backends (Exa, DuckDuckGo)NotebookEditTool- Edit Jupyter notebook cells (replace/insert/delete)TodoWriteTool- Create and manage structured task lists for tracking progressTaskTool- Launch sub-agents to handle complex tasks in isolated sessionsTaskOutputTool- Sub-agents use this to return results earlySkillTool- Load and execute skills from skill directoriesThinkTool- Record agent's reasoning and thinking process for transparency
Web Tools Usage:
from minicode.tools.builtin import WebFetchTool, WebSearchTool
# Fetch web content
webfetch = WebFetchTool()
result = await webfetch.execute(
{"url": "https://example.com", "format": "markdown"},
context
)
# Search the web
websearch = WebSearchTool(default_backend="exa")
result = await websearch.execute(
{
"query": "Python tutorials",
"num_results": 10,
"type": "deep", # Exa-specific: auto, fast, or deep
"livecrawl": "preferred" # Exa-specific: fallback or preferred
},
context
)
WebFetch Features:
- Supports multiple output formats:
text,markdown,html - Automatic HTML to Markdown conversion using html2text
- Plain text extraction with script/style removal
- Configurable timeout (default 30s, max 120s)
- 5MB size limit for responses
WebSearch Features:
- Configurable backends:
exa(default),duckduckgo(requires duckduckgo-search package) - Exa backend supports advanced options: search type (auto/fast/deep), live crawl mode
- Customizable number of results
- LLM-optimized context from Exa
Notebook Tools Usage:
from minicode.tools.builtin import NotebookEditTool
# Replace a cell's content
notebook_tool = NotebookEditTool()
result = await notebook_tool.execute(
{
"notebook_path": "/path/to/notebook.ipynb",
"cell_id": "abc123",
"new_source": "print('Hello, World!')"
},
context
)
# Insert a new cell
result = await notebook_tool.execute(
{
"notebook_path": "/path/to/notebook.ipynb",
"edit_mode": "insert",
"cell_id": "abc123", # Insert after this cell
"cell_type": "code",
"new_source": "x = 42"
},
context
)
# Delete a cell
result = await notebook_tool.execute(
{
"notebook_path": "/path/to/notebook.ipynb",
"edit_mode": "delete",
"cell_id": "abc123",
"new_source": "" # Required but not used
},
context
)
NotebookEdit Features:
- Replace cell content by cell ID
- Insert new cells (code or markdown) at any position
- Delete cells by ID
- Change cell type (code โ markdown)
- Automatically clears outputs when editing code cells
- Preserves notebook metadata and structure
TodoWrite Usage:
from minicode.tools.builtin import TodoWriteTool
# Create and manage task lists
todo_tool = TodoWriteTool()
result = await todo_tool.execute(
{
"todos": [
{
"content": "Implement feature X",
"activeForm": "Implementing feature X",
"status": "pending"
},
{
"content": "Write tests",
"activeForm": "Writing tests",
"status": "in_progress"
},
{
"content": "Update documentation",
"activeForm": "Updating documentation",
"status": "completed"
}
]
},
context
)
TodoWrite Features:
- Track multiple tasks with status (pending/in_progress/completed)
- Each task has
content(imperative form) andactiveForm(present continuous) - Provides visibility into agent progress
- Warns if more than one task is in_progress
- Warns if no task is in_progress when pending tasks exist
- Helps organize complex multi-step tasks
Background Process Tools Usage:
from minicode.tools.builtin import BashTool, BashOutputTool, KillShellTool
# Start a background process
bash_tool = BashTool()
result = await bash_tool.execute(
{
"command": "python long_running_script.py",
"run_in_background": True
},
context
)
bash_id = result["bash_id"]
# Monitor output from background process
output_tool = BashOutputTool()
output = await output_tool.execute(
{
"bash_id": bash_id,
"filter": "ERROR|WARNING" # Optional regex filter
},
context
)
print(output["output"]) # Only new output since last check
# Kill background process
kill_tool = KillShellTool()
result = await kill_tool.execute(
{"shell_id": bash_id},
context
)
Background Process Features:
- Run long-running commands without blocking
- Monitor output incrementally with BashOutput
- Filter output with regex patterns
- Kill processes when needed
- Each background process gets a unique ID
- Output buffer automatically managed
AskUserQuestion Usage:
from minicode.tools.builtin import AskUserQuestionTool
# Define callback to handle questions (for UI/web integration)
async def question_handler(question: str) -> str:
# Get answer from your UI/web interface
return user_interface.get_input(question)
# Create tool with callback
ask_tool = AskUserQuestionTool(
question_callback=question_handler,
default_timeout=None # No timeout by default
)
# Agent can ask questions during execution
result = await ask_tool.execute(
{
"question": "Which API version should I use?",
"default_answer": "v2", # Optional default
"timeout": 30 # Optional timeout in seconds
},
context
)
print(result["answer"]) # User's answer
# CLI mode (no callback - uses stdin)
cli_tool = AskUserQuestionTool() # Will use input() in thread pool
result = await cli_tool.execute(
{"question": "Continue with installation?"},
context
)
AskUserQuestion Features:
- Support both callback-based and CLI-based interaction
- Multi-round conversations - ask follow-up questions
- Optional timeout with default answers
- Inform agent when user doesn't respond (timeout without default)
- Non-blocking async execution (even for stdin)
- Flexible integration with any UI framework
4. MCP Integration
minicode supports Model Context Protocol (MCP) for connecting to external tool servers. The configuration format is compatible with Claude Code.
Method 1: Agent with MCP Servers (Recommended)
The simplest way to use MCP is through the Agent's built-in support:
import asyncio
from minicode import Agent
from minicode.llm import OpenAILLM
async def main():
# Configure MCP servers
mcp_servers = [
{
"name": "memory",
"command": ["npx", "-y", "@modelcontextprotocol/server-memory"],
},
{
"name": "filesystem",
"command": ["npx", "-y", "@modelcontextprotocol/server-filesystem", "/path/to/dir"],
},
]
# Use async context manager for automatic setup/cleanup
async with Agent(
name="assistant",
llm=OpenAILLM(api_key="your-key"),
mcp_servers=mcp_servers,
) as agent:
# MCP tools are automatically discovered and registered
async for chunk in agent.stream("Store this note: Hello World"):
if chunk.get("type") == "content":
print(chunk.get("content", ""), end="")
asyncio.run(main())
Method 2: Configuration File (Claude Code Compatible)
Create a .mcp.json file in your project directory or ~/.claude.json for user-level config:
{
"mcpServers": {
"memory": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-memory"],
"env": {
"NODE_ENV": "production"
}
},
"api-server": {
"type": "http",
"url": "http://localhost:8080/mcp",
"headers": {
"Authorization": "Bearer your-token"
}
}
}
}
The Agent automatically loads MCP servers from config files:
async with Agent(
name="assistant",
llm=OpenAILLM(api_key="your-key"),
# use_global_mcp=True is the default
) as agent:
# MCP servers from .mcp.json are automatically loaded
pass
Config file locations (in order of precedence):
MINICODE_CONFIGenvironment variable.mcp.jsonormcp.jsonin current directory~/.claude.json(user-level config)
To disable automatic config loading:
agent = Agent(
name="assistant",
llm=my_llm,
use_global_mcp=False, # Don't load from config files
)
Method 3: Programmatic Global Config
Add MCP servers programmatically to the global config:
from minicode import add_global_mcp_server, Agent
# Add stdio server
add_global_mcp_server(
name="memory",
command="npx",
args=["-y", "@modelcontextprotocol/server-memory"],
env={"NODE_ENV": "production"},
)
# Add HTTP server
add_global_mcp_server(
name="api-server",
url="http://localhost:8080/mcp",
headers={"Authorization": "Bearer token"},
)
# Agent will automatically use these servers
async with Agent(name="assistant", llm=my_llm) as agent:
pass
Method 4: Direct MCPClient Usage
For more control, use MCPClient directly:
from minicode import MCPClient
mcp = MCPClient()
# Add stdio server
await mcp.add_server(
name="memory",
command=["npx", "-y", "@modelcontextprotocol/server-memory"],
)
# Add HTTP server
await mcp.add_server(
name="api",
url="http://localhost:8080/mcp",
headers={"Authorization": "Bearer token"},
)
# Get tools and use with agent
tools = mcp.get_tools()
agent = Agent(name="assistant", llm=my_llm, tools=tools)
# Don't forget to cleanup
await mcp.disconnect_all()
MCP Server Configuration
| Field | Type | Description |
|---|---|---|
name |
string | Unique identifier for the server |
type |
string | "stdio" (default) or "http" |
command |
string | Command to run (stdio only) |
args |
list | Command arguments (stdio only) |
url |
string | Server URL (http only) |
env |
dict | Environment variables (stdio only) |
headers |
dict | HTTP headers (http only) |
Popular MCP Servers
@modelcontextprotocol/server-memory- Knowledge graph storage@modelcontextprotocol/server-filesystem- File system access@modelcontextprotocol/server-github- GitHub integration@modelcontextprotocol/server-postgres- PostgreSQL database@modelcontextprotocol/server-sqlite- SQLite database
See MCP Servers for more options.
5. Skills System
Skills provide specialized instructions and workflows for specific tasks. Use the SkillTool to access skills:
from minicode.tools.builtin import SkillTool
# Create skill tool (automatically discovers skills)
skill_tool = SkillTool()
# Add to agent
agent.add_tool(skill_tool)
# The agent can now invoke skills by name
# For example: {"skill": "data-analysis"}
Skill files should be placed in:
.minicode/skills/(project-specific)~/.minicode/skills/(user-wide)- Or set
MINICODE_SKILLS_DIRenvironment variable to specify a custom directory
Skill Format:
Each skill is a separate directory with a SKILL.md file (case-insensitive, e.g., skill.md also works, but uppercase is recommended):
.minicode/skills/
โโโ my-skill/ # Directory name is for human readability
โ โโโ SKILL.md # Core skill definition (required)
โ โโโ example.py # Additional files can be referenced
โ โโโ docs/ # Additional directories can be included
โ โโโ guide.md
โโโ another-skill/
โโโ SKILL.md
SKILL.md Format:
---
name: my_skill
description: This skill does something useful. Use it when you need to process text input.
---
# Skill Content
This is the main skill content in Markdown format.
You can reference other files in this skill directory:
- See [example.py](./example.py) for implementation details
- Check [guide.md](./docs/guide.md) for usage guide
The agent will selectively read referenced files based on the skill description.
Required YAML metadata fields:
name: Unique, short, human-readable identifierdescription: Natural language description of the skill and when to use it
Examples
See the examples/ directory for complete examples:
- basic_agent.py - Interactive agent with file tools
- custom_llm.py - Create custom LLM implementations
- custom_tool.py - Create custom tools
- mcp_example.py - MCP integration examples
- web_tools_example.py - WebSearch and WebFetch usage examples
- notebook_edit_example.py - Jupyter notebook editing examples
- todowrite_example.py - Task management and tracking examples
- background_process_example.py - Background process management examples
- askuserquestion_example.py - User interaction and question handling examples
Project Structure
minicode/
โโโ src/minicode/
โ โโโ __init__.py # Main package exports
โ โโโ agent.py # Core Agent implementation
โ โโโ llm/
โ โ โโโ base.py # BaseLLM abstract class
โ โ โโโ openai.py # OpenAI implementation
โ โโโ tools/
โ โ โโโ base.py # BaseTool abstract class
โ โ โโโ registry.py # Tool registry
โ โ โโโ builtin/ # Built-in tools
โ โโโ mcp/
โ โ โโโ client.py # MCP client
โ โ โโโ transport.py # Transport layer
โ โโโ skills/
โ โ โโโ loader.py # Skills loader
โ โโโ session/
โ โโโ message.py # Message types
โ โโโ prompt.py # Prompt management
โโโ examples/ # Example scripts
โโโ tests/ # Test suite
Development
Setup
# Clone the repository
git clone https://github.com/WalterSumbon/minicode.git
cd minicode
# Install in development mode
pip install -e ".[dev]"
Running Tests
# Run all unit tests (excludes integration tests)
pytest
# Run with coverage
pytest --cov=minicode
# Run integration tests (makes real API calls)
pytest -m integration
# Run specific test file
pytest tests/test_web_tools.py -v
See tests/README.md for detailed testing documentation.
Code Style
# Format code
black src/
# Lint code
ruff check src/
# Type check
mypy src/
Design Principles
- Simple and Clean - Code should be easy to understand and modify
- Async First - Built with async/await for efficient operations
- Type Safe - Full type annotations for better IDE support
- Extensible - Easy to add custom LLMs, tools, and integrations
- Minimal Dependencies - Only essential packages included
Comparison with opencode
| Feature | opencode (TypeScript) | minicode (Python) |
|---|---|---|
| Language | TypeScript | Python |
| LLM Support | Multiple providers | Extensible (OpenAI included) |
| Tool System | โ | โ |
| MCP Support | โ | โ |
| Skills | โ | โ |
| Async | โ | โ |
| Type Safety | โ | โ (with type hints) |
Roadmap
Planned Features
-
Advanced Tools
- โ AskUserQuestion - Interactive user Q&A with timeout and default answer support
- โ Bash - Execute bash commands with timeout support and background execution
- โ BashOutput - Monitor output from background processes
- โ KillShell - Terminate background processes
- โ Glob - File pattern matching tool
- โ Grep - Code search based on ripgrep (with Python fallback)
- โ Edit - Precise string replacement in files
- โ WebFetch - Fetch and process web content (HTML to Markdown/text)
- โ WebSearch - Web search integration (Exa + configurable backends)
- โ NotebookEdit - Jupyter notebook cell editing (replace/insert/delete)
-
Advanced Features
- โ User Interaction - AskUserQuestion tool for agent-user communication
- โ Background Process Management - BashOutput and KillShell tools for managing long-running processes
- โ Task Management - TodoWrite tool for tracking agent tasks
- Permission System - Optional callback mechanism for tool execution confirmation
- File Locking - Prevent concurrent file modification conflicts
- LSP Integration - Language Server Protocol for code intelligence and diagnostics
- Multi-Agent System - Task delegation and sub-agent management
Current Limitations
- No built-in permission/confirmation system (tools execute directly)
- No concurrent file operation protection
- No LSP integration for code diagnostics
These features are intentionally omitted to keep minicode simple and focused. They can be added as optional extensions or in future versions based on community needs.
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
License
MIT License - see LICENSE file 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 minicode_sdk-0.1.0.tar.gz.
File metadata
- Download URL: minicode_sdk-0.1.0.tar.gz
- Upload date:
- Size: 107.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.10.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b47aeecfc5573e6d62421a4c00042b82633b25341fa206c64cabc44868345c78
|
|
| MD5 |
ae330c9a9c68c9864da6fb25b83de4d1
|
|
| BLAKE2b-256 |
3d6d53a2d2bf43bc83e253e5c752039ff44700f6cc2f3a63eb7395ca13bc8fba
|
File details
Details for the file minicode_sdk-0.1.0-py3-none-any.whl.
File metadata
- Download URL: minicode_sdk-0.1.0-py3-none-any.whl
- Upload date:
- Size: 89.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.10.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1dbb7dc1f006790ac4fb8bf25e799aa6d5299c70db05e42a64bffac8f1b40ad0
|
|
| MD5 |
f3b3f8923f121194e06fcfc64a5c3d2a
|
|
| BLAKE2b-256 |
fae90793e2c39d9016638358c8d09da67d8d0c4ea6706b68377ede68bdd10777
|