MCP PHP SDK

Builder

FinalYes
Tags
phpstan-import-type

Handler from ElementReference

author

Kyrian Obikwelu koshnawaza@gmail.com

Table of Contents

Properties

$cachePolicy  : CachePolicy|null
$container  : ContainerInterface|null
$discoverer  : DiscovererInterface|null
$discoveryBasePath  : string|null
$discoveryCache  : CacheInterface|null
$discoveryExcludeDirs  : array<string|int, mixed>|array<string|int, string>
$discoveryNamePatterns  : array<string|int, string>|null
$discoveryScanDirs  : array<string|int, string>
$eventDispatcher  : EventDispatcherInterface|null
$explicitPrompts  : array<int, ProviderInterface>}>
$explicitResources  : array<int, ResourceHandlerInterface}>
$explicitResourceTemplates  : array<int, ProviderInterface>}>
$explicitTools  : array<int, ToolHandlerInterface}>
$extensionMessages  : array<int, Request>|Notification>>
$extensionMethods  : array<string, string>
$extensions  : array<string, array<string, mixed>>
$gcDivisor  : int
$gcProbability  : int
$hasCustomRegistry  : bool
$headerValidation  : bool
$inputRequiredRounds  : int
$inputRequiredShim  : bool
$inputRequiredTimeout  : int
$instructions  : string|null
$lazyLoading  : bool
$loaders  : array<string|int, LoaderInterface>
$logger  : LoggerInterface|null
$modernVersions  : array<int, ProtocolVersion>|null
$notificationBus  : NotificationBusInterface|null
$notificationHandlers  : array<int, NotificationHandlerInterface>
$paginationLimit  : int
$parts  : AssembledParts|null
$prompts  : array<string|int, Icon[], meta: ?array}>
$protocolVersion  : ProtocolVersion|null
$referenceHandler  : ReferenceHandlerInterface|null
$registry  : RegistryInterface
$requestHandlers  : array<int, RequestHandlerInterface<string|int, mixed>>
$requestStateKey  : string|null
$requestStateTtl  : int
$resources  : array<string|int, Icon[], meta: ?array}>
$resourceTemplates  : array<string|int, Annotations, meta: ?array}>
$schemaGenerator  : SchemaGeneratorInterface|null
$serverCapabilities  : ServerCapabilities|null
$serverInfo  : Implementation|null
$sessionManager  : SessionManagerInterface|null
$sessionStore  : SessionStoreInterface|null
$subscriptionLifetime  : float
$subscriptionManager  : SubscriptionManagerInterface|null
$tools  : array<string|int, Icon[], meta: ?array, outputSchema: ?array}>

Methods

add()  : self
Registers an element using an explicit schema value object paired with a handler interface.
addLoader()  : self
Register a single custom loader.
addLoaders()  : self
addNotificationHandler()  : self
Register a single custom notification handler.
addNotificationHandlers()  : self
Register multiple custom notification handlers.
addPrompt()  : self
Manually registers a prompt handler.
addRequestHandler()  : self
Register a single custom method handler.
addRequestHandlers()  : self
Register multiple custom method handlers.
addResource()  : self
Manually registers a resource handler.
addResourceTemplate()  : self
Manually registers a resource template handler.
addTool()  : self
Manually registers a tool handler.
build()  : Server
Builds the fully configured Server instance.
buildStateless()  : StatelessProtocol
Builds a dispatcher for the modern (SEP-2575) lifecycle on its own.
enableExtension()  : self
Enable one or more MCP protocol extensions, announced to clients under `capabilities.extensions` during the initialize handshake.
setCachePolicy()  : self
Sets how long, and to whom, this server's answers may be cached (SEP-2549).
setCapabilities()  : self
Explicitly set server capabilities. If set, this overrides automatic detection.
setContainer()  : self
Provides a PSR-11 DI container, primarily for resolving user-defined handler classes.
setDiscoverer()  : self
setDiscovery()  : self
setEventDispatcher()  : self
setHeaderValidator()  : self
Controls whether {@see self::buildStateless()} checks the standard MCP request headers (SEP-2243) against the JSON-RPC body. On by default.
setInputRequiredLimits()  : self
Bounds on the shim's loop: how many times a handler may be re-entered for one request, and how long one answer is waited for.
setInstructions()  : self
Configures the instructions describing how to use the server and its features.
setLazyLoading()  : self
Controls when configured loaders (manual elements, discovery, custom loaders) run.
setLogger()  : self
Provides a PSR-3 logger instance. Defaults to NullLogger.
setModernVersions()  : self
Revisions the modern-era leg answers for. Defaults to every modern revision this SDK knows.
setNotificationBus()  : self
Sets the bus carrying server-initiated notifications to open `subscriptions/listen` streams (SEP-2575).
setPaginationLimit()  : self
Configures the server's pagination limit.
setProtocolVersion()  : self
setReferenceHandler()  : self
setRegistry()  : self
setRequestState()  : self
Sets the key signing the `requestState` carried across the rounds of a multi round-trip request (SEP-2322). Without one, every echoed state is refused.
setResourceSubscriptionManager()  : self
setSchemaGenerator()  : self
setServerInfo()  : self
Sets the server's identity. Required.
setSession()  : self
Configures the session layer.
setSubscriptionLifetime()  : self
Sets how long a `subscriptions/listen` stream is held open before the server closes it gracefully.
withoutInputRequiredShim()  : self
Stop serving multi round-trip handlers to handshake-era clients.
withoutModernEra()  : self
Serve only the handshake era, refusing modern-era traffic.
assemble()  : AssembledParts
Resolves the builder's configuration into the parts both lifecycles need.
createDiscoverer()  : DiscovererInterface
detectCapabilities()  : ServerCapabilities
When loaded, capabilities are read from the registry. When deferred, reading it would force the load, so they are advertised from the configured sources instead — opaque sources (custom loaders, discovery) advertise all kinds, and over-advertising is harmless per MCP semantics.
requestStateCodec()  : RequestStateCodec|null
resolve()  : AssembledParts

Properties

$container

private ContainerInterface|null $container = null

$discoverer

private DiscovererInterface|null $discoverer = null

$discoveryBasePath

private string|null $discoveryBasePath = null

$discoveryCache

private CacheInterface|null $discoveryCache = null

$discoveryExcludeDirs

private array<string|int, mixed>|array<string|int, string> $discoveryExcludeDirs = []

$discoveryNamePatterns

private array<string|int, string>|null $discoveryNamePatterns = null

$discoveryScanDirs

private array<string|int, string> $discoveryScanDirs = []

$eventDispatcher

private EventDispatcherInterface|null $eventDispatcher = null

$explicitPrompts

private array<int, ProviderInterface>}> $explicitPrompts = []

$explicitResources

private array<int, ResourceHandlerInterface}> $explicitResources = []

$explicitResourceTemplates

private array<int, ProviderInterface>}> $explicitResourceTemplates = []

$explicitTools

private array<int, ToolHandlerInterface}> $explicitTools = []

$extensionMessages

private array<int, Request>|Notification>> $extensionMessages = []

$extensionMethods

private array<string, string> $extensionMethods = []

RPC method to the extension identifier defining it

$extensions

private array<string, array<string, mixed>> $extensions = []

$gcDivisor

private int $gcDivisor = 100

$gcProbability

private int $gcProbability = 1

$hasCustomRegistry

private bool $hasCustomRegistry = false

$headerValidation

private bool $headerValidation = true

$inputRequiredRounds

private int $inputRequiredRounds = \Mcp\Server\InputRequiredShim::DEFAULT_MAX_ROUNDS

$inputRequiredShim

private bool $inputRequiredShim = true

$inputRequiredTimeout

private int $inputRequiredTimeout = \Mcp\Server\InputRequiredShim::DEFAULT_ROUND_TIMEOUT

$instructions

private string|null $instructions = null

$lazyLoading

private bool $lazyLoading = true

$logger

private LoggerInterface|null $logger = null

$modernVersions

private array<int, ProtocolVersion>|null $modernVersions = null

null defaults to every modern revision, [] serves none

$paginationLimit

private int $paginationLimit = 50

$parts

private AssembledParts|null $parts = null
Tags
see
self::assemble()

for why this is memoized

$prompts

private array<string|int, Icon[], meta: ?array}> $prompts = []

$requestStateKey

private string|null $requestStateKey = null

$requestStateTtl

private int $requestStateTtl = 600

$resources

private array<string|int, Icon[], meta: ?array}> $resources = []

$resourceTemplates

private array<string|int, Annotations, meta: ?array}> $resourceTemplates = []

$subscriptionLifetime

private float $subscriptionLifetime = 30.0

$tools

private array<string|int, Icon[], meta: ?array, outputSchema: ?array}> $tools = []

Methods

add()

Registers an element using an explicit schema value object paired with a handler interface.

public add(Tool|ResourceDefinition|ResourceTemplate|Prompt $definition, ElementHandlerInterface $handler[, array<string, ProviderInterface$completionProviders = [] ]) : self

Use this entry point when an element's name, schema, or description is only known at runtime (e.g. config-driven integrations). For statically-known elements, prefer addTool/addResource/addResourceTemplate/addPrompt, which can derive metadata from reflection of the handler.

Mismatched pairings (e.g. a Tool with a PromptHandlerInterface) raise Mcp\Exception\InvalidArgumentException. Completion providers are only supported on Prompt and ResourceTemplate definitions; supplying them with Tool or ResourceDefinition raises the same exception.

Parameters
$definition : Tool|ResourceDefinition|ResourceTemplate|Prompt
$handler : ElementHandlerInterface
$completionProviders : array<string, ProviderInterface> = []

Keyed by argument/variable name

Return values
self

addPrompt()

Manually registers a prompt handler.

public addPrompt(Handler $handler[, string|null $name = null ][, string|null $title = null ][, string|null $description = null ][, array<string|int, Icon>|null $icons = null ][, array<string, mixed>|null $meta = null ]) : self
Parameters
$handler : Handler
$name : string|null = null
$title : string|null = null
$description : string|null = null
$icons : array<string|int, Icon>|null = null
$meta : array<string, mixed>|null = null
Return values
self

addRequestHandlers()

Register multiple custom method handlers.

public addRequestHandlers(iterable<string|int, RequestHandlerInterface<string|int, mixed>> $handlers) : self
Parameters
$handlers : iterable<string|int, RequestHandlerInterface<string|int, mixed>>
Return values
self

addResource()

Manually registers a resource handler.

public addResource(Handler $handler, string $uri[, string|null $name = null ][, string|null $title = null ][, string|null $description = null ][, string|null $mimeType = null ][, int|null $size = null ][, Annotations|null $annotations = null ][, array<string|int, Icon>|null $icons = null ][, array<string, mixed>|null $meta = null ]) : self
Parameters
$handler : Handler
$uri : string
$name : string|null = null
$title : string|null = null

Optional human-readable title for display in UI

$description : string|null = null
$mimeType : string|null = null
$size : int|null = null
$annotations : Annotations|null = null
$icons : array<string|int, Icon>|null = null
$meta : array<string, mixed>|null = null
Return values
self

addResourceTemplate()

Manually registers a resource template handler.

public addResourceTemplate(Handler $handler, string $uriTemplate[, string|null $name = null ][, string|null $title = null ][, string|null $description = null ][, string|null $mimeType = null ][, Annotations|null $annotations = null ][, array<string, mixed>|null $meta = null ]) : self
Parameters
$handler : Handler
$uriTemplate : string
$name : string|null = null
$title : string|null = null

Optional human-readable title for display in UI

$description : string|null = null
$mimeType : string|null = null
$annotations : Annotations|null = null
$meta : array<string, mixed>|null = null
Return values
self

addTool()

Manually registers a tool handler.

public addTool(Handler $handler[, string|null $name = null ][, string|null $title = null ][, string|null $description = null ][, ToolAnnotations|null $annotations = null ][, array<string, mixed>|null $inputSchema = null ][, array<string|int, Icon>|null $icons = null ][, array<string, mixed>|null $meta = null ][, array<string, mixed>|null $outputSchema = null ]) : self
Parameters
$handler : Handler
$name : string|null = null
$title : string|null = null

Optional human-readable title for display in UI

$description : string|null = null
$annotations : ToolAnnotations|null = null
$inputSchema : array<string, mixed>|null = null
$icons : array<string|int, Icon>|null = null
$meta : array<string, mixed>|null = null
$outputSchema : array<string, mixed>|null = null
Return values
self

build()

Builds the fully configured Server instance.

public build() : Server

The result carries a dispatcher for each era. Which one answers is a per-request decision the transport makes, so one server object — and one endpoint — serves handshake-era and modern-era clients alike.

Return values
Server

buildStateless()

Builds a dispatcher for the modern (SEP-2575) lifecycle on its own.

public buildStateless([array<int, ProtocolVersion$supportedVersions = [ProtocolVersion::V2026_07_28] ]) : StatelessProtocol

Tools, prompts, resources and their handlers are era-independent, so one builder configuration drives either lifecycle. self::build() wires both together; this is the modern era by itself, for an endpoint that serves nothing else.

Parameters
$supportedVersions : array<int, ProtocolVersion> = [ProtocolVersion::V2026_07_28]

revisions this dispatcher will answer for

Return values
StatelessProtocol

enableExtension()

Enable one or more MCP protocol extensions, announced to clients under `capabilities.extensions` during the initialize handshake.

public enableExtension(ExtensionInterface ...$extensions) : self

An extension also contributes the message classes its methods decode into and the handlers serving them, if any — see AbstractExtension for extensions that only announce a capability.

Parameters
$extensions : ExtensionInterface
Tags
throws
InvalidArgumentException

if the identifier is not a valid _meta prefix

LogicException

if the same extension is enabled more than once, or two enabled extensions define the same RPC method

Return values
self

setCachePolicy()

Sets how long, and to whom, this server's answers may be cached (SEP-2549).

public setCachePolicy(CachePolicy $policy) : self

The modern lifecycle must put ttlMs and cacheScope on every cacheable result; without a policy it says "private, immediately stale", which is conformant and forfeits the point. Build one with CachePolicy::default() and narrow it per method:

$builder->setCachePolicy(
    CachePolicy::default(60_000)
        ->withMethod('tools/list', 3_600_000, CacheScope::Public),
);
Parameters
$policy : CachePolicy
Return values
self

setCapabilities()

Explicitly set server capabilities. If set, this overrides automatic detection.

public setCapabilities(ServerCapabilities $serverCapabilities) : self
Parameters
$serverCapabilities : ServerCapabilities
Return values
self

setContainer()

Provides a PSR-11 DI container, primarily for resolving user-defined handler classes.

public setContainer(ContainerInterface $container) : self

Defaults to a basic internal container.

Parameters
$container : ContainerInterface
Return values
self

setDiscoverer()

public setDiscoverer(DiscovererInterface $discoverer) : self
Parameters
$discoverer : DiscovererInterface
Return values
self

setDiscovery()

public setDiscovery(string $basePath[, array<string|int, string> $scanDirs = ['.', 'src'] ][, array<string|int, string> $excludeDirs = [] ][, CacheInterface|null $cache = null ][, array<string|int, string> $namePatterns = DiscovererInterface::DEFAULT_NAME_PATERNS ]) : self
Parameters
$basePath : string
$scanDirs : array<string|int, string> = ['.', 'src']
$excludeDirs : array<string|int, string> = []
$cache : CacheInterface|null = null
$namePatterns : array<string|int, string> = DiscovererInterface::DEFAULT_NAME_PATERNS
Return values
self

setEventDispatcher()

public setEventDispatcher(EventDispatcherInterface $eventDispatcher) : self
Parameters
$eventDispatcher : EventDispatcherInterface
Return values
self

setHeaderValidator()

Controls whether {@see self::buildStateless()} checks the standard MCP request headers (SEP-2243) against the JSON-RPC body. On by default.

public setHeaderValidator([bool $headerValidation = true ]) : self

Disable for a StatelessProtocol served by a transport with no header layer — the validator would otherwise reject every request for carrying none of the standard headers.

Parameters
$headerValidation : bool = true
Return values
self

setInputRequiredLimits()

Bounds on the shim's loop: how many times a handler may be re-entered for one request, and how long one answer is waited for.

public setInputRequiredLimits(int $maxRounds, int $roundTimeout) : self

The wait holds the originating request open, so on a process-per-request runtime it holds a worker too. Size it against your pool, not against a user's patience.

Parameters
$maxRounds : int
$roundTimeout : int
Return values
self

setInstructions()

Configures the instructions describing how to use the server and its features.

public setInstructions(string|null $instructions) : self

This can be used by clients to improve the LLM's understanding of available tools, resources, etc. It can be thought of like a "hint" to the model. For example, this information MAY be added to the system prompt.

Parameters
$instructions : string|null
Return values
self

setLazyLoading()

Controls when configured loaders (manual elements, discovery, custom loaders) run.

public setLazyLoading([bool $lazyLoading = true ]) : self

Lazy (the default) defers loading to the first registry read so a persistent runtime does not freeze the registry to a source not yet ready at build time. Disable to load eagerly at build. A registry supplied via setRegistry() is always loaded eagerly.

Parameters
$lazyLoading : bool = true
Return values
self

setLogger()

Provides a PSR-3 logger instance. Defaults to NullLogger.

public setLogger(LoggerInterface $logger) : self
Parameters
$logger : LoggerInterface
Return values
self

setNotificationBus()

Sets the bus carrying server-initiated notifications to open `subscriptions/listen` streams (SEP-2575).

public setNotificationBus(NotificationBusInterface $bus) : self

Without one, a listen stream acknowledges and then carries nothing: there is no safe default, because the right implementation depends on whether the publisher and the stream share a process. InMemoryNotificationBus is correct for stdio and persistent runtimes; under PHP-FPM, where they are different workers, use Psr16NotificationBus or an implementation over your own broker.

Registry changes are published automatically when an event dispatcher is configured; anything else — notifications/resources/updated above all — is published by the application calling NotificationBusInterface::publish().

Parameters
$bus : NotificationBusInterface
Return values
self

setPaginationLimit()

Configures the server's pagination limit.

public setPaginationLimit(int $paginationLimit) : self
Parameters
$paginationLimit : int
Return values
self

setRequestState()

Sets the key signing the `requestState` carried across the rounds of a multi round-trip request (SEP-2322). Without one, every echoed state is refused.

public setRequestState(string $key[, int $ttl = 600 ]) : self

The same key must reach every instance that might serve the retry, so a per-process random value only works for a single-process deployment.

Parameters
$key : string

at least 32 bytes

$ttl : int = 600

how long a minted state stays valid, in seconds

Return values
self

setServerInfo()

Sets the server's identity. Required.

public setServerInfo(string $name, string $version[, string|null $description = null ][, array<string|int, Icon>|null $icons = null ][, string|null $websiteUrl = null ][, string|null $title = null ]) : self
Parameters
$name : string
$version : string
$description : string|null = null
$icons : array<string|int, Icon>|null = null
$websiteUrl : string|null = null
$title : string|null = null

Display name for UI and end-user contexts. Falls back to $name when absent.

Return values
self

setSession()

Configures the session layer.

public setSession([SessionStoreInterface|null $sessionStore = null ][, SessionManagerInterface|null $sessionManager = null ][, int $gcProbability = 1 ][, int $gcDivisor = 100 ]) : self
Parameters
$sessionStore : SessionStoreInterface|null = null
$sessionManager : SessionManagerInterface|null = null
$gcProbability : int = 1

The numerator of the GC probability fraction (like PHP's session.gc_probability). Set to 0 to disable GC.

$gcDivisor : int = 100

The denominator of the GC probability fraction (like PHP's session.gc_divisor). Probability = gcProbability/gcDivisor.

Return values
self

setSubscriptionLifetime()

Sets how long a `subscriptions/listen` stream is held open before the server closes it gracefully.

public setSubscriptionLifetime(float $seconds) : self

The real ceiling is the runtime's: under PHP-FPM a stream cannot outlive max_execution_time, and a value above it buys a killed worker instead of a longer subscription. Pass 0 for "until the client or the runtime ends it", which is what a persistent runtime wants.

Parameters
$seconds : float
Return values
self

withoutInputRequiredShim()

Stop serving multi round-trip handlers to handshake-era clients.

public withoutInputRequiredShim() : self

A handler that returns an InputRequiredResult is written for the modern era, where the client answers the embedded requests and retries the call. On a handshake-era connection the SDK fulfils it instead, by sending those requests over that connection's own channel and re-entering the handler with the answers — so one handler serves both eras. See InputRequiredShim for what re-entry costs.

Turn it off to have such a handler fail on a handshake-era connection rather than be fulfilled behind your back.

Return values
self

withoutModernEra()

Serve only the handshake era, refusing modern-era traffic.

public withoutModernEra() : self

The default is to serve both from whatever the server is run on, because an endpoint that turns a client away for speaking the newer revision is almost never what anyone wants. Call this when it is: a deployment that has to stay on the handshake wire, or one whose tools call back into the client and would fail the modern half anyway.

Return values
self

assemble()

Resolves the builder's configuration into the parts both lifecycles need.

private assemble() : AssembledParts

Memoized: the two eras share one registry, one session manager and one set of handler instances, so they answer for the same server rather than for two that merely started from the same configuration.

Return values
AssembledParts

createDiscoverer()

private createDiscoverer(LoggerInterface $logger) : DiscovererInterface
Parameters
$logger : LoggerInterface
Return values
DiscovererInterface

detectCapabilities()

When loaded, capabilities are read from the registry. When deferred, reading it would force the load, so they are advertised from the configured sources instead — opaque sources (custom loaders, discovery) advertise all kinds, and over-advertising is harmless per MCP semantics.

private detectCapabilities(RegistryInterface $registry, bool $eagerlyLoaded, EventDispatcherInterface|null $eventDispatcher) : ServerCapabilities
Parameters
$registry : RegistryInterface
$eagerlyLoaded : bool
$eventDispatcher : EventDispatcherInterface|null
Return values
ServerCapabilities

resolve()

private resolve() : AssembledParts
Return values
AssembledParts
On this page

Search results