Skip to content

Commit 6bfdcb8

Browse files
authored
Python: Respect optional MCP prompt arguments (#14676)
### Motivation and Context MCP prompts are loaded with every argument marked required, ignoring `PromptArgument.required` (optional; absent means not required). The function-calling schema then lists all of them as required, and invoking the prompt without an optional argument fails with "Parameter style is required but not provided in the arguments." ### Description Use `bool(prompt_argument.required)`. - New test: required/optional/absent arguments map to True/False/False, and invoking with only the required one sends `arguments={"code": ...}`; fails before, passes after. - `tests/unit/connectors/mcp`, `tests/unit/functions` and `tests/unit/kernel` pass; ruff and mypy clean. ### Contribution Checklist - [x] The code builds clean without any errors or warnings - [x] The PR follows the [SK Contribution Guidelines](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md) and the [pre-submission formatting script](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md#development-scripts) raises no violations - [x] All unit tests pass, and I have added new tests where possible - [x] I didn't break anyone 😄
1 parent 7a957a0 commit 6bfdcb8

2 files changed

Lines changed: 34 additions & 1 deletion

File tree

‎python/semantic_kernel/connectors/mcp.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,7 @@ def _get_parameter_dict_from_mcp_prompt(prompt: types.Prompt) -> list[dict[str,
195195
{
196196
"name": prompt_argument.name,
197197
"description": prompt_argument.description,
198-
"is_required": True,
198+
"is_required": bool(prompt_argument.required),
199199
"type_object": str,
200200
}
201201
for prompt_argument in prompt.arguments

‎python/tests/unit/connectors/mcp/test_mcp.py‎

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -900,3 +900,36 @@ def echo() -> str:
900900
result = await client.call_tool(expected_name, {})
901901
assert not result.isError
902902
assert result.content == [types.TextContent(type="text", text="hello")]
903+
904+
905+
async def test_mcp_prompt_optional_arguments_are_not_required():
906+
plugin = MCPStdioPlugin(name="test", command="echo", load_tools=False)
907+
plugin.session = AsyncMock(spec=ClientSession)
908+
plugin.session.list_prompts.return_value = types.ListPromptsResult(
909+
prompts=[
910+
types.Prompt(
911+
name="review",
912+
arguments=[
913+
types.PromptArgument(name="code", required=True),
914+
types.PromptArgument(name="style", required=False),
915+
types.PromptArgument(name="language"),
916+
],
917+
)
918+
]
919+
)
920+
plugin.session.get_prompt.return_value = types.GetPromptResult(
921+
messages=[types.PromptMessage(role="user", content=types.TextContent(type="text", text="review this"))]
922+
)
923+
await plugin.load_prompts()
924+
kernel = Kernel()
925+
kernel.add_plugin(plugin, "test")
926+
function = kernel.get_function("test", "review")
927+
928+
assert {p.name: p.is_required for p in function.metadata.parameters} == {
929+
"code": True,
930+
"style": False,
931+
"language": False,
932+
}
933+
result = await kernel.invoke(function, code="x = 1")
934+
assert str(result) == "review this"
935+
plugin.session.get_prompt.assert_awaited_once_with("review", arguments={"code": "x = 1"})

0 commit comments

Comments
 (0)