Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion PRD.md
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,8 @@ Note: Include metadata field for infos like http status code, CLI exit code, etc
Basic templating should be enabled in parts where we have templating such as execution part, headers and etc.
Loops and Control blocks should be applied in large text parts, like text execution and while parsing a file in file execution flow.

- **Basic**: replaces placeholders like `{{props.propertyName}}` and `{{env.VAR_NAME}}` with values.
- **Basic**: replaces placeholders like `{{props.propertyName}}` and `{{env.VAR_NAME}}` with values (always as strings).
- **JSON-Native**: resolves placeholders like `{!!props.propertyName!!}` and `{!!env.VAR_NAME!!}` to native JSON types (boolean, number, array, object, null). Must be the sole value in a field.
- **Loops**: For array and object props or env variables, Adapter should be able to parse `@for` -> `@endfor` and `@foreach` -> `@endforeach`
- **Control Blocks**: Adapter should be able to use control blocks: `@if` -> `@elseif` -> `@else` -> `@endif`

Expand Down
235 changes: 229 additions & 6 deletions docs/schema_reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -660,19 +660,36 @@ This behavior prevents template substitution errors for optional properties that
"default": 100
},
"file_extensions": {
"type": "string",
"description": "Optional comma-separated list of file extensions"
"type": "array",
"description": "Optional list of file extensions",
"items": {
"type": "string"
}
}
},
"required": ["pattern", "directory"]
},
"execution": {
"type": "text",
"text": "Searching '{{props.pattern}}' in {{props.directory}} (images: {{props.include_images}}, max: {{props.max_results}})"
"type": "http",
"method": "POST",
"url": "https://api.example.com/search",
"body": {
"type": "json",
"content": {
"pattern": "{{props.pattern}}",
"directory": "{{props.directory}}",
"include_images": "{!!props.include_images!!}",
"case_sensitive": "{!!props.case_sensitive!!}",
"max_results": "{!!props.max_results!!}",
"file_extensions": "{!!props.file_extensions!!}"
}
}
}
}
```

**Note**: Use `{!! ... !!}` syntax for non-string types (boolean, number, array, object) to preserve their native JSON types. See [JSON-Native Placeholders](#json-native-placeholders) for more details.

**Execution with minimal properties:**
```python
# Only required properties provided
Expand All @@ -690,10 +707,12 @@ client.execute("search_files", properties={
client.execute("search_files", properties={
"pattern": "FIXME",
"directory": "/tmp",
"include_images": true,
"max_results": 50
"include_images": True,
"max_results": 50,
"file_extensions": [".py", ".js"]
})
# Result: include_images=true, max_results=50 (overridden), case_sensitive=true (default)
# file_extensions=[".py", ".js"] (provided)
```

**Property Resolution Rules:**
Expand Down Expand Up @@ -1175,6 +1194,210 @@ Replace placeholders with values from the context.

---

### JSON-Native Placeholders

Resolve placeholders to their native JSON types (boolean, number, array, object, null) instead of strings.

**Syntax**: `{!!path.to.value!!}`

**Important**: JSON-native placeholders must be the **only** content in a field. They cannot be mixed with other text.

#### Supported Types

- **Boolean**: `true` or `false` (not `"true"` or `"false"`)
- **Number**: Integer or float (not string representation)
- **Array**: Native JSON array (not stringified)
- **Object**: Native JSON object (not stringified)
- **Null**: `null` value (not `"null"` string)

#### Examples

**Boolean Properties**:

```json
{
"execution": {
"type": "http",
"body": {
"type": "json",
"content": {
"include_images": "{!!props.include_images!!}",
"case_sensitive": "{!!props.case_sensitive!!}"
}
}
}
}
```

When executed with properties `{"include_images": true, "case_sensitive": false}`, the JSON body will be:

```json
{
"include_images": true,
"case_sensitive": false
}
```

**Array Properties**:

```json
{
"execution": {
"type": "http",
"body": {
"type": "json",
"content": {
"urls": "{!!props.urls!!}",
"tags": "{!!props.tags!!}"
}
}
}
}
```

When executed with properties `{"urls": ["https://a.com", "https://b.com"], "tags": ["urgent", "review"]}`, the JSON body will be:

```json
{
"urls": ["https://a.com", "https://b.com"],
"tags": ["urgent", "review"]
}
```

**Object Properties**:

```json
{
"execution": {
"type": "http",
"body": {
"type": "json",
"content": {
"config": "{!!props.config!!}",
"metadata": "{!!props.metadata!!}"
}
}
}
}
```

When executed with properties `{"config": {"debug": false, "retries": 3}, "metadata": {"version": "1.0"}}`, the JSON body will be:

```json
{
"config": {
"debug": false,
"retries": 3
},
"metadata": {
"version": "1.0"
}
}
```

**Number Properties**:

```json
{
"execution": {
"type": "http",
"body": {
"type": "json",
"content": {
"max_results": "{!!props.max_results!!}",
"quality": "{!!props.quality!!}"
}
}
}
}
```

When executed with properties `{"max_results": 100, "quality": 0.95}`, the JSON body will be:

```json
{
"max_results": 100,
"quality": 0.95
}
```

**Mixed Native and String Placeholders**:

```json
{
"execution": {
"type": "http",
"body": {
"type": "json",
"content": {
"enabled": "{!!props.enabled!!}",
"count": "{!!props.count!!}",
"name": "{{props.name}}",
"description": "Search for {{props.query}}"
}
}
}
}
```

When executed with properties `{"enabled": true, "count": 50, "name": "My Search", "query": "testing"}`, the JSON body will be:

```json
{
"enabled": true,
"count": 50,
"name": "My Search",
"description": "Search for testing"
}
```

#### Limitations and Error Cases

**✅ Valid Usage**:

```json
{
"enabled": "{!!props.enabled!!}",
"items": "{!!props.items!!}",
"config": "{!!env.CONFIG!!}"
}
```

**❌ Invalid Usage** (will raise errors):

```json
{
"message": "Status: {!!props.enabled!!}",
"url": "https://api.com/{!!props.path!!}",
"combined": "{!!props.value!!} and more text"
}
```

**Error Messages**:

- **Mixed Content**: "Invalid JSON-native placeholder format: 'text {!!props.value!!}'. Must be exactly {!!path!!} with no surrounding content."
- **Missing Property**: "Failed to resolve JSON-native placeholder '{!!props.missing!!}': Path 'props.missing' not found in context"
- **Invalid Syntax**: "Invalid JSON-native placeholder format: '{{props.value}}'. Must be exactly {!!path!!} with no surrounding content."

#### When to Use JSON-Native vs String Placeholders

**Use `{!! ... !!}` when**:

- Property must be a native boolean (`true`/`false`) in JSON
- Property must be a native number (integer or float) in JSON
- Property is an array that should remain an array in JSON
- Property is an object that should remain an object in JSON
- You need to preserve the exact type from input schema

**Use `{{ ... }}` when**:

- Building strings with multiple placeholders
- Concatenating values: `"User {{props.name}} has ID {{props.id}}"`
- Property should always be a string in the output
- Using in URLs, headers, or other string-only contexts

---

### For Loops

Iterate a fixed number of times using a range.
Expand Down
22 changes: 20 additions & 2 deletions src/mcipy/executors/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,13 +153,22 @@ def _apply_basic_templating_to_dict(
"""
Apply basic templating to all string values in a dictionary.

Supports both standard {{...}} placeholders (resolved to strings) and
JSON-native {!!...!!} placeholders (resolved to native types).

Args:
data: Dictionary to process (modified in-place)
context: Context dictionary for template resolution
"""
for key, value in data.items():
if isinstance(value, str):
data[key] = self.template_engine.render_basic(value, context)
# Check if this is a JSON-native placeholder
if self.template_engine.is_json_native_placeholder(value):
# Resolve to native type
data[key] = self.template_engine.resolve_json_native(value, context)
else:
# Standard string templating
data[key] = self.template_engine.render_basic(value, context)
elif isinstance(value, dict):
self._apply_basic_templating_to_dict(value, context)
elif isinstance(value, list):
Expand All @@ -169,13 +178,22 @@ def _apply_basic_templating_to_list(self, data: list[Any], context: dict[str, An
"""
Apply basic templating to all string values in a list.

Supports both standard {{...}} placeholders (resolved to strings) and
JSON-native {!!...!!} placeholders (resolved to native types).

Args:
data: List to process (modified in-place)
context: Context dictionary for template resolution
"""
for i, value in enumerate(data):
if isinstance(value, str):
data[i] = self.template_engine.render_basic(value, context)
# Check if this is a JSON-native placeholder
if self.template_engine.is_json_native_placeholder(value):
# Resolve to native type
data[i] = self.template_engine.resolve_json_native(value, context)
else:
# Standard string templating
data[i] = self.template_engine.render_basic(value, context)
elif isinstance(value, dict):
self._apply_basic_templating_to_dict(value, context)
elif isinstance(value, list):
Expand Down
Loading