Skip to main content
Glama
linga009

mcp-library-builder

by linga009

mcp-library-builder

CI License: MIT Python 3.10+

Turn any Python library into a real, standalone MCP server โ€” automatically.

Not toy examples (a calculator, a weather lookup). Real libraries: math, pandas, requests, simpy, keras, and anything else you can pip install.

Why

Tool/function calling exists because LLMs are unreliable at precise computation and at tracking state accurately. This project takes that to its logical conclusion: an LLM's job is a one-time build step โ€” reading a library's structure and generating a clean MCP server for it โ€” never a runtime dependency. Once generated, a server is plain, deterministic Python; using it involves no LLM computation, guessing, or hallucinated state.

Related MCP server: AutoMCP

How it works

flowchart LR
    lib["Python library\n(math, pandas, ...)"] --> introspect["Introspect\ninspect"]
    introspect --> classify["Classify\nfunction / constructor / method"]
    classify --> curate["Curate ๐Ÿง \nLLM picks the useful tools"]
    curate --> generate["Generate\nJinja2 templates"]
    generate --> validate["Validate\nimport-check"]
    validate --> out["Real MCP server\nplain, deterministic Python"]

    style curate fill:#fff3cd,stroke:#997404,stroke-width:2px

Only the highlighted Curate step ever touches an LLM โ€” deciding which of a library's functions are common enough to be first-class tools. Every other step (introspecting the real library, generating the server, checking that it actually imports) is deterministic Python with zero hallucination risk by construction, since it only ever reads and renders real code.

Status

Phase 1 (this codebase): the full pipeline works end-to-end for stateless libraries โ€” including keyword-only parameters, *args/**kwargs, optional parameters, and libraries containing a function named after the module itself (or after the generated server's own bindings). It has been swept against 77 real standard-library modules, all of which generate importable servers, and against combinations of up to 10 libraries in one aggregated bundle (288 tools generated and registered correctly in that run). Stateful, object-oriented libraries (simpy, keras) are Phase 2.

Tried on real libraries

Past the standard library, this has been run against two genuinely complicated real combinations, unfiltered โ€” including the failures, which are more informative than the successes:

  • A real ML-workflow bundle: numpy + scipy + pandas + scikit-learn

    • mlflow, combined into one server with build_aggregated_server. First run: 210 real tools, one process, everything called back computed a correct answer (numpy_zeros, pandas_unique, sklearn_get_config, ...). It also found two real bugs, both since fixed:

    • mlflow.register_prompt has a legitimate model_config parameter that collides with a name Pydantic reserves internally, which briefly took the entire build down. Fixed by renaming just that one parameter in the generated wrapper's own signature (model_config โ†’ model_config_param) rather than dropping the function โ€” same fix applied to the pre-existing underscore-prefix case (hashlib.file_digest's _bufsize โ†’ bufsize), which used to be dropped outright and is now a real, callable tool too.

    • numpy's most-used functions (mean, sum, dot, median, ...) were entirely invisible โ€” not even logged โ€” because numpy wraps them in a private dispatcher type inspect.isfunction/isbuiltin doesn't recognize. Broadening the check to inspect.isroutine (still verified to exclude ordinary callable objects) took numpy alone from 60 to 245 introspectable functions in this same bundle. mean/sum themselves still don't make the final tool list โ€” they have a separate, already-handled problem (a sentinel default that isn't a literal, same class of issue as pandas.read_csv) โ€” but they're now visibly skipped with a reason instead of silently absent, and most of their siblings (dot, median, transpose, reshape, ...) are fully recovered.

    Rerun with both fixes: 397 tools, one process. (tensorflow/keras still couldn't be included โ€” no PyPI build of TensorFlow exists yet for the Python version this was run on, and Keras won't import without a working TensorFlow backend. Not a limitation of this tool.)

  • The snake bites its own tail: this project's own generator/curation.py imports langchain.chat_models.init_chat_model to power its LLM step. Pointed at langchain.chat_models and langgraph.graph โ€” the exact modules this tool itself depends on โ€” it correctly generated langchain_chat_models_init_chat_model and langgraph_graph_add_messages as real, working tools. add_messages, called with real langchain_core.messages objects, returned the exact merged result its own docstring documents.

See Limitations for what these runs still reveal about where the tool's real edges are.

Install

pip install -e ".[dev]"
cp .env.example .env  # fill in an API key for curation

Usage

python -m generator.server

This starts the generator's own MCP server, exposing two tools:

  • build_mcp_server(library_name: str) -> str โ€” generates a server for the named importable library and returns the path to the generated file. Output goes to ./generated_servers/<library_name>/server.py, relative to the directory you run the tool from.

  • build_aggregated_server(library_names: list[str], bundle_name: str) -> str โ€” generates one combined server spanning several importable libraries, so a project needing multiple libraries gets a single MCP process and a single client config entry instead of one per library. Each library's tools are prefixed with its own name (math_sqrt, pandas_read_csv, ...) to avoid collisions between libraries. A library that fails is skipped rather than failing the whole build; output goes to ./generated_servers/<bundle_name>/server.py.

flowchart TB
    subgraph withoutAgg["Without aggregation โ€” one process per library"]
        direction LR
        m1["math server"]
        p1["pandas server"]
        r1["requests server"]
    end
    subgraph withAgg["One build_aggregated_server call โ€” one process"]
        direction LR
        bundle["math_sqrt ยท pandas_read_csv ยท requests_get ยท ..."]
    end
    withoutAgg -.-> withAgg

Generation is validated before it is reported as successful: the curated function list is checked against what introspection actually found, and the generated module is imported. A run that would produce unusable output raises generator.server.GenerationError rather than handing back a path to code nobody checked.

Individual functions that cannot be expressed as MCP tools are skipped so the rest of the library still generates โ€” a default value that is not a Python literal (shutil.copytree's copy_function=<function copy2 at 0x...>), or a signature inspect cannot read at all (math.log). A parameter name MCP tool registration would otherwise reject (a leading _, or a collision with a Pydantic-reserved name) is renamed rather than costing the function โ€” see Limitations. Each skip that does happen is reported at INFO on the generator.introspection logger, with the reason.

What a generated server looks like

build_mcp_server("math"), curated down to just sqrt, produces this โ€” real output, not a mockup:

# -*- coding: utf-8 -*-
"""Generated MCP server for math.

DO NOT EDIT BY HAND โ€” regenerate via generator.build_mcp_server().
"""

from mcp.server.mcpserver import MCPServer as _MCPServer
import math as _lib

_server = _MCPServer(name="math-mcp-server")

@_server.tool()
def sqrt(x: float) -> float:
    'Return the square root of x.'
    return _lib.sqrt(x)

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

No LLM calls anywhere in that file โ€” it's a plain, deterministic wrapper around the real math.sqrt, readable and auditable like any other generated code. An aggregated bundle looks the same, just with one import ... as _<library> line and one @_server.tool() function per promoted function across every requested library, each tool name prefixed with its own library's name (math_sqrt, time_time, ...).

Using a generated server

The output is a normal stdio MCP server โ€” point any MCP client at it the same way you'd point it at a hand-written one. For Claude Desktop or Claude Code, add it to the client's MCP server config:

{
  "mcpServers": {
    "math": {
      "command": "python",
      "args": ["generated_servers/math/server.py"]
    }
  }
}

For an aggregated bundle, point at generated_servers/<bundle_name>/server.py instead โ€” one config entry covers every library in that bundle.

Limitations

Found through actual use, not guessed at upfront:

  • Only module-level plain functions are introspected โ€” no classes, no methods, no state. This is why a library organized around objects rather than free functions can look nearly empty: scipy's bare top level yields 1 function, sklearn's yields 5 (config utilities, not train_test_split or any estimator), and langchain/langgraph's yield 0 โ€” their real API lives one or two levels down, in submodules or classes this tool doesn't walk yet. Point it at the specific submodule you actually want (scipy.stats, langchain.chat_models) rather than the bare package name. Stateful/object-oriented support is the planned Phase 2.

  • A parameter name MCP tool registration would otherwise reject โ€” a leading _, or a collision with a Pydantic-reserved name like model_config โ€” is renamed in the generated wrapper's own signature rather than costing the whole function. hashlib.file_digest's _bufsize becomes bufsize; mlflow.register_prompt's model_config becomes model_config_param; the real call still uses the library's real keyword. Only still skipped (and logged) if the rename itself would collide with another parameter of the same function.

  • A default value that isn't a literal Python expression still costs the function. shutil.copytree's copy_function=<function copy2 ...>, numpy's keepdims=<no value> sentinel on many reduction functions, pandas.read_csv's sep=<no_default> โ€” none of these can be renamed away, since the problem is the value, not the name. Safely skipped and logged; one bad function never takes down the rest of a library or bundle, but the promoted tool list is sometimes narrower than the library's real surface.

  • Curation quality depends on the LLM you point it at. Every real trial above used a stand-in that promotes everything introspection found, to stress-test the generation pipeline itself rather than an LLM's judgment about what's "useful" โ€” that judgment call hasn't been evaluated yet.

Contributing

This was built out of curiosity, to see if it'd be useful to someone โ€” consider this an open invitation rather than a finished product. Concretely useful things to try or pick up:

  • Try it on a library not listed above and open an issue with what happened โ€” especially the failures. A silent skip nobody logged, or a new whole-build-breaking collision, is exactly the kind of thing real use finds and a test suite doesn't โ€” both model_config and numpy's dispatcher-wrapped functions were found this way and are now fixed.

  • Submodule walking โ€” so build_mcp_server("scipy.stats") doesn't require knowing the submodule name upfront.

  • Phase 2: class/state support โ€” the biggest single gap. Most of what makes pandas, sklearn, and keras actually useful is object-oriented, and none of it is reachable yet.

  • Anything in Limitations above.

PRs and issues both welcome.

Running the tests

pip install -e ".[dev]"
pytest -v

137 tests, real behavior throughout: generated code is actually written to disk, imported, and called โ€” nothing is mocked except the LLM curation call itself. CI runs this same suite, plus linting, against Python 3.10 through 3.14 on every push and pull request.

Linting

ruff check .
ruff format generator/ tests/

ruff format is deliberately scoped to the actual source directories, not the whole repo โ€” it also reformats fenced code blocks inside .md files, which would silently drift the README's real, verified generated-server sample away from being byte-for-byte accurate.

License

MIT

Maintenance

ActivityMaintained
ResponsivenessSyncing

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Connectors

Related MCP Servers

  • F
    license
    Not graded
    quality
    D
    maintenance
    A minimal Python package for easily setting up and running MCP servers and clients, allowing functions to be automatically exposed as tools that LLMs can use with just 2 lines of code.
    22
  • A
    license
    Not graded
    quality
    B
    maintenance
    Automatically generates MCP server tools from OpenAPI specifications, enabling LLMs to interact with any API defined by an OpenAPI spec through natural language.
    19
    MIT
  • A
    license
    Not graded
    quality
    B
    maintenance
    Convert plain Python modules into MCP servers without decorators or boilerplate, automatically exposing functions as schematized tools.
    MIT

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/linga009/mcp-library-builder'

If you have feedback or need assistance with the MCP directory API, please join our Discord server