Skip to main content
The PHP SDK instruments servers built with the official PHP MCP SDK, mcp/sdk 0.7.x, on PHP 8.1 and newer. It captures sessions and tool calls without changing how your server behaves.

Install

Set your API key

The SDK reads your configuration from the environment via Config::fromEnvironment():
Copy both values from the dashboard when you mint the key. US workspaces can omit ANALYTICS_INGEST_URL (the SDK defaults to the US endpoint); EU workspaces must set it to https://eu.armature.tech/api/mcp-analytics/ingest. If the key is missing, the SDK quietly no-ops — your server keeps working, and no data is sent.

Instrument your server

Call Analytics::instrument() on the builder before adding or discovering tools:
server.php
Manual tools, explicit Builder::add(...) definitions, custom loaders, and attribute discovery are all covered — the instrumentation decorates the official registry after definitions are finalized. If your application already passes a custom container, registry, or referenceHandler to the builder, hand the same instances to Analytics::instrument(...) — otherwise the SDK installs its own and tools registered through yours could bypass the wrapper:
Supplying a custom registry makes mcp/sdk 0.7 finalize builder loaders eagerly during build(); loader failures stay visible rather than being hidden.

Streamable HTTP

Add the analytics PSR-15 middleware after your authentication middleware and keep the official transport defaults:
Server::run() returns the PSR-7 response for HTTP transports — return $response from your framework handler, or pass it to your response emitter in a standalone script; nothing is sent to the client otherwise. If your application doesn’t already provide PSR-17 factories and a PSR-7 server-request creator, install one implementation: composer require nyholm/psr7 nyholm/psr7-server. The middleware retains only the request headers and scalar authentication attributes needed for attribution. It never reads the request body or changes the request or response.
PHP is request-scoped by design, so delivery is awaited by default — events are sent before the response returns. That’s the right mode for PHP-FPM, Lambda, and other serverless runtimes with no extra configuration.

Configuration

Config::fromEnvironment() is the only path that reads ANALYTICS_INGEST_API_KEY and ANALYTICS_INGEST_URL — the bare constructor defaults apiKey to null, which disables delivery. To customize options, carry the environment values over:
The redaction callbacks redact and redactEvent are documented in the SDK README. Two more options live next to them: redactSecrets (boolean, default true) turns built-in credential redaction off, and telemetryFieldMap maps existing tool arguments onto telemetry fields.

Flush on shutdown

For long-running processes call $analytics->close() when the server exits (as in the stdio example above); in request-scoped runtimes call $analytics->flush() after the transport runs.

Verify locally

The language-independent doctor can inspect a running server, verify the telemetry contract on every tool, and authenticate your ingest key. The doctor is Node-based and, outside a JavaScript project, its MCP client package must be listed explicitly (it is an optional peer dependency, so bare npx @armature-tech/mcp-analytics doctor fails with ERR_MODULE_NOT_FOUND):
Use --skip-ingest for an offline schema check and --json for machine-readable output.

Troubleshooting

mcp/sdk 0.7 accepts closures, class/method pairs, and invokable class strings — not invokable object instances. Adapt a bare named function or invokable object with Closure::fromCallable(...) before passing it to addTool().
Keep your application’s existing PSR-17 implementation, or install the Nyholm packages shown above, then pass the factories explicitly to the transport.
The SDK supports PHP 8.1+ and mcp/sdk >=0.7.0 <0.8.0. Upgrade or constrain your application explicitly rather than bypassing Composer’s platform checks.
Check that ANALYTICS_INGEST_API_KEY is set in the server’s environment (missing keys no-op silently by design), that ANALYTICS_INGEST_URL matches your workspace region — EU keys are rejected by the US endpoint — and pass an onError callback to surface delivery failures. If you construct Config yourself, remember the bare constructor never reads environment variables — start from Config::fromEnvironment().