universal-mcp 0.1.15rc5__py3-none-any.whl → 0.1.16__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.
- universal_mcp/analytics.py +7 -1
- universal_mcp/applications/README.md +122 -0
- universal_mcp/applications/__init__.py +51 -56
- universal_mcp/applications/application.py +255 -82
- universal_mcp/cli.py +27 -43
- universal_mcp/config.py +16 -48
- universal_mcp/exceptions.py +8 -0
- universal_mcp/integrations/__init__.py +1 -3
- universal_mcp/integrations/integration.py +18 -2
- universal_mcp/logger.py +31 -29
- universal_mcp/servers/server.py +6 -18
- universal_mcp/stores/store.py +2 -12
- universal_mcp/tools/__init__.py +12 -1
- universal_mcp/tools/adapters.py +11 -0
- universal_mcp/tools/func_metadata.py +11 -15
- universal_mcp/tools/manager.py +163 -117
- universal_mcp/tools/tools.py +6 -13
- universal_mcp/utils/agentr.py +2 -6
- universal_mcp/utils/common.py +33 -0
- universal_mcp/utils/docstring_parser.py +4 -13
- universal_mcp/utils/installation.py +67 -184
- universal_mcp/utils/openapi/__inti__.py +0 -0
- universal_mcp/utils/{api_generator.py → openapi/api_generator.py} +2 -4
- universal_mcp/utils/{docgen.py → openapi/docgen.py} +17 -54
- universal_mcp/utils/openapi/openapi.py +882 -0
- universal_mcp/utils/openapi/preprocessor.py +1093 -0
- universal_mcp/utils/{readme.py → openapi/readme.py} +21 -37
- universal_mcp-0.1.16.dist-info/METADATA +282 -0
- universal_mcp-0.1.16.dist-info/RECORD +44 -0
- universal_mcp-0.1.16.dist-info/licenses/LICENSE +21 -0
- universal_mcp/utils/openapi.py +0 -646
- universal_mcp-0.1.15rc5.dist-info/METADATA +0 -245
- universal_mcp-0.1.15rc5.dist-info/RECORD +0 -39
- /universal_mcp/{templates → utils/templates}/README.md.j2 +0 -0
- /universal_mcp/{templates → utils/templates}/api_client.py.j2 +0 -0
- {universal_mcp-0.1.15rc5.dist-info → universal_mcp-0.1.16.dist-info}/WHEEL +0 -0
- {universal_mcp-0.1.15rc5.dist-info → universal_mcp-0.1.16.dist-info}/entry_points.txt +0 -0
@@ -1,7 +1,10 @@
|
|
1
1
|
from abc import ABC, abstractmethod
|
2
|
+
from collections.abc import Callable
|
3
|
+
from typing import Any
|
2
4
|
|
3
5
|
import httpx
|
4
|
-
from gql import Client
|
6
|
+
from gql import Client as GraphQLClient
|
7
|
+
from gql import gql
|
5
8
|
from gql.transport.requests import RequestsHTTPTransport
|
6
9
|
from graphql import DocumentNode
|
7
10
|
from loguru import logger
|
@@ -12,41 +15,93 @@ from universal_mcp.integrations import Integration
|
|
12
15
|
|
13
16
|
class BaseApplication(ABC):
|
14
17
|
"""
|
15
|
-
|
18
|
+
Base class for all applications in the Universal MCP system.
|
19
|
+
|
20
|
+
This abstract base class defines the common interface and functionality
|
21
|
+
that all applications must implement. It provides basic initialization
|
22
|
+
and credential management capabilities.
|
23
|
+
|
24
|
+
Attributes:
|
25
|
+
name (str): The name of the application
|
26
|
+
_credentials (Optional[Dict[str, Any]]): Cached credentials for the application
|
16
27
|
"""
|
17
28
|
|
18
|
-
def __init__(self, name: str, **kwargs):
|
29
|
+
def __init__(self, name: str, **kwargs: Any) -> None:
|
30
|
+
"""
|
31
|
+
Initialize the base application.
|
32
|
+
|
33
|
+
Args:
|
34
|
+
name: The name of the application
|
35
|
+
**kwargs: Additional keyword arguments passed to the application
|
36
|
+
"""
|
19
37
|
self.name = name
|
20
38
|
logger.debug(f"Initializing Application '{name}' with kwargs: {kwargs}")
|
21
39
|
analytics.track_app_loaded(name) # Track app loading
|
22
40
|
|
23
41
|
@abstractmethod
|
24
|
-
def list_tools(self):
|
42
|
+
def list_tools(self) -> list[Callable]:
|
43
|
+
"""
|
44
|
+
List all available tools for the application.
|
45
|
+
|
46
|
+
Returns:
|
47
|
+
List[Any]: A list of tools available in the application
|
48
|
+
"""
|
25
49
|
pass
|
26
50
|
|
27
51
|
|
28
52
|
class APIApplication(BaseApplication):
|
29
53
|
"""
|
30
|
-
|
54
|
+
Application that uses HTTP APIs to interact with external services.
|
55
|
+
|
56
|
+
This class provides a base implementation for applications that communicate
|
57
|
+
with external services via HTTP APIs. It handles authentication, request
|
58
|
+
management, and response processing.
|
59
|
+
|
60
|
+
Attributes:
|
61
|
+
name (str): The name of the application
|
62
|
+
integration (Optional[Integration]): The integration configuration
|
63
|
+
default_timeout (int): Default timeout for HTTP requests in seconds
|
64
|
+
base_url (str): Base URL for API requests
|
31
65
|
"""
|
32
66
|
|
33
|
-
def __init__(
|
67
|
+
def __init__(
|
68
|
+
self,
|
69
|
+
name: str,
|
70
|
+
integration: Integration | None = None,
|
71
|
+
client: httpx.Client | None = None,
|
72
|
+
**kwargs: Any,
|
73
|
+
) -> None:
|
74
|
+
"""
|
75
|
+
Initialize the API application.
|
76
|
+
|
77
|
+
Args:
|
78
|
+
name: The name of the application
|
79
|
+
integration: Optional integration configuration
|
80
|
+
**kwargs: Additional keyword arguments
|
81
|
+
"""
|
34
82
|
super().__init__(name, **kwargs)
|
35
|
-
self.default_timeout = 180
|
36
|
-
self.integration = integration
|
37
|
-
logger.debug(
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
83
|
+
self.default_timeout: int = 180
|
84
|
+
self.integration: Integration | None = integration
|
85
|
+
logger.debug(f"Initializing APIApplication '{name}' with integration: {integration}")
|
86
|
+
self._client: httpx.Client | None = client
|
87
|
+
self.base_url: str = ""
|
88
|
+
|
89
|
+
def _get_headers(self) -> dict[str, str]:
|
90
|
+
"""
|
91
|
+
Get the headers for API requests.
|
92
|
+
|
93
|
+
This method constructs the appropriate headers based on the available
|
94
|
+
credentials. It supports various authentication methods including
|
95
|
+
direct headers, API keys, and access tokens.
|
43
96
|
|
44
|
-
|
97
|
+
Returns:
|
98
|
+
Dict[str, str]: Headers to be used in API requests
|
99
|
+
"""
|
45
100
|
if not self.integration:
|
46
101
|
logger.debug("No integration configured, returning empty headers")
|
47
102
|
return {}
|
48
103
|
credentials = self.integration.get_credentials()
|
49
|
-
logger.debug(
|
104
|
+
logger.debug("Got credentials for integration")
|
50
105
|
|
51
106
|
# Check if direct headers are provided
|
52
107
|
headers = credentials.get("headers")
|
@@ -55,11 +110,7 @@ class APIApplication(BaseApplication):
|
|
55
110
|
return headers
|
56
111
|
|
57
112
|
# Check if api key is provided
|
58
|
-
api_key = (
|
59
|
-
credentials.get("api_key")
|
60
|
-
or credentials.get("API_KEY")
|
61
|
-
or credentials.get("apiKey")
|
62
|
-
)
|
113
|
+
api_key = credentials.get("api_key") or credentials.get("API_KEY") or credentials.get("apiKey")
|
63
114
|
if api_key:
|
64
115
|
logger.debug("Using API key from credentials")
|
65
116
|
return {
|
@@ -79,49 +130,87 @@ class APIApplication(BaseApplication):
|
|
79
130
|
return {}
|
80
131
|
|
81
132
|
@property
|
82
|
-
def client(self):
|
133
|
+
def client(self) -> httpx.Client:
|
134
|
+
"""
|
135
|
+
Get the HTTP client instance.
|
136
|
+
|
137
|
+
This property ensures that the HTTP client is properly initialized
|
138
|
+
with the correct base URL and headers.
|
139
|
+
|
140
|
+
Returns:
|
141
|
+
httpx.Client: The initialized HTTP client
|
142
|
+
"""
|
83
143
|
if not self._client:
|
84
144
|
headers = self._get_headers()
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
self.
|
89
|
-
|
90
|
-
)
|
91
|
-
else:
|
92
|
-
self._client = httpx.Client(
|
93
|
-
base_url=self.base_url, # Pass the base_url here
|
94
|
-
headers=headers,
|
95
|
-
timeout=self.default_timeout,
|
96
|
-
)
|
145
|
+
self._client = httpx.Client(
|
146
|
+
base_url=self.base_url,
|
147
|
+
headers=headers,
|
148
|
+
timeout=self.default_timeout,
|
149
|
+
)
|
97
150
|
return self._client
|
98
151
|
|
99
|
-
def _get(self, url, params=None):
|
152
|
+
def _get(self, url: str, params: dict[str, Any] | None = None) -> httpx.Response:
|
153
|
+
"""
|
154
|
+
Make a GET request to the specified URL.
|
155
|
+
|
156
|
+
Args:
|
157
|
+
url: The URL to send the request to
|
158
|
+
params: Optional query parameters
|
159
|
+
|
160
|
+
Returns:
|
161
|
+
httpx.Response: The response from the server
|
162
|
+
|
163
|
+
Raises:
|
164
|
+
httpx.HTTPError: If the request fails
|
165
|
+
"""
|
100
166
|
logger.debug(f"Making GET request to {url} with params: {params}")
|
101
167
|
response = self.client.get(url, params=params)
|
102
168
|
response.raise_for_status()
|
103
169
|
logger.debug(f"GET request successful with status code: {response.status_code}")
|
104
170
|
return response
|
105
171
|
|
106
|
-
def _post(self, url, data, params=None):
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
172
|
+
def _post(self, url: str, data: dict[str, Any], params: dict[str, Any] | None = None) -> httpx.Response:
|
173
|
+
"""
|
174
|
+
Make a POST request to the specified URL.
|
175
|
+
|
176
|
+
Args:
|
177
|
+
url: The URL to send the request to
|
178
|
+
data: The data to send in the request body
|
179
|
+
params: Optional query parameters
|
180
|
+
|
181
|
+
Returns:
|
182
|
+
httpx.Response: The response from the server
|
183
|
+
|
184
|
+
Raises:
|
185
|
+
httpx.HTTPError: If the request fails
|
186
|
+
"""
|
187
|
+
logger.debug(f"Making POST request to {url} with params: {params} and data: {data}")
|
188
|
+
response = httpx.post(
|
111
189
|
url,
|
190
|
+
headers=self._get_headers(),
|
112
191
|
json=data,
|
113
192
|
params=params,
|
114
193
|
)
|
115
194
|
response.raise_for_status()
|
116
|
-
logger.debug(
|
117
|
-
f"POST request successful with status code: {response.status_code}"
|
118
|
-
)
|
195
|
+
logger.debug(f"POST request successful with status code: {response.status_code}")
|
119
196
|
return response
|
120
197
|
|
121
|
-
def _put(self, url, data, params=None):
|
122
|
-
|
123
|
-
|
124
|
-
|
198
|
+
def _put(self, url: str, data: dict[str, Any], params: dict[str, Any] | None = None) -> httpx.Response:
|
199
|
+
"""
|
200
|
+
Make a PUT request to the specified URL.
|
201
|
+
|
202
|
+
Args:
|
203
|
+
url: The URL to send the request to
|
204
|
+
data: The data to send in the request body
|
205
|
+
params: Optional query parameters
|
206
|
+
|
207
|
+
Returns:
|
208
|
+
httpx.Response: The response from the server
|
209
|
+
|
210
|
+
Raises:
|
211
|
+
httpx.HTTPError: If the request fails
|
212
|
+
"""
|
213
|
+
logger.debug(f"Making PUT request to {url} with params: {params} and data: {data}")
|
125
214
|
response = self.client.put(
|
126
215
|
url,
|
127
216
|
json=data,
|
@@ -131,51 +220,100 @@ class APIApplication(BaseApplication):
|
|
131
220
|
logger.debug(f"PUT request successful with status code: {response.status_code}")
|
132
221
|
return response
|
133
222
|
|
134
|
-
def _delete(self, url, params=None):
|
135
|
-
|
223
|
+
def _delete(self, url: str, params: dict[str, Any] | None = None) -> httpx.Response:
|
224
|
+
"""
|
225
|
+
Make a DELETE request to the specified URL.
|
226
|
+
|
227
|
+
Args:
|
228
|
+
url: The URL to send the request to
|
229
|
+
params: Optional query parameters
|
230
|
+
|
231
|
+
Returns:
|
232
|
+
httpx.Response: The response from the server
|
233
|
+
|
234
|
+
Raises:
|
235
|
+
httpx.HTTPError: If the request fails
|
236
|
+
"""
|
136
237
|
logger.debug(f"Making DELETE request to {url} with params: {params}")
|
137
238
|
response = self.client.delete(url, params=params, timeout=self.default_timeout)
|
138
239
|
response.raise_for_status()
|
139
|
-
logger.debug(
|
140
|
-
f"DELETE request successful with status code: {response.status_code}"
|
141
|
-
)
|
240
|
+
logger.debug(f"DELETE request successful with status code: {response.status_code}")
|
142
241
|
return response
|
143
242
|
|
144
|
-
def _patch(self, url, data, params=None):
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
243
|
+
def _patch(self, url: str, data: dict[str, Any], params: dict[str, Any] | None = None) -> httpx.Response:
|
244
|
+
"""
|
245
|
+
Make a PATCH request to the specified URL.
|
246
|
+
|
247
|
+
Args:
|
248
|
+
url: The URL to send the request to
|
249
|
+
data: The data to send in the request body
|
250
|
+
params: Optional query parameters
|
251
|
+
|
252
|
+
Returns:
|
253
|
+
httpx.Response: The response from the server
|
254
|
+
|
255
|
+
Raises:
|
256
|
+
httpx.HTTPError: If the request fails
|
257
|
+
"""
|
258
|
+
logger.debug(f"Making PATCH request to {url} with params: {params} and data: {data}")
|
149
259
|
response = self.client.patch(
|
150
260
|
url,
|
151
261
|
json=data,
|
152
262
|
params=params,
|
153
263
|
)
|
154
264
|
response.raise_for_status()
|
155
|
-
logger.debug(
|
156
|
-
f"PATCH request successful with status code: {response.status_code}"
|
157
|
-
)
|
265
|
+
logger.debug(f"PATCH request successful with status code: {response.status_code}")
|
158
266
|
return response
|
159
267
|
|
160
|
-
def validate(self):
|
161
|
-
pass
|
162
|
-
|
163
268
|
|
164
269
|
class GraphQLApplication(BaseApplication):
|
165
270
|
"""
|
166
|
-
|
271
|
+
Application that uses GraphQL to interact with external services.
|
272
|
+
|
273
|
+
This class provides a base implementation for applications that communicate
|
274
|
+
with external services via GraphQL. It handles authentication, query execution,
|
275
|
+
and response processing.
|
276
|
+
|
277
|
+
Attributes:
|
278
|
+
name (str): The name of the application
|
279
|
+
base_url (str): Base URL for GraphQL endpoint
|
280
|
+
integration (Optional[Integration]): The integration configuration
|
167
281
|
"""
|
168
282
|
|
169
283
|
def __init__(
|
170
|
-
self,
|
171
|
-
|
284
|
+
self,
|
285
|
+
name: str,
|
286
|
+
base_url: str,
|
287
|
+
integration: Integration | None = None,
|
288
|
+
client: GraphQLClient | None = None,
|
289
|
+
**kwargs: Any,
|
290
|
+
) -> None:
|
291
|
+
"""
|
292
|
+
Initialize the GraphQL application.
|
293
|
+
|
294
|
+
Args:
|
295
|
+
name: The name of the application
|
296
|
+
base_url: The base URL for the GraphQL endpoint
|
297
|
+
integration: Optional integration configuration
|
298
|
+
**kwargs: Additional keyword arguments
|
299
|
+
"""
|
172
300
|
super().__init__(name, **kwargs)
|
173
301
|
self.base_url = base_url
|
302
|
+
self.integration = integration
|
174
303
|
logger.debug(f"Initializing Application '{name}' with kwargs: {kwargs}")
|
175
|
-
|
176
|
-
self._client = None
|
304
|
+
self._client: GraphQLClient | None = client
|
177
305
|
|
178
|
-
def _get_headers(self):
|
306
|
+
def _get_headers(self) -> dict[str, str]:
|
307
|
+
"""
|
308
|
+
Get the headers for GraphQL requests.
|
309
|
+
|
310
|
+
This method constructs the appropriate headers based on the available
|
311
|
+
credentials. It supports various authentication methods including
|
312
|
+
direct headers, API keys, and access tokens.
|
313
|
+
|
314
|
+
Returns:
|
315
|
+
Dict[str, str]: Headers to be used in GraphQL requests
|
316
|
+
"""
|
179
317
|
if not self.integration:
|
180
318
|
logger.debug("No integration configured, returning empty headers")
|
181
319
|
return {}
|
@@ -189,11 +327,7 @@ class GraphQLApplication(BaseApplication):
|
|
189
327
|
return headers
|
190
328
|
|
191
329
|
# Check if api key is provided
|
192
|
-
api_key = (
|
193
|
-
credentials.get("api_key")
|
194
|
-
or credentials.get("API_KEY")
|
195
|
-
or credentials.get("apiKey")
|
196
|
-
)
|
330
|
+
api_key = credentials.get("api_key") or credentials.get("API_KEY") or credentials.get("apiKey")
|
197
331
|
if api_key:
|
198
332
|
logger.debug("Using API key from credentials")
|
199
333
|
return {
|
@@ -211,23 +345,62 @@ class GraphQLApplication(BaseApplication):
|
|
211
345
|
return {}
|
212
346
|
|
213
347
|
@property
|
214
|
-
def client(self):
|
348
|
+
def client(self) -> GraphQLClient:
|
349
|
+
"""
|
350
|
+
Get the GraphQL client instance.
|
351
|
+
|
352
|
+
This property ensures that the GraphQL client is properly initialized
|
353
|
+
with the correct transport and headers.
|
354
|
+
|
355
|
+
Returns:
|
356
|
+
Client: The initialized GraphQL client
|
357
|
+
"""
|
215
358
|
if not self._client:
|
216
359
|
headers = self._get_headers()
|
217
360
|
transport = RequestsHTTPTransport(url=self.base_url, headers=headers)
|
218
|
-
self._client =
|
361
|
+
self._client = GraphQLClient(transport=transport, fetch_schema_from_transport=True)
|
219
362
|
return self._client
|
220
363
|
|
221
|
-
def mutate(
|
364
|
+
def mutate(
|
365
|
+
self,
|
366
|
+
mutation: str | DocumentNode,
|
367
|
+
variables: dict[str, Any] | None = None,
|
368
|
+
) -> dict[str, Any]:
|
369
|
+
"""
|
370
|
+
Execute a GraphQL mutation.
|
371
|
+
|
372
|
+
Args:
|
373
|
+
mutation: The GraphQL mutation string or DocumentNode
|
374
|
+
variables: Optional variables for the mutation
|
375
|
+
|
376
|
+
Returns:
|
377
|
+
Dict[str, Any]: The result of the mutation
|
378
|
+
|
379
|
+
Raises:
|
380
|
+
Exception: If the mutation execution fails
|
381
|
+
"""
|
222
382
|
if isinstance(mutation, str):
|
223
383
|
mutation = gql(mutation)
|
224
384
|
return self.client.execute(mutation, variable_values=variables)
|
225
385
|
|
226
|
-
def query(
|
386
|
+
def query(
|
387
|
+
self,
|
388
|
+
query: str | DocumentNode,
|
389
|
+
variables: dict[str, Any] | None = None,
|
390
|
+
) -> dict[str, Any]:
|
391
|
+
"""
|
392
|
+
Execute a GraphQL query.
|
393
|
+
|
394
|
+
Args:
|
395
|
+
query: The GraphQL query string or DocumentNode
|
396
|
+
variables: Optional variables for the query
|
397
|
+
|
398
|
+
Returns:
|
399
|
+
Dict[str, Any]: The result of the query
|
400
|
+
|
401
|
+
Raises:
|
402
|
+
Exception: If the query execution fails
|
403
|
+
"""
|
227
404
|
if isinstance(query, str):
|
228
405
|
query = gql(query)
|
229
406
|
return self.client.execute(query, variable_values=variables)
|
230
|
-
|
231
|
-
@abstractmethod
|
232
|
-
def list_tools(self):
|
233
|
-
pass
|
universal_mcp/cli.py
CHANGED
@@ -7,8 +7,7 @@ from rich.panel import Panel
|
|
7
7
|
|
8
8
|
from universal_mcp.utils.installation import (
|
9
9
|
get_supported_apps,
|
10
|
-
|
11
|
-
install_cursor,
|
10
|
+
install_app,
|
12
11
|
)
|
13
12
|
|
14
13
|
# Setup rich console and logging
|
@@ -39,7 +38,7 @@ def generate(
|
|
39
38
|
This name will be used for the folder in applications/.
|
40
39
|
"""
|
41
40
|
# Import here to avoid circular imports
|
42
|
-
from universal_mcp.utils.api_generator import generate_api_from_schema
|
41
|
+
from universal_mcp.utils.openapi.api_generator import generate_api_from_schema
|
43
42
|
|
44
43
|
if not schema_path.exists():
|
45
44
|
console.print(f"[red]Error: Schema file {schema_path} does not exist[/red]")
|
@@ -62,17 +61,11 @@ def generate(
|
|
62
61
|
@app.command()
|
63
62
|
def readme(
|
64
63
|
file_path: Path = typer.Argument(..., help="Path to the Python file to process"),
|
65
|
-
class_name: str = typer.Option(
|
66
|
-
None,
|
67
|
-
"--class-name",
|
68
|
-
"-c",
|
69
|
-
help="Class name to use for the API client",
|
70
|
-
),
|
71
64
|
):
|
72
65
|
"""Generate a README.md file for the API client."""
|
73
|
-
from universal_mcp.utils.readme import generate_readme
|
66
|
+
from universal_mcp.utils.openapi.readme import generate_readme
|
74
67
|
|
75
|
-
readme_file = generate_readme(file_path
|
68
|
+
readme_file = generate_readme(file_path)
|
76
69
|
console.print(f"[green]README.md file generated at: {readme_file}[/green]")
|
77
70
|
|
78
71
|
|
@@ -91,7 +84,7 @@ def docgen(
|
|
91
84
|
This command uses litellm with structured output to generate high-quality
|
92
85
|
Google-style docstrings for all functions in the specified Python file.
|
93
86
|
"""
|
94
|
-
from universal_mcp.utils.docgen import process_file
|
87
|
+
from universal_mcp.utils.openapi.docgen import process_file
|
95
88
|
|
96
89
|
if not file_path.exists():
|
97
90
|
console.print(f"[red]Error: File not found: {file_path}[/red]")
|
@@ -107,9 +100,7 @@ def docgen(
|
|
107
100
|
|
108
101
|
@app.command()
|
109
102
|
def run(
|
110
|
-
config_path: Path | None = typer.Option(
|
111
|
-
None, "--config", "-c", help="Path to the config file"
|
112
|
-
),
|
103
|
+
config_path: Path | None = typer.Option(None, "--config", "-c", help="Path to the config file"),
|
113
104
|
):
|
114
105
|
"""Run the MCP server"""
|
115
106
|
from universal_mcp.config import ServerConfig
|
@@ -118,10 +109,7 @@ def run(
|
|
118
109
|
|
119
110
|
setup_logger()
|
120
111
|
|
121
|
-
if config_path
|
122
|
-
config = ServerConfig.model_validate_json(config_path.read_text())
|
123
|
-
else:
|
124
|
-
config = ServerConfig()
|
112
|
+
config = ServerConfig.model_validate_json(config_path.read_text()) if config_path else ServerConfig()
|
125
113
|
server = server_from_config(config)
|
126
114
|
server.run(transport=config.transport)
|
127
115
|
|
@@ -157,14 +145,7 @@ def install(app_name: str = typer.Argument(..., help="Name of app to install")):
|
|
157
145
|
type=str,
|
158
146
|
)
|
159
147
|
try:
|
160
|
-
|
161
|
-
console.print(f"[blue]Installing mcp server for: {app_name}[/blue]")
|
162
|
-
install_claude(api_key)
|
163
|
-
console.print("[green]App installed successfully[/green]")
|
164
|
-
elif app_name == "cursor":
|
165
|
-
console.print(f"[blue]Installing mcp server for: {app_name}[/blue]")
|
166
|
-
install_cursor(api_key)
|
167
|
-
console.print("[green]App installed successfully[/green]")
|
148
|
+
install_app(app_name, api_key)
|
168
149
|
except Exception as e:
|
169
150
|
console.print(f"[red]Error installing app: {e}[/red]")
|
170
151
|
raise typer.Exit(1) from e
|
@@ -213,7 +194,7 @@ def init(
|
|
213
194
|
prompt_suffix=" (e.g., reddit, youtube): ",
|
214
195
|
).strip()
|
215
196
|
validate_pattern(app_name, "app name")
|
216
|
-
|
197
|
+
app_name = app_name.lower()
|
217
198
|
if not output_dir:
|
218
199
|
path_str = typer.prompt(
|
219
200
|
"Enter the output directory for the project",
|
@@ -225,18 +206,12 @@ def init(
|
|
225
206
|
if not output_dir.exists():
|
226
207
|
try:
|
227
208
|
output_dir.mkdir(parents=True, exist_ok=True)
|
228
|
-
console.print(
|
229
|
-
f"[green]✅ Created output directory at '{output_dir}'[/green]"
|
230
|
-
)
|
209
|
+
console.print(f"[green]✅ Created output directory at '{output_dir}'[/green]")
|
231
210
|
except Exception as e:
|
232
|
-
console.print(
|
233
|
-
f"[red]❌ Failed to create output directory '{output_dir}': {e}[/red]"
|
234
|
-
)
|
211
|
+
console.print(f"[red]❌ Failed to create output directory '{output_dir}': {e}[/red]")
|
235
212
|
raise typer.Exit(code=1) from e
|
236
213
|
elif not output_dir.is_dir():
|
237
|
-
console.print(
|
238
|
-
f"[red]❌ Output path '{output_dir}' exists but is not a directory.[/red]"
|
239
|
-
)
|
214
|
+
console.print(f"[red]❌ Output path '{output_dir}' exists but is not a directory.[/red]")
|
240
215
|
raise typer.Exit(code=1)
|
241
216
|
|
242
217
|
# Integration type
|
@@ -247,9 +222,7 @@ def init(
|
|
247
222
|
prompt_suffix=" (api_key, oauth, agentr, none): ",
|
248
223
|
).lower()
|
249
224
|
if integration_type not in ("api_key", "oauth", "agentr", "none"):
|
250
|
-
console.print(
|
251
|
-
"[red]❌ Integration type must be one of: api_key, oauth, agentr, none[/red]"
|
252
|
-
)
|
225
|
+
console.print("[red]❌ Integration type must be one of: api_key, oauth, agentr, none[/red]")
|
253
226
|
raise typer.Exit(code=1)
|
254
227
|
|
255
228
|
console.print("[blue]🚀 Generating project using cookiecutter...[/blue]")
|
@@ -264,11 +237,22 @@ def init(
|
|
264
237
|
},
|
265
238
|
)
|
266
239
|
except Exception as exc:
|
267
|
-
console.print(f"❌ Project generation failed: {exc}"
|
240
|
+
console.print(f"❌ Project generation failed: {exc}")
|
268
241
|
raise typer.Exit(code=1) from exc
|
269
242
|
|
270
|
-
project_dir = output_dir / f"
|
271
|
-
console.print(f"✅ Project created at {project_dir}"
|
243
|
+
project_dir = output_dir / f"{app_name}"
|
244
|
+
console.print(f"✅ Project created at {project_dir}")
|
245
|
+
|
246
|
+
|
247
|
+
@app.command()
|
248
|
+
def preprocess(
|
249
|
+
schema_path: Path = typer.Option(None, "--schema", "-s", help="Path to the OpenAPI schema file."),
|
250
|
+
output_path: Path = typer.Option(None, "--output", "-o", help="Path to save the processed schema."),
|
251
|
+
):
|
252
|
+
from universal_mcp.utils.openapi.preprocessor import run_preprocessing
|
253
|
+
|
254
|
+
"""Preprocess an OpenAPI schema using LLM to fill or enhance descriptions."""
|
255
|
+
run_preprocessing(schema_path, output_path)
|
272
256
|
|
273
257
|
|
274
258
|
if __name__ == "__main__":
|