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.
- awslabs/__init__.py +16 -0
- awslabs/openapi_mcp_server/__init__.py +69 -0
- awslabs/openapi_mcp_server/api/__init__.py +18 -0
- awslabs/openapi_mcp_server/api/config.py +200 -0
- awslabs/openapi_mcp_server/auth/__init__.py +27 -0
- awslabs/openapi_mcp_server/auth/api_key_auth.py +185 -0
- awslabs/openapi_mcp_server/auth/auth_cache.py +190 -0
- awslabs/openapi_mcp_server/auth/auth_errors.py +206 -0
- awslabs/openapi_mcp_server/auth/auth_factory.py +146 -0
- awslabs/openapi_mcp_server/auth/auth_protocol.py +63 -0
- awslabs/openapi_mcp_server/auth/auth_provider.py +160 -0
- awslabs/openapi_mcp_server/auth/base_auth.py +218 -0
- awslabs/openapi_mcp_server/auth/basic_auth.py +171 -0
- awslabs/openapi_mcp_server/auth/bearer_auth.py +108 -0
- awslabs/openapi_mcp_server/auth/cognito_auth.py +538 -0
- awslabs/openapi_mcp_server/auth/register.py +100 -0
- awslabs/openapi_mcp_server/patch/__init__.py +17 -0
- awslabs/openapi_mcp_server/prompts/__init__.py +18 -0
- awslabs/openapi_mcp_server/prompts/generators/__init__.py +22 -0
- awslabs/openapi_mcp_server/prompts/generators/operation_prompts.py +642 -0
- awslabs/openapi_mcp_server/prompts/generators/workflow_prompts.py +257 -0
- awslabs/openapi_mcp_server/prompts/models.py +70 -0
- awslabs/openapi_mcp_server/prompts/prompt_manager.py +150 -0
- awslabs/openapi_mcp_server/server.py +511 -0
- awslabs/openapi_mcp_server/utils/__init__.py +18 -0
- awslabs/openapi_mcp_server/utils/cache_provider.py +249 -0
- awslabs/openapi_mcp_server/utils/config.py +35 -0
- awslabs/openapi_mcp_server/utils/error_handler.py +349 -0
- awslabs/openapi_mcp_server/utils/http_client.py +263 -0
- awslabs/openapi_mcp_server/utils/metrics_provider.py +503 -0
- awslabs/openapi_mcp_server/utils/openapi.py +217 -0
- awslabs/openapi_mcp_server/utils/openapi_validator.py +253 -0
- awslabs_openapi_mcp_server-0.1.1.dist-info/METADATA +418 -0
- awslabs_openapi_mcp_server-0.1.1.dist-info/RECORD +38 -0
- awslabs_openapi_mcp_server-0.1.1.dist-info/WHEEL +4 -0
- awslabs_openapi_mcp_server-0.1.1.dist-info/entry_points.txt +2 -0
- awslabs_openapi_mcp_server-0.1.1.dist-info/licenses/LICENSE +175 -0
- awslabs_openapi_mcp_server-0.1.1.dist-info/licenses/NOTICE +2 -0
|
@@ -0,0 +1,257 @@
|
|
|
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
|
+
"""Workflow prompt generation for OpenAPI specifications."""
|
|
15
|
+
|
|
16
|
+
from awslabs.openapi_mcp_server import logger
|
|
17
|
+
from awslabs.openapi_mcp_server.prompts.models import PromptArgument
|
|
18
|
+
from fastmcp.prompts.prompt import Prompt
|
|
19
|
+
from typing import Any, Dict, List
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def identify_workflows(paths: Dict[str, Any]) -> List[Dict[str, Any]]:
|
|
23
|
+
"""Identify common workflows from API paths."""
|
|
24
|
+
workflows = []
|
|
25
|
+
|
|
26
|
+
# Group operations by resource type
|
|
27
|
+
resource_operations = {}
|
|
28
|
+
|
|
29
|
+
for path, path_item in paths.items():
|
|
30
|
+
# Extract resource type from path
|
|
31
|
+
path_parts = path.strip('/').split('/')
|
|
32
|
+
resource_type = None
|
|
33
|
+
|
|
34
|
+
# Look for resource identifier in path
|
|
35
|
+
for part in path_parts:
|
|
36
|
+
if part and not part.startswith('{'):
|
|
37
|
+
resource_type = part
|
|
38
|
+
break
|
|
39
|
+
|
|
40
|
+
if not resource_type:
|
|
41
|
+
continue
|
|
42
|
+
|
|
43
|
+
# Initialize resource operations
|
|
44
|
+
if resource_type not in resource_operations:
|
|
45
|
+
resource_operations[resource_type] = {
|
|
46
|
+
'list': None,
|
|
47
|
+
'get': None,
|
|
48
|
+
'create': None,
|
|
49
|
+
'update': None,
|
|
50
|
+
'delete': None,
|
|
51
|
+
'search': None,
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
# Categorize operations
|
|
55
|
+
for method, operation in path_item.items():
|
|
56
|
+
if not isinstance(operation, dict):
|
|
57
|
+
continue
|
|
58
|
+
|
|
59
|
+
op_id = operation.get('operationId', '')
|
|
60
|
+
op_id_lower = op_id.lower()
|
|
61
|
+
|
|
62
|
+
# Categorize based on method and operation ID
|
|
63
|
+
if method == 'get':
|
|
64
|
+
if 'list' in op_id_lower or 'getall' in op_id_lower:
|
|
65
|
+
resource_operations[resource_type]['list'] = operation
|
|
66
|
+
elif 'search' in op_id_lower or 'find' in op_id_lower:
|
|
67
|
+
resource_operations[resource_type]['search'] = operation
|
|
68
|
+
else:
|
|
69
|
+
resource_operations[resource_type]['get'] = operation
|
|
70
|
+
elif method == 'post':
|
|
71
|
+
if 'create' in op_id_lower or 'add' in op_id_lower:
|
|
72
|
+
resource_operations[resource_type]['create'] = operation
|
|
73
|
+
elif method in ['put', 'patch']:
|
|
74
|
+
resource_operations[resource_type]['update'] = operation
|
|
75
|
+
elif method == 'delete':
|
|
76
|
+
resource_operations[resource_type]['delete'] = operation
|
|
77
|
+
|
|
78
|
+
# Identify List-Get-Update workflow
|
|
79
|
+
for resource_type, operations in resource_operations.items():
|
|
80
|
+
if operations['list'] and operations['get'] and operations['update']:
|
|
81
|
+
workflows.append(
|
|
82
|
+
{
|
|
83
|
+
'name': f'{resource_type}_list_get_update',
|
|
84
|
+
'type': 'list_get_update',
|
|
85
|
+
'resource_type': resource_type,
|
|
86
|
+
'operations': {
|
|
87
|
+
'list': operations['list'],
|
|
88
|
+
'get': operations['get'],
|
|
89
|
+
'update': operations['update'],
|
|
90
|
+
},
|
|
91
|
+
}
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
# Identify Search-Create workflow
|
|
95
|
+
if operations['search'] and operations['create']:
|
|
96
|
+
workflows.append(
|
|
97
|
+
{
|
|
98
|
+
'name': f'{resource_type}_search_create',
|
|
99
|
+
'type': 'search_create',
|
|
100
|
+
'resource_type': resource_type,
|
|
101
|
+
'operations': {'search': operations['search'], 'create': operations['create']},
|
|
102
|
+
}
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
return workflows
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def generate_workflow_documentation(workflow: Dict[str, Any]) -> str:
|
|
109
|
+
"""Generate documentation for a workflow."""
|
|
110
|
+
workflow_type = workflow['type']
|
|
111
|
+
resource_type = workflow['resource_type']
|
|
112
|
+
operations = workflow['operations']
|
|
113
|
+
|
|
114
|
+
doc_lines = []
|
|
115
|
+
|
|
116
|
+
# Add title (concise)
|
|
117
|
+
doc_lines.append(
|
|
118
|
+
f'# {resource_type.capitalize()} {workflow_type.replace("_", " ").title()} Workflow'
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
# Add workflow steps
|
|
122
|
+
doc_lines.append('\n## Steps')
|
|
123
|
+
|
|
124
|
+
if workflow_type == 'list_get_update':
|
|
125
|
+
list_op_id = operations['list'].get('operationId', 'list')
|
|
126
|
+
get_op_id = operations['get'].get('operationId', 'get')
|
|
127
|
+
update_op_id = operations['update'].get('operationId', 'update')
|
|
128
|
+
|
|
129
|
+
doc_lines.append(f'\n1. List {resource_type}s using `{list_op_id}`')
|
|
130
|
+
doc_lines.append(f'2. Get a specific {resource_type} using `{get_op_id}`')
|
|
131
|
+
doc_lines.append(f'3. Update the {resource_type} using `{update_op_id}`')
|
|
132
|
+
|
|
133
|
+
# Add code example
|
|
134
|
+
doc_lines.append('\n## Example Code')
|
|
135
|
+
doc_lines.append('```python')
|
|
136
|
+
doc_lines.append(f'# List all {resource_type}s')
|
|
137
|
+
doc_lines.append(f'{resource_type}_list = await {list_op_id}()')
|
|
138
|
+
doc_lines.append(f'\n# Get a specific {resource_type}')
|
|
139
|
+
doc_lines.append(
|
|
140
|
+
f"{resource_type}_id = {resource_type}_list[0]['id'] # Example: use first item"
|
|
141
|
+
)
|
|
142
|
+
doc_lines.append(f'{resource_type}_details = await {get_op_id}({resource_type}_id)')
|
|
143
|
+
doc_lines.append(f'\n# Update the {resource_type}')
|
|
144
|
+
doc_lines.append('update_data = {')
|
|
145
|
+
doc_lines.append(' # Include required fields here')
|
|
146
|
+
doc_lines.append('}')
|
|
147
|
+
doc_lines.append(f'updated = await {update_op_id}({resource_type}_id, update_data)')
|
|
148
|
+
doc_lines.append('```')
|
|
149
|
+
|
|
150
|
+
elif workflow_type == 'search_create':
|
|
151
|
+
search_op_id = operations['search'].get('operationId', 'search')
|
|
152
|
+
create_op_id = operations['create'].get('operationId', 'create')
|
|
153
|
+
|
|
154
|
+
doc_lines.append(f'\n1. Search for {resource_type}s using `{search_op_id}`')
|
|
155
|
+
doc_lines.append(f'2. If not found, create a new {resource_type} using `{create_op_id}`')
|
|
156
|
+
|
|
157
|
+
# Add code example
|
|
158
|
+
doc_lines.append('\n## Example Code')
|
|
159
|
+
doc_lines.append('```python')
|
|
160
|
+
doc_lines.append(f'# Search for {resource_type}s')
|
|
161
|
+
doc_lines.append('search_criteria = {')
|
|
162
|
+
doc_lines.append(' # Include search parameters here')
|
|
163
|
+
doc_lines.append('}')
|
|
164
|
+
doc_lines.append(f'search_results = await {search_op_id}(**search_criteria)')
|
|
165
|
+
doc_lines.append('\n# Create if not found')
|
|
166
|
+
doc_lines.append('if not search_results:')
|
|
167
|
+
doc_lines.append(' create_data = {')
|
|
168
|
+
doc_lines.append(' # Include required fields here')
|
|
169
|
+
doc_lines.append(' }')
|
|
170
|
+
doc_lines.append(f' new_{resource_type} = await {create_op_id}(create_data)')
|
|
171
|
+
doc_lines.append('```')
|
|
172
|
+
|
|
173
|
+
return '\n'.join(doc_lines)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def create_workflow_prompt(server: Any, workflow: Dict[str, Any]) -> bool:
|
|
177
|
+
"""Create and register a workflow prompt with the server.
|
|
178
|
+
|
|
179
|
+
Args:
|
|
180
|
+
server: MCP server instance
|
|
181
|
+
workflow: Workflow definition
|
|
182
|
+
|
|
183
|
+
Returns:
|
|
184
|
+
bool: True if prompt was registered successfully, False otherwise
|
|
185
|
+
|
|
186
|
+
"""
|
|
187
|
+
try:
|
|
188
|
+
workflow_type = workflow['type']
|
|
189
|
+
resource_type = workflow['resource_type']
|
|
190
|
+
|
|
191
|
+
# Generate documentation
|
|
192
|
+
documentation = generate_workflow_documentation(workflow)
|
|
193
|
+
|
|
194
|
+
# Get operations from workflow
|
|
195
|
+
operations = workflow['operations']
|
|
196
|
+
|
|
197
|
+
# Extract arguments from workflow operations
|
|
198
|
+
workflow_args = []
|
|
199
|
+
|
|
200
|
+
# Add resource type as an argument
|
|
201
|
+
workflow_args.append(
|
|
202
|
+
PromptArgument(
|
|
203
|
+
name='resource_type',
|
|
204
|
+
description=f'The type of resource ({resource_type})',
|
|
205
|
+
required=False,
|
|
206
|
+
)
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
# Add operation-specific arguments
|
|
210
|
+
for op_type, operation in operations.items():
|
|
211
|
+
if operation and 'parameters' in operation:
|
|
212
|
+
for param in operation.get('parameters', []):
|
|
213
|
+
if param.get('required', False):
|
|
214
|
+
param_name = param.get('name', '')
|
|
215
|
+
param_desc = param.get('description', f'Parameter for {op_type} operation')
|
|
216
|
+
|
|
217
|
+
# Check if this parameter is already added
|
|
218
|
+
if not any(arg.name == param_name for arg in workflow_args):
|
|
219
|
+
workflow_args.append(
|
|
220
|
+
PromptArgument(
|
|
221
|
+
name=param_name,
|
|
222
|
+
description=param_desc,
|
|
223
|
+
required=False, # Optional in workflow context
|
|
224
|
+
)
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
# Create a function that returns messages for this workflow
|
|
228
|
+
def workflow_fn() -> List[Dict[str, Any]]:
|
|
229
|
+
# Create messages
|
|
230
|
+
messages = [{'role': 'user', 'content': {'type': 'text', 'text': documentation}}]
|
|
231
|
+
|
|
232
|
+
return messages
|
|
233
|
+
|
|
234
|
+
# Register the function as a prompt
|
|
235
|
+
if hasattr(server, '_prompt_manager'):
|
|
236
|
+
# Create tags based on workflow metadata
|
|
237
|
+
tags = {resource_type, workflow_type}
|
|
238
|
+
|
|
239
|
+
# Create a prompt from the function
|
|
240
|
+
prompt = Prompt.from_function(
|
|
241
|
+
fn=workflow_fn,
|
|
242
|
+
name=workflow['name'],
|
|
243
|
+
description=f'Execute a {workflow_type} workflow for {resource_type}',
|
|
244
|
+
tags=tags,
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
# Add the prompt to the server
|
|
248
|
+
server._prompt_manager.add_prompt(prompt)
|
|
249
|
+
logger.debug(f'Added workflow prompt: {workflow["name"]}')
|
|
250
|
+
return True
|
|
251
|
+
else:
|
|
252
|
+
logger.warning('Server does not have _prompt_manager')
|
|
253
|
+
return False
|
|
254
|
+
|
|
255
|
+
except Exception as e:
|
|
256
|
+
logger.warning(f'Failed to create workflow prompt: {e}')
|
|
257
|
+
return False
|
|
@@ -0,0 +1,70 @@
|
|
|
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
|
+
"""Data models for MCP prompts."""
|
|
15
|
+
|
|
16
|
+
from pydantic import BaseModel, Field
|
|
17
|
+
from typing import Any, Dict, List, Literal, Optional, Union
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class PromptArgument(BaseModel):
|
|
21
|
+
"""Argument for an MCP prompt."""
|
|
22
|
+
|
|
23
|
+
name: str = Field(..., description='Unique identifier for the argument')
|
|
24
|
+
description: Optional[str] = Field(None, description='Human-readable description')
|
|
25
|
+
required: bool = Field(False, description='Whether the argument is required')
|
|
26
|
+
|
|
27
|
+
def dict(self) -> Dict[str, Any]:
|
|
28
|
+
"""Convert to dictionary representation."""
|
|
29
|
+
result = {'name': self.name, 'required': self.required}
|
|
30
|
+
if self.description:
|
|
31
|
+
result['description'] = self.description
|
|
32
|
+
return result
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class ResourceContent(BaseModel):
|
|
36
|
+
"""Content for a resource message."""
|
|
37
|
+
|
|
38
|
+
uri: str = Field(..., description='URI of the resource')
|
|
39
|
+
mimeType: str = Field('application/json', description='MIME type of the resource')
|
|
40
|
+
text: Optional[str] = Field(None, description='Text content of the resource')
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class TextMessage(BaseModel):
|
|
44
|
+
"""Text message content."""
|
|
45
|
+
|
|
46
|
+
type: Literal['text'] = Field('text', description='Type of message content')
|
|
47
|
+
text: str = Field(..., description='Text content')
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class ResourceMessage(BaseModel):
|
|
51
|
+
"""Resource message content."""
|
|
52
|
+
|
|
53
|
+
type: Literal['resource'] = Field('resource', description='Type of message content')
|
|
54
|
+
resource: ResourceContent = Field(..., description='Resource content')
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class PromptMessage(BaseModel):
|
|
58
|
+
"""Message in an MCP prompt."""
|
|
59
|
+
|
|
60
|
+
role: str = Field(..., description='Role of the message sender')
|
|
61
|
+
content: Union[TextMessage, ResourceMessage] = Field(..., description='Content of the message')
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class MCPPrompt(BaseModel):
|
|
65
|
+
"""MCP-compliant prompt definition."""
|
|
66
|
+
|
|
67
|
+
name: str = Field(..., description='Unique identifier for the prompt')
|
|
68
|
+
description: Optional[str] = Field(None, description='Human-readable description')
|
|
69
|
+
arguments: Optional[List[PromptArgument]] = Field(None, description='Arguments for the prompt')
|
|
70
|
+
messages: Optional[List[PromptMessage]] = Field(None, description='Messages in the prompt')
|
|
@@ -0,0 +1,150 @@
|
|
|
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
|
+
"""MCP prompt manager for OpenAPI specifications."""
|
|
15
|
+
|
|
16
|
+
from awslabs.openapi_mcp_server import logger
|
|
17
|
+
from awslabs.openapi_mcp_server.prompts.generators.operation_prompts import create_operation_prompt
|
|
18
|
+
from awslabs.openapi_mcp_server.prompts.generators.workflow_prompts import (
|
|
19
|
+
create_workflow_prompt,
|
|
20
|
+
identify_workflows,
|
|
21
|
+
)
|
|
22
|
+
from typing import Any, Dict
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class MCPPromptManager:
|
|
26
|
+
"""Manager for MCP-compliant prompts."""
|
|
27
|
+
|
|
28
|
+
def __init__(self):
|
|
29
|
+
"""Initialize the prompt manager."""
|
|
30
|
+
self.prompts = []
|
|
31
|
+
self.resource_handlers = {}
|
|
32
|
+
|
|
33
|
+
async def generate_prompts(
|
|
34
|
+
self, server: Any, api_name: str, openapi_spec: Dict[str, Any]
|
|
35
|
+
) -> Dict[str, bool]:
|
|
36
|
+
"""Generate MCP-compliant prompts from an OpenAPI specification.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
server: MCP server instance
|
|
40
|
+
api_name: Name of the API
|
|
41
|
+
openapi_spec: OpenAPI specification
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
Status of prompt generation
|
|
45
|
+
|
|
46
|
+
"""
|
|
47
|
+
logger.info(f'Generating MCP prompts for {api_name}')
|
|
48
|
+
|
|
49
|
+
# Extract API information
|
|
50
|
+
paths = openapi_spec.get('paths', {})
|
|
51
|
+
|
|
52
|
+
# Track generation status
|
|
53
|
+
status = {'operation_prompts_generated': False, 'workflow_prompts_generated': False}
|
|
54
|
+
|
|
55
|
+
# Generate operation prompts
|
|
56
|
+
operation_count = 0
|
|
57
|
+
|
|
58
|
+
for path, path_item in paths.items():
|
|
59
|
+
for method, operation in path_item.items():
|
|
60
|
+
if method not in ['get', 'post', 'put', 'patch', 'delete']:
|
|
61
|
+
continue
|
|
62
|
+
|
|
63
|
+
operation_id = operation.get('operationId')
|
|
64
|
+
if not operation_id:
|
|
65
|
+
continue
|
|
66
|
+
|
|
67
|
+
# Create and register operation prompt
|
|
68
|
+
success = create_operation_prompt(
|
|
69
|
+
server=server,
|
|
70
|
+
api_name=api_name,
|
|
71
|
+
operation_id=operation_id,
|
|
72
|
+
method=method,
|
|
73
|
+
path=path,
|
|
74
|
+
summary=operation.get('summary', ''),
|
|
75
|
+
description=operation.get('description', ''),
|
|
76
|
+
parameters=operation.get('parameters', []),
|
|
77
|
+
request_body=operation.get('requestBody'),
|
|
78
|
+
responses=operation.get('responses', {}),
|
|
79
|
+
security=operation.get('security', []),
|
|
80
|
+
paths=paths,
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
if success:
|
|
84
|
+
operation_count += 1
|
|
85
|
+
|
|
86
|
+
status['operation_prompts_generated'] = operation_count > 0
|
|
87
|
+
logger.info(f'Generated {operation_count} operation prompts')
|
|
88
|
+
|
|
89
|
+
# Generate workflow prompts
|
|
90
|
+
workflows = identify_workflows(paths)
|
|
91
|
+
workflow_count = 0
|
|
92
|
+
|
|
93
|
+
for workflow in workflows:
|
|
94
|
+
# Create and register workflow prompt
|
|
95
|
+
success = create_workflow_prompt(server, workflow)
|
|
96
|
+
if success:
|
|
97
|
+
workflow_count += 1
|
|
98
|
+
|
|
99
|
+
status['workflow_prompts_generated'] = workflow_count > 0
|
|
100
|
+
logger.info(f'Generated {workflow_count} workflow prompts')
|
|
101
|
+
|
|
102
|
+
return status
|
|
103
|
+
|
|
104
|
+
def register_api_resource_handler(self, server: Any, api_name: str, client: Any) -> None:
|
|
105
|
+
"""Register a handler for API resources.
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
server: MCP server instance
|
|
109
|
+
api_name: Name of the API
|
|
110
|
+
client: HTTP client for making API requests
|
|
111
|
+
|
|
112
|
+
"""
|
|
113
|
+
|
|
114
|
+
async def api_resource_handler(uri: str, params: Dict[str, Any]) -> Dict[str, Any]:
|
|
115
|
+
"""Handle API resource requests."""
|
|
116
|
+
# Extract path from URI
|
|
117
|
+
# Format: api://api_name/path/to/resource
|
|
118
|
+
path = uri.split(f'api://{api_name}')[1]
|
|
119
|
+
|
|
120
|
+
# Substitute path parameters
|
|
121
|
+
for param_name, param_value in params.items():
|
|
122
|
+
path = path.replace(f'{{{param_name}}}', str(param_value))
|
|
123
|
+
|
|
124
|
+
try:
|
|
125
|
+
# Make the API request using the authenticated client
|
|
126
|
+
response = await client.get(path)
|
|
127
|
+
response.raise_for_status()
|
|
128
|
+
|
|
129
|
+
# Return the response
|
|
130
|
+
return {
|
|
131
|
+
'text': response.text,
|
|
132
|
+
'mimeType': response.headers.get('Content-Type', 'application/json'),
|
|
133
|
+
}
|
|
134
|
+
except Exception as e:
|
|
135
|
+
logger.error(f'Error accessing API resource {uri}: {e}')
|
|
136
|
+
return {'text': f'Error: {str(e)}', 'mimeType': 'text/plain'}
|
|
137
|
+
|
|
138
|
+
# Store the resource handler for later use
|
|
139
|
+
resource_uri = f'api://{api_name}/'
|
|
140
|
+
self.resource_handlers[resource_uri] = api_resource_handler
|
|
141
|
+
|
|
142
|
+
# Try to register the resource handler if the server supports it
|
|
143
|
+
try:
|
|
144
|
+
if hasattr(server, 'register_resource_handler'):
|
|
145
|
+
server.register_resource_handler(resource_uri, api_resource_handler)
|
|
146
|
+
logger.debug(f'Registered resource handler for {resource_uri}')
|
|
147
|
+
else:
|
|
148
|
+
logger.debug(f'Stored resource handler locally for {resource_uri}')
|
|
149
|
+
except Exception as e:
|
|
150
|
+
logger.warning(f'Failed to register resource handler: {e}')
|