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
|
* [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 `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 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
|
0.4.0
|
||||||
-----
|
-----
|
||||||
|
|
|
||||||
|
|
@ -352,6 +352,7 @@ $server = Server::builder()
|
||||||
|
|
||||||
- `handler` (callable|string): The prompt handler
|
- `handler` (callable|string): The prompt handler
|
||||||
- `name` (string|null): Optional prompt name
|
- `name` (string|null): Optional prompt name
|
||||||
|
- `title` (string|null): Optional human-readable title for display in UI
|
||||||
- `description` (string|null): Optional prompt description
|
- `description` (string|null): Optional prompt description
|
||||||
- `icons` (Icon[]|null): Optional array of icons for the prompt
|
- `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 $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 ?string $description Optional description of the prompt. Defaults to method DocBlock summary.
|
||||||
* @param ?Icon[] $icons Optional list of icon URLs representing the prompt
|
* @param ?Icon[] $icons Optional list of icon URLs representing the prompt
|
||||||
* @param ?array<string, mixed> $meta Optional metadata
|
* @param ?array<string, mixed> $meta Optional metadata
|
||||||
*/
|
*/
|
||||||
public function __construct(
|
public function __construct(
|
||||||
public ?string $name = null,
|
public ?string $name = null,
|
||||||
|
public ?string $title = null,
|
||||||
public ?string $description = null,
|
public ?string $description = null,
|
||||||
public ?array $icons = null,
|
public ?array $icons = null,
|
||||||
public ?array $meta = null,
|
public ?array $meta = null,
|
||||||
|
|
|
||||||
|
|
@ -276,7 +276,7 @@ final class Discoverer implements DiscovererInterface
|
||||||
$paramTag = $paramTags['$'.$param->getName()] ?? null;
|
$paramTag = $paramTags['$'.$param->getName()] ?? null;
|
||||||
$arguments[] = new PromptArgument($param->getName(), $paramTag ? trim((string) $paramTag->getDescription()) : null, !$param->isOptional() && !$param->isDefaultValueAvailable());
|
$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);
|
$completionProviders = $this->getCompletionProviders($method);
|
||||||
$prompts[$name] = new PromptReference($prompt, [$className, $methodName], false, $completionProviders);
|
$prompts[$name] = new PromptReference($prompt, [$className, $methodName], false, $completionProviders);
|
||||||
++$discoveredCount['prompts'];
|
++$discoveredCount['prompts'];
|
||||||
|
|
|
||||||
|
|
@ -252,6 +252,7 @@ final class ArrayLoader implements LoaderInterface
|
||||||
}
|
}
|
||||||
$prompt = new Prompt(
|
$prompt = new Prompt(
|
||||||
name: $name,
|
name: $name,
|
||||||
|
title: $data['title'] ?? null,
|
||||||
description: $description,
|
description: $description,
|
||||||
arguments: $arguments,
|
arguments: $arguments,
|
||||||
icons: $data['icons'] ?? null,
|
icons: $data['icons'] ?? null,
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ use Mcp\Exception\InvalidArgumentException;
|
||||||
*
|
*
|
||||||
* @phpstan-type PromptData array{
|
* @phpstan-type PromptData array{
|
||||||
* name: string,
|
* name: string,
|
||||||
|
* title?: string,
|
||||||
* description?: string,
|
* description?: string,
|
||||||
* arguments?: PromptArgumentData[],
|
* arguments?: PromptArgumentData[],
|
||||||
* icons?: IconData[],
|
* icons?: IconData[],
|
||||||
|
|
@ -33,6 +34,7 @@ class Prompt implements \JsonSerializable
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @param string $name the name of the prompt or prompt template
|
* @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 ?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 ?PromptArgument[] $arguments A list of arguments for templating. Null if not a template.
|
||||||
* @param ?Icon[] $icons optional icons representing the prompt
|
* @param ?Icon[] $icons optional icons representing the prompt
|
||||||
|
|
@ -40,6 +42,7 @@ class Prompt implements \JsonSerializable
|
||||||
*/
|
*/
|
||||||
public function __construct(
|
public function __construct(
|
||||||
public readonly string $name,
|
public readonly string $name,
|
||||||
|
public readonly ?string $title = null,
|
||||||
public readonly ?string $description = null,
|
public readonly ?string $description = null,
|
||||||
public readonly ?array $arguments = null,
|
public readonly ?array $arguments = null,
|
||||||
public readonly ?array $icons = null,
|
public readonly ?array $icons = null,
|
||||||
|
|
@ -73,6 +76,7 @@ class Prompt implements \JsonSerializable
|
||||||
|
|
||||||
return new self(
|
return new self(
|
||||||
name: $data['name'],
|
name: $data['name'],
|
||||||
|
title: $data['title'] ?? null,
|
||||||
description: $data['description'] ?? null,
|
description: $data['description'] ?? null,
|
||||||
arguments: $arguments,
|
arguments: $arguments,
|
||||||
icons: isset($data['icons']) && \is_array($data['icons']) ? array_map(Icon::fromArray(...), $data['icons']) : null,
|
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{
|
* @return array{
|
||||||
* name: string,
|
* name: string,
|
||||||
|
* title?: string,
|
||||||
* description?: string,
|
* description?: string,
|
||||||
* arguments?: array<PromptArgument>,
|
* arguments?: array<PromptArgument>,
|
||||||
* icons?: Icon[],
|
* icons?: Icon[],
|
||||||
|
|
@ -92,6 +97,9 @@ class Prompt implements \JsonSerializable
|
||||||
public function jsonSerialize(): array
|
public function jsonSerialize(): array
|
||||||
{
|
{
|
||||||
$data = ['name' => $this->name];
|
$data = ['name' => $this->name];
|
||||||
|
if (null !== $this->title) {
|
||||||
|
$data['title'] = $this->title;
|
||||||
|
}
|
||||||
if (null !== $this->description) {
|
if (null !== $this->description) {
|
||||||
$data['description'] = $this->description;
|
$data['description'] = $this->description;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -473,11 +473,12 @@ final class Builder
|
||||||
public function addPrompt(
|
public function addPrompt(
|
||||||
\Closure|array|string $handler,
|
\Closure|array|string $handler,
|
||||||
?string $name = null,
|
?string $name = null,
|
||||||
|
?string $title = null,
|
||||||
?string $description = null,
|
?string $description = null,
|
||||||
?array $icons = null,
|
?array $icons = null,
|
||||||
?array $meta = null,
|
?array $meta = null,
|
||||||
): self {
|
): self {
|
||||||
$this->prompts[] = compact('handler', 'name', 'description', 'icons', 'meta');
|
$this->prompts[] = compact('handler', 'name', 'title', 'description', 'icons', 'meta');
|
||||||
|
|
||||||
return $this;
|
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')
|
->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')
|
->addResource(static fn () => 'Watched resource content', 'test://watched-resource', 'watched-resource', 'A resource that can be watched')
|
||||||
// Prompts
|
// Prompts
|
||||||
->addPrompt(static fn () => [['role' => 'user', 'content' => 'This is a simple prompt for testing.']], 'test_simple_prompt', 'A simple prompt without arguments')
|
->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'], 'test_prompt_with_arguments', 'A prompt with required arguments')
|
->addPrompt([Elements::class, 'promptWithArguments'], name: 'test_prompt_with_arguments', description: 'A prompt with required arguments')
|
||||||
->addPrompt([Elements::class, 'promptWithEmbeddedResource'], 'test_prompt_with_embedded_resource', 'A prompt that includes an embedded resource')
|
->addPrompt([Elements::class, 'promptWithEmbeddedResource'], name: 'test_prompt_with_embedded_resource', description: 'A prompt that includes an embedded resource')
|
||||||
->addPrompt([Elements::class, 'promptWithImage'], 'test_prompt_with_image', 'A prompt that includes image content')
|
->addPrompt([Elements::class, 'promptWithImage'], name: 'test_prompt_with_image', description: 'A prompt that includes image content')
|
||||||
->build();
|
->build();
|
||||||
|
|
||||||
$response = $server->run($transport);
|
$response = $server->run($transport);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue