Summary
Creating an MCP server eagerly generates and fully inlines input/output JSON Schemas for the entire AdCP tool catalog, even when the handler advertises only a small subset. In a downstream seller this makes server construction CPU- and memory-heavy enough to produce startup timeouts under parallel CI load.
This is not a 7.0.2 regression: 7.0.1 and 7.0.2 are effectively identical. The same path is slightly larger/slower in 8.0.0b3.
Measurements
Environment: Python 3.13.13 on macOS arm64. Definition size is compact JSON-serialized size; RSS is resource.getrusage(...).ru_maxrss converted to MiB.
| SDK |
schema generation |
definitions before |
definitions after |
max RSS before |
max RSS after |
| 7.0.1 |
6.746s |
0.022 MB |
50.287 MB |
335.7 MiB |
573.8 MiB |
| 7.0.2 |
6.825s |
0.022 MB |
50.287 MB |
336.0 MiB |
574.2 MiB |
| 8.0.0b3 |
7.987s |
0.024 MB |
53.596 MB |
488.1 MiB |
734.6 MiB |
For the downstream seller on 7.0.2:
- Normal full app construction: roughly 11–12s, of which about 7.2s is
_build_mcp_and_a2a_app.
- Under coverage: the SDK transport construction phase rises to 15.3s.
- With 13 CPU workers active: that phase rises to 14.0s.
- The SDK has 64 tool definitions; the handler advertises 14, but schema generation still calls
_model_to_json_schema 126 times for the global input/output surface.
The largest expanded definitions included:
| Tool |
input + output size |
Advertised by this handler? |
comply_test_controller |
8.319 MB |
No |
get_task_status |
8.307 MB |
Yes |
build_creative |
7.924 MB |
No |
preview_creative |
5.632 MB |
No |
Profile
A cProfile run scoped to _build_mcp_and_a2a_app attributed the work as follows (profiled timings include profiler overhead):
- 139,037,285 function calls
_ensure_pydantic_schemas_applied: 22.999s cumulative
_model_to_json_schema: 126 calls, 22.994s cumulative
_inline_refs: 126 calls, 16.554s cumulative
_resolve: about 7.1 million recursive calls
copy.deepcopy: about 13.0 million calls, 12.925s cumulative
The eager schema pass also appears to partially defeat the import-memory improvement from #959: server construction touches the complete model surface after import and retains the expanded global definitions.
Minimal measurement
import json
import resource
import time
import adcp
from adcp.server import mcp_tools
def rss_mib():
return resource.getrusage(resource.RUSAGE_SELF).ru_maxrss / (1024 * 1024)
def encoded_size(value):
return len(json.dumps(value, separators=(",", ":")))
before_size = encoded_size(mcp_tools.ADCP_TOOL_DEFINITIONS)
before_rss = rss_mib()
started = time.perf_counter()
mcp_tools._ensure_pydantic_schemas_applied()
elapsed = time.perf_counter() - started
print(
{
"version": adcp.__version__,
"generation_s": elapsed,
"definitions_before": before_size,
"definitions_after": encoded_size(mcp_tools.ADCP_TOOL_DEFINITIONS),
"max_rss_before_mib": before_rss,
"max_rss_after_mib": rss_mib(),
}
)
Possible directions
- Determine the advertised tool set before schema generation and generate schemas only for those tools.
- Precompute/version-pin the MCP schemas as package assets instead of rebuilding them at process startup.
- Reduce recursive deep-copying during
$ref expansion, potentially by memoizing resolved definitions or retaining $defs where client compatibility permits.
- Add startup time, expanded schema size, and peak-memory regression benchmarks.
Selective generation seems especially valuable: in this example, 50 of 64 tool surfaces are generated and retained but never advertised.
Summary
Creating an MCP server eagerly generates and fully inlines input/output JSON Schemas for the entire AdCP tool catalog, even when the handler advertises only a small subset. In a downstream seller this makes server construction CPU- and memory-heavy enough to produce startup timeouts under parallel CI load.
This is not a 7.0.2 regression: 7.0.1 and 7.0.2 are effectively identical. The same path is slightly larger/slower in 8.0.0b3.
Measurements
Environment: Python 3.13.13 on macOS arm64. Definition size is compact JSON-serialized size; RSS is
resource.getrusage(...).ru_maxrssconverted to MiB.For the downstream seller on 7.0.2:
_build_mcp_and_a2a_app._model_to_json_schema126 times for the global input/output surface.The largest expanded definitions included:
comply_test_controllerget_task_statusbuild_creativepreview_creativeProfile
A cProfile run scoped to
_build_mcp_and_a2a_appattributed the work as follows (profiled timings include profiler overhead):_ensure_pydantic_schemas_applied: 22.999s cumulative_model_to_json_schema: 126 calls, 22.994s cumulative_inline_refs: 126 calls, 16.554s cumulative_resolve: about 7.1 million recursive callscopy.deepcopy: about 13.0 million calls, 12.925s cumulativeThe eager schema pass also appears to partially defeat the import-memory improvement from #959: server construction touches the complete model surface after import and retains the expanded global definitions.
Minimal measurement
Possible directions
$refexpansion, potentially by memoizing resolved definitions or retaining$defswhere client compatibility permits.Selective generation seems especially valuable: in this example, 50 of 64 tool surfaces are generated and retained but never advertised.