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.
Files changed (42) hide show
  1. iflow_mcp_modelcontextinterface_mcix-1.1.1.dev0.dist-info/METADATA +931 -0
  2. iflow_mcp_modelcontextinterface_mcix-1.1.1.dev0.dist-info/RECORD +42 -0
  3. iflow_mcp_modelcontextinterface_mcix-1.1.1.dev0.dist-info/WHEEL +4 -0
  4. iflow_mcp_modelcontextinterface_mcix-1.1.1.dev0.dist-info/entry_points.txt +2 -0
  5. iflow_mcp_modelcontextinterface_mcix-1.1.1.dev0.dist-info/licenses/LICENSE +21 -0
  6. mci/__init__.py +10 -0
  7. mci/assets/example_toolset.mci.json +37 -0
  8. mci/assets/example_toolset.mci.yaml +23 -0
  9. mci/assets/gitignore +1 -0
  10. mci/assets/mci.json +29 -0
  11. mci/assets/mci.yaml +19 -0
  12. mci/cli/__init__.py +8 -0
  13. mci/cli/add.py +108 -0
  14. mci/cli/envs.py +257 -0
  15. mci/cli/formatters/__init__.py +12 -0
  16. mci/cli/formatters/env_formatter.py +83 -0
  17. mci/cli/formatters/json_formatter.py +93 -0
  18. mci/cli/formatters/table_formatter.py +138 -0
  19. mci/cli/formatters/yaml_formatter.py +93 -0
  20. mci/cli/install.py +147 -0
  21. mci/cli/list.py +153 -0
  22. mci/cli/run.py +125 -0
  23. mci/cli/validate.py +113 -0
  24. mci/core/__init__.py +8 -0
  25. mci/core/config.py +144 -0
  26. mci/core/dynamic_server.py +187 -0
  27. mci/core/file_finder.py +105 -0
  28. mci/core/mci_client.py +196 -0
  29. mci/core/mcp_server.py +240 -0
  30. mci/core/schema_editor.py +284 -0
  31. mci/core/tool_converter.py +119 -0
  32. mci/core/tool_manager.py +118 -0
  33. mci/core/validator.py +162 -0
  34. mci/mci.py +39 -0
  35. mci/py.typed +0 -0
  36. mci/utils/__init__.py +8 -0
  37. mci/utils/dotenv.py +170 -0
  38. mci/utils/env_scanner.py +84 -0
  39. mci/utils/error_formatter.py +165 -0
  40. mci/utils/error_handler.py +174 -0
  41. mci/utils/timestamp.py +50 -0
  42. mci/utils/validation.py +92 -0
@@ -0,0 +1,93 @@
1
+ """
2
+ json_formatter.py - JSON formatter for file output
3
+
4
+ This module provides formatting for outputting tools to JSON files
5
+ with metadata including timestamp, source file, filters, and tool count.
6
+ """
7
+
8
+ import json
9
+ from typing import Any
10
+
11
+ from mcipy.models import Tool
12
+
13
+ from mci.utils.timestamp import generate_timestamp_filename, get_iso_timestamp
14
+
15
+
16
+ class JSONFormatter:
17
+ """
18
+ Formats tool information as JSON files with metadata.
19
+
20
+ This class provides methods to format tool lists into JSON files
21
+ with timestamp, source file, filters applied, and total count.
22
+ """
23
+
24
+ @staticmethod
25
+ def format_to_file(
26
+ tools: list[Tool],
27
+ mci_file: str,
28
+ filters_applied: list[str] | None = None,
29
+ verbose: bool = False,
30
+ ) -> str:
31
+ """
32
+ Format tools to a JSON file and return the filename.
33
+
34
+ Creates a timestamped JSON file with tool data and metadata.
35
+ Filename format: tools_YYYYMMDD_HHMMSS.json
36
+
37
+ Args:
38
+ tools: List of Tool objects to format
39
+ mci_file: Path to the source MCI file
40
+ filters_applied: Optional list of filter specifications that were applied
41
+ verbose: Whether to include verbose tool metadata
42
+
43
+ Returns:
44
+ The filename of the created JSON file
45
+
46
+ Example:
47
+ >>> formatter = JSONFormatter()
48
+ >>> filename = formatter.format_to_file(tools, "mci.json", ["tags:api"])
49
+ >>> print(filename)
50
+ tools_20241029_143022.json
51
+ """
52
+ # Generate timestamped filename
53
+ filename = generate_timestamp_filename("json")
54
+
55
+ # Build output data structure
56
+ output_data: dict[str, Any] = {
57
+ "timestamp": get_iso_timestamp(),
58
+ "mci_file": mci_file,
59
+ "filters_applied": filters_applied or [],
60
+ "total": len(tools),
61
+ "tools": [],
62
+ }
63
+
64
+ # Format each tool
65
+ for tool in tools:
66
+ tool_data: dict[str, str | list[str] | dict[str, Any] | bool] = {
67
+ "name": tool.name,
68
+ "source": tool.toolset_source or "main",
69
+ "description": tool.description or "",
70
+ }
71
+
72
+ # Add verbose fields if requested
73
+ if verbose:
74
+ tool_data["tags"] = tool.tags
75
+ tool_data["execution_type"] = (
76
+ tool.execution.type.value
77
+ if hasattr(tool.execution.type, "value")
78
+ else str(tool.execution.type)
79
+ )
80
+
81
+ if tool.inputSchema:
82
+ tool_data["inputSchema"] = tool.inputSchema
83
+
84
+ if tool.disabled:
85
+ tool_data["disabled"] = tool.disabled
86
+
87
+ output_data["tools"].append(tool_data)
88
+
89
+ # Write to file
90
+ with open(filename, "w") as f:
91
+ json.dump(output_data, f, indent=2)
92
+
93
+ return filename
@@ -0,0 +1,138 @@
1
+ """
2
+ table_formatter.py - Rich table formatter for CLI output
3
+
4
+ This module provides formatting for displaying tools in Rich terminal tables
5
+ with support for both basic and verbose output modes.
6
+ """
7
+
8
+ from mcipy.models import Tool
9
+ from rich.markup import escape
10
+ from rich.table import Table
11
+
12
+
13
+ class TableFormatter:
14
+ """
15
+ Formats tool information as Rich terminal tables.
16
+
17
+ This class provides methods to format tool lists into beautiful
18
+ terminal tables using the Rich library, with support for both
19
+ basic and verbose output modes.
20
+ """
21
+
22
+ @staticmethod
23
+ def format(tools: list[Tool], verbose: bool = False) -> Table | list[str]:
24
+ """
25
+ Format tools as a Rich table.
26
+
27
+ Args:
28
+ tools: List of Tool objects to format
29
+ verbose: Whether to show verbose output with additional metadata
30
+
31
+ Returns:
32
+ Rich Table object (basic mode) or list of Rich markup strings (verbose mode)
33
+
34
+ Example:
35
+ >>> formatter = TableFormatter()
36
+ >>> output = formatter.format(tools, verbose=False)
37
+ >>> print(output)
38
+ """
39
+ if verbose:
40
+ return TableFormatter.format_verbose(tools)
41
+ else:
42
+ return TableFormatter.format_basic(tools)
43
+
44
+ @staticmethod
45
+ def format_basic(tools: list[Tool]) -> Table:
46
+ """
47
+ Format tools in basic table mode.
48
+
49
+ Displays a table with columns: Name, Source, Description
50
+
51
+ Args:
52
+ tools: List of Tool objects to format
53
+
54
+ Returns:
55
+ Rich Table object ready for rendering
56
+ """
57
+ # Create table
58
+ table = Table(
59
+ title=f"🧩 Available Tools ({len(tools)})",
60
+ show_header=True,
61
+ header_style="bold cyan",
62
+ )
63
+
64
+ table.add_column("Name", style="green", no_wrap=True)
65
+ table.add_column("Source", style="blue")
66
+ table.add_column("Description", style="white")
67
+
68
+ # Add rows
69
+ for tool in tools:
70
+ name = tool.name
71
+ source = tool.toolset_source or "main"
72
+ description = tool.description or ""
73
+
74
+ table.add_row(name, source, description)
75
+
76
+ return table
77
+
78
+ @staticmethod
79
+ def format_verbose(tools: list[Tool]) -> list[str]:
80
+ """
81
+ Format tools in verbose mode with detailed information.
82
+
83
+ Shows detailed information for each tool including tags,
84
+ parameters, execution type, and other metadata.
85
+
86
+ Args:
87
+ tools: List of Tool objects to format
88
+
89
+ Returns:
90
+ List of formatted output lines with Rich markup
91
+ """
92
+ output_lines: list[str] = []
93
+
94
+ output_lines.append(f"🧩 Available Tools ({len(tools)}):\n")
95
+
96
+ for tool in tools:
97
+ # Tool header
98
+ source = tool.toolset_source or "main"
99
+ output_lines.append(f"[bold green]{tool.name}[/bold green] [blue]({source})[/blue]")
100
+
101
+ # Description
102
+ if tool.description:
103
+ output_lines.append(f"├── Description: {tool.description}")
104
+
105
+ # Tags
106
+ if tool.tags:
107
+ tags_str = ", ".join(tool.tags)
108
+ # Escape the brackets and content to prevent Rich markup interpretation
109
+ output_lines.append(f"├── Tags: {escape(f'[{tags_str}]')}")
110
+
111
+ # Execution type
112
+ execution_type = (
113
+ tool.execution.type.value
114
+ if hasattr(tool.execution.type, "value")
115
+ else str(tool.execution.type)
116
+ )
117
+ output_lines.append(f"├── Execution: {execution_type}")
118
+
119
+ # Parameters from inputSchema
120
+ if tool.inputSchema and "properties" in tool.inputSchema:
121
+ params = tool.inputSchema["properties"]
122
+ required = tool.inputSchema.get("required", [])
123
+
124
+ param_strs = []
125
+ for param_name, param_def in params.items():
126
+ param_type = param_def.get("type", "any")
127
+ is_required = param_name in required
128
+ req_indicator = "" if is_required else " (optional)"
129
+ param_strs.append(f"{param_name} ({param_type}){req_indicator}")
130
+
131
+ if param_strs:
132
+ output_lines.append(f"└── Parameters: {', '.join(param_strs)}")
133
+ else:
134
+ output_lines.append("└── Parameters: none")
135
+
136
+ output_lines.append("") # Empty line between tools
137
+
138
+ return output_lines
@@ -0,0 +1,93 @@
1
+ """
2
+ yaml_formatter.py - YAML formatter for file output
3
+
4
+ This module provides formatting for outputting tools to YAML files
5
+ with metadata including timestamp, source file, filters, and tool count.
6
+ """
7
+
8
+ from typing import Any
9
+
10
+ import yaml
11
+ from mcipy.models import Tool
12
+
13
+ from mci.utils.timestamp import generate_timestamp_filename, get_iso_timestamp
14
+
15
+
16
+ class YAMLFormatter:
17
+ """
18
+ Formats tool information as YAML files with metadata.
19
+
20
+ This class provides methods to format tool lists into YAML files
21
+ with timestamp, source file, filters applied, and total count.
22
+ """
23
+
24
+ @staticmethod
25
+ def format_to_file(
26
+ tools: list[Tool],
27
+ mci_file: str,
28
+ filters_applied: list[str] | None = None,
29
+ verbose: bool = False,
30
+ ) -> str:
31
+ """
32
+ Format tools to a YAML file and return the filename.
33
+
34
+ Creates a timestamped YAML file with tool data and metadata.
35
+ Filename format: tools_YYYYMMDD_HHMMSS.yaml
36
+
37
+ Args:
38
+ tools: List of Tool objects to format
39
+ mci_file: Path to the source MCI file
40
+ filters_applied: Optional list of filter specifications that were applied
41
+ verbose: Whether to include verbose tool metadata
42
+
43
+ Returns:
44
+ The filename of the created YAML file
45
+
46
+ Example:
47
+ >>> formatter = YAMLFormatter()
48
+ >>> filename = formatter.format_to_file(tools, "mci.json", ["tags:api"])
49
+ >>> print(filename)
50
+ tools_20241029_143022.yaml
51
+ """
52
+ # Generate timestamped filename
53
+ filename = generate_timestamp_filename("yaml")
54
+
55
+ # Build output data structure
56
+ output_data: dict[str, Any] = {
57
+ "timestamp": get_iso_timestamp(),
58
+ "mci_file": mci_file,
59
+ "filters_applied": filters_applied or [],
60
+ "total": len(tools),
61
+ "tools": [],
62
+ }
63
+
64
+ # Format each tool
65
+ for tool in tools:
66
+ tool_data: dict[str, str | list[str] | dict[str, Any] | bool] = {
67
+ "name": tool.name,
68
+ "source": tool.toolset_source or "main",
69
+ "description": tool.description or "",
70
+ }
71
+
72
+ # Add verbose fields if requested
73
+ if verbose:
74
+ tool_data["tags"] = tool.tags
75
+ tool_data["execution_type"] = (
76
+ tool.execution.type.value
77
+ if hasattr(tool.execution.type, "value")
78
+ else str(tool.execution.type)
79
+ )
80
+
81
+ if tool.inputSchema:
82
+ tool_data["inputSchema"] = tool.inputSchema
83
+
84
+ if tool.disabled:
85
+ tool_data["disabled"] = tool.disabled
86
+
87
+ output_data["tools"].append(tool_data)
88
+
89
+ # Write to file
90
+ with open(filename, "w") as f:
91
+ yaml.dump(output_data, f, default_flow_style=False, sort_keys=False)
92
+
93
+ return filename
mci/cli/install.py ADDED
@@ -0,0 +1,147 @@
1
+ """
2
+ install.py - CLI command for initializing MCI project structure
3
+
4
+ This module provides the 'install' command which creates a new MCI project
5
+ by copying template files and setting up the directory structure.
6
+ """
7
+
8
+ from importlib.resources import files
9
+ from pathlib import Path
10
+
11
+ import click
12
+
13
+
14
+ def copy_asset(resource_name: str, dest_path: Path, overwrite: bool = False) -> bool:
15
+ """
16
+ Copy an asset file from the package to a destination path.
17
+
18
+ Args:
19
+ resource_name: Name of the asset file in src/mci/assets/
20
+ dest_path: Destination path to copy the file to
21
+ overwrite: Whether to overwrite existing files (default: False)
22
+
23
+ Returns:
24
+ True if file was copied, False if it already existed and overwrite=False
25
+ """
26
+ if dest_path.exists() and not overwrite:
27
+ return False
28
+
29
+ # Get the asset file content using importlib.resources
30
+ asset_files = files("mci.assets")
31
+ asset_file = asset_files.joinpath(resource_name)
32
+
33
+ # Read the asset content and write to destination
34
+ content = asset_file.read_text()
35
+ dest_path.parent.mkdir(parents=True, exist_ok=True)
36
+ dest_path.write_text(content)
37
+
38
+ return True
39
+
40
+
41
+ def create_mci_file(format: str = "json") -> None:
42
+ """
43
+ Create the main MCI configuration file (mci.json or mci.yaml).
44
+
45
+ Args:
46
+ format: File format, either "json" or "yaml"
47
+ """
48
+ if format == "yaml":
49
+ filename = "mci.yaml"
50
+ asset_name = "mci.yaml"
51
+ else:
52
+ filename = "mci.json"
53
+ asset_name = "mci.json"
54
+
55
+ dest_path = Path.cwd() / filename
56
+
57
+ if copy_asset(asset_name, dest_path):
58
+ click.echo(f"✓ Created {filename}")
59
+ else:
60
+ click.echo(f"⚠ {filename} already exists, skipping")
61
+
62
+
63
+ def create_mci_directory() -> None:
64
+ """
65
+ Create the ./mci/ directory and update .gitignore.
66
+ """
67
+ mci_dir = Path.cwd() / "mci"
68
+ mci_dir.mkdir(exist_ok=True)
69
+ click.echo("✓ Created ./mci/ directory")
70
+
71
+ # Create or update .gitignore
72
+ gitignore_path = mci_dir / ".gitignore"
73
+
74
+ if gitignore_path.exists():
75
+ # Check if 'mcp/' already exists in .gitignore
76
+ content = gitignore_path.read_text()
77
+ if "mcp/" not in content:
78
+ # Append mcp/ entry
79
+ with gitignore_path.open("a") as f:
80
+ if not content.endswith("\n"):
81
+ f.write("\n")
82
+ f.write("mcp/\n")
83
+ click.echo("✓ Updated ./mci/.gitignore with mcp/ entry")
84
+ else:
85
+ click.echo("⚠ ./mci/.gitignore already contains mcp/ entry, skipping")
86
+ else:
87
+ # Create new .gitignore from template
88
+ copy_asset("gitignore", gitignore_path, overwrite=True)
89
+ click.echo("✓ Created ./mci/.gitignore")
90
+
91
+
92
+ def create_example_toolset(format: str = "json") -> None:
93
+ """
94
+ Create the example toolset file in ./mci/ directory.
95
+
96
+ Args:
97
+ format: File format, either "json" or "yaml"
98
+ """
99
+ if format == "yaml":
100
+ filename = "example_toolset.mci.yaml"
101
+ asset_name = "example_toolset.mci.yaml"
102
+ else:
103
+ filename = "example_toolset.mci.json"
104
+ asset_name = "example_toolset.mci.json"
105
+
106
+ dest_path = Path.cwd() / "mci" / filename
107
+
108
+ if copy_asset(asset_name, dest_path):
109
+ click.echo(f"✓ Created ./mci/{filename}")
110
+ else:
111
+ click.echo(f"⚠ ./mci/{filename} already exists, skipping")
112
+
113
+
114
+ @click.command()
115
+ @click.option(
116
+ "--yaml",
117
+ is_flag=True,
118
+ help="Create YAML configuration file instead of JSON",
119
+ )
120
+ def install(yaml: bool):
121
+ """
122
+ Initialize an MCI project structure.
123
+
124
+ Creates mci.json (or mci.yaml with --yaml flag), ./mci/ directory,
125
+ example toolset file, and .gitignore configuration.
126
+ """
127
+ click.echo("Initializing MCI project...")
128
+ click.echo()
129
+
130
+ format = "yaml" if yaml else "json"
131
+
132
+ # Create main configuration file
133
+ create_mci_file(format=format)
134
+
135
+ # Create ./mci directory and .gitignore
136
+ create_mci_directory()
137
+
138
+ # Create example toolset
139
+ create_example_toolset(format=format)
140
+
141
+ click.echo()
142
+ click.echo("✓ MCI project initialized successfully!")
143
+ click.echo()
144
+ click.echo("Next steps:")
145
+ click.echo(" 1. Review the generated configuration files")
146
+ click.echo(" 2. Run 'mcix list' to see available tools")
147
+ click.echo(" 3. Run 'mcix validate' to check your configuration")
mci/cli/list.py ADDED
@@ -0,0 +1,153 @@
1
+ """
2
+ list.py - List command for displaying available tools
3
+
4
+ This module implements the `list` command for the MCI CLI, which displays
5
+ all available tools in the current MCI configuration with support for
6
+ filtering, multiple output formats, and verbose mode.
7
+ """
8
+
9
+ import click
10
+ from mcipy import MCIClientError
11
+ from rich.console import Console
12
+
13
+ from mci.cli.formatters import JSONFormatter, TableFormatter, YAMLFormatter
14
+ from mci.core.file_finder import MCIFileFinder
15
+ from mci.core.mci_client import MCIClientWrapper
16
+ from mci.core.tool_manager import ToolManager
17
+ from mci.utils.error_handler import ErrorHandler
18
+
19
+
20
+ @click.command()
21
+ @click.option(
22
+ "--file",
23
+ "-f",
24
+ type=click.Path(exists=True),
25
+ default=None,
26
+ help="Path to MCI schema file (defaults to mci.json or mci.yaml in current directory)",
27
+ )
28
+ @click.option(
29
+ "--filter",
30
+ type=str,
31
+ default=None,
32
+ help="Filter tools (format: type:value1,value2 - e.g., tags:api,database)",
33
+ )
34
+ @click.option(
35
+ "--format",
36
+ type=click.Choice(["table", "json", "yaml"], case_sensitive=False),
37
+ default="table",
38
+ help="Output format (default: table)",
39
+ )
40
+ @click.option(
41
+ "--verbose",
42
+ "-v",
43
+ is_flag=True,
44
+ help="Show detailed tool information including tags, parameters, etc.",
45
+ )
46
+ def list_command(file: str | None, filter: str | None, format: str, verbose: bool):
47
+ """
48
+ List available tools from the MCI configuration.
49
+
50
+ Displays all tools defined in the MCI schema file, with support for
51
+ filtering, multiple output formats (table, JSON, YAML), and verbose mode.
52
+
53
+ The list command uses the same tool loading and filtering logic as the
54
+ run command, ensuring consistency between what is listed and what will
55
+ actually be available when running the MCP server.
56
+
57
+ Examples:
58
+
59
+ # List all tools in table format
60
+ mcix list
61
+
62
+ # List tools from specific file
63
+ mcix list --file=./custom.mci.json
64
+
65
+ # List tools with filter
66
+ mcix list --filter=tags:api,database
67
+
68
+ # List tools with verbose output
69
+ mcix list --verbose
70
+
71
+ # Export tools to JSON file
72
+ mcix list --format=json
73
+
74
+ # Export tools to YAML file with verbose info
75
+ mcix list --format=yaml --verbose
76
+ """
77
+ console = Console()
78
+
79
+ try:
80
+ # Find MCI file
81
+ if file is None:
82
+ finder = MCIFileFinder()
83
+ file = finder.find_mci_file()
84
+ if file is None:
85
+ console.print(
86
+ "[red]✗[/red] No MCI schema file found. "
87
+ "Run 'mcix install' to create one or specify --file.",
88
+ style="red",
89
+ )
90
+ raise click.Abort()
91
+
92
+ # Load schema using MCIClientWrapper
93
+ try:
94
+ client = MCIClientWrapper(file)
95
+ except MCIClientError as e:
96
+ console.print(ErrorHandler.format_mci_client_error(e))
97
+ raise click.Abort() from e
98
+ except Exception as e:
99
+ console.print(ErrorHandler.format_generic_error(e))
100
+ raise click.Abort() from e
101
+
102
+ # Get tools (with or without filter)
103
+ if filter:
104
+ try:
105
+ tools = ToolManager.apply_filter_spec(client, filter)
106
+ except ValueError as e:
107
+ console.print(f"[red]✗[/red] Invalid filter: {e}", style="red")
108
+ raise click.Abort() from e
109
+ else:
110
+ tools = client.get_tools()
111
+
112
+ # Format and display output
113
+ if format == "table":
114
+ # Display table to console
115
+ output = TableFormatter.format(tools, verbose=verbose)
116
+ if isinstance(output, list):
117
+ # Verbose mode returns list of Rich markup strings
118
+ for line in output:
119
+ console.print(line)
120
+ else:
121
+ # Basic mode returns a Table object
122
+ console.print(output)
123
+
124
+ elif format == "json":
125
+ # Write to JSON file
126
+ filters_applied = [filter] if filter else []
127
+ filename = JSONFormatter.format_to_file(
128
+ tools=tools,
129
+ mci_file=file,
130
+ filters_applied=filters_applied,
131
+ verbose=verbose,
132
+ )
133
+ console.print(f"[green]✓[/green] Tools exported to: {filename}")
134
+
135
+ elif format == "yaml":
136
+ # Write to YAML file
137
+ filters_applied = [filter] if filter else []
138
+ filename = YAMLFormatter.format_to_file(
139
+ tools=tools,
140
+ mci_file=file,
141
+ filters_applied=filters_applied,
142
+ verbose=verbose,
143
+ )
144
+ console.print(f"[green]✓[/green] Tools exported to: {filename}")
145
+
146
+ except click.Abort:
147
+ raise
148
+ except MCIClientError as e:
149
+ console.print(ErrorHandler.format_mci_client_error(e))
150
+ raise click.Abort() from e
151
+ except Exception as e:
152
+ console.print(ErrorHandler.format_generic_error(e))
153
+ raise click.Abort() from e