Repository navigation
Merge PRs #2 to #9 into main - #10
Merged
Merged
Conversation
…sh timeouts and clear the guard at final shutdown
A jsonSerialize() that returns a new object on every call, or a long chain of wrappers, made the normalizer recurse until PHP ran out of memory. Containers and jsonSerialize() expansions now count towards a 512 nesting cap that is separate from the TS depth; past it a RangeException drops the event with one 'Failed to truncate event' line. The last-resort clone gets the same cap.
The last resort collected string paths with array_merge() per entry and copied each branch by value for every cut, so 16 000 strings of 200 units took over 6 s. Paths now go into one accumulator and cuts are applied in place on the private clone. The output is unchanged.
…events (array) $object names protected and private properties "\0*\0name" and "\0Class\0name". PHP refuses such keys as property names, so every walker that built a stdClass from them threw and the event was dropped. Values::entries() now leaves them out, like the non-public properties they are, and the walkers read array entries through it.
capture() encoded other values with json_encode() flags that fail on invalid UTF-8 and on NaN or INF, so a database error array with latin1 bytes or a NaN ratio came out as [object Object]. The value now goes through WireValue and the wire encoder flags: bad bytes become U+FFFD, non-finite numbers null, dates ISO strings and closure properties are left out, as in TS.
The three tool-list injection failure lines called getMessage() directly. A legacy exception that stores an object as its message made that call throw out of inject(), and an array message raised a warning. They now use ErrorText::message(); ordinary messages log the same text as before.
A test process that Infection kills never runs the shutdown cleanup, so its HOME was left behind in /tmp. The HOME now lives in .cache/test-home/<id> inside the repo; the cleanup is unchanged and no process sweeps the others.
…ber contract tests
…brary's listing members
…mpty _meta as an object
… AGENTCAT_ENABLED=false turn AgentCat off
…nd PHPStan rector/rector 2.7.0 preloads its bundled phpstan/phpdoc-parser 2.x into PHPUnit 12+ processes, and phpdocumentor/type-resolver below 1.10 calls the 1.x ConstExprParser constructor, so mcp/sdk tools/list failed with -32603 on the lowest set. symfony/polyfill-uuid below 1.20 pulls paragonie/random_compat 2.0, whose untyped random_bytes() shadows the built-in for PHPStan, and psr/log 2.0's LoggerTrait leaves log() without a context value type.
AgentCat's tools/list and tools/call answers reached mcp/sdk's ResponseEvent listeners as an internal WireResult. They are now subclasses of ListToolsResult and CallToolResult whose typed fields are the library's (or the tool's) own result and whose jsonSerialize() returns the exact wire tree, so the bytes on the wire do not change. A contract test checks that both parents stay extendable with the same constructor, and the manifest lists them for the drift job.
AgentCat::track(), flush() and shutdown() created the curl multi handle in the CurlTransport constructor, so a PHP with curl_multi_init in disable_functions died with a fatal error before any try block. The multi handle now starts with the first transfer, and a transport whose curl functions are missing drops every delivery and logs one line.
…ptions signals() read Builder::$parts outside StrictErrors, so a read that warns (a member a new mcp/sdk removed) reached PHP's error handler, which prints to STDOUT on a stdio server.
They say that AgentCat is off, so a PSR-3 logger that keeps WARNING and above must still show them. The text is unchanged.
TS listWrap logs its no-tools warning when tools is not an array or is empty, then serves the answer as is. The PHP listing warned only for an empty list.
A tracked server is owned by the client that tracked it first. Custom events for that server (Plan C2) must go out through that client's publisher, so TrackedServer now holds it.
Every comment in src/ and tests/ now states, in plain words, the rule or the reason it relies on. Decision IDs (D07), bug IDs (B10) and criterion IDs (ac-05-E5) stay. CapturingCallTool names the tested laravel/mcp versions, 1.0.0 to 1.1.0. Outside comments, unused test group attributes go, and three dataset names and one assertion message are reworded; no src/ code changes. PackageFilesTest guards the committed text of src/, tests/, config/, resources/, .github/, README.md, CHANGELOG.md and CONTRIBUTING.md, a reference that wraps onto the next comment line included.
…he criteria list does
The guard pattern is case-insensitive, and it now reads recipe/ and every committed root file. An explicit list allows the ordinary phrases it would otherwise flag: MCP's own tasks ("task 2 of an MCP server", "task 2 is cancelled") and another document's appendix. A data provider holds positive and negative examples. Comments spell a criterion as the criteria list does (ac-03-11).
Comments only in src/: the docblocks are reflowed, the drift reason is stated once in Drift, and CapturingToolInvoker names laravel/mcp 1.1.0. The package probe lists the dist's top-level entries with bash globs instead of GNU find's -printf.
…ignoring every unknown App class
Composer's pretty version of a tag keeps the tag's v (v0.9.0). The log prefix, the diagnostics attributes, the events and the User-Agent now carry the bare version (0.9.0), as the TypeScript SDK names versions, so one release has one value across SDKs.
The changelog names no Symfony Flex recipe: the recipe reaches Flex after the release. The tools/list line says in a customer's words that a tool on a later page keeps its own parameters, that under per-request runtimes the server's own tools/list handler runs once for each page read during a tools/call, and what that costs, without IDs a reader cannot look up. The contributing guide answers issues and pull requests as soon as we can, with no deadline (D70).
A customer's get_more_tools on a page before the last made the last page append AgentCat's too, so a client saw two tools of that name. A tracked server now remembers a page that named the customer's tool and appends nothing to its last page. A process that never read that page, every request under PHP-FPM, cannot know it: the shadowing warning now says that AgentCat's tool is then listed too, on the last page, and to rename the customer's tool.
…line With usleep() disabled, a wait for a retry threw inside the transport's guard, and the delivery loop ran hot until its deadline: about 590,000 passes in the 5 s shutdown budget of a request whose event API failed. A wait now sleeps with usleep(), or with time_nanosleep() where usleep() is disabled. Where both are disabled, the wait says it cannot block, one line per process says why, and the delivery ends at the first retry that is not due yet.
…es only A customer's project takes stable releases: with minimum-stability dev, a dependency that has only dev releases would pass the probe and fail for customers. The release legs now resolve with stable, and only the dev-main leg keeps dev.
…ges off STDOUT The dev-main jobs write PHPUnit's failures, STDERR included and without colors, to a report that the issue step appends, as the snapshot and branding jobs do, so the issue names the failing test. The PHP nightly job prints PHP's own messages to STDERR: a dependency's deprecation can no longer break the tests that send headers, and the log still shows it.
…d alone The early return after the customer's own tool had become a second way to the same answer: the record of that tool already keeps the last page free of AgentCat's.
…eline opis/json-schema calls spl_object_hash(), which PHP 8.6 deprecates. PHPUnit counts the deprecation, and failOnDeprecation fails the PHP 8.6 and PHP nightly jobs although every test passes. These two jobs now read phpunit-baseline.xml, which lists only that line. Every other deprecation still fails them. The baseline stays in the repository root, because PHPUnit joins the baseline's directory and each path without resolving "..". It is not in phpunit.xml.dist, because Infection copies that file to a temporary directory without rewriting the baseline path. A test reads the file, so a broken baseline fails on every PHP version, not only in the PHP 8.6 jobs.
…agist CI tested fixed versions of the MCP libraries: the lowest and highest of each require-dev range, plus mcp/sdk 0.9.0 pinned. A caret range on 0.x stops at the next minor, so a new mcp/sdk 0.9.1 or 0.10, symfony/mcp-bundle 0.15 or laravel/mcp 2.0 never ran, although composer.json lets a customer install it. Like the TypeScript SDK's mcp-compatibility.yml, a discover job now reads Packagist on every pull request and every night. It picks the newest patch of each minor release of mcp/sdk, laravel/mcp and symfony/mcp-bundle, from the lowest version that require-dev allows. The released jobs run PHPStan and the adapter suites against each of them, and the package probe runs on each mcp/sdk release. In the nightly, a failing release opens an issue. - tests/Support/ReleaseDiscovery.php selects the releases, and tests/bin/released-versions.php prints them for the matrix. - tests/bin/require-library.sh installs one version of a library. The local tests/bin/mcp-sdk-leg.sh uses it too. - tests/Support/HttpGet.php holds the GET that branding-drift.php had inline. - WorkflowsTest makes sure that All green needs every gating CI job, that the released jobs take the discovered releases, and that every nightly job opens its issue when it fails. - The released jobs replace the mcp/sdk 0.9.0 job.
The package probe ran on PHP 8.3 with every discovered mcp/sdk release. A release that needs PHP 8.4 cannot install there, so every pull request would fail without an AgentCat defect. ReleaseDiscovery now expands Packagist's minified rows, reads each release's PHP requirement with composer/semver, and gives each row the lowest PHP that CI tests and the release allows. - The nightly also runs the package probe on each discovered mcp/sdk release. - A nightly issue names the minor release, so a failure that lasts across its patches stays in one issue. The issue shows the end of the install, PHPStan and PHPUnit output, or the message of released-versions.php. - released-versions.php tries a failed GET twice more, and exits 1 for an unreadable composer.json as its header says. - The discover steps make sure that the output is a JSON list that is not empty before the matrix reads it. - require-library.sh requires the version without an update, then runs a full update, so every root package can move. - composer/semver is a dev dependency now; it came only through orchestra.
feat: event pipeline, curl transport and shutdown delivery
feat: mcp/sdk adapter and AgentCat::track()
feat: publishCustomEvent, AgentCat::fake(), signal drain and end-to-end tests
feat: exporters (OTLP, Datadog, Sentry, PostHog) and diagnostics
feat: laravel/mcp adapter, Laravel bridge and runtime delivery
Symfony bundle and runtime delivery tests in Docker
Compatibility and release preparation for 1.0.0
ci: discover MCP library releases on Packagist and test each minor
kashishhora
approved these changes
Oct 22, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
PRs #2 to #9 merged into
feat/plan-a-foundation, not intomain: each one still had the branch of the PR before it as its base. This PR brings them tomain.feat/plan-a-foundationholds the merge commits of feat: event pipeline, curl transport and shutdown delivery #2 to ci: discover MCP library releases on Packagist and test each minor #9, and its tree is the same as the tree of ci: discover MCP library releases on Packagist and test each minor #9 (feat/release-discovery).mainhas no conflicts, and the merged tree is the same as the tree offeat/plan-a-foundation.Test plan