mcp-skill
Turn any MCP server into a typed Python SDK.
Compile MCP tools into code.
mcp-skill introspects an MCP server and generates a Python class where each tool becomes a typed async method.
Before and After
Before
Agents call MCP tools through the model loop — one round-trip per tool call:
llm.call_tool("web_search_preview", {"query": "..."})
# → model decides next step → calls another tool → model decides again → ...
After
Agents call tools directly in code:
result = await app.web_search_preview(
objective="find latest news",
search_queries=["topic X 2026"]
)
# Agent processes result in code — no round-trip back to model
How It Works
┌─────────────┐ ┌───────────────┐ ┌────────────────────┐
│ MCP Server │─────▶│ mcp-skill │─────▶│ Generated Skill │
│ (any URL) │ │ CLI │ │ │
│ │ │ │ │ app.py │
│ Tools: │ │ 1. Connect │ │ ├─ Typed class │
│ - search │ │ 2. Introspec │ │ ├─ Async methods │
│ - fetch │ │ 3. Map types │ │ ├─ Auth + storage │
│ - ... │ │ 4. Generate │ │ └─ JSON parsing │
│ │ │ 5. Validate │ │ │
└─────────────┘ └───────────────┘ │ SKILL.md │
│ └─ Agent docs │
└────────────────────┘
- Connects to the MCP server using fastmcp
- Introspects all available tools via
list_tools() - Converts each tool's JSON Schema into Python type annotations
- Generates a typed
Appclass where each MCP tool becomes anasyncmethod - Validates the output with
ast.parse→ruff→ty - Generates
SKILL.mdwith tool documentation and usage examples for agents
Motivation
MCP servers give agents access to tools, but every tool call round-trips through the model — request tool, execute, full result back into context, decide next step. For large payloads or sequential calls, this burns tokens and adds latency.
Programmatic Tool Calling fixes this: the agent writes code that calls tools directly, without model round-trips per invocation. Fetch, filter, aggregate — all in one code block.
mcp-skill makes this possible by compiling any MCP server into a plain Python class. Each tool becomes a typed async method. The agent just writes Python.
For a deeper explanation of why MCP vs. CLI is the wrong framing, see MCP vs CLI is the Wrong Question.
from parallel_search.app import ParallelApp
app = ParallelApp(auth="sk-...")
result = await app.web_search_preview(
objective="find latest news on topic X",
search_queries=["topic X 2026"]
)
Setup
# Install with uv
uv pip install -e .
# Or use directly without installing globally
uvx --from . mcp-skill create --url https://your-mcp-server.com/mcp --auth api-key
Requires uv and Python 3.10+.
Release Workflow
Releases are managed by release-please, which only considers commits on main that follow the Conventional Commits format.
Use commit subjects like:
feat: add CLI command to inspect generated apps
fix: handle auth recovery when cached credentials are stale
chore: update release workflow docs
Version bump behavior:
feat:creates a minor releasefix:creates a patch releasefeat!:or aBREAKING CHANGE:footer creates a major release
Best practices:
- Prefer squash merges so the PR title becomes the commit on
main - Write PR titles in Conventional Commits format
- Keep the subject line short and imperative
- Add a short body when the change needs extra context
Optional local enforcement with pre-commit:
uv tool install pre-commit
pre-commit install
pre-commit install --hook-type commit-msg
This repo includes a commit-msg hook in .pre-commit-config.yaml that rejects non-conventional commit subjects before they are created.
If release-please says a commit "could not be parsed", push a new conventional commit to main and rerun the workflow.
Usage
# Interactive mode — prompts for URL, auth type, etc.
uvx --from . mcp-skill create
# Non-interactive mode
uvx --from . mcp-skill create \
--url https://search-mcp.parallel.ai/mcp \
--auth api-key \
--api-key YOUR_KEY \
--name parallel-search \
--non-interactive
Discover Existing Apps
Use the CLI as a local catalog for both version-controlled skills/ apps and generated .agents/skills/ apps.
# List every discovered app
uvx --from . mcp-skill list-apps
# List callable functions for a specific app
uvx --from . mcp-skill list-functions notion
# Inspect a specific function's signature and docstring
uvx --from . mcp-skill inspect notion notion_search
Use an App in Python
Once you find the app and method you need, call it from async Python:
import asyncio
from sentry.app import SentryApp
async def main():
sentry = SentryApp()
user = await sentry.whoami()
print(user)
asyncio.run(main())
All generated tool wrappers are async. Use them carefully with await inside an async function. If you skip await, you will get a coroutine object instead of the actual result.
Generated Output
The skill lands in .agents/skills/<name>/ as a Python package:
.agents/skills/parallel_search/
├── __init__.py
├── app.py # Typed Python class wrapping the MCP server
└── SKILL.md # Agent-facing docs, dependencies, and usage
Here's what the generated app.py looks like:
class ParallelApp:
def __init__(self, url: str = "https://...", auth=None) -> None:
...
async def web_search_preview(
self,
objective: str,
search_queries: list[str],
) -> dict[str, Any]:
"""Search the web with multiple queries in parallel."""
...
async def fetch_url(
self,
url: str,
max_length: int = None,
) -> dict[str, Any]:
"""Fetch and extract content from a URL."""
...
def list_tools(self):
return [self.web_search_preview, self.fetch_url]
Each method connects to the MCP server, calls the underlying tool, and returns parsed JSON. Auth credentials are persisted to ~/.mcp-skill/auth/ after first use — keyed by server URL, so credentials persist across restarts automatically.
Who Is This For?
Developers building:
- MCP-based agents that need direct tool access without model round-trips
- Automation systems using MCP tools as programmatic building blocks
- Code-execution agents using Programmatic Tool Calling
Current Limitations
- Auth: Supports API key (Bearer or custom header), OAuth (including PKCE with either dynamic client registration or pre-registered
client_id), and none — no mTLS - Runtime dependency: Generated code depends on fastmcp for MCP client connections
- Connection per call: Each method creates a new MCP client connection (no pooling)
- Tools only: MCP resources and prompts not yet supported
Task List
Tracked improvements based on real-world usage:
- Fix output directory path — Changed from
.agents/skill/<name>to.agents/skills/<name> - Add dependency info to SKILL.md — Dependencies listed with
uvandpipinstall commands - Generate
__init__.py— Skill directory is a proper Python package - Post-generation validation —
ast.parse→ruff check→ty checkwithuvxfallback - Package-style imports — Moved
app.pyto skill root; import viafrom <skill>.app import <Class> - Persistent token storage — FileTree-backed credential storage at
~/.mcp-skill/auth/, keyed by server URL - Unified auth signature — All auth types use
auth=Nonein__init__ - Sanitize skill names — Hyphens/dots converted to underscores for valid Python identifiers
- Async CLI — CLI commands are fully async via
asyncclick - Local app discovery —
mcp-skill list-apps,mcp-skill list-functions <app>, andmcp-skill inspect <app> <function> - Support MCP resources and prompts — Currently only tools are introspected and generated
Metadata
Release files for mcp-skill 0.4.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| mcp_skill-0.4.0.tar.gz | 179.3 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| mcp_skill-0.4.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 205.0 kB
Release files / mcp_skill-0.4.0.tar.gz
| Download URL | mcp_skill-0.4.0.tar.gz |
|---|---|
| Size | 179.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
f9745f497256452627bdb61fc54c28f2ad36ef16c8add140f8f8fc7c156c8c6f
|
|
BLAKE2b-256 checksum How to use checksums |
d6a369aa70c66a62fa12bdee492794b0d2e6516e06639a68aa14b15fafc4cced
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.10.11 {"installer":{"name":"uv","version":"0.10.11","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / mcp_skill-0.4.0-py3-none-any.whl
| Download URL | mcp_skill-0.4.0-py3-none-any.whl |
|---|---|
| Size | 25.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
a01225645cea6b0debb29f952b24306c3240e089c1fde70b97a8c551a8537307
|
|
BLAKE2b-256 checksum How to use checksums |
19f9ce0581ea1a69ecdeea0ed9282ef0805fc3c532f724bbcdb9c087b06f425b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.10.11 {"installer":{"name":"uv","version":"0.10.11","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Security for mcp-skill 0.4.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.