Maintained entry and verification

servers.mcp_server_sdk:cli_main owns the stdio entry. TOML discovers the actual servers, stores, retrievers and other source packages; console aliases share that entry. Core public exports load lazily so tool discovery and --help do not require initializing optional RAG backends. MCP SDK v1 is bounded below v2, whose handler API changed. Python 3.10 is the minimum supported interpreter for current dependencies.

python -m build --wheel
python -m venv .venv-wheel
.venv-wheel/bin/python -m pip install dist/*.whl
.venv-wheel/bin/python scripts/check_installed_wheel.py
.venv-wheel/bin/python -m unittest discover -s tests/contracts -v
.venv-wheel/bin/python -m unittest discover -s tests/unit -p test_ocr_processor.py -v

The install check runs all five console aliases with --help, then starts the installed executable outside the source tree and initializes a real MCP stdio session. It lists tools, checks the echo and the missing-document error. It supplies a syntactically valid fixture key; no live credential, OCR or model request is made. The contract suites cover actual OCRProcessor.process(Document), not the stale initialize/process_pdf_file test interface that the implementation never exposed. They check page Markdown, connector payloads, upload/signed URL failures, empty text and partial-file failures. The processor does not report empty/partial OCR as success.

The Mistral connector uses the current /v1/ocr, /v1/files, /v1/files/{file_id}/url contracts and pages[].markdown, with old text page fields retained for compatibility. Upload streams close after requests and multipart Content-Type is left to requests. Sources reviewed 2026-10-09: Mistral OCR API, Mistral Files API, and MCP SDK migration.

The package contract workflow now runs real install/protocol/OCR tests. The broader CI, storage and documentation workflows retain their checks. Their type/security failures are no longer discarded. Actionlint checks all workflow syntax; service, format, type and behavioral results are recorded per actual run. This repair does not certify remote models, vector services, full RAG quality or a production release.

Full-suite recovery evidence

On 2026-10-09, before the Windows MIME/platform-type follow-up, the frozen bf4ba464 Python 3.11 candidate passed all default unit, contract, integration, component, E2E and performance suites: 668 tests and 22 subtests. All 197 Python source hashes were unchanged between the run’s start and end. Native startup used the selected Torch OpenMP library with the bypass disabled. The five existing intentionally failing CI probe cases remain disabled by their own opt-in fixture. FAISS-unavailable is now tested explicitly even when FAISS is installed. Native vector updates use new Haystack documents instead of mutating deprecated fields. Context cleanup uses the manager’s public sessions. MIME validation fails closed when libmagic cannot determine a type, and path checks resolve the base and reject symlinks. Tests cover real configuration removal/persistence, processor discovery, Haystack run/messages and public session queries; nonexistent historical APIs are not implemented to satisfy stale fixtures.

Every cleanup entry consumes only explicitly registered resources. PID cleanup checks both parent ownership and creation time. The contract creates two same-name child processes itself: the registered child exits while the unregistered control stays alive. It also proves an unregistered file is preserved and no broad name or port scanning command runs. User and other agent processes are never test subjects.

HTML documentation builds with warnings as errors; RST checking delegates only Sphinx extension directives to that real Sphinx build. Markdown remains editable source. Missing guide chapters and deployment materials are marked as missing. The final local formatting, lint and type checks pass across the declared targets; the corresponding remote workflow results remain separate evidence. These behavioral results alone do not establish a complete CI pass.

Pytest’s default paths match the automated suites actually run in CI. The existing tests/manual scripts remain separate live-service experiments; they are not silently treated as verified research or added to offline CI by discovery tests. Explicit manual execution and its service/data requirements remain unverified.

Container entry and verification

The image installs the package from pyproject.toml rather than installing a development requirements file outside the checkout. The old builder copied only requirements.txt, whose editable project reference could not resolve. Its floating Debian image also named obsolete system packages, and its runtime referenced missing root mcp_server.py and health_check.py files. EXPOSE 8000 did not create an HTTP server.

The maintained image uses Python 3.11.17 on Debian Trixie, selects official CPU Torch wheels, and runs mcp-academic-rag-server as a non-root user. No HTTP health check is declared. Startup environment validation checks key syntax and creates the configured data directory; it does not verify OCR, model availability or answer quality. Default browser rendering is not installed in the image: hosts that need PDF rendering install Chromium and system dependencies in a derived image. The existing explicit missing-browser error remains active.

docker build --target runtime-validation -t academic-rag:dependencies .
docker build -t academic-rag:local .
python scripts/check_container.py academic-rag:local --dependency-image academic-rag:dependencies --receipt container-runtime-receipt.json

This check uses an actual MCP ClientSession against a unique, owned docker run -i container with networking disabled. It validates initialization, discovery, echo, native missing-file/invalid-arguments errors, non-root execution, native Torch/FAISS operations, installed dependency consistency, help and rejected missing credentials. Cleanup only addresses that exact test container. CI loads the built image before this check; it no longer waits thirty seconds before invoking a nonexistent health script. The historical Compose stack remains separate and unverified: it assumes HTTP on port 8000 and references absent config/nginx.conf. It is not the current MCP stdio client configuration.

Primary installation references: PyTorch CPU installation and Playwright Docker prerequisites.

Publication is an explicit action

Ordinary pushes and pull requests run checks without publishing images. Manual workflow dispatch defaults publish_target to none; explicit staging or production selection publishes the corresponding image only after the required checks. These jobs publish container images and do not claim a running website or deployment; the unsupported example domain URLs and run-number version tags are removed. Manual images use the existing commit SHA, while an explicitly published GitHub release keeps its actual existing tag.

Release checkout fetches full history. scripts/release_changelog.py handles first releases with no earlier tag and rejects missing current tags. Git commit subjects remain text in a random-delimited environment value and are read with process.env.CHANGELOG; they are never inserted into JavaScript source. Temporary local Git fixtures check literal shell/JavaScript syntax remains data. The conditions test ordinary pushes/PRs, default manual checks, explicit manual targets and published/created release events without publishing an artifact.

The retained Docker storage job now starts the same real embedded Milvus service declared in .github/milvus-ci.yml; its old independent service omitted the standalone command, polled the gRPC port with HTTP, and selected a nonexistent requires_milvus test set. The replacement scripts/check_milvus_service.py uses the actual optional storage-tests SDK declaration in an isolated service environment. Local Linux ARM Milvus 2.3.4 and PyMilvus 2.6.17 passed real two-vector insertion, nearest-neighbor ordering, row query and owned-collection cleanup. The script rejects non-loopback hosts before connecting; the local fixture starts and removes only its own uniquely named container on an ephemeral loopback port. This verifies service protocol compatibility, not the unimplemented application MilvusDocumentStore. SDK deprecation warnings remain visible in its log. See Milvus SDK compatibility.

The MCP query_documents.top_k parameter now reaches ChatSession and the actual Haystack retriever. Previously it only sliced the displayed sources while all retrieved text still entered the prompt. Controlled native retrieval checks ranking, returned counts, prompt contents, configured-default preservation and invalid limits failing before session/query creation; other provider/generation options are unchanged.

A second recovery pass exercises the current Haystack 2.x embedders/retrievers, real in-memory and FAISS stores, prompts, sessions and migration without service calls: 77 targeted integration/component/unit cases and 9 MCP boundary cases pass. Three negative migration cases have equal document counts but changed content, metadata or vectors; verification rejects each. Memory storage still has no disk backup implementation, so a requested backup fails rather than reporting success. Its migration test explicitly opts out of backup; source documents remain intact when backup fails. All 11 existing performance cases pass with correctly sized fixture batches and vector dimensions. These are local controlled benchmarks, not host deployment performance or research-quality evidence.

Current parser, storage and coverage boundaries

Document.get_text_content() owns conversion of current OCR text dictionaries, embedding text chunks and historical string stages into stored text. Embedding and storage consume it together. Offline tests cover current and legacy inputs, partial batches and a failing embedding boundary without model or OCR requests.

Math markup uses the current Python-Markdown InlineProcessor/BlockProcessor APIs and xml.etree.ElementTree; tests parse the actual rendered HTML and reject nontext OCR values. Internal temporary PDF tests use normal Chromium with scripts disabled, verify actual text, tables and page dimensions, reject local/remote resources and dangerous legacy options, and prove a missing browser fails. They do not export a user deliverable or establish extraction quality. PDF creation requires python -m playwright install chromium. Python-Markdown extension API explains these parser contracts.

Vector factory migration delegates to VectorStoreMigrator.migrate, performs the copy and verifies real target documents. Its historical no-op success path is removed. The compatibility helper has no backup parameter and explicitly performs migration without backup; the migrator’s separate requested-backup failure stays observable. Milvus availability requires an actual backend class, not just an installed client dependency. The repository’s Milvus document-store stub reports unavailable; installing its SDK does not implement that stub.

Vector CI measures the complete document_stores directory, including executed migration lines. It does not additionally name the dotted migration submodule: coverage’s early lookup imported native Torch a second time and caused a locally reproduced exit 139. No coverage files or valid test cases are excluded by removing that duplicate. See Coverage source selection.

Monitoring runtime and provider boundary

pyproject.toml owns runtime and development dependency constraints; requirements.txt installs the declared development and monitoring extras. MCP-only wheel installation keeps monitoring optional. The monitoring HTML source is core/templates/dashboard.html; dashboard initialization only reads this packaged template. scripts/check_monitoring_wheel.py checks actual HTML and metrics/alerts/health HTTP routes with a read-only installed package.

Telemetry requires the actual OpenTelemetry SDK when initialized. Disabled tracing or metrics use the SDK API’s explicit no-op providers; a missing SDK or explicitly enabled missing exporter fails initialization and leaves no initialized singleton. Legacy Jaeger Thrift imports are loaded only when explicitly selected. Modern Jaeger supports OTLP, as documented by OpenTelemetry. PrometheusMetricReader registers a collector but does not host HTTP; the obsolete endpoint constructor option is rejected explicitly. Hosts configure their HTTP server separately, following the Python exporter documentation. SDK tests collect actual in-memory spans and metric data without external telemetry services.

RAG invokes the existing BaseLLMConnector.generate contract through a typed Haystack component, so providers with client-only implementations do not need a nonexistent native generator. Per-call generation options reach that execution path; OpenAI native messages use ChatMessage.text. Controlled provider fixtures validate retrieval, options, native reply text and explicit provider errors; normal browser tests retain the production HTTP and JavaScript flow.

Darwin native startup

The installed macOS ARM wheels can include separate OpenMP libraries in Torch, FAISS and scikit-learn. Actual Torch and FAISS operations aborted with exit 134 in both source and installed-wheel environments. Import order alone did not fix this. Importing scikit-learn implicitly set KMP_DUPLICATE_LIB_OK=True, which masked the conflict in earlier pytest runs; those runs do not prove safe native interoperation. The repair never enables that bypass or modifies wheel binaries.

The five console aliases and python -m servers.mcp_server_sdk prepare the startup environment after argument parsing and before asynchronous startup. On Darwin they replace only the explicitly invoked CLI once, preserving sys.orig_argv, stdin, stdout, stderr and exit status. The new process places its own installed Torch lib directory first in DYLD_LIBRARY_PATH and fixes KMP_DUPLICATE_LIB_OK=FALSE. A checked reentry marker prevents recursive replacement. --help and --validate-only retain their existing lightweight behavior. Other platforms perform no process replacement or environment changes.

Library imports do not replace a hosting application. An embedded consumer must launch its Python process with the environment returned by utils.native_runtime.native_subprocess_environment(), then call the SDK’s async main(). Applying that mapping to os.environ inside an already running Python process does not configure dyld’s startup search path. The SDK fails startup explicitly if its native preconditions cannot be verified. The helper does not expose credentials or write a global environment configuration. The macOS-only guard queries its own process’s KERN_PROCARGS2, extracts only the two required startup keys after skipping the counted arguments, and checks the current settings and already loaded OpenMP paths. Apple marks this kernel interface API_UNSTABLE; it is verified on the current host, and unsupported or malformed responses fail startup explicitly. This checks ordinary startup configuration, not a hostile application’s deliberate modification of its initial process stack. Raw arguments and environment values are never logged.

scripts/check_native_runtime.py runs in a cold process with the bypass explicitly disabled. It compares Torch matrix products, actual FAISS insertion and nearest-neighbor searches, and scikit-learn clustering against independent NumPy values. On Darwin it also checks that only the selected Torch OpenMP library is loaded. This check is separate from MCP initialization/discovery and from external model or OCR requests.

Security checks

Windows CPython’s actual matrix crashed during python-magic 0.4.27 import in magic.compat, before collecting any test node. Both Windows native numerical checks had passed. The upstream Git Bash/MSYS report describes the same hang/access-violation pattern; the failing run did not record which DLL it selected, so that path is not asserted as a measured fact.

Windows development/Web extras select python-magic-standalone 0.4.28, an explicitly unofficial distribution of the maintainer-contributed wheel branch. The wrapper retains its upstream MIT license; bundled native components retain their upstream licenses. Project version and license metadata are unchanged. Other platforms retain python-magic and their existing libmagic installation. The Windows adapter binds only the distribution-owned native DLL and database through absolute paths, verifies pointer architecture and library version, and searches dependencies only in the owned DLL directory and System32. It does not import the fork’s compatibility/fallback loader or search PATH/CWD for Cygwin/MSYS libraries. Competing magic distributions, missing files, wrong ABI and identification errors fail closed. Each request owns and closes its native handle; Python opens Unicode filenames and supplies bounded content bytes to the native MIME API. The existing size limit and allowed MIME types remain the authority. No extension-based acceptance or new MIME inference engine is added.

The real Windows matrix checks seven generated content cases (PDF, PNG, JPEG, TIFF, UTF-8 text, UTF-16 text and DOCX), Unicode filenames, 21 threaded inspections and rejection of unknown bytes, a disguised executable, HTML, JSON and shell content. It emits the actual owned DLL path/hash/version only after assertions pass. Non-Windows runs explicitly skip that platform acceptance case; local negative fixtures do not establish Windows compatibility. The subsequent source-type repair preserves the runtime platform checks while allowing strict mypy to check the Darwin helper from Linux, Darwin and Windows targets.

The subsequent Windows run collected 496 tests successfully and passed the native numerical check, but stopped at the existing maximum of ten failures before reaching the MIME content case. Its failures exposed open temporary-file handles and default-codepage fixture reads, plus the configuration CLI’s Chinese output on redirected CP1252 streams. Fixtures now close owned temporary handles and use explicit UTF-8; the CLI configures only its own stdout/stderr as UTF-8. Controlled CP1252 child processes check real generation, validation and failure statuses. Windows now runs the same native MIME content case before the complete unit suite; all unit selections, coverage and failure limits remain unchanged.

The old Safety command passed a filename to an output-format enum and failed before scanning. The current Safety CLI also requires account-backed service initialization. CI audits the actually installed project dependency environment with PyPA pip-audit, which reports known vulnerabilities as JSON and preserves a nonzero failure status. No vulnerability is ignored. The factory-import follow-up passed 47 factory/native-storage cases including three added negative import cases. These reject unknown backend/module/dependency inputs before executing their controlled fixture modules. The three maintained backend modules and two optional dependency imports now use literal module names; fallback and unavailable Milvus behavior remain intact. Actual Semgrep 1.180.0 auto rules scanned the same 10 source files with 290 applicable rules: the previous two blocking variable-import findings are zero after the change, with no rule exclusions. Semgrep runs in a separate tools environment to keep its own OpenTelemetry constraints from changing the project runtime; --error makes findings fail the gate. Bandit scans the declared source directories with the repository’s existing pyproject.toml configuration, avoiding temporary virtualenv copies while retaining all configured source checks. Publishing validation runs argument lists without a shell and aborts on a failed artifact check; validation did not publish a package.

PDF parsing uses maintained pypdf; the deprecated PyPDF2 vulnerability advisory requires migration. The unpatched pdfkit advisory is addressed by removing that renderer and its dependency. Chromium preserves page size, orientation, UTF-8 and margins. Unsupported wkhtmltopdf-specific options fail explicitly. NLTK had no source consumer and was removed from the speculative enhanced declaration; its unpatched model-artifact advisory is not ignored. The final isolated declared .[dev,monitoring] inventory has 186 distributions: 185 dependencies were audited with no known vulnerabilities. The local unpublished project itself has no PyPI advisory record and is explicitly marked unauditable in the JSON report; source checks remain separate. Redis is installed only in the development extra to check the existing optional adapters against its actual typed SDK. MCP-only runtime installation retains missing-Redis fallback.

The hosted runner’s preinstalled setuptools 79.0.1 produced the actual remote audit failure. An isolated audit of that exact version reproduces the nonzero status; its two report entries share PYSEC-2026-3447, fixed in setuptools 83. CI now creates a fresh project environment and upgrades its build tools before installing the declared dependencies, rather than auditing unrelated or stale hosted-toolcache packages. Failed reports are uploaded as well.

The actual container Trivy scan initially failed before scanning because the GitHub repository owner had uppercase letters. Each image-building job now normalizes the same repository name before use; five Bash environment-file contracts failed before the change and pass afterward. The final Linux ARM image’s seven installed configuration/query/factory/startup source hashes match the frozen source. Its base Python tooling is upgraded separately from the isolated runtime. The actual inventories audit four base distributions and 106 public runtime distributions with zero known advisories. The unpublished project and the CPU Torch local-version distribution are explicitly unauditable on PyPI.

Successful Trivy execution does not mean zero findings. The 193 candidate’s scan retained 281 Debian records without a reported fix and six Python records. The six versions match pip/_vendor/bom.cdx.json in the official pip 26.2.1 wheel: msgpack 1.1.2, setuptools 70.3.0 and urllib3 2.7.0. pip includes executable vendored urllib3; upgrading the top-level distribution does not update that copy. Its setuptools subset is pkg_resources, and its msgpack copy is pure Python; applicability must be assessed per advisory rather than dismissing all six records as stale metadata.

The base uses the official python:3.11.17-slim-trixie tag in both stages. The runtime applies available upgrades from the same Trixie package sources before installing libgomp/libmagic; it adds no mixed-release or external package repository. Debian records SQLite CVE-2025-7458 as vulnerable in Bookworm and fixed in Trixie; this base change must still be verified against the actual final package inventory and complete scan. It does not imply all OS findings disappear. The container check loads the real system libmagic and records its native version, then performs SQLite file transactions and rollback, configuration save/reload, real in-memory document write/read/search/delete and chat-session save/recovery. No project SQLite document-store implementation or external model result is claimed. References: official Python tags and Debian SQLite advisory.

The runtime-validation image retains the installer and runs real pip check in both Python installations. Production derives from that stage and uninstalls the complete pip distribution from both environments, including vendored code and its BOM, then removes the standard-library ensurepip installer package and its bundled pip wheel. The first actual cloud image inspection found a retained pip 24.0 wheel at /usr/local/lib/python3.11/ensurepip/_bundled/; checking only site-packages would have missed it. Runtime setuptools and wheel remain installed. The actual container checker repeats both pre-cleanup dependency checks, verifies pip is physically absent from both final site-packages directories and the shared standard-library ensurepip directory/import path, then runs the existing native and official MCP stdio contracts without networking. CI preserves both inventories and the complete unsuppressed Trivy SARIF. The security job repeats the actual runtime contracts on its own scan image before Trivy runs, binding the receipt image ID to that scan; another job’s independently built image is not substituted as evidence. Installing packages inside the finished image requires a derived build stage with an installer; bootstrapping pip through ensurepip is also unavailable. The maintained runtime itself does not install packages. Results remain bound to the exact candidate image and must be recorded separately from this source-level design. Container publication retains its existing explicit release/manual conditions.

The earlier Windows matrix stopped during pytest collection after FAISS loaded; no test node had started. Its incomplete jobs are not passing evidence. The native verification and unit steps now have five- and twenty-minute failure limits, respectively. A C-level repeating traceback watchdog starts before pytest import/collection and writes through a duplicated original stderr handle, so pytest capture cannot hide a stalled collector’s trace. All test selections, coverage and the existing 300-second per-test timeout remain intact. Original logs are uploaded even on failure; missing JUnit output is not fabricated.

scripts/check_bandit.py validates and applies the existing medium severity and medium confidence configuration with -ll/-ii, preserving the existing B101/B601 skips. It also saves all severities/confidences to bandit-report-all.json; low findings remain visible. A malformed or incomplete scan fails. The former command did not apply configured thresholds and scanned temporary virtualenv copies. Actual clean, low-informational and high-failing fixture sources verify scanner statuses; no finding is converted to success with a shell fallback.

Retained and retired server entry contracts

The config-center example uses its validated effective ConfigManager snapshot through ServerContext(config_manager=...), then delegates processing/query execution to the maintained SDK helpers with explicit context injection. Its old content/id/embedding input schema referred to methods and constructors that do not exist; current document tools take file_path, preserve model-stage metadata and share the actual native RAG store/session. The retained debug servers use one JSON line transport, terminate on stdin EOF, reject malformed JSON/requests/params, ignore notifications and retain the current request ID on failure. Empty PDF text does not become an extraction success or fictitious OCR fallback.

tests/temp_root_tests/test_mcp.py was retired: it hardcoded an inaccessible E: drive path and awaited a response to initialization notifications, which can hang. The actual official MCP stdio client contract lives in tests/e2e/test_rag_workflow.py; it verifies discovery, processing and registered logical failures with native CallToolResult.isError=True. Helper list APIs remain compatible, and success after a controlled failure is checked.