universal-mcp 0.1.23rc2__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.
- universal_mcp/agentr/__init__.py +6 -0
- universal_mcp/agentr/agentr.py +30 -0
- universal_mcp/{utils/agentr.py → agentr/client.py} +22 -7
- universal_mcp/agentr/integration.py +104 -0
- universal_mcp/agentr/registry.py +91 -0
- universal_mcp/agentr/server.py +51 -0
- universal_mcp/agents/__init__.py +6 -0
- universal_mcp/agents/auto.py +576 -0
- universal_mcp/agents/base.py +88 -0
- universal_mcp/agents/cli.py +27 -0
- universal_mcp/agents/codeact/__init__.py +243 -0
- universal_mcp/agents/codeact/sandbox.py +27 -0
- universal_mcp/agents/codeact/test.py +15 -0
- universal_mcp/agents/codeact/utils.py +61 -0
- universal_mcp/agents/hil.py +104 -0
- universal_mcp/agents/llm.py +10 -0
- universal_mcp/agents/react.py +58 -0
- universal_mcp/agents/simple.py +40 -0
- universal_mcp/agents/utils.py +111 -0
- universal_mcp/analytics.py +44 -14
- universal_mcp/applications/__init__.py +42 -75
- universal_mcp/applications/application.py +187 -133
- universal_mcp/applications/sample/app.py +245 -0
- universal_mcp/cli.py +14 -231
- universal_mcp/client/oauth.py +122 -18
- universal_mcp/client/token_store.py +62 -3
- universal_mcp/client/{client.py → transport.py} +127 -48
- universal_mcp/config.py +189 -49
- universal_mcp/exceptions.py +54 -6
- universal_mcp/integrations/__init__.py +0 -18
- universal_mcp/integrations/integration.py +185 -168
- universal_mcp/servers/__init__.py +2 -14
- universal_mcp/servers/server.py +84 -258
- universal_mcp/stores/store.py +126 -93
- universal_mcp/tools/__init__.py +3 -0
- universal_mcp/tools/adapters.py +20 -11
- universal_mcp/tools/func_metadata.py +1 -1
- universal_mcp/tools/manager.py +38 -53
- universal_mcp/tools/registry.py +41 -0
- universal_mcp/tools/tools.py +24 -3
- universal_mcp/types.py +10 -0
- universal_mcp/utils/common.py +245 -0
- universal_mcp/utils/installation.py +3 -4
- universal_mcp/utils/openapi/api_generator.py +71 -17
- universal_mcp/utils/openapi/api_splitter.py +0 -1
- universal_mcp/utils/openapi/cli.py +669 -0
- universal_mcp/utils/openapi/filters.py +114 -0
- universal_mcp/utils/openapi/openapi.py +315 -23
- universal_mcp/utils/openapi/postprocessor.py +275 -0
- universal_mcp/utils/openapi/preprocessor.py +63 -8
- universal_mcp/utils/openapi/test_generator.py +287 -0
- universal_mcp/utils/prompts.py +634 -0
- universal_mcp/utils/singleton.py +4 -1
- universal_mcp/utils/testing.py +196 -8
- universal_mcp-0.1.24rc3.dist-info/METADATA +68 -0
- universal_mcp-0.1.24rc3.dist-info/RECORD +70 -0
- universal_mcp/applications/README.md +0 -122
- universal_mcp/client/__main__.py +0 -30
- universal_mcp/client/agent.py +0 -96
- universal_mcp/integrations/README.md +0 -25
- universal_mcp/servers/README.md +0 -79
- universal_mcp/stores/README.md +0 -74
- universal_mcp/tools/README.md +0 -86
- universal_mcp-0.1.23rc2.dist-info/METADATA +0 -283
- universal_mcp-0.1.23rc2.dist-info/RECORD +0 -51
- /universal_mcp/{utils → tools}/docstring_parser.py +0 -0
- {universal_mcp-0.1.23rc2.dist-info → universal_mcp-0.1.24rc3.dist-info}/WHEEL +0 -0
- {universal_mcp-0.1.23rc2.dist-info → universal_mcp-0.1.24rc3.dist-info}/entry_points.txt +0 -0
- {universal_mcp-0.1.23rc2.dist-info → universal_mcp-0.1.24rc3.dist-info}/licenses/LICENSE +0 -0
@@ -5,7 +5,6 @@ from loguru import logger
|
|
5
5
|
|
6
6
|
from universal_mcp.exceptions import KeyNotFoundError, NotAuthorizedError
|
7
7
|
from universal_mcp.stores import BaseStore, MemoryStore
|
8
|
-
from universal_mcp.utils.agentr import AgentrClient
|
9
8
|
|
10
9
|
|
11
10
|
def sanitize_api_key_name(name: str) -> str:
|
@@ -19,75 +18,124 @@ def sanitize_api_key_name(name: str) -> str:
|
|
19
18
|
class Integration:
|
20
19
|
"""Abstract base class for handling application integrations and authentication.
|
21
20
|
|
22
|
-
This class defines
|
23
|
-
|
21
|
+
This class defines a common interface for various authentication and
|
22
|
+
authorization strategies an application might use to connect with
|
23
|
+
external services. Subclasses implement specific mechanisms like
|
24
|
+
API key handling, OAuth 2.0 flows, or delegation to platforms like AgentR.
|
24
25
|
|
25
|
-
|
26
|
-
|
27
|
-
store: Optional Store instance for persisting credentials and other data
|
26
|
+
Each integration is associated with a name and can use a `BaseStore`
|
27
|
+
instance for persisting credentials or other relevant data.
|
28
28
|
|
29
29
|
Attributes:
|
30
|
-
name: The name
|
31
|
-
|
30
|
+
name (str): The unique name identifying this integration instance
|
31
|
+
(e.g., "my_app_api_key", "github_oauth").
|
32
|
+
store (BaseStore): The storage backend (e.g., `MemoryStore`,
|
33
|
+
`KeyringStore`) used for persisting credentials.
|
34
|
+
Defaults to `MemoryStore` if not provided.
|
32
35
|
"""
|
33
36
|
|
34
37
|
def __init__(self, name: str, store: BaseStore | None = None):
|
38
|
+
"""Initializes the Integration.
|
39
|
+
|
40
|
+
Args:
|
41
|
+
name (str): The unique name/identifier for this integration instance.
|
42
|
+
store (BaseStore | None, optional): A store instance for
|
43
|
+
persisting credentials. Defaults to `MemoryStore()`.
|
44
|
+
"""
|
35
45
|
self.name = name
|
36
46
|
self.store = store or MemoryStore()
|
47
|
+
self.type = ""
|
37
48
|
|
38
49
|
def authorize(self) -> str | dict[str, Any]:
|
39
|
-
"""
|
50
|
+
"""Initiates or provides details for the authorization process.
|
51
|
+
|
52
|
+
The exact behavior and return type of this method depend on the
|
53
|
+
specific integration subclass. It might return an authorization URL
|
54
|
+
for the user to visit, parameters needed to construct such a URL,
|
55
|
+
or instructions on how to manually provide credentials.
|
40
56
|
|
41
57
|
Returns:
|
42
|
-
|
58
|
+
str | dict[str, Any]: Typically, an authorization URL (str) or a
|
59
|
+
dictionary containing parameters needed for
|
60
|
+
the authorization flow.
|
43
61
|
|
44
62
|
Raises:
|
45
|
-
ValueError: If
|
63
|
+
ValueError: If essential configuration for authorization is missing.
|
64
|
+
NotImplementedError: If the subclass does not implement this method.
|
46
65
|
"""
|
47
|
-
|
66
|
+
raise NotImplementedError("Subclasses must implement the authorize method.")
|
48
67
|
|
49
68
|
def get_credentials(self) -> dict[str, Any]:
|
50
|
-
"""
|
69
|
+
"""Retrieves the stored credentials for this integration.
|
70
|
+
|
71
|
+
Fetches credentials associated with `self.name` from the `self.store`.
|
51
72
|
|
52
73
|
Returns:
|
53
|
-
|
74
|
+
dict[str, Any]: A dictionary containing the credentials. The structure
|
75
|
+
of this dictionary is specific to the integration type.
|
54
76
|
|
55
77
|
Raises:
|
56
|
-
NotAuthorizedError: If credentials are not found
|
78
|
+
NotAuthorizedError: If credentials are not found in the store
|
79
|
+
or are otherwise invalid/inaccessible.
|
80
|
+
KeyNotFoundError: If the key (self.name) is not found in the store.
|
57
81
|
"""
|
58
|
-
|
59
|
-
|
82
|
+
try:
|
83
|
+
credentials = self.store.get(self.name)
|
84
|
+
if credentials is None: # Explicitly check for None if store can return it
|
85
|
+
raise NotAuthorizedError(f"No credentials found for {self.name}")
|
86
|
+
return credentials
|
87
|
+
except KeyNotFoundError as e:
|
88
|
+
raise NotAuthorizedError(f"Credentials not found for {self.name}: {e}") from e
|
60
89
|
|
61
90
|
def set_credentials(self, credentials: dict[str, Any]) -> None:
|
62
|
-
"""
|
91
|
+
"""Stores the provided credentials for this integration.
|
92
|
+
|
93
|
+
Saves the given credentials dictionary into `self.store` associated
|
94
|
+
with `self.name`.
|
63
95
|
|
64
96
|
Args:
|
65
|
-
credentials:
|
97
|
+
credentials (dict[str, Any]): A dictionary containing the credentials
|
98
|
+
to be stored. The required structure depends on the integration.
|
66
99
|
|
67
100
|
Raises:
|
68
|
-
ValueError: If credentials are invalid or missing
|
101
|
+
ValueError: If the provided credentials are invalid or missing
|
102
|
+
required fields for the specific integration type.
|
69
103
|
"""
|
70
104
|
self.store.set(self.name, credentials)
|
71
105
|
|
106
|
+
def __str__(self) -> str:
|
107
|
+
return f"Integration(name={self.name}, type={self.type})"
|
72
108
|
|
73
|
-
|
74
|
-
|
109
|
+
def __repr__(self) -> str:
|
110
|
+
return self.__str__()
|
75
111
|
|
76
|
-
This class implements the Integration interface for services that use API key
|
77
|
-
authentication. It handles storing and retrieving API keys using the provided
|
78
|
-
store.
|
79
112
|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
113
|
+
class ApiKeyIntegration(Integration):
|
114
|
+
"""Handles integrations that use a simple API key for authentication.
|
115
|
+
|
116
|
+
This class manages storing and retrieving an API key. The key name is
|
117
|
+
automatically sanitized (e.g., uppercased and suffixed with `_API_KEY`)
|
118
|
+
before being used with the store.
|
84
119
|
|
85
120
|
Attributes:
|
86
|
-
name: The name
|
87
|
-
store: Store
|
121
|
+
name (str): The sanitized name used as the key for storing the API key.
|
122
|
+
store (BaseStore): Store for persisting the API key.
|
123
|
+
type (str): Set to "api_key".
|
124
|
+
_api_key (str | None): Cached API key.
|
88
125
|
"""
|
89
126
|
|
90
127
|
def __init__(self, name: str, store: BaseStore | None = None, **kwargs):
|
128
|
+
"""Initializes ApiKeyIntegration.
|
129
|
+
|
130
|
+
The provided `name` is sanitized (e.g., 'mykey' becomes 'MYKEY_API_KEY')
|
131
|
+
to form the actual key used for storage.
|
132
|
+
|
133
|
+
Args:
|
134
|
+
name (str): The base name for the API key (e.g., "TAVILY").
|
135
|
+
store (BaseStore | None, optional): Store for credentials.
|
136
|
+
Defaults to `MemoryStore()`.
|
137
|
+
**kwargs: Additional arguments passed to the parent `Integration`.
|
138
|
+
"""
|
91
139
|
self.type = "api_key"
|
92
140
|
sanitized_name = sanitize_api_key_name(name)
|
93
141
|
super().__init__(sanitized_name, store, **kwargs)
|
@@ -95,25 +143,39 @@ class ApiKeyIntegration(Integration):
|
|
95
143
|
self._api_key: str | None = None
|
96
144
|
|
97
145
|
@property
|
98
|
-
def api_key(self) -> str
|
146
|
+
def api_key(self) -> str: # Changed to str, as it raises if None effectively
|
147
|
+
"""Retrieves the API key, loading it from the store if necessary.
|
148
|
+
|
149
|
+
If the API key is not already cached in `_api_key`, it attempts
|
150
|
+
to load it from `self.store` using `self.name` as the key.
|
151
|
+
|
152
|
+
Returns:
|
153
|
+
str: The API key.
|
154
|
+
|
155
|
+
Raises:
|
156
|
+
NotAuthorizedError: If the API key is not found in the store.
|
157
|
+
The original `KeyNotFoundError` is chained.
|
158
|
+
"""
|
99
159
|
if not self._api_key:
|
100
160
|
try:
|
101
|
-
credentials = self.store.get(self.name)
|
161
|
+
credentials = self.store.get(self.name) # type: ignore
|
102
162
|
self._api_key = credentials
|
103
163
|
except KeyNotFoundError as e:
|
104
164
|
action = self.authorize()
|
105
165
|
raise NotAuthorizedError(action) from e
|
106
|
-
return self._api_key
|
166
|
+
return self._api_key # type: ignore
|
107
167
|
|
108
168
|
@api_key.setter
|
109
169
|
def api_key(self, value: str | None) -> None:
|
110
|
-
"""
|
170
|
+
"""Sets and stores the API key.
|
111
171
|
|
112
172
|
Args:
|
113
|
-
value: The API key value
|
173
|
+
value (str | None): The API key value. If None, `_api_key` is set
|
174
|
+
to None, but nothing is stored (or cleared from store).
|
175
|
+
Consider if None should clear the store.
|
114
176
|
|
115
177
|
Raises:
|
116
|
-
ValueError: If
|
178
|
+
ValueError: If `value` is provided and is not a string.
|
117
179
|
"""
|
118
180
|
if value is not None and not isinstance(value, str):
|
119
181
|
raise ValueError("API key must be a string")
|
@@ -122,63 +184,63 @@ class ApiKeyIntegration(Integration):
|
|
122
184
|
self.store.set(self.name, value)
|
123
185
|
|
124
186
|
def get_credentials(self) -> dict[str, str]:
|
125
|
-
"""
|
187
|
+
"""Retrieves the API key and returns it in a standard dictionary format.
|
126
188
|
|
127
189
|
Returns:
|
128
|
-
|
190
|
+
dict[str, str]: A dictionary like `{"api_key": "your_api_key_value"}`.
|
129
191
|
|
130
192
|
Raises:
|
131
|
-
NotAuthorizedError: If API key
|
193
|
+
NotAuthorizedError: If the API key cannot be retrieved.
|
132
194
|
"""
|
133
195
|
return {"api_key": self.api_key}
|
134
196
|
|
135
197
|
def set_credentials(self, credentials: dict[str, Any]) -> None:
|
136
|
-
"""
|
198
|
+
"""Sets the API key from a dictionary.
|
199
|
+
|
200
|
+
Expects `credentials` to be a dictionary, typically containing
|
201
|
+
an 'api_key' field, but it stores the entire dictionary as is
|
202
|
+
under `self.name`. For direct API key setting, use the `api_key` property.
|
137
203
|
|
138
204
|
Args:
|
139
|
-
credentials:
|
205
|
+
credentials (dict[str, Any]): A dictionary containing the API key
|
206
|
+
or related credential information.
|
140
207
|
|
141
208
|
Raises:
|
142
|
-
ValueError: If credentials
|
209
|
+
ValueError: If `credentials` is not a dictionary.
|
143
210
|
"""
|
144
211
|
if not credentials or not isinstance(credentials, dict):
|
145
212
|
raise ValueError("Invalid credentials format")
|
146
213
|
self.store.set(self.name, credentials)
|
147
214
|
|
148
215
|
def authorize(self) -> str:
|
149
|
-
"""
|
216
|
+
"""Provides instructions for setting the API key.
|
217
|
+
|
218
|
+
Since API key setup is typically manual, this method returns a
|
219
|
+
message guiding the user on how to provide the key.
|
150
220
|
|
151
221
|
Returns:
|
152
|
-
str:
|
222
|
+
str: A message instructing the user to provide the API key
|
223
|
+
for `self.name`.
|
153
224
|
"""
|
154
225
|
return f"Please ask the user for api key and set the API Key for {self.name} in the store"
|
155
226
|
|
156
227
|
|
157
228
|
class OAuthIntegration(Integration):
|
158
|
-
"""
|
159
|
-
|
160
|
-
This class implements the
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
Args:
|
165
|
-
name: The name identifier for this integration
|
166
|
-
store: Optional Store instance for persisting credentials and other data
|
167
|
-
client_id: OAuth client ID
|
168
|
-
client_secret: OAuth client secret
|
169
|
-
auth_url: OAuth authorization URL
|
170
|
-
token_url: OAuth token exchange URL
|
171
|
-
scope: OAuth scope string
|
172
|
-
**kwargs: Additional keyword arguments passed to parent class
|
229
|
+
"""Manages OAuth 2.0 authentication and authorization flows.
|
230
|
+
|
231
|
+
This class implements the necessary steps for an OAuth 2.0 client,
|
232
|
+
including generating authorization request parameters, handling the
|
233
|
+
redirect callback from the authorization server, exchanging the
|
234
|
+
authorization code for access/refresh tokens, and refreshing tokens.
|
173
235
|
|
174
236
|
Attributes:
|
175
|
-
name:
|
176
|
-
store: Store
|
177
|
-
client_id: OAuth
|
178
|
-
client_secret: OAuth
|
179
|
-
auth_url:
|
180
|
-
token_url:
|
181
|
-
scope: OAuth
|
237
|
+
name (str): Name of the integration.
|
238
|
+
store (BaseStore): Store for OAuth tokens.
|
239
|
+
client_id (str | None): The OAuth 2.0 Client ID.
|
240
|
+
client_secret (str | None): The OAuth 2.0 Client Secret.
|
241
|
+
auth_url (str | None): The authorization server's endpoint URL.
|
242
|
+
token_url (str | None): The token server's endpoint URL.
|
243
|
+
scope (str | None): The requested OAuth scopes, space-separated.
|
182
244
|
"""
|
183
245
|
|
184
246
|
def __init__(
|
@@ -192,7 +254,21 @@ class OAuthIntegration(Integration):
|
|
192
254
|
scope: str | None = None,
|
193
255
|
**kwargs,
|
194
256
|
):
|
257
|
+
"""Initializes the OAuthIntegration.
|
258
|
+
|
259
|
+
Args:
|
260
|
+
name (str): The unique name for this integration instance.
|
261
|
+
store (BaseStore | None, optional): Store for credentials.
|
262
|
+
Defaults to `MemoryStore()`.
|
263
|
+
client_id (str | None, optional): The OAuth 2.0 Client ID.
|
264
|
+
client_secret (str | None, optional): The OAuth 2.0 Client Secret.
|
265
|
+
auth_url (str | None, optional): The authorization server's endpoint URL.
|
266
|
+
token_url (str | None, optional): The token server's endpoint URL.
|
267
|
+
scope (str | None, optional): The requested OAuth scopes, space-separated.
|
268
|
+
**kwargs: Additional arguments passed to the parent `Integration`.
|
269
|
+
"""
|
195
270
|
super().__init__(name, store, **kwargs)
|
271
|
+
self.type = "oauth"
|
196
272
|
self.client_id = client_id
|
197
273
|
self.client_secret = client_secret
|
198
274
|
self.auth_url = auth_url
|
@@ -200,24 +276,30 @@ class OAuthIntegration(Integration):
|
|
200
276
|
self.scope = scope
|
201
277
|
|
202
278
|
def get_credentials(self) -> dict[str, Any] | None:
|
203
|
-
"""
|
279
|
+
"""Retrieves stored OAuth tokens for this integration.
|
204
280
|
|
205
281
|
Returns:
|
206
|
-
|
282
|
+
dict[str, Any] | None: A dictionary containing the OAuth tokens
|
283
|
+
(e.g., `access_token`, `refresh_token`) if found,
|
284
|
+
otherwise None.
|
207
285
|
"""
|
208
286
|
credentials = self.store.get(self.name)
|
209
287
|
if not credentials:
|
210
288
|
return None
|
211
|
-
return credentials
|
289
|
+
return credentials # type: ignore
|
212
290
|
|
213
291
|
def set_credentials(self, credentials: dict[str, Any]) -> None:
|
214
|
-
"""
|
292
|
+
"""Stores OAuth tokens for this integration.
|
293
|
+
|
294
|
+
Validates that essential fields like 'access_token' are present.
|
215
295
|
|
216
296
|
Args:
|
217
|
-
credentials:
|
297
|
+
credentials (dict[str, Any]): A dictionary containing OAuth tokens.
|
298
|
+
Must include at least 'access_token'.
|
218
299
|
|
219
300
|
Raises:
|
220
|
-
ValueError: If credentials
|
301
|
+
ValueError: If `credentials` is not a dictionary or if 'access_token'
|
302
|
+
is missing.
|
221
303
|
"""
|
222
304
|
if not credentials or not isinstance(credentials, dict):
|
223
305
|
raise ValueError("Invalid credentials format")
|
@@ -226,13 +308,19 @@ class OAuthIntegration(Integration):
|
|
226
308
|
self.store.set(self.name, credentials)
|
227
309
|
|
228
310
|
def authorize(self) -> dict[str, Any]:
|
229
|
-
"""
|
311
|
+
"""Constructs parameters required for the OAuth authorization request.
|
312
|
+
|
313
|
+
These parameters are typically used to build the URL to which the
|
314
|
+
user must be redirected to grant authorization.
|
230
315
|
|
231
316
|
Returns:
|
232
|
-
|
317
|
+
dict[str, Any]: A dictionary containing the authorization endpoint URL
|
318
|
+
(`url`), query parameters (`params`), client secret,
|
319
|
+
and token URL.
|
233
320
|
|
234
321
|
Raises:
|
235
|
-
ValueError: If
|
322
|
+
ValueError: If essential OAuth configuration like `client_id`,
|
323
|
+
`client_secret`, `auth_url`, or `token_url` is missing.
|
236
324
|
"""
|
237
325
|
if not all([self.client_id, self.client_secret, self.auth_url, self.token_url]):
|
238
326
|
raise ValueError("Missing required OAuth configuration")
|
@@ -251,19 +339,24 @@ class OAuthIntegration(Integration):
|
|
251
339
|
}
|
252
340
|
|
253
341
|
def handle_callback(self, code: str) -> dict[str, Any]:
|
254
|
-
"""
|
342
|
+
"""Handles the OAuth callback by exchanging the authorization code for tokens.
|
343
|
+
|
344
|
+
This method is called after the user authorizes the application and the
|
345
|
+
authorization server redirects back with an authorization code.
|
255
346
|
|
256
347
|
Args:
|
257
|
-
code:
|
348
|
+
code (str): The authorization code received from the OAuth server.
|
258
349
|
|
259
350
|
Returns:
|
260
|
-
|
351
|
+
dict[str, Any]: A dictionary containing the access token, refresh token
|
352
|
+
(if any), and other token response data. These are also
|
353
|
+
stored via `set_credentials`.
|
261
354
|
|
262
355
|
Raises:
|
263
|
-
ValueError: If
|
264
|
-
httpx.
|
356
|
+
ValueError: If essential OAuth configuration is missing.
|
357
|
+
httpx.HTTPStatusError: If the token exchange request to `token_url` fails.
|
265
358
|
"""
|
266
|
-
if not all([self.client_id, self.client_secret, self.token_url]):
|
359
|
+
if not all([self.client_id, self.client_secret, self.token_url]): # type: ignore
|
267
360
|
raise ValueError("Missing required OAuth configuration")
|
268
361
|
|
269
362
|
token_params = {
|
@@ -273,24 +366,26 @@ class OAuthIntegration(Integration):
|
|
273
366
|
"grant_type": "authorization_code",
|
274
367
|
}
|
275
368
|
|
276
|
-
response = httpx.post(self.token_url, data=token_params)
|
369
|
+
response = httpx.post(self.token_url, data=token_params) # type: ignore
|
277
370
|
response.raise_for_status()
|
278
371
|
credentials = response.json()
|
279
372
|
self.store.set(self.name, credentials)
|
280
373
|
return credentials
|
281
374
|
|
282
375
|
def refresh_token(self) -> dict[str, Any]:
|
283
|
-
"""
|
376
|
+
"""Refreshes an expired access token using a stored refresh token.
|
284
377
|
|
285
378
|
Returns:
|
286
|
-
|
379
|
+
dict[str, Any]: A dictionary containing the new access token,
|
380
|
+
refresh token, and other token response data.
|
381
|
+
These are also stored.
|
287
382
|
|
288
383
|
Raises:
|
289
|
-
ValueError: If
|
290
|
-
|
291
|
-
|
384
|
+
ValueError: If essential OAuth configuration is missing.
|
385
|
+
KeyError: If a refresh token is not found in the stored credentials.
|
386
|
+
httpx.HTTPStatusError: If the token refresh request fails.
|
292
387
|
"""
|
293
|
-
if not all([self.client_id, self.client_secret, self.token_url]):
|
388
|
+
if not all([self.client_id, self.client_secret, self.token_url]): # type: ignore
|
294
389
|
raise ValueError("Missing required OAuth configuration")
|
295
390
|
|
296
391
|
credentials = self.get_credentials()
|
@@ -304,86 +399,8 @@ class OAuthIntegration(Integration):
|
|
304
399
|
"refresh_token": credentials["refresh_token"],
|
305
400
|
}
|
306
401
|
|
307
|
-
response = httpx.post(self.token_url, data=token_params)
|
402
|
+
response = httpx.post(self.token_url, data=token_params) # type: ignore
|
308
403
|
response.raise_for_status()
|
309
404
|
credentials = response.json()
|
310
405
|
self.store.set(self.name, credentials)
|
311
406
|
return credentials
|
312
|
-
|
313
|
-
|
314
|
-
class AgentRIntegration(Integration):
|
315
|
-
"""Integration class for AgentR API authentication and authorization.
|
316
|
-
|
317
|
-
This class handles API key authentication and OAuth authorization flow for AgentR services.
|
318
|
-
|
319
|
-
Args:
|
320
|
-
name (str): Name of the integration
|
321
|
-
api_key (str, optional): AgentR API key. If not provided, will look for AGENTR_API_KEY env var
|
322
|
-
**kwargs: Additional keyword arguments passed to parent Integration class
|
323
|
-
|
324
|
-
Raises:
|
325
|
-
ValueError: If no API key is provided or found in environment variables
|
326
|
-
"""
|
327
|
-
|
328
|
-
def __init__(self, name: str, api_key: str | None = None, base_url: str | None = None, **kwargs):
|
329
|
-
super().__init__(name, **kwargs)
|
330
|
-
self.client = AgentrClient(api_key=api_key, base_url=base_url)
|
331
|
-
self._credentials = None
|
332
|
-
|
333
|
-
def set_credentials(self, credentials: dict | None = None):
|
334
|
-
"""Set credentials for the integration.
|
335
|
-
|
336
|
-
This method is not implemented for AgentR integration. Instead it redirects to the authorize flow.
|
337
|
-
|
338
|
-
Args:
|
339
|
-
credentials (dict | None, optional): Credentials dict (not used). Defaults to None.
|
340
|
-
|
341
|
-
Returns:
|
342
|
-
str: Authorization URL from authorize() method
|
343
|
-
"""
|
344
|
-
return self.authorize()
|
345
|
-
|
346
|
-
@property
|
347
|
-
def credentials(self):
|
348
|
-
"""Get credentials for the integration from the AgentR API.
|
349
|
-
|
350
|
-
Makes API request to retrieve stored credentials for this integration.
|
351
|
-
|
352
|
-
Returns:
|
353
|
-
dict: Credentials data from API response
|
354
|
-
|
355
|
-
Raises:
|
356
|
-
NotAuthorizedError: If credentials are not found (404 response)
|
357
|
-
HTTPError: For other API errors
|
358
|
-
"""
|
359
|
-
if self._credentials is not None:
|
360
|
-
return self._credentials
|
361
|
-
self._credentials = self.client.get_credentials(self.name)
|
362
|
-
return self._credentials
|
363
|
-
|
364
|
-
def get_credentials(self):
|
365
|
-
"""Get credentials for the integration from the AgentR API.
|
366
|
-
|
367
|
-
Makes API request to retrieve stored credentials for this integration.
|
368
|
-
|
369
|
-
Returns:
|
370
|
-
dict: Credentials data from API response
|
371
|
-
|
372
|
-
Raises:
|
373
|
-
NotAuthorizedError: If credentials are not found (404 response)
|
374
|
-
HTTPError: For other API errors
|
375
|
-
"""
|
376
|
-
return self.credentials
|
377
|
-
|
378
|
-
def authorize(self):
|
379
|
-
"""Get authorization URL for the integration.
|
380
|
-
|
381
|
-
Makes API request to get OAuth authorization URL.
|
382
|
-
|
383
|
-
Returns:
|
384
|
-
str: Message containing authorization URL
|
385
|
-
|
386
|
-
Raises:
|
387
|
-
HTTPError: If API request fails
|
388
|
-
"""
|
389
|
-
return self.client.get_authorization_url(self.name)
|
@@ -1,15 +1,3 @@
|
|
1
|
-
from universal_mcp.
|
2
|
-
from universal_mcp.servers.server import AgentRServer, BaseServer, LocalServer, SingleMCPServer
|
1
|
+
from universal_mcp.servers.server import BaseServer, LocalServer, SingleMCPServer
|
3
2
|
|
4
|
-
|
5
|
-
def server_from_config(config: ServerConfig):
|
6
|
-
if config.type == "agentr":
|
7
|
-
return AgentRServer(config=config, api_key=config.api_key)
|
8
|
-
|
9
|
-
elif config.type == "local":
|
10
|
-
return LocalServer(config=config)
|
11
|
-
else:
|
12
|
-
raise ValueError(f"Unsupported server type: {config.type}")
|
13
|
-
|
14
|
-
|
15
|
-
__all__ = [AgentRServer, LocalServer, SingleMCPServer, BaseServer, server_from_config]
|
3
|
+
__all__ = ["LocalServer", "SingleMCPServer", "BaseServer"]
|