Skip to content

Commit 3ee5830

Browse files
authored
Merge pull request #64 from Model-Context-Interface/copilot/support-default-field-parsing
Support default values in input schema and skip optional properties without defaults
2 parents 8bd09ca + 1a17993 commit 3ee5830

File tree

6 files changed

+803
-3
lines changed

6 files changed

+803
-3
lines changed

docs/schema_reference.md

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -617,6 +617,91 @@ execution:
617617
url: "https://api.example.com/resources/{{props.id}}"
618618
```
619619

620+
### Input Schema and Default Values
621+
622+
The `inputSchema` field uses JSON Schema to define the expected properties for a tool. When executing a tool, the MCI adapter processes properties as follows:
623+
624+
1. **Required Properties**: Must be provided, or execution will fail with a validation error
625+
2. **Optional Properties with Defaults**: If not provided, the default value is used
626+
3. **Optional Properties without Defaults**: If not provided, they are skipped (not included in template context)
627+
628+
This behavior prevents template substitution errors for optional properties that aren't needed for a particular execution.
629+
630+
#### Example: Properties with Defaults
631+
632+
```json
633+
{
634+
"name": "search_files",
635+
"description": "Search for text in files",
636+
"inputSchema": {
637+
"type": "object",
638+
"properties": {
639+
"pattern": {
640+
"type": "string",
641+
"description": "Search pattern"
642+
},
643+
"directory": {
644+
"type": "string",
645+
"description": "Directory to search in"
646+
},
647+
"include_images": {
648+
"type": "boolean",
649+
"description": "Include image files in search",
650+
"default": false
651+
},
652+
"case_sensitive": {
653+
"type": "boolean",
654+
"description": "Use case-sensitive search",
655+
"default": true
656+
},
657+
"max_results": {
658+
"type": "number",
659+
"description": "Maximum number of results",
660+
"default": 100
661+
},
662+
"file_extensions": {
663+
"type": "string",
664+
"description": "Optional comma-separated list of file extensions"
665+
}
666+
},
667+
"required": ["pattern", "directory"]
668+
},
669+
"execution": {
670+
"type": "text",
671+
"text": "Searching '{{props.pattern}}' in {{props.directory}} (images: {{props.include_images}}, max: {{props.max_results}})"
672+
}
673+
}
674+
```
675+
676+
**Execution with minimal properties:**
677+
```python
678+
# Only required properties provided
679+
client.execute("search_files", properties={
680+
"pattern": "TODO",
681+
"directory": "/home/user/projects"
682+
})
683+
# Result: include_images=false, case_sensitive=true, max_results=100 (defaults used)
684+
# file_extensions is skipped (not in template, no default)
685+
```
686+
687+
**Execution with overridden defaults:**
688+
```python
689+
# Some defaults overridden
690+
client.execute("search_files", properties={
691+
"pattern": "FIXME",
692+
"directory": "/tmp",
693+
"include_images": true,
694+
"max_results": 50
695+
})
696+
# Result: include_images=true, max_results=50 (overridden), case_sensitive=true (default)
697+
```
698+
699+
**Property Resolution Rules:**
700+
- Properties provided at execution time always take precedence over defaults
701+
- Default values can be any valid JSON type: boolean, number, string, array, object, null
702+
- Optional properties without defaults are not included in the template context if not provided
703+
- This prevents `{{props.optional_prop}}` from causing errors when `optional_prop` is not provided
704+
620705
---
621706

622707
## Execution Types

src/mcipy/mcp_integration.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
fetching their tool definitions, and building MCI-compatible toolset schemas.
66
"""
77

8-
import asyncio, concurrent.futures
8+
import asyncio
9+
import concurrent.futures
910
from datetime import UTC, datetime, timedelta
1011
from typing import Any
1112

@@ -102,6 +103,7 @@ def fetch_and_build_toolset(
102103
- If a loop IS running (e.g., inside an async CLI), offload the async
103104
work to a separate thread that owns its own loop, and block until it finishes.
104105
"""
106+
105107
async def _coro():
106108
return await MCPIntegration.fetch_and_build_toolset_async(
107109
server_name, server_config, schema_version, env_context, template_engine

src/mcipy/tool_manager.py

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -227,12 +227,17 @@ def execute(
227227
# This handles three cases: None (no schema), {} (empty schema), and {...} (schema with properties)
228228
if tool.inputSchema is not None and tool.inputSchema:
229229
self._validate_input_properties(tool, properties)
230+
# Resolve properties with defaults applied and optional properties skipped
231+
resolved_properties = self._resolve_properties_with_defaults(tool, properties)
232+
else:
233+
# No schema, use properties as-is
234+
resolved_properties = properties
230235

231236
# Build context for execution
232237
context: dict[str, Any] = {
233-
"props": properties,
238+
"props": resolved_properties,
234239
"env": env_vars,
235-
"input": properties, # Alias for backward compatibility
240+
"input": resolved_properties, # Alias for backward compatibility
236241
}
237242

238243
# Build path validation context
@@ -299,3 +304,58 @@ def _validate_input_properties(self, tool: Tool, properties: dict[str, Any]) ->
299304
f"Tool '{tool.name}' requires properties: {', '.join(required)}. "
300305
f"Missing: {', '.join(missing_props)}"
301306
)
307+
308+
def _resolve_properties_with_defaults(
309+
self, tool: Tool, properties: dict[str, Any]
310+
) -> dict[str, Any]:
311+
"""
312+
Resolve properties with default values and skip optional properties.
313+
314+
For each property in the input schema:
315+
- If provided in properties: use the provided value
316+
- Else if has default value in schema: use the default
317+
- Else if required: already validated, should not happen
318+
- Else (optional without default): skip, don't include in resolved properties
319+
320+
This prevents template substitution errors for optional properties that
321+
are not provided and have no default value.
322+
323+
Args:
324+
tool: Tool object with inputSchema
325+
properties: Properties provided by the caller
326+
327+
Returns:
328+
Resolved properties dictionary with defaults applied and optional properties skipped
329+
"""
330+
input_schema = tool.inputSchema
331+
if not input_schema:
332+
return properties
333+
334+
# Get schema properties definition
335+
schema_properties = input_schema.get("properties", {})
336+
if not schema_properties:
337+
# No properties defined in schema, return as-is
338+
return properties
339+
340+
# Get required properties list
341+
required = set(input_schema.get("required", []))
342+
343+
# Build resolved properties
344+
resolved: dict[str, Any] = {}
345+
346+
# Process each property in the schema
347+
for prop_name, prop_schema in schema_properties.items():
348+
if prop_name in properties:
349+
# Property was provided, use it
350+
resolved[prop_name] = properties[prop_name]
351+
elif "default" in prop_schema:
352+
# Property not provided but has default, use default
353+
resolved[prop_name] = prop_schema["default"]
354+
elif prop_name in required:
355+
# Required property not provided - this should have been caught by validation
356+
# but we'll include it anyway to maintain consistency
357+
# (validation should have raised an error before we get here)
358+
pass
359+
# else: optional property without default - skip it
360+
361+
return resolved

0 commit comments

Comments
 (0)