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.
Files changed (48) hide show
  1. universal_mcp/analytics.py +43 -11
  2. universal_mcp/applications/application.py +186 -132
  3. universal_mcp/applications/sample_tool_app.py +80 -0
  4. universal_mcp/cli.py +5 -229
  5. universal_mcp/client/agents/__init__.py +4 -0
  6. universal_mcp/client/agents/base.py +38 -0
  7. universal_mcp/client/agents/llm.py +115 -0
  8. universal_mcp/client/agents/react.py +67 -0
  9. universal_mcp/client/cli.py +181 -0
  10. universal_mcp/client/oauth.py +122 -18
  11. universal_mcp/client/token_store.py +62 -3
  12. universal_mcp/client/{client.py → transport.py} +127 -48
  13. universal_mcp/config.py +160 -46
  14. universal_mcp/exceptions.py +50 -6
  15. universal_mcp/integrations/__init__.py +1 -4
  16. universal_mcp/integrations/integration.py +220 -121
  17. universal_mcp/servers/__init__.py +1 -1
  18. universal_mcp/servers/server.py +114 -247
  19. universal_mcp/stores/store.py +126 -93
  20. universal_mcp/tools/func_metadata.py +1 -1
  21. universal_mcp/tools/manager.py +15 -3
  22. universal_mcp/tools/tools.py +2 -2
  23. universal_mcp/utils/agentr.py +3 -4
  24. universal_mcp/utils/installation.py +3 -4
  25. universal_mcp/utils/openapi/api_generator.py +28 -2
  26. universal_mcp/utils/openapi/api_splitter.py +0 -1
  27. universal_mcp/utils/openapi/cli.py +243 -0
  28. universal_mcp/utils/openapi/filters.py +114 -0
  29. universal_mcp/utils/openapi/openapi.py +31 -2
  30. universal_mcp/utils/openapi/preprocessor.py +62 -7
  31. universal_mcp/utils/prompts.py +787 -0
  32. universal_mcp/utils/singleton.py +4 -1
  33. universal_mcp/utils/testing.py +6 -6
  34. universal_mcp-0.1.24rc2.dist-info/METADATA +54 -0
  35. universal_mcp-0.1.24rc2.dist-info/RECORD +53 -0
  36. universal_mcp/applications/README.md +0 -122
  37. universal_mcp/client/__main__.py +0 -30
  38. universal_mcp/client/agent.py +0 -96
  39. universal_mcp/integrations/README.md +0 -25
  40. universal_mcp/servers/README.md +0 -79
  41. universal_mcp/stores/README.md +0 -74
  42. universal_mcp/tools/README.md +0 -86
  43. universal_mcp-0.1.23rc2.dist-info/METADATA +0 -283
  44. universal_mcp-0.1.23rc2.dist-info/RECORD +0 -51
  45. /universal_mcp/{utils → tools}/docstring_parser.py +0 -0
  46. {universal_mcp-0.1.23rc2.dist-info → universal_mcp-0.1.24rc2.dist-info}/WHEEL +0 -0
  47. {universal_mcp-0.1.23rc2.dist-info → universal_mcp-0.1.24rc2.dist-info}/entry_points.txt +0 -0
  48. {universal_mcp-0.1.23rc2.dist-info → universal_mcp-0.1.24rc2.dist-info}/licenses/LICENSE +0 -0
@@ -8,6 +8,14 @@ from loguru import logger
8
8
 
9
9
 
10
10
  class Analytics:
11
+ """A singleton class for tracking analytics events using PostHog.
12
+
13
+ This class handles the initialization of the PostHog client and provides
14
+ methods to track key events such as application loading and tool execution.
15
+ Telemetry can be disabled by setting the TELEMETRY_DISABLED environment
16
+ variable to "true".
17
+ """
18
+
11
19
  _instance = None
12
20
 
13
21
  def __new__(cls):
@@ -17,7 +25,13 @@ class Analytics:
17
25
  return cls._instance
18
26
 
19
27
  def _initialize(self):
20
- """Initialize the Analytics singleton"""
28
+ """Initializes the PostHog client and sets up analytics properties.
29
+
30
+ This internal method configures the PostHog API key and host.
31
+ It also determines if analytics should be enabled based on the
32
+ TELEMETRY_DISABLED environment variable and generates a unique
33
+ user ID.
34
+ """
21
35
  posthog.host = "https://us.i.posthog.com"
22
36
  posthog.api_key = "phc_6HXMDi8CjfIW0l04l34L7IDkpCDeOVz9cOz1KLAHXh8"
23
37
  self.enabled = os.getenv("TELEMETRY_DISABLED", "false").lower() != "true"
@@ -26,16 +40,27 @@ class Analytics:
26
40
  @staticmethod
27
41
  @lru_cache(maxsize=1)
28
42
  def get_version():
29
- """
30
- Get the version of the Universal MCP
43
+ """Retrieves the installed version of the universal_mcp package.
44
+
45
+ Uses importlib.metadata to get the package version.
46
+ Caches the result for efficiency.
47
+
48
+ Returns:
49
+ str: The package version string, or "unknown" if not found.
31
50
  """
32
51
  try:
33
52
  return version("universal_mcp")
34
- except ImportError:
53
+ except ImportError: # Should be PackageNotFoundError, but matching existing code
35
54
  return "unknown"
36
55
 
37
56
  def track_app_loaded(self, app_name: str):
38
- """Track when the app is loaded"""
57
+ """Tracks an event when an application is successfully loaded.
58
+
59
+ This event helps understand which applications are being utilized.
60
+
61
+ Args:
62
+ app_name (str): The name of the application that was loaded.
63
+ """
39
64
  if not self.enabled:
40
65
  return
41
66
  try:
@@ -53,15 +78,22 @@ class Analytics:
53
78
  app_name: str,
54
79
  status: str,
55
80
  error: str = None,
56
- user_id=None,
81
+ user_id=None, # Note: user_id is captured in PostHog but not used from this param
57
82
  ):
58
- """Track when a tool is called
83
+ """Tracks an event when a tool is called within an application.
84
+
85
+ This event provides insights into tool usage patterns, success rates,
86
+ and potential errors.
59
87
 
60
88
  Args:
61
- tool_name: Name of the tool being called
62
- status: Status of the tool call (success/error)
63
- error: Error message if status is error
64
- user_id: Optional user ID to track
89
+ tool_name (str): The name of the tool that was called.
90
+ app_name (str): The name of the application the tool belongs to.
91
+ status (str): The status of the tool call (e.g., "success", "error").
92
+ error (str, optional): The error message if the tool call failed.
93
+ Defaults to None.
94
+ user_id (str, optional): An optional user identifier.
95
+ Note: Currently, the class uses an internally
96
+ generated user_id for PostHog events.
65
97
  """
66
98
  if not self.enabled:
67
99
  return
@@ -14,25 +14,26 @@ from universal_mcp.integrations import Integration
14
14
 
15
15
 
16
16
  class BaseApplication(ABC):
17
- """
18
- Base class for all applications in the Universal MCP system.
17
+ """Defines the foundational structure for applications in Universal MCP.
19
18
 
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.
19
+ This abstract base class (ABC) outlines the common interface and core
20
+ functionality that all concrete application classes must implement.
21
+ It handles basic initialization, such as setting the application name,
22
+ and mandates the implementation of a method to list available tools.
23
+ Analytics for application loading are also tracked here.
23
24
 
24
25
  Attributes:
25
- name (str): The name of the application
26
- _credentials (Optional[Dict[str, Any]]): Cached credentials for the application
26
+ name (str): The unique name identifying the application.
27
27
  """
28
28
 
29
29
  def __init__(self, name: str, **kwargs: Any) -> None:
30
- """
31
- Initialize the base application.
30
+ """Initializes the BaseApplication.
32
31
 
33
32
  Args:
34
- name: The name of the application
35
- **kwargs: Additional keyword arguments passed to the application
33
+ name (str): The unique name for this application instance.
34
+ **kwargs (Any): Additional keyword arguments that might be specific
35
+ to the concrete application implementation. These are
36
+ logged but not directly used by BaseApplication.
36
37
  """
37
38
  self.name = name
38
39
  logger.debug(f"Initializing Application '{name}' with kwargs: {kwargs}")
@@ -40,28 +41,35 @@ class BaseApplication(ABC):
40
41
 
41
42
  @abstractmethod
42
43
  def list_tools(self) -> list[Callable]:
43
- """
44
- List all available tools for the application.
44
+ """Lists all tools provided by this application.
45
+
46
+ This method must be implemented by concrete subclasses to return
47
+ a list of callable tool objects that the application exposes.
45
48
 
46
49
  Returns:
47
- List[Any]: A list of tools available in the application
50
+ list[Callable]: A list of callable objects, where each callable
51
+ represents a tool offered by the application.
48
52
  """
49
53
  pass
50
54
 
51
55
 
52
56
  class APIApplication(BaseApplication):
53
- """
54
- Application that uses HTTP APIs to interact with external services.
57
+ """Base class for applications interacting with RESTful HTTP APIs.
55
58
 
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
+ Extends `BaseApplication` to provide functionalities specific to
60
+ API-based integrations. This includes managing an `httpx.Client`
61
+ for making HTTP requests, handling authentication headers, processing
62
+ responses, and offering convenient methods for common HTTP verbs
63
+ (GET, POST, PUT, DELETE, PATCH).
59
64
 
60
65
  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
66
+ name (str): The name of the application.
67
+ integration (Integration | None): An optional Integration object
68
+ responsible for managing authentication and credentials.
69
+ default_timeout (int): The default timeout in seconds for HTTP requests.
70
+ base_url (str): The base URL for the API endpoint. This should be
71
+ set by the subclass.
72
+ _client (httpx.Client | None): The internal httpx client instance.
65
73
  """
66
74
 
67
75
  def __init__(
@@ -71,13 +79,17 @@ class APIApplication(BaseApplication):
71
79
  client: httpx.Client | None = None,
72
80
  **kwargs: Any,
73
81
  ) -> None:
74
- """
75
- Initialize the API application.
82
+ """Initializes the APIApplication.
76
83
 
77
84
  Args:
78
- name: The name of the application
79
- integration: Optional integration configuration
80
- **kwargs: Additional keyword arguments
85
+ name (str): The unique name for this application instance.
86
+ integration (Integration | None, optional): An Integration object
87
+ to handle authentication. Defaults to None.
88
+ client (httpx.Client | None, optional): An existing httpx.Client
89
+ instance. If None, a new client will be created on demand.
90
+ Defaults to None.
91
+ **kwargs (Any): Additional keyword arguments passed to the
92
+ BaseApplication.
81
93
  """
82
94
  super().__init__(name, **kwargs)
83
95
  self.default_timeout: int = 180
@@ -87,15 +99,17 @@ class APIApplication(BaseApplication):
87
99
  self.base_url: str = ""
88
100
 
89
101
  def _get_headers(self) -> dict[str, str]:
90
- """
91
- Get the headers for API requests.
102
+ """Constructs HTTP headers for API requests based on the integration.
92
103
 
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.
104
+ Retrieves credentials from the configured `integration` and attempts
105
+ to create appropriate authentication headers. It supports direct header
106
+ injection, API keys (as Bearer tokens), and access tokens (as Bearer
107
+ tokens).
96
108
 
97
109
  Returns:
98
- Dict[str, str]: Headers to be used in API requests
110
+ dict[str, str]: A dictionary of HTTP headers. Returns an empty
111
+ dictionary if no integration is configured or if
112
+ no suitable credentials are found.
99
113
  """
100
114
  if not self.integration:
101
115
  logger.debug("No integration configured, returning empty headers")
@@ -131,14 +145,15 @@ class APIApplication(BaseApplication):
131
145
 
132
146
  @property
133
147
  def client(self) -> httpx.Client:
134
- """
135
- Get the HTTP client instance.
148
+ """Provides an initialized `httpx.Client` instance.
136
149
 
137
- This property ensures that the HTTP client is properly initialized
138
- with the correct base URL and headers.
150
+ If a client was not provided during initialization or has not been
151
+ created yet, this property will instantiate a new `httpx.Client`.
152
+ The client is configured with the `base_url` and headers derived
153
+ from the `_get_headers` method.
139
154
 
140
155
  Returns:
141
- httpx.Client: The initialized HTTP client
156
+ httpx.Client: The active `httpx.Client` instance.
142
157
  """
143
158
  if not self._client:
144
159
  headers = self._get_headers()
@@ -150,22 +165,25 @@ class APIApplication(BaseApplication):
150
165
  return self._client
151
166
 
152
167
  def _handle_response(self, response: httpx.Response) -> dict[str, Any]:
153
- """
154
- Handle API responses by checking for errors and parsing the response appropriately.
168
+ """Processes an HTTP response, checking for errors and parsing JSON.
155
169
 
156
- This method:
157
- 1. Checks for API errors and provides detailed error context including status code and response body
158
- 2. For successful responses, automatically parses JSON or returns success message
170
+ This method first calls `response.raise_for_status()` to raise an
171
+ `httpx.HTTPStatusError` if the HTTP request failed. If successful,
172
+ it attempts to parse the response body as JSON. If JSON parsing
173
+ fails, it returns a dictionary containing the success status,
174
+ status code, and raw text of the response.
159
175
 
160
176
  Args:
161
- response: The HTTP response to process
177
+ response (httpx.Response): The HTTP response object from `httpx`.
162
178
 
163
179
  Returns:
164
- dict[str, Any] | str: Parsed JSON data if response contains JSON,
165
- otherwise a success message with status code
180
+ dict[str, Any]: The parsed JSON response as a dictionary, or
181
+ a status dictionary if JSON parsing is not possible
182
+ for a successful response.
166
183
 
167
184
  Raises:
168
- httpx.HTTPStatusError: If the response indicates an error status, with full error details
185
+ httpx.HTTPStatusError: If the HTTP response status code indicates
186
+ an error (4xx or 5xx).
169
187
  """
170
188
  response.raise_for_status()
171
189
  try:
@@ -174,18 +192,19 @@ class APIApplication(BaseApplication):
174
192
  return {"status": "success", "status_code": response.status_code, "text": response.text}
175
193
 
176
194
  def _get(self, url: str, params: dict[str, Any] | None = None) -> httpx.Response:
177
- """
178
- Make a GET request to the specified URL.
195
+ """Makes a GET request to the specified URL.
179
196
 
180
197
  Args:
181
- url: The URL to send the request to
182
- params: Optional query parameters
198
+ url (str): The URL endpoint for the request (relative to `base_url`).
199
+ params (dict[str, Any] | None, optional): Optional URL query parameters.
200
+ Defaults to None.
183
201
 
184
202
  Returns:
185
- httpx.Response: The raw HTTP response object
203
+ httpx.Response: The raw HTTP response object. The `_handle_response`
204
+ method should typically be used to process this.
186
205
 
187
206
  Raises:
188
- httpx.HTTPStatusError: If the request fails (when raise_for_status() is called)
207
+ httpx.HTTPStatusError: Propagated if the underlying client request fails.
189
208
  """
190
209
  logger.debug(f"Making GET request to {url} with params: {params}")
191
210
  response = self.client.get(url, params=params)
@@ -200,26 +219,34 @@ class APIApplication(BaseApplication):
200
219
  content_type: str = "application/json",
201
220
  files: dict[str, Any] | None = None,
202
221
  ) -> httpx.Response:
203
- """
204
- Make a POST request to the specified URL.
222
+ """Makes a POST request to the specified URL.
223
+
224
+ Handles different `content_type` values for sending data,
225
+ including 'application/json', 'application/x-www-form-urlencoded',
226
+ and 'multipart/form-data' (for file uploads).
205
227
 
206
228
  Args:
207
- url: The URL to send the request to
208
- data: The data to send. For 'application/json', this is JSON-serializable.
209
- For 'application/x-www-form-urlencoded' or 'multipart/form-data', this is a dict of form fields.
210
- For other content types, this is raw bytes or string.
211
- params: Optional query parameters
212
- content_type: The Content-Type of the request body.
213
- Examples: 'application/json', 'application/x-www-form-urlencoded',
214
- 'multipart/form-data', 'application/octet-stream', 'text/plain'.
215
- files: Optional dictionary of files to upload for 'multipart/form-data'.
216
- Example: {'file_field_name': ('filename.txt', open('file.txt', 'rb'), 'text/plain')}
229
+ url (str): The URL endpoint for the request (relative to `base_url`).
230
+ data (Any): The data to send in the request body.
231
+ For 'application/json', this should be a JSON-serializable object.
232
+ For 'application/x-www-form-urlencoded' or 'multipart/form-data' (if `files` is None),
233
+ this should be a dictionary of form fields.
234
+ For other content types (e.g., 'application/octet-stream'), this should be bytes or a string.
235
+ params (dict[str, Any] | None, optional): Optional URL query parameters.
236
+ Defaults to None.
237
+ content_type (str, optional): The Content-Type of the request body.
238
+ Defaults to "application/json".
239
+ files (dict[str, Any] | None, optional): A dictionary for file uploads
240
+ when `content_type` is 'multipart/form-data'.
241
+ Example: `{'file_field': ('filename.txt', open('file.txt', 'rb'), 'text/plain')}`.
242
+ Defaults to None.
217
243
 
218
244
  Returns:
219
- httpx.Response: The raw HTTP response object
245
+ httpx.Response: The raw HTTP response object. The `_handle_response`
246
+ method should typically be used to process this.
220
247
 
221
248
  Raises:
222
- httpx.HTTPStatusError: If the request fails (when raise_for_status() is called)
249
+ httpx.HTTPStatusError: Propagated if the underlying client request fails.
223
250
  """
224
251
  logger.debug(
225
252
  f"Making POST request to {url} with params: {params}, data type: {type(data)}, content_type={content_type}, files: {'yes' if files else 'no'}"
@@ -269,26 +296,34 @@ class APIApplication(BaseApplication):
269
296
  content_type: str = "application/json",
270
297
  files: dict[str, Any] | None = None,
271
298
  ) -> httpx.Response:
272
- """
273
- Make a PUT request to the specified URL.
299
+ """Makes a PUT request to the specified URL.
300
+
301
+ Handles different `content_type` values for sending data,
302
+ including 'application/json', 'application/x-www-form-urlencoded',
303
+ and 'multipart/form-data' (for file uploads).
274
304
 
275
305
  Args:
276
- url: The URL to send the request to
277
- data: The data to send. For 'application/json', this is JSON-serializable.
278
- For 'application/x-www-form-urlencoded' or 'multipart/form-data', this is a dict of form fields.
279
- For other content types, this is raw bytes or string.
280
- params: Optional query parameters
281
- content_type: The Content-Type of the request body.
282
- Examples: 'application/json', 'application/x-www-form-urlencoded',
283
- 'multipart/form-data', 'application/octet-stream', 'text/plain'.
284
- files: Optional dictionary of files to upload for 'multipart/form-data'.
285
- Example: {'file_field_name': ('filename.txt', open('file.txt', 'rb'), 'text/plain')}
306
+ url (str): The URL endpoint for the request (relative to `base_url`).
307
+ data (Any): The data to send in the request body.
308
+ For 'application/json', this should be a JSON-serializable object.
309
+ For 'application/x-www-form-urlencoded' or 'multipart/form-data' (if `files` is None),
310
+ this should be a dictionary of form fields.
311
+ For other content types (e.g., 'application/octet-stream'), this should be bytes or a string.
312
+ params (dict[str, Any] | None, optional): Optional URL query parameters.
313
+ Defaults to None.
314
+ content_type (str, optional): The Content-Type of the request body.
315
+ Defaults to "application/json".
316
+ files (dict[str, Any] | None, optional): A dictionary for file uploads
317
+ when `content_type` is 'multipart/form-data'.
318
+ Example: `{'file_field': ('filename.txt', open('file.txt', 'rb'), 'text/plain')}`.
319
+ Defaults to None.
286
320
 
287
321
  Returns:
288
- httpx.Response: The raw HTTP response object
322
+ httpx.Response: The raw HTTP response object. The `_handle_response`
323
+ method should typically be used to process this.
289
324
 
290
325
  Raises:
291
- httpx.HTTPStatusError: If the request fails (when raise_for_status() is called)
326
+ httpx.HTTPStatusError: Propagated if the underlying client request fails.
292
327
  """
293
328
  logger.debug(
294
329
  f"Making PUT request to {url} with params: {params}, data type: {type(data)}, content_type={content_type}, files: {'yes' if files else 'no'}"
@@ -332,18 +367,19 @@ class APIApplication(BaseApplication):
332
367
  return response
333
368
 
334
369
  def _delete(self, url: str, params: dict[str, Any] | None = None) -> httpx.Response:
335
- """
336
- Make a DELETE request to the specified URL.
370
+ """Makes a DELETE request to the specified URL.
337
371
 
338
372
  Args:
339
- url: The URL to send the request to
340
- params: Optional query parameters
373
+ url (str): The URL endpoint for the request (relative to `base_url`).
374
+ params (dict[str, Any] | None, optional): Optional URL query parameters.
375
+ Defaults to None.
341
376
 
342
377
  Returns:
343
- httpx.Response: The raw HTTP response object
378
+ httpx.Response: The raw HTTP response object. The `_handle_response`
379
+ method should typically be used to process this.
344
380
 
345
381
  Raises:
346
- httpx.HTTPStatusError: If the request fails (when raise_for_status() is called)
382
+ httpx.HTTPStatusError: Propagated if the underlying client request fails.
347
383
  """
348
384
  logger.debug(f"Making DELETE request to {url} with params: {params}")
349
385
  response = self.client.delete(url, params=params, timeout=self.default_timeout)
@@ -351,19 +387,21 @@ class APIApplication(BaseApplication):
351
387
  return response
352
388
 
353
389
  def _patch(self, url: str, data: dict[str, Any], params: dict[str, Any] | None = None) -> httpx.Response:
354
- """
355
- Make a PATCH request to the specified URL.
390
+ """Makes a PATCH request to the specified URL.
356
391
 
357
392
  Args:
358
- url: The URL to send the request to
359
- data: The data to send in the request body
360
- params: Optional query parameters
393
+ url (str): The URL endpoint for the request (relative to `base_url`).
394
+ data (dict[str, Any]): The JSON-serializable data to send in the
395
+ request body.
396
+ params (dict[str, Any] | None, optional): Optional URL query parameters.
397
+ Defaults to None.
361
398
 
362
399
  Returns:
363
- httpx.Response: The raw HTTP response object
400
+ httpx.Response: The raw HTTP response object. The `_handle_response`
401
+ method should typically be used to process this.
364
402
 
365
403
  Raises:
366
- httpx.HTTPStatusError: If the request fails (when raise_for_status() is called)
404
+ httpx.HTTPStatusError: Propagated if the underlying client request fails.
367
405
  """
368
406
  logger.debug(f"Making PATCH request to {url} with params: {params} and data: {data}")
369
407
  response = self.client.patch(
@@ -376,17 +414,20 @@ class APIApplication(BaseApplication):
376
414
 
377
415
 
378
416
  class GraphQLApplication(BaseApplication):
379
- """
380
- Application that uses GraphQL to interact with external services.
417
+ """Base class for applications interacting with GraphQL APIs.
381
418
 
382
- This class provides a base implementation for applications that communicate
383
- with external services via GraphQL. It handles authentication, query execution,
384
- and response processing.
419
+ Extends `BaseApplication` to facilitate interactions with services
420
+ that provide a GraphQL endpoint. It manages a `gql.Client` for
421
+ executing queries and mutations, handles authentication headers
422
+ similarly to `APIApplication`, and provides dedicated methods for
423
+ GraphQL operations.
385
424
 
386
425
  Attributes:
387
- name (str): The name of the application
388
- base_url (str): Base URL for GraphQL endpoint
389
- integration (Optional[Integration]): The integration configuration
426
+ name (str): The name of the application.
427
+ base_url (str): The complete URL of the GraphQL endpoint.
428
+ integration (Integration | None): An optional Integration object
429
+ for managing authentication.
430
+ _client (GraphQLClient | None): The internal `gql.Client` instance.
390
431
  """
391
432
 
392
433
  def __init__(
@@ -397,14 +438,18 @@ class GraphQLApplication(BaseApplication):
397
438
  client: GraphQLClient | None = None,
398
439
  **kwargs: Any,
399
440
  ) -> None:
400
- """
401
- Initialize the GraphQL application.
441
+ """Initializes the GraphQLApplication.
402
442
 
403
443
  Args:
404
- name: The name of the application
405
- base_url: The base URL for the GraphQL endpoint
406
- integration: Optional integration configuration
407
- **kwargs: Additional keyword arguments
444
+ name (str): The unique name for this application instance.
445
+ base_url (str): The full URL of the GraphQL endpoint.
446
+ integration (Integration | None, optional): An Integration object
447
+ to handle authentication. Defaults to None.
448
+ client (GraphQLClient | None, optional): An existing `gql.Client`
449
+ instance. If None, a new client will be created on demand.
450
+ Defaults to None.
451
+ **kwargs (Any): Additional keyword arguments passed to the
452
+ BaseApplication.
408
453
  """
409
454
  super().__init__(name, **kwargs)
410
455
  self.base_url = base_url
@@ -413,15 +458,16 @@ class GraphQLApplication(BaseApplication):
413
458
  self._client: GraphQLClient | None = client
414
459
 
415
460
  def _get_headers(self) -> dict[str, str]:
416
- """
417
- Get the headers for GraphQL requests.
461
+ """Constructs HTTP headers for GraphQL requests based on the integration.
418
462
 
419
- This method constructs the appropriate headers based on the available
420
- credentials. It supports various authentication methods including
421
- direct headers, API keys, and access tokens.
463
+ Retrieves credentials from the configured `integration` and attempts
464
+ to create appropriate authentication headers. Primarily supports
465
+ API keys or access tokens as Bearer tokens in the Authorization header.
422
466
 
423
467
  Returns:
424
- Dict[str, str]: Headers to be used in GraphQL requests
468
+ dict[str, str]: A dictionary of HTTP headers. Returns an empty
469
+ dictionary if no integration is configured or if
470
+ no suitable credentials are found.
425
471
  """
426
472
  if not self.integration:
427
473
  logger.debug("No integration configured, returning empty headers")
@@ -455,14 +501,16 @@ class GraphQLApplication(BaseApplication):
455
501
 
456
502
  @property
457
503
  def client(self) -> GraphQLClient:
458
- """
459
- Get the GraphQL client instance.
504
+ """Provides an initialized `gql.Client` instance.
460
505
 
461
- This property ensures that the GraphQL client is properly initialized
462
- with the correct transport and headers.
506
+ If a client was not provided during initialization or has not been
507
+ created yet, this property instantiates a new `gql.Client`.
508
+ The client is configured with a `RequestsHTTPTransport` using the
509
+ `base_url` and headers from `_get_headers`. It's also set to
510
+ fetch the schema from the transport.
463
511
 
464
512
  Returns:
465
- Client: The initialized GraphQL client
513
+ GraphQLClient: The active `gql.Client` instance.
466
514
  """
467
515
  if not self._client:
468
516
  headers = self._get_headers()
@@ -475,18 +523,21 @@ class GraphQLApplication(BaseApplication):
475
523
  mutation: str | DocumentNode,
476
524
  variables: dict[str, Any] | None = None,
477
525
  ) -> dict[str, Any]:
478
- """
479
- Execute a GraphQL mutation.
526
+ """Executes a GraphQL mutation.
480
527
 
481
528
  Args:
482
- mutation: The GraphQL mutation string or DocumentNode
483
- variables: Optional variables for the mutation
529
+ mutation (str | DocumentNode): The GraphQL mutation string or a
530
+ pre-parsed `gql.DocumentNode` object. If a string is provided,
531
+ it will be parsed using `gql()`.
532
+ variables (dict[str, Any] | None, optional): A dictionary of variables
533
+ to pass with the mutation. Defaults to None.
484
534
 
485
535
  Returns:
486
- Dict[str, Any]: The result of the mutation
536
+ dict[str, Any]: The JSON response from the GraphQL server as a dictionary.
487
537
 
488
538
  Raises:
489
- Exception: If the mutation execution fails
539
+ Exception: If the GraphQL client encounters an error during execution
540
+ (e.g., network issue, GraphQL server error).
490
541
  """
491
542
  if isinstance(mutation, str):
492
543
  mutation = gql(mutation)
@@ -497,18 +548,21 @@ class GraphQLApplication(BaseApplication):
497
548
  query: str | DocumentNode,
498
549
  variables: dict[str, Any] | None = None,
499
550
  ) -> dict[str, Any]:
500
- """
501
- Execute a GraphQL query.
551
+ """Executes a GraphQL query.
502
552
 
503
553
  Args:
504
- query: The GraphQL query string or DocumentNode
505
- variables: Optional variables for the query
554
+ query (str | DocumentNode): The GraphQL query string or a
555
+ pre-parsed `gql.DocumentNode` object. If a string is provided,
556
+ it will be parsed using `gql()`.
557
+ variables (dict[str, Any] | None, optional): A dictionary of variables
558
+ to pass with the query. Defaults to None.
506
559
 
507
560
  Returns:
508
- Dict[str, Any]: The result of the query
561
+ dict[str, Any]: The JSON response from the GraphQL server as a dictionary.
509
562
 
510
563
  Raises:
511
- Exception: If the query execution fails
564
+ Exception: If the GraphQL client encounters an error during execution
565
+ (e.g., network issue, GraphQL server error).
512
566
  """
513
567
  if isinstance(query, str):
514
568
  query = gql(query)