iflow-mcp_modelcontextinterface-mcix 1.1.1.dev0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- iflow_mcp_modelcontextinterface_mcix-1.1.1.dev0.dist-info/METADATA +931 -0
- iflow_mcp_modelcontextinterface_mcix-1.1.1.dev0.dist-info/RECORD +42 -0
- iflow_mcp_modelcontextinterface_mcix-1.1.1.dev0.dist-info/WHEEL +4 -0
- iflow_mcp_modelcontextinterface_mcix-1.1.1.dev0.dist-info/entry_points.txt +2 -0
- iflow_mcp_modelcontextinterface_mcix-1.1.1.dev0.dist-info/licenses/LICENSE +21 -0
- mci/__init__.py +10 -0
- mci/assets/example_toolset.mci.json +37 -0
- mci/assets/example_toolset.mci.yaml +23 -0
- mci/assets/gitignore +1 -0
- mci/assets/mci.json +29 -0
- mci/assets/mci.yaml +19 -0
- mci/cli/__init__.py +8 -0
- mci/cli/add.py +108 -0
- mci/cli/envs.py +257 -0
- mci/cli/formatters/__init__.py +12 -0
- mci/cli/formatters/env_formatter.py +83 -0
- mci/cli/formatters/json_formatter.py +93 -0
- mci/cli/formatters/table_formatter.py +138 -0
- mci/cli/formatters/yaml_formatter.py +93 -0
- mci/cli/install.py +147 -0
- mci/cli/list.py +153 -0
- mci/cli/run.py +125 -0
- mci/cli/validate.py +113 -0
- mci/core/__init__.py +8 -0
- mci/core/config.py +144 -0
- mci/core/dynamic_server.py +187 -0
- mci/core/file_finder.py +105 -0
- mci/core/mci_client.py +196 -0
- mci/core/mcp_server.py +240 -0
- mci/core/schema_editor.py +284 -0
- mci/core/tool_converter.py +119 -0
- mci/core/tool_manager.py +118 -0
- mci/core/validator.py +162 -0
- mci/mci.py +39 -0
- mci/py.typed +0 -0
- mci/utils/__init__.py +8 -0
- mci/utils/dotenv.py +170 -0
- mci/utils/env_scanner.py +84 -0
- mci/utils/error_formatter.py +165 -0
- mci/utils/error_handler.py +174 -0
- mci/utils/timestamp.py +50 -0
- mci/utils/validation.py +92 -0
mci/mci.py
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""
|
|
2
|
+
mci.py - Main entry point for the MCI CLI Tool
|
|
3
|
+
|
|
4
|
+
This module provides the main CLI interface for managing MCI (Model Context Interface)
|
|
5
|
+
schemas and dynamically running MCP servers using defined MCI toolsets.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import click
|
|
9
|
+
|
|
10
|
+
from mci.cli.add import add
|
|
11
|
+
from mci.cli.envs import envs_command
|
|
12
|
+
from mci.cli.install import install
|
|
13
|
+
from mci.cli.list import list_command
|
|
14
|
+
from mci.cli.run import run
|
|
15
|
+
from mci.cli.validate import validate
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@click.group()
|
|
19
|
+
@click.version_option()
|
|
20
|
+
def main():
|
|
21
|
+
"""
|
|
22
|
+
MCI CLI - Manage Model Context Interface schemas and run MCP servers.
|
|
23
|
+
|
|
24
|
+
Use 'mci COMMAND --help' for more information on a specific command.
|
|
25
|
+
"""
|
|
26
|
+
pass
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
# Register commands
|
|
30
|
+
main.add_command(add)
|
|
31
|
+
main.add_command(envs_command, name="envs")
|
|
32
|
+
main.add_command(install)
|
|
33
|
+
main.add_command(list_command, name="list")
|
|
34
|
+
main.add_command(run)
|
|
35
|
+
main.add_command(validate)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
if __name__ == "__main__":
|
|
39
|
+
main()
|
mci/py.typed
ADDED
|
File without changes
|
mci/utils/__init__.py
ADDED
mci/utils/dotenv.py
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
"""
|
|
2
|
+
dotenv.py - Environment variable file parsing utilities
|
|
3
|
+
|
|
4
|
+
This module provides functionality to parse .env files using the python-dotenv library
|
|
5
|
+
and merge environment variables from multiple sources. It supports:
|
|
6
|
+
- Standard .env format via python-dotenv
|
|
7
|
+
- Comments starting with #
|
|
8
|
+
- Blank lines
|
|
9
|
+
- Export keyword (which is ignored)
|
|
10
|
+
- Quoted values
|
|
11
|
+
|
|
12
|
+
The module is used to automatically load .env files from the project root
|
|
13
|
+
and ./mci directory when initializing MCI configurations. It supports both
|
|
14
|
+
.env and .env.mci files, with .env.mci taking precedence for MCI-specific
|
|
15
|
+
configuration.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
import os
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
|
|
21
|
+
from dotenv import dotenv_values
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def parse_dotenv_file(file_path: str | Path) -> dict[str, str]:
|
|
25
|
+
"""
|
|
26
|
+
Parse a .env file and return a dictionary of environment variables.
|
|
27
|
+
|
|
28
|
+
This function uses python-dotenv to parse .env files, which supports:
|
|
29
|
+
- KEY=VALUE format
|
|
30
|
+
- Lines starting with # are comments (ignored)
|
|
31
|
+
- Blank lines are ignored
|
|
32
|
+
- Export keyword is ignored (e.g., "export KEY=VALUE" is treated as "KEY=VALUE")
|
|
33
|
+
- Values can be quoted with single or double quotes
|
|
34
|
+
- Variable expansion and interpolation (if needed)
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
file_path: Path to the .env file to parse
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
Dictionary of environment variable key-value pairs (excluding None values)
|
|
41
|
+
|
|
42
|
+
Example:
|
|
43
|
+
>>> env_vars = parse_dotenv_file(".env")
|
|
44
|
+
>>> print(env_vars.get("API_KEY"))
|
|
45
|
+
'my-secret-key'
|
|
46
|
+
"""
|
|
47
|
+
file_path = Path(file_path)
|
|
48
|
+
|
|
49
|
+
if not file_path.exists():
|
|
50
|
+
return {}
|
|
51
|
+
|
|
52
|
+
try:
|
|
53
|
+
# Use dotenv_values to parse the file
|
|
54
|
+
# This returns a dict with all variables, including None for empty values
|
|
55
|
+
env_dict = dotenv_values(file_path)
|
|
56
|
+
# Filter out None values and convert to strings
|
|
57
|
+
return {k: str(v) for k, v in env_dict.items() if v is not None}
|
|
58
|
+
except (OSError, UnicodeDecodeError):
|
|
59
|
+
# If we can't read the file, return empty dict (silent failure)
|
|
60
|
+
# This maintains the "no error if .env is missing" requirement
|
|
61
|
+
return {}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def find_and_merge_dotenv_files(project_root: str | Path | None = None) -> dict[str, str]:
|
|
65
|
+
"""
|
|
66
|
+
Find and merge .env files from project root and ./mci directory.
|
|
67
|
+
|
|
68
|
+
This function looks for .env files with the following priority order:
|
|
69
|
+
1. Check for .env.mci files first (MCI-specific configs have priority)
|
|
70
|
+
a. {project_root}/mci/.env.mci (MCI library MCI-specific)
|
|
71
|
+
b. {project_root}/.env.mci (project MCI-specific - highest priority)
|
|
72
|
+
2. If no .env.mci files exist, check for .env files
|
|
73
|
+
a. {project_root}/mci/.env (MCI library defaults)
|
|
74
|
+
b. {project_root}/.env (project-level configs)
|
|
75
|
+
|
|
76
|
+
Variables from files loaded later override those from earlier files.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
project_root: Path to the project root directory. If None, uses current directory.
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
Dictionary of merged environment variables
|
|
83
|
+
|
|
84
|
+
Example:
|
|
85
|
+
>>> env_vars = find_and_merge_dotenv_files()
|
|
86
|
+
>>> # Variables from root .env.mci override all others
|
|
87
|
+
"""
|
|
88
|
+
if project_root is None:
|
|
89
|
+
project_root = Path.cwd()
|
|
90
|
+
else:
|
|
91
|
+
project_root = Path(project_root)
|
|
92
|
+
|
|
93
|
+
merged_env: dict[str, str] = {}
|
|
94
|
+
|
|
95
|
+
# Check for .env.mci files first (MCI-specific configs)
|
|
96
|
+
mci_env_mci_path = project_root / "mci" / ".env.mci"
|
|
97
|
+
root_env_mci_path = project_root / ".env.mci"
|
|
98
|
+
|
|
99
|
+
has_env_mci = mci_env_mci_path.exists() or root_env_mci_path.exists()
|
|
100
|
+
|
|
101
|
+
if has_env_mci:
|
|
102
|
+
# Priority order for .env.mci files (lowest to highest):
|
|
103
|
+
# 1. ./mci/.env.mci (library MCI-specific)
|
|
104
|
+
if mci_env_mci_path.exists():
|
|
105
|
+
mci_env_mci_vars = parse_dotenv_file(mci_env_mci_path)
|
|
106
|
+
merged_env.update(mci_env_mci_vars)
|
|
107
|
+
|
|
108
|
+
# 2. .env.mci (project root MCI-specific - highest priority)
|
|
109
|
+
if root_env_mci_path.exists():
|
|
110
|
+
root_env_mci_vars = parse_dotenv_file(root_env_mci_path)
|
|
111
|
+
merged_env.update(root_env_mci_vars)
|
|
112
|
+
else:
|
|
113
|
+
# No .env.mci files found, check for .env files
|
|
114
|
+
# Priority order for .env files (lowest to highest):
|
|
115
|
+
# 1. ./mci/.env (library defaults)
|
|
116
|
+
mci_env_path = project_root / "mci" / ".env"
|
|
117
|
+
if mci_env_path.exists():
|
|
118
|
+
mci_env_vars = parse_dotenv_file(mci_env_path)
|
|
119
|
+
merged_env.update(mci_env_vars)
|
|
120
|
+
|
|
121
|
+
# 2. .env (project root - higher priority)
|
|
122
|
+
root_env_path = project_root / ".env"
|
|
123
|
+
if root_env_path.exists():
|
|
124
|
+
root_env_vars = parse_dotenv_file(root_env_path)
|
|
125
|
+
merged_env.update(root_env_vars)
|
|
126
|
+
|
|
127
|
+
return merged_env
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def get_env_with_dotenv(
|
|
131
|
+
project_root: str | Path | None = None, additional_env: dict[str, str] | None = None
|
|
132
|
+
) -> dict[str, str]:
|
|
133
|
+
"""
|
|
134
|
+
Get complete environment variables including system, .env files, and additional vars.
|
|
135
|
+
|
|
136
|
+
The precedence order (lowest to highest):
|
|
137
|
+
- If .env.mci files exist:
|
|
138
|
+
1. ./mci/.env.mci (library MCI-specific)
|
|
139
|
+
2. {project_root}/.env.mci (project MCI-specific)
|
|
140
|
+
- If no .env.mci files exist:
|
|
141
|
+
1. ./mci/.env (library defaults)
|
|
142
|
+
2. {project_root}/.env (project-level)
|
|
143
|
+
- Then:
|
|
144
|
+
3. System environment variables (os.environ)
|
|
145
|
+
4. Additional environment variables passed as argument (highest)
|
|
146
|
+
|
|
147
|
+
Args:
|
|
148
|
+
project_root: Path to the project root directory. If None, uses current directory.
|
|
149
|
+
additional_env: Additional environment variables to merge (highest priority)
|
|
150
|
+
|
|
151
|
+
Returns:
|
|
152
|
+
Dictionary of merged environment variables with proper precedence
|
|
153
|
+
|
|
154
|
+
Example:
|
|
155
|
+
>>> # Get all env vars with .env files loaded
|
|
156
|
+
>>> env_vars = get_env_with_dotenv()
|
|
157
|
+
>>> # Add custom vars that override everything
|
|
158
|
+
>>> env_vars = get_env_with_dotenv(additional_env={"API_KEY": "override"})
|
|
159
|
+
"""
|
|
160
|
+
# Start with .env files (lowest priority)
|
|
161
|
+
merged_env = find_and_merge_dotenv_files(project_root)
|
|
162
|
+
|
|
163
|
+
# Merge with system environment variables (higher priority)
|
|
164
|
+
merged_env.update(os.environ)
|
|
165
|
+
|
|
166
|
+
# Merge with additional environment variables (highest priority)
|
|
167
|
+
if additional_env:
|
|
168
|
+
merged_env.update(additional_env)
|
|
169
|
+
|
|
170
|
+
return merged_env
|
mci/utils/env_scanner.py
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"""
|
|
2
|
+
env_scanner.py - Environment variable scanning utilities
|
|
3
|
+
|
|
4
|
+
This module provides utilities to scan MCI schemas and extract all
|
|
5
|
+
referenced environment variables from template placeholders.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import re
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class EnvScanner:
|
|
13
|
+
"""
|
|
14
|
+
Scans MCI schemas to find all environment variable references.
|
|
15
|
+
|
|
16
|
+
Environment variables can appear in templates as {{env.VARIABLE_NAME}}.
|
|
17
|
+
This scanner recursively searches through dictionaries, lists, and strings
|
|
18
|
+
to find all such references.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
# Regex pattern to match {{env.VARIABLE_NAME}}
|
|
22
|
+
ENV_PATTERN: re.Pattern[str] = re.compile(r"\{\{env\.([A-Za-z_][A-Za-z0-9_]*)\}\}")
|
|
23
|
+
|
|
24
|
+
@staticmethod
|
|
25
|
+
def scan_value(value: Any) -> set[str]:
|
|
26
|
+
"""
|
|
27
|
+
Scan a single value for environment variable references.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
value: Value to scan (can be str, dict, list, or other types)
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
Set of environment variable names found
|
|
34
|
+
|
|
35
|
+
Example:
|
|
36
|
+
>>> EnvScanner.scan_value("{{env.API_KEY}}")
|
|
37
|
+
{'API_KEY'}
|
|
38
|
+
>>> EnvScanner.scan_value("Hello {{env.USER}}, your key is {{env.API_KEY}}")
|
|
39
|
+
{'USER', 'API_KEY'}
|
|
40
|
+
"""
|
|
41
|
+
env_vars: set[str] = set()
|
|
42
|
+
|
|
43
|
+
if isinstance(value, str):
|
|
44
|
+
# Extract all env variable names from the string
|
|
45
|
+
matches = EnvScanner.ENV_PATTERN.findall(value)
|
|
46
|
+
env_vars.update(matches)
|
|
47
|
+
|
|
48
|
+
elif isinstance(value, dict):
|
|
49
|
+
# Recursively scan all values in the dictionary
|
|
50
|
+
for v in value.values():
|
|
51
|
+
env_vars.update(EnvScanner.scan_value(v))
|
|
52
|
+
|
|
53
|
+
elif isinstance(value, list):
|
|
54
|
+
# Recursively scan all items in the list
|
|
55
|
+
for item in value:
|
|
56
|
+
env_vars.update(EnvScanner.scan_value(item))
|
|
57
|
+
|
|
58
|
+
return env_vars
|
|
59
|
+
|
|
60
|
+
@staticmethod
|
|
61
|
+
def scan_dict(data: dict[str, Any]) -> set[str]:
|
|
62
|
+
"""
|
|
63
|
+
Scan a dictionary for environment variable references.
|
|
64
|
+
|
|
65
|
+
This is a convenience wrapper around scan_value() for dictionaries.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
data: Dictionary to scan
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
Set of environment variable names found
|
|
72
|
+
|
|
73
|
+
Example:
|
|
74
|
+
>>> schema = {
|
|
75
|
+
... "execution": {
|
|
76
|
+
... "type": "http",
|
|
77
|
+
... "url": "{{env.BASE_URL}}/api",
|
|
78
|
+
... "headers": {"Authorization": "Bearer {{env.API_KEY}}"}
|
|
79
|
+
... }
|
|
80
|
+
... }
|
|
81
|
+
>>> EnvScanner.scan_dict(schema)
|
|
82
|
+
{'BASE_URL', 'API_KEY'}
|
|
83
|
+
"""
|
|
84
|
+
return EnvScanner.scan_value(data)
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
"""
|
|
2
|
+
error_formatter.py - Format validation errors and warnings for CLI display
|
|
3
|
+
|
|
4
|
+
This module provides utilities for formatting validation errors and warnings
|
|
5
|
+
in a user-friendly way with color-coded output using Rich library.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
|
|
10
|
+
from rich.console import Console
|
|
11
|
+
from rich.panel import Panel
|
|
12
|
+
from rich.text import Text
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass
|
|
16
|
+
class ValidationError:
|
|
17
|
+
"""Represents a validation error with message and optional location."""
|
|
18
|
+
|
|
19
|
+
message: str
|
|
20
|
+
location: str | None = None
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass
|
|
24
|
+
class ValidationWarning:
|
|
25
|
+
"""Represents a validation warning with message and optional suggestion."""
|
|
26
|
+
|
|
27
|
+
message: str
|
|
28
|
+
suggestion: str | None = None
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class ErrorFormatter:
|
|
32
|
+
"""
|
|
33
|
+
Formats validation errors and warnings for CLI display.
|
|
34
|
+
|
|
35
|
+
This class provides methods to format validation results using the Rich library
|
|
36
|
+
for beautiful, color-coded terminal output.
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
def __init__(self, console: Console | None = None):
|
|
40
|
+
"""
|
|
41
|
+
Initialize the ErrorFormatter.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
console: Rich Console instance. Defaults to a new Console if not provided.
|
|
45
|
+
"""
|
|
46
|
+
self.console: Console = console if console is not None else Console()
|
|
47
|
+
|
|
48
|
+
def format_validation_errors(self, errors: list[ValidationError]) -> None:
|
|
49
|
+
"""
|
|
50
|
+
Display validation errors with color-coded output.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
errors: List of ValidationError objects to display
|
|
54
|
+
|
|
55
|
+
Example:
|
|
56
|
+
>>> formatter = ErrorFormatter()
|
|
57
|
+
>>> errors = [ValidationError(message="Missing required field: name")]
|
|
58
|
+
>>> formatter.format_validation_errors(errors)
|
|
59
|
+
"""
|
|
60
|
+
if not errors:
|
|
61
|
+
return
|
|
62
|
+
|
|
63
|
+
self.console.print()
|
|
64
|
+
error_text = Text()
|
|
65
|
+
error_text.append("❌ Validation Errors\n\n", style="bold red")
|
|
66
|
+
|
|
67
|
+
for i, error in enumerate(errors, 1):
|
|
68
|
+
error_text.append(f"{i}. ", style="red")
|
|
69
|
+
if error.location:
|
|
70
|
+
error_text.append(f"[{error.location}] ", style="yellow")
|
|
71
|
+
error_text.append(f"{error.message}\n", style="red")
|
|
72
|
+
|
|
73
|
+
panel = Panel(
|
|
74
|
+
error_text,
|
|
75
|
+
title="[bold red]Schema Validation Failed",
|
|
76
|
+
border_style="red",
|
|
77
|
+
expand=False,
|
|
78
|
+
)
|
|
79
|
+
self.console.print(panel)
|
|
80
|
+
|
|
81
|
+
def format_validation_warnings(self, warnings: list[ValidationWarning]) -> None:
|
|
82
|
+
"""
|
|
83
|
+
Display validation warnings with color-coded output.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
warnings: List of ValidationWarning objects to display
|
|
87
|
+
|
|
88
|
+
Example:
|
|
89
|
+
>>> formatter = ErrorFormatter()
|
|
90
|
+
>>> warnings = [ValidationWarning(
|
|
91
|
+
... message="Toolset file not found: weather.mci.json",
|
|
92
|
+
... suggestion="Create the file or update your schema"
|
|
93
|
+
... )]
|
|
94
|
+
>>> formatter.format_validation_warnings(warnings)
|
|
95
|
+
"""
|
|
96
|
+
if not warnings:
|
|
97
|
+
return
|
|
98
|
+
|
|
99
|
+
self.console.print()
|
|
100
|
+
warning_text = Text()
|
|
101
|
+
warning_text.append("⚠️ Validation Warnings\n\n", style="bold yellow")
|
|
102
|
+
|
|
103
|
+
for i, warning in enumerate(warnings, 1):
|
|
104
|
+
warning_text.append(f"{i}. ", style="yellow")
|
|
105
|
+
warning_text.append(f"{warning.message}\n", style="yellow")
|
|
106
|
+
if warning.suggestion:
|
|
107
|
+
warning_text.append(f" 💡 {warning.suggestion}\n", style="cyan dim")
|
|
108
|
+
|
|
109
|
+
panel = Panel(
|
|
110
|
+
warning_text,
|
|
111
|
+
title="[bold yellow]Warnings",
|
|
112
|
+
border_style="yellow",
|
|
113
|
+
expand=False,
|
|
114
|
+
)
|
|
115
|
+
self.console.print(panel)
|
|
116
|
+
|
|
117
|
+
def format_validation_success(self, file_path: str) -> None:
|
|
118
|
+
"""
|
|
119
|
+
Display validation success message.
|
|
120
|
+
|
|
121
|
+
Args:
|
|
122
|
+
file_path: Path to the validated file
|
|
123
|
+
|
|
124
|
+
Example:
|
|
125
|
+
>>> formatter = ErrorFormatter()
|
|
126
|
+
>>> formatter.format_validation_success("mci.json")
|
|
127
|
+
"""
|
|
128
|
+
self.console.print()
|
|
129
|
+
success_text = Text()
|
|
130
|
+
success_text.append("✅ Schema is valid!\n\n", style="bold green")
|
|
131
|
+
success_text.append(f"File: {file_path}", style="green")
|
|
132
|
+
|
|
133
|
+
panel = Panel(
|
|
134
|
+
success_text,
|
|
135
|
+
title="[bold green]Validation Successful",
|
|
136
|
+
border_style="green",
|
|
137
|
+
expand=False,
|
|
138
|
+
)
|
|
139
|
+
self.console.print(panel)
|
|
140
|
+
self.console.print()
|
|
141
|
+
|
|
142
|
+
def format_mci_error(self, error_message: str) -> None:
|
|
143
|
+
"""
|
|
144
|
+
Display an MCIClient error message.
|
|
145
|
+
|
|
146
|
+
Args:
|
|
147
|
+
error_message: Error message from MCIClient
|
|
148
|
+
|
|
149
|
+
Example:
|
|
150
|
+
>>> formatter = ErrorFormatter()
|
|
151
|
+
>>> formatter.format_mci_error("Failed to load schema: Invalid JSON")
|
|
152
|
+
"""
|
|
153
|
+
self.console.print()
|
|
154
|
+
error_text = Text()
|
|
155
|
+
error_text.append("❌ MCI Error\n\n", style="bold red")
|
|
156
|
+
error_text.append(error_message, style="red")
|
|
157
|
+
|
|
158
|
+
panel = Panel(
|
|
159
|
+
error_text,
|
|
160
|
+
title="[bold red]Error",
|
|
161
|
+
border_style="red",
|
|
162
|
+
expand=False,
|
|
163
|
+
)
|
|
164
|
+
self.console.print(panel)
|
|
165
|
+
self.console.print()
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
"""
|
|
2
|
+
error_handler.py - Error handling utilities for CLI
|
|
3
|
+
|
|
4
|
+
This module provides utilities for formatting errors from mci-py
|
|
5
|
+
in a CLI-friendly way, with helpful messages and suggestions.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from mcipy import MCIClientError
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ErrorHandler:
|
|
12
|
+
"""
|
|
13
|
+
Handles and formats errors for CLI display.
|
|
14
|
+
|
|
15
|
+
This class provides methods to format exceptions from mci-py
|
|
16
|
+
into user-friendly error messages suitable for terminal output.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
@staticmethod
|
|
20
|
+
def format_mci_client_error(error: MCIClientError) -> str:
|
|
21
|
+
"""
|
|
22
|
+
Format MCIClientError for CLI display.
|
|
23
|
+
|
|
24
|
+
Converts technical error messages from MCIClient into
|
|
25
|
+
user-friendly messages with helpful suggestions.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
error: MCIClientError exception from mci-py
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
Formatted error message string
|
|
32
|
+
|
|
33
|
+
Example:
|
|
34
|
+
>>> from mcipy import MCIClientError
|
|
35
|
+
>>> try:
|
|
36
|
+
... # Some MCIClient operation
|
|
37
|
+
... pass
|
|
38
|
+
... except MCIClientError as e:
|
|
39
|
+
... msg = ErrorHandler.format_mci_client_error(e)
|
|
40
|
+
... print(msg)
|
|
41
|
+
"""
|
|
42
|
+
error_str = str(error)
|
|
43
|
+
|
|
44
|
+
# Check for common error patterns and provide helpful messages
|
|
45
|
+
if "No such file or directory" in error_str:
|
|
46
|
+
return (
|
|
47
|
+
f"❌ Schema file not found\n\n"
|
|
48
|
+
f"Error: {error_str}\n\n"
|
|
49
|
+
f"💡 Suggestions:\n"
|
|
50
|
+
f" • Check that the file path is correct\n"
|
|
51
|
+
f" • Run 'mcix install' to create a default mci.json file\n"
|
|
52
|
+
f" • Use --file option to specify a different schema file"
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
if "Unsupported file extension" in error_str:
|
|
56
|
+
return (
|
|
57
|
+
f"❌ Unsupported file format\n\n"
|
|
58
|
+
f"Error: {error_str}\n\n"
|
|
59
|
+
f"💡 Supported formats:\n"
|
|
60
|
+
f" • .json (JSON format)\n"
|
|
61
|
+
f" • .yaml or .yml (YAML format)"
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
if "Failed to load schema" in error_str:
|
|
65
|
+
# Try to extract the specific parse error
|
|
66
|
+
return (
|
|
67
|
+
f"❌ Failed to load schema\n\n"
|
|
68
|
+
f"Error: {error_str}\n\n"
|
|
69
|
+
f"💡 Suggestions:\n"
|
|
70
|
+
f" • Check that the file contains valid JSON or YAML\n"
|
|
71
|
+
f" • Run 'mcix validate' to see detailed validation errors\n"
|
|
72
|
+
f" • Check for syntax errors like missing commas or brackets"
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
if "Tool not found" in error_str:
|
|
76
|
+
return (
|
|
77
|
+
f"❌ Tool not found\n\n"
|
|
78
|
+
f"Error: {error_str}\n\n"
|
|
79
|
+
f"💡 Suggestions:\n"
|
|
80
|
+
f" • Run 'mcix list' to see all available tools\n"
|
|
81
|
+
f" • Check the tool name for typos\n"
|
|
82
|
+
f" • Ensure the tool is defined in your schema"
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
if "Template variable not found" in error_str:
|
|
86
|
+
return (
|
|
87
|
+
f"❌ Missing template variable\n\n"
|
|
88
|
+
f"Error: {error_str}\n\n"
|
|
89
|
+
f"💡 Suggestions:\n"
|
|
90
|
+
f" • Set required environment variables before running the tool\n"
|
|
91
|
+
f" • Check your schema for {{{{env.VARIABLE}}}} placeholders\n"
|
|
92
|
+
f" • Use --env option to provide environment variables"
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
if "validation" in error_str.lower() or "invalid" in error_str.lower():
|
|
96
|
+
return (
|
|
97
|
+
f"❌ Schema validation error\n\n"
|
|
98
|
+
f"Error: {error_str}\n\n"
|
|
99
|
+
f"💡 Suggestions:\n"
|
|
100
|
+
f" • Run 'mcix validate' for detailed validation errors\n"
|
|
101
|
+
f" • Check that all required fields are present\n"
|
|
102
|
+
f" • Verify that field types match the schema specification"
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
# Default formatting for other errors
|
|
106
|
+
return f"❌ Error\n\n{error_str}"
|
|
107
|
+
|
|
108
|
+
@staticmethod
|
|
109
|
+
def format_generic_error(error: Exception) -> str:
|
|
110
|
+
"""
|
|
111
|
+
Format a generic exception for CLI display.
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
error: Any Python exception
|
|
115
|
+
|
|
116
|
+
Returns:
|
|
117
|
+
Formatted error message string
|
|
118
|
+
|
|
119
|
+
Example:
|
|
120
|
+
>>> try:
|
|
121
|
+
... # Some operation
|
|
122
|
+
... pass
|
|
123
|
+
... except Exception as e:
|
|
124
|
+
... msg = ErrorHandler.format_generic_error(e)
|
|
125
|
+
... print(msg)
|
|
126
|
+
"""
|
|
127
|
+
error_str = str(error)
|
|
128
|
+
error_type = type(error).__name__
|
|
129
|
+
|
|
130
|
+
return f"❌ {error_type}\n\n{error_str}"
|
|
131
|
+
|
|
132
|
+
@staticmethod
|
|
133
|
+
def format_file_not_found_error(file_path: str) -> str:
|
|
134
|
+
"""
|
|
135
|
+
Format a file not found error with helpful suggestions.
|
|
136
|
+
|
|
137
|
+
Args:
|
|
138
|
+
file_path: Path to the file that was not found
|
|
139
|
+
|
|
140
|
+
Returns:
|
|
141
|
+
Formatted error message string
|
|
142
|
+
|
|
143
|
+
Example:
|
|
144
|
+
>>> msg = ErrorHandler.format_file_not_found_error("mci.json")
|
|
145
|
+
>>> print(msg)
|
|
146
|
+
"""
|
|
147
|
+
return (
|
|
148
|
+
f"❌ File not found: {file_path}\n\n"
|
|
149
|
+
f"💡 Suggestions:\n"
|
|
150
|
+
f" • Run 'mcix install' to create a default mci.json file\n"
|
|
151
|
+
f" • Check that you're in the correct directory\n"
|
|
152
|
+
f" • Use --file option to specify a different schema file"
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
@staticmethod
|
|
156
|
+
def format_validation_error(message: str) -> str:
|
|
157
|
+
"""
|
|
158
|
+
Format a validation error message.
|
|
159
|
+
|
|
160
|
+
Args:
|
|
161
|
+
message: Validation error message
|
|
162
|
+
|
|
163
|
+
Returns:
|
|
164
|
+
Formatted error message string
|
|
165
|
+
|
|
166
|
+
Example:
|
|
167
|
+
>>> msg = ErrorHandler.format_validation_error("Missing required field: name")
|
|
168
|
+
>>> print(msg)
|
|
169
|
+
"""
|
|
170
|
+
return (
|
|
171
|
+
f"❌ Validation Error\n\n"
|
|
172
|
+
f"{message}\n\n"
|
|
173
|
+
f"💡 Run 'mcix validate' for detailed validation information"
|
|
174
|
+
)
|
mci/utils/timestamp.py
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""
|
|
2
|
+
timestamp.py - Timestamp utilities for file output
|
|
3
|
+
|
|
4
|
+
This module provides utilities for generating timestamped filenames
|
|
5
|
+
and ISO 8601 timestamps for use in CLI output files.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from datetime import UTC, datetime
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def generate_timestamp_filename(format: str, prefix: str = "tools") -> str:
|
|
12
|
+
"""
|
|
13
|
+
Generate a timestamped filename for output files.
|
|
14
|
+
|
|
15
|
+
Creates a filename in the format: {prefix}_YYYYMMDD_HHMMSS.{format}
|
|
16
|
+
Uses UTC time for consistency.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
format: File extension (e.g., "json", "yaml")
|
|
20
|
+
prefix: Filename prefix (default: "tools")
|
|
21
|
+
|
|
22
|
+
Returns:
|
|
23
|
+
Timestamped filename string (e.g., "tools_20241029_143022.json")
|
|
24
|
+
|
|
25
|
+
Example:
|
|
26
|
+
>>> filename = generate_timestamp_filename("json")
|
|
27
|
+
>>> print(filename)
|
|
28
|
+
tools_20241029_143022.json
|
|
29
|
+
"""
|
|
30
|
+
now = datetime.now(UTC)
|
|
31
|
+
timestamp = now.strftime("%Y%m%d_%H%M%S")
|
|
32
|
+
return f"{prefix}_{timestamp}.{format}"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def get_iso_timestamp() -> str:
|
|
36
|
+
"""
|
|
37
|
+
Get current timestamp in ISO 8601 format.
|
|
38
|
+
|
|
39
|
+
Returns UTC timestamp in ISO 8601 format for use in metadata.
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
ISO 8601 formatted timestamp string
|
|
43
|
+
|
|
44
|
+
Example:
|
|
45
|
+
>>> timestamp = get_iso_timestamp()
|
|
46
|
+
>>> print(timestamp)
|
|
47
|
+
2024-10-29T14:30:22Z
|
|
48
|
+
"""
|
|
49
|
+
now = datetime.now(UTC)
|
|
50
|
+
return now.strftime("%Y-%m-%dT%H:%M:%SZ")
|