Skip to content
This repository was archived by the owner on Nov 12, 2025. It is now read-only.

Commit 466b8c5

Browse files
Merge pull request #9 from EdouardCourty/feat/add-unsafe-prompt-argument
feat(prompt): support unsafe arguments
2 parents b9cfd42 + 45c679d commit 466b8c5

7 files changed

Lines changed: 78 additions & 13 deletions

File tree

src/MethodHandler/PromptsGetMethodHandler.php

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
use Ecourty\McpServerBundle\Service\InputSanitizer;
1818
use Ecourty\McpServerBundle\Service\PromptRegistry;
1919
use Psr\EventDispatcher\EventDispatcherInterface;
20-
use Symfony\Component\Console\Attribute\Argument;
2120
use Symfony\Component\HttpFoundation\Response;
2221

2322
/**
@@ -61,21 +60,20 @@ public function handle(JsonRpcRequest $request): array
6160

6261
try {
6362
$arguments = $request->params['arguments'] ?? [];
64-
$sanitizedArguments = $this->inputSanitizer->sanitize($arguments);
6563

66-
$this->validatePromptArguments($sanitizedArguments, $promptDefinition);
67-
$argumentCollection = ArgumentCollection::fromArray($sanitizedArguments);
64+
$this->validatePromptArguments($arguments, $promptDefinition);
65+
$sanitizedArguments = $this->getSanitizedArguments($arguments, $promptDefinition);
6866

6967
if (method_exists($prompt, '__invoke') === false) {
7068
throw new \LogicException(\sprintf('Prompt "%s" does not implement the __invoke method.', $promptName));
7169
}
7270

7371
$this->eventDispatcher?->dispatch(new PromptGetEvent(
7472
promptName: $promptName,
75-
arguments: $argumentCollection,
73+
arguments: $sanitizedArguments,
7674
));
7775

78-
$promptResult = $prompt->__invoke($argumentCollection);
76+
$promptResult = $prompt->__invoke($sanitizedArguments);
7977

8078
if ($promptResult instanceof PromptResult === false) {
8179
throw new \LogicException(\sprintf(
@@ -85,11 +83,11 @@ public function handle(JsonRpcRequest $request): array
8583
));
8684
}
8785

88-
$this->eventDispatcher?->dispatch(new PromptResultEvent($promptName, $argumentCollection, $promptResult));
86+
$this->eventDispatcher?->dispatch(new PromptResultEvent($promptName, $sanitizedArguments, $promptResult));
8987
} catch (\Throwable $exception) {
9088
$this->eventDispatcher?->dispatch(new PromptExceptionEvent(
9189
promptName: $promptName,
92-
arguments: $argumentCollection ?? null,
90+
arguments: $sanitizedArguments ?? null,
9391
exception: $exception,
9492
));
9593

@@ -103,6 +101,33 @@ public function handle(JsonRpcRequest $request): array
103101
return $promptResult->toArray();
104102
}
105103

104+
/**
105+
* @param array<string, mixed> $inputArgument
106+
*/
107+
private function getSanitizedArguments(array $inputArgument, PromptDefinition $promptDefinition): ArgumentCollection
108+
{
109+
$sanitizedArguments = [];
110+
$promptArguments = $promptDefinition->arguments ?? [];
111+
112+
foreach ($promptArguments as $argument) {
113+
$argumentName = $argument->name;
114+
$unsafeArgumentValue = $inputArgument[$argumentName] ?? null;
115+
116+
if ($argument->allowUnsafe === true) {
117+
$sanitizedArguments[$argumentName] = $unsafeArgumentValue;
118+
119+
continue;
120+
}
121+
122+
$sanitizedArguments[$argumentName] = $this->inputSanitizer->sanitize($unsafeArgumentValue);
123+
}
124+
125+
return ArgumentCollection::fromArray($sanitizedArguments);
126+
}
127+
128+
/**
129+
* @param array<string, mixed> $arguments
130+
*/
106131
private function validatePromptArguments(array $arguments, PromptDefinition $promptDefinition): void
107132
{
108133
$promptArguments = $promptDefinition->arguments ?? [];

src/Prompt/Argument.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,22 +9,28 @@
99
*/
1010
class Argument
1111
{
12+
/**
13+
* @param bool $required Indicates if the argument is required (will throw an error if true and not provided).
14+
* @param bool $allowUnsafe Indicates if the argument allows unsafe content (will not be sanitized if true).
15+
*/
1216
public function __construct(
1317
public readonly string $name,
1418
public readonly string $description,
1519
public readonly bool $required = true,
20+
public readonly bool $allowUnsafe = false,
1621
) {
1722
}
1823

1924
/**
20-
* @return array{name: string, description: string, required: bool}
25+
* @return array{name: string, description: string, required: bool, allowUnsafe: bool}
2126
*/
2227
public function toArray(): array
2328
{
2429
return [
2530
'name' => $this->name,
2631
'description' => $this->description,
2732
'required' => $this->required,
33+
'allowUnsafe' => $this->allowUnsafe,
2834
];
2935
}
3036
}

src/Service/PromptRegistry.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ public function getPromptsDefinitions(): array
5353
/**
5454
* @internal
5555
*
56-
* @param array<array{name: string, description: string, required: bool}> $argumentDefinitions
56+
* @param array<array{name: string, description: string, required: bool, allowUnsafe: bool}> $argumentDefinitions
5757
*/
5858
public function addPromptDefinition(
5959
string $name,
@@ -70,6 +70,7 @@ public function addPromptDefinition(
7070
name: $argumentDefinition['name'],
7171
description: $argumentDefinition['description'],
7272
required: $argumentDefinition['required'],
73+
allowUnsafe: $argumentDefinition['allowUnsafe'],
7374
);
7475
}
7576

tests/Controller/EntrypointControllerTest.php

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -454,4 +454,37 @@ public function testPromptGetWithNonRequiredParameterLeftEmpty(): void
454454
$this->assertArrayHasKey('result', $responseContent);
455455
$this->assertArrayNotHasKey('error', $responseContent);
456456
}
457+
458+
public function testPromptGetWithUnsafeParameterAllowed(): void
459+
{
460+
$unsafeContent = '<script>alert("This is unsafe!");</script>';
461+
462+
$response = $this->request(
463+
method: Request::METHOD_POST,
464+
url: '/mcp',
465+
body: [
466+
'jsonrpc' => '2.0',
467+
'id' => 1,
468+
'method' => 'prompts/get',
469+
'params' => [
470+
'name' => 'generate-git-commit-message',
471+
'arguments' => [
472+
'changes' => $unsafeContent,
473+
'scope' => 'feature',
474+
],
475+
],
476+
],
477+
);
478+
479+
$responseContent = json_decode((string) $response->getContent(), true);
480+
$this->assertNotFalse($responseContent);
481+
482+
$this->assertSame('2.0', $responseContent['jsonrpc']);
483+
$this->assertSame(1, $responseContent['id']);
484+
$this->assertArrayHasKey('result', $responseContent);
485+
486+
// Check if the unsafe content is present in the response
487+
$resultContent = $responseContent['result'];
488+
$this->assertStringContainsString($unsafeContent, $resultContent['messages'][0]['content']['text']);
489+
}
457490
}

tests/Service/PromptRegistryTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ public static function providePromptDefinitionTestData(): array
6262
'name' => 'generate-git-commit-message',
6363
'expectedDescription' => 'Generate a git commit message based on the provided changes.',
6464
'expectedArguments' => [
65-
new Argument('changes', 'The changed made in the codebase', true),
65+
new Argument('changes', 'The changed made in the codebase', true, true),
6666
new Argument('scope', 'The scope of the changes, e.g., feature, bugfix, etc.', true),
6767
],
6868
],

tests/TestApp/src/Prompt/GenerateGitCommitMessage.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
name: 'generate-git-commit-message',
1717
description: 'Generate a git commit message based on the provided changes.',
1818
arguments: [
19-
new Argument(name: 'changes', description: 'The changed made in the codebase'),
19+
new Argument(name: 'changes', description: 'The changed made in the codebase', allowUnsafe: true),
2020
new Argument(name: 'scope', description: 'The scope of the changes, e.g., feature, bugfix, etc.'),
2121
],
2222
)]

tests/TestApp/src/Prompt/GreetingPrompt.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
#[AsPrompt(
1616
name: 'greeting',
1717
arguments: [
18-
new Argument('name', description: 'The name of the person to greet.', required: false),
18+
new Argument('name', description: 'The name of the person to greet.', required: false, allowUnsafe: true),
1919
],
2020
)]
2121
class GreetingPrompt

0 commit comments

Comments
 (0)