Summary
Enum constraints on MCP tool parameters never reach the model. getSchemeParameter() resolves a property by matching on type alone, so a standard JSON Schema enum is turned into a plain StringSchema and the allowed values are dropped.
src/Relay.php
$type = data_get($property, 'type');
return match ($type) {
'string' => new StringSchema($name, $description), // enum is dropped here
'enum' => new EnumSchema($name, $description, data_get($property, 'options', [])),
...
};
MCP servers emit standard JSON Schema, where an enum is a string carrying an enum keyword:
{ "type": "string", "enum": ["open", "closed"] }
The 'enum' branch expects {"type": "enum", "options": [...]}, which is not JSON Schema and which no MCP server can produce, so that branch is effectively unreachable. Enums nested inside array items are lost the same way.
Worth noting Prism's own EnumSchema::toArray() serialises to {"description": ..., "enum": [...], "type": "string"}, so Relay cannot even read back what Prism itself writes.
Steps to reproduce
Expose an MCP tool with an enum parameter, for example with laravel/mcp:
public function schema(JsonSchema $schema): array
{
return [
'status' => $schema->string()
->enum(['open', 'closed'])
->description('The status to filter on'),
];
}
Then resolve it:
$tool = Relay::tools('my-server')[0];
$tool->parameters()['status'];
// Prism\Prism\Schema\StringSchema <- expected EnumSchema, options are gone
Impact
The model receives a free text parameter with no allowed values, so it invents them. In our case a directory search tool was called with categories that do not exist, the filters were silently ignored, and the answer was built from an unfiltered result set.
The only workaround is to repeat every allowed value in the parameter description, which Relay does preserve. That means each MCP server has to duplicate its own enums into prose and pay the extra tokens on every request.
Possible fix
Resolve the enum keyword before the type based matching. Happy to send a PR, it is a small change.
Summary
Enum constraints on MCP tool parameters never reach the model.
getSchemeParameter()resolves a property by matching ontypealone, so a standard JSON Schema enum is turned into a plainStringSchemaand the allowed values are dropped.src/Relay.phpMCP servers emit standard JSON Schema, where an enum is a
stringcarrying anenumkeyword:{ "type": "string", "enum": ["open", "closed"] }The
'enum'branch expects{"type": "enum", "options": [...]}, which is not JSON Schema and which no MCP server can produce, so that branch is effectively unreachable. Enums nested inside arrayitemsare lost the same way.Worth noting Prism's own
EnumSchema::toArray()serialises to{"description": ..., "enum": [...], "type": "string"}, so Relay cannot even read back what Prism itself writes.Steps to reproduce
Expose an MCP tool with an enum parameter, for example with
laravel/mcp:Then resolve it:
Impact
The model receives a free text parameter with no allowed values, so it invents them. In our case a directory search tool was called with categories that do not exist, the filters were silently ignored, and the answer was built from an unfiltered result set.
The only workaround is to repeat every allowed value in the parameter
description, which Relay does preserve. That means each MCP server has to duplicate its own enums into prose and pay the extra tokens on every request.Possible fix
Resolve the
enumkeyword before the type based matching. Happy to send a PR, it is a small change.