Skip to content

Commit 9f538fd

Browse files
authored
feat: extend get_component_definition to include input/output schema (#31)
* feat: extend get_component_definition to include input/output schema * test: update test data with input/output schema information * test: extend test assertions to cover input/output schema * feat: enhanced output schema formatting in component definition * test: update test with full output schema example * test: update assertions to check for detailed output schema * fix: linting
1 parent 10e1dc4 commit 9f538fd

2 files changed

Lines changed: 116 additions & 2 deletions

File tree

src/deepset_mcp/tools/haystack_service.py

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ async def get_component_definition(client: AsyncClientProtocol, component_type:
3636
component_type_info = component_def["properties"]["type"]
3737
init_params = component_def["properties"].get("init_parameters", {}).get("properties", {})
3838

39-
# Format the output
39+
# Format the basic component information
4040
parts = [
4141
f"Component: {component_type}",
4242
f"Name: {component_def.get('title', 'Unknown')}",
@@ -55,6 +55,71 @@ async def get_component_definition(client: AsyncClientProtocol, component_type:
5555
default = f" (default: {param_info['default']})" if "default" in param_info else ""
5656
parts.append(f" {param_name}: {param_type}{default}\n {param_desc}")
5757

58+
# Fetch and add input/output information
59+
try:
60+
# Extract component name from the full path
61+
component_name = component_type.split(".")[-1]
62+
io_info = await haystack_service.get_component_input_output(component_name)
63+
64+
# Add Input Schema
65+
parts.append("\nInput Schema:")
66+
if "input" in io_info:
67+
input_props = io_info["input"].get("properties", {})
68+
if not input_props:
69+
parts.append(" No input parameters")
70+
else:
71+
required = io_info["input"].get("required", [])
72+
for param_name, param_info in input_props.items():
73+
req_marker = " (required)" if param_name in required else ""
74+
param_type = param_info.get("_annotation", param_info.get("type", "Unknown"))
75+
param_desc = param_info.get("description", "No description available.")
76+
default = f" (default: {param_info['default']})" if "default" in param_info else ""
77+
parts.append(f" {param_name}: {param_type}{req_marker}{default}\n {param_desc}")
78+
else:
79+
parts.append(" Input schema not available")
80+
81+
# Add Output Schema
82+
parts.append("\nOutput Schema:")
83+
if "output" in io_info and isinstance(io_info["output"], dict):
84+
output_info = io_info["output"]
85+
if "properties" in output_info:
86+
output_props = output_info.get("properties", {})
87+
if not output_props:
88+
parts.append(" No output parameters")
89+
else:
90+
required = output_info.get("required", [])
91+
for param_name, param_info in output_props.items():
92+
req_marker = " (required)" if param_name in required else ""
93+
param_type = param_info.get("_annotation", param_info.get("type", "Unknown"))
94+
param_desc = param_info.get("description", "No description available.")
95+
default = f" (default: {param_info['default']})" if "default" in param_info else ""
96+
parts.append(f" {param_name}: {param_type}{req_marker}{default}\n {param_desc}")
97+
98+
# Include any definitions if they exist
99+
if "definitions" in output_info:
100+
parts.append("\n Definitions:")
101+
for def_name, def_info in output_info["definitions"].items():
102+
parts.append(f"\n {def_name}:")
103+
if "properties" in def_info:
104+
def_required = def_info.get("required", [])
105+
for prop_name, prop_info in def_info["properties"].items():
106+
req_marker = " (required)" if prop_name in def_required else ""
107+
prop_type = prop_info.get("_annotation", prop_info.get("type", "Unknown"))
108+
prop_desc = prop_info.get("description", "No description available.")
109+
default = f" (default: {prop_info['default']})" if "default" in prop_info else ""
110+
parts.append(
111+
f" {prop_name}: {prop_type}{req_marker}{default}\n {prop_desc}"
112+
)
113+
else:
114+
# Simple output schema
115+
desc = output_info.get("description", "No description available.")
116+
output_type = output_info.get("type", "Unknown")
117+
parts.append(f" Type: {output_type}\n {desc}")
118+
else:
119+
parts.append(" Output schema not available")
120+
except Exception as e:
121+
parts.append(f"\nFailed to fetch input/output schema: {str(e)}")
122+
58123
return "\n".join(parts)
59124

60125

test/unit/tools/test_haystack_service.py

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,43 @@ async def test_get_component_definition_success() -> None:
7676
}
7777
}
7878

79-
resource = FakeHaystackServiceResource(get_component_schemas_response=response)
79+
io_response = {
80+
"input": {
81+
"properties": {
82+
"file_path": {"_annotation": "str", "description": "Path to the XLSX file", "type": "string"}
83+
},
84+
"required": ["file_path"],
85+
"type": "object",
86+
},
87+
"output": {
88+
"properties": {
89+
"documents": {
90+
"_annotation": "typing.List[haystack.dataclasses.document.Document]",
91+
"description": "List of documents",
92+
"type": "array",
93+
"items": {"$ref": "#/definitions/Document"},
94+
}
95+
},
96+
"required": ["documents"],
97+
"type": "object",
98+
"definitions": {
99+
"Document": {
100+
"type": "object",
101+
"properties": {
102+
"content": {"type": "string", "description": "The content of the document"},
103+
"meta": {"type": "object", "description": "Metadata about the document"},
104+
},
105+
"required": ["content"],
106+
}
107+
},
108+
},
109+
}
110+
111+
class FakeHaystackServiceResourceWithIO(FakeHaystackServiceResource):
112+
async def get_component_input_output(self, component_name: str) -> dict[str, Any]:
113+
return io_response
114+
115+
resource = FakeHaystackServiceResourceWithIO(get_component_schemas_response=response)
80116
client = FakeClient(resource)
81117
result = await get_component_definition(client, component_type)
82118

@@ -89,6 +125,19 @@ async def test_get_component_definition_success() -> None:
89125
assert "table_format" in result
90126
assert "default: csv" in result
91127

128+
# Check input/output schema information
129+
assert "Input Schema:" in result
130+
assert "file_path" in result
131+
assert "Path to the XLSX file" in result
132+
assert "(required)" in result
133+
assert "Output Schema:" in result
134+
assert "List of documents" in result
135+
assert "documents: typing.List[haystack.dataclasses.document.Document]" in result
136+
assert "Definitions:" in result
137+
assert "Document:" in result
138+
assert "content: string (required)" in result
139+
assert "meta: object" in result
140+
92141

93142
@pytest.mark.asyncio
94143
async def test_get_component_definition_not_found() -> None:

0 commit comments

Comments
 (0)