awslabs.openapi-mcp-server 0.1.1__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 (38) hide show
  1. awslabs/__init__.py +16 -0
  2. awslabs/openapi_mcp_server/__init__.py +69 -0
  3. awslabs/openapi_mcp_server/api/__init__.py +18 -0
  4. awslabs/openapi_mcp_server/api/config.py +200 -0
  5. awslabs/openapi_mcp_server/auth/__init__.py +27 -0
  6. awslabs/openapi_mcp_server/auth/api_key_auth.py +185 -0
  7. awslabs/openapi_mcp_server/auth/auth_cache.py +190 -0
  8. awslabs/openapi_mcp_server/auth/auth_errors.py +206 -0
  9. awslabs/openapi_mcp_server/auth/auth_factory.py +146 -0
  10. awslabs/openapi_mcp_server/auth/auth_protocol.py +63 -0
  11. awslabs/openapi_mcp_server/auth/auth_provider.py +160 -0
  12. awslabs/openapi_mcp_server/auth/base_auth.py +218 -0
  13. awslabs/openapi_mcp_server/auth/basic_auth.py +171 -0
  14. awslabs/openapi_mcp_server/auth/bearer_auth.py +108 -0
  15. awslabs/openapi_mcp_server/auth/cognito_auth.py +538 -0
  16. awslabs/openapi_mcp_server/auth/register.py +100 -0
  17. awslabs/openapi_mcp_server/patch/__init__.py +17 -0
  18. awslabs/openapi_mcp_server/prompts/__init__.py +18 -0
  19. awslabs/openapi_mcp_server/prompts/generators/__init__.py +22 -0
  20. awslabs/openapi_mcp_server/prompts/generators/operation_prompts.py +642 -0
  21. awslabs/openapi_mcp_server/prompts/generators/workflow_prompts.py +257 -0
  22. awslabs/openapi_mcp_server/prompts/models.py +70 -0
  23. awslabs/openapi_mcp_server/prompts/prompt_manager.py +150 -0
  24. awslabs/openapi_mcp_server/server.py +511 -0
  25. awslabs/openapi_mcp_server/utils/__init__.py +18 -0
  26. awslabs/openapi_mcp_server/utils/cache_provider.py +249 -0
  27. awslabs/openapi_mcp_server/utils/config.py +35 -0
  28. awslabs/openapi_mcp_server/utils/error_handler.py +349 -0
  29. awslabs/openapi_mcp_server/utils/http_client.py +263 -0
  30. awslabs/openapi_mcp_server/utils/metrics_provider.py +503 -0
  31. awslabs/openapi_mcp_server/utils/openapi.py +217 -0
  32. awslabs/openapi_mcp_server/utils/openapi_validator.py +253 -0
  33. awslabs_openapi_mcp_server-0.1.1.dist-info/METADATA +418 -0
  34. awslabs_openapi_mcp_server-0.1.1.dist-info/RECORD +38 -0
  35. awslabs_openapi_mcp_server-0.1.1.dist-info/WHEEL +4 -0
  36. awslabs_openapi_mcp_server-0.1.1.dist-info/entry_points.txt +2 -0
  37. awslabs_openapi_mcp_server-0.1.1.dist-info/licenses/LICENSE +175 -0
  38. awslabs_openapi_mcp_server-0.1.1.dist-info/licenses/NOTICE +2 -0
@@ -0,0 +1,511 @@
1
+ # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ """awslabs openapi MCP Server implementation."""
15
+
16
+ import argparse
17
+ import asyncio
18
+ import httpx
19
+ import re
20
+ import signal
21
+ import sys
22
+
23
+ # Import from our modules - use direct imports from sub-modules for better patching in tests
24
+ from awslabs.openapi_mcp_server import logger
25
+ from awslabs.openapi_mcp_server.api.config import Config, load_config
26
+ from awslabs.openapi_mcp_server.prompts import MCPPromptManager
27
+ from awslabs.openapi_mcp_server.utils.http_client import HttpClientFactory, make_request_with_retry
28
+ from awslabs.openapi_mcp_server.utils.metrics_provider import metrics
29
+ from awslabs.openapi_mcp_server.utils.openapi import load_openapi_spec
30
+ from awslabs.openapi_mcp_server.utils.openapi_validator import validate_openapi_spec
31
+ from fastmcp import FastMCP
32
+ from fastmcp.server.openapi import FastMCPOpenAPI, RouteMap, RouteType
33
+ from typing import Any, Dict
34
+
35
+
36
+ def create_mcp_server(config: Config) -> FastMCP:
37
+ """Create and configure the FastMCP server.
38
+
39
+ Args:
40
+ config: Server configuration
41
+
42
+ Returns:
43
+ FastMCP: The configured FastMCP server
44
+
45
+ """
46
+ # Log environment information
47
+ logger.debug('Environment information:')
48
+ logger.debug(f'Python version: {sys.version}')
49
+ try:
50
+ logger.debug(f'HTTPX version: {httpx.__version__}')
51
+ except AttributeError:
52
+ logger.debug('HTTPX version: unknown')
53
+
54
+ logger.info('Creating FastMCP server')
55
+
56
+ # Create the FastMCP server
57
+ server = FastMCP(
58
+ 'awslabs.openapi-mcp-server',
59
+ instructions='This server acts as a bridge between OpenAPI specifications and LLMs, allowing models to have a better understanding of available API capabilities without requiring manual tool definitions.',
60
+ dependencies=[
61
+ 'pydantic',
62
+ 'loguru',
63
+ 'httpx',
64
+ ],
65
+ )
66
+
67
+ try:
68
+ # Load OpenAPI spec
69
+ if not config.api_spec_url and not config.api_spec_path:
70
+ logger.error('No API spec URL or path provided')
71
+ raise ValueError('Either api_spec_url or api_spec_path must be provided')
72
+
73
+ logger.debug(
74
+ f'Loading OpenAPI spec from URL: {config.api_spec_url} or path: {config.api_spec_path}'
75
+ )
76
+ openapi_spec = load_openapi_spec(url=config.api_spec_url, path=config.api_spec_path)
77
+
78
+ # Validate the OpenAPI spec
79
+ if not validate_openapi_spec(openapi_spec):
80
+ logger.warning('OpenAPI specification validation failed, but continuing anyway')
81
+
82
+ # Create a client for the API
83
+ if not config.api_base_url:
84
+ logger.error('No API base URL provided')
85
+ raise ValueError('API base URL must be provided')
86
+
87
+ # Configure authentication using the auth factory
88
+ from awslabs.openapi_mcp_server.auth import get_auth_provider, is_auth_type_available
89
+
90
+ # Import and register the specific auth provider
91
+ from awslabs.openapi_mcp_server.auth.register import register_provider_by_type
92
+
93
+ # Register only the provider we need
94
+ if config.auth_type and config.auth_type != 'none':
95
+ logger.debug(f'Registering authentication provider for type: {config.auth_type}')
96
+ register_provider_by_type(config.auth_type)
97
+ else:
98
+ logger.debug('No authentication type specified, using none')
99
+
100
+ # Check if the requested auth type is available
101
+ if config.auth_type != 'none' and not is_auth_type_available(config.auth_type):
102
+ logger.warning(
103
+ f'Authentication type {config.auth_type} is not available. Falling back to none.'
104
+ )
105
+ config.auth_type = 'none'
106
+
107
+ # Get the auth provider
108
+ auth_provider = get_auth_provider(config)
109
+
110
+ # Get authentication components
111
+ auth_headers = auth_provider.get_auth_headers()
112
+ # Get auth params (not used directly but may be needed in the future)
113
+ _ = auth_provider.get_auth_params()
114
+ auth_cookies = auth_provider.get_auth_cookies()
115
+ httpx_auth = auth_provider.get_httpx_auth()
116
+
117
+ # Helper function to handle authentication configuration errors
118
+ def handle_auth_error(auth_type, error_message):
119
+ """Handle authentication configuration errors.
120
+
121
+ Args:
122
+ auth_type: The authentication type
123
+ error_message: The error message to log
124
+
125
+ """
126
+ logger.error(
127
+ f'Authentication provider {auth_provider.provider_name} is not properly configured'
128
+ )
129
+ logger.error(error_message)
130
+ logger.error('Server shutting down due to authentication configuration error.')
131
+ sys.exit(1)
132
+
133
+ # Check if the provider is properly configured
134
+ if not auth_provider.is_configured() and config.auth_type != 'none':
135
+ if config.auth_type == 'bearer':
136
+ handle_auth_error(
137
+ 'bearer',
138
+ 'Bearer authentication requires a valid token. Please provide a token using --auth-token command line argument or AUTH_TOKEN environment variable.',
139
+ )
140
+ elif config.auth_type == 'basic':
141
+ handle_auth_error(
142
+ 'basic',
143
+ 'Basic authentication requires both username and password. Please provide them using --auth-username and --auth-password command line arguments or AUTH_USERNAME and AUTH_PASSWORD environment variables.',
144
+ )
145
+ elif config.auth_type == 'api_key':
146
+ handle_auth_error(
147
+ 'api_key',
148
+ 'API Key authentication requires a valid API key. Please provide it using --auth-api-key command line argument or AUTH_API_KEY environment variable.',
149
+ )
150
+ elif config.auth_type == 'cognito':
151
+ handle_auth_error(
152
+ 'cognito',
153
+ 'Cognito authentication requires client ID, username, and password. Please provide them using --auth-cognito-client-id, --auth-cognito-username, and --auth-cognito-password command line arguments or corresponding environment variables.',
154
+ )
155
+ else:
156
+ logger.warning(
157
+ 'Continuing with incomplete authentication configuration. This may cause API requests to fail.'
158
+ )
159
+
160
+ # Log authentication info
161
+ if config.auth_type != 'none':
162
+ logger.info(f'Using {auth_provider.provider_name} authentication')
163
+
164
+ # Create the HTTP client with authentication and connection pooling
165
+ client = HttpClientFactory.create_client(
166
+ base_url=config.api_base_url,
167
+ headers=auth_headers,
168
+ auth=httpx_auth,
169
+ cookies=auth_cookies,
170
+ )
171
+ logger.info(f'Created HTTP client for API base URL: {config.api_base_url}')
172
+
173
+ custom_mappings = []
174
+
175
+ # Identify GET operations with query parameters in the OpenAPI spec
176
+ for path, path_item in openapi_spec.get('paths', {}).items():
177
+ for method, operation in path_item.items():
178
+ if method.lower() == 'get':
179
+ parameters = operation.get('parameters', [])
180
+ query_params = [p for p in parameters if p.get('in') == 'query']
181
+ if query_params:
182
+ # Create a specific mapping for this path to ensure it's treated as a TOOL
183
+ custom_mappings.append(
184
+ RouteMap(
185
+ methods=['GET'],
186
+ pattern=f'^{re.escape(path)}$',
187
+ route_type=RouteType.TOOL,
188
+ )
189
+ )
190
+
191
+ # Create the FastMCP server with custom route mappings
192
+ logger.info('Creating FastMCP server with OpenAPI specification')
193
+ # Update API name from OpenAPI spec title if available
194
+ if openapi_spec and isinstance(openapi_spec, dict) and 'info' in openapi_spec:
195
+ if 'title' in openapi_spec['info'] and openapi_spec['info']['title']:
196
+ config.api_name = openapi_spec['info']['title']
197
+ logger.info(f'Updated API name from OpenAPI spec title: {config.api_name}')
198
+ server = FastMCPOpenAPI(
199
+ openapi_spec=openapi_spec,
200
+ client=client,
201
+ name=config.api_name or 'OpenAPI MCP Server',
202
+ route_maps=custom_mappings, # Custom mappings take precedence over default mappings
203
+ )
204
+
205
+ # Log route information at debug level
206
+ if logger.level == 'DEBUG':
207
+ # Use getattr with default value to safely access attributes
208
+ openapi_router = getattr(server, '_openapi_router', None)
209
+ if openapi_router is not None:
210
+ routes = getattr(openapi_router, '_routes', [])
211
+ logger.debug(f'Server has {len(routes)} routes')
212
+
213
+ # Log details of each route
214
+ for i, route in enumerate(routes):
215
+ path = getattr(route, 'path', 'unknown')
216
+ method = getattr(route, 'method', 'unknown')
217
+ route_type = getattr(route, 'route_type', 'unknown')
218
+ logger.debug(f'Route {i}: {method} {path} - Type: {route_type}')
219
+
220
+ logger.info(f'Successfully configured API: {config.api_name}')
221
+
222
+ # Generate MCP-compliant prompts
223
+ try:
224
+ logger.info(f'Generating MCP prompts for API: {config.api_name}')
225
+ # Create prompt manager
226
+ prompt_manager = MCPPromptManager()
227
+
228
+ # Generate prompts
229
+ asyncio.run(prompt_manager.generate_prompts(server, config.api_name, openapi_spec))
230
+
231
+ # Register resource handler
232
+ prompt_manager.register_api_resource_handler(server, config.api_name, client)
233
+
234
+ except Exception as e:
235
+ logger.warning(f'Failed to generate operation-specific prompts: {e}')
236
+ import traceback
237
+
238
+ logger.warning(f'Traceback: {traceback.format_exc()}')
239
+
240
+ # Register health check tool
241
+ async def health_check() -> Dict[str, Any]:
242
+ """Check the health of the server and API.
243
+
244
+ Returns:
245
+ Dict[str, Any]: Health check results
246
+
247
+ """
248
+ api_health = True
249
+ api_message = 'API is reachable'
250
+
251
+ # Try to make a simple request to the API
252
+ try:
253
+ # Use the retry-enabled request function
254
+ response = await make_request_with_retry(
255
+ client=client, method='GET', url='/', max_retries=2, retry_delay=0.5
256
+ )
257
+ status_code = response.status_code
258
+ if status_code >= 400:
259
+ api_health = False
260
+ api_message = f'API returned status code {status_code}'
261
+ except Exception as e:
262
+ api_health = False
263
+ api_message = f'Error connecting to API: {str(e)}'
264
+
265
+ # Get metrics summary
266
+ summary = metrics.get_summary()
267
+
268
+ return {
269
+ 'server': {
270
+ 'status': 'healthy',
271
+ 'version': config.version,
272
+ 'uptime': 'N/A', # Would require tracking start time
273
+ },
274
+ 'api': {
275
+ 'name': config.api_name,
276
+ 'status': 'healthy' if api_health else 'unhealthy',
277
+ 'message': api_message,
278
+ 'base_url': config.api_base_url,
279
+ },
280
+ 'metrics': summary,
281
+ }
282
+
283
+ except Exception as e:
284
+ logger.error(f'Error setting up API: {e}')
285
+ logger.error('Server shutting down due to API setup error.')
286
+ import traceback
287
+
288
+ logger.error(f'Traceback: {traceback.format_exc()}')
289
+ sys.exit(1)
290
+
291
+ # Move the logging here, after the server is fully initialized
292
+ # Get the actual tools from the server's internal structure
293
+ tool_count = 0
294
+ tool_names = []
295
+
296
+ # Try different ways to access tools based on FastMCP implementation
297
+ if hasattr(server, 'list_tools'):
298
+ try:
299
+ # Use asyncio to run the async method in a synchronous context
300
+ tools = asyncio.run(server.list_tools()) # type: ignore
301
+ tool_count = len(tools)
302
+ tool_names = [tool.get('name') for tool in tools]
303
+
304
+ # DEBUG - Log detailed information about each tool
305
+ logger.debug(f'Found {tool_count} tools via list_tools()')
306
+ for i, tool in enumerate(tools):
307
+ tool_name = tool.get('name', 'unknown')
308
+ tool_desc = tool.get('description', 'no description')
309
+ logger.debug(f'Tool {i}: {tool_name} - {tool_desc}')
310
+
311
+ # Check if the tool has a schema
312
+ if 'parameters' in tool:
313
+ params = tool.get('parameters', {})
314
+ if 'properties' in params:
315
+ properties = params.get('properties', {})
316
+ logger.debug(f' Parameters: {list(properties.keys())}')
317
+ except Exception as e:
318
+ logger.warning(f'Failed to list tools: {e}')
319
+ import traceback
320
+
321
+ logger.debug(f'Tool listing error traceback: {traceback.format_exc()}')
322
+
323
+ # DEBUG - Try to access tools directly if available
324
+ tools = getattr(server, '_tools', {})
325
+ if tools:
326
+ logger.debug(f'Server has {len(tools)} tools in _tools attribute')
327
+ for tool_name, tool in tools.items():
328
+ logger.debug(f'Direct tool: {tool_name}')
329
+
330
+ # Log the prompt count
331
+ prompt_count = (
332
+ len(server._prompt_manager._prompts)
333
+ if hasattr(server, '_prompt_manager') and hasattr(server._prompt_manager, '_prompts')
334
+ else 0
335
+ )
336
+
337
+ # Log details of registered components
338
+ if tool_count > 0:
339
+ logger.info(f'Registered tools: {tool_names}')
340
+
341
+ if (
342
+ prompt_count > 0
343
+ and hasattr(server, '_prompt_manager')
344
+ and hasattr(server._prompt_manager, '_prompts')
345
+ ):
346
+ prompt_names = list(server._prompt_manager._prompts.keys())
347
+ logger.info(f'Registered prompts: {prompt_names}')
348
+
349
+ return server
350
+
351
+
352
+ def setup_signal_handlers():
353
+ """Set up signal handlers for graceful shutdown."""
354
+ # Store original SIGINT handler
355
+ original_sigint = signal.getsignal(signal.SIGINT)
356
+
357
+ def signal_handler(sig, frame):
358
+ """Handle signals by logging metrics then chain to original handler."""
359
+ logger.debug(f'Received signal {sig}, shutting down gracefully...')
360
+
361
+ # Log final metrics
362
+ summary = metrics.get_summary()
363
+ logger.info(f'Final metrics: {summary}')
364
+
365
+ # if sig is signal.SIGINT handle gracefully
366
+ if sig == signal.SIGINT:
367
+ logger.info('Process Interrupted, Shutting down gracefully...')
368
+ sys.exit(0)
369
+
370
+ # For SIGINT, chain to the original handler
371
+ if (
372
+ sig == signal.SIGINT
373
+ and original_sigint != signal.SIG_DFL
374
+ and original_sigint != signal.SIG_IGN
375
+ ):
376
+ # Call the original handler
377
+ if callable(original_sigint):
378
+ original_sigint(sig, frame)
379
+
380
+ # For other signals or if no original handler, just return
381
+ # This lets the default handling take over
382
+
383
+ # Register for SIGTERM only
384
+ signal.signal(signal.SIGTERM, signal_handler)
385
+
386
+ # For SIGINT, we'll use a special handler that logs then chains to original
387
+ signal.signal(signal.SIGINT, signal_handler)
388
+
389
+
390
+ def main():
391
+ """Run the MCP server with CLI argument support."""
392
+ parser = argparse.ArgumentParser(
393
+ description='This project is a server that dynamically creates Model Context Protocol (MCP) tools and resources from OpenAPI specifications. It allows Large Language Models (LLMs) to interact with APIs through the Model Context Protocol.'
394
+ )
395
+ # Server configuration
396
+ parser.add_argument('--port', type=int, help='Port to run the server on')
397
+ parser.add_argument(
398
+ '--log-level',
399
+ choices=['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'],
400
+ default='INFO',
401
+ help='Set logging level',
402
+ )
403
+ parser.add_argument('--debug', action='store_true', help='Enable debug mode')
404
+
405
+ # API configuration
406
+ parser.add_argument('--api-name', help='Name of the API (default: petstore)')
407
+ parser.add_argument('--api-url', help='Base URL of the API')
408
+ parser.add_argument('--spec-url', help='URL of the OpenAPI specification')
409
+ parser.add_argument('--spec-path', help='Local path to the OpenAPI specification file')
410
+
411
+ # Authentication configuration
412
+ parser.add_argument(
413
+ '--auth-type',
414
+ choices=['none', 'basic', 'bearer', 'api_key', 'cognito'],
415
+ help='Authentication type to use (default: none)',
416
+ )
417
+
418
+ # Basic auth
419
+ parser.add_argument('--auth-username', help='Username for basic authentication')
420
+ parser.add_argument('--auth-password', help='Password for basic authentication')
421
+
422
+ # Bearer auth
423
+ parser.add_argument('--auth-token', help='Token for bearer authentication')
424
+
425
+ # API key auth
426
+ parser.add_argument('--auth-api-key', help='API key for API key authentication')
427
+ parser.add_argument('--auth-api-key-name', help='Name of the API key (default: api_key)')
428
+ parser.add_argument(
429
+ '--auth-api-key-in',
430
+ choices=['header', 'query', 'cookie'],
431
+ help='Where to place the API key (default: header)',
432
+ )
433
+
434
+ # Cognito auth
435
+ parser.add_argument('--auth-cognito-client-id', help='Client ID for Cognito authentication')
436
+ parser.add_argument('--auth-cognito-username', help='Username for Cognito authentication')
437
+ parser.add_argument('--auth-cognito-password', help='Password for Cognito authentication')
438
+ parser.add_argument(
439
+ '--auth-cognito-user-pool-id', help='User Pool ID for Cognito authentication'
440
+ )
441
+ parser.add_argument('--auth-cognito-region', help='AWS region for Cognito (default: us-east-1)')
442
+
443
+ args = parser.parse_args()
444
+
445
+ # Set up logging with loguru at specified level
446
+ logger.remove()
447
+ logger.add(lambda msg: print(msg, end=''), level=args.log_level)
448
+ logger.info(f'Starting server with logging level: {args.log_level}')
449
+
450
+ # Load configuration
451
+ logger.debug('Loading configuration from arguments and environment')
452
+ config = load_config(args)
453
+ logger.debug(f'Configuration loaded: api_name={config.api_name}, transport={config.transport}')
454
+
455
+ # Create and run the MCP server
456
+ logger.info('Creating MCP server')
457
+ mcp_server = create_mcp_server(config)
458
+
459
+ # Set up signal handlers
460
+ setup_signal_handlers()
461
+
462
+ try:
463
+ # Get counts of prompts, tools, resources, and resource templates
464
+ async def get_all_counts(server):
465
+ prompts = await server.get_prompts()
466
+ tools = await server.get_tools()
467
+ resources = await server.get_resources()
468
+
469
+ # Get resource templates if available
470
+ resource_templates = []
471
+ if hasattr(server, 'get_resource_templates'):
472
+ try:
473
+ resource_templates = await server.get_resource_templates()
474
+ except AttributeError as e:
475
+ # This is expected if the method exists but is not implemented
476
+ logger.debug(f'get_resource_templates exists but not implemented: {e}')
477
+ except Exception as e:
478
+ # Log other unexpected errors
479
+ logger.warning(f'Error retrieving resource templates: {e}')
480
+
481
+ return len(prompts), len(tools), len(resources), len(resource_templates)
482
+
483
+ prompt_count, tool_count, resource_count, resource_template_count = asyncio.run(
484
+ get_all_counts(mcp_server)
485
+ )
486
+
487
+ # Log all counts in a single statement
488
+ logger.info(
489
+ f'Server components: {prompt_count} prompts, {tool_count} tools, {resource_count} resources, {resource_template_count} resource templates'
490
+ )
491
+
492
+ # Check if we have at least one tool or resource
493
+ if tool_count == 0 and resource_count == 0:
494
+ logger.warning(
495
+ 'No tools or resources were registered. This might indicate an issue with the API specification or authentication.'
496
+ )
497
+ except Exception as e:
498
+ logger.error(f'Error counting tools and resources: {e}')
499
+ logger.error('Server shutting down due to error in tool/resource registration.')
500
+ import traceback
501
+
502
+ logger.error(f'Traceback: {traceback.format_exc()}')
503
+ sys.exit(1)
504
+
505
+ # Run server with stdio transport only
506
+ logger.info('Running server with stdio transport')
507
+ mcp_server.run()
508
+
509
+
510
+ if __name__ == '__main__':
511
+ main()
@@ -0,0 +1,18 @@
1
+ # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ """Utilities for the OpenAPI MCP Server."""
15
+
16
+ from awslabs.openapi_mcp_server import logger
17
+
18
+ __all__ = ['logger']