universal-mcp 0.1.7rc2__py3-none-any.whl → 0.1.8__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/__init__.py +0 -2
- universal_mcp/analytics.py +75 -0
- universal_mcp/applications/ahrefs/README.md +76 -0
- universal_mcp/applications/ahrefs/app.py +2291 -0
- universal_mcp/applications/application.py +95 -5
- universal_mcp/applications/calendly/README.md +78 -0
- universal_mcp/applications/calendly/__init__.py +0 -0
- universal_mcp/applications/calendly/app.py +1195 -0
- universal_mcp/applications/coda/README.md +133 -0
- universal_mcp/applications/coda/__init__.py +0 -0
- universal_mcp/applications/coda/app.py +3671 -0
- universal_mcp/applications/e2b/app.py +14 -35
- universal_mcp/applications/figma/README.md +74 -0
- universal_mcp/applications/figma/__init__.py +0 -0
- universal_mcp/applications/figma/app.py +1261 -0
- universal_mcp/applications/firecrawl/app.py +29 -32
- universal_mcp/applications/github/app.py +127 -85
- universal_mcp/applications/google_calendar/app.py +62 -138
- universal_mcp/applications/google_docs/app.py +47 -52
- universal_mcp/applications/google_drive/app.py +119 -113
- universal_mcp/applications/google_mail/app.py +124 -50
- universal_mcp/applications/google_sheet/app.py +89 -91
- universal_mcp/applications/markitdown/app.py +9 -8
- universal_mcp/applications/notion/app.py +254 -134
- universal_mcp/applications/perplexity/app.py +13 -45
- universal_mcp/applications/reddit/app.py +94 -85
- universal_mcp/applications/resend/app.py +12 -23
- universal_mcp/applications/{serp → serpapi}/app.py +14 -33
- universal_mcp/applications/tavily/app.py +11 -28
- universal_mcp/applications/wrike/README.md +71 -0
- universal_mcp/applications/wrike/__init__.py +0 -0
- universal_mcp/applications/wrike/app.py +1372 -0
- universal_mcp/applications/youtube/README.md +82 -0
- universal_mcp/applications/youtube/__init__.py +0 -0
- universal_mcp/applications/youtube/app.py +1428 -0
- universal_mcp/applications/zenquotes/app.py +12 -2
- universal_mcp/exceptions.py +9 -2
- universal_mcp/integrations/__init__.py +24 -1
- universal_mcp/integrations/agentr.py +27 -4
- universal_mcp/integrations/integration.py +143 -30
- universal_mcp/logger.py +3 -56
- universal_mcp/servers/__init__.py +6 -14
- universal_mcp/servers/server.py +201 -146
- universal_mcp/stores/__init__.py +7 -2
- universal_mcp/stores/store.py +103 -40
- universal_mcp/tools/__init__.py +3 -0
- universal_mcp/tools/adapters.py +43 -0
- universal_mcp/tools/func_metadata.py +213 -0
- universal_mcp/tools/tools.py +342 -0
- universal_mcp/utils/docgen.py +325 -119
- universal_mcp/utils/docstring_parser.py +179 -0
- universal_mcp/utils/dump_app_tools.py +33 -23
- universal_mcp/utils/installation.py +199 -8
- universal_mcp/utils/openapi.py +229 -46
- {universal_mcp-0.1.7rc2.dist-info → universal_mcp-0.1.8.dist-info}/METADATA +9 -5
- universal_mcp-0.1.8.dist-info/RECORD +81 -0
- universal_mcp-0.1.7rc2.dist-info/RECORD +0 -58
- /universal_mcp/{utils/bridge.py → applications/ahrefs/__init__.py} +0 -0
- /universal_mcp/applications/{serp → serpapi}/README.md +0 -0
- {universal_mcp-0.1.7rc2.dist-info → universal_mcp-0.1.8.dist-info}/WHEEL +0 -0
- {universal_mcp-0.1.7rc2.dist-info → universal_mcp-0.1.8.dist-info}/entry_points.txt +0 -0
@@ -6,10 +6,20 @@ class ZenquotesApp(APIApplication):
|
|
6
6
|
super().__init__(name="zenquote", **kwargs)
|
7
7
|
|
8
8
|
def get_quote(self) -> str:
|
9
|
-
"""
|
9
|
+
"""
|
10
|
+
Fetches a random inspirational quote from the Zen Quotes API.
|
10
11
|
|
11
12
|
Returns:
|
12
|
-
A
|
13
|
+
A formatted string containing the quote and its author in the format 'quote - author'
|
14
|
+
|
15
|
+
Raises:
|
16
|
+
RequestException: If the HTTP request to the Zen Quotes API fails
|
17
|
+
JSONDecodeError: If the API response contains invalid JSON
|
18
|
+
IndexError: If the API response doesn't contain any quotes
|
19
|
+
KeyError: If the quote data doesn't contain the expected 'q' or 'a' fields
|
20
|
+
|
21
|
+
Tags:
|
22
|
+
fetch, quotes, api, http, important
|
13
23
|
"""
|
14
24
|
url = "https://zenquotes.io/api/random"
|
15
25
|
response = self._get(url)
|
universal_mcp/exceptions.py
CHANGED
@@ -1,6 +1,13 @@
|
|
1
1
|
class NotAuthorizedError(Exception):
|
2
2
|
"""Raised when a user is not authorized to access a resource or perform an action."""
|
3
3
|
|
4
|
-
def __init__(self, message
|
4
|
+
def __init__(self, message: str):
|
5
5
|
self.message = message
|
6
|
-
|
6
|
+
|
7
|
+
|
8
|
+
class ToolError(Exception):
|
9
|
+
"""Raised when a tool is not found or fails to execute."""
|
10
|
+
|
11
|
+
|
12
|
+
class InvalidSignature(Exception):
|
13
|
+
"""Raised when a signature is invalid."""
|
@@ -1,8 +1,31 @@
|
|
1
|
+
from universal_mcp.config import IntegrationConfig
|
1
2
|
from universal_mcp.integrations.agentr import AgentRIntegration
|
2
3
|
from universal_mcp.integrations.integration import (
|
3
4
|
ApiKeyIntegration,
|
4
5
|
Integration,
|
5
6
|
OAuthIntegration,
|
6
7
|
)
|
8
|
+
from universal_mcp.stores.store import BaseStore
|
7
9
|
|
8
|
-
|
10
|
+
|
11
|
+
def integration_from_config(
|
12
|
+
config: IntegrationConfig, store: BaseStore | None = None, **kwargs
|
13
|
+
) -> Integration:
|
14
|
+
if config.type == "api_key":
|
15
|
+
return ApiKeyIntegration(config.name, store=store, **kwargs)
|
16
|
+
elif config.type == "agentr":
|
17
|
+
api_key = kwargs.get("api_key")
|
18
|
+
if not api_key:
|
19
|
+
raise ValueError("api_key is required for AgentR integration")
|
20
|
+
return AgentRIntegration(config.name, api_key=api_key)
|
21
|
+
else:
|
22
|
+
raise ValueError(f"Unsupported integration type: {config.type}")
|
23
|
+
|
24
|
+
|
25
|
+
__all__ = [
|
26
|
+
"AgentRIntegration",
|
27
|
+
"Integration",
|
28
|
+
"ApiKeyIntegration",
|
29
|
+
"OAuthIntegration",
|
30
|
+
"integration_from_config",
|
31
|
+
]
|
@@ -29,7 +29,10 @@ class AgentRIntegration(Integration):
|
|
29
29
|
"API key for AgentR is missing. Please visit https://agentr.dev to create an API key, then set it as AGENTR_API_KEY environment variable."
|
30
30
|
)
|
31
31
|
raise ValueError("AgentR API key required - get one at https://agentr.dev")
|
32
|
-
self.base_url = os.getenv("AGENTR_BASE_URL", "https://api.agentr.dev")
|
32
|
+
self.base_url = os.getenv("AGENTR_BASE_URL", "https://api.agentr.dev").rstrip(
|
33
|
+
"/"
|
34
|
+
)
|
35
|
+
self._credentials = None
|
33
36
|
|
34
37
|
def set_credentials(self, credentials: dict | None = None):
|
35
38
|
"""Set credentials for the integration.
|
@@ -43,9 +46,9 @@ class AgentRIntegration(Integration):
|
|
43
46
|
str: Authorization URL from authorize() method
|
44
47
|
"""
|
45
48
|
return self.authorize()
|
46
|
-
# raise NotImplementedError("AgentR Integration does not support setting credentials. Visit the authorize url to set credentials.")
|
47
49
|
|
48
|
-
|
50
|
+
@property
|
51
|
+
def credentials(self):
|
49
52
|
"""Get credentials for the integration from the AgentR API.
|
50
53
|
|
51
54
|
Makes API request to retrieve stored credentials for this integration.
|
@@ -57,16 +60,36 @@ class AgentRIntegration(Integration):
|
|
57
60
|
NotAuthorizedError: If credentials are not found (404 response)
|
58
61
|
HTTPError: For other API errors
|
59
62
|
"""
|
63
|
+
if self._credentials is not None:
|
64
|
+
return self._credentials
|
60
65
|
response = httpx.get(
|
61
66
|
f"{self.base_url}/api/{self.name}/credentials/",
|
62
67
|
headers={"accept": "application/json", "X-API-KEY": self.api_key},
|
63
68
|
)
|
64
69
|
if response.status_code == 404:
|
70
|
+
logger.warning(
|
71
|
+
f"No credentials found for {self.name}. Requesting authorization..."
|
72
|
+
)
|
65
73
|
action = self.authorize()
|
66
74
|
raise NotAuthorizedError(action)
|
67
75
|
response.raise_for_status()
|
68
76
|
data = response.json()
|
69
|
-
|
77
|
+
self._credentials = data
|
78
|
+
return self._credentials
|
79
|
+
|
80
|
+
def get_credentials(self):
|
81
|
+
"""Get credentials for the integration from the AgentR API.
|
82
|
+
|
83
|
+
Makes API request to retrieve stored credentials for this integration.
|
84
|
+
|
85
|
+
Returns:
|
86
|
+
dict: Credentials data from API response
|
87
|
+
|
88
|
+
Raises:
|
89
|
+
NotAuthorizedError: If credentials are not found (404 response)
|
90
|
+
HTTPError: For other API errors
|
91
|
+
"""
|
92
|
+
return self.credentials
|
70
93
|
|
71
94
|
def authorize(self):
|
72
95
|
"""Get authorization URL for the integration.
|
@@ -1,10 +1,12 @@
|
|
1
1
|
from abc import ABC, abstractmethod
|
2
|
+
from typing import Any
|
2
3
|
|
3
4
|
import httpx
|
4
5
|
from loguru import logger
|
5
6
|
|
6
7
|
from universal_mcp.exceptions import NotAuthorizedError
|
7
|
-
from universal_mcp.stores
|
8
|
+
from universal_mcp.stores import BaseStore
|
9
|
+
from universal_mcp.stores.store import KeyNotFoundError
|
8
10
|
|
9
11
|
|
10
12
|
def sanitize_api_key_name(name: str) -> str:
|
@@ -30,37 +32,43 @@ class Integration(ABC):
|
|
30
32
|
store: Store instance for persisting credentials and other data
|
31
33
|
"""
|
32
34
|
|
33
|
-
def __init__(self, name: str, store:
|
35
|
+
def __init__(self, name: str, store: BaseStore | None = None):
|
34
36
|
self.name = name
|
35
37
|
self.store = store
|
36
38
|
|
37
39
|
@abstractmethod
|
38
|
-
def authorize(self):
|
40
|
+
def authorize(self) -> str | dict[str, Any]:
|
39
41
|
"""Authorize the integration.
|
40
42
|
|
41
43
|
Returns:
|
42
|
-
str: Authorization URL.
|
44
|
+
Union[str, Dict[str, Any]]: Authorization URL or parameters needed for authorization.
|
45
|
+
|
46
|
+
Raises:
|
47
|
+
ValueError: If required configuration is missing.
|
43
48
|
"""
|
44
49
|
pass
|
45
50
|
|
46
51
|
@abstractmethod
|
47
|
-
def get_credentials(self):
|
52
|
+
def get_credentials(self) -> dict[str, Any]:
|
48
53
|
"""Get credentials for the integration.
|
49
54
|
|
50
55
|
Returns:
|
51
|
-
|
56
|
+
Dict[str, Any]: Credentials for the integration.
|
52
57
|
|
53
58
|
Raises:
|
54
|
-
NotAuthorizedError: If credentials are not found.
|
59
|
+
NotAuthorizedError: If credentials are not found or invalid.
|
55
60
|
"""
|
56
61
|
pass
|
57
62
|
|
58
63
|
@abstractmethod
|
59
|
-
def set_credentials(self, credentials: dict):
|
64
|
+
def set_credentials(self, credentials: dict[str, Any]) -> None:
|
60
65
|
"""Set credentials for the integration.
|
61
66
|
|
62
67
|
Args:
|
63
|
-
credentials:
|
68
|
+
credentials: Dictionary containing credentials for the integration.
|
69
|
+
|
70
|
+
Raises:
|
71
|
+
ValueError: If credentials are invalid or missing required fields.
|
64
72
|
"""
|
65
73
|
pass
|
66
74
|
|
@@ -82,35 +90,93 @@ class ApiKeyIntegration(Integration):
|
|
82
90
|
store: Store instance for persisting credentials and other data
|
83
91
|
"""
|
84
92
|
|
85
|
-
def __init__(self, name: str, store:
|
93
|
+
def __init__(self, name: str, store: BaseStore | None = None, **kwargs):
|
94
|
+
self.type = "api_key"
|
86
95
|
sanitized_name = sanitize_api_key_name(name)
|
87
96
|
super().__init__(sanitized_name, store, **kwargs)
|
88
97
|
logger.info(f"Initializing API Key Integration: {name} with store: {store}")
|
98
|
+
self._api_key: str | None = None
|
99
|
+
|
100
|
+
@property
|
101
|
+
def api_key(self) -> str | None:
|
102
|
+
if not self._api_key:
|
103
|
+
try:
|
104
|
+
credentials = self.store.get(self.name)
|
105
|
+
self.api_key = credentials
|
106
|
+
except KeyNotFoundError as e:
|
107
|
+
action = self.authorize()
|
108
|
+
raise NotAuthorizedError(action) from e
|
109
|
+
return self._api_key
|
110
|
+
|
111
|
+
def get_credentials(self) -> dict[str, str]:
|
112
|
+
"""Get API key credentials.
|
89
113
|
|
90
|
-
|
91
|
-
|
92
|
-
if credentials is None:
|
93
|
-
action = self.authorize()
|
94
|
-
raise NotAuthorizedError(action)
|
95
|
-
return {"api_key": credentials}
|
114
|
+
Returns:
|
115
|
+
Dict[str, str]: Dictionary containing the API key.
|
96
116
|
|
97
|
-
|
117
|
+
Raises:
|
118
|
+
NotAuthorizedError: If API key is not found.
|
119
|
+
"""
|
120
|
+
return {"api_key": self.api_key}
|
121
|
+
|
122
|
+
def set_credentials(self, credentials: dict[str, Any]) -> None:
|
123
|
+
"""Set API key credentials.
|
124
|
+
|
125
|
+
Args:
|
126
|
+
credentials: Dictionary containing the API key.
|
127
|
+
|
128
|
+
Raises:
|
129
|
+
ValueError: If credentials are invalid or missing API key.
|
130
|
+
"""
|
131
|
+
if not credentials or not isinstance(credentials, dict):
|
132
|
+
raise ValueError("Invalid credentials format")
|
98
133
|
self.store.set(self.name, credentials)
|
99
134
|
|
100
|
-
def authorize(self):
|
135
|
+
def authorize(self) -> str:
|
136
|
+
"""Get authorization instructions for API key.
|
137
|
+
|
138
|
+
Returns:
|
139
|
+
str: Instructions for setting up API key.
|
140
|
+
"""
|
101
141
|
return f"Please ask the user for api key and set the API Key for {self.name} in the store"
|
102
142
|
|
103
143
|
|
104
144
|
class OAuthIntegration(Integration):
|
145
|
+
"""Integration class for OAuth based authentication.
|
146
|
+
|
147
|
+
This class implements the Integration interface for services that use OAuth
|
148
|
+
authentication. It handles the OAuth flow including authorization, token exchange,
|
149
|
+
and token refresh.
|
150
|
+
|
151
|
+
Args:
|
152
|
+
name: The name identifier for this integration
|
153
|
+
store: Optional Store instance for persisting credentials and other data
|
154
|
+
client_id: OAuth client ID
|
155
|
+
client_secret: OAuth client secret
|
156
|
+
auth_url: OAuth authorization URL
|
157
|
+
token_url: OAuth token exchange URL
|
158
|
+
scope: OAuth scope string
|
159
|
+
**kwargs: Additional keyword arguments passed to parent class
|
160
|
+
|
161
|
+
Attributes:
|
162
|
+
name: The name identifier for this integration
|
163
|
+
store: Store instance for persisting credentials and other data
|
164
|
+
client_id: OAuth client ID
|
165
|
+
client_secret: OAuth client secret
|
166
|
+
auth_url: OAuth authorization URL
|
167
|
+
token_url: OAuth token exchange URL
|
168
|
+
scope: OAuth scope string
|
169
|
+
"""
|
170
|
+
|
105
171
|
def __init__(
|
106
172
|
self,
|
107
173
|
name: str,
|
108
|
-
store:
|
109
|
-
client_id: str = None,
|
110
|
-
client_secret: str = None,
|
111
|
-
auth_url: str = None,
|
112
|
-
token_url: str = None,
|
113
|
-
scope: str = None,
|
174
|
+
store: BaseStore | None = None,
|
175
|
+
client_id: str | None = None,
|
176
|
+
client_secret: str | None = None,
|
177
|
+
auth_url: str | None = None,
|
178
|
+
token_url: str | None = None,
|
179
|
+
scope: str | None = None,
|
114
180
|
**kwargs,
|
115
181
|
):
|
116
182
|
super().__init__(name, store, **kwargs)
|
@@ -120,20 +186,41 @@ class OAuthIntegration(Integration):
|
|
120
186
|
self.token_url = token_url
|
121
187
|
self.scope = scope
|
122
188
|
|
123
|
-
def get_credentials(self):
|
189
|
+
def get_credentials(self) -> dict[str, Any] | None:
|
190
|
+
"""Get OAuth credentials.
|
191
|
+
|
192
|
+
Returns:
|
193
|
+
Optional[Dict[str, Any]]: Dictionary containing OAuth tokens if found, None otherwise.
|
194
|
+
"""
|
124
195
|
credentials = self.store.get(self.name)
|
125
196
|
if not credentials:
|
126
197
|
return None
|
127
198
|
return credentials
|
128
199
|
|
129
|
-
def set_credentials(self, credentials: dict):
|
200
|
+
def set_credentials(self, credentials: dict[str, Any]) -> None:
|
201
|
+
"""Set OAuth credentials.
|
202
|
+
|
203
|
+
Args:
|
204
|
+
credentials: Dictionary containing OAuth tokens.
|
205
|
+
|
206
|
+
Raises:
|
207
|
+
ValueError: If credentials are invalid or missing required tokens.
|
208
|
+
"""
|
130
209
|
if not credentials or not isinstance(credentials, dict):
|
131
210
|
raise ValueError("Invalid credentials format")
|
132
211
|
if "access_token" not in credentials:
|
133
212
|
raise ValueError("Credentials must contain access_token")
|
134
213
|
self.store.set(self.name, credentials)
|
135
214
|
|
136
|
-
def authorize(self):
|
215
|
+
def authorize(self) -> dict[str, Any]:
|
216
|
+
"""Get OAuth authorization parameters.
|
217
|
+
|
218
|
+
Returns:
|
219
|
+
Dict[str, Any]: Dictionary containing OAuth authorization parameters.
|
220
|
+
|
221
|
+
Raises:
|
222
|
+
ValueError: If required OAuth configuration is missing.
|
223
|
+
"""
|
137
224
|
if not all([self.client_id, self.client_secret, self.auth_url, self.token_url]):
|
138
225
|
raise ValueError("Missing required OAuth configuration")
|
139
226
|
|
@@ -150,7 +237,19 @@ class OAuthIntegration(Integration):
|
|
150
237
|
"token_url": self.token_url,
|
151
238
|
}
|
152
239
|
|
153
|
-
def handle_callback(self, code: str):
|
240
|
+
def handle_callback(self, code: str) -> dict[str, Any]:
|
241
|
+
"""Handle OAuth callback and exchange code for tokens.
|
242
|
+
|
243
|
+
Args:
|
244
|
+
code: Authorization code from OAuth callback.
|
245
|
+
|
246
|
+
Returns:
|
247
|
+
Dict[str, Any]: Dictionary containing OAuth tokens.
|
248
|
+
|
249
|
+
Raises:
|
250
|
+
ValueError: If required OAuth configuration is missing.
|
251
|
+
httpx.HTTPError: If token exchange request fails.
|
252
|
+
"""
|
154
253
|
if not all([self.client_id, self.client_secret, self.token_url]):
|
155
254
|
raise ValueError("Missing required OAuth configuration")
|
156
255
|
|
@@ -167,15 +266,29 @@ class OAuthIntegration(Integration):
|
|
167
266
|
self.store.set(self.name, credentials)
|
168
267
|
return credentials
|
169
268
|
|
170
|
-
def refresh_token(self):
|
269
|
+
def refresh_token(self) -> dict[str, Any]:
|
270
|
+
"""Refresh OAuth access token using refresh token.
|
271
|
+
|
272
|
+
Returns:
|
273
|
+
Dict[str, Any]: Dictionary containing new OAuth tokens.
|
274
|
+
|
275
|
+
Raises:
|
276
|
+
ValueError: If required OAuth configuration is missing.
|
277
|
+
httpx.HTTPError: If token refresh request fails.
|
278
|
+
KeyError: If refresh token is not found in current credentials.
|
279
|
+
"""
|
171
280
|
if not all([self.client_id, self.client_secret, self.token_url]):
|
172
281
|
raise ValueError("Missing required OAuth configuration")
|
173
282
|
|
283
|
+
credentials = self.get_credentials()
|
284
|
+
if not credentials or "refresh_token" not in credentials:
|
285
|
+
raise KeyError("Refresh token not found in current credentials")
|
286
|
+
|
174
287
|
token_params = {
|
175
288
|
"client_id": self.client_id,
|
176
289
|
"client_secret": self.client_secret,
|
177
290
|
"grant_type": "refresh_token",
|
178
|
-
"refresh_token":
|
291
|
+
"refresh_token": credentials["refresh_token"],
|
179
292
|
}
|
180
293
|
|
181
294
|
response = httpx.post(self.token_url, data=token_params)
|
universal_mcp/logger.py
CHANGED
@@ -1,70 +1,17 @@
|
|
1
|
-
import os
|
2
1
|
import sys
|
3
|
-
import uuid
|
4
|
-
from functools import lru_cache
|
5
2
|
|
6
3
|
from loguru import logger
|
7
4
|
|
8
5
|
|
9
|
-
@lru_cache(maxsize=1)
|
10
|
-
def get_version():
|
11
|
-
"""
|
12
|
-
Get the version of the Universal MCP
|
13
|
-
"""
|
14
|
-
try:
|
15
|
-
from importlib.metadata import version
|
16
|
-
|
17
|
-
return version("universal_mcp")
|
18
|
-
except ImportError:
|
19
|
-
return "unknown"
|
20
|
-
|
21
|
-
|
22
|
-
def get_user_id():
|
23
|
-
"""
|
24
|
-
Generate a unique user ID for the current session
|
25
|
-
"""
|
26
|
-
return "universal_" + str(uuid.uuid4())[:8]
|
27
|
-
|
28
|
-
|
29
|
-
def posthog_sink(message, user_id=get_user_id()):
|
30
|
-
"""
|
31
|
-
Custom sink for sending logs to PostHog
|
32
|
-
"""
|
33
|
-
try:
|
34
|
-
import posthog
|
35
|
-
|
36
|
-
posthog.host = "https://us.i.posthog.com"
|
37
|
-
posthog.api_key = "phc_6HXMDi8CjfIW0l04l34L7IDkpCDeOVz9cOz1KLAHXh8"
|
38
|
-
|
39
|
-
record = message.record
|
40
|
-
properties = {
|
41
|
-
"level": record["level"].name,
|
42
|
-
"module": record["name"],
|
43
|
-
"function": record["function"],
|
44
|
-
"line": record["line"],
|
45
|
-
"message": record["message"],
|
46
|
-
"version": get_version(),
|
47
|
-
}
|
48
|
-
posthog.capture(user_id, "universal_mcp", properties)
|
49
|
-
except Exception:
|
50
|
-
# Silently fail if PostHog capture fails - don't want logging to break the app
|
51
|
-
pass
|
52
|
-
|
53
|
-
|
54
6
|
def setup_logger():
|
55
7
|
logger.remove()
|
8
|
+
# STDOUT cant be used as a sink because it will break the stream
|
56
9
|
# logger.add(
|
57
10
|
# sink=sys.stdout,
|
58
|
-
# format="<green>{time:YYYY-MM-DD HH:mm:ss}</green> | <level>{level: <8}</level> | <cyan>{name}</cyan>:<cyan>{function}</cyan>:<cyan>{line}</cyan> - <level>{message}</level>",
|
59
11
|
# level="INFO",
|
60
|
-
# colorize=True,
|
61
12
|
# )
|
13
|
+
# STDERR
|
62
14
|
logger.add(
|
63
15
|
sink=sys.stderr,
|
64
|
-
|
65
|
-
level="WARNING",
|
66
|
-
colorize=True,
|
16
|
+
level="INFO",
|
67
17
|
)
|
68
|
-
telemetry_enabled = os.getenv("TELEMETRY_ENABLED", "true").lower() == "true"
|
69
|
-
if telemetry_enabled:
|
70
|
-
logger.add(posthog_sink, level="INFO") # PostHog telemetry
|
@@ -4,20 +4,12 @@ from universal_mcp.servers.server import AgentRServer, LocalServer
|
|
4
4
|
|
5
5
|
def server_from_config(config: ServerConfig):
|
6
6
|
if config.type == "agentr":
|
7
|
-
return AgentRServer(
|
8
|
-
|
9
|
-
description=config.description,
|
10
|
-
api_key=config.api_key,
|
11
|
-
port=config.port,
|
12
|
-
)
|
7
|
+
return AgentRServer(config=config, api_key=config.api_key)
|
8
|
+
|
13
9
|
elif config.type == "local":
|
14
|
-
return LocalServer(
|
15
|
-
|
16
|
-
|
17
|
-
store=config.store,
|
18
|
-
apps_list=config.apps,
|
19
|
-
port=config.port,
|
20
|
-
)
|
10
|
+
return LocalServer(config=config)
|
11
|
+
else:
|
12
|
+
raise ValueError(f"Unsupported server type: {config.type}")
|
21
13
|
|
22
14
|
|
23
|
-
__all__ = [AgentRServer, LocalServer]
|
15
|
+
__all__ = [AgentRServer, LocalServer, server_from_config]
|