universal-mcp 0.1.23rc2__py3-none-any.whl → 0.1.24rc2__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 +43 -11
- universal_mcp/applications/application.py +186 -132
- universal_mcp/applications/sample_tool_app.py +80 -0
- universal_mcp/cli.py +5 -229
- universal_mcp/client/agents/__init__.py +4 -0
- universal_mcp/client/agents/base.py +38 -0
- universal_mcp/client/agents/llm.py +115 -0
- universal_mcp/client/agents/react.py +67 -0
- universal_mcp/client/cli.py +181 -0
- 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 +160 -46
- universal_mcp/exceptions.py +50 -6
- universal_mcp/integrations/__init__.py +1 -4
- universal_mcp/integrations/integration.py +220 -121
- universal_mcp/servers/__init__.py +1 -1
- universal_mcp/servers/server.py +114 -247
- universal_mcp/stores/store.py +126 -93
- universal_mcp/tools/func_metadata.py +1 -1
- universal_mcp/tools/manager.py +15 -3
- universal_mcp/tools/tools.py +2 -2
- universal_mcp/utils/agentr.py +3 -4
- universal_mcp/utils/installation.py +3 -4
- universal_mcp/utils/openapi/api_generator.py +28 -2
- universal_mcp/utils/openapi/api_splitter.py +0 -1
- universal_mcp/utils/openapi/cli.py +243 -0
- universal_mcp/utils/openapi/filters.py +114 -0
- universal_mcp/utils/openapi/openapi.py +31 -2
- universal_mcp/utils/openapi/preprocessor.py +62 -7
- universal_mcp/utils/prompts.py +787 -0
- universal_mcp/utils/singleton.py +4 -1
- universal_mcp/utils/testing.py +6 -6
- universal_mcp-0.1.24rc2.dist-info/METADATA +54 -0
- universal_mcp-0.1.24rc2.dist-info/RECORD +53 -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.24rc2.dist-info}/WHEEL +0 -0
- {universal_mcp-0.1.23rc2.dist-info → universal_mcp-0.1.24rc2.dist-info}/entry_points.txt +0 -0
- {universal_mcp-0.1.23rc2.dist-info → universal_mcp-0.1.24rc2.dist-info}/licenses/LICENSE +0 -0
@@ -12,10 +12,7 @@ def integration_from_config(config: IntegrationConfig, store: BaseStore | None =
|
|
12
12
|
if config.type == "api_key":
|
13
13
|
return ApiKeyIntegration(config.name, store=store, **kwargs)
|
14
14
|
elif config.type == "agentr":
|
15
|
-
|
16
|
-
if not api_key:
|
17
|
-
raise ValueError("api_key is required for AgentR integration")
|
18
|
-
return AgentRIntegration(config.name, api_key=api_key)
|
15
|
+
return AgentRIntegration(config.name, **kwargs)
|
19
16
|
else:
|
20
17
|
raise ValueError(f"Unsupported integration type: {config.type}")
|
21
18
|
|
@@ -19,75 +19,117 @@ def sanitize_api_key_name(name: str) -> str:
|
|
19
19
|
class Integration:
|
20
20
|
"""Abstract base class for handling application integrations and authentication.
|
21
21
|
|
22
|
-
This class defines
|
23
|
-
|
22
|
+
This class defines a common interface for various authentication and
|
23
|
+
authorization strategies an application might use to connect with
|
24
|
+
external services. Subclasses implement specific mechanisms like
|
25
|
+
API key handling, OAuth 2.0 flows, or delegation to platforms like AgentR.
|
24
26
|
|
25
|
-
|
26
|
-
|
27
|
-
store: Optional Store instance for persisting credentials and other data
|
27
|
+
Each integration is associated with a name and can use a `BaseStore`
|
28
|
+
instance for persisting credentials or other relevant data.
|
28
29
|
|
29
30
|
Attributes:
|
30
|
-
name: The name
|
31
|
-
|
31
|
+
name (str): The unique name identifying this integration instance
|
32
|
+
(e.g., "my_app_api_key", "github_oauth").
|
33
|
+
store (BaseStore): The storage backend (e.g., `MemoryStore`,
|
34
|
+
`KeyringStore`) used for persisting credentials.
|
35
|
+
Defaults to `MemoryStore` if not provided.
|
32
36
|
"""
|
33
37
|
|
34
38
|
def __init__(self, name: str, store: BaseStore | None = None):
|
39
|
+
"""Initializes the Integration.
|
40
|
+
|
41
|
+
Args:
|
42
|
+
name (str): The unique name for this integration instance.
|
43
|
+
store (BaseStore | None, optional): A store instance for
|
44
|
+
persisting credentials. Defaults to `MemoryStore()`.
|
45
|
+
"""
|
35
46
|
self.name = name
|
36
47
|
self.store = store or MemoryStore()
|
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
|
|
72
106
|
|
73
107
|
class ApiKeyIntegration(Integration):
|
74
|
-
"""
|
75
|
-
|
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.
|
108
|
+
"""Handles integrations that use a simple API key for authentication.
|
79
109
|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
**kwargs: Additional keyword arguments passed to parent class
|
110
|
+
This class manages storing and retrieving an API key. The key name is
|
111
|
+
automatically sanitized (e.g., uppercased and suffixed with `_API_KEY`)
|
112
|
+
before being used with the store.
|
84
113
|
|
85
114
|
Attributes:
|
86
|
-
name: The name
|
87
|
-
store: Store
|
115
|
+
name (str): The sanitized name used as the key for storing the API key.
|
116
|
+
store (BaseStore): Store for persisting the API key.
|
117
|
+
type (str): Set to "api_key".
|
118
|
+
_api_key (str | None): Cached API key.
|
88
119
|
"""
|
89
120
|
|
90
121
|
def __init__(self, name: str, store: BaseStore | None = None, **kwargs):
|
122
|
+
"""Initializes ApiKeyIntegration.
|
123
|
+
|
124
|
+
The provided `name` is sanitized (e.g., 'mykey' becomes 'MYKEY_API_KEY')
|
125
|
+
to form the actual key used for storage.
|
126
|
+
|
127
|
+
Args:
|
128
|
+
name (str): The base name for the API key (e.g., "TAVILY").
|
129
|
+
store (BaseStore | None, optional): Store for credentials.
|
130
|
+
Defaults to `MemoryStore()`.
|
131
|
+
**kwargs: Additional arguments passed to the parent `Integration`.
|
132
|
+
"""
|
91
133
|
self.type = "api_key"
|
92
134
|
sanitized_name = sanitize_api_key_name(name)
|
93
135
|
super().__init__(sanitized_name, store, **kwargs)
|
@@ -95,25 +137,39 @@ class ApiKeyIntegration(Integration):
|
|
95
137
|
self._api_key: str | None = None
|
96
138
|
|
97
139
|
@property
|
98
|
-
def api_key(self) -> str
|
140
|
+
def api_key(self) -> str: # Changed to str, as it raises if None effectively
|
141
|
+
"""Retrieves the API key, loading it from the store if necessary.
|
142
|
+
|
143
|
+
If the API key is not already cached in `_api_key`, it attempts
|
144
|
+
to load it from `self.store` using `self.name` as the key.
|
145
|
+
|
146
|
+
Returns:
|
147
|
+
str: The API key.
|
148
|
+
|
149
|
+
Raises:
|
150
|
+
NotAuthorizedError: If the API key is not found in the store.
|
151
|
+
The original `KeyNotFoundError` is chained.
|
152
|
+
"""
|
99
153
|
if not self._api_key:
|
100
154
|
try:
|
101
|
-
credentials = self.store.get(self.name)
|
155
|
+
credentials = self.store.get(self.name) # type: ignore
|
102
156
|
self._api_key = credentials
|
103
157
|
except KeyNotFoundError as e:
|
104
158
|
action = self.authorize()
|
105
159
|
raise NotAuthorizedError(action) from e
|
106
|
-
return self._api_key
|
160
|
+
return self._api_key # type: ignore
|
107
161
|
|
108
162
|
@api_key.setter
|
109
163
|
def api_key(self, value: str | None) -> None:
|
110
|
-
"""
|
164
|
+
"""Sets and stores the API key.
|
111
165
|
|
112
166
|
Args:
|
113
|
-
value: The API key value
|
167
|
+
value (str | None): The API key value. If None, `_api_key` is set
|
168
|
+
to None, but nothing is stored (or cleared from store).
|
169
|
+
Consider if None should clear the store.
|
114
170
|
|
115
171
|
Raises:
|
116
|
-
ValueError: If
|
172
|
+
ValueError: If `value` is provided and is not a string.
|
117
173
|
"""
|
118
174
|
if value is not None and not isinstance(value, str):
|
119
175
|
raise ValueError("API key must be a string")
|
@@ -122,63 +178,63 @@ class ApiKeyIntegration(Integration):
|
|
122
178
|
self.store.set(self.name, value)
|
123
179
|
|
124
180
|
def get_credentials(self) -> dict[str, str]:
|
125
|
-
"""
|
181
|
+
"""Retrieves the API key and returns it in a standard dictionary format.
|
126
182
|
|
127
183
|
Returns:
|
128
|
-
|
184
|
+
dict[str, str]: A dictionary like `{"api_key": "your_api_key_value"}`.
|
129
185
|
|
130
186
|
Raises:
|
131
|
-
NotAuthorizedError: If API key
|
187
|
+
NotAuthorizedError: If the API key cannot be retrieved.
|
132
188
|
"""
|
133
189
|
return {"api_key": self.api_key}
|
134
190
|
|
135
191
|
def set_credentials(self, credentials: dict[str, Any]) -> None:
|
136
|
-
"""
|
192
|
+
"""Sets the API key from a dictionary.
|
193
|
+
|
194
|
+
Expects `credentials` to be a dictionary, typically containing
|
195
|
+
an 'api_key' field, but it stores the entire dictionary as is
|
196
|
+
under `self.name`. For direct API key setting, use the `api_key` property.
|
137
197
|
|
138
198
|
Args:
|
139
|
-
credentials:
|
199
|
+
credentials (dict[str, Any]): A dictionary containing the API key
|
200
|
+
or related credential information.
|
140
201
|
|
141
202
|
Raises:
|
142
|
-
ValueError: If credentials
|
203
|
+
ValueError: If `credentials` is not a dictionary.
|
143
204
|
"""
|
144
205
|
if not credentials or not isinstance(credentials, dict):
|
145
206
|
raise ValueError("Invalid credentials format")
|
146
207
|
self.store.set(self.name, credentials)
|
147
208
|
|
148
209
|
def authorize(self) -> str:
|
149
|
-
"""
|
210
|
+
"""Provides instructions for setting the API key.
|
211
|
+
|
212
|
+
Since API key setup is typically manual, this method returns a
|
213
|
+
message guiding the user on how to provide the key.
|
150
214
|
|
151
215
|
Returns:
|
152
|
-
str:
|
216
|
+
str: A message instructing the user to provide the API key
|
217
|
+
for `self.name`.
|
153
218
|
"""
|
154
219
|
return f"Please ask the user for api key and set the API Key for {self.name} in the store"
|
155
220
|
|
156
221
|
|
157
222
|
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
|
223
|
+
"""Manages OAuth 2.0 authentication and authorization flows.
|
224
|
+
|
225
|
+
This class implements the necessary steps for an OAuth 2.0 client,
|
226
|
+
including generating authorization request parameters, handling the
|
227
|
+
redirect callback from the authorization server, exchanging the
|
228
|
+
authorization code for access/refresh tokens, and refreshing tokens.
|
173
229
|
|
174
230
|
Attributes:
|
175
|
-
name:
|
176
|
-
store: Store
|
177
|
-
client_id: OAuth
|
178
|
-
client_secret: OAuth
|
179
|
-
auth_url:
|
180
|
-
token_url:
|
181
|
-
scope: OAuth
|
231
|
+
name (str): Name of the integration.
|
232
|
+
store (BaseStore): Store for OAuth tokens.
|
233
|
+
client_id (str | None): The OAuth 2.0 Client ID.
|
234
|
+
client_secret (str | None): The OAuth 2.0 Client Secret.
|
235
|
+
auth_url (str | None): The authorization server's endpoint URL.
|
236
|
+
token_url (str | None): The token server's endpoint URL.
|
237
|
+
scope (str | None): The requested OAuth scopes, space-separated.
|
182
238
|
"""
|
183
239
|
|
184
240
|
def __init__(
|
@@ -192,6 +248,19 @@ class OAuthIntegration(Integration):
|
|
192
248
|
scope: str | None = None,
|
193
249
|
**kwargs,
|
194
250
|
):
|
251
|
+
"""Initializes the OAuthIntegration.
|
252
|
+
|
253
|
+
Args:
|
254
|
+
name (str): The unique name for this integration instance.
|
255
|
+
store (BaseStore | None, optional): Store for credentials.
|
256
|
+
Defaults to `MemoryStore()`.
|
257
|
+
client_id (str | None, optional): The OAuth 2.0 Client ID.
|
258
|
+
client_secret (str | None, optional): The OAuth 2.0 Client Secret.
|
259
|
+
auth_url (str | None, optional): The authorization server's endpoint URL.
|
260
|
+
token_url (str | None, optional): The token server's endpoint URL.
|
261
|
+
scope (str | None, optional): The requested OAuth scopes, space-separated.
|
262
|
+
**kwargs: Additional arguments passed to the parent `Integration`.
|
263
|
+
"""
|
195
264
|
super().__init__(name, store, **kwargs)
|
196
265
|
self.client_id = client_id
|
197
266
|
self.client_secret = client_secret
|
@@ -200,24 +269,30 @@ class OAuthIntegration(Integration):
|
|
200
269
|
self.scope = scope
|
201
270
|
|
202
271
|
def get_credentials(self) -> dict[str, Any] | None:
|
203
|
-
"""
|
272
|
+
"""Retrieves stored OAuth tokens for this integration.
|
204
273
|
|
205
274
|
Returns:
|
206
|
-
|
275
|
+
dict[str, Any] | None: A dictionary containing the OAuth tokens
|
276
|
+
(e.g., `access_token`, `refresh_token`) if found,
|
277
|
+
otherwise None.
|
207
278
|
"""
|
208
279
|
credentials = self.store.get(self.name)
|
209
280
|
if not credentials:
|
210
281
|
return None
|
211
|
-
return credentials
|
282
|
+
return credentials # type: ignore
|
212
283
|
|
213
284
|
def set_credentials(self, credentials: dict[str, Any]) -> None:
|
214
|
-
"""
|
285
|
+
"""Stores OAuth tokens for this integration.
|
286
|
+
|
287
|
+
Validates that essential fields like 'access_token' are present.
|
215
288
|
|
216
289
|
Args:
|
217
|
-
credentials:
|
290
|
+
credentials (dict[str, Any]): A dictionary containing OAuth tokens.
|
291
|
+
Must include at least 'access_token'.
|
218
292
|
|
219
293
|
Raises:
|
220
|
-
ValueError: If credentials
|
294
|
+
ValueError: If `credentials` is not a dictionary or if 'access_token'
|
295
|
+
is missing.
|
221
296
|
"""
|
222
297
|
if not credentials or not isinstance(credentials, dict):
|
223
298
|
raise ValueError("Invalid credentials format")
|
@@ -226,13 +301,19 @@ class OAuthIntegration(Integration):
|
|
226
301
|
self.store.set(self.name, credentials)
|
227
302
|
|
228
303
|
def authorize(self) -> dict[str, Any]:
|
229
|
-
"""
|
304
|
+
"""Constructs parameters required for the OAuth authorization request.
|
305
|
+
|
306
|
+
These parameters are typically used to build the URL to which the
|
307
|
+
user must be redirected to grant authorization.
|
230
308
|
|
231
309
|
Returns:
|
232
|
-
|
310
|
+
dict[str, Any]: A dictionary containing the authorization endpoint URL
|
311
|
+
(`url`), query parameters (`params`), client secret,
|
312
|
+
and token URL.
|
233
313
|
|
234
314
|
Raises:
|
235
|
-
ValueError: If
|
315
|
+
ValueError: If essential OAuth configuration like `client_id`,
|
316
|
+
`client_secret`, `auth_url`, or `token_url` is missing.
|
236
317
|
"""
|
237
318
|
if not all([self.client_id, self.client_secret, self.auth_url, self.token_url]):
|
238
319
|
raise ValueError("Missing required OAuth configuration")
|
@@ -251,19 +332,24 @@ class OAuthIntegration(Integration):
|
|
251
332
|
}
|
252
333
|
|
253
334
|
def handle_callback(self, code: str) -> dict[str, Any]:
|
254
|
-
"""
|
335
|
+
"""Handles the OAuth callback by exchanging the authorization code for tokens.
|
336
|
+
|
337
|
+
This method is called after the user authorizes the application and the
|
338
|
+
authorization server redirects back with an authorization code.
|
255
339
|
|
256
340
|
Args:
|
257
|
-
code:
|
341
|
+
code (str): The authorization code received from the OAuth server.
|
258
342
|
|
259
343
|
Returns:
|
260
|
-
|
344
|
+
dict[str, Any]: A dictionary containing the access token, refresh token
|
345
|
+
(if any), and other token response data. These are also
|
346
|
+
stored via `set_credentials`.
|
261
347
|
|
262
348
|
Raises:
|
263
|
-
ValueError: If
|
264
|
-
httpx.
|
349
|
+
ValueError: If essential OAuth configuration is missing.
|
350
|
+
httpx.HTTPStatusError: If the token exchange request to `token_url` fails.
|
265
351
|
"""
|
266
|
-
if not all([self.client_id, self.client_secret, self.token_url]):
|
352
|
+
if not all([self.client_id, self.client_secret, self.token_url]): # type: ignore
|
267
353
|
raise ValueError("Missing required OAuth configuration")
|
268
354
|
|
269
355
|
token_params = {
|
@@ -273,24 +359,26 @@ class OAuthIntegration(Integration):
|
|
273
359
|
"grant_type": "authorization_code",
|
274
360
|
}
|
275
361
|
|
276
|
-
response = httpx.post(self.token_url, data=token_params)
|
362
|
+
response = httpx.post(self.token_url, data=token_params) # type: ignore
|
277
363
|
response.raise_for_status()
|
278
364
|
credentials = response.json()
|
279
365
|
self.store.set(self.name, credentials)
|
280
366
|
return credentials
|
281
367
|
|
282
368
|
def refresh_token(self) -> dict[str, Any]:
|
283
|
-
"""
|
369
|
+
"""Refreshes an expired access token using a stored refresh token.
|
284
370
|
|
285
371
|
Returns:
|
286
|
-
|
372
|
+
dict[str, Any]: A dictionary containing the new access token,
|
373
|
+
refresh token, and other token response data.
|
374
|
+
These are also stored.
|
287
375
|
|
288
376
|
Raises:
|
289
|
-
ValueError: If
|
290
|
-
|
291
|
-
|
377
|
+
ValueError: If essential OAuth configuration is missing.
|
378
|
+
KeyError: If a refresh token is not found in the stored credentials.
|
379
|
+
httpx.HTTPStatusError: If the token refresh request fails.
|
292
380
|
"""
|
293
|
-
if not all([self.client_id, self.client_secret, self.token_url]):
|
381
|
+
if not all([self.client_id, self.client_secret, self.token_url]): # type: ignore
|
294
382
|
raise ValueError("Missing required OAuth configuration")
|
295
383
|
|
296
384
|
credentials = self.get_credentials()
|
@@ -304,7 +392,7 @@ class OAuthIntegration(Integration):
|
|
304
392
|
"refresh_token": credentials["refresh_token"],
|
305
393
|
}
|
306
394
|
|
307
|
-
response = httpx.post(self.token_url, data=token_params)
|
395
|
+
response = httpx.post(self.token_url, data=token_params) # type: ignore
|
308
396
|
response.raise_for_status()
|
309
397
|
credentials = response.json()
|
310
398
|
self.store.set(self.name, credentials)
|
@@ -312,49 +400,61 @@ class OAuthIntegration(Integration):
|
|
312
400
|
|
313
401
|
|
314
402
|
class AgentRIntegration(Integration):
|
315
|
-
"""
|
316
|
-
|
317
|
-
This class handles API key authentication and OAuth authorization flow for AgentR services.
|
403
|
+
"""Manages authentication and authorization via the AgentR platform.
|
318
404
|
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
**kwargs: Additional keyword arguments passed to parent Integration class
|
405
|
+
This integration uses an `AgentrClient` to interact with the AgentR API
|
406
|
+
for operations like retrieving authorization URLs and fetching stored
|
407
|
+
credentials. It simplifies integration with services supported by AgentR.
|
323
408
|
|
324
|
-
|
325
|
-
|
409
|
+
Attributes:
|
410
|
+
name (str): Name of the integration (e.g., "github", "google").
|
411
|
+
store (BaseStore): Store, typically not used directly by this class
|
412
|
+
as AgentR manages the primary credential storage.
|
413
|
+
client (AgentrClient): Client for communicating with the AgentR API.
|
414
|
+
_credentials (dict | None): Cached credentials.
|
326
415
|
"""
|
327
416
|
|
328
|
-
def __init__(self, name: str,
|
417
|
+
def __init__(self, name: str, client: AgentrClient | None = None, **kwargs):
|
418
|
+
"""Initializes the AgentRIntegration.
|
419
|
+
|
420
|
+
Args:
|
421
|
+
name (str): The name of the service integration as configured on
|
422
|
+
the AgentR platform (e.g., "github").
|
423
|
+
client (AgentrClient | None, optional): The AgentR client. If not provided,
|
424
|
+
a new `AgentrClient` will be created.
|
425
|
+
**kwargs: Additional arguments passed to the parent `Integration`.
|
426
|
+
"""
|
329
427
|
super().__init__(name, **kwargs)
|
330
|
-
self.client = AgentrClient(
|
428
|
+
self.client = client or AgentrClient()
|
331
429
|
self._credentials = None
|
332
430
|
|
333
|
-
def set_credentials(self, credentials: dict | None = None):
|
334
|
-
"""
|
431
|
+
def set_credentials(self, credentials: dict[str, Any] | None = None) -> str:
|
432
|
+
"""Not used for direct credential setting; initiates authorization instead.
|
335
433
|
|
336
|
-
|
434
|
+
For AgentR integrations, credentials are set via the AgentR platform's
|
435
|
+
OAuth flow. This method effectively redirects to the `authorize` flow.
|
337
436
|
|
338
437
|
Args:
|
339
|
-
credentials (dict | None, optional):
|
438
|
+
credentials (dict | None, optional): Not used by this implementation.
|
340
439
|
|
341
440
|
Returns:
|
342
|
-
str:
|
441
|
+
str: The authorization URL or message from the `authorize()` method.
|
343
442
|
"""
|
344
443
|
return self.authorize()
|
345
444
|
|
346
445
|
@property
|
347
446
|
def credentials(self):
|
348
|
-
"""
|
447
|
+
"""Retrieves credentials from the AgentR API, with caching.
|
349
448
|
|
350
|
-
|
449
|
+
If credentials are not cached locally (in `_credentials`), this property
|
450
|
+
fetches them from the AgentR platform using `self.client.get_credentials`.
|
351
451
|
|
352
452
|
Returns:
|
353
|
-
dict:
|
453
|
+
dict: The credentials dictionary obtained from AgentR.
|
354
454
|
|
355
455
|
Raises:
|
356
|
-
NotAuthorizedError: If credentials are not found (404
|
357
|
-
|
456
|
+
NotAuthorizedError: If credentials are not found (e.g., 404 from AgentR).
|
457
|
+
httpx.HTTPStatusError: For other API errors from AgentR.
|
358
458
|
"""
|
359
459
|
if self._credentials is not None:
|
360
460
|
return self._credentials
|
@@ -362,28 +462,27 @@ class AgentRIntegration(Integration):
|
|
362
462
|
return self._credentials
|
363
463
|
|
364
464
|
def get_credentials(self):
|
365
|
-
"""
|
366
|
-
|
367
|
-
Makes API request to retrieve stored credentials for this integration.
|
465
|
+
"""Retrieves credentials from the AgentR API. Alias for `credentials` property.
|
368
466
|
|
369
467
|
Returns:
|
370
|
-
dict:
|
468
|
+
dict: The credentials dictionary obtained from AgentR.
|
371
469
|
|
372
470
|
Raises:
|
373
|
-
NotAuthorizedError: If credentials are not found
|
374
|
-
|
471
|
+
NotAuthorizedError: If credentials are not found.
|
472
|
+
httpx.HTTPStatusError: For other API errors.
|
375
473
|
"""
|
376
474
|
return self.credentials
|
377
475
|
|
378
|
-
def authorize(self):
|
379
|
-
"""
|
476
|
+
def authorize(self) -> str:
|
477
|
+
"""Retrieves the authorization URL from the AgentR platform.
|
380
478
|
|
381
|
-
|
479
|
+
This URL should be presented to the user to initiate the OAuth flow
|
480
|
+
managed by AgentR for the service associated with `self.name`.
|
382
481
|
|
383
482
|
Returns:
|
384
|
-
str:
|
483
|
+
str: The authorization URL.
|
385
484
|
|
386
485
|
Raises:
|
387
|
-
|
486
|
+
httpx.HTTPStatusError: If the API request to AgentR fails.
|
388
487
|
"""
|
389
488
|
return self.client.get_authorization_url(self.name)
|
@@ -12,4 +12,4 @@ def server_from_config(config: ServerConfig):
|
|
12
12
|
raise ValueError(f"Unsupported server type: {config.type}")
|
13
13
|
|
14
14
|
|
15
|
-
__all__ = [AgentRServer, LocalServer, SingleMCPServer, BaseServer, server_from_config]
|
15
|
+
__all__ = ["AgentRServer", "LocalServer", "SingleMCPServer", "BaseServer", "server_from_config"]
|