Add title field to Prompt for MCP spec compliance (#278)
The MCP 2025-11-25 specification defines an optional `title` property on Prompt for human-readable display in UI contexts, distinct from the programmatic `name` identifier. This was missing from the SDK. BC Break: Builder::addPrompt() signature changed — $title parameter added between $name and $description. Callers using positional arguments for $description must switch to named arguments. Closes #276
This commit is contained in:
parent
863cfc1e78
commit
7af5a345c1
8 changed files with 21 additions and 6 deletions
|
|
@ -12,6 +12,8 @@ All notable changes to `mcp/sdk` will be documented in this file.
|
|||
* [BC break] Make Symfony Finder component optional. Users would need to install `symfony/finder` now themselves
|
||||
* Add `LenientOidcDiscoveryMetadataPolicy` for identity providers that omit `code_challenge_methods_supported` (e.g. FusionAuth, Microsoft Entra ID)
|
||||
* Add OAuth 2.0 Dynamic Client Registration middleware (RFC 7591)
|
||||
* Add optional `title` field to `Prompt` and `McpPrompt` for MCP spec compliance
|
||||
* [BC Break] `Builder::addPrompt()` signature changed — `$title` parameter added between `$name` and `$description`. Callers using positional arguments for `$description` must switch to named arguments.
|
||||
|
||||
0.4.0
|
||||
-----
|
||||
|
|
|
|||
|
|
@ -352,6 +352,7 @@ $server = Server::builder()
|
|||
|
||||
- `handler` (callable|string): The prompt handler
|
||||
- `name` (string|null): Optional prompt name
|
||||
- `title` (string|null): Optional human-readable title for display in UI
|
||||
- `description` (string|null): Optional prompt description
|
||||
- `icons` (Icon[]|null): Optional array of icons for the prompt
|
||||
|
||||
|
|
|
|||
|
|
@ -24,12 +24,14 @@ class McpPrompt
|
|||
{
|
||||
/**
|
||||
* @param ?string $name overrides the prompt name (defaults to method name)
|
||||
* @param ?string $title Optional human-readable title for display in UI
|
||||
* @param ?string $description Optional description of the prompt. Defaults to method DocBlock summary.
|
||||
* @param ?Icon[] $icons Optional list of icon URLs representing the prompt
|
||||
* @param ?array<string, mixed> $meta Optional metadata
|
||||
*/
|
||||
public function __construct(
|
||||
public ?string $name = null,
|
||||
public ?string $title = null,
|
||||
public ?string $description = null,
|
||||
public ?array $icons = null,
|
||||
public ?array $meta = null,
|
||||
|
|
|
|||
|
|
@ -276,7 +276,7 @@ final class Discoverer implements DiscovererInterface
|
|||
$paramTag = $paramTags['$'.$param->getName()] ?? null;
|
||||
$arguments[] = new PromptArgument($param->getName(), $paramTag ? trim((string) $paramTag->getDescription()) : null, !$param->isOptional() && !$param->isDefaultValueAvailable());
|
||||
}
|
||||
$prompt = new Prompt($name, $description, $arguments, $instance->icons, $instance->meta);
|
||||
$prompt = new Prompt($name, $instance->title, $description, $arguments, $instance->icons, $instance->meta);
|
||||
$completionProviders = $this->getCompletionProviders($method);
|
||||
$prompts[$name] = new PromptReference($prompt, [$className, $methodName], false, $completionProviders);
|
||||
++$discoveredCount['prompts'];
|
||||
|
|
|
|||
|
|
@ -252,6 +252,7 @@ final class ArrayLoader implements LoaderInterface
|
|||
}
|
||||
$prompt = new Prompt(
|
||||
name: $name,
|
||||
title: $data['title'] ?? null,
|
||||
description: $description,
|
||||
arguments: $arguments,
|
||||
icons: $data['icons'] ?? null,
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ use Mcp\Exception\InvalidArgumentException;
|
|||
*
|
||||
* @phpstan-type PromptData array{
|
||||
* name: string,
|
||||
* title?: string,
|
||||
* description?: string,
|
||||
* arguments?: PromptArgumentData[],
|
||||
* icons?: IconData[],
|
||||
|
|
@ -33,6 +34,7 @@ class Prompt implements \JsonSerializable
|
|||
{
|
||||
/**
|
||||
* @param string $name the name of the prompt or prompt template
|
||||
* @param ?string $title Optional human-readable title for display in UI
|
||||
* @param ?string $description an optional description of what this prompt provides
|
||||
* @param ?PromptArgument[] $arguments A list of arguments for templating. Null if not a template.
|
||||
* @param ?Icon[] $icons optional icons representing the prompt
|
||||
|
|
@ -40,6 +42,7 @@ class Prompt implements \JsonSerializable
|
|||
*/
|
||||
public function __construct(
|
||||
public readonly string $name,
|
||||
public readonly ?string $title = null,
|
||||
public readonly ?string $description = null,
|
||||
public readonly ?array $arguments = null,
|
||||
public readonly ?array $icons = null,
|
||||
|
|
@ -73,6 +76,7 @@ class Prompt implements \JsonSerializable
|
|||
|
||||
return new self(
|
||||
name: $data['name'],
|
||||
title: $data['title'] ?? null,
|
||||
description: $data['description'] ?? null,
|
||||
arguments: $arguments,
|
||||
icons: isset($data['icons']) && \is_array($data['icons']) ? array_map(Icon::fromArray(...), $data['icons']) : null,
|
||||
|
|
@ -83,6 +87,7 @@ class Prompt implements \JsonSerializable
|
|||
/**
|
||||
* @return array{
|
||||
* name: string,
|
||||
* title?: string,
|
||||
* description?: string,
|
||||
* arguments?: array<PromptArgument>,
|
||||
* icons?: Icon[],
|
||||
|
|
@ -92,6 +97,9 @@ class Prompt implements \JsonSerializable
|
|||
public function jsonSerialize(): array
|
||||
{
|
||||
$data = ['name' => $this->name];
|
||||
if (null !== $this->title) {
|
||||
$data['title'] = $this->title;
|
||||
}
|
||||
if (null !== $this->description) {
|
||||
$data['description'] = $this->description;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -473,11 +473,12 @@ final class Builder
|
|||
public function addPrompt(
|
||||
\Closure|array|string $handler,
|
||||
?string $name = null,
|
||||
?string $title = null,
|
||||
?string $description = null,
|
||||
?array $icons = null,
|
||||
?array $meta = null,
|
||||
): self {
|
||||
$this->prompts[] = compact('handler', 'name', 'description', 'icons', 'meta');
|
||||
$this->prompts[] = compact('handler', 'name', 'title', 'description', 'icons', 'meta');
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -58,10 +58,10 @@ $server = Server::builder()
|
|||
->addResourceTemplate([Elements::class, 'resourceTemplate'], 'test://template/{id}/data', 'template', 'A resource template with parameter substitution', 'application/json')
|
||||
->addResource(static fn () => 'Watched resource content', 'test://watched-resource', 'watched-resource', 'A resource that can be watched')
|
||||
// Prompts
|
||||
->addPrompt(static fn () => [['role' => 'user', 'content' => 'This is a simple prompt for testing.']], 'test_simple_prompt', 'A simple prompt without arguments')
|
||||
->addPrompt([Elements::class, 'promptWithArguments'], 'test_prompt_with_arguments', 'A prompt with required arguments')
|
||||
->addPrompt([Elements::class, 'promptWithEmbeddedResource'], 'test_prompt_with_embedded_resource', 'A prompt that includes an embedded resource')
|
||||
->addPrompt([Elements::class, 'promptWithImage'], 'test_prompt_with_image', 'A prompt that includes image content')
|
||||
->addPrompt(static fn () => [['role' => 'user', 'content' => 'This is a simple prompt for testing.']], name: 'test_simple_prompt', description: 'A simple prompt without arguments')
|
||||
->addPrompt([Elements::class, 'promptWithArguments'], name: 'test_prompt_with_arguments', description: 'A prompt with required arguments')
|
||||
->addPrompt([Elements::class, 'promptWithEmbeddedResource'], name: 'test_prompt_with_embedded_resource', description: 'A prompt that includes an embedded resource')
|
||||
->addPrompt([Elements::class, 'promptWithImage'], name: 'test_prompt_with_image', description: 'A prompt that includes image content')
|
||||
->build();
|
||||
|
||||
$response = $server->run($transport);
|
||||
|
|
|
|||
Loading…
Reference in a new issue