Skip to content

MCP Server API Reference

create_server

create_server

create_server(roots=None, *, allow_write=None, output_dir=None, allow_any_extension=False, name='pygaeb')

Build a configured FastMCP server exposing the pyGAEB tools.

Useful programmatically as well as via the pygaeb-mcp console script — a larger application can build this and mount it alongside its own tools.

Parameters:

Name Type Description Default
roots list[str] | None

Directories the server may read GAEB files from. Defaults to PYGAEB_MCP_ROOTS or the process working directory.

None
allow_write bool | None

Register the write tools (export_document, convert_document). Defaults to PYGAEB_MCP_ALLOW_WRITE.

None
output_dir str | None

The only directory writes may target. Required when allow_write is true.

None
allow_any_extension bool

Skip the GAEB extension allowlist on input paths.

False
name str

Server name reported to the client.

'pygaeb'

Returns:

Type Description
Any

A FastMCP instance with the tools and prompts registered.

Raises:

Type Description
ImportError

If the mcp extra is not installed.

ValueError

If a root is missing, or writes are enabled without a valid output directory.

main

main

main(argv=None)

Console-script entry point for pygaeb-mcp.

Uses argparse rather than click so the mcp extra does not drag in the cli extra.

ToolContext

ToolContext dataclass

ToolContext(cache, roots, allow_any_extension=False, allow_write=False, output_dir=None)

Everything the tools need that must not appear in their schemas.

build_tools

build_tools

build_tools(ctx)

Build the tool callables bound to ctx.

Returns the 9 read tools, plus the 2 write tools when ctx.allow_write.

DocumentCache

DocumentCache

DocumentCache(max_documents=None, max_cache_mb=None)

LRU cache of parsed documents, bounded by both count and total megabytes.

Under the default stdio transport the cache lives for exactly one client session, which is why it needs no cross-session namespacing.

derive_handle staticmethod

derive_handle(path, validation)

Derive the deterministic handle for path in its current state.

get

get(handle)

Fetch a cached document, refreshing its LRU position.

Raises:

Type Description
ValueError

If the handle is unknown or has been evicted.

peek

peek(handle)

Fetch without raising and without touching the LRU order.

get_if_present

get_if_present(handle)

Fetch without raising, refreshing the LRU position on a hit.

This is what serving paths should use: a document kept hot purely via repeated open_document calls must count as recently used, or the cheap cached path would make it the eviction candidate.

put

put(handle, path, doc)

Insert a freshly parsed document and evict if over budget.

stats

stats()

Current occupancy — used by tests and troubleshooting.

clear

clear()

Drop everything.

CachedDocument

CachedDocument dataclass

CachedDocument(handle, path, doc, size_mb, summary=None, diffs=dict(), _tree=None)

A parsed document plus the derived structures the tools reuse.

tree property

tree

The BoQ tree, built on first use then reused.

Gates on is_procurement rather than on award.boq being absent: AwardInfo.boq has a default_factory, so on a trade/cost/quantity document it is an empty BoQ, never None.

Raises:

Type Description
ValueError

If the document is not a procurement document.

resolve_within_roots

resolve_within_roots

resolve_within_roots(path, roots, *, allow_any_extension=False)

Validate an untrusted read path and return its resolved form.

Path.resolve() runs before the containment check, so a symlink inside a root that points outside it is rejected rather than followed.

Parameters:

Name Type Description Default
path str

The untrusted path.

required
roots list[Path]

Allowed directories, from :func:resolve_roots.

required
allow_any_extension bool

Skip the GAEB extension allowlist.

False

Returns:

Type Description
Path

The resolved, validated path.

Raises:

Type Description
ValueError

If the path escapes the roots, does not exist, is not a file, has a non-GAEB extension, or exceeds max_file_size_mb.

resolve_roots

resolve_roots

resolve_roots(roots=None)

Resolve the allowlist of directories the server may read from.

Precedence: roots (CLI) > PYGAEB_MCP_ROOTS env/settings > the process working directory.

Parameters:

Name Type Description Default
roots list[str] | None

Explicit root directories, typically from --root.

None

Returns:

Type Description
list[Path]

Absolute, resolved directories.

Raises:

Type Description
ValueError

If a configured root does not exist or is not a directory.

bound

bound

bound(payload)

Final backstop: shrink payload until it serializes under the char budget.

Pagination and allowlists should already have kept us well clear of this. It exists so that a pathological document cannot blow the context window even if a projection is wrong — trailing entries are dropped from the longest list and the result is flagged rather than silently truncated.

Limitation: only top-level lists can be shrunk. A payload dominated by a single large string is flagged truncated but not reduced — which is why every tool that returns a text window must clamp it via :func:max_text_window before calling this.

paginate

paginate

paginate(seq, offset, limit)

Slice seq and report the full population size.

Returns:

Type Description
list[Any]

(page, total_matched, has_more). total_matched is the size of the

int

whole match set, not the page, so the model knows what it did not see.