Documentation Index
Fetch the complete documentation index at: https://usemci.dev/llms.txt
Use this file to discover all available pages before exploring further.
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:
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:
# 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:
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
Get all available tools from the loaded schema.
Method Signature:
def tools(self) -> list[Tool]
Parameters:
None
Returns:
| Type | Description |
|---|
list[Tool] | List of all Tool objects in the schema |
Example:
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:
[
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:
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:
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:
[
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:
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:
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:
[
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.
Filter tools to include only those with at least one matching tag.
Method Signature:
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:
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:
[
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
Filter tools to exclude those with any matching tag.
Method Signature:
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:
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:
[
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
Filter tools to include only those from specified toolsets.
Method Signature:
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:
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:
[
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:
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:
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:
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:
# Raises MCIClientError
MCIClientError: Tool not found: invalid_tool_name
Error Response - Execution Error:
ExecutionResult(
isError=True,
content=None,
error="HTTP request failed: 404 Not Found",
metadata={
"status_code": 404,
"execution_time_ms": 75
}
)
Error Response - Network Error:
ExecutionResult(
isError=True,
content=None,
error="Connection timeout after 5000ms",
metadata=None
)
List available tool names as strings.
Method Signature:
def list_tools(self) -> list[str]
Parameters:
None
Returns:
| Type | Description |
|---|
list[str] | List of tool names (strings) |
Example:
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:
["get_weather", "create_report", "search_files", "load_file", "generate_text"]
Error Response:
No errors - returns empty list if no tools defined.
Get a tool’s input schema (JSON Schema format).
Method Signature:
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:
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:
{
"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:
{} # Empty dict if tool has no input schema
Error Response:
# 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:
{
"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": {...}
}
]
}
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:
{
"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:
ExecutionResult(
isError=False,
content={
"location": "New York",
"temperature": 72,
"conditions": "Sunny"
},
error=None,
metadata={
"status_code": 200,
"execution_time_ms": 150
}
)
Example - Error:
ExecutionResult(
isError=True,
content=None,
error="HTTP request failed: 404 Not Found",
metadata={
"status_code": 404,
"execution_time_ms": 75
}
)
Example - Text Content:
ExecutionResult(
isError=False,
content="Hello, World!",
error=None,
metadata=None
)
Example - File Content:
ExecutionResult(
isError=False,
content="File content with template: user@example.com",
error=None,
metadata=None
)
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:
{
"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:
{
"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:
{
"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:
{
"type": "cli",
"command": "ls",
"args": ["-la", "/home/user"],
"timeout_ms": 5000
}
Example - Command with Flags:
{
"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:
{
"type": "file",
"path": "/home/user/templates/email.txt",
"enableTemplating": true
}
Example - Read File Without Templating:
{
"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:
{
"type": "text",
"text": "Hello {{props.name}}! Your email is {{env.USER_EMAIL}}."
}
Execution Result:
# 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:
{
"type": "apiKey",
"in": "header",
"name": "X-API-Key",
"value": "{{env.API_KEY}}"
}
Example - Query parameter:
{
"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:
{
"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:
{
"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:
{
"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:
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
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:
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:
result = client.execute("slow_api", {})
# ExecutionResult(
# isError=True,
# content=None,
# error="Connection timeout after 30000ms",
# metadata=None
# )
CLI Command Failed:
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:
result = client.execute("read_config", {})
# ExecutionResult(
# isError=True,
# content=None,
# error="File not found: /path/to/config.json",
# metadata=None
# )
Template Variable Missing:
# 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:
# 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:
-
Schema-level settings (applies to all tools):
{
"schemaVersion": "1.0",
"enableAnyPaths": false,
"directoryAllowList": ["/home/user/data", "./configs"],
"tools": [...]
}
-
Tool-level settings (overrides schema-level):
{
"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:
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:
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:
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:
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:
{
"execution": {
"type": "http",
"url": "https://api.example.com/users/{{props.userId}}",
"headers": {
"Authorization": "Bearer {{env.API_TOKEN}}"
}
}
}
With execution:
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:
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:
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:
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:
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:
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
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:
async with LiteMcpClient(cfg) as client:
tools = await client.list_tools()
# Returns: ["store_memory", "retrieve_memory", "list_memories"]
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:
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)
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)
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
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:
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:
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