Skip to content

Commit 3c16cd7

Browse files
committed
fix: Correct MCP inputSchema generation and bump version to 0.3.0
1 parent ebdd5c2 commit 3c16cd7

4 files changed

Lines changed: 61 additions & 19 deletions

File tree

click_mcp/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,5 @@
44

55
from .decorator import click_mcp
66

7-
__version__ = "0.2.0"
7+
__version__ = "0.3.0"
88
__all__ = ["click_mcp", "__version__"]

click_mcp/scanner.py

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -60,18 +60,30 @@ def _convert_command_info_to_tool(
6060
"""Convert a Click command info dict to an MCP tool."""
6161
description = command_info.get("help") or command_info.get("short_help") or ""
6262

63-
parameters: Dict[str, Dict[str, Any]] = {}
63+
properties: Dict[str, Dict[str, Any]] = {}
64+
required_params: List[str] = []
65+
6466
for param_info in command_info.get("params", []):
6567
param_name = param_info.get("name")
6668
if param_name:
6769
param_data = _get_parameter_info_from_dict(param_info)
6870
if param_data:
69-
parameters[param_name] = param_data
71+
properties[param_name] = param_data
72+
if param_data.get("required", False):
73+
required_params.append(param_name)
74+
75+
# Construct the final input schema according to JSON Schema / MCP spec
76+
input_schema: Dict[str, Any] = {
77+
"type": "object",
78+
"properties": properties,
79+
}
80+
if required_params:
81+
input_schema["required"] = sorted(required_params) # Sort for consistent output
7082

7183
return types.Tool(
7284
name=name,
7385
description=description,
74-
inputSchema=parameters,
86+
inputSchema=input_schema,
7587
)
7688

7789

@@ -99,12 +111,15 @@ def _get_parameter_info_from_dict(
99111
if "choices" in param_info:
100112
schema["enum"] = param_info["choices"]
101113

102-
# Create parameter info
114+
# Create parameter info (this represents the schema for a single property)
103115
param_data = {
104116
"description": param_info.get("help", ""),
105-
"required": param_info.get("required", False),
106117
"schema": schema,
107118
}
119+
# Add 'required' flag separately for collecting at the top level
120+
is_required = param_info.get("required", False)
121+
if is_required:
122+
param_data["required"] = True # Keep track for the loop above
108123

109124
# Add default if available and not callable
110125
default = param_info.get("default")

pyproject.toml

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
44

55
[project]
66
name = "click-mcp"
7-
version = "0.2.0"
7+
version = "0.3.0"
88
description = "Extend Click applications with Model Context Protocol (MCP) support"
99
readme = "README.md"
1010
authors = [
@@ -34,9 +34,6 @@ Issues = "https://github.com/crowecawcaw/click-mcp/issues"
3434
[tool.hatch.build.targets.wheel]
3535
packages = ["click_mcp"]
3636

37-
[tool.hatch.version]
38-
path = "click_mcp/_version.py"
39-
4037
[tool.black]
4138
target-version = ['py310', 'py311', 'py312', 'py313']
4239
line-length = 88

tests/test_mcp_server.py

Lines changed: 39 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,13 @@ async def test_basic_server_tools(basic_mcp_session):
6767
break
6868

6969
assert greet_tool is not None
70-
assert "name" in greet_tool.inputSchema
71-
assert greet_tool.inputSchema["name"]["required"] is True
70+
# Verify inputSchema structure
71+
assert greet_tool.inputSchema["type"] == "object"
72+
assert "properties" in greet_tool.inputSchema
73+
assert "name" in greet_tool.inputSchema["properties"]
74+
assert greet_tool.inputSchema["properties"]["name"]["required"] is True
75+
assert "required" in greet_tool.inputSchema
76+
assert "name" in greet_tool.inputSchema["required"]
7277

7378
# Find the users.list command
7479
users_list_tool = None
@@ -78,6 +83,14 @@ async def test_basic_server_tools(basic_mcp_session):
7883
break
7984

8085
assert users_list_tool is not None
86+
# Verify inputSchema structure (should only have help param)
87+
assert users_list_tool.inputSchema["type"] == "object"
88+
assert "properties" in users_list_tool.inputSchema
89+
# Click automatically adds --help, so we expect only that property
90+
assert len(users_list_tool.inputSchema["properties"]) <= 1
91+
if len(users_list_tool.inputSchema["properties"]) == 1:
92+
assert "help" in users_list_tool.inputSchema["properties"]
93+
assert "required" not in users_list_tool.inputSchema # No required params
8194

8295
# Find the echo command
8396
echo_tool = None
@@ -87,9 +100,14 @@ async def test_basic_server_tools(basic_mcp_session):
87100
break
88101

89102
assert echo_tool is not None
90-
assert "count" in echo_tool.inputSchema
91-
assert echo_tool.inputSchema["count"]["schema"]["type"] == "integer"
92-
assert "message" in echo_tool.inputSchema
103+
# Verify inputSchema structure
104+
assert echo_tool.inputSchema["type"] == "object"
105+
assert "properties" in echo_tool.inputSchema
106+
assert "count" in echo_tool.inputSchema["properties"]
107+
assert echo_tool.inputSchema["properties"]["count"]["schema"]["type"] == "integer"
108+
assert "message" in echo_tool.inputSchema["properties"]
109+
assert "required" in echo_tool.inputSchema
110+
assert "message" in echo_tool.inputSchema["required"] # Only message is required
93111

94112

95113
@pytest.mark.anyio
@@ -234,8 +252,14 @@ async def test_advanced_server_tools(advanced_mcp_session):
234252
break
235253

236254
assert config_set_tool is not None
237-
assert "key" in config_set_tool.inputSchema
238-
assert "value" in config_set_tool.inputSchema
255+
# Verify inputSchema structure
256+
assert config_set_tool.inputSchema["type"] == "object"
257+
assert "properties" in config_set_tool.inputSchema
258+
assert "key" in config_set_tool.inputSchema["properties"]
259+
assert "value" in config_set_tool.inputSchema["properties"]
260+
assert "required" in config_set_tool.inputSchema
261+
assert "key" in config_set_tool.inputSchema["required"]
262+
assert "value" in config_set_tool.inputSchema["required"]
239263

240264
# Find the greet command with formal option
241265
greet_tool = None
@@ -245,8 +269,14 @@ async def test_advanced_server_tools(advanced_mcp_session):
245269
break
246270

247271
assert greet_tool is not None
248-
assert "formal" in greet_tool.inputSchema
249-
assert greet_tool.inputSchema["formal"]["schema"]["type"] == "boolean"
272+
# Verify inputSchema structure
273+
assert greet_tool.inputSchema["type"] == "object"
274+
assert "properties" in greet_tool.inputSchema
275+
assert "name" in greet_tool.inputSchema["properties"]
276+
assert "formal" in greet_tool.inputSchema["properties"]
277+
assert greet_tool.inputSchema["properties"]["formal"]["schema"]["type"] == "boolean"
278+
assert "required" in greet_tool.inputSchema
279+
assert "name" in greet_tool.inputSchema["required"] # Only name is required
250280

251281

252282
@pytest.fixture

0 commit comments

Comments
 (0)