any2mcp

any2mcp

Convert plain Python modules into MCP servers without decorators or boilerplate, automatically exposing functions as schematized tools.

Category
访问服务器

README

any2mcp

Turn any Python module into an MCP server — without handing a language model your shell.

CI PyPI Python License: MIT

You have a file full of useful Python functions and you want an agent to call them. Today that means importing an MCP framework and decorating every function — so your business logic now depends on MCP.

any2mcp skips that. Point it at a module and every public function becomes a fully-schematized MCP tool, with argument types and descriptions derived from the type hints and docstrings you already wrote:

any2mcp ./tools.py

But "expose every function" is a dangerous default, and most MCP tooling stops there. If your module happens to contain a run_shell() or a delete_path(), that naive approach just gave a language model your machine.

So any2mcp reads your code before it exposes it:

$ any2mcp examples/devtools.py --risk-report

any2mcp risk report: examples/devtools.py
policy: guard (allows up to 'moderate')

  RISK      TOOL               CAPABILITIES  STATUS
  --------  -----------------  ------------  -------
  safe      word_count         -             exposed
  low       read_project_file  fs-read       exposed
      - .read_text (line 36) [heuristic]
  low       list_directory     fs-read       exposed
      - os.listdir (line 44)
  moderate  write_note         fs-write      exposed
      - .write_text (line 50) [heuristic]
  low       parse_json_file    fs-read       exposed
      - open (line 56)
  high      run_shell          subprocess    BLOCKED
      - subprocess.run (line 66)
      ! blocked: risk 'high' (subprocess) exceeds policy 'guard' (max 'moderate')
  high      delete_path        fs-delete     BLOCKED
      - shutil.rmtree (line 85)
      - .unlink (line 87) [heuristic]
      ! blocked: risk 'high' (fs-delete) exceeds policy 'guard' (max 'moderate')

5 exposed, 2 blocked.

The two dangerous functions are not exposed, and no flags were needed to get that.


The difference

Before — one decorator per function, in a file that now depends on MCP:

from mcp.server.fastmcp import FastMCP

mcp = FastMCP("calculator")

@mcp.tool()
def add(a: float, b: float) -> float:
    """Add two numbers."""
    return a + b

@mcp.tool()
def divide(a: float, b: float) -> float:
    """Divide a by b."""
    return a / b

# ...repeat for every function, forever

if __name__ == "__main__":
    mcp.run()

After — your module stays plain Python and knows nothing about MCP:

# calculator.py — no imports, no decorators
def add(a: float, b: float) -> float:
    """Add two numbers."""
    return a + b

def divide(a: float, b: float) -> float:
    """Divide a by b."""
    return a / b
any2mcp calculator.py

Install

pip install any2mcp
# or, with uv:
uv tool install any2mcp

Not yet on PyPI? Install straight from source: uv tool install "git+https://github.com/SanoberRehman/any2mcp"

Quickstart

any2mcp tools.py --risk-report       # audit what each function can reach
any2mcp tools.py --list              # show the exposed tools + JSON schemas
any2mcp tools.py                     # serve over stdio
any2mcp tools.py:add                 # expose only `add`
any2mcp tools.py --exclude '_*'      # filter with globs
any2mcp mypackage.tools              # a dotted, importable module works too

The safety model

This is the part worth reading carefully, because the guarantees are uneven and it matters which is which.

What is enforced

A blocked function is never registered as a tool. It is absent from tools/list, so the model cannot see it, cannot call it, and cannot talk its way past it. There is no confirmation token to forge and no runtime check to race — the tool simply does not exist on the server.

You can watch this happen against a real MCP client:

$ python examples/demo_safety.py

=== any2mcp devtools.py (default policy: guard) ===
tools visible to the model: ['word_count', 'read_project_file', 'list_directory', 'write_note', 'parse_json_file']
  run_shell: withheld; calling it anyway -> Unknown tool: run_shell
  delete_path: withheld; calling it anyway -> Unknown tool: delete_path
  word_count still works -> {"words": 3, "lines": 1, "characters": 18}

What is a heuristic

Which functions get classified as risky. any2mcp parses your module and looks for calls that reach the filesystem, the network, subprocesses, or the dynamic-execution builtins. It resolves import aliases, so both of these are caught:

import subprocess as sp
def a(cmd): sp.run(cmd)              # -> subprocess (high)

from os import system as sh
def b(cmd): sh(cmd)                  # -> subprocess (high)

and it follows calls within your module, so a wrapper inherits its helper's capabilities:

def _upload(data): requests.post("https://...", data=data)
def publish(data): _upload(data)     # -> network, "via _upload()"

But static analysis is not sound, and indirection defeats it:

def sneaky(cmd):
    getattr(os, "system")(cmd)       # -> reported SAFE. Not detected.

So a safe classification means "no known-risky call was found", never "this function is harmless". These blind spots are asserted in tests/test_risk.py under TestDocumentedBlindSpots, so this claim cannot quietly drift into implying more than it should.

What it is not

any2mcp is not a sandbox. Tools that are exposed run in-process with your full privileges. The policy decides what gets offered to a model; it does not contain what happens next.

Importing a module also executes its top-level code. That is why --risk-report never imports a file target — it works from source, so you can audit code before running any of it, and even audit a module whose dependencies you don't have installed.

Policies

Policy Allows Use it when
open everything You have read the code and want the 0.1 behaviour.
guard (default) up to moderate — reads, writes, network General use. Blocks subprocesses, eval, recursive deletes, and anything unanalyzable.
readonly up to low — reads, env The agent should look but not touch.
strict safe only Pure computation, no side effects of any kind.

Overrides, in order of precedence — naming a specific tool beats a category rule, and an explicit deny always wins:

any2mcp tools.py --deny-tool 'admin_*'        # never expose these
any2mcp tools.py --allow-tool run_shell       # expose this one on purpose
any2mcp tools.py --deny-capability network    # block a whole capability

Capabilities are fs-read, fs-write, fs-delete, network, subprocess, dynamic-exec, and env.

Use it as a CI gate

--risk-report exits 3 when anything is blocked and 0 when nothing is, so it can fail a build if someone adds a subprocess call to a module you expose to agents:

- run: any2mcp src/agent_tools.py --risk-report --format json

Audit log

any2mcp tools.py --audit-log calls.jsonl

Every call is appended as one JSON object. Argument names and types are recorded; values are not — tool arguments routinely carry API keys and file contents, and a log that silently copied them to disk would be its own vulnerability. Opt in with --audit-values when you've decided that's appropriate for your data.

{"ts": "2026-07-28T05:45:14.604+00:00", "event": "blocked", "tool": "run_shell",
 "risk": "high", "reason": "risk 'high' (subprocess) exceeds policy 'guard' (max 'moderate')"}
{"ts": "2026-07-28T05:45:14.612+00:00", "event": "call", "tool": "write_note",
 "risk": "moderate", "duration_ms": 0.431, "ok": true,
 "args": {"path": "str", "text": "str"}}
{"ts": "2026-07-28T05:45:14.614+00:00", "event": "call", "tool": "word_count",
 "risk": "safe", "duration_ms": 0.003, "ok": true, "args": {"text": "str"}}

Blocked tools are recorded too, so the log shows what was withheld, not just what ran.

Rich schemas, for free

any2mcp doesn't invent its own type system — it hands each function to the MCP SDK's pydantic machinery, the same code path the official @tool decorator uses. So the hard cases just work: Optional[...], unions, Enum, Literal, list/dict/tuple, pydantic models and dataclasses as parameters, defaults, and async def.

// any2mcp calculator.py --list  (excerpt for round_to)
{
  "name": "round_to",
  "input_schema": {
    "properties": {
      "value":   { "type": "number" },
      "ndigits": { "type": "integer", "default": 2 },
      "mode":    { "$ref": "#/$defs/Rounding", "default": "nearest" }
    },
    "$defs": { "Rounding": { "enum": ["up", "down", "nearest"], "type": "string" } },
    "required": ["value"]
  }
}

A function whose signature genuinely can't be schematized is skipped with a message on stderr — one unsupported function costs one tool, never the whole server.

Use it with Claude Desktop (or any MCP client)

{
  "mcpServers": {
    "my-tools": {
      "command": "any2mcp",
      "args": ["/absolute/path/to/tools.py"]
    }
  }
}

Add "--policy", "readonly" to the args array to tighten it further.

Which functions get exposed?

Resolved in this order:

  1. A :selector (tools.py:add) exposes exactly that one function.
  2. Otherwise, if the module defines __all__, that list is the allow-list.
  3. Otherwise, every public function (no leading underscore) defined in the module — functions merely imported into it are skipped, so from os.path import join won't leak in as a tool.

--include / --exclude globs apply on top, then the policy decides what survives.

Roadmap

  • [ ] Policy file (any2mcp.toml) so per-tool decisions can be reviewed in git
  • [ ] Expose class methods and @staticmethods
  • [ ] Resources & prompts, not just tools
  • [ ] Taint tracking, to tell read_file("/etc/passwd") from read_file(user_path)

OpenAPI and FastAPI conversion are intentionally out of scope — fastmcp already does those well. any2mcp focuses on the gap they leave: plain modules, zero decoration, and a defensible answer to "what did I just expose?"

Development

uv sync
uv run pytest        # 127 tests, incl. real MCP client sessions
uv run ruff check .

License

MIT — see LICENSE.

推荐服务器

Baidu Map

Baidu Map

百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。

官方
精选
JavaScript
Playwright MCP Server

Playwright MCP Server

一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。

官方
精选
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。

官方
精选
本地
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。

官方
精选
本地
TypeScript
VeyraX

VeyraX

一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。

官方
精选
本地
graphlit-mcp-server

graphlit-mcp-server

模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。

官方
精选
TypeScript
Kagi MCP Server

Kagi MCP Server

一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。

官方
精选
Python
e2b-mcp-server

e2b-mcp-server

使用 MCP 通过 e2b 运行代码。

官方
精选
Neon MCP Server

Neon MCP Server

用于与 Neon 管理 API 和数据库交互的 MCP 服务器

官方
精选
Exa MCP Server

Exa MCP Server

模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。

官方
精选