# MCI Python Adapter - API Reference

This document provides a comprehensive API reference for the Python MCI Adapter (`mcipy`). The API follows OpenAPI-style documentation with detailed method signatures, parameters, response formats, and examples.

## Table of Contents

- [MCIClient Class](#mciclient-class)
  - [Initialization](#initialization)
  - [Methods](#methods)
    - [tools()](#tools)
    - [only()](#only)
    - [without()](#without)
    - [tags()](#tags)
    - [withoutTags()](#withouttags)
    - [toolsets()](#toolsets)
    - [execute()](#execute)
    - [list_tools()](#list_tools)
    - [get_tool_schema()](#get_tool_schema)
- [Data Models](#data-models)
  - [MCISchema](#mcischema)
  - [Tool](#tool)
  - [ExecutionResult](#executionresult)
  - [Metadata](#metadata)
- [Execution Configurations](#execution-configurations)
  - [HTTPExecutionConfig](#httpexecutionconfig)
  - [CLIExecutionConfig](#cliexecutionconfig)
  - [FileExecutionConfig](#fileexecutionconfig)
  - [TextExecutionConfig](#textexecutionconfig)
- [Authentication Models](#authentication-models)
  - [ApiKeyAuth](#apikeyauth)
  - [BearerAuth](#bearerauth)
  - [BasicAuth](#basicauth)
  - [OAuth2Auth](#oauth2auth)
- [Error Handling](#error-handling)

---

## MCIClient Class

The `MCIClient` class is the main entry point for the MCI Python adapter. It provides methods for loading, filtering, and executing MCI tools from a JSON or YAML schema file.

### Initialization

#### `MCIClient(schema_file_path=None, env_vars=None, json_file_path=None, validating=False)`

Initialize the MCI client with a schema file and optional environment variables.

**Parameters:**

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `schema_file_path` | `str` | Conditional* | Path to the MCI schema file (`.json`, `.yaml`, or `.yml`) |
| `env_vars` | `dict[str, Any]` | No | Environment variables for template substitution (default: `{}`) |
| `json_file_path` | `str` | Conditional* | **DEPRECATED.** Use `schema_file_path` instead. Kept for backward compatibility. |
| `validating` | `bool` | No | Enable pure schema validation mode without loading MCP servers, toolsets, or resolving templates. Tool execution is disabled in this mode. (default: `False`) |

*Either `schema_file_path` or `json_file_path` must be provided.

**Raises:**

- `MCIClientError` - If the schema file cannot be loaded or parsed
- `MCIClientError` - If neither `schema_file_path` nor `json_file_path` is provided

**Example:**

```python
from mcipy import MCIClient

# Initialize with JSON file (recommended)
client = MCIClient(schema_file_path="example.mci.json")

# Initialize with YAML file
client = MCIClient(schema_file_path="example.mci.yaml")

# Initialize with environment variables
client = MCIClient(
    schema_file_path="example.mci.json",
    env_vars={
        "API_KEY": "your-secret-key",
        "USERNAME": "demo_user",
        "BEARER_TOKEN": "token-123"
    }
)

# Validating mode: validate schema without loading toolsets or MCP servers
# Useful for CI validation, IDE plugins, or schema checking
client = MCIClient(
    schema_file_path="example.mci.json",
    validating=True  # No env vars needed, no side effects
)

# Check what tools are defined (inline tools only in validating mode)
tool_names = client.list_tools()

# Backward compatibility: json_file_path still works
client = MCIClient(json_file_path="example.mci.json")
# Works with YAML too
client = MCIClient(json_file_path="example.mci.yaml")
```

**Success Response:**

Returns an initialized `MCIClient` instance ready to use.

**Error Response:**

```python
# Raises MCIClientError
MCIClientError: Failed to load schema from invalid.json: [Errno 2] No such file or directory: 'invalid.json'

# Unsupported file extension
MCIClientError: Failed to load schema from file.txt: Unsupported file extension '.txt'. Supported extensions: .json, .yaml, .yml
```

#### Validating Mode

When `validating=True` is specified, the client operates in a special validation-only mode:

**What happens in validating mode:**
- ✅ Schema file is parsed and validated (JSON/YAML syntax, structure, types)
- ✅ Schema version is checked for compatibility
- ✅ Tool definitions are validated (required fields, execution types)
- ✅ Toolset files are checked for existence (but not loaded)
- ✅ MCP server configurations are validated (but servers are not contacted)
- ✅ Read-only operations work normally (`list_tools()`, `tools()`, `only()`, `without()`, etc.)

**What does NOT happen in validating mode:**
- ❌ No template resolution (placeholders like `{{env.VAR}}` are accepted as-is)
- ❌ No MCP server connections or tool fetching
- ❌ No toolset file loading (only existence is checked)
- ❌ No file writes or cache directory creation
- ❌ No network requests
- ❌ Tool execution is blocked (raises `MCIClientError`)

**Use cases for validating mode:**
- **CI/CD validation**: Check schema validity without requiring environment variables
- **IDE/Editor plugins**: Validate schemas and provide autocomplete without side effects
- **Schema testing**: Verify schema structure before deployment
- **Documentation generation**: Parse schemas to generate tool documentation
- **Pre-deployment checks**: Validate schemas before committing to version control

**Example:**

```python
from mcipy import MCIClient, MCIClientError

# Validate a schema with MCP servers that require env vars
client = MCIClient(
    schema_file_path="schema_with_mcp.mci.json",
    validating=True  # No env vars needed!
)

# This works - checking what tools are defined
print(f"Schema contains {len(client.list_tools())} inline tools")

# This works - listing toolsets (just checks they exist)
# Toolsets are not loaded, so tools from toolsets won't appear in list_tools()

# This raises an error - execution is disabled
try:
    client.execute("some_tool", {})
except MCIClientError as e:
    print(f"Expected error: {e}")
    # Output: Tool execution is disabled in validating mode. Initialize MCIClient with validating=False to execute tools.
```

---

### Methods

#### `tools()`

Get all available tools from the loaded schema.

**Method Signature:**

```python
def tools(self) -> list[Tool]
```

**Parameters:**

None

**Returns:**

| Type | Description |
|------|-------------|
| `list[Tool]` | List of all Tool objects in the schema |

**Example:**

```python
from mcipy import MCIClient

client = MCIClient(json_file_path="example.mci.json")
all_tools = client.tools()

for tool in all_tools:
    print(f"Tool: {tool.name} - {tool.description}")
```

**Success Response:**

```python
[
    Tool(
        name="get_weather",
        annotations=Annotations(title="Get Weather Information"),
        description="Fetch current weather information for a location",
        inputSchema={
            "type": "object",
            "properties": {
                "location": {"type": "string", "description": "City name or location"}
            },
            "required": ["location"]
        },
        execution=HTTPExecutionConfig(...)
    ),
    Tool(
        name="create_report",
        annotations=Annotations(title="Create Report"),
        description="Create a new report using HTTP POST request",
        inputSchema={...},
        execution=HTTPExecutionConfig(...)
    )
]
```

**Error Response:**

No errors - always returns a list (may be empty if no tools defined).

---

#### `only()`

Filter tools to include only specified tools by name.

**Method Signature:**

```python
def only(self, tool_names: list[str]) -> list[Tool]
```

**Parameters:**

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `tool_names` | `list[str]` | Yes | List of tool names to include |

**Returns:**

| Type | Description |
|------|-------------|
| `list[Tool]` | Filtered list of Tool objects matching the specified names |

**Example:**

```python
from mcipy import MCIClient

client = MCIClient(json_file_path="example.mci.json")

# Get only weather-related tools
weather_tools = client.only(["get_weather", "get_forecast"])

for tool in weather_tools:
    print(f"Weather tool: {tool.name}")
```

**Success Response:**

```python
[
    Tool(
        name="get_weather",
        annotations=Annotations(title="Get Weather Information"),
        description="Fetch current weather information for a location",
        inputSchema={...},
        execution=HTTPExecutionConfig(...)
    ),
    Tool(
        name="get_forecast",
        annotations=Annotations(title="Get Weather Forecast"),
        description="Get weather forecast for the next 7 days",
        inputSchema={...},
        execution=HTTPExecutionConfig(...)
    )
]
```

**Error Response:**

No errors - returns empty list if no tools match the specified names.

---

#### `without()`

Filter tools to exclude specified tools by name.

**Method Signature:**

```python
def without(self, tool_names: list[str]) -> list[Tool]
```

**Parameters:**

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `tool_names` | `list[str]` | Yes | List of tool names to exclude |

**Returns:**

| Type | Description |
|------|-------------|
| `list[Tool]` | Filtered list of Tool objects excluding the specified names |

**Example:**

```python
from mcipy import MCIClient

client = MCIClient(json_file_path="example.mci.json")

# Get all tools except dangerous ones
safe_tools = client.without(["delete_data", "admin_tools"])

for tool in safe_tools:
    print(f"Safe tool: {tool.name}")
```

**Success Response:**

```python
[
    Tool(
        name="get_weather",
        annotations=Annotations(title="Get Weather Information"),
        description="Fetch current weather information for a location",
        inputSchema={...},
        execution=HTTPExecutionConfig(...)
    ),
    Tool(
        name="search_data",
        annotations=Annotations(title="Search Data"),
        description="Search for data in the database",
        inputSchema={...},
        execution=HTTPExecutionConfig(...)
    )
    # delete_data and admin_tools are excluded
]
```

**Error Response:**

No errors - returns all tools if specified names don't exist.

---

#### `tags()`

Filter tools to include only those with at least one matching tag.

**Method Signature:**

```python
def tags(self, tags: list[str]) -> list[Tool]
```

**Parameters:**

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `tags` | `list[str]` | Yes | List of tags to filter by (OR logic - tool must have at least one matching tag) |

**Returns:**

| Type | Description |
|------|-------------|
| `list[Tool]` | Filtered list of Tool objects that have at least one of the specified tags |

**Example:**

```python
from mcipy import MCIClient

client = MCIClient(schema_file_path="example.mci.json")

# Get all tools tagged with "api" or "database"
api_or_db_tools = client.tags(["api", "database"])

for tool in api_or_db_tools:
    print(f"Tool: {tool.name}, Tags: {tool.tags}")
```

**Success Response:**

```python
[
    Tool(
        name="github_api",
        description="GitHub API client",
        tags=["api", "external"],
        execution=HTTPExecutionConfig(...)
    ),
    Tool(
        name="database_query",
        description="Query database",
        tags=["database", "internal"],
        execution=CLIExecutionConfig(...)
    )
]
```

**Error Response:**

No errors - returns empty list if no tools have any of the specified tags.

**Notes:**

- Tags are case-sensitive and matched exactly as provided
- Uses OR logic: a tool is included if it has ANY of the specified tags
- Tools without tags are never included
- Empty tag list returns empty result

---

#### `withoutTags()`

Filter tools to exclude those with any matching tag.

**Method Signature:**

```python
def withoutTags(self, tags: list[str]) -> list[Tool]
```

**Parameters:**

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `tags` | `list[str]` | Yes | List of tags to exclude (OR logic - tool is excluded if it has any matching tag) |

**Returns:**

| Type | Description |
|------|-------------|
| `list[Tool]` | Filtered list of Tool objects that do NOT have any of the specified tags |

**Example:**

```python
from mcipy import MCIClient

client = MCIClient(schema_file_path="example.mci.json")

# Get all tools that are NOT tagged with "external" or "deprecated"
internal_tools = client.withoutTags(["external", "deprecated"])

for tool in internal_tools:
    print(f"Internal tool: {tool.name}")
```

**Success Response:**

```python
[
    Tool(
        name="database_query",
        description="Query database",
        tags=["database", "internal"],
        execution=CLIExecutionConfig(...)
    ),
    Tool(
        name="generate_report",
        description="Generate internal report",
        tags=["internal", "reporting"],
        execution=TextExecutionConfig(...)
    )
    # Tools with "external" or "deprecated" tags are excluded
]
```

**Error Response:**

No errors - returns all tools if none have any of the specified tags.

**Notes:**

- Tags are case-sensitive and matched exactly as provided
- Uses OR logic for exclusion: a tool is excluded if it has ANY of the specified tags
- Tools without tags are always included (they don't have the excluded tags)
- Empty tag list returns all tools

---

#### `toolsets()`

Filter tools to include only those from specified toolsets.

**Method Signature:**

```python
def toolsets(self, toolset_names: list[str]) -> list[Tool]
```

**Parameters:**

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `toolset_names` | `list[str]` | Yes | List of toolset names to include (OR logic - tool is included if it came from any matching toolset) |

**Returns:**

| Type | Description |
|------|-------------|
| `list[Tool]` | Filtered list of Tool objects from the specified toolsets |

**Example:**

```python
from mcipy import MCIClient

client = MCIClient(schema_file_path="example.mci.json")

# Get all tools from the "weather" toolset
weather_tools = client.toolsets(["weather"])

# Get tools from multiple toolsets
api_tools = client.toolsets(["weather", "database", "github"])

for tool in api_tools:
    print(f"Tool: {tool.name} (from {tool.toolset_source})")
```

**Success Response:**

```python
[
    Tool(
        name="get_weather",
        description="Get current weather",
        tags=["weather", "read"],
        toolset_source="weather",
        execution=HTTPExecutionConfig(...)
    ),
    Tool(
        name="query_data",
        description="Query database",
        tags=["database", "read"],
        toolset_source="database",
        execution=CLIExecutionConfig(...)
    )
]
```

**Error Response:**

No errors - returns empty list if no tools match the specified toolset names.

**Notes:**

- Only returns tools that were loaded from toolsets (not main schema tools)
- Uses OR logic: a tool is included if it came from ANY of the specified toolsets
- Toolset filtering respects schema-level filters (only tools registered by their toolset's filter are included)
- Empty toolset list returns no tools
- Tools must have been loaded via the `toolsets` field in the main schema
- The `toolset_source` field on each Tool indicates which toolset it came from

---

#### `execute()`

Execute a tool by name with the provided properties.

**Method Signature:**

```python
def execute(self, tool_name: str, properties: dict[str, Any] | None = None) -> ExecutionResult
```

**Parameters:**

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `tool_name` | `str` | Yes | Name of the tool to execute |
| `properties` | `dict[str, Any]` | No | Properties/parameters to pass to the tool (default: `{}`) |

**Returns:**

| Type | Description |
|------|-------------|
| `ExecutionResult` | Result object with success/error status and content |

**Raises:**

- `MCIClientError` - If tool not found or execution fails with validation error

**Example:**

```python
from mcipy import MCIClient

client = MCIClient(
    json_file_path="example.mci.json",
    env_vars={"API_KEY": "your-secret-key"}
)

# Execute a tool with properties
result = client.execute(
    tool_name="get_weather",
    properties={"location": "New York"}
)

# Handle result
if result.result.isError:
    print(f"Error: {result.result.content[0].text}")
else:
    print(f"Success: {result.result.content[0].text}")
    if result.metadata:
        print(f"Metadata: {result.metadata}")
```

**Success Response:**

```python
ExecutionResult(
    isError=False,
    content={
        "location": "New York",
        "temperature": 72,
        "conditions": "Sunny",
        "humidity": 45
    },
    error=None,
    metadata={
        "status_code": 200,
        "execution_time_ms": 150
    }
)
```

**Error Response - Tool Not Found:**

```python
# Raises MCIClientError
MCIClientError: Tool not found: invalid_tool_name
```

**Error Response - Execution Error:**

```python
ExecutionResult(
    isError=True,
    content=None,
    error="HTTP request failed: 404 Not Found",
    metadata={
        "status_code": 404,
        "execution_time_ms": 75
    }
)
```

**Error Response - Network Error:**

```python
ExecutionResult(
    isError=True,
    content=None,
    error="Connection timeout after 5000ms",
    metadata=None
)
```

---

#### `list_tools()`

List available tool names as strings.

**Method Signature:**

```python
def list_tools(self) -> list[str]
```

**Parameters:**

None

**Returns:**

| Type | Description |
|------|-------------|
| `list[str]` | List of tool names (strings) |

**Example:**

```python
from mcipy import MCIClient

client = MCIClient(json_file_path="example.mci.json")
tool_names = client.list_tools()

print(f"Available tools: {tool_names}")
```

**Success Response:**

```python
["get_weather", "create_report", "search_files", "load_file", "generate_text"]
```

**Error Response:**

No errors - returns empty list if no tools defined.

---

#### `get_tool_schema()`

Get a tool's input schema (JSON Schema format).

**Method Signature:**

```python
def get_tool_schema(self, tool_name: str) -> dict[str, Any]
```

**Parameters:**

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `tool_name` | `str` | Yes | Name of the tool |

**Returns:**

| Type | Description |
|------|-------------|
| `dict[str, Any]` | Tool's input schema as a dictionary (or empty dict if no schema) |

**Raises:**

- `MCIClientError` - If tool not found

**Example:**

```python
from mcipy import MCIClient

client = MCIClient(json_file_path="example.mci.json")
schema = client.get_tool_schema("get_weather")

print(f"Schema: {schema}")
```

**Success Response:**

```python
{
    "type": "object",
    "properties": {
        "location": {
            "type": "string",
            "description": "City name or location"
        },
        "unit": {
            "type": "string",
            "description": "Temperature unit (celsius or fahrenheit)",
            "enum": ["celsius", "fahrenheit"]
        }
    },
    "required": ["location"]
}
```

**Success Response - No Schema:**

```python
{}  # Empty dict if tool has no input schema
```

**Error Response:**

```python
# Raises MCIClientError
MCIClientError: Tool not found: invalid_tool_name
```

---

## Data Models

### MCISchema

Top-level MCI schema representing the complete MCI context file.

**Fields:**

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `schemaVersion` | `str` | Yes | Schema version (e.g., "1.0") |
| `metadata` | `Metadata` | No | Optional metadata about the tool collection |
| `tools` | `list[Tool]` | Yes | List of tool definitions |

**Example:**

```python
{
    "schemaVersion": "1.0",
    "metadata": {
        "name": "Example MCI Tools",
        "description": "Example tool collection",
        "version": "1.0.0",
        "license": "MIT",
        "authors": ["MCI Team"]
    },
    "tools": [
        {
            "name": "get_weather",
            "annotations": {
        "title": "Get Weather"
            },
            "description": "Get weather information",
            "inputSchema": {...},
            "execution": {...}
        }
    ]
}
```

---

### Tool

Individual tool definition with name, description, input schema, and execution configuration.

**Fields:**

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `name` | `str` | Yes | Unique identifier for the tool |
| `annotations` | `Annotations` | No | Tool metadata and hints |
| `disabled` | `bool` | No | If true, tool is ignored (default: false) |
| `description` | `str` | No | Description of what the tool does |
| `inputSchema` | `dict[str, Any]` | No | JSON Schema defining expected input properties |
| `execution` | `HTTPExecutionConfig` \| `CLIExecutionConfig` \| `FileExecutionConfig` \| `TextExecutionConfig` | Yes | Execution configuration (determines how tool is executed) |

**Example:**

```python
{
    "name": "get_weather",
    "annotations": {
        "title": "Get Weather Information"
    },
    "description": "Fetch current weather information for a location",
    "inputSchema": {
        "type": "object",
        "properties": {
            "location": {
                "type": "string",
                "description": "City name or location"
            }
        },
        "required": ["location"]
    },
    "execution": {
        "type": "http",
        "method": "GET",
        "url": "https://api.example.com/weather",
        "params": {
            "location": "{{props.location}}"
        }
    }
}
```

---

### ExecutionResult

Result format returned from tool execution.

**Fields:**

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `isError` | `bool` | Yes | Whether an error occurred during execution |
| `content` | `Any` | No | Result content (None if error) |
| `error` | `str` | No | Error message (None if success) |
| `metadata` | `dict[str, Any]` | No | Additional metadata (e.g., status_code, execution_time_ms) |

**Example - Success:**

```python
ExecutionResult(
    isError=False,
    content={
        "location": "New York",
        "temperature": 72,
        "conditions": "Sunny"
    },
    error=None,
    metadata={
        "status_code": 200,
        "execution_time_ms": 150
    }
)
```

**Example - Error:**

```python
ExecutionResult(
    isError=True,
    content=None,
    error="HTTP request failed: 404 Not Found",
    metadata={
        "status_code": 404,
        "execution_time_ms": 75
    }
)
```

**Example - Text Content:**

```python
ExecutionResult(
    isError=False,
    content="Hello, World!",
    error=None,
    metadata=None
)
```

**Example - File Content:**

```python
ExecutionResult(
    isError=False,
    content="File content with template: user@example.com",
    error=None,
    metadata=None
)
```

---

### Metadata

Optional metadata about the MCI tool collection.

**Fields:**

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `name` | `str` | No | Name of the tool collection |
| `description` | `str` | No | Description of the collection |
| `version` | `str` | No | Version number (e.g., "1.0.0") |
| `license` | `str` | No | License type (e.g., "MIT") |
| `authors` | `list[str]` | No | List of author names |

**Example:**

```python
{
    "name": "Weather Tools",
    "description": "Collection of weather-related tools",
    "version": "1.0.0",
    "license": "MIT",
    "authors": ["Alice Smith", "Bob Jones"]
}
```

---

## Execution Configurations

### HTTPExecutionConfig

Configuration for HTTP-based tool execution.

**Fields:**

| Field | Type | Required | Default | Description |
|-------|------|----------|---------|-------------|
| `type` | `ExecutionType` | Yes | `"http"` | Execution type identifier |
| `method` | `str` | No | `"GET"` | HTTP method (GET, POST, PUT, DELETE, etc.) |
| `url` | `str` | Yes | - | URL endpoint for the request |
| `headers` | `dict[str, str]` | No | `None` | HTTP headers |
| `auth` | `AuthConfig` | No | `None` | Authentication configuration |
| `params` | `dict[str, Any]` | No | `None` | Query parameters |
| `body` | `HTTPBodyConfig` | No | `None` | Request body configuration |
| `timeout_ms` | `int` | No | `30000` | Request timeout in milliseconds |
| `retries` | `RetryConfig` | No | `None` | Retry configuration |

**Example - GET Request:**

```python
{
    "type": "http",
    "method": "GET",
    "url": "https://api.example.com/weather",
    "params": {
        "location": "{{props.location}}",
        "units": "metric"
    },
    "headers": {
        "Accept": "application/json"
    },
    "timeout_ms": 5000
}
```

**Example - POST Request with Authentication:**

```python
{
    "type": "http",
    "method": "POST",
    "url": "https://api.example.com/reports",
    "headers": {
        "Content-Type": "application/json"
    },
    "auth": {
        "type": "bearer",
        "token": "{{env.BEARER_TOKEN}}"
    },
    "body": {
        "type": "json",
        "content": {
            "title": "{{props.title}}",
            "content": "{{props.content}}"
        }
    },
    "timeout_ms": 10000,
    "retries": {
        "attempts": 3,
        "backoff_ms": 1000
    }
}
```

---

### CLIExecutionConfig

Configuration for command-line tool execution.

**Fields:**

| Field | Type | Required | Default | Description |
|-------|------|----------|---------|-------------|
| `type` | `ExecutionType` | Yes | `"cli"` | Execution type identifier |
| `command` | `str` | Yes | - | Command to execute |
| `args` | `list[str]` | No | `None` | Command arguments |
| `flags` | `dict[str, FlagConfig]` | No | `None` | Command flags configuration |
| `cwd` | `str` | No | `None` | Working directory for command execution |
| `timeout_ms` | `int` | No | `30000` | Execution timeout in milliseconds |

**Example - Simple Command:**

```python
{
    "type": "cli",
    "command": "ls",
    "args": ["-la", "/home/user"],
    "timeout_ms": 5000
}
```

**Example - Command with Flags:**

```python
{
    "type": "cli",
    "command": "grep",
    "args": ["-r", "{{props.pattern}}"],
    "flags": {
        "--color": {
            "from": "props.color",
            "type": "boolean"
        }
    },
    "cwd": "/home/user/projects",
    "timeout_ms": 10000
}
```

---

### FileExecutionConfig

Configuration for file reading and templating.

**Fields:**

| Field | Type | Required | Default | Description |
|-------|------|----------|---------|-------------|
| `type` | `ExecutionType` | Yes | `"file"` | Execution type identifier |
| `path` | `str` | Yes | - | Path to the file to read |
| `enableTemplating` | `bool` | No | `True` | Whether to process template placeholders in file content |

**Example - Read File with Templating:**

```python
{
    "type": "file",
    "path": "/home/user/templates/email.txt",
    "enableTemplating": true
}
```

**Example - Read File Without Templating:**

```python
{
    "type": "file",
    "path": "/home/user/data/config.json",
    "enableTemplating": false
}
```

---

### TextExecutionConfig

Configuration for simple text template execution.

**Fields:**

| Field | Type | Required | Default | Description |
|-------|------|----------|---------|-------------|
| `type` | `ExecutionType` | Yes | `"text"` | Execution type identifier |
| `text` | `str` | Yes | - | Text template with placeholder support |

**Example:**

```python
{
    "type": "text",
    "text": "Hello {{props.name}}! Your email is {{env.USER_EMAIL}}."
}
```

**Execution Result:**

```python
# With properties={"name": "Alice"} and env_vars={"USER_EMAIL": "alice@example.com"}
ExecutionResult(
    isError=False,
    content="Hello Alice! Your email is alice@example.com.",
    error=None,
    metadata=None
)
```

---

## Authentication Models

### ApiKeyAuth

API Key authentication configuration.

**Fields:**

| Field | Type | Required | Default | Description |
|-------|------|----------|---------|-------------|
| `type` | `str` | Yes | `"apiKey"` | Authentication type |
| `in` | `str` | Yes | - | Where to place the key: "header" or "query" |
| `name` | `str` | Yes | - | Name of the header or query parameter |
| `value` | `str` | Yes | - | API key value (supports templates) |

**Example - Header-based:**

```python
{
    "type": "apiKey",
    "in": "header",
    "name": "X-API-Key",
    "value": "{{env.API_KEY}}"
}
```

**Example - Query parameter:**

```python
{
    "type": "apiKey",
    "in": "query",
    "name": "api_key",
    "value": "{{env.API_KEY}}"
}
```

---

### BearerAuth

Bearer token authentication configuration.

**Fields:**

| Field | Type | Required | Default | Description |
|-------|------|----------|---------|-------------|
| `type` | `str` | Yes | `"bearer"` | Authentication type |
| `token` | `str` | Yes | - | Bearer token value (supports templates) |

**Example:**

```python
{
    "type": "bearer",
    "token": "{{env.BEARER_TOKEN}}"
}
```

---

### BasicAuth

Basic authentication (username/password) configuration.

**Fields:**

| Field | Type | Required | Default | Description |
|-------|------|----------|---------|-------------|
| `type` | `str` | Yes | `"basic"` | Authentication type |
| `username` | `str` | Yes | - | Username (supports templates) |
| `password` | `str` | Yes | - | Password (supports templates) |

**Example:**

```python
{
    "type": "basic",
    "username": "{{env.USERNAME}}",
    "password": "{{env.PASSWORD}}"
}
```

---

### OAuth2Auth

OAuth2 authentication configuration.

**Fields:**

| Field | Type | Required | Default | Description |
|-------|------|----------|---------|-------------|
| `type` | `str` | Yes | `"oauth2"` | Authentication type |
| `flow` | `str` | Yes | - | OAuth2 flow type (e.g., "clientCredentials") |
| `tokenUrl` | `str` | Yes | - | Token endpoint URL |
| `clientId` | `str` | Yes | - | OAuth2 client ID |
| `clientSecret` | `str` | Yes | - | OAuth2 client secret (supports templates) |
| `scopes` | `list[str]` | No | `None` | Optional OAuth2 scopes |

**Example:**

```python
{
    "type": "oauth2",
    "flow": "clientCredentials",
    "tokenUrl": "https://auth.example.com/oauth/token",
    "clientId": "my-client-id",
    "clientSecret": "{{env.OAUTH_CLIENT_SECRET}}",
    "scopes": ["read:data", "write:data"]
}
```

---

## Error Handling

The MCI Python adapter provides consistent error handling across all operations.

### Exception Types

#### MCIClientError

Raised by `MCIClient` methods for client-level errors.

**Common Causes:**

- Schema file not found or invalid
- Tool not found
- Invalid tool execution

**Example:**

```python
from mcipy import MCIClient, MCIClientError

try:
    client = MCIClient(json_file_path="nonexistent.json")
except MCIClientError as e:
    print(f"Client error: {e}")
    # Output: Client error: Failed to load schema from nonexistent.json: [Errno 2] No such file or directory
```

### ExecutionResult Error Format

Execution errors are returned as `ExecutionResult` objects with `isError=True`.

**Error Fields:**

| Field | Description |
|-------|-------------|
| `isError` | Always `True` for errors |
| `content` | Always `None` for errors |
| `error` | Human-readable error message |
| `metadata` | Optional additional error context |

**Example Error Scenarios:**

**HTTP Request Failed:**

```python
result = client.execute("get_weather", {"location": "InvalidCity"})
# ExecutionResult(
#     isError=True,
#     content=None,
#     error="HTTP request failed: 404 Not Found",
#     metadata={"status_code": 404, "execution_time_ms": 75}
# )
```

**Connection Timeout:**

```python
result = client.execute("slow_api", {})
# ExecutionResult(
#     isError=True,
#     content=None,
#     error="Connection timeout after 30000ms",
#     metadata=None
# )
```

**CLI Command Failed:**

```python
result = client.execute("invalid_command", {})
# ExecutionResult(
#     isError=True,
#     content=None,
#     error="Command failed with exit code 127: command not found",
#     metadata={"exit_code": 127}
# )
```

**File Not Found:**

```python
result = client.execute("read_config", {})
# ExecutionResult(
#     isError=True,
#     content=None,
#     error="File not found: /path/to/config.json",
#     metadata=None
# )
```

**Template Variable Missing:**

```python
# If {{env.MISSING_VAR}} is referenced but not provided
result = client.execute("template_tool", {})
# ExecutionResult(
#     isError=True,
#     content=None,
#     error="Template variable not found: env.MISSING_VAR",
#     metadata=None
# )
```

**Path Validation Error:**

```python
# When a tool attempts to access a file outside allowed directories
result = client.execute("read_file", {"path": "/etc/passwd"})
# ExecutionResult(
#     isError=True,
#     content=None,
#     error="File path access outside context directory and allow-list is not allowed unless enableAnyPaths is true. Path: /etc/passwd",
#     metadata=None
# )
```

### Security: Path Validation

The MCI Python adapter includes built-in path validation to prevent unauthorized file system access.

**Default Behavior:**
- File and CLI execution are restricted to the schema file's directory
- Subdirectories of the schema directory are allowed
- Paths outside the schema directory are blocked unless explicitly allowed

**Configuration Options:**

1. **Schema-level settings** (applies to all tools):
   ```json
   {
     "schemaVersion": "1.0",
     "enableAnyPaths": false,
     "directoryAllowList": ["/home/user/data", "./configs"],
     "tools": [...]
   }
   ```

2. **Tool-level settings** (overrides schema-level):
   ```json
   {
     "name": "read_system_file",
     "enableAnyPaths": true,
     "execution": {
       "type": "file",
       "path": "{{props.file_path}}"
     }
   }
   ```

**Path Validation Behavior:**

| Scenario | Allowed? |
|----------|----------|
| File in schema directory | ✓ Yes |
| File in subdirectory of schema directory | ✓ Yes |
| File outside schema directory (no config) | ✗ No - Error |
| File in `directoryAllowList` | ✓ Yes |
| Any path with `enableAnyPaths: true` | ✓ Yes |

**Best Practices:**
- Keep `enableAnyPaths` disabled unless absolutely necessary
- Use `directoryAllowList` for specific directories instead of `enableAnyPaths`
- Validate user input before passing to tools that access files
- Review tool configurations regularly for security implications

### Error Handling Best Practices

**Check isError Flag:**

```python
result = client.execute("get_weather", {"location": "New York"})

if result.result.isError:
    print(f"Error occurred: {result.result.content[0].text}")
    if result.metadata:
        print(f"Additional context: {result.metadata}")
else:
    print(f"Success: {result.result.content[0].text}")
```

**Try-Except for Client Errors:**

```python
try:
    client = MCIClient(json_file_path="example.mci.json")
    result = client.execute("get_weather", {"location": "New York"})
    
    if result.result.isError:
        # Handle execution errors
        print(f"Execution failed: {result.result.content[0].text}")
    else:
        # Process successful result
        print(f"Weather data: {result.result.content[0].text}")
        
except MCIClientError as e:
    # Handle client-level errors (tool not found, invalid schema, etc.)
    print(f"Client error: {e}")
```

**Validate Tools Before Execution:**

```python
client = MCIClient(json_file_path="example.mci.json")

# Check if tool exists
available_tools = client.list_tools()
if "get_weather" in available_tools:
    result = client.execute("get_weather", {"location": "New York"})
else:
    print("Tool 'get_weather' not available")
```

---

## Complete Usage Example

Here's a comprehensive example demonstrating all major features:

```python
from mcipy import MCIClient, MCIClientError

# Initialize client with environment variables
try:
    client = MCIClient(
        json_file_path="example.mci.json",
        env_vars={
            "API_KEY": "your-secret-key",
            "BEARER_TOKEN": "bearer-token-123",
            "USERNAME": "demo_user"
        }
    )
except MCIClientError as e:
    print(f"Failed to initialize client: {e}")
    exit(1)

# List all available tools
print("Available tools:")
for tool_name in client.list_tools():
    print(f"  - {tool_name}")

# Get detailed tool information
all_tools = client.tools()
for tool in all_tools:
    print(f"\nTool: {tool.name}")
    print(f"  Title: {tool.annotations.title if tool.annotations else \'N/A\'}")
    print(f"  Description: {tool.description}")

# Filter tools
weather_tools = client.only(["get_weather", "get_forecast"])
print(f"\nWeather tools: {[t.name for t in weather_tools]}")

safe_tools = client.without(["delete_data", "admin_tools"])
print(f"Safe tools: {[t.name for t in safe_tools]}")

# Get tool schema
try:
    schema = client.get_tool_schema("get_weather")
    print(f"\nWeather tool schema: {schema}")
except MCIClientError as e:
    print(f"Error getting schema: {e}")

# Execute a tool
result = client.execute(
    tool_name="get_weather",
    properties={"location": "New York"}
)

if result.result.isError:
    print(f"\nExecution failed: {result.error}")
    if result.metadata:
        print(f"Error metadata: {result.metadata}")
else:
    print(f"\nExecution successful!")
    print(f"Content: {result.result.content[0].text}")
    if result.metadata:
        print(f"Metadata: {result.metadata}")
```

---

## Template Syntax

MCI supports template placeholders for dynamic value substitution:

- `{{props.fieldName}}` - Access properties passed to execute()
- `{{env.VARIABLE_NAME}}` - Access environment variables
- `{{input.fieldName}}` - **Deprecated** alias for props (use `props` instead)

> **Note:** `{{input.fieldName}}` is supported for backward compatibility but is deprecated. Use `{{props.fieldName}}` in all new code.
**Example:**

```json
{
    "execution": {
        "type": "http",
        "url": "https://api.example.com/users/{{props.userId}}",
        "headers": {
            "Authorization": "Bearer {{env.API_TOKEN}}"
        }
    }
}
```

With execution:

```python
result = client.execute(
    "get_user",
    properties={"userId": "12345"}
)
# Resolves to: https://api.example.com/users/12345
```

---

## Notes

- All methods are synchronous (blocking) - execution waits for completion
- Environment variables should be provided during initialization, not at execution time
- Templates are processed before execution using a simple `{{}}` placeholder substitution system (not full Jinja2 syntax)
- HTTP responses are automatically parsed as JSON when possible
- CLI commands capture both stdout and stderr
- File paths can be relative or absolute
- Timeout values are in milliseconds
- All string fields support template substitution unless explicitly disabled

---

## LiteMcpClient Class

The `LiteMcpClient` class provides a lightweight integration with MCP (Model Context Protocol) servers using the official `mcp` package. It allows connecting to MCP tool servers via STDIO (e.g., uvx, npx) and HTTP/SSE endpoints.

### Configuration Models

#### `StdioCfg`

Configuration for STDIO-based MCP servers (local servers via command-line).

**Fields:**

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `type` | `Literal["stdio"]` | Yes | Must be "stdio" |
| `command` | `str` | Yes | Command to run (e.g., "uvx", "npx") |
| `args` | `List[str]` | No | Arguments to pass to the command (default: `[]`) |
| `env` | `Dict[str, str]` | No | Environment variables for the server process (default: `{}`) |

**Example:**

```python
from mcipy import StdioCfg

# STDIO configuration for uvx
stdio_cfg = StdioCfg(
    command="uvx",
    args=["mcp-server-memory"],
    env={"API_KEY": "secret"}
)

# STDIO configuration for npx
stdio_cfg = StdioCfg(
    command="npx",
    args=["-y", "@modelcontextprotocol/server-memory"]
)
```

#### `SseCfg`

Configuration for HTTP/SSE-based MCP servers (web-based servers).

**Fields:**

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `type` | `Literal["http"]` | Yes | Must be "http" |
| `url` | `HttpUrl` | Yes | Server URL (e.g., "http://localhost:8000/mcp") |
| `headers` | `Dict[str, str]` | No | HTTP headers for authentication (default: `{}`) |

**Example:**

```python
from mcipy import SseCfg

# HTTP configuration with authentication
http_cfg = SseCfg(
    url="https://api.example.com/mcp",
    headers={"Authorization": "Bearer token123"}
)

# HTTP configuration without authentication
http_cfg = SseCfg(url="http://localhost:8000/mcp")
```

#### `ClientCfg`

Complete configuration for the LiteMcpClient.

**Fields:**

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `server` | `StdioCfg \| SseCfg` | Yes | Server connection configuration |
| `request_timeout` | `Optional[float]` | No | Request timeout in seconds (default: 60.0) |

**Example:**

```python
from mcipy import ClientCfg, StdioCfg

# Client configuration with STDIO server
client_cfg = ClientCfg(
    server=StdioCfg(command="uvx", args=["mcp-server"]),
    request_timeout=120.0
)
```

### Initialization

#### `LiteMcpClient(cfg: ClientCfg)`

Initialize the LiteMcpClient with configuration.

**Parameters:**

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `cfg` | `ClientCfg` | Yes | Client configuration specifying server type and connection details |

**Example:**

```python
from mcipy import LiteMcpClient, ClientCfg, StdioCfg

cfg = ClientCfg(
    server=StdioCfg(command="uvx", args=["mcp-server-memory"])
)
client = LiteMcpClient(cfg)
```

### Usage

The `LiteMcpClient` must be used as an async context manager to properly manage the connection lifecycle.

**Example:**

```python
import asyncio
from mcipy import LiteMcpClient, ClientCfg, StdioCfg

async def main():
    cfg = ClientCfg(
        server=StdioCfg(
            command="npx",
            args=["-y", "@modelcontextprotocol/server-memory"]
        )
    )
    
    async with LiteMcpClient(cfg) as client:
        # List available tools
        tools = await client.list_tools()
        print(f"Available tools: {tools}")
        
        # Call a tool
        result = await client.call_tool("store_memory", key="test", value="data")
        print(f"Result: {result}")

asyncio.run(main())
```

### Methods

#### `async list_tools() -> List[str]`

List all available tools from the MCP server.

**Returns:**

- `List[str]` - List of tool names available on the server

**Raises:**

- `RuntimeError` - If session is not initialized (client not used as context manager)

**Example:**

```python
async with LiteMcpClient(cfg) as client:
    tools = await client.list_tools()
    # Returns: ["store_memory", "retrieve_memory", "list_memories"]
```

#### `async call_tool(name: str, **arguments: Any) -> Any`

Call a tool on the MCP server with the provided arguments.

**Parameters:**

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `name` | `str` | Yes | Name of the tool to call |
| `**arguments` | `Any` | No | Keyword arguments to pass to the tool |

**Returns:**

- `Any` - The tool execution result from the server (typically containing `content` and metadata)

**Raises:**

- `RuntimeError` - If session is not initialized (client not used as context manager)

**Example:**

```python
async with LiteMcpClient(cfg) as client:
    # Call tool with arguments
    result = await client.call_tool(
        "store_memory",
        key="user_preference",
        value="dark_mode"
    )
    print(result.content)  # Access result content
```

### Complete Examples

#### STDIO Example (uvx)

```python
import asyncio
from mcipy import LiteMcpClient, ClientCfg, StdioCfg

async def main():
    cfg = ClientCfg(
        server=StdioCfg(
            command="uvx",
            args=["mcp-server-memory"],
            env={}
        )
    )
    
    async with LiteMcpClient(cfg) as client:
        tools = await client.list_tools()
        print(f"Available tools: {tools}")
        
        if "store_memory" in tools:
            await client.call_tool(
                "store_memory",
                key="greeting",
                value="Hello, World!"
            )

asyncio.run(main())
```

#### STDIO Example (npx)

```python
import asyncio
from mcipy import LiteMcpClient, ClientCfg, StdioCfg

async def main():
    cfg = ClientCfg(
        server=StdioCfg(
            command="npx",
            args=["-y", "@modelcontextprotocol/server-filesystem", "/path/to/dir"]
        )
    )
    
    async with LiteMcpClient(cfg) as client:
        tools = await client.list_tools()
        print(f"Filesystem tools: {tools}")

asyncio.run(main())
```

#### HTTP Example

```python
import asyncio
from mcipy import LiteMcpClient, ClientCfg, SseCfg

async def main():
    cfg = ClientCfg(
        server=SseCfg(
            url="https://api.githubcopilot.com/mcp/",
            headers={"Authorization": "Bearer YOUR_TOKEN"}
        )
    )
    
    async with LiteMcpClient(cfg) as client:
        tools = await client.list_tools()
        print(f"GitHub MCP tools: {tools}")

asyncio.run(main())
```

### Error Handling

**RuntimeError**: Raised when attempting to use methods outside of context manager:

```python
cfg = ClientCfg(server=StdioCfg(command="uvx"))
client = LiteMcpClient(cfg)

# This will raise RuntimeError
await client.list_tools()  # Error: Session not initialized

# Correct usage:
async with client:
    await client.list_tools()  # Works correctly
```

**Connection Errors**: Network or process errors are propagated from the underlying MCP client:

```python
try:
    async with LiteMcpClient(cfg) as client:
        tools = await client.list_tools()
except Exception as e:
    print(f"Connection failed: {e}")
```

### Notes

- The `LiteMcpClient` uses the official `mcp` package for MCP protocol communication
- STDIO transport merges environment variables from the configuration with the current process environment
- HTTP transport uses Streamable HTTP, the modern replacement for SSE
- All async operations must be called from within the context manager
- The client automatically handles connection setup and teardown

---

## See Also

- [Quickstart Guide](quickstart.md) - Getting started with MCI
- [Schema Reference](schema_reference.md) - Complete JSON schema documentation
- [Examples](../examples/) - Example tool definitions and usage patterns
