MCP utilities¶
Shared machinery for building MCP servers and clients used by Klea apps: metadata schemas, tool registration, an httpx session lifespan, path permission checks, and the reusable bundled tools server.
Schemas¶
Schemas shared by MCP servers and clients.
File: klea_utils/mcp/schemas.py
Copyright 2026 Ankur Sinha Author: Ankur Sinha <sanjay DOT ankur AT gmail DOT com>
- class klea_utils.mcp.schemas.ToolCallSchema(*, tool: str = '', args: dict[str, ~typing.Any]=<factory>, reason: str = '')[source]¶
Bases:
BaseModelA single tool call selected by a tools picker node.
- model_config = {}¶
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class klea_utils.mcp.schemas.ToolCallsSchema(*, tool_calls: list[ToolCallSchema] = <factory>)[source]¶
Bases:
BaseModelThe structured output of a tools picker node: a list of tool calls.
- model_config = {}¶
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class klea_utils.mcp.schemas.ToolInfo(*, description: str | None = None, title: str | None = None, tags: set[str] | None = None, checkpaths: list[str] | None = None, meta: dict[str, Any] | None = None, read_only: bool | None = None, destructive: bool | None = None, idempotent: bool | None = None, open_world: bool | None = None)[source]¶
Bases:
BaseModelMetadata used to describe an MCP tool to clients and models.
The
read_only/destructive/idempotent/open_worldfields map 1:1 to the standard MCPToolAnnotationshints (readOnlyHint/destructiveHint/idempotentHint/openWorldHint); see https://fastmcp.wiki/en/servers/tools#mcp-annotations for what each hint means and how a client is expected to act on it.klea_utils.mcp.registry.register_tools()folds them onto the registered tool.- model_config = {}¶
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
Errors¶
Custom error classes for the MCP tooling.
File: klea_utils/mcp/errors.py
Copyright 2026 Ankur Sinha Author: Ankur Sinha <sanjay DOT ankur AT gmail DOT com>
- exception klea_utils.mcp.errors.DocumentConversionError[source]¶
Bases:
ExceptionRaised when a document file cannot be converted to text.
Carries a user-facing message that tools report through their
errorresult field.
- exception klea_utils.mcp.errors.PermissionDeniedError[source]¶
Bases:
PermissionErrorRaised when a tool is denied access to a path.
Tool registration¶
Shared MCP tool registration helpers.
File: klea_utils/mcp/registry.py
Copyright 2026 Ankur Sinha Author: Ankur Sinha <sanjay DOT ankur AT gmail DOT com>
- klea_utils.mcp.registry.register_tools(mcp: fastmcp.FastMCP, modules: list[ModuleType])[source]¶
Register tools from the given modules.
A function is registered as a tool when it is decorated with
tool_meta()(which attachesToolInfometadata). The function name is used as the tool name. Helper functions in the same module that are not decorated are ignored (logged at debug level, so a forgotten decoration is easy to spot). Only functions defined in the given module are registered, so an imported decorated function is not picked up accidentally.- Parameters:
mcp – FastMCP server to register the tools on.
modules – list of modules with tool function definitions
- klea_utils.mcp.registry.tool_meta(metadata: ToolInfo)[source]¶
Decorator that attaches
ToolInfometadata to a tool function.Usage:
@tool_meta(ToolInfo(tags={"bundled", "web"})) async def web_fetch(ctx: Context, url: str, ...): ...
The metadata is read by
register_tools()when the function is registered on a FastMCP server (it setsdescription,title,tags, andmetaon the tool if provided). A function is only registered as a tool when it carries this decoration; the function name is used as the tool name.- Parameters:
metadata –
ToolInfoto attach to the decorated function.- Returns:
The decorated function, unchanged, with
_tool_metaset.
Tool call dispatch¶
Client-side MCP tool-call dispatch with permission gating.
File: klea_utils/mcp/dispatch.py
Copyright 2026 Ankur Sinha Author: Ankur Sinha <sanjay DOT ankur AT gmail DOT com>
- async klea_utils.mcp.dispatch.dispatch_tool_calls(mcp_client: Any, tool_calls: list[tuple[str, dict[str, Any]]], tools_meta: dict[str, dict[str, Any]] | None = None, project_root: str | None = None) list[fastmcp.client.client.CallToolResult][source]¶
Gate and dispatch tool calls against an MCP server.
For each
(tool, args)pair, the tool’smeta(fromtools_meta) is checked withcheck_tool_arguments_permissions()before the call reaches the server; denied calls never touch the server and instead produce a synthetic non-halting error result. Allowed calls are dispatched in parallel, and the returned list stays aligned with the input tool_calls order.- Parameters:
mcp_client – MCP client used for
call_tool. Its reentrant context is entered/exited by this helper. Typed asAnybecausefastmcp.Client.call_toolhas a complex signature (optionalarguments, keyword-onlyraise_on_error,CallToolResult | ToolTaskreturn) that a structural protocol would not cleanly match; tests substitute a fake implementing the subset used here.tool_calls –
(tool name, arguments)pairs to invoke.tools_meta – Mapping of tool name to the tool’s
metadict (e.g.{t.name: t.meta for t in mcp_tools}). Path arguments declared undercheckpathsare permission-checked client-side.project_root – Boundary directory for the permission gate. Defaults to the current working directory.
- Returns:
One
CallToolResultper input call, in input order.
HTTP session lifespan¶
Shared FastMCP lifespan helpers.
File: klea_utils/mcp/lifespan.py
Copyright 2026 Ankur Sinha Author: Ankur Sinha <sanjay DOT ankur AT gmail DOT com>
- klea_utils.mcp.lifespan.make_http_session_lifespan(session_key: str = 'http_session')[source]¶
Create a FastMCP lifespan that provides a shared httpx session.
Tools that need an HTTP session (e.g. klea_utils.mcp.tool_impls.web_fetch) read it from
ctx.lifespan_context[<session_key>]in their MCP wrapper. Lifespans are composable with the|operator.- Parameters:
session_key – Lifespan context key under which the session is stored.
- Returns:
A FastMCP
@lifespan-decorated function.
Repository sources¶
Framework-agnostic functions that list the versions and files of archival
repositories (GitHub, FigShare, DANDI Archive, BioModels). The returned
file lists carry direct download_url values that can be fed to
klea_utils.mcp.tool_impls.download_file.download_files() (or the
single-file download_file implementation), e.g. by wrapping them into
MCP tools.
Shared helpers for the repository source implementations.
File: klea_utils/mcp/tool_impls/repositories/sources.py
Copyright 2026 Ankur Sinha Author: Ankur Sinha <sanjay DOT ankur AT gmail DOT com>
- klea_utils.mcp.tool_impls.repositories.sources.REQUEST_TIMEOUT = Timeout(timeout=30.0)¶
Request timeout for the JSON API calls, in seconds.
Error classes for the repository source implementations.
File: klea_utils/mcp/tool_impls/repositories/errors.py
Copyright 2026 Ankur Sinha Author: Ankur Sinha <sanjay DOT ankur AT gmail DOT com>
- exception klea_utils.mcp.tool_impls.repositories.errors.RepositorySourceError[source]¶
Bases:
ExceptionRaised when a repository source cannot be queried.
Carries a user-facing message that the public functions report through their
errorresult field.
GitHub repository source implementation.
File: klea_utils/mcp/tool_impls/repositories/github.py
Copyright 2026 Ankur Sinha Author: Ankur Sinha <sanjay DOT ankur AT gmail DOT com>
- klea_utils.mcp.tool_impls.repositories.github.GITHUB_API_BASE = 'https://api.github.com/repos'¶
GitHub REST API base for the
/reposcollection.
- klea_utils.mcp.tool_impls.repositories.github.PAGE_SIZE = 100¶
Page size for the branches/tags listing endpoints.
- klea_utils.mcp.tool_impls.repositories.github.RAW_BASE = 'https://raw.githubusercontent.com'¶
Base URL for direct raw file downloads.
- async klea_utils.mcp.tool_impls.repositories.github.github_list_files(session: SessionLike | None, url: str, version: str | None = None) dict[str, Any][source]¶
List the files in a GitHub repository at a given version.
The
versionis a git branch or a tag. When a name exists as both a branch and a tag, a branch is assumed (git ref resolution precedence). Ifversionis omitted, the repository’s default branch is used.- Use when:
Getting the file list of a GitHub repository so files can be downloaded.
- Parameters:
url – GitHub repository URL (https://github.com/<owner>/<repo>).
version – Branch or tag to list. Defaults to the default branch.
- Returns:
Dictionary with source, url, version, files (path, name, download_url, size), and error.
- async klea_utils.mcp.tool_impls.repositories.github.github_list_versions(session: SessionLike | None, url: str) dict[str, Any][source]¶
List the available versions (branches and tags) of a GitHub repository.
A GitHub version is a git branch or a tag; both are merged into a single list. When a name exists as both a branch and a tag, it is listed once.
- Use when:
Discovering which branches/tags a GitHub repository offers before listing its files.
- Parameters:
url – GitHub repository URL (https://github.com/<owner>/<repo>).
- Returns:
Dictionary with source, url, versions, and an empty files list.
File: klea_utils/mcp/tool_impls/repositories/figshare.py
Copyright 2026 Ankur Sinha Author: Ankur Sinha <sanjay DOT ankur AT gmail DOT com>
FigShare API v2 base URL. Even for institutional FigShare instances (e.g.
rdr.ucl.ac.uk) the article IDs and the API endpoint are shared.
Cap on the number of pages fetched, to bound runaway loops.
Page size for the article files endpoint (the API maximum is 1000).
List the files of a FigShare article.
FigShare serves the same file list for every version of an article (the files endpoint is not versioned); the
versionargument is accepted for a uniform API but only labels the result. Whenversionis omitted, the article’s current version is reported.- Use when:
Getting the file list of a FigShare article so files can be downloaded.
- Parameters:
url – FigShare article URL (e.g. https://figshare.com/articles/dataset/<title>/<article_id>).
version – Version label for the result. Defaults to the article’s current version.
- Returns:
Dictionary with source, url, version, files (path, name, download_url, size), and error.
List the available versions of a FigShare article.
- Use when:
Discovering which versions a FigShare article offers before listing its files.
- Parameters:
url – FigShare article URL (e.g. https://figshare.com/articles/dataset/<title>/<article_id>).
- Returns:
Dictionary with source, url, versions, and an empty files list.
DANDI Archive repository source implementation.
File: klea_utils/mcp/tool_impls/repositories/dandi.py
Copyright 2026 Ankur Sinha Author: Ankur Sinha <sanjay DOT ankur AT gmail DOT com>
- klea_utils.mcp.tool_impls.repositories.dandi.DANDI_API_BASE = 'https://api.dandiarchive.org/api'¶
DANDI Archive REST API base URL.
- klea_utils.mcp.tool_impls.repositories.dandi.DANDI_HOSTS = ('dandiarchive.org', 'www.dandiarchive.org')¶
Hosts that serve DANDI Archive.
- klea_utils.mcp.tool_impls.repositories.dandi.DRAFT_VERSION = 'draft'¶
The default version label, used when none is specified.
- klea_utils.mcp.tool_impls.repositories.dandi.MAX_FILES = 10000¶
Cap on the total number of files collected, to bound runaway recursion.
- klea_utils.mcp.tool_impls.repositories.dandi.MAX_PAGES = 100¶
Cap on the number of pages fetched per folder, to bound runaway loops.
- klea_utils.mcp.tool_impls.repositories.dandi.PAGE_SIZE = 100¶
Page size for the
assets/pathsendpoint.
- async klea_utils.mcp.tool_impls.repositories.dandi.dandi_list_files(session: SessionLike | None, url: str, version: str | None = None) dict[str, Any][source]¶
List the files of a DANDI dandiset at a given version.
The file tree is walked recursively via the
assets/pathsendpoint: entries with anassetare files, entries without one are folders that are descended into. Whenversionis omitted, thedraftversion is used.Use when: - Getting the file list of a DANDI dandiset so files can be downloaded.
- Parameters:
url – DANDI dandiset URL (https://dandiarchive.org/dandiset/<id>).
version – Version to list (e.g.
draftor a published version). Defaults todraft.
- Returns:
Dictionary with source, url, version, files (path, name, download_url, size), and error.
- async klea_utils.mcp.tool_impls.repositories.dandi.dandi_list_versions(session: SessionLike | None, url: str) dict[str, Any][source]¶
List the available versions of a DANDI dandiset.
The list includes the working
draftversion as well as published versions.- Use when:
Discovering which versions a DANDI dandiset offers before listing its files.
- Parameters:
url – DANDI dandiset URL (https://dandiarchive.org/dandiset/<id>).
- Returns:
Dictionary with source, url, versions, and an empty files list.
BioModels repository source implementation.
File: klea_utils/mcp/tool_impls/repositories/biomodels.py
Copyright 2026 Ankur Sinha Author: Ankur Sinha <sanjay DOT ankur AT gmail DOT com>
- klea_utils.mcp.tool_impls.repositories.biomodels.BIOMODELS_API_BASE = 'https://www.biomodels.org'¶
Canonical BioModels API base. The legacy
www.ebi.ac.uk/biomodelshost redirects here, so all API calls go to this base regardless of the host used in the model URL.
- klea_utils.mcp.tool_impls.repositories.biomodels.BIOMODELS_HOSTS = ('biomodels.org', 'www.biomodels.org')¶
Hosts that serve BioModels model pages.
- async klea_utils.mcp.tool_impls.repositories.biomodels.biomodels_list_files(session: SessionLike | None, url: str, version: str | None = None) dict[str, Any][source]¶
List the files of a BioModels model at a given revision.
The file list combines the
mainandadditionalfile groups of the model record. Whenversionis omitted, the latest revision is used.Use when: - Getting the file list of a BioModels model so files can be downloaded.
- Parameters:
url – BioModels model URL (e.g. https://www.biomodels.org/MODEL0912160000).
version – Revision number (as a string) to list. Defaults to the latest revision.
- Returns:
Dictionary with source, url, version, files (path, name, download_url, size), and error.
- async klea_utils.mcp.tool_impls.repositories.biomodels.biomodels_list_versions(session: SessionLike | None, url: str) dict[str, Any][source]¶
List the available revisions (versions) of a BioModels model.
- Use when:
Discovering which revisions a BioModels model offers before listing its files.
- Parameters:
url – BioModels model URL (e.g. https://www.biomodels.org/MODEL0912160000).
- Returns:
Dictionary with source, url, versions, and an empty files list.
Bundled tools server¶
The bundled tools server (auto-launched by apps as a stdio subprocess and
exposed standalone via the klea-mcp CLI) and its configuration.
- class klea_utils.mcp.server.config.BundledToolsConfig(*, enabled: bool = True, include_tags: set[str] = <factory>, exclude_tags: set[str] = <factory>)[source]¶
Bases:
BaseModelConfiguration for the shared bundled tools server.
Read by the app orchestrators (
BaseLangGraph) to decide whether the bundled tools server is wired into the graph and, when it is, which of its tools are exposed. Tools are filtered by tag (seeklea_utils.mcp.server.bundled_toolsfor the bundled tag vocabulary); the tags are applied to the stdio config entry so fastmcp’sTransformingStdioMCPServerenforces them.Pydantic-only on purpose: config loads must not pull in fastmcp.
- model_config = {}¶
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
Bundled tools server for Klea.
Provides the common Klea tools (web fetch, file list/read, download) as an
MCP server. Apps auto-launch this module over stdio (see
BaseLangGraph._bundled_server_config) so users get the common tools with
no extra setup; the same server can also be run standalone over HTTP via the
klea-mcp CLI for remote deployments.
Tool implementations live in klea_utils.mcp.tool_impls and the FastMCP
wrappers in klea_utils.mcp.server.bundled_tools; this module wires them
onto a FastMCP server.
File: klea_utils/mcp/server/bundled.py
Copyright 2026 Ankur Sinha Author: Ankur Sinha <sanjay DOT ankur AT gmail DOT com>
- klea_utils.mcp.server.bundled.bundle_server = 'KleaBundled'¶
The bundled FastMCP server instance. Apps embed this module as a stdio subprocess (
python -m klea_utils.mcp.server.bundled); tests and theklea-mcpCLI use it directly.
- klea_utils.mcp.server.bundled.main(transport: str = <typer.models.OptionInfo object>, port: int = <typer.models.OptionInfo object>) None[source]¶
Run the bundled tools server.
Accessed via the
klea-mcpentry point.--transport httpserves the same pre-registered tools over HTTP so a remote client (e.g. a RAG deployment that runs Klea and the bundled server on different hosts) can point itsmcp_serversconfig at this server’s URL.
FastMCP tool wrappers for the shared Klea bundled tools.
File: klea_utils/mcp/server/bundled_tools.py
Copyright 2026 Ankur Sinha Author: Ankur Sinha <sanjay DOT ankur AT gmail DOT com>
- klea_utils.mcp.server.bundled_tools.BUNDLED_TAG = 'bundled'¶
Common tags carried by every bundled tool, so “enable the common set” is a single include_tags: [“bundled”] in the app config.
- async klea_utils.mcp.server.bundled_tools.download_file(ctx: fastmcp.Context, url: Annotated[str, FieldInfo(annotation=NoneType, required=True, metadata=[MinLen(min_length=1)])], file_path: Annotated[str, FieldInfo(annotation=NoneType, required=True, metadata=[MinLen(min_length=1)])]) fastmcp.tools.ToolResult[source]¶
Download a URL to a local file.
Use this tool to fetch binary or text resources from the web and save them to disk for later reading or processing.
Use when: - Downloading a file such as a PDF, dataset, or archive. - Saving remote content locally before inspecting it.
Do not use for: - Reading a web page as text (use the web fetch tool instead).
Example: download_file(url=”https://example.com/paper.pdf”, file_path=”paper.pdf”)
- Parameters:
url – HTTP or HTTPS URL to download.
file_path – Destination path, relative to the working directory. Existing files are overwritten.
- Returns:
Dictionary with the saved path, or an error on failure.
- async klea_utils.mcp.server.bundled_tools.list_files(path: Annotated[str, FieldInfo(annotation=NoneType, required=True, description="Directory path to list. Must be relative to current working directory and cannot contain '..' for security", metadata=[MinLen(min_length=1)])], max_depth: Annotated[int | None, FieldInfo(annotation=NoneType, required=True, description="Maximum directory depth to traverse. 'None' for unlimited")] = None, pattern: Annotated[str, FieldInfo(annotation=NoneType, required=True, description="\n Space separated file patterns to filter based on files type.\n Correct: '*.py'\n Correct: '*.md'\n Correct: '*.py *.md'\n ")] = '*', include_files: Annotated[bool, FieldInfo(annotation=NoneType, required=True, description='Whether to include files in results')] = True, include_directories: Annotated[bool, FieldInfo(annotation=NoneType, required=True, description='Whether to include directories in results')] = True, recursive: Annotated[bool, FieldInfo(annotation=NoneType, required=True, description='If True, traverse subdirectories recursively')] = False, max_results: Annotated[int, FieldInfo(annotation=NoneType, required=True, description='Maximum number of entries to return', metadata=[Ge(ge=1), Le(le=10000)])] = 100) fastmcp.tools.ToolResult[source]¶
List files and directories with filtering and metadata.
Use this tool to explore the local file system structure and find specific files.
Use when: - Discovering what files exist in the working directory. - Finding files by name, type, or location.
Do not use for: - Reading a file’s contents (use the read file tool instead).
Example:
list_files(path=".", pattern="*.py", recursive=True)- Parameters:
path – Directory path to list. Must be relative to the current working directory and cannot contain ‘..’ for security.
max_depth – Maximum directory depth to traverse. ‘None’ for unlimited.
pattern – Space separated file patterns to filter files by type.
include_files – Whether to include files in results.
include_directories – Whether to include directories in results.
recursive – If True, traverse subdirectories recursively.
max_results – Maximum number of entries to return.
- Returns:
Dictionary with list of files, truncated flag, and error.
- async klea_utils.mcp.server.bundled_tools.read_file(path: Annotated[str, FieldInfo(annotation=NoneType, required=True, description="File path to read. Must be relative to current working directory and cannot contain '..' for security", metadata=[MinLen(min_length=1)])], offset: Annotated[int, FieldInfo(annotation=NoneType, required=True, description='1-indexed line to start reading from', metadata=[Ge(ge=1)])] = 1, limit: Annotated[int | None, FieldInfo(annotation=NoneType, required=True, description="Maximum number of lines to return. 'None' for end of file")] = 2000, max_chars: Annotated[int, FieldInfo(annotation=NoneType, required=True, description='Hard cap on characters of content to return', metadata=[Ge(ge=1)])] = 100000) fastmcp.tools.ToolResult[source]¶
Read a file and return a slice of its text content.
Use this tool to inspect source files, logs, or documents as plain text. Document formats (PDF, office files) are converted to Markdown first.
Use when: - You need to see the contents of a file in the project. - You want to page through a large file by line numbers.
Do not use for: - Listing a directory (use the list files tool instead). - Fetching remote content (use the web fetch tool instead).
Example: read_file(path=”README.md”, offset=1, limit=100)
- Parameters:
path – File path to read. Must be relative to the current working directory and cannot contain ‘..’ for security.
offset – 1-indexed line to start reading from.
limit – Maximum number of lines to return. None reads to the end.
max_chars – Hard cap on characters of content to return.
- Returns:
Dictionary with content, line range, total_lines, truncated, error.
- async klea_utils.mcp.server.bundled_tools.web_fetch(ctx: fastmcp.Context, url: Annotated[str, FieldInfo(annotation=NoneType, required=True, metadata=[MinLen(min_length=1)])], timeout: Annotated[float, FieldInfo(annotation=NoneType, required=True, metadata=[Ge(ge=1.0), Le(le=120.0)])] = 30.0, max_chars: Annotated[int, FieldInfo(annotation=NoneType, required=True, metadata=[Ge(ge=1), Le(le=1000000)])] = 100000) fastmcp.tools.ToolResult[source]¶
Fetch a URL and return its text content.
Use this tool to read web pages, docs, or other HTTP resources.
Use when: - Reading a page or document from the web. - Checking a URL that a user or another tool referenced.
Do not use for: - Downloading a file to disk (use the download file tool instead).
Example: web_fetch(url=”https://example.com”)
- Parameters:
url – HTTP or HTTPS URL to fetch.
timeout – Request timeout in seconds.
max_chars – Maximum number of characters of content to return.
- Returns:
Dictionary with url, status_code, content_type, content, truncated, error.