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
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
mci/__init__.py,sha256=nuemO6CcSJn9t-0Ah57JVcbIkzSFEZPjYWTfHC3nbkI,204
|
|
2
|
+
mci/mci.py,sha256=Xw3ibISXDPtLaz3RGMk1WqPODbByyijRXceXOXWUNIw,909
|
|
3
|
+
mci/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
+
mci/assets/example_toolset.mci.json,sha256=oNxS_QxfScOJWjBr3JmNeNGoScquDPzZ8zBVh-qr7qo,770
|
|
5
|
+
mci/assets/example_toolset.mci.yaml,sha256=WS8pIf4t5VIEbp0aWLeGw0S31KwgGHBY7RuaYAluLbw,541
|
|
6
|
+
mci/assets/gitignore,sha256=l6yi4sEzheFyEHHK6oHzm9nP2DIZjrwXfMVOIGsjxG0,5
|
|
7
|
+
mci/assets/mci.json,sha256=GgFiQJ3EtL7NVtMVwoleFKU-H71WCEEYXY283s4WIYk,600
|
|
8
|
+
mci/assets/mci.yaml,sha256=B95JmmzjUgYOSW2s6AFWMkQo9jc6dX7LGhZ33QRkSgk,420
|
|
9
|
+
mci/cli/__init__.py,sha256=N6bvmpH2Wrn5HXELYEQ7H7QDmAknL0ih_WCUfcWDvCs,190
|
|
10
|
+
mci/cli/add.py,sha256=6FeuGQTLZs23A8_ZReJryk7LAPey-L8z4ZVEKPQVbIQ,3217
|
|
11
|
+
mci/cli/envs.py,sha256=WTgkszkdg5-B5JMZ7gDA4TgU-H7z7Ov6RpH2d2pyQyg,8860
|
|
12
|
+
mci/cli/install.py,sha256=NDQnNvgzPJlSQnQ_RLnVy19dIonEYbJEvZoxYidw08o,4359
|
|
13
|
+
mci/cli/list.py,sha256=o7hyb9iNZ9fKO7vKLY4HhpaVcC_n8xuwSO6WHVOw3O4,5018
|
|
14
|
+
mci/cli/run.py,sha256=0a1Vj413_XzCgtX0Mky1-K6uIQgaYC2ZMv1BIsJjJQk,4101
|
|
15
|
+
mci/cli/validate.py,sha256=qit0lpz0YkGAArPUCrQA1_vOtPQhlrll3IvCDXq1hcM,3409
|
|
16
|
+
mci/cli/formatters/__init__.py,sha256=mijJRgrkNYkoLHztNUM3ezskwmhOZtHCGogFjxW7fq4,435
|
|
17
|
+
mci/cli/formatters/env_formatter.py,sha256=zuL76kTzV7Kph58QvTlQNpAlxYhOmn-tfg70z9E5n9Y,2674
|
|
18
|
+
mci/cli/formatters/json_formatter.py,sha256=Sopuw7eYP-0eakUlat_5o1F82AkbUAKgzA_qwOOvNjE,2919
|
|
19
|
+
mci/cli/formatters/table_formatter.py,sha256=kV73T5phFlLe468X4PAfpTiFvzKMZq6iKri1ZIJ70Gc,4541
|
|
20
|
+
mci/cli/formatters/yaml_formatter.py,sha256=0_G2jE2zIXnun7mlU9cQzUUsztqZMhUVkqt22tiCohw,2952
|
|
21
|
+
mci/core/__init__.py,sha256=nuCREJL3nCOQi5IQKVyL1swl_xrz7QPdUOO6Ju_K2Vc,187
|
|
22
|
+
mci/core/config.py,sha256=3oiJz9bjfympXZsDwisZZrKLY1hb_1rb7ERGil3YOYM,5869
|
|
23
|
+
mci/core/dynamic_server.py,sha256=5nnTO8LadRJdjCA4zRJ0qZIHBXFTY7TSX3JLZByECXU,6977
|
|
24
|
+
mci/core/file_finder.py,sha256=nB0TVV5kSqYzVp04KCE_q0n4xabudowFWNQkvQ7H0pg,3226
|
|
25
|
+
mci/core/mci_client.py,sha256=k8lTBcEbw830TcQEEWI3PiD7-WgugOY6y6ZxZnZU8kE,6797
|
|
26
|
+
mci/core/mcp_server.py,sha256=csIkKv9bMaecAR2hu-7p_Jht3QHZs6xPIqiSVTI5YR4,8453
|
|
27
|
+
mci/core/schema_editor.py,sha256=uYrHgcdz3vLagjNjKxPuVt1Rt615NLYgMQCo86xhZM4,10065
|
|
28
|
+
mci/core/tool_converter.py,sha256=xRluMABCB9whdy_Y0ncug9FpbumnpkxC7_9_n2KN2S4,4491
|
|
29
|
+
mci/core/tool_manager.py,sha256=eAggVfC6sAxwYhAfkNbK-FnJBpySRvOEx5GGMLLyZF4,4438
|
|
30
|
+
mci/core/validator.py,sha256=7b5mt2uP0ko2nUWGHStB4sKNicTRrwJEMPcx0CBEYmo,5746
|
|
31
|
+
mci/utils/__init__.py,sha256=b_nmqu4_AAAq3D5p9njpkXj8y9oU_-MyLMFBYhQmAII,179
|
|
32
|
+
mci/utils/dotenv.py,sha256=XBUhGcQtHQ8hJFoM9NlY0bxTMDUqqkI7zdzCYe73s4w,6181
|
|
33
|
+
mci/utils/env_scanner.py,sha256=7W00ov1DuWaxD6j6M56pXb0SqQdYaA_EeCSAzyVt77Q,2599
|
|
34
|
+
mci/utils/error_formatter.py,sha256=TOs6CT3SAcBWUycjn1er6Tzp6kJkAB_5u_vYfZ5VRJw,5093
|
|
35
|
+
mci/utils/error_handler.py,sha256=ybv_JpHDTqW8Zmm4Vv2MyQGzJ20T1hMbe7D5UwM4nCw,5899
|
|
36
|
+
mci/utils/timestamp.py,sha256=AxRpVv9cTQByduDOJYEYtRZl2XGdrAtCZtdvb2oQXP0,1341
|
|
37
|
+
mci/utils/validation.py,sha256=qKI3tn8oZzDRVP0qrMhPnMJSYjEi2XTj_1b28Ly0WG8,1806
|
|
38
|
+
iflow_mcp_modelcontextinterface_mcix-1.1.1.dev0.dist-info/METADATA,sha256=u1tAv0pcLi-YrUK4VMP0DZShVRtTqTvLscjOPIShJC8,25157
|
|
39
|
+
iflow_mcp_modelcontextinterface_mcix-1.1.1.dev0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
40
|
+
iflow_mcp_modelcontextinterface_mcix-1.1.1.dev0.dist-info/entry_points.txt,sha256=5d6RSblyFS_5gbD524eS-tJEUzQQ8C2mUugZVnkTKbU,34
|
|
41
|
+
iflow_mcp_modelcontextinterface_mcix-1.1.1.dev0.dist-info/licenses/LICENSE,sha256=tMFywLM9365292nkpvJgJTcleQn_ABNSELN5YMV2dzo,1069
|
|
42
|
+
iflow_mcp_modelcontextinterface_mcix-1.1.1.dev0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 maestroerror
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
mci/__init__.py
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": "1.0",
|
|
3
|
+
"metadata": {
|
|
4
|
+
"name": "Example Toolset",
|
|
5
|
+
"description": "Example MCI toolset with CLI tool"
|
|
6
|
+
},
|
|
7
|
+
"tools": [
|
|
8
|
+
{
|
|
9
|
+
"name": "list_files",
|
|
10
|
+
"description": "List files in a directory",
|
|
11
|
+
"inputSchema": {
|
|
12
|
+
"type": "object",
|
|
13
|
+
"properties": {
|
|
14
|
+
"directory": {
|
|
15
|
+
"type": "string",
|
|
16
|
+
"description": "Directory to list files from"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"required": [
|
|
20
|
+
"directory"
|
|
21
|
+
]
|
|
22
|
+
},
|
|
23
|
+
"execution": {
|
|
24
|
+
"type": "cli",
|
|
25
|
+
"command": "ls",
|
|
26
|
+
"args": [
|
|
27
|
+
"-la",
|
|
28
|
+
"{{props.directory}}"
|
|
29
|
+
]
|
|
30
|
+
},
|
|
31
|
+
"enableAnyPaths": false,
|
|
32
|
+
"directoryAllowList": [
|
|
33
|
+
"{{env.PROJECT_ROOT}}"
|
|
34
|
+
]
|
|
35
|
+
}
|
|
36
|
+
]
|
|
37
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
schemaVersion: '1.0'
|
|
2
|
+
metadata:
|
|
3
|
+
name: Example Toolset
|
|
4
|
+
description: Example MCI toolset with CLI tool
|
|
5
|
+
tools:
|
|
6
|
+
- name: list_files
|
|
7
|
+
description: List files in a directory
|
|
8
|
+
inputSchema:
|
|
9
|
+
type: object
|
|
10
|
+
properties:
|
|
11
|
+
directory:
|
|
12
|
+
type: string
|
|
13
|
+
description: Directory to list files from
|
|
14
|
+
required: [directory]
|
|
15
|
+
execution:
|
|
16
|
+
type: cli
|
|
17
|
+
command: ls
|
|
18
|
+
args:
|
|
19
|
+
- '-la'
|
|
20
|
+
- '{{props.directory}}'
|
|
21
|
+
enableAnyPaths: false
|
|
22
|
+
directoryAllowList:
|
|
23
|
+
- '{{env.PROJECT_ROOT}}'
|
mci/assets/gitignore
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
mcp/
|
mci/assets/mci.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": "1.0",
|
|
3
|
+
"metadata": {
|
|
4
|
+
"name": "Example Project",
|
|
5
|
+
"description": "Example MCI configuration"
|
|
6
|
+
},
|
|
7
|
+
"tools": [
|
|
8
|
+
{
|
|
9
|
+
"name": "echo_test",
|
|
10
|
+
"description": "Simple echo test tool",
|
|
11
|
+
"inputSchema": {
|
|
12
|
+
"type": "object",
|
|
13
|
+
"properties": {
|
|
14
|
+
"message": {
|
|
15
|
+
"type": "string",
|
|
16
|
+
"description": "Message to echo"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"required": ["message"]
|
|
20
|
+
},
|
|
21
|
+
"execution": {
|
|
22
|
+
"type": "text",
|
|
23
|
+
"text": "Echo: {{props.message}}"
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
],
|
|
27
|
+
"toolsets": [],
|
|
28
|
+
"mcp_servers": {}
|
|
29
|
+
}
|
mci/assets/mci.yaml
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
schemaVersion: '1.0'
|
|
2
|
+
metadata:
|
|
3
|
+
name: Example Project
|
|
4
|
+
description: Example MCI configuration
|
|
5
|
+
tools:
|
|
6
|
+
- name: echo_test
|
|
7
|
+
description: Simple echo test tool
|
|
8
|
+
inputSchema:
|
|
9
|
+
type: object
|
|
10
|
+
properties:
|
|
11
|
+
message:
|
|
12
|
+
type: string
|
|
13
|
+
description: Message to echo
|
|
14
|
+
required: [message]
|
|
15
|
+
execution:
|
|
16
|
+
type: text
|
|
17
|
+
text: 'Echo: {{props.message}}'
|
|
18
|
+
toolsets: []
|
|
19
|
+
mcp_servers: {}
|
mci/cli/__init__.py
ADDED
mci/cli/add.py
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"""
|
|
2
|
+
add.py - CLI command to add toolset references to MCI schemas
|
|
3
|
+
|
|
4
|
+
This module provides the `mcix add` command which adds toolset references
|
|
5
|
+
to MCI schema files with optional filtering, while preserving the original
|
|
6
|
+
file format (JSON or YAML).
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import sys
|
|
10
|
+
|
|
11
|
+
import click
|
|
12
|
+
from rich.console import Console
|
|
13
|
+
|
|
14
|
+
from mci.core.file_finder import MCIFileFinder
|
|
15
|
+
from mci.core.schema_editor import SchemaEditor, parse_add_filter
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@click.command()
|
|
19
|
+
@click.argument("toolset_name", type=str)
|
|
20
|
+
@click.option(
|
|
21
|
+
"--filter",
|
|
22
|
+
"-f",
|
|
23
|
+
default=None,
|
|
24
|
+
help='Filter specification (e.g., "only:Tool1,Tool2" or "tags:api,database")',
|
|
25
|
+
)
|
|
26
|
+
@click.option(
|
|
27
|
+
"--path",
|
|
28
|
+
"-p",
|
|
29
|
+
default=None,
|
|
30
|
+
help="Path to MCI schema file (default: auto-discover mci.json/mci.yaml)",
|
|
31
|
+
)
|
|
32
|
+
def add(toolset_name: str, filter: str | None, path: str | None) -> None:
|
|
33
|
+
"""
|
|
34
|
+
Add a toolset reference to an MCI schema file.
|
|
35
|
+
|
|
36
|
+
This command adds a toolset to the schema's toolsets array. If the toolset
|
|
37
|
+
already exists, it will be updated with the new filter (if provided).
|
|
38
|
+
The original file format (JSON or YAML) is preserved.
|
|
39
|
+
|
|
40
|
+
TOOLSET_NAME is the name of the toolset to add.
|
|
41
|
+
|
|
42
|
+
Examples:
|
|
43
|
+
|
|
44
|
+
\b
|
|
45
|
+
# Add a toolset without filter
|
|
46
|
+
mcix add weather-tools
|
|
47
|
+
|
|
48
|
+
\b
|
|
49
|
+
# Add a toolset with "only" filter
|
|
50
|
+
mcix add analytics --filter=only:Tool1,Tool2
|
|
51
|
+
|
|
52
|
+
\b
|
|
53
|
+
# Add a toolset with "tags" filter
|
|
54
|
+
mcix add api-tools --filter=tags:api,database
|
|
55
|
+
|
|
56
|
+
\b
|
|
57
|
+
# Add to a custom file
|
|
58
|
+
mcix add weather-tools --path=custom.mci.json
|
|
59
|
+
"""
|
|
60
|
+
console = Console()
|
|
61
|
+
|
|
62
|
+
# Find the schema file
|
|
63
|
+
if path is None:
|
|
64
|
+
file_finder = MCIFileFinder()
|
|
65
|
+
schema_file = file_finder.find_mci_file()
|
|
66
|
+
if schema_file is None:
|
|
67
|
+
console.print("[red]❌ No MCI schema file found in current directory[/red]\n")
|
|
68
|
+
console.print("[yellow]💡 Run 'mcix install' to create a default mci.json file[/yellow]")
|
|
69
|
+
sys.exit(1)
|
|
70
|
+
else:
|
|
71
|
+
schema_file = path
|
|
72
|
+
|
|
73
|
+
# Parse filter specification if provided
|
|
74
|
+
filter_type = None
|
|
75
|
+
filter_value = None
|
|
76
|
+
if filter:
|
|
77
|
+
try:
|
|
78
|
+
filter_type, filter_value = parse_add_filter(filter)
|
|
79
|
+
except ValueError as e:
|
|
80
|
+
console.print(f"[red]❌ Invalid filter specification: {e}[/red]\n")
|
|
81
|
+
sys.exit(1)
|
|
82
|
+
|
|
83
|
+
# Load, modify, and save the schema
|
|
84
|
+
editor = SchemaEditor()
|
|
85
|
+
try:
|
|
86
|
+
editor.load_schema(schema_file)
|
|
87
|
+
editor.add_toolset(toolset_name, filter_type, filter_value)
|
|
88
|
+
editor.save_schema()
|
|
89
|
+
|
|
90
|
+
# Display success message
|
|
91
|
+
console.print(f"[green]✅ Added toolset '{toolset_name}' to {schema_file}[/green]")
|
|
92
|
+
if filter_type and filter_value:
|
|
93
|
+
console.print(f"[dim] Filter: {filter_type}:{filter_value}[/dim]")
|
|
94
|
+
|
|
95
|
+
except FileNotFoundError as e:
|
|
96
|
+
console.print(f"[red]❌ File not found: {e}[/red]\n")
|
|
97
|
+
sys.exit(1)
|
|
98
|
+
except ValueError as e:
|
|
99
|
+
console.print(f"[red]❌ Error: {e}[/red]\n")
|
|
100
|
+
sys.exit(1)
|
|
101
|
+
except Exception as e:
|
|
102
|
+
console.print(f"[red]❌ Unexpected error: {e}[/red]\n")
|
|
103
|
+
sys.exit(1)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
# Allow running as script
|
|
107
|
+
if __name__ == "__main__":
|
|
108
|
+
add()
|
mci/cli/envs.py
ADDED
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
"""
|
|
2
|
+
envs.py - Environment variables command for displaying required env vars
|
|
3
|
+
|
|
4
|
+
This module implements the `envs` command for the MCI CLI, which scans
|
|
5
|
+
the MCI configuration and displays all environment variables referenced
|
|
6
|
+
in templates across tools, toolsets, and MCP servers.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
import click
|
|
13
|
+
import yaml
|
|
14
|
+
from mcipy import MCIClientError
|
|
15
|
+
from rich.console import Console
|
|
16
|
+
from rich.table import Table
|
|
17
|
+
|
|
18
|
+
from mci.cli.formatters.env_formatter import EnvFormatter
|
|
19
|
+
from mci.core.file_finder import MCIFileFinder
|
|
20
|
+
from mci.utils.env_scanner import EnvScanner
|
|
21
|
+
from mci.utils.error_handler import ErrorHandler
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def extract_env_vars_from_schema(schema_path: str) -> dict[str, list[str]]:
|
|
25
|
+
"""
|
|
26
|
+
Extract all environment variables from a schema file and its referenced files.
|
|
27
|
+
|
|
28
|
+
This function loads the main schema file and scans it along with all
|
|
29
|
+
referenced toolsets and MCP servers to find environment variable references.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
schema_path: Path to the main MCI schema file
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
Dictionary mapping variable names to list of locations where used
|
|
36
|
+
(e.g., {"API_KEY": ["main", "weather-toolset"]})
|
|
37
|
+
|
|
38
|
+
Raises:
|
|
39
|
+
FileNotFoundError: If schema file or referenced files are not found
|
|
40
|
+
ValueError: If schema file is invalid
|
|
41
|
+
"""
|
|
42
|
+
env_vars: dict[str, set[str]] = {}
|
|
43
|
+
schema_dir = Path(schema_path).parent.resolve()
|
|
44
|
+
|
|
45
|
+
# Load main schema
|
|
46
|
+
with open(schema_path) as f:
|
|
47
|
+
if schema_path.endswith((".yaml", ".yml")):
|
|
48
|
+
main_schema = yaml.safe_load(f)
|
|
49
|
+
else:
|
|
50
|
+
main_schema = json.load(f)
|
|
51
|
+
|
|
52
|
+
# Scan main schema (excluding mcp_servers which we'll scan separately)
|
|
53
|
+
main_schema_without_mcp = {k: v for k, v in main_schema.items() if k != "mcp_servers"}
|
|
54
|
+
main_env_vars = EnvScanner.scan_dict(main_schema_without_mcp)
|
|
55
|
+
for var in main_env_vars:
|
|
56
|
+
if var not in env_vars:
|
|
57
|
+
env_vars[var] = set()
|
|
58
|
+
env_vars[var].add("main")
|
|
59
|
+
|
|
60
|
+
# Get library directory for toolsets
|
|
61
|
+
library_dir = main_schema.get("libraryDir", "mci")
|
|
62
|
+
lib_path = schema_dir / library_dir
|
|
63
|
+
|
|
64
|
+
# Scan registered toolsets
|
|
65
|
+
toolsets = main_schema.get("toolsets", [])
|
|
66
|
+
for toolset_ref in toolsets:
|
|
67
|
+
toolset_name = toolset_ref if isinstance(toolset_ref, str) else toolset_ref.get("name")
|
|
68
|
+
if not toolset_name:
|
|
69
|
+
continue
|
|
70
|
+
|
|
71
|
+
# Try to find toolset file
|
|
72
|
+
toolset_file = _find_toolset_file(lib_path, toolset_name)
|
|
73
|
+
if toolset_file:
|
|
74
|
+
try:
|
|
75
|
+
with open(toolset_file) as f:
|
|
76
|
+
if toolset_file.suffix in {".yaml", ".yml"}:
|
|
77
|
+
toolset_schema = yaml.safe_load(f)
|
|
78
|
+
else:
|
|
79
|
+
toolset_schema = json.load(f)
|
|
80
|
+
|
|
81
|
+
# Scan toolset
|
|
82
|
+
toolset_env_vars = EnvScanner.scan_dict(toolset_schema)
|
|
83
|
+
for var in toolset_env_vars:
|
|
84
|
+
if var not in env_vars:
|
|
85
|
+
env_vars[var] = set()
|
|
86
|
+
env_vars[var].add(toolset_name)
|
|
87
|
+
except Exception as e:
|
|
88
|
+
# Warn but continue if toolset file is invalid
|
|
89
|
+
console = Console()
|
|
90
|
+
console.print(
|
|
91
|
+
f"[yellow]⚠[/yellow] Warning: Could not load toolset '{toolset_name}': {e}",
|
|
92
|
+
style="yellow",
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
# Scan MCP servers
|
|
96
|
+
mcp_servers = main_schema.get("mcp_servers", {})
|
|
97
|
+
if isinstance(mcp_servers, dict):
|
|
98
|
+
for server_name, server_config in mcp_servers.items():
|
|
99
|
+
if isinstance(server_config, dict):
|
|
100
|
+
server_env_vars = EnvScanner.scan_dict(server_config)
|
|
101
|
+
for var in server_env_vars:
|
|
102
|
+
if var not in env_vars:
|
|
103
|
+
env_vars[var] = set()
|
|
104
|
+
env_vars[var].add(f"mcp:{server_name}")
|
|
105
|
+
|
|
106
|
+
# Convert sets to sorted lists
|
|
107
|
+
result = {var: sorted(locations) for var, locations in env_vars.items()}
|
|
108
|
+
return result
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _find_toolset_file(lib_path: Path, toolset_name: str) -> Path | None:
|
|
112
|
+
"""
|
|
113
|
+
Find a toolset file in the library directory.
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
lib_path: Path to the library directory
|
|
117
|
+
toolset_name: Name of the toolset
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
Path to the toolset file if found, None otherwise
|
|
121
|
+
"""
|
|
122
|
+
if not lib_path.exists():
|
|
123
|
+
return None
|
|
124
|
+
|
|
125
|
+
# Try various file patterns
|
|
126
|
+
patterns = [
|
|
127
|
+
f"{toolset_name}.mci.json",
|
|
128
|
+
f"{toolset_name}.mci.yaml",
|
|
129
|
+
f"{toolset_name}.mci.yml",
|
|
130
|
+
f"{toolset_name}.json",
|
|
131
|
+
f"{toolset_name}.yaml",
|
|
132
|
+
f"{toolset_name}.yml",
|
|
133
|
+
]
|
|
134
|
+
|
|
135
|
+
for pattern in patterns:
|
|
136
|
+
file_path = lib_path / pattern
|
|
137
|
+
if file_path.exists() and file_path.is_file():
|
|
138
|
+
return file_path
|
|
139
|
+
|
|
140
|
+
# Check if it's a directory with a schema file
|
|
141
|
+
dir_path = lib_path / toolset_name
|
|
142
|
+
if dir_path.exists() and dir_path.is_dir():
|
|
143
|
+
for pattern in patterns:
|
|
144
|
+
file_path = dir_path / pattern
|
|
145
|
+
if file_path.exists() and file_path.is_file():
|
|
146
|
+
return file_path
|
|
147
|
+
|
|
148
|
+
return None
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
@click.command()
|
|
152
|
+
@click.option(
|
|
153
|
+
"--file",
|
|
154
|
+
"-f",
|
|
155
|
+
type=click.Path(exists=True),
|
|
156
|
+
default=None,
|
|
157
|
+
help="Path to MCI schema file (defaults to mci.json or mci.yaml in current directory)",
|
|
158
|
+
)
|
|
159
|
+
@click.option(
|
|
160
|
+
"--format",
|
|
161
|
+
type=click.Choice(["table", "env"], case_sensitive=False),
|
|
162
|
+
default="table",
|
|
163
|
+
help="Output format (default: table)",
|
|
164
|
+
)
|
|
165
|
+
def envs_command(file: str | None, format: str):
|
|
166
|
+
"""
|
|
167
|
+
List all environment variables used in the MCI configuration.
|
|
168
|
+
|
|
169
|
+
Scans the main schema file, referenced toolsets, and MCP servers to find
|
|
170
|
+
all environment variable references in templates ({{env.VARIABLE}}).
|
|
171
|
+
|
|
172
|
+
Outputs a list of all required environment variables with their locations,
|
|
173
|
+
helping you understand what credentials and configuration are needed.
|
|
174
|
+
|
|
175
|
+
Examples:
|
|
176
|
+
|
|
177
|
+
# Show environment variables in table format
|
|
178
|
+
mci envs
|
|
179
|
+
|
|
180
|
+
# Generate .env.example.mci file
|
|
181
|
+
mci envs --format=env
|
|
182
|
+
|
|
183
|
+
# Scan specific schema file
|
|
184
|
+
mci envs --file=custom.mci.json
|
|
185
|
+
"""
|
|
186
|
+
console = Console()
|
|
187
|
+
|
|
188
|
+
try:
|
|
189
|
+
# Find MCI file
|
|
190
|
+
if file is None:
|
|
191
|
+
finder = MCIFileFinder()
|
|
192
|
+
file = finder.find_mci_file()
|
|
193
|
+
if file is None:
|
|
194
|
+
console.print(
|
|
195
|
+
"[red]✗[/red] No MCI schema file found. "
|
|
196
|
+
"Run 'mcix install' to create one or specify --file.",
|
|
197
|
+
style="red",
|
|
198
|
+
)
|
|
199
|
+
raise click.Abort()
|
|
200
|
+
|
|
201
|
+
# Extract environment variables
|
|
202
|
+
try:
|
|
203
|
+
env_vars = extract_env_vars_from_schema(file)
|
|
204
|
+
except FileNotFoundError as e:
|
|
205
|
+
console.print(f"[red]✗[/red] File not found: {e}", style="red")
|
|
206
|
+
raise click.Abort() from e
|
|
207
|
+
except (json.JSONDecodeError, yaml.YAMLError) as e:
|
|
208
|
+
console.print(f"[red]✗[/red] Invalid schema file: {e}", style="red")
|
|
209
|
+
raise click.Abort() from e
|
|
210
|
+
except Exception as e:
|
|
211
|
+
console.print(ErrorHandler.format_generic_error(e))
|
|
212
|
+
raise click.Abort() from e
|
|
213
|
+
|
|
214
|
+
# Check if any variables were found
|
|
215
|
+
if not env_vars:
|
|
216
|
+
if format == "table":
|
|
217
|
+
console.print("[yellow]ℹ[/yellow] No environment variables found in schema.")
|
|
218
|
+
else:
|
|
219
|
+
console.print(
|
|
220
|
+
"[yellow]ℹ[/yellow] No environment variables found. Skipping file generation."
|
|
221
|
+
)
|
|
222
|
+
return
|
|
223
|
+
|
|
224
|
+
# Format and display output
|
|
225
|
+
if format == "table":
|
|
226
|
+
# Display table to console
|
|
227
|
+
table = Table(
|
|
228
|
+
title=f"🔐 Environment Variables ({len(env_vars)})",
|
|
229
|
+
show_header=True,
|
|
230
|
+
header_style="bold cyan",
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
table.add_column("Variable", style="green", no_wrap=True)
|
|
234
|
+
table.add_column("Used In", style="blue")
|
|
235
|
+
|
|
236
|
+
# Sort by variable name
|
|
237
|
+
for var_name in sorted(env_vars.keys()):
|
|
238
|
+
locations = env_vars[var_name]
|
|
239
|
+
locations_str = ", ".join(locations)
|
|
240
|
+
table.add_row(var_name, locations_str)
|
|
241
|
+
|
|
242
|
+
console.print(table)
|
|
243
|
+
|
|
244
|
+
elif format == "env":
|
|
245
|
+
# Write to .env.example.mci file
|
|
246
|
+
filename = EnvFormatter.format_to_file(env_vars)
|
|
247
|
+
console.print(f"[green]✓[/green] Environment variables exported to: {filename}")
|
|
248
|
+
console.print("[dim]Copy this file to .env.mci and fill in your values[/dim]")
|
|
249
|
+
|
|
250
|
+
except click.Abort:
|
|
251
|
+
raise
|
|
252
|
+
except MCIClientError as e:
|
|
253
|
+
console.print(ErrorHandler.format_mci_client_error(e))
|
|
254
|
+
raise click.Abort() from e
|
|
255
|
+
except Exception as e:
|
|
256
|
+
console.print(ErrorHandler.format_generic_error(e))
|
|
257
|
+
raise click.Abort() from e
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""
|
|
2
|
+
formatters package - Output formatters for CLI commands
|
|
3
|
+
|
|
4
|
+
This package provides formatters for displaying tool information
|
|
5
|
+
in various formats including Rich tables, JSON, and YAML.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from mci.cli.formatters.json_formatter import JSONFormatter
|
|
9
|
+
from mci.cli.formatters.table_formatter import TableFormatter
|
|
10
|
+
from mci.cli.formatters.yaml_formatter import YAMLFormatter
|
|
11
|
+
|
|
12
|
+
__all__ = ["TableFormatter", "JSONFormatter", "YAMLFormatter"]
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"""
|
|
2
|
+
env_formatter.py - Environment variable file formatter
|
|
3
|
+
|
|
4
|
+
This module provides formatting for environment variable output in .env file format.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class EnvFormatter:
|
|
11
|
+
"""
|
|
12
|
+
Formats environment variable information as .env files.
|
|
13
|
+
|
|
14
|
+
This formatter generates .env.example.mci files containing all detected
|
|
15
|
+
environment variables with empty values, suitable for use as templates.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
@staticmethod
|
|
19
|
+
def format_to_string(env_vars: dict[str, list[str]]) -> str:
|
|
20
|
+
"""
|
|
21
|
+
Format environment variables as a string in .env format.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
env_vars: Dictionary mapping variable names to list of locations where used
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
Formatted string in .env format
|
|
28
|
+
|
|
29
|
+
Example:
|
|
30
|
+
>>> env_vars = {"API_KEY": ["main"], "DB_URL": ["database-toolset"]}
|
|
31
|
+
>>> print(EnvFormatter.format_to_string(env_vars))
|
|
32
|
+
# .env.example.mci
|
|
33
|
+
...
|
|
34
|
+
"""
|
|
35
|
+
# Sort variables alphabetically
|
|
36
|
+
sorted_vars = sorted(env_vars.keys())
|
|
37
|
+
|
|
38
|
+
# Generate content
|
|
39
|
+
lines: list[str] = []
|
|
40
|
+
lines.append("# .env.example.mci")
|
|
41
|
+
lines.append("# Environment variables used in MCI configuration")
|
|
42
|
+
lines.append("#")
|
|
43
|
+
lines.append("# Copy this file to .env.mci and fill in your values")
|
|
44
|
+
lines.append("")
|
|
45
|
+
|
|
46
|
+
for var_name in sorted_vars:
|
|
47
|
+
locations = env_vars[var_name]
|
|
48
|
+
# Add comment showing where the variable is used
|
|
49
|
+
location_str = ", ".join(sorted(locations))
|
|
50
|
+
lines.append(f"# Used in: {location_str}")
|
|
51
|
+
lines.append(f"{var_name}=")
|
|
52
|
+
lines.append("")
|
|
53
|
+
|
|
54
|
+
return "\n".join(lines)
|
|
55
|
+
|
|
56
|
+
@staticmethod
|
|
57
|
+
def format_to_file(env_vars: dict[str, list[str]], output_path: str | None = None) -> str:
|
|
58
|
+
"""
|
|
59
|
+
Format environment variables and write to .env.example.mci file.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
env_vars: Dictionary mapping variable names to list of locations where used
|
|
63
|
+
output_path: Optional custom output path (default: .env.example.mci)
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
Path to the generated file
|
|
67
|
+
|
|
68
|
+
Example:
|
|
69
|
+
>>> env_vars = {
|
|
70
|
+
... "API_KEY": ["main", "weather-toolset"],
|
|
71
|
+
... "DB_URL": ["database-toolset"]
|
|
72
|
+
... }
|
|
73
|
+
>>> path = EnvFormatter.format_to_file(env_vars)
|
|
74
|
+
>>> print(path)
|
|
75
|
+
.env.example.mci
|
|
76
|
+
"""
|
|
77
|
+
if output_path is None:
|
|
78
|
+
output_path = ".env.example.mci"
|
|
79
|
+
|
|
80
|
+
content = EnvFormatter.format_to_string(env_vars)
|
|
81
|
+
Path(output_path).write_text(content)
|
|
82
|
+
|
|
83
|
+
return output_path
|