universal-mcp 0.1.14__py3-none-any.whl → 0.1.15__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 (34) hide show
  1. universal_mcp/analytics.py +7 -1
  2. universal_mcp/applications/README.md +122 -0
  3. universal_mcp/applications/__init__.py +48 -46
  4. universal_mcp/applications/application.py +249 -40
  5. universal_mcp/cli.py +49 -49
  6. universal_mcp/config.py +95 -22
  7. universal_mcp/exceptions.py +8 -0
  8. universal_mcp/integrations/integration.py +18 -2
  9. universal_mcp/logger.py +59 -8
  10. universal_mcp/servers/__init__.py +2 -2
  11. universal_mcp/stores/store.py +2 -12
  12. universal_mcp/tools/__init__.py +14 -2
  13. universal_mcp/tools/adapters.py +25 -0
  14. universal_mcp/tools/func_metadata.py +12 -2
  15. universal_mcp/tools/manager.py +236 -0
  16. universal_mcp/tools/tools.py +5 -249
  17. universal_mcp/utils/common.py +33 -0
  18. universal_mcp/utils/openapi/__inti__.py +0 -0
  19. universal_mcp/utils/{api_generator.py → openapi/api_generator.py} +1 -1
  20. universal_mcp/utils/openapi/openapi.py +930 -0
  21. universal_mcp/utils/openapi/preprocessor.py +1223 -0
  22. universal_mcp/utils/{readme.py → openapi/readme.py} +21 -31
  23. universal_mcp/utils/templates/README.md.j2 +17 -0
  24. {universal_mcp-0.1.14.dist-info → universal_mcp-0.1.15.dist-info}/METADATA +6 -3
  25. universal_mcp-0.1.15.dist-info/RECORD +44 -0
  26. universal_mcp-0.1.15.dist-info/licenses/LICENSE +21 -0
  27. universal_mcp/templates/README.md.j2 +0 -93
  28. universal_mcp/utils/dump_app_tools.py +0 -78
  29. universal_mcp/utils/openapi.py +0 -697
  30. universal_mcp-0.1.14.dist-info/RECORD +0 -39
  31. /universal_mcp/utils/{docgen.py → openapi/docgen.py} +0 -0
  32. /universal_mcp/{templates → utils/templates}/api_client.py.j2 +0 -0
  33. {universal_mcp-0.1.14.dist-info → universal_mcp-0.1.15.dist-info}/WHEEL +0 -0
  34. {universal_mcp-0.1.14.dist-info → universal_mcp-0.1.15.dist-info}/entry_points.txt +0 -0
@@ -48,7 +48,12 @@ class Analytics:
48
48
  logger.error(f"Failed to track app_loaded event: {e}")
49
49
 
50
50
  def track_tool_called(
51
- self, tool_name: str, status: str, error: str = None, user_id=None
51
+ self,
52
+ tool_name: str,
53
+ app_name: str,
54
+ status: str,
55
+ error: str = None,
56
+ user_id=None,
52
57
  ):
53
58
  """Track when a tool is called
54
59
 
@@ -63,6 +68,7 @@ class Analytics:
63
68
  try:
64
69
  properties = {
65
70
  "tool_name": tool_name,
71
+ "app_name": app_name,
66
72
  "status": status,
67
73
  "error": error,
68
74
  "version": self.get_version(),
@@ -0,0 +1,122 @@
1
+ # Universal MCP Applications Module
2
+
3
+ This module provides the core functionality for managing and integrating applications within the Universal MCP system. It offers a flexible framework for creating, managing, and interacting with various types of applications through a unified interface.
4
+
5
+ ## Overview
6
+
7
+ The applications module provides three main base classes for building application integrations:
8
+
9
+ 1. `BaseApplication`: The abstract base class that defines the common interface for all applications
10
+ 2. `APIApplication`: A concrete implementation for applications that communicate via HTTP APIs
11
+ 3. `GraphQLApplication`: A specialized implementation for applications that use GraphQL APIs
12
+
13
+ ## Key Features
14
+
15
+ - **Dynamic Application Loading**: Applications can be loaded dynamically from external packages
16
+ - **Unified Credential Management**: Centralized handling of application credentials
17
+ - **HTTP API Support**: Built-in support for RESTful API interactions
18
+ - **GraphQL Support**: Specialized support for GraphQL-based applications
19
+ - **Automatic Package Installation**: Automatic installation of application packages from GitHub
20
+
21
+ ## Base Classes
22
+
23
+ ### BaseApplication
24
+
25
+ The foundation class for all applications, providing:
26
+ - Basic initialization
27
+ - Credential management
28
+ - Tool listing interface
29
+
30
+ ### APIApplication
31
+
32
+ Extends BaseApplication to provide:
33
+ - HTTP client management
34
+ - Authentication handling
35
+ - Common HTTP methods (GET, POST, PUT, DELETE, PATCH)
36
+ - Request/response handling
37
+
38
+ ### GraphQLApplication
39
+
40
+ Specialized for GraphQL-based applications, offering:
41
+ - GraphQL client management
42
+ - Query and mutation execution
43
+ - Authentication handling
44
+
45
+ ## Usage
46
+
47
+ ### Creating a New Application
48
+
49
+ 1. Create a new package following the naming convention: `universal_mcp_<app_name>`
50
+ 2. Implement your application class inheriting from one of the base classes
51
+ 3. Name your class following the convention: `<AppName>App`
52
+
53
+ Example:
54
+ ```python
55
+ from universal_mcp.applications import APIApplication
56
+
57
+ class MyApp(APIApplication):
58
+ def __init__(self, name: str, integration=None, **kwargs):
59
+ super().__init__(name, integration, **kwargs)
60
+ self.base_url = "https://api.example.com"
61
+
62
+ def list_tools(self):
63
+ return [self.my_tool]
64
+
65
+ def my_tool(self):
66
+ # Implementation here
67
+ pass
68
+ ```
69
+
70
+ ### Loading an Application
71
+
72
+ ```python
73
+ from universal_mcp.applications import app_from_slug
74
+
75
+ # The system will automatically install the package if needed
76
+ MyApp = app_from_slug("my-app")
77
+ app = MyApp("my-app-instance")
78
+ ```
79
+
80
+ ## Authentication
81
+
82
+ The module supports various authentication methods:
83
+ - API Keys
84
+ - Access Tokens
85
+ - Custom Headers
86
+ - Bearer Tokens
87
+
88
+ Credentials are managed through the integration system and can be accessed via the `credentials` property.
89
+
90
+ ## Error Handling
91
+
92
+ The module includes comprehensive error handling for:
93
+ - Package installation failures
94
+ - Import errors
95
+ - API request failures
96
+ - Authentication issues
97
+
98
+ ## Logging
99
+
100
+ All operations are logged using the `loguru` logger, providing detailed information about:
101
+ - Application initialization
102
+ - API requests
103
+ - Authentication attempts
104
+ - Package installation
105
+ - Error conditions
106
+
107
+ ## Requirements
108
+
109
+ - Python 3.8+
110
+ - httpx
111
+ - gql
112
+ - loguru
113
+ - uv (for package installation)
114
+
115
+ ## Contributing
116
+
117
+ To contribute a new application:
118
+ 1. Create a new package following the naming conventions
119
+ 2. Implement the application class
120
+ 3. Add proper error handling and logging
121
+ 4. Include comprehensive documentation
122
+ 5. Submit a pull request to the Universal MCP repository
@@ -2,6 +2,7 @@ import importlib
2
2
  import os
3
3
  import subprocess
4
4
  import sys
5
+ from importlib.metadata import version
5
6
 
6
7
  from loguru import logger
7
8
 
@@ -10,6 +11,12 @@ from universal_mcp.applications.application import (
10
11
  BaseApplication,
11
12
  GraphQLApplication,
12
13
  )
14
+ from universal_mcp.utils.common import (
15
+ get_default_class_name,
16
+ get_default_module_path,
17
+ get_default_package_name,
18
+ get_default_repository_path,
19
+ )
13
20
 
14
21
  UNIVERSAL_MCP_HOME = os.path.join(os.path.expanduser("~"), ".universal-mcp", "packages")
15
22
 
@@ -24,41 +31,36 @@ sys.path.append(UNIVERSAL_MCP_HOME)
24
31
  # Class name is NameApp, eg, GoogleCalendarApp
25
32
 
26
33
 
27
- def _import_class(module_path: str, class_name: str):
28
- """
29
- Helper to import a class by name from a module.
30
- Raises ModuleNotFoundError if module or class does not exist.
31
- """
32
- try:
33
- module = importlib.import_module(module_path)
34
- except ModuleNotFoundError as e:
35
- logger.debug(f"Import failed for module '{module_path}': {e}")
36
- raise
37
- try:
38
- return getattr(module, class_name)
39
- except AttributeError as e:
40
- logger.error(f"Class '{class_name}' not found in module '{module_path}'")
41
- raise ModuleNotFoundError(
42
- f"Class '{class_name}' not found in module '{module_path}'"
43
- ) from e
44
-
45
-
46
- def _install_package(slug_clean: str):
34
+ def _install_or_upgrade_package(package_name: str, repository_path: str):
47
35
  """
48
36
  Helper to install a package via pip from the universal-mcp GitHub repository.
49
37
  """
50
- repo_url = f"git+https://github.com/universal-mcp/{slug_clean}"
51
- cmd = ["uv", "pip", "install", repo_url, "--target", UNIVERSAL_MCP_HOME]
52
- logger.info(f"Installing package '{slug_clean}' with command: {' '.join(cmd)}")
38
+ try:
39
+ current_version = version(package_name)
40
+ logger.info(f"Current version of {package_name} is {current_version}")
41
+ except ImportError:
42
+ current_version = None
43
+ if current_version is not None:
44
+ return
45
+ cmd = [
46
+ "uv",
47
+ "pip",
48
+ "install",
49
+ "--upgrade",
50
+ repository_path,
51
+ "--target",
52
+ UNIVERSAL_MCP_HOME,
53
+ ]
54
+ logger.debug(f"Installing package '{package_name}' with command: {' '.join(cmd)}")
53
55
  try:
54
56
  subprocess.check_call(cmd)
55
57
  except subprocess.CalledProcessError as e:
56
- logger.error(f"Installation failed for '{slug_clean}': {e}")
58
+ logger.error(f"Installation failed for '{package_name}': {e}")
57
59
  raise ModuleNotFoundError(
58
- f"Installation failed for package '{slug_clean}'"
60
+ f"Installation failed for package '{package_name}'"
59
61
  ) from e
60
62
  else:
61
- logger.info(f"Package '{slug_clean}' installed successfully")
63
+ logger.debug(f"Package {package_name} installed successfully")
62
64
 
63
65
 
64
66
  def app_from_slug(slug: str):
@@ -66,29 +68,29 @@ def app_from_slug(slug: str):
66
68
  Dynamically resolve and return the application class for the given slug.
67
69
  Attempts installation from GitHub if the package is not found locally.
68
70
  """
69
- slug_clean = slug.strip().lower()
70
- class_name = "".join(part.capitalize() for part in slug_clean.split("-")) + "App"
71
- package_prefix = f"universal_mcp_{slug_clean.replace('-', '_')}"
72
- module_path = f"{package_prefix}.app"
73
-
74
- logger.info(
71
+ class_name = get_default_class_name(slug)
72
+ module_path = get_default_module_path(slug)
73
+ package_name = get_default_package_name(slug)
74
+ repository_path = get_default_repository_path(slug)
75
+ logger.debug(
75
76
  f"Resolving app for slug '{slug}' → module '{module_path}', class '{class_name}'"
76
77
  )
77
78
  try:
78
- return _import_class(module_path, class_name)
79
- except ModuleNotFoundError as orig_err:
80
- logger.warning(
81
- f"Module '{module_path}' not found locally: {orig_err}. Installing..."
82
- )
83
- _install_package(slug_clean)
84
- # Retry import after installation
85
- try:
86
- return _import_class(module_path, class_name)
87
- except ModuleNotFoundError as retry_err:
88
- logger.error(
89
- f"Still cannot import '{module_path}' after installation: {retry_err}"
90
- )
91
- raise
79
+ _install_or_upgrade_package(package_name, repository_path)
80
+ module = importlib.import_module(module_path)
81
+ class_ = getattr(module, class_name)
82
+ logger.debug(f"Loaded class '{class_}' from module '{module_path}'")
83
+ return class_
84
+ except ModuleNotFoundError as e:
85
+ raise ModuleNotFoundError(
86
+ f"Package '{module_path}' not found locally. Please install it first."
87
+ ) from e
88
+ except AttributeError as e:
89
+ raise AttributeError(
90
+ f"Class '{class_name}' not found in module '{module_path}'"
91
+ ) from e
92
+ except Exception as e:
93
+ raise Exception(f"Error importing module '{module_path}': {e}") from e
92
94
 
93
95
 
94
96
  __all__ = [
@@ -1,7 +1,10 @@
1
1
  from abc import ABC, abstractmethod
2
+ from collections.abc import Callable
3
+ from typing import Any
2
4
 
3
5
  import httpx
4
- from gql import Client, gql
6
+ from gql import Client as GraphQLClient
7
+ from gql import gql
5
8
  from gql.transport.requests import RequestsHTTPTransport
6
9
  from graphql import DocumentNode
7
10
  from loguru import logger
@@ -12,41 +15,95 @@ from universal_mcp.integrations import Integration
12
15
 
13
16
  class BaseApplication(ABC):
14
17
  """
15
- BaseApplication is the base class for all applications.
18
+ Base class for all applications in the Universal MCP system.
19
+
20
+ This abstract base class defines the common interface and functionality
21
+ that all applications must implement. It provides basic initialization
22
+ and credential management capabilities.
23
+
24
+ Attributes:
25
+ name (str): The name of the application
26
+ _credentials (Optional[Dict[str, Any]]): Cached credentials for the application
16
27
  """
17
28
 
18
- def __init__(self, name: str, **kwargs):
29
+ def __init__(self, name: str, **kwargs: Any) -> None:
30
+ """
31
+ Initialize the base application.
32
+
33
+ Args:
34
+ name: The name of the application
35
+ **kwargs: Additional keyword arguments passed to the application
36
+ """
19
37
  self.name = name
20
38
  logger.debug(f"Initializing Application '{name}' with kwargs: {kwargs}")
21
39
  analytics.track_app_loaded(name) # Track app loading
22
40
 
23
41
  @abstractmethod
24
- def list_tools(self):
42
+ def list_tools(self) -> list[Callable]:
43
+ """
44
+ List all available tools for the application.
45
+
46
+ Returns:
47
+ List[Any]: A list of tools available in the application
48
+ """
25
49
  pass
26
50
 
27
51
 
28
52
  class APIApplication(BaseApplication):
29
53
  """
30
- APIApplication is an application that uses an API to interact with the world.
54
+ Application that uses HTTP APIs to interact with external services.
55
+
56
+ This class provides a base implementation for applications that communicate
57
+ with external services via HTTP APIs. It handles authentication, request
58
+ management, and response processing.
59
+
60
+ Attributes:
61
+ name (str): The name of the application
62
+ integration (Optional[Integration]): The integration configuration
63
+ default_timeout (int): Default timeout for HTTP requests in seconds
64
+ base_url (str): Base URL for API requests
31
65
  """
32
66
 
33
- def __init__(self, name: str, integration: Integration = None, **kwargs):
67
+ def __init__(
68
+ self,
69
+ name: str,
70
+ integration: Integration | None = None,
71
+ client: httpx.Client | None = None,
72
+ **kwargs: Any,
73
+ ) -> None:
74
+ """
75
+ Initialize the API application.
76
+
77
+ Args:
78
+ name: The name of the application
79
+ integration: Optional integration configuration
80
+ **kwargs: Additional keyword arguments
81
+ """
34
82
  super().__init__(name, **kwargs)
35
- self.default_timeout = 180
36
- self.integration = integration
83
+ self.default_timeout: int = 180
84
+ self.integration: Integration | None = integration
37
85
  logger.debug(
38
86
  f"Initializing APIApplication '{name}' with integration: {integration}"
39
87
  )
40
- self._client = None
41
- # base_url should be set by subclasses, e.g., self.base_url = "https://api.example.com"
88
+ self._client: httpx.Client | None = client
42
89
  self.base_url: str = "" # Initialize, but subclasses should set this
43
90
 
44
- def _get_headers(self):
91
+ def _get_headers(self) -> dict[str, str]:
92
+ """
93
+ Get the headers for API requests.
94
+
95
+ This method constructs the appropriate headers based on the available
96
+ credentials. It supports various authentication methods including
97
+ direct headers, API keys, and access tokens.
98
+
99
+ Returns:
100
+ Dict[str, str]: Headers to be used in API requests
101
+ """
45
102
  if not self.integration:
46
103
  logger.debug("No integration configured, returning empty headers")
47
104
  return {}
48
105
  credentials = self.integration.get_credentials()
49
- logger.debug(f"Got credentials for integration: {credentials.keys()}")
106
+ logger.debug("Got credentials for integration")
50
107
 
51
108
  # Check if direct headers are provided
52
109
  headers = credentials.get("headers")
@@ -79,36 +136,74 @@ class APIApplication(BaseApplication):
79
136
  return {}
80
137
 
81
138
  @property
82
- def client(self):
139
+ def client(self) -> httpx.Client:
140
+ """
141
+ Get the HTTP client instance.
142
+
143
+ This property ensures that the HTTP client is properly initialized
144
+ with the correct base URL and headers.
145
+
146
+ Returns:
147
+ httpx.Client: The initialized HTTP client
148
+ """
83
149
  if not self._client:
84
150
  headers = self._get_headers()
85
151
  if not self.base_url:
86
152
  logger.warning(f"APIApplication '{self.name}' base_url is not set.")
87
- # Fallback: Initialize client without base_url, requiring full URLs in methods
88
153
  self._client = httpx.Client(
89
154
  headers=headers, timeout=self.default_timeout
90
155
  )
91
156
  else:
92
157
  self._client = httpx.Client(
93
- base_url=self.base_url, # Pass the base_url here
158
+ base_url=self.base_url,
94
159
  headers=headers,
95
160
  timeout=self.default_timeout,
96
161
  )
97
162
  return self._client
98
163
 
99
- def _get(self, url, params=None):
164
+ def _get(self, url: str, params: dict[str, Any] | None = None) -> httpx.Response:
165
+ """
166
+ Make a GET request to the specified URL.
167
+
168
+ Args:
169
+ url: The URL to send the request to
170
+ params: Optional query parameters
171
+
172
+ Returns:
173
+ httpx.Response: The response from the server
174
+
175
+ Raises:
176
+ httpx.HTTPError: If the request fails
177
+ """
100
178
  logger.debug(f"Making GET request to {url} with params: {params}")
101
179
  response = self.client.get(url, params=params)
102
180
  response.raise_for_status()
103
181
  logger.debug(f"GET request successful with status code: {response.status_code}")
104
182
  return response
105
183
 
106
- def _post(self, url, data, params=None):
184
+ def _post(
185
+ self, url: str, data: dict[str, Any], params: dict[str, Any] | None = None
186
+ ) -> httpx.Response:
187
+ """
188
+ Make a POST request to the specified URL.
189
+
190
+ Args:
191
+ url: The URL to send the request to
192
+ data: The data to send in the request body
193
+ params: Optional query parameters
194
+
195
+ Returns:
196
+ httpx.Response: The response from the server
197
+
198
+ Raises:
199
+ httpx.HTTPError: If the request fails
200
+ """
107
201
  logger.debug(
108
202
  f"Making POST request to {url} with params: {params} and data: {data}"
109
203
  )
110
- response = self.client.post(
204
+ response = httpx.post(
111
205
  url,
206
+ headers=self._get_headers(),
112
207
  json=data,
113
208
  params=params,
114
209
  )
@@ -118,7 +213,23 @@ class APIApplication(BaseApplication):
118
213
  )
119
214
  return response
120
215
 
121
- def _put(self, url, data, params=None):
216
+ def _put(
217
+ self, url: str, data: dict[str, Any], params: dict[str, Any] | None = None
218
+ ) -> httpx.Response:
219
+ """
220
+ Make a PUT request to the specified URL.
221
+
222
+ Args:
223
+ url: The URL to send the request to
224
+ data: The data to send in the request body
225
+ params: Optional query parameters
226
+
227
+ Returns:
228
+ httpx.Response: The response from the server
229
+
230
+ Raises:
231
+ httpx.HTTPError: If the request fails
232
+ """
122
233
  logger.debug(
123
234
  f"Making PUT request to {url} with params: {params} and data: {data}"
124
235
  )
@@ -131,8 +242,20 @@ class APIApplication(BaseApplication):
131
242
  logger.debug(f"PUT request successful with status code: {response.status_code}")
132
243
  return response
133
244
 
134
- def _delete(self, url, params=None):
135
- # Now `url` can be a relative path if base_url is set in the client
245
+ def _delete(self, url: str, params: dict[str, Any] | None = None) -> httpx.Response:
246
+ """
247
+ Make a DELETE request to the specified URL.
248
+
249
+ Args:
250
+ url: The URL to send the request to
251
+ params: Optional query parameters
252
+
253
+ Returns:
254
+ httpx.Response: The response from the server
255
+
256
+ Raises:
257
+ httpx.HTTPError: If the request fails
258
+ """
136
259
  logger.debug(f"Making DELETE request to {url} with params: {params}")
137
260
  response = self.client.delete(url, params=params, timeout=self.default_timeout)
138
261
  response.raise_for_status()
@@ -141,8 +264,23 @@ class APIApplication(BaseApplication):
141
264
  )
142
265
  return response
143
266
 
144
- def _patch(self, url, data, params=None):
145
- # Now `url` can be a relative path if base_url is set in the client
267
+ def _patch(
268
+ self, url: str, data: dict[str, Any], params: dict[str, Any] | None = None
269
+ ) -> httpx.Response:
270
+ """
271
+ Make a PATCH request to the specified URL.
272
+
273
+ Args:
274
+ url: The URL to send the request to
275
+ data: The data to send in the request body
276
+ params: Optional query parameters
277
+
278
+ Returns:
279
+ httpx.Response: The response from the server
280
+
281
+ Raises:
282
+ httpx.HTTPError: If the request fails
283
+ """
146
284
  logger.debug(
147
285
  f"Making PATCH request to {url} with params: {params} and data: {data}"
148
286
  )
@@ -157,25 +295,55 @@ class APIApplication(BaseApplication):
157
295
  )
158
296
  return response
159
297
 
160
- def validate(self):
161
- pass
162
-
163
298
 
164
299
  class GraphQLApplication(BaseApplication):
165
300
  """
166
- GraphQLApplication is a collection of tools that can be used by an agent.
301
+ Application that uses GraphQL to interact with external services.
302
+
303
+ This class provides a base implementation for applications that communicate
304
+ with external services via GraphQL. It handles authentication, query execution,
305
+ and response processing.
306
+
307
+ Attributes:
308
+ name (str): The name of the application
309
+ base_url (str): Base URL for GraphQL endpoint
310
+ integration (Optional[Integration]): The integration configuration
167
311
  """
168
312
 
169
313
  def __init__(
170
- self, name: str, base_url: str, integration: Integration = None, **kwargs
171
- ):
314
+ self,
315
+ name: str,
316
+ base_url: str,
317
+ integration: Integration | None = None,
318
+ client: GraphQLClient | None = None,
319
+ **kwargs: Any,
320
+ ) -> None:
321
+ """
322
+ Initialize the GraphQL application.
323
+
324
+ Args:
325
+ name: The name of the application
326
+ base_url: The base URL for the GraphQL endpoint
327
+ integration: Optional integration configuration
328
+ **kwargs: Additional keyword arguments
329
+ """
172
330
  super().__init__(name, **kwargs)
173
331
  self.base_url = base_url
332
+ self.integration = integration
174
333
  logger.debug(f"Initializing Application '{name}' with kwargs: {kwargs}")
175
- analytics.track_app_loaded(name) # Track app loading
176
- self._client = None
334
+ self._client: GraphQLClient | None = client
335
+
336
+ def _get_headers(self) -> dict[str, str]:
337
+ """
338
+ Get the headers for GraphQL requests.
177
339
 
178
- def _get_headers(self):
340
+ This method constructs the appropriate headers based on the available
341
+ credentials. It supports various authentication methods including
342
+ direct headers, API keys, and access tokens.
343
+
344
+ Returns:
345
+ Dict[str, str]: Headers to be used in GraphQL requests
346
+ """
179
347
  if not self.integration:
180
348
  logger.debug("No integration configured, returning empty headers")
181
349
  return {}
@@ -211,23 +379,64 @@ class GraphQLApplication(BaseApplication):
211
379
  return {}
212
380
 
213
381
  @property
214
- def client(self):
382
+ def client(self) -> GraphQLClient:
383
+ """
384
+ Get the GraphQL client instance.
385
+
386
+ This property ensures that the GraphQL client is properly initialized
387
+ with the correct transport and headers.
388
+
389
+ Returns:
390
+ Client: The initialized GraphQL client
391
+ """
215
392
  if not self._client:
216
393
  headers = self._get_headers()
217
394
  transport = RequestsHTTPTransport(url=self.base_url, headers=headers)
218
- self._client = Client(transport=transport, fetch_schema_from_transport=True)
395
+ self._client = GraphQLClient(
396
+ transport=transport, fetch_schema_from_transport=True
397
+ )
219
398
  return self._client
220
399
 
221
- def mutate(self, mutation: str | DocumentNode, variables: dict = None):
400
+ def mutate(
401
+ self,
402
+ mutation: str | DocumentNode,
403
+ variables: dict[str, Any] | None = None,
404
+ ) -> dict[str, Any]:
405
+ """
406
+ Execute a GraphQL mutation.
407
+
408
+ Args:
409
+ mutation: The GraphQL mutation string or DocumentNode
410
+ variables: Optional variables for the mutation
411
+
412
+ Returns:
413
+ Dict[str, Any]: The result of the mutation
414
+
415
+ Raises:
416
+ Exception: If the mutation execution fails
417
+ """
222
418
  if isinstance(mutation, str):
223
419
  mutation = gql(mutation)
224
420
  return self.client.execute(mutation, variable_values=variables)
225
421
 
226
- def query(self, query: str | DocumentNode, variables: dict = None):
422
+ def query(
423
+ self,
424
+ query: str | DocumentNode,
425
+ variables: dict[str, Any] | None = None,
426
+ ) -> dict[str, Any]:
427
+ """
428
+ Execute a GraphQL query.
429
+
430
+ Args:
431
+ query: The GraphQL query string or DocumentNode
432
+ variables: Optional variables for the query
433
+
434
+ Returns:
435
+ Dict[str, Any]: The result of the query
436
+
437
+ Raises:
438
+ Exception: If the query execution fails
439
+ """
227
440
  if isinstance(query, str):
228
441
  query = gql(query)
229
442
  return self.client.execute(query, variable_values=variables)
230
-
231
- @abstractmethod
232
- def list_tools(self):
233
- pass