Repository navigation
Releases
Release 0.22.0 (2026-09-24)
Shipped in all three bridges. Implemented in
apcore-mcp-python,apcore-mcp-typescriptand
apcore-mcp-rust. See each bridge's own CHANGELOG for its per-language details.
Raises the required floor to apcore 0.31.0 and apcore-toolkit 0.12.0 across all three bridges, and
fixes a credential-disclosure defect found while reviewing what those two releases changed.
Security
-
$refsibling keys were discarded duringSchemaConverterinlining, droppingx-sensitive
(schema_converter.json, new fixture, 8 cases + 1
error case). A node like{"$ref": "#/$defs/Token", "x-sensitive": true}resolved to the
referenced definition alone, silently losing every key written beside the$ref. This is a
credential-disclosure path, not a fidelity nicety: theExecutionRouter's output redaction reads
x-sensitiveoff the resolved schema to decide what to mask (seeoutput_redaction.json), so a
field marked sensitive behind a$refreached the redactor with nothing to redact on and was
returned in plaintext.$refresolution is now specified to shallow-merge sibling keys over the
resolved target, sibling winning on conflict, at every depth and across chained refs — see
docs/features/schema-converter.md#ref-sibling-keys-are-preserved.
A missing$defsentry still raises; this only changes what happens to siblings on a reference
that does resolve.Found by reviewing what apcore 0.31.0 (decision D-98/D-124) and apcore-toolkit 0.12.0 changed:
both fixed the identical defect in their own$refresolvers.SchemaConverter's
_inline_refs/inline_refs/_inlineRefsis an independent implementation with no shared code
path to either, so it was not fixed by bumping the dependency floor and carried the same latent
bug in all three bridges.
Changed — dependency floor
- Required apcore floor raised to 0.31.0 and required apcore-toolkit floor raised to 0.12.0
across all three bridges. apcore 0.31.0 is two joined audit cycles (PROTOCOL_SPECv1.37.0 →
v1.59.0) settling 54 cross-language divergences, five of them security defects, none on a surface
this project uses (ACL/ApprovalRequest/CancelToken/Context construction/Registry/Module — grepped
per-bridge against every symbol apcore 0.31.0 changed). apcore-toolkit 0.12.0 adds the Device
Authorization Flow (unused here) andBindingLoader.load'spatternparameter (BindingLoaderis
not used by this project); its own required-apcore-floor bump to 0.31.0 is inherited transitively.
See each bridge's CHANGELOG for its own no-code-change confirmation.
Release 0.21.0 (2026-09-07)
Shipped in all three bridges. Implemented in
apcore-mcp-python0.21.0,
apcore-mcp-typescript0.21.0 andapcore-mcp-rust0.21.0. See each bridge's own CHANGELOG for
its per-language details.
Contract clarification release. No fixture changed and contract_version stays at 1.0 — the
conformance corpus already specified everything it covers correctly. What changed is that one rule
the corpus does not cover was never written down, and two bridges had each guessed differently.
Changed
-
docs/features/openapi-backend.md: the proxy request timeout is now specified as fixed and
unconfigurable.timeoutwas already documented as "Spec fetch only; not the per-call proxy
timeout", but the doc never said what the proxy timeout is, leaving the other half of the
contract unstated. It is apcore-toolkit'sHTTPProxyRegistryWriterdefault of 60 seconds in all
three SDKs; Python and TypeScript reach it by omitting the argument, Rust states it explicitly
becauseHTTPProxyRegistryWriter::newthere takes the timeout positionally, has no default, and
rejects a non-positive value.This is a genuine contract addition for Rust, whose proxy timeout was configurable — via
mcp.openapi.timeout, the key documented for the spec fetch — until
apcore-mcp-rust#9 was fixed. The
three bridges agree on this behaviour for the first time as of 0.21.0.Related bridge-side fixes, none of which required a fixture or contract change:
apcore-mcp-rust#8 (--openapi-header
built a header map and never passed it),
apcore-mcp-rust#9 (timeout
configured the proxy instead of the fetch),
apcore-mcp-typescript#10
(timeoutpassed as seconds into a milliseconds parameter) and
#19 (Config.project_rootnever read on
the Config Bus or CLI routes, in both TypeScript and Rust).
Known gap
config_casesinconformance/fixtures/openapi_backend.jsoncallresolve_spec_location
directly with an explicitproject_root, so they exercise the resolution helper and never the
wiring that supplies its argument — which is exactly how #19 survived in two bridges. Each bridge
now pins its own wiring locally (tests/openapi_backend_option_plumbing.rs,
tests/openapi-backend-wiring.test.ts,tests/test_openapi_option_plumbing.py), but a
conformance case entering through the config route, with acwddiffering from
project_root, would close this across all three at once. Not attempted here: it needs a new
case shape and a matching runner in each bridge.
Release 0.20.0 (2026-09-06)
Shipped in all three bridges. Unlike 0.18.0 and 0.19.0, this release is not contract-only:
every clause below is implemented inapcore-mcp-python0.20.0,apcore-mcp-typescript0.20.0
andapcore-mcp-rust0.20.0, and both conformance fixtures are driven by all three. See each
bridge's own CHANGELOG for its per-language details.
SDK-floor and feature release, driven by apcore 0.29.0/0.30.0 and apcore-toolkit 0.11.0/0.11.1. apcore 0.29.0
closed the shape of an ACL pattern array at every entry point (a breaking change for any
mcp.acl file carrying one of the affected shapes); apcore-toolkit 0.11.0 shipped the OpenAPI
Scanner, which makes an OpenAPI document a viable backend source for the first time; and apcore
0.30.0 (2026-09-06) declared the path-typed configuration key set and the project-root resolution
base, which reaches the mcp.openapi.spec key this entry introduces.
Changed — required SDK floors
-
apcore
>=0.28.0→>=0.30.0; apcore-toolkit>=0.10.2→>=0.11.1, across all three
bridges. Three separate reasons, kept separate because they expire differently:Floor Why apcore 0.29.0 The ACL correctness floor. On 0.28.0 the pattern-array shapes below load silently and the deployment believes it has a rule it does not have apcore-toolkit 0.11.0 OpenAPIScannerdoes not exist below it, and it is where the RustHTTPProxyRegistryWriterstopped rejectingHEAD/OPTIONS/TRACEbefore any network callapcore 0.30.0 Forced transitively — apcore-toolkit 0.11.1 requires it — and independently needed: Config.project_rootis whatmcp.openapi.specresolves a relative path againstapcore-toolkit 0.11.1 A floor bump only (to apcore 0.30.0). No toolkit API changed; the scanner and the writer are byte-identical to 0.11.0 What apcore 0.30.0 does not reach. Its §5.12.6 binding-discovery contract, the
bindings.dir/bindings.patterndefaults, thetarget_id→targetbinding-field repair and
the regeneratedconfig_key_governance.jsonall concern apcore'sConfig/BindingLoader
layer. Verified by grep across all threesrc/trees: no bridge referencesextensions.root,
acl.root,schema.root,bindings.dir,bindings.pattern, apcore'sBindingLoader, or
project_roottoday. The §9.2.2 path-resolution change is additionally a deprecation phase
— the 1.x line keeps current semantics exactly — so nothing in a shipped bridge changes meaning.Other apcore 0.29.0 changes are likewise out of scope:
ApprovalRequestgainscaller_id
andaction(additive, populated at apcore's ownBuiltinApprovalGateconstruction site — the
bridge's Approval Handler reads the request, never builds
one);CancelToken.raise_if_cancelled()is an additive alias forcheck(); Rust's
APCore::on/offnow returnResultandACLRulebecame#[non_exhaustive], both of which
touch only the Rust bridge's construction sites. apcore-toolkit's TUI View Model is a CLI
table-rendering surface with no consumer here. -
mcp.openapi.specis themcpnamespace's first path-typed key, and apcore 0.30.0's
protections do not cover it. Measured, not inferred:Config.path_typed_keys()returns a
hardcoded tuple of apcore's own four keys and never consults a namespace registered through
Config.register_namespace, and the §9.2.1 requirement-5 empty-value discard is gated on that
same fixed set. SoAPCORE_MCP_OPENAPI_SPEC=is an override to""that resolves to the working
directory — exactly the silent failure apcore 0.30.0 closed forAPCORE_ACL_ROOT=, in a
namespace the fix does not extend to. The bridge therefore owns three rules of its own; see
OpenAPI Backend § The spec location is a path-typed key.This is the one place where being a new key is an advantage. apcore's §9.2.2 forbids adopting
the project-root rule ahead of its deprecation window because its four keys have deployed
configurations relying on the current bases.mcp.openapi.spechas no deployed population at
all — it has never shipped — so it adopts the target semantics immediately rather than inheriting
a deprecation cycle it owes nobody.
Added — contract
-
ACL Builder gets its own spec — new document
docs/features/acl-builder.md.build_acl_from_confighas
shipped in all three bridges since 0.14.0 with a shared conformance fixture and no feature spec;
apcore 0.29.0's §6.2.1 closure is too large a change to land in a doc comment. The document
specifies themcp.aclschema, the division of validation labour between the bridge and apcore,
the error-type contract, and the two behaviour changes below. -
OpenAPI Backend — new document
docs/features/openapi-backend.md. A third backend source:
point the bridge at an OpenAPI 3.0/3.1 document and every operation becomes an MCP tool, proxied
over HTTP. The whole path is composition of shipped code —load_spec→OpenAPIScanner.scan→
HTTPProxyRegistryWriter.write→Registry→ the existing bridge — so it introduces no scanning
logic, no schema conversion and no new execution path. New Config Bus sectionmcp.openapiand
seven CLI flags (--from-openapi,--openapi-base-url,--openapi-prefix,
--openapi-include,--openapi-exclude,--openapi-header,--openapi-no-deprecated).This is the first backend source with full Rust parity: directory auto-discovery is
Python/TypeScript-only because apcore's public Rust API has no runtime directory discovery, and
an OpenAPI document needs no discoverer.Two governance findings are recorded normatively rather than left implicit. Every scanned
module arrives withrequires_approval = false— the toolkit infers annotations from the HTTP
method alone, andPOST/PATCHcarry no behavioural hint at all, so aPOST /chargesthat
moves money is annotated exactly like aPOST /echoand the approval gate fires for neither. And
an ACLtargetspattern written against an operation name is owned by the upstream API's
authors: a document that renamesdeleteUsertoremoveUserchanges the derivedmodule_id,
and atargets: ["deleteUser"]deny rule stops matching — underdefault_effect: allowthat is
a fail-open of the same shape apcore#112 closed inside the ACL. Both carry required mitigations:
a startup WARNING when write-method modules register with no ACL attached, and a mandatory
prefixin mixed deployments so a catch-all deny can be written against something the upstream
does not control.
Changed — contract
-
mcp.aclno longer accepts acallers/targetsarray outside PROTOCOL_SPEC §6.2.1's closed
shape. BREAKING for any deployment carrying one.[],["$or"],["$not"],
["$not", p1, p2, …], an empty pattern string, and a reserved token at any index but 0 are
rejected at every entry point — file loading, direct construction and runtime insertion alike.
Through apcore 0.28.0 all of these matched nothing and left the rule inert, which on adeny
rule underdefault_effect: allowpermitted the call the operator wrote the rule to block.
The affected population is exactly the one that believes it has a rule and does not.Migration:
targets: []meaning "everything" becomes["*"]; meaning "nothing" means the rule
should be deleted. The multi-operand$nothas no mechanical rewrite —["$not", p1]
preserves what the rule has actually been doing, but ifNOT (p1 OR p2)was intended, a leading
denyis not equivalent, because a non-matching rule lets evaluation continue to later rules and
adenyends it. Migration tooling MUST NOT apply it automatically. -
build_acl_from_config's per-rule validation order is realigned to §6.2.1's, which is
normative for the first time. All three bridges currently validate
callers→targets→effect→approval; §6.2.1 fixes the order aseffect→approval→
callers→targets, withdefault_effectjudged ahead of every rule and the rule index
dominating. A rule wrong in botheffectandcallerswas therefore reported forcallersby
the Config Bus door and foreffectby apcore's — the same file, two different answers depending
on which door it reached first. The unknown-key check stays ahead of all four; the
default_effectcheck already sits ahead of the rule loop. -
A §6.2.1 rejection MUST be re-raised in the bridge's own error type, with the rule index.
apcore raisesACLRuleErrorfrom insideACLRule(...), and its message names the type, not
the rule —ACLRule has an invalid 'targets' (PROTOCOL_SPEC §6.2.1): …. That is correct for
apcore, where a rule under construction has no position yet, and useless to an operator holding a
20-rule YAML block, since the closure's entire remedy is its message. In Python it is also the
wrong type:ACLRuleErrorextendsModuleError, notValueError, so
build_acl_from_config's documented "raisesValueError" contract became false for every
§6.2.1 fault. All three bridges now catch and re-raise with themcp.acl.rules[i]prefix
(mcp.aclfor a section-scoped fault), preserving apcore's message verbatim after the prefix and
chaining the original as the cause. The Rust bridge already wrapped; Python and TypeScript let it
escape raw and now do not. TypeScript and Rust additionally construct a throwaway single-rule ACL
per rule to learn which rule apcore refused — neitherapcore-jsnorapcore-rustexports a
per-rule validator, and both validate inside the whole-list constructor. -
New required startup diagnostic: the tier-2 never-matches warning. Closing the arities does
not exha...
Release 0.19.0 (2026-09-01)
Spec-contract release, driven by apcore 0.28.0 and three open issues on system.* management
modules (#14, #15, #16).
Added — contract
- System Management Extension (
com.aiperceivable/management), Phase A only — new spec document
docs/features/system-management-extension.md.
Defines an unofficial (SEP-2133) MCP
extension a conforming adapter advertises incapabilities.extensionsduringinitialize, if and
only if at least onesystem.*module is reachable. The extension is pure discovery metadata: a
client that does not declare it MUST still reach every management resource and tool, subject
only to Activation/ACL/Approval — PROTOCOL_SPEC §6.6.3 already forbids adapters from inventing a
permission switch beyond those three layers, and a capability-gated access difference would be
exactly that. Resolves #16 Phase A (Phases B and C — Working Group
discussion and formal SEP submission — are out of scope here and tracked by their own follow-ups).
Changed — contract
system.health.*/system.usage.*/system.manifest.*now project as MCP resources, not
tools;system.control.*is unchanged.docs/features/mcp-server-factory.md's "Bijective
Mapping" constraint previously had no exception and described all ninesystem.*modules as tools.
Classification is bymodule_idprefix only, per PROTOCOL_SPEC §6.6.2 — an adapter SHOULD NOT
introduce its own classification mechanism. The three summary/full modules are static resources
(apcore://system.health.summary,apcore://system.usage.summary{?period},
apcore://system.manifest.full); the three per-module modules are resource templates
(apcore://system.{health,manifest}.module/{module_id},
apcore://system.usage.module/{module_id}{?period}). Aresources/readon any of these MUST
dispatch through the same Activation → ACL → Approval → Executor pipeline atools/callwould use —
never a second, resource-only path that bypasses ACL or the audit trail. Resolves
#15(a); tracked per-language in apcore-mcp-typescript#9,
apcore-mcp-python#8, apcore-mcp-rust#6.- New required startup guard: the unprotected-control-surface warning.
docs/features/mcp-server-factory.md
now requiresserve()/async_serve()to callExecutor.governanceState()/
governance_state()once the executor is fully assembled and, when
.unprotectedControlSurface/.unprotected_control_surfaceistrue, emit a prominent
(not fatal) startup warning naming the specific gaps and the configuration that closes each one.
This accessor was itself blocked on aiperceivable/apcore#97 until apcore 0.28.0 shipped it; the
warning was the reason #15(b) tracked that dependency instead of re-deriving the predicate from
executor internals.
Fixed — spec chain vs. implementation
- ACL rule template documented a
sys.*namespace that was never registered. The canonical IDs
aresystem.health.*/system.usage.*/system.manifest.*/system.control.*— there is no
sys.prefix. A copied deny rule using the wrong namespace never matches, so it silently fails
to block anything, which is the dangerous direction. Fixes #14; the
corrected three-rule template (read-only allow, control allow, catch-all deny) and the mechanism
notes about@externalnormalization and JWT-derivedidentity_types/rolesship in all three
adapters'acl_builderdoc comments.
Added — conformance
conformance/fixtures/acl_config.jsongainedapprovalcontract cases (contract_version1.0 → 1.1). Theapprovalrule key (apcore 0.28.0, PROTOCOL_SPEC §6.1.6) shipped in all three bridges with nothing pinning its accepted-value set, and all three implemented it independently: Python accepted apcore's full set (required/not_required), while Rust and TypeScript each narrowed it torequiredonly, on the reasoning thatmcp.aclshould have exactly one spelling for "no approval requirement here". That made those two bridges stricter than the schema they bridge — a rule that loads from apcore's ownacl/directory failed at startup when carried through the Config Bus — so the samemcp.aclYAML started a Python bridge and refused to start a Rust or TypeScript one. Three cases now pin the contract:approval: requiredaccepted,approval: not_requiredaccepted, and a value outside the closed set rejected. Thedeny+approval: requiredrejection is deliberately left to per-language tests: it originates in apcore's own constructor and its message wording differs per language, so a sharedexpected_error_substringwould be pinning apcore's phrasing rather than the bridge's behavior.- New shared fixture
conformance/fixtures/system_surface.json. Pins the nine canonical
system.*modules' exact MCP primitive (tool / resource / resource template) and name/URI,
operationalizing #15's "byte-identicaltools/list,resources/list,resources/templates/list
across TypeScript, Python and Rust" acceptance criterion as a regression fixture each adapter's
own test suite drives, rather than a one-time manual comparison. Finding this fixture useful
immediately: driving it against the three adapters caughtsystem.usage.module's resource
template missing the RFC 6570{?period}query-expansion suffix in the Python and Rust
implementations (apcore-mcp-typescripthad it right) — exactly the class of divergence this
fixture exists to catch. See each adapter's own CHANGELOG for the fix.
No SDK behaviour asserted by this document alone
As with 0.18.0, this document records the contract; conformance is the per-language implementation
tracked in the sub-issues above. See each adapter's own CHANGELOG for what actually shipped at its
current version.
Release 0.18.0 (2026-08-19)
Spec-correctness release. A full cross-language sync round audited every feature spec and the
PRD/SRS/tech-design/test-plan chain against all three SDKs at 0.17.2 and found the spec layer had
drifted from the shipped implementations in both directions — requirements describing signatures no
SDK has, and specs still describing behaviour that had already been fixed in code. This release
realigns the documentation with what the bridges actually do, resolves the contradictions the audit
surfaced, and settles one genuine contract question.
No SDK release accompanies this one. Nothing here changes Python or TypeScript behaviour. The
one item that obligates an implementation change is the mcp_ extras separator below, which Rust
must adopt.
Changed — contract
mcp_extras separator is now normatively\n, not\n\n. The doc chain disagreed with
itself:features/annotation-mapper.mdspecified\n\nwhile SRS FR-EXTRAANNOT-001 and tech
design §6.2 specified\n. Each side had followers — Rust implemented\n\n, Python and
TypeScript implemented\n— so the code divergence was downstream of a spec divergence.\n
wins: it was already the majority on both the doc side and the implementation side. For
extra = {"mcp_a": "1", "mcp_b": "2"}with one non-default flag the correct suffix is
"\n\n[Annotations: readonly=true]\na: 1\nb: 2".
Action required (Rust):to_description_suffix(src/adapters/annotations.rs) must join the
extras with\ninside the annotations paragraph rather than emitting each as its own
\n\n-separated section, and the tests pinning the old shape move with it. Python and
TypeScript are already conformant.annotation-mapper.mdalso now requires ordinal (code
point) sorting of the extras keys rather than locale collation, which varies by host.
Fixed — spec chain vs. implementation
- Output redaction with an empty
output_schema(security-relevant). Tech design §6.16 stated
redaction returns "the original output unchanged if output_schema is None or{}", and SRS
FR-REDACT-001's boundary conditions agreed — directly contradicting FR-REDACT-002,
TC-REDACT-002, and the measured behaviour recorded inconformance/fixtures/output_redaction.json,
all of which require_secret_*prefixed keys to be masked regardless of schema markings. An
implementer following the tech design would have returned_secret_api_keyin plaintext for every
module without an output schema — the common case, and a direct NFR-SEC-004 violation. Both
statements corrected, and the two distinct cases are now spelled out separately: an empty{}
schema is still passed toredact_sensitive()(prefix rule applies), whereas a tool with no
registered schema at all skips redaction entirely. ExecutionRouter.handle_callreturn type. SRS §3.3, tech design §6.3 and test plan §5.3 all
specifiedCallToolResult; all three SDKs return the tuple(content, is_error, trace_id)and
the MCP SDK server layer materialises theCallToolResulta layer above. TC-EXEC-001 as written
could not pass against any SDK. The SRS glossary now names both levels ("call-result tuple",
"error response dict"), every signature-level assertion was corrected, and the test plan carries
an explicit unpacking note.ErrorMapper.to_mcp_error/AnnotationMapper.to_mcp_annotationsreturn types. Fourteen
FR-ERROR requirements and five FR-ANNOT requirements specified MCP SDK objects
(CallToolResult,ToolAnnotations); both return plain camelCase dicts. The SRS also carried no
errorTypefield at all in its error model. Corrected, including the canonical
GENERAL_INTERNAL_ERRORcode and the fact that the AI-guidance keys are camelCase on the
wire (retryable/aiGuidance/userFixable/suggestion) even though the apcore-side
source attributes are snake_case.- MCP tool names are not sanitized. Tech design ADR-03 stated MCP tool names come from
display.mcp.alias"pre-sanitized byDisplayResolver" withmodule_idas a fallback
"auto-sanitized: dots → underscores". NoDisplayResolverexists in any SDK, no sanitization is
applied, and themodule_idis exposed verbatim —image.resizeis the MCP tool name. ADR-03's
.→-normalization is OpenAI-only and remains correct; its scope is now stated. - The display overlay was documented against a key no SDK reads.
features/mcp-server-factory.md's Display Precedence named
descriptor.annotations.extra["display_overlay"]. The real path isdescriptor.display["mcp"]
withdescriptor.metadata["display"]["mcp"]as a compatibility fallback, carryingalias,
descriptionandguidance. Rewritten, including the"\n\nGuidance: {text}"append and the
precedence of an operator-typeddescriptionoverrich_descriptionrendering. serve()/async_serve()had three mutually exclusive signatures of record — SRS §7.8
listed 13 parameters, SRS §8.1.1 listed 21, tech design §7.1 listed 30. The real surface is 37
(serve) and 32 (async_serve). SRS §8.1.1 is now the complete, authoritative signature;
tech design §7.1 matches it; SRS §7.8 is explicitly labelled a partial constraints table;
features/mcp-server-factory.mdgroups the same set by concern. Also corrected along the way:
exempt_pathsis a set, not a list, and defaults to{"/health", "/metrics"}rather than
empty; the parameter ismiddleware(singular), nevermiddlewares; andmetrics_collector
accepts a bool.AsyncTaskBridge.is_async(descriptor)(tech design §6.15) exists in no SDK and in no other
document — the method isis_async_module.PreflightResultis not a bridge return type. Four documents specified
ExecutionRouter.validate_tool(...) -> PreflightResult.PreflightResultis an upstream apcore
type; all three bridges project it to a plain{valid, checks, requires_approval}dict and none
re-exports it. Also recorded: Python'svalidate_toolis synchronous while TypeScript's and
Rust's are async, and all three reportvalid: truefor an executor with novalidate()method.- The standalone output redactor was never built. Tech design §6.16 specified
src/apcore_mcp/server/redactor.pyexposingredact_tool_output(output, output_schema), and
five test-plan steps called it. Redaction is a privateExecutionRoutermethod
(_maybe_redact/_maybeRedact/redact_output) that resolves the schema from the router's
own map. Both documents now describe the shipped shape. - Version floors were stale in both directions. Tech design's
pyproject.tomlsample declared
requires-python = ">=3.10"(real:>=3.11, matching the SRS, the PRD and the README) and
apcore>=0.17.1,<1.0; SRS NFR-COMPAT-002 declaredapcore >= 0.19.0. All three SDKs pin
0.27.0 —apcore>=0.27.0,apcore-js>=0.27.0,apcore = "0.27"— alongside
apcore-toolkit>=0.10.0andmcp-embedded-ui>=0.4.0. Also recorded: apcore-toolkit is an
optional[markdown]extra in Python but a hard dependency in TypeScript and Rust, which is
intentional per-language behaviour and not drift. - F-045 and F-046 were named differently in different documents. The SRS traceability table
called them "Observability Auto-Wiring" and "Cross-SDK Cancel Dispatcher"; the PRD, the tech
design, and the SRS's own §10.3 body call them "Decorator Metadata Mapping" and "Custom
Middleware Injection". The traceability table was the outlier and now matches. - The test plan's own coverage-gap note was wrong about four features. F-037, F-039, F-040 and
F-041 were listed as pending while §5.11, §5.13, §5.14 and §5.15 fully define their test cases.
The note now lists only the six features that genuinely have no cases (F-033, F-034, F-035,
F-044, F-045, F-046). - Feature specs that had fallen behind fixes already made in code:
schema-converter.mdstill documented TypeScript's converter-levelstrictdefault asfalse
(it istruesince the [SC-11] alignment);execution-router.mdstill documented Rust's
redact_outputas defaulting tofalse(it istruein all three, and afalsedefault would
have made Rust the one bridge leakingx-sensitiveoutput);error-mapper.mdstill described
userFixableas "TypeScript only, hardcoded" (all three now read it off the error object). - Broken cross-references. Two links and two SRS table-of-contents anchors did not resolve;
mkdocs build --strictnow passes clean.
Changed — status corrections
- Extension Bridge (F-042 / EB-2) is documented as unimplemented in all three SDKs.
features/extension-bridge.mdclaimed adapter-hook injection was "functional in
Python+TypeScript", andfeatures/mcp-server-factory.mddocumented the three hooks as accepted
serve()keyword arguments. Neither is true and neither ever was: Python'sserve()takes 37
keyword parameters andMCPServerFactory.__init__takes exactlystrictandrich_description,
with none ofextensions/schema_converter/annotation_mapper/error_mapperin either;
TypeScript rejects the same options at compile time (tscTS2353) and silently drops them for a
JavaScript caller. There is no supported way to replace the built-in SchemaConverter,
AnnotationMapper or ErrorMapper on any entry point. Consequently test plan §9f's TC-EXTMGR-002
through TC-EXTMGR-006 are unwritable and are marked DEFERRED; only TC-EXTMGR-001, the
default-adapter case, remains active. EB-1 (ExtensionManager.apply()) stays withdrawn. - The Registry Listener's Client Notification Bridge is documented as unimplemented. No SDK
contains a call site that sendsnotifications/tools/list_changed— emission, the 100 ms
debounce window, per-session ACL filtering and HTTP fan-out are all design targets. All three
nonetheless advertisetools: { listChanged: true }in the in...
Release 0.17.0 (2026-06-23)
Cross-SDK maintenance release resolving an extended audit of the serve/embed
entry points and the Phase B approval chain, and lifting all three SDKs onto
apcore 0.25 / apcore-toolkit 0.9.1. Released as apcore-mcp-{python,rust,typescript} v0.17.0.
Fixed — cross-SDK contract
serve()/async_serve()now expose the Phase B approval inputs
(approval_store/approval_notifyin Python/Rust,approvalStore/
approvalNotifyin TypeScript) in all three SDKs. Previously these were only
reachable by constructingAPCoreMCPdirectly, so the documented Phase B flow
could not be driven from the top-level entry point.- Rust Phase B approval is now actually wired end-to-end. In 0.16.x the Rust
StorageBackedApprovalHandlerwas stored but never dispatched, the
__apcore_approval_checkmeta-tool was never advertised, and the TTL sweep
never ran — so the Phase B contract indocs/features/approval-phase-b.mdwas
effectively unimplemented on Rust despite the A-002 builder methods existing.
It is now live: bridge dispatch, meta-tool advertisement, and sweep all run. - Framework-integration server parity. The non-blocking embeddable server
(MCPServerin Python/Rust) now exposes the same capability surface as the
blockingserve()— output formatter, strategy, observability, output
redaction, trace, explorer branding, approval, middleware/ACL, and dynamic
registration. The RustMCPServer::start()andasync_serve()paths now
actually register handlers and serve/mcp(previouslystart()only awaited
shutdown andasync_serve()returned a Router with no/mcproute).
Changed
- All three SDKs raised their dependency floors to apcore 0.25 /
apcore-toolkit 0.9.1 (drop-in; no consumed API changed). The Rust upgrade
was unblocked by apcore-toolkit 0.9.1 lifting its ownapcorefloor to 0.25.
Known limitations (Rust)
strategyoverrides apply on the trace execution path only; the non-trace
call()path still runs the executor's own strategy.BackendSource::ExtensionsDircannot honor runtime directory discovery through
apcore's public API and builds an empty registry with a warning;
BackendSource::Registryis fully supported.
Release 0.16.0 (2026-06-12)
Cross-SDK spec release adding the Approval Phase B storage-backed approval handler and a full cross-language sync pass covering all 16 MCP feature modules.
Added
- Approval Phase B (
docs/features/approval-phase-b.md) —StorageBackedApprovalHandlerimplements long-lived, out-of-band human approval via a persistentApprovalStore. Complements Phase A (ElicitationApprovalHandler) for flows where the approver is not present at the terminal (automated pipelines, mobile approvals, team workflows). Key additions across all three SDKs:StorageBackedApprovalHandler(store, notify_callback?)— always returnsApprovalResult(status="pending")and fires the notify callback fire-and-forget.ApprovalStoreprotocol / trait / interface —save_pending,get_result,resolve.InMemoryApprovalStore— default in-process store with separateresolved_ttl/pending_ttl/sweep_interval/max_recordsknobs.__apcore_approval_checkmeta-tool — sixth reserved meta-tool alongside the five__apcore_task_*/ preview tools. Lets the AI agent poll for out-of-band approval without a full module call.APCoreMCPgainsapproval_store/approvalStoreconstructor parameter (Python, TypeScript) that automatically wraps the store in aStorageBackedApprovalHandlerand registers__apcore_approval_check. Seedocs/features/approval-phase-b.md.
Cross-language sync — full-scope mcp round (2026-06-12)
Full pass across all 16 feature-spec modules against apcore-mcp-{python,rust,typescript} v0.16.0. See sync-report-apcore-mcp-2026-06-12.md for the full checklist.
Critical findings (implementation gaps against spec):
- A-001 (Rust) — OC-5 rename not applied:
convert_registryinOpenAIConverterstill takes&serde_json::Value(old JSON-snapshot path). The&Registrycanonical variant was added asconvert_registry_apcore— wrong name. CHANGELOG 0.14.0 migration saidconvert_registry→ Registry andconvert_registry_json→ old snapshot; that rename was not executed. Rust callers following the migration guide callconvert_registry(®istry)and get a type error. Fix: renameconvert_registry_apcore→convert_registry; rename oldconvert_registry→convert_registry_json. - A-002 (Rust) —
APCoreMCPBuildermissingapproval_store()/approval_notify():StorageBackedApprovalHandler,InMemoryApprovalStore, andApprovalStoreare all exported fromapcore-mcp-rust, but the builder wires onlyapproval_handler(Arc<dyn ApprovalHandler>). Python/TS acceptapproval_store=/approvalStore=directly. The usage example inapproval-phase-b.mdfor Rust does not compile. Fix: addapproval_store(Arc<dyn ApprovalStore>)andapproval_notify(callback)builder methods. - A-003 (Python) —
McpErrorFormatteralias never exported (EM-1 regression): CHANGELOG 0.14.0 EM-1 documents "both names exported fromapcore_mcpandapcore_mcp.adapters" as shipped. The class informatter.pyis namedMCPErrorFormatteronly; noMcpErrorFormatter = MCPErrorFormatteralias exists.from apcore_mcp import McpErrorFormatterraisesImportErrorin Python at v0.16.0. Fix: add alias informatter.pyand both__init__.pyexport lists.
Warning findings:
- A-004 (Rust) —
RegistryListener.start()takes(registry, factory): spec contract says "No parameters". Python/TS take the registry and factory in the constructor then callstart()with no args; Rust hasnew()with no args andstart(registry, factory). Cross-language documentation and usage examples are misaligned. - A-005 (Rust) —
AsyncTaskBridgeconstructor takesexecutor, notmanager: spec__init__contract listsmanager: AsyncTaskManageras the required input. Rustnew(executor)constructs the manager internally. Documenting this as a Rust-specific structural idiom is tracked; spec contract table needs updating. - A-006 (Rust) —
is_async_moduledecomposed signature: spec saysis_async_module(descriptor)(one duck-typed param); Rust exposesis_async_module(metadata, annotations_extra)(two params).is_async_module_descriptor(descriptor)also exists as a one-param alternative. Spec should document the two forms or consolidate.
Doc consistency (Phase B):
- B-001:
approval-phase-b.mdnot listed inmkdocs.ymlnavigation — unreachable from the published docs site. - B-002:
approval-phase-b.mdclaims Rust builder supportsapproval_store(snake_case) — it doesn't pending A-002. - B-003:
overview.mdfeature table does not list Approval Phase B or__apcore_approval_check. - B-004: CHANGELOG 0.14.0 EM-1 entry claims Python
McpErrorFormatteris exported — this is inaccurate; see A-003.
Deferred to a future release
- A-001 fix (Rust) —
convert_registryrename;convert_registry_jsonpreservation. - A-002 fix (Rust) —
APCoreMCPBuilder::approval_store/approval_notifybuilder methods. - A-003 fix (Python) —
McpErrorFormatteralias and__all__update. - B-001 fix — add
approval-phase-b.mdtomkdocs.ymlnav.
Release 0.15.0 (2026-05-14)
Cross-SDK release leveraging apcore 0.21.0 + apcore-toolkit 0.6.0.
Promotes three new upstream capabilities into MCP-facing surface area
across all three SDKs (apcore-mcp-python, apcore-mcp-rust,
apcore-mcp-typescript). Byte-equivalent meta-tool, error envelope,
and Markdown rendering across languages.
Changed
- Spec:
AsyncTaskBridge.submit— document Rust's bridge-managed progress channel (progress_senders[task_id]registered aftermanager.submit) as a language-specific variation of the Python/TypeScript in-context (context.data[MCP_PROGRESS_KEY]) contract. Wire-levelnotifications/progressevents are unchanged across SDKs (D10-001). - Spec:
ElicitationApprovalHandler— document Rust's constructor-injectedOption<Arc<dyn ElicitCallback>>contract as a language-specific variation of the Python/TypeScript per-callrequest.context.data[MCP_ELICIT_KEY]lookup. Rejection semantics are preserved across SDKs (D10-002). - Spec:
JWTAuthenticator.authenticate—typeclaim normalization: empty string""is treated as a missing claim and falls back to the default"user", aligning Python's truthy-fallback semantics with TypeScript and Rust (D11-107). - Spec: Approval handler — document panic-guard cross-language asymmetry: Python
try/except Exception(BaseException propagates), TypeScripttry/catch(no panic distinction), Rustfutures::FutureExt::catch_unwind(catchespanic!()across.await). Treat panic-safety as Rust-only when porting modules (D11-108). - Dependency bumps:
apcore >= 0.21.0(Python/Rust),apcore-js >= 0.21.1(TS),apcore-toolkit >= 0.6.0(Python/Rust) />= 0.6.1(TS). - Rust BREAKING:
AsyncTaskBridge::{submit, cancel, cancel_session_tasks, handle_meta_tool, shutdown}are nowasync fn— propagates upstream apcore 0.20+ async signatures (D10-003 / D10-004). Sync transport-layer cancel handlers nowtokio::spawnthe cancel call as fire-and-forget.
Added
__apcore_module_previewmeta-tool (PROTOCOL_SPEC §5.6 / §12.8) — fifth reserved meta-tool alongside the four__apcore_task_*ones. Drivesexecutor.validate(module_id, inputs, context)and returns a structured{valid, requires_approval, predicted_changes, checks}envelope WITHOUT executing the module. Lets AI orchestrators answer "what would change in the world if I called this?" before invoking destructive or stateful modules.arguments: nulland missingargumentsare preserved verbatim — the calling business decides whether null is a valid input. Structurally-impossible shapes (arrays, scalars) return a typed validation error. Cross-SDK wire-equivalent across Python, Rust, and TypeScript.rich_description/richDescriptionoption onMCPServerFactory(Python, Rust, TS) andOpenAIConverter(Python, Rust, TS) — rendersTool.description/ OpenAIfunction.descriptionas canonical apcore-toolkit Markdown (format_module(style="markdown")) instead of the plain one-line description. Includes title, description, parameters list, returns list, behavior table (only fields differing from defaults — toolkit 0.6 alignment), tags, and examples. LLMs select tools primarily from this string; Markdown packs more decision-relevant signal per token. Display-overlaymcp.descriptionoverrides still win first. Falls back to the plain description with a one-shot WARN log whenapcore-toolkitis unavailable (Python optional[markdown]extra; TS optional peer dep; Rust mandatory).markdownmodule in all 3 SDKs — public helpers for descriptor → ScannedModule adapter and direct Markdown rendering. TS adds aprimeMarkdownToolkit()async loader so synchronous code paths can render after startup priming.CIRCUIT_BREAKER_OPENerror mapping (apcore 0.20 sync alignment A-001) —ErrorMappernow dispatchesCircuitBreakerOpenError(Python) /ApcoreErrorCode::CircuitBreakerOpen(Rust) /ErrorCodes.CIRCUIT_BREAKER_OPEN(TS) to aretryable: trueenvelope withaiGuidancemirrored from the apcore error class. Added to constants tables in all 3 SDKs.- Rust-specific: new
ConvertOptionsstruct +OpenAIConverter::*_with_optionsmethod family (backwards-compatible additive API);markdownmodule re-exported fromlib.rs; new publicjson_entry_to_scanned_module(module_id, &Value) -> ScannedModuleadapter that lets the duck-typed JSON registry path drive the same Markdown rendering as the&Registrypath.
Fixed
- Rust
ApprovalResult/ApprovalRequest— adapted to apcore 0.21's#[non_exhaustive]annotations via thelet mut x = X::default(); x.field = ...; xconstruction pattern. - Rust binding-error match — removed deleted
ApcoreErrorCode::BindingPolicyViolationvariant (apcore 0.21 dropped it; the wire string remains supported via the constants table for backward-compat with legacy emitters). - Cross-SDK
arguments: nullpreservation — preview handler now forwards null/missingargumentsverbatim toexecutor.validate()instead of silently coercing to{}. Lets the calling business decide whether null is a valid module input.
Tests
- Python: 771 passed (was 758, +13 net)
- Rust: 843 passed (was 828, +15 net)
- TypeScript: 549 passed (was 534, +15 net)
v0.14.0
Added
- 2 new features (F-042, F-043) leveraging apcore 0.19.0 + apcore-toolkit 0.5.0:
- Extension Bridge (F-042, P1) — formalizes
ExtensionManager → MCPServerFactorywiring; centralizes resolution precedence (kwarg > ExtensionManager > built-in default) forschema_converter,annotation_mapper,error_mapper, and the load-order policy (extensions first, built-in middleware second). Seedocs/features/extension-bridge.md. - Async Task Bridge (F-043, P1) — routes async-hinted modules (
metadata.async == trueorannotations.extra.mcp_async == "true") to apcore'sAsyncTaskManager.submit()via four reserved meta-tools (__apcore_task_submit,__apcore_task_status,__apcore_task_cancel,__apcore_task_list). Progress fan-out via_meta.progressToken. Seedocs/features/async-task-bridge.md.
- Extension Bridge (F-042, P1) — formalizes
- W3C Trace Context propagation — inbound
_meta.traceparentis parsed into the apcoreContext.trace_parent; outbound responses carry a freshly-minted_meta.traceparentso MCP clients can correlate trace chains across module boundaries (Python only at v0.14.0; TypeScript and Rust parse inbound but do not yet inject outbound — tracked in cross-language sync report). - Observability auto-wiring —
serve(observability=True)/APCoreMCP(observability=True)instantiateMetricsCollector+MetricsMiddlewareandUsageCollector+UsageMiddlewareon the Executor and expose/{explorer_prefix}/api/usage(and/api/usage/{module_id}) endpoints. CLI flag--observabilitytoggles the same. - isinstance-based error dispatch —
ErrorMapperdispatchesTaskLimitExceededError,DependencyNotFoundError, andDependencyVersionMismatchErrorvia class checks against apcore 0.19 error classes (no longer duck-typed by code). - Expanded
ModuleAnnotationssurfacing —cache_ttl,cache_key_fields, andpagination_stylenow appear in the description annotation block when non-default; aligns with apcore 0.19's 12-fieldModuleAnnotations. - 4 new error code mappings —
DEPENDENCY_NOT_FOUND,DEPENDENCY_VERSION_MISMATCH,TASK_LIMIT_EXCEEDED(withretryable: True),VERSION_CONSTRAINT_INVALID.
Changed
- Dependency bump: requires
apcore >= 0.19.0(was>= 0.17.1) forAsyncTaskManager,TraceContext,Context.create(trace_parent=...), the 12-fieldModuleAnnotations, and the new dependency/binding error classes. - New optional dependency:
apcore-toolkit >= 0.5.0— providesBindingLoader/BindingParserandScannedModule.displayfor consumers loading.binding.yamlfiles. Not wired by apcore-mcp itself; available transitively for downstream callers. ExecutionRouter.handle_callresponsecontentitem type widened fromlist[dict[str, str]]tolist[dict[str, Any]]to carry the optional_metafield. Translated to MCPTextContent.metaon the wire.MCPServerFactory.register_handlersgains optionalasync_bridgeanddescriptor_lookupkwargs (Python). Backward-compatible: when omitted, behavior is unchanged.- Updated PRD to v1.8 (46 features), SRS to v2.0, Tech Design to v1.9, Test Plan to v1.8.
- Feature count: P0=9, P1=15, P2=22, Total=46.
Cross-language sync — deferred-modules round (2026-04-28)
Follow-up sweep across the 10 modules deferred from the original 0.14.0 sync. Each item below is a cross-SDK divergence or contract gap closed inside this release; together they reduce the outstanding critical/high backlog from 24 to 1 (EB-1 spec drift, recommended for spec revision).
- EUI-1 —
/validateendpoint:mcp-embedded-uiminimum bumped to0.4.0across all three SDKs. The newPOST /tools/{name}/validateroute flows automatically through the existingcreate_mount/createNodeHandleradapters; schema-only validation, ungated byallow_execute/ authenticator, never invokes the router'shandle_call. TC-011 integration tests added in all three SDKs. - TM-4 (Python) — transport-disconnect cancellation forwarding:
TransportManager.set_async_task_bridge(bridge)matches TSsetAsyncTaskBridgeand Rustset_cancel_handler. The transport scopes a per-connection session id via the newtransport_session_varContextVar;factory.handle_call_toolforwards it assession_keytobridge.submit(...), and on transport teardown the manager callsbridge.cancel_session_tasks(session_id). Wired automatically byserve(),async_serve(), andAPCoreMCP.serve/async_servewhen an async bridge is present. 6 regression tests added. - EM-1 (Python) —
McpErrorFormattercanonical class name with backwards-compatibleMCPErrorFormatteralias for cross-SDK API parity. Both names are exported fromapcore_mcpandapcore_mcp.adapters. - EM-3 (Python+Rust) — hardcoded
userFixable=truefor dependency / binding / version-constraint error codes (matches TS). AffectsDEPENDENCY_NOT_FOUND,DEPENDENCY_VERSION_MISMATCH,VERSION_CONSTRAINT_INVALID,BINDING_SCHEMA_INFERENCE_FAILED,BINDING_SCHEMA_MODE_CONFLICT,BINDING_STRICT_SCHEMA_INCOMPATIBLE,BINDING_POLICY_VIOLATION. apcore 0.19's error classes don't yet setuser_fixable=truethemselves, so the bridge stamps the hint to give MCP clients a consistent self-healing signal. 9 Python + 5 Rust regression tests added. - EM-6 (Rust) — generic-error fallback:
ErrorMapper::internal_error_response()andErrorMapper::to_mcp_error_any<E: std::error::Error>()return the{is_error:true, error_type:"GENERAL_INTERNAL_ERROR", message:"Internal error occurred", details:null}envelope for any non-ModuleErrorinput — matches Python'sto_mcp_error(error: Exception)and TypeScript'stoMcpError(error: unknown). 3 regression tests added. - MID-5 — bijection-guarded denormalize across all 3 SDKs: new variants
try_denormalize(Python) /tryDenormalize(TS) /denormalize_checked(Rust) run the dash→dot replacement and validate the result againstMODULE_ID_PATTERN, returningNone/null/Err(InvalidModuleId)if the input was not produced bynormalize. Plaindenormalizestays lenient (backwards compatible). Useful for sanitizing OpenAI tool-call responses against the registered module set. 8 Python + 9 TS + 5 Rust regression tests added. - OC-1 (TypeScript) — strict-mode walker parity with Python+Rust: the TS strict-mode pipeline now mirrors apcore's canonical
to_strict_schema: promotesx-llm-description→description, strips allx-*extension keys after promotion, recurses intooneOf/anyOf/allOfand$defs/definitions, sorts property names alphabetically, and removesdefaultvalues. Output now matches Python+Rust (which delegate to apcore directly). 6 regression tests added. - AH-1 (Rust) — per-request elicit callback via task-local: added
tokio::task_local! ELICIT_CALLBACKinapcore_mcp::helpers.ElicitationApprovalHandler::request_approvalnow resolves the callback from the task-local first (matching Python+TS, which read it fromcontext.data), with the constructor field as a fallback. apcore-rust'sContext::datacannot hold boxedFns, so a task-local is the closest cross-SDK equivalent without changing apcore. 4 regression tests added. - EB-2 (Python + TypeScript) — adapter-hook kwargs in
serve()/async_serve(): passschema_converter,annotation_mapper, anderror_mapperto override the factory's built-in adapters. Useful for extensions that customize JSON-Schema strictness, the annotation wire format, or error formatting. Rust deferred to a future release because its adapters are stateless unit structs and require a trait-based redesign first. - JWT-2 (Python) — case-insensitive
Authorizationheader lookup.JWTAuthenticator.authenticatenow tries bothheaders["authorization"]andheaders["Authorization"]. ASGI lower-cases header names but direct callers (tests, hooks, manual invocations) may pass the capitalised form; RFC 7230 §3.2 mandates case-insensitive header names. Matches TS+Rust behaviour. 1 regression test. - AM-L1 — F-041 annotation extras format unification across all 3 SDKs.
mcp_*extras now appear after the[Annotations: ...]block as<stripped-key>: <value>lines separated by single newlines (matches the wire format that TypeScript was already emitting). Pre-fix Python emitted each extra as its own section separated by\n\n; pre-fix Rust inlined them into the[Annotations: ...]block asmcp_key=value. 1 regression test in each SDK.
Breaking changes (cross-SDK API unification)
- JWT-1 —
Authenticator.authenticatesignature unified. All three SDKs now useauthenticate(headers: HeaderMap) -> Awaitable<Identity | null>.- Python:
authenticateis nowasync. Existing sync implementations continue to work via the newapcore_mcp.auth.protocol.call_authenticator(auth, headers)helper, which inspects the return value and awaits if it's a coroutine. Tests forJWTAuthenticatorare nowasync def. - TypeScript:
Authenticator.authenticatenow takesRecord<string, string>instead ofIncomingMessage. Use the newextractHeaders(req)helper (re-exported from the package root) to flatten a NodeIncomingMessagebefore calling. - Rust: no change (already aligned).
- Python:
- OC-5 — Rust
convert_registrysignature. The canonical Rust entrypoint now takes&apcore::registry::Registrydirectly (matching Python+TS duck-typed Registry input). The pre-fix&Value-snapshot variant is preserved asconvert_registry_jsonfor callers that hold a serialized snapshot.
Migration
This release contains several behaviour-changing fixes. None of the runtime changes are silent — each is observable via type errors or test failures during upgrade.
Python
Authenticator.authenticateis nowasync. Existing sync implementations continue to work via the bridge:# Before (sync): class MyAuth: def authenticate(self, headers: dict[str, str]) -> Identity | None: ...
...
v0.13.0
Added
- 6 new features (F-036 through F-041) fully leveraging apcore 0.17.1:
- Pipeline Strategy Selection (F-036, P1) —
serve(strategy="standard"|"internal"|"testing"|"performance"|"minimal")parameter, CLI--strategyflag, and Config Busmcp.pipeline.strategy. - Pipeline Observability (F-037, P2) —
serve(trace=True)enablescall_async_with_trace();PipelineTracedata in_meta.traceresponse, Explorer, and MetricsCollector. - Tool Output Redaction (F-038, P1) —
redact_sensitive(output, output_schema)applied by default before serialization. Fields withx-sensitiveor_secret_*keys replaced with"***REDACTED***". - Tool Preflight Validation (F-039, P2) —
ExecutionRouter.validate_tool()and ExplorerPOST /validateendpoint for dry-run viaExecutor.validate(). - YAML Pipeline Configuration (F-040, P2) — Config Bus
mcp.pipelinesection for declarative pipeline customization viabuild_strategy_from_config(). - Annotation Metadata Passthrough (F-041, P2) —
ModuleAnnotations.extrakeys prefixed withmcp_flow to tool descriptions and Explorer.
- Pipeline Strategy Selection (F-036, P1) —
- 4 new error mappings —
ConfigEnvMapConflictError,PipelineAbortError,StepNotFoundError,VersionIncompatibleError. - RegistryListener wired to
serve(dynamic=True)— dynamic tool registration now operational.
Changed
- Dependency bump: requires
apcore >= 0.17.1(was>= 0.15.1) for Pipeline v2 delegation, step metadata, YAML pipeline configuration,build_minimal_strategy(),requires/provideson BaseStep, and sensitive field redaction. - Pipeline v2 alignment — 11-step pipeline with
call_chain_guard(renamed fromsafety_check), middleware before input validation. - Updated PRD to v1.7 (41 features), SRS to v1.8 (127 requirements), Tech Design to v1.7, Test Plan to v1.6.
- Feature count: P0=9, P1=11, P2=21, Total=41.