fid-mcp 0.1.5__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.
fid_mcp/__init__.py ADDED
@@ -0,0 +1 @@
1
+ # fid-mcp package
fid_mcp/config.py ADDED
@@ -0,0 +1,243 @@
1
+ from enum import Enum
2
+ from pydantic import BaseModel
3
+ from typing import Any, List, TypeVar, Type, cast, Callable
4
+ import json
5
+ import re
6
+ from pathlib import Path
7
+
8
+
9
+ T = TypeVar("T")
10
+ EnumT = TypeVar("EnumT", bound=Enum)
11
+
12
+
13
+ def from_str(x: Any) -> str:
14
+ assert isinstance(x, str)
15
+ return x
16
+
17
+
18
+ def from_int(x: Any) -> int:
19
+ assert isinstance(x, int) and not isinstance(x, bool)
20
+ return x
21
+
22
+
23
+ def to_enum(c: Type[EnumT], x: Any) -> EnumT:
24
+ assert isinstance(x, c)
25
+ return x.value
26
+
27
+
28
+ def to_class(c: Type[T], x: Any) -> dict:
29
+ assert isinstance(x, c)
30
+ return cast(Any, x).to_dict()
31
+
32
+
33
+ def from_list(f: Callable[[Any], T], x: Any) -> List[T]:
34
+ assert isinstance(x, list)
35
+ return [f(y) for y in x]
36
+
37
+
38
+ class Function(Enum):
39
+ """The type of function to execute"""
40
+
41
+ SHELL = "shell"
42
+
43
+
44
+ class ShellParams(BaseModel):
45
+ command: str
46
+ wait: int
47
+ """Wait time in seconds"""
48
+ expect_patterns: List[str] | None = None
49
+ """Regex patterns to expect during command execution"""
50
+ responses: List[str] | None = None
51
+ """Responses to send when patterns are matched (must match length of expect_patterns)"""
52
+
53
+ @staticmethod
54
+ def from_dict(obj: Any) -> 'ShellParams':
55
+ assert isinstance(obj, dict)
56
+ command = from_str(obj.get("command"))
57
+ wait = from_int(obj.get("wait"))
58
+ expect_patterns = from_list(from_str, obj.get("expect_patterns")) if obj.get("expect_patterns") else None
59
+ responses = from_list(from_str, obj.get("responses")) if obj.get("responses") else None
60
+ return ShellParams(command=command, wait=wait, expect_patterns=expect_patterns, responses=responses)
61
+
62
+ def to_dict(self) -> dict:
63
+ result: dict = {}
64
+ result["command"] = from_str(self.command)
65
+ result["wait"] = from_int(self.wait)
66
+ if self.expect_patterns is not None:
67
+ result["expect_patterns"] = from_list(from_str, self.expect_patterns)
68
+ if self.responses is not None:
69
+ result["responses"] = from_list(from_str, self.responses)
70
+ return result
71
+
72
+
73
+ class Step(BaseModel):
74
+ function: Function
75
+ """The type of function to execute"""
76
+
77
+ shell_params: ShellParams
78
+
79
+ @staticmethod
80
+ def from_dict(obj: Any) -> 'Step':
81
+ assert isinstance(obj, dict)
82
+ function = Function(obj.get("function"))
83
+ shell_params = ShellParams.from_dict(obj.get("shellParams"))
84
+ return Step(function=function, shell_params=shell_params)
85
+
86
+ def to_dict(self) -> dict:
87
+ result: dict = {}
88
+ result["function"] = to_enum(Function, self.function)
89
+ result["shellParams"] = to_class(ShellParams, self.shell_params)
90
+ return result
91
+
92
+
93
+ class Param(BaseModel):
94
+ default: str
95
+ name: str
96
+
97
+ @staticmethod
98
+ def from_dict(obj: Any) -> 'Param':
99
+ assert isinstance(obj, dict)
100
+ default = from_str(obj.get("default"))
101
+ name = from_str(obj.get("name"))
102
+ return Param(default=default, name=name)
103
+
104
+ def to_dict(self) -> dict:
105
+ result: dict = {}
106
+ result["default"] = from_str(self.default)
107
+ result["name"] = from_str(self.name)
108
+ return result
109
+
110
+
111
+ class Tool(BaseModel):
112
+ description: str
113
+ name: str
114
+ steps: List[Step]
115
+ tool_params: List[Param]
116
+
117
+ @staticmethod
118
+ def from_dict(obj: Any) -> 'Tool':
119
+ assert isinstance(obj, dict)
120
+ description = from_str(obj.get("description"))
121
+ name = from_str(obj.get("name"))
122
+ steps = from_list(Step.from_dict, obj.get("steps"))
123
+ tool_params = from_list(Param.from_dict, obj.get("toolParams"))
124
+ return Tool(description=description, name=name, steps=steps, tool_params=tool_params)
125
+
126
+ def to_dict(self) -> dict:
127
+ result: dict = {}
128
+ result["description"] = from_str(self.description)
129
+ result["name"] = from_str(self.name)
130
+ result["steps"] = from_list(lambda x: to_class(Step, x), self.steps)
131
+ result["toolParams"] = from_list(lambda x: to_class(Param, x), self.tool_params)
132
+ return result
133
+
134
+
135
+ class Version(Enum):
136
+ THE_100 = "1.0.0"
137
+
138
+
139
+ class Coordinate(BaseModel):
140
+ description: str
141
+ name: str
142
+ tools: List[Tool]
143
+ version: Version
144
+
145
+ @staticmethod
146
+ def from_dict(obj: Any) -> 'Coordinate':
147
+ assert isinstance(obj, dict)
148
+ description = from_str(obj.get("description"))
149
+ name = from_str(obj.get("name"))
150
+ tools = from_list(Tool.from_dict, obj.get("tools"))
151
+ version = Version(obj.get("version"))
152
+ return Coordinate(description=description, name=name, tools=tools, version=version)
153
+
154
+ def to_dict(self) -> dict:
155
+ result: dict = {}
156
+ result["description"] = from_str(self.description)
157
+ result["name"] = from_str(self.name)
158
+ result["tools"] = from_list(lambda x: to_class(Tool, x), self.tools)
159
+ result["version"] = to_enum(Version, self.version)
160
+ return result
161
+
162
+
163
+ def coordinate_from_dict(s: Any) -> Coordinate:
164
+ return Coordinate.from_dict(s)
165
+
166
+
167
+ def coordinate_to_dict(x: Coordinate) -> Any:
168
+ return to_class(Coordinate, x)
169
+
170
+
171
+ def load_and_validate_config(config_path: str) -> Coordinate:
172
+ """Load and validate a fidtools configuration file using Pydantic models"""
173
+ config_path = Path(config_path)
174
+
175
+ # Load the configuration file
176
+ with open(config_path, 'r') as f:
177
+ config_data = json.load(f)
178
+
179
+ # Validate using Pydantic models and additional validation
180
+ validate_config_dict(config_data)
181
+
182
+ # Convert to Coordinate object
183
+ return coordinate_from_dict(config_data)
184
+
185
+
186
+ def validate_config_dict(config_data: dict) -> None:
187
+ """Validate a configuration dictionary using Pydantic models and check parameter references"""
188
+ # Validate using Pydantic by attempting to create a Coordinate object
189
+ try:
190
+ coordinate_from_dict(config_data)
191
+ except Exception as e:
192
+ raise ValueError(f"Configuration validation failed: {str(e)}")
193
+
194
+ # Additional validation: check parameter references
195
+ _validate_parameter_references(config_data)
196
+
197
+
198
+ def _validate_parameter_references(config_data: dict) -> None:
199
+ """Validate that all parameter references in shellParams are defined in toolParams"""
200
+ for tool_idx, tool in enumerate(config_data.get("tools", [])):
201
+ tool_name = tool.get("name", f"tool[{tool_idx}]")
202
+
203
+ # Get defined parameters
204
+ defined_params = set()
205
+ for param in tool.get("toolParams", []):
206
+ defined_params.add(param.get("name"))
207
+
208
+ # Check each step for parameter references
209
+ for step_idx, step in enumerate(tool.get("steps", [])):
210
+ if step.get("function") == "shell" and "shellParams" in step:
211
+ shell_params = step["shellParams"]
212
+
213
+ # Check parameter references in command
214
+ if "command" in shell_params:
215
+ referenced_params = _extract_parameter_references(shell_params["command"])
216
+
217
+ # Check if all referenced parameters are defined
218
+ undefined_params = referenced_params - defined_params
219
+ if undefined_params:
220
+ raise ValueError(
221
+ f"Tool '{tool_name}' step {step_idx}: "
222
+ f"shellParams.command references undefined parameters: {sorted(undefined_params)}. "
223
+ f"Defined parameters: {sorted(defined_params)}"
224
+ )
225
+
226
+
227
+ def _extract_parameter_references(text: str) -> set:
228
+ """Extract parameter references (${param}) from a text string"""
229
+ if not isinstance(text, str):
230
+ return set()
231
+
232
+ # Find all ${...} patterns
233
+ pattern = r'\$\{([^}]+)\}'
234
+ matches = re.findall(pattern, text)
235
+
236
+ # Extract simple parameter names (not complex paths like params.name or step[0].data)
237
+ simple_params = set()
238
+ for match in matches:
239
+ # Only consider simple parameter names (no dots, no brackets)
240
+ if '.' not in match and '[' not in match and ']' not in match:
241
+ simple_params.add(match)
242
+
243
+ return simple_params