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
|
None
|
allow_write
|
bool | None
|
Register the write tools ( |
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 |
Raises:
| Type | Description |
|---|---|
ImportError
|
If the |
ValueError
|
If a root is missing, or writes are enabled without a valid output directory. |
main¶
main
¶
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
¶
Everything the tools need that must not appear in their schemas.
build_tools¶
build_tools
¶
Build the tool callables bound to ctx.
Returns the 9 read tools, plus the 2 write tools when ctx.allow_write.
DocumentCache¶
DocumentCache
¶
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 the deterministic handle for path in its current state.
get
¶
Fetch a cached document, refreshing its LRU position.
Raises:
| Type | Description |
|---|---|
ValueError
|
If the handle is unknown or has been evicted. |
get_if_present
¶
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.
CachedDocument¶
CachedDocument
dataclass
¶
A parsed document plus the derived structures the tools reuse.
tree
property
¶
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
¶
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: |
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 |
resolve_roots¶
resolve_roots
¶
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 |
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
¶
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
¶
Slice seq and report the full population size.
Returns:
| Type | Description |
|---|---|
list[Any]
|
(page, total_matched, has_more). |
int
|
whole match set, not the page, so the model knows what it did not see. |