universal-mcp 0.1.24rc2__py3-none-any.whl → 0.1.24rc3__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 (57) hide show
  1. universal_mcp/agentr/__init__.py +6 -0
  2. universal_mcp/agentr/agentr.py +30 -0
  3. universal_mcp/{utils/agentr.py → agentr/client.py} +19 -3
  4. universal_mcp/agentr/integration.py +104 -0
  5. universal_mcp/agentr/registry.py +91 -0
  6. universal_mcp/agentr/server.py +51 -0
  7. universal_mcp/agents/__init__.py +6 -0
  8. universal_mcp/agents/auto.py +576 -0
  9. universal_mcp/agents/base.py +88 -0
  10. universal_mcp/agents/cli.py +27 -0
  11. universal_mcp/agents/codeact/__init__.py +243 -0
  12. universal_mcp/agents/codeact/sandbox.py +27 -0
  13. universal_mcp/agents/codeact/test.py +15 -0
  14. universal_mcp/agents/codeact/utils.py +61 -0
  15. universal_mcp/agents/hil.py +104 -0
  16. universal_mcp/agents/llm.py +10 -0
  17. universal_mcp/agents/react.py +58 -0
  18. universal_mcp/agents/simple.py +40 -0
  19. universal_mcp/agents/utils.py +111 -0
  20. universal_mcp/analytics.py +5 -7
  21. universal_mcp/applications/__init__.py +42 -75
  22. universal_mcp/applications/application.py +1 -1
  23. universal_mcp/applications/sample/app.py +245 -0
  24. universal_mcp/cli.py +10 -3
  25. universal_mcp/config.py +33 -7
  26. universal_mcp/exceptions.py +4 -0
  27. universal_mcp/integrations/__init__.py +0 -15
  28. universal_mcp/integrations/integration.py +9 -91
  29. universal_mcp/servers/__init__.py +2 -14
  30. universal_mcp/servers/server.py +10 -51
  31. universal_mcp/tools/__init__.py +3 -0
  32. universal_mcp/tools/adapters.py +20 -11
  33. universal_mcp/tools/manager.py +29 -56
  34. universal_mcp/tools/registry.py +41 -0
  35. universal_mcp/tools/tools.py +22 -1
  36. universal_mcp/types.py +10 -0
  37. universal_mcp/utils/common.py +245 -0
  38. universal_mcp/utils/openapi/api_generator.py +46 -18
  39. universal_mcp/utils/openapi/cli.py +445 -19
  40. universal_mcp/utils/openapi/openapi.py +284 -21
  41. universal_mcp/utils/openapi/postprocessor.py +275 -0
  42. universal_mcp/utils/openapi/preprocessor.py +1 -1
  43. universal_mcp/utils/openapi/test_generator.py +287 -0
  44. universal_mcp/utils/prompts.py +188 -341
  45. universal_mcp/utils/testing.py +190 -2
  46. {universal_mcp-0.1.24rc2.dist-info → universal_mcp-0.1.24rc3.dist-info}/METADATA +16 -2
  47. universal_mcp-0.1.24rc3.dist-info/RECORD +70 -0
  48. universal_mcp/applications/sample_tool_app.py +0 -80
  49. universal_mcp/client/agents/__init__.py +0 -4
  50. universal_mcp/client/agents/base.py +0 -38
  51. universal_mcp/client/agents/llm.py +0 -115
  52. universal_mcp/client/agents/react.py +0 -67
  53. universal_mcp/client/cli.py +0 -181
  54. universal_mcp-0.1.24rc2.dist-info/RECORD +0 -53
  55. {universal_mcp-0.1.24rc2.dist-info → universal_mcp-0.1.24rc3.dist-info}/WHEEL +0 -0
  56. {universal_mcp-0.1.24rc2.dist-info → universal_mcp-0.1.24rc3.dist-info}/entry_points.txt +0 -0
  57. {universal_mcp-0.1.24rc2.dist-info → universal_mcp-0.1.24rc3.dist-info}/licenses/LICENSE +0 -0
@@ -67,8 +67,9 @@ class Analytics:
67
67
  properties = {
68
68
  "version": self.get_version(),
69
69
  "app_name": app_name,
70
+ "user_id": self.user_id,
70
71
  }
71
- posthog.capture(self.user_id, "app_loaded", properties)
72
+ posthog.capture("app_loaded", properties=properties)
72
73
  except Exception as e:
73
74
  logger.error(f"Failed to track app_loaded event: {e}")
74
75
 
@@ -77,8 +78,7 @@ class Analytics:
77
78
  tool_name: str,
78
79
  app_name: str,
79
80
  status: str,
80
- error: str = None,
81
- user_id=None, # Note: user_id is captured in PostHog but not used from this param
81
+ error: str | None = None,
82
82
  ):
83
83
  """Tracks an event when a tool is called within an application.
84
84
 
@@ -91,9 +91,6 @@ class Analytics:
91
91
  status (str): The status of the tool call (e.g., "success", "error").
92
92
  error (str, optional): The error message if the tool call failed.
93
93
  Defaults to None.
94
- user_id (str, optional): An optional user identifier.
95
- Note: Currently, the class uses an internally
96
- generated user_id for PostHog events.
97
94
  """
98
95
  if not self.enabled:
99
96
  return
@@ -104,8 +101,9 @@ class Analytics:
104
101
  "status": status,
105
102
  "error": error,
106
103
  "version": self.get_version(),
104
+ "user_id": self.user_id,
107
105
  }
108
- posthog.capture(self.user_id, "tool_called", properties)
106
+ posthog.capture("tool_called", properties=properties)
109
107
  except Exception as e:
110
108
  logger.error(f"Failed to track tool_called event: {e}")
111
109
 
@@ -1,9 +1,3 @@
1
- import importlib
2
- import os
3
- import subprocess
4
- import sys
5
- from pathlib import Path
6
-
7
1
  from loguru import logger
8
2
 
9
3
  from universal_mcp.applications.application import (
@@ -11,92 +5,65 @@ from universal_mcp.applications.application import (
11
5
  BaseApplication,
12
6
  GraphQLApplication,
13
7
  )
8
+ from universal_mcp.config import AppConfig
14
9
  from universal_mcp.utils.common import (
15
- get_default_class_name,
16
- get_default_module_path,
17
- get_default_package_name,
18
- get_default_repository_path,
10
+ load_app_from_local_file,
11
+ load_app_from_local_folder,
12
+ load_app_from_package,
13
+ load_app_from_remote_file,
14
+ load_app_from_remote_zip,
19
15
  )
20
16
 
21
- UNIVERSAL_MCP_HOME = Path.home() / ".universal-mcp" / "packages"
22
-
23
- if not UNIVERSAL_MCP_HOME.exists():
24
- UNIVERSAL_MCP_HOME.mkdir(parents=True, exist_ok=True)
25
-
26
- # set python path to include the universal-mcp home directory
27
- sys.path.append(str(UNIVERSAL_MCP_HOME))
28
-
29
-
30
- # Name are in the format of "app-name", eg, google-calendar
31
- # Class name is NameApp, eg, GoogleCalendarApp
32
-
33
17
  app_cache: dict[str, type[BaseApplication]] = {}
34
18
 
35
19
 
36
- def _install_or_upgrade_package(package_name: str, repository_path: str):
20
+ def app_from_slug(slug: str) -> type[BaseApplication]:
37
21
  """
38
- Helper to install a package via pip from the universal-mcp GitHub repository.
22
+ Dynamically resolve and return the application class based on slug.
39
23
  """
40
- uv_path = os.getenv("UV_PATH")
41
- uv_executable = str(Path(uv_path) / "uv") if uv_path else "uv"
42
- logger.info(f"Using uv executable: {uv_executable}")
43
- cmd = [
44
- uv_executable,
45
- "pip",
46
- "install",
47
- "--upgrade",
48
- repository_path,
49
- "--target",
50
- str(UNIVERSAL_MCP_HOME),
51
- ]
52
- logger.debug(f"Installing package '{package_name}' with command: {' '.join(cmd)}")
53
- try:
54
- result = subprocess.run(cmd, capture_output=True, text=True)
55
- if result.stdout:
56
- logger.info(f"Command stdout: {result.stdout}")
57
- if result.stderr:
58
- logger.info(f"Command stderr: {result.stderr}")
59
- result.check_returncode()
60
- except subprocess.CalledProcessError as e:
61
- logger.error(f"Installation failed for '{package_name}': {e}")
62
- if e.stdout:
63
- logger.error(f"Command stdout: {e.stdout}")
64
- if e.stderr:
65
- logger.error(f"Command stderr: {e.stderr}")
66
- raise ModuleNotFoundError(f"Installation failed for package '{package_name}'") from e
67
- else:
68
- logger.debug(f"Package {package_name} installed successfully")
24
+ return app_from_config(AppConfig(name=slug, source_type="package"))
69
25
 
70
26
 
71
- def app_from_slug(slug: str):
27
+ def app_from_config(config: AppConfig) -> type[BaseApplication]:
72
28
  """
73
- Dynamically resolve and return the application class for the given slug.
74
- Attempts installation from GitHub if the package is not found locally.
29
+ Dynamically resolve and return the application class based on AppConfig.
75
30
  """
76
- if slug in app_cache:
77
- return app_cache[slug]
78
- class_name = get_default_class_name(slug)
79
- module_path = get_default_module_path(slug)
80
- package_name = get_default_package_name(slug)
81
- repository_path = get_default_repository_path(slug)
82
- logger.debug(f"Resolving app for slug '{slug}' → module '{module_path}', class '{class_name}'")
31
+ if config.name in app_cache:
32
+ return app_cache[config.name]
33
+
34
+ app_class = None
83
35
  try:
84
- _install_or_upgrade_package(package_name, repository_path)
85
- module = importlib.import_module(module_path)
86
- class_ = getattr(module, class_name)
87
- logger.debug(f"Loaded class '{class_}' from module '{module_path}'")
88
- app_cache[slug] = class_
89
- return class_
90
- except ModuleNotFoundError as e:
91
- raise ModuleNotFoundError(f"Package '{module_path}' not found locally. Please install it first.") from e
92
- except AttributeError as e:
93
- raise AttributeError(f"Class '{class_name}' not found in module '{module_path}'") from e
36
+ match config.source_type:
37
+ case "package":
38
+ app_class = load_app_from_package(config)
39
+ case "local_folder":
40
+ app_class = load_app_from_local_folder(config)
41
+ case "remote_zip":
42
+ app_class = load_app_from_remote_zip(config)
43
+ case "remote_file":
44
+ app_class = load_app_from_remote_file(config)
45
+ case "local_file":
46
+ app_class = load_app_from_local_file(config)
47
+ case _:
48
+ raise ValueError(f"Unsupported source_type: {config.source_type}")
49
+
94
50
  except Exception as e:
95
- raise Exception(f"Error importing module '{module_path}': {e}") from e
51
+ logger.error(
52
+ f"Failed to load application '{config.name}' from source '{config.source_type}': {e}",
53
+ exc_info=True,
54
+ )
55
+ raise
56
+
57
+ if not app_class:
58
+ raise ImportError(f"Could not load application class for '{config.name}'")
59
+
60
+ logger.debug(f"Loaded class '{app_class.__name__}' for app '{config.name}'")
61
+ app_cache[config.name] = app_class
62
+ return app_class
96
63
 
97
64
 
98
65
  __all__ = [
99
- "app_from_slug",
66
+ "app_from_config",
100
67
  "BaseApplication",
101
68
  "APIApplication",
102
69
  "GraphQLApplication",
@@ -10,7 +10,7 @@ from graphql import DocumentNode
10
10
  from loguru import logger
11
11
 
12
12
  from universal_mcp.analytics import analytics
13
- from universal_mcp.integrations import Integration
13
+ from universal_mcp.integrations.integration import Integration
14
14
 
15
15
 
16
16
  class BaseApplication(ABC):
@@ -0,0 +1,245 @@
1
+ import datetime
2
+
3
+ import httpx
4
+
5
+ from universal_mcp.applications.application import BaseApplication
6
+
7
+
8
+ class SampleToolApp(BaseApplication):
9
+ """A sample application providing basic utility tools."""
10
+
11
+ def __init__(self):
12
+ """Initializes the SampleToolApp with the name 'sample_tool_app'."""
13
+ super().__init__(name="sample_tool_app")
14
+
15
+ def get_current_time(self):
16
+ """Get the current system time as a formatted string.
17
+
18
+ Returns:
19
+ str: The current time in the format 'YYYY-MM-DD HH:MM:SS'.
20
+ """
21
+ return datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
22
+
23
+ def get_current_date(self):
24
+ """Get the current system date as a formatted string.
25
+
26
+ Returns:
27
+ str: The current date in the format 'YYYY-MM-DD'.
28
+ """
29
+ return datetime.datetime.now().strftime("%Y-%m-%d")
30
+
31
+ def calculate(self, expression: str):
32
+ """Safely evaluate a mathematical expression.
33
+
34
+ Args:
35
+ expression (str): The mathematical expression to evaluate.
36
+
37
+ Returns:
38
+ str: The result of the calculation, or an error message if evaluation fails.
39
+ """
40
+ try:
41
+ # Safe evaluation of mathematical expressions
42
+ result = eval(expression, {"__builtins__": {}}, {}) # noqa: S102
43
+ return f"Result: {result}"
44
+ except Exception as e:
45
+ return f"Error in calculation: {str(e)}"
46
+
47
+ def file_operations(self, operation: str, filename: str, content: str = ""):
48
+ """Perform file read or write operations.
49
+
50
+ Args:
51
+ operation (str): The operation to perform, either 'read' or 'write'.
52
+ filename (str): The name of the file to operate on.
53
+ content (str, optional): The content to write to the file (used only for 'write'). Defaults to "".
54
+
55
+ Returns:
56
+ str: The result of the file operation, or an error message if the operation fails.
57
+ """
58
+ try:
59
+ if operation == "read":
60
+ with open(filename) as f:
61
+ return f"File content:\n{f.read()}"
62
+ elif operation == "write":
63
+ with open(filename, "w") as f:
64
+ f.write(content)
65
+ return f"Successfully wrote to {filename}"
66
+ else:
67
+ return "Invalid operation. Use 'read' or 'write'"
68
+ except Exception as e:
69
+ return f"File operation error: {str(e)}"
70
+
71
+ def get_weather(
72
+ self,
73
+ latitude: float,
74
+ longitude: float,
75
+ current: list[str] | None = None,
76
+ hourly: list[str] | None = None,
77
+ daily: list[str] | None = None,
78
+ timezone: str = "auto",
79
+ temperature_unit: str = "celsius",
80
+ wind_speed_unit: str = "kmh",
81
+ precipitation_unit: str = "mm",
82
+ ) -> dict:
83
+ """
84
+ Get weather data from Open-Meteo API.
85
+
86
+ Args:
87
+ latitude (float): Latitude coordinate
88
+ longitude (float): Longitude coordinate
89
+ current (List[str], optional): Current weather parameters to fetch
90
+ hourly (List[str], optional): Hourly weather parameters to fetch
91
+ daily (List[str], optional): Daily weather parameters to fetch
92
+ timezone (str): Timezone (default: "auto")
93
+ temperature_unit (str): Temperature unit - "celsius" or "fahrenheit"
94
+ wind_speed_unit (str): Wind speed unit - "kmh", "ms", "mph", "kn"
95
+ precipitation_unit (str): Precipitation unit - "mm" or "inch"
96
+
97
+ Returns:
98
+ Dict: Weather data from the API
99
+
100
+ Raises:
101
+ httpx.RequestError: If API request fails
102
+ ValueError: If coordinates are invalid
103
+ """
104
+ # Validate coordinates
105
+ if not (-90 <= latitude <= 90):
106
+ raise ValueError("Latitude must be between -90 and 90")
107
+ if not (-180 <= longitude <= 180):
108
+ raise ValueError("Longitude must be between -180 and 180")
109
+
110
+ # Base URL
111
+ base_url = "https://api.open-meteo.com/v1/forecast"
112
+
113
+ # Default parameters if none provided
114
+ if current is None:
115
+ current = ["temperature_2m", "relative_humidity_2m", "weather_code", "wind_speed_10m", "wind_direction_10m"]
116
+
117
+ if daily is None:
118
+ daily = ["temperature_2m_max", "temperature_2m_min", "weather_code", "precipitation_sum"]
119
+
120
+ # Build parameters
121
+ params = {
122
+ "latitude": latitude,
123
+ "longitude": longitude,
124
+ "timezone": timezone,
125
+ "temperature_unit": temperature_unit,
126
+ "wind_speed_unit": wind_speed_unit,
127
+ "precipitation_unit": precipitation_unit,
128
+ }
129
+
130
+ # Add weather parameters
131
+ if current:
132
+ params["current"] = ",".join(current)
133
+ if hourly:
134
+ params["hourly"] = ",".join(hourly)
135
+ if daily:
136
+ params["daily"] = ",".join(daily)
137
+
138
+ try:
139
+ # Make API request
140
+ with httpx.Client(timeout=10) as client:
141
+ response = client.get(base_url, params=params)
142
+ response.raise_for_status()
143
+ return response.json()
144
+
145
+ except httpx.TimeoutException as e:
146
+ raise httpx.RequestError("Request timed out") from e
147
+ except httpx.ConnectError as e:
148
+ raise httpx.RequestError("Connection error") from e
149
+ except httpx.HTTPStatusError as e:
150
+ raise httpx.RequestError(f"HTTP error: {e}") from e
151
+ except httpx.RequestError as e:
152
+ raise httpx.RequestError(f"Request failed: {e}") from e
153
+
154
+ def get_simple_weather(self, latitude: float, longitude: float) -> dict:
155
+ """
156
+ Get simplified current weather data.
157
+
158
+ Args:
159
+ latitude (float): Latitude coordinate
160
+ longitude (float): Longitude coordinate
161
+
162
+ Returns:
163
+ Dict: Simplified weather data with current conditions
164
+ """
165
+
166
+ try:
167
+ weather_data = self.get_weather(
168
+ latitude=latitude,
169
+ longitude=longitude,
170
+ current=[
171
+ "temperature_2m",
172
+ "relative_humidity_2m",
173
+ "weather_code",
174
+ "wind_speed_10m",
175
+ "wind_direction_10m",
176
+ "precipitation",
177
+ ],
178
+ )
179
+
180
+ # Weather code descriptions (WMO Weather interpretation codes)
181
+ weather_codes = {
182
+ 0: "Clear sky",
183
+ 1: "Mainly clear",
184
+ 2: "Partly cloudy",
185
+ 3: "Overcast",
186
+ 45: "Fog",
187
+ 48: "Depositing rime fog",
188
+ 51: "Light drizzle",
189
+ 53: "Moderate drizzle",
190
+ 55: "Dense drizzle",
191
+ 61: "Slight rain",
192
+ 63: "Moderate rain",
193
+ 65: "Heavy rain",
194
+ 71: "Slight snow fall",
195
+ 73: "Moderate snow fall",
196
+ 75: "Heavy snow fall",
197
+ 80: "Slight rain showers",
198
+ 81: "Moderate rain showers",
199
+ 82: "Violent rain showers",
200
+ 95: "Thunderstorm",
201
+ 96: "Thunderstorm with slight hail",
202
+ 99: "Thunderstorm with heavy hail",
203
+ }
204
+
205
+ current = weather_data.get("current", {})
206
+ weather_code = current.get("weather_code", 0)
207
+
208
+ simplified = {
209
+ "location": {
210
+ "latitude": weather_data.get("latitude"),
211
+ "longitude": weather_data.get("longitude"),
212
+ "timezone": weather_data.get("timezone"),
213
+ },
214
+ "current": {
215
+ "time": current.get("time"),
216
+ "temperature": current.get("temperature_2m"),
217
+ "temperature_unit": weather_data.get("current_units", {}).get("temperature_2m", "°C"),
218
+ "humidity": current.get("relative_humidity_2m"),
219
+ "weather_description": weather_codes.get(weather_code, "Unknown"),
220
+ "weather_code": weather_code,
221
+ "wind_speed": current.get("wind_speed_10m"),
222
+ "wind_speed_unit": weather_data.get("current_units", {}).get("wind_speed_10m", "km/h"),
223
+ "wind_direction": current.get("wind_direction_10m"),
224
+ "precipitation": current.get("precipitation", 0),
225
+ },
226
+ }
227
+
228
+ return simplified
229
+
230
+ except Exception as e:
231
+ return {"error": str(e)}
232
+
233
+ def list_tools(self):
234
+ """List all available tool methods in this application.
235
+
236
+ Returns:
237
+ list: A list of callable tool methods.
238
+ """
239
+ return [
240
+ self.get_current_time,
241
+ self.get_current_date,
242
+ self.calculate,
243
+ self.file_operations,
244
+ self.get_simple_weather,
245
+ ]
universal_mcp/cli.py CHANGED
@@ -4,7 +4,7 @@ import typer
4
4
  from rich.console import Console
5
5
  from rich.panel import Panel
6
6
 
7
- from universal_mcp.client.cli import app as client_app
7
+ from universal_mcp.agents.cli import app as client_app
8
8
  from universal_mcp.utils.installation import (
9
9
  get_supported_apps,
10
10
  install_app,
@@ -24,13 +24,20 @@ def run(
24
24
  config_path: Path | None = typer.Option(None, "--config", "-c", help="Path to the config file"),
25
25
  ):
26
26
  """Run the MCP server"""
27
+ from universal_mcp.agentr.server import AgentrServer
27
28
  from universal_mcp.config import ServerConfig
28
29
  from universal_mcp.logger import setup_logger
29
- from universal_mcp.servers import server_from_config
30
+ from universal_mcp.servers import LocalServer
30
31
 
31
32
  config = ServerConfig.model_validate_json(config_path.read_text()) if config_path else ServerConfig()
32
33
  setup_logger(level=config.log_level)
33
- server = server_from_config(config)
34
+
35
+ if config.type == "agentr":
36
+ server = AgentrServer(config=config, api_key=config.api_key)
37
+ elif config.type == "local":
38
+ server = LocalServer(config=config)
39
+ else:
40
+ raise ValueError(f"Unsupported server type: {config.type}")
34
41
  server.run(transport=config.transport)
35
42
 
36
43
 
universal_mcp/config.py CHANGED
@@ -23,7 +23,7 @@ class StoreConfig(BaseModel):
23
23
  )
24
24
  path: Path | None = Field(
25
25
  default=None,
26
- description="Filesystem path for store types that require it (e.g., a future 'file' store type). Currently not used by memory, environment, or keyring.",
26
+ description="Filesystem path for store types that require it (e.g., a future 'file' store type)",
27
27
  )
28
28
 
29
29
 
@@ -73,6 +73,21 @@ class AppConfig(BaseModel):
73
73
  description="A list of specific actions or tools provided by this application that should be exposed. If None or empty, all tools from the application might be exposed by default, depending on the application's implementation.",
74
74
  )
75
75
 
76
+ source_type: Literal["package", "local_folder", "remote_zip", "remote_file", "local_file"] = Field(
77
+ default="package",
78
+ description="The source of the application. 'package' (default) installs from a repository, 'local_folder' loads from a local path, 'remote_zip' downloads and extracts a project zip, 'remote_file' downloads a single Python file from a URL, 'local_file' loads a single Python file from the local filesystem.",
79
+ )
80
+ source_path: str | None = Field(
81
+ default=None,
82
+ description="The path or URL for 'local_folder', 'remote_zip', 'remote_file', or 'local_file' source types.",
83
+ )
84
+
85
+ @model_validator(mode="after")
86
+ def check_path_for_non_package_sources(self) -> Self:
87
+ if self.source_type in ["local_folder", "remote_zip", "remote_file", "local_file"] and not self.source_path:
88
+ raise ValueError(f"'source_path' is required for source_type '{self.source_type}'")
89
+ return self
90
+
76
91
 
77
92
  class ServerConfig(BaseSettings):
78
93
  """Core configuration settings for the Universal MCP server.
@@ -89,14 +104,13 @@ class ServerConfig(BaseSettings):
89
104
  case_sensitive=True,
90
105
  extra="allow",
91
106
  )
92
-
93
107
  name: str = Field(default="Universal MCP", description="Name of the MCP server")
94
108
  description: str = Field(
95
109
  default="Universal MCP", description="A brief description of this MCP server's purpose or deployment."
96
110
  )
97
111
  type: Literal["local", "agentr", "other"] = Field(
98
112
  default="agentr",
99
- description="Deployment type of the server. 'local' runs apps defined in 'apps' list; 'agentr' dynamically loads apps from the AgentR platform.",
113
+ description="Source of apps to load. Local apps are defined in 'apps' list; AgentR apps are dynamically loaded from the AgentR platform.",
100
114
  )
101
115
  transport: Literal["stdio", "sse", "streamable-http"] = Field(
102
116
  default="stdio",
@@ -148,7 +162,7 @@ class ServerConfig(BaseSettings):
148
162
  return v
149
163
 
150
164
  @classmethod
151
- def load_json_config(cls, path: str = "local_config.json") -> Self:
165
+ def load_json_config(cls, path: str = "server_config.json") -> Self:
152
166
  """Loads server configuration from a JSON file.
153
167
 
154
168
  Args:
@@ -221,16 +235,28 @@ class ClientConfig(BaseSettings):
221
235
 
222
236
  mcpServers: dict[str, ClientTransportConfig] = Field(
223
237
  ...,
224
- description="A dictionary where keys are descriptive names for MCP server connections and values are `ClientTransportConfig` objects defining how to connect to each server.",
238
+ description="Dictionary of MCP server connections. Keys are descriptive names for the server, values are `ClientTransportConfig` objects defining how to connect to each server.",
239
+ )
240
+ apps: list[AppConfig] = Field(
241
+ default=[],
242
+ description="List of application configurations to load",
243
+ )
244
+ store: StoreConfig | None = Field(
245
+ default=None,
246
+ description="Default credential store configuration for applications that do not define their own specific store.",
247
+ )
248
+ model: str = Field(
249
+ default="openrouter/auto",
250
+ description="The model to use for the LLM.",
225
251
  )
226
252
 
227
253
  @classmethod
228
- def load_json_config(cls, path: str = "servers.json") -> Self:
254
+ def load_json_config(cls, path: Path) -> Self:
229
255
  """Loads client configuration from a JSON file.
230
256
 
231
257
  Args:
232
258
  path (str, optional): The path to the JSON configuration file.
233
- Defaults to "servers.json".
259
+ Defaults to "client_config.json".
234
260
 
235
261
  Returns:
236
262
  ClientConfig: An instance of ClientConfig populated with data
@@ -26,6 +26,10 @@ class ToolError(Exception):
26
26
  pass
27
27
 
28
28
 
29
+ class ToolNotFoundError(Exception):
30
+ """Raised when a tool is not found"""
31
+
32
+
29
33
  class InvalidSignature(Exception):
30
34
  """Raised when a cryptographic signature verification fails.
31
35
 
@@ -1,26 +1,11 @@
1
- from universal_mcp.config import IntegrationConfig
2
1
  from universal_mcp.integrations.integration import (
3
- AgentRIntegration,
4
2
  ApiKeyIntegration,
5
3
  Integration,
6
4
  OAuthIntegration,
7
5
  )
8
- from universal_mcp.stores.store import BaseStore
9
-
10
-
11
- def integration_from_config(config: IntegrationConfig, store: BaseStore | None = None, **kwargs) -> Integration:
12
- if config.type == "api_key":
13
- return ApiKeyIntegration(config.name, store=store, **kwargs)
14
- elif config.type == "agentr":
15
- return AgentRIntegration(config.name, **kwargs)
16
- else:
17
- raise ValueError(f"Unsupported integration type: {config.type}")
18
-
19
6
 
20
7
  __all__ = [
21
- "AgentRIntegration",
22
8
  "ApiKeyIntegration",
23
9
  "Integration",
24
10
  "OAuthIntegration",
25
- "integration_from_config",
26
11
  ]