swiftmcp 0.0.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.
- swiftmcp/__init__.py +0 -0
- swiftmcp/cli/main.py +39 -0
- swiftmcp/cli/proxy_serve.py +33 -0
- swiftmcp/core/composition/mcp_composition.py +54 -0
- swiftmcp/core/openapi/openapi2mcp.py +47 -0
- swiftmcp/core/proxy/mcp_proxy.py +25 -0
- swiftmcp/core/tools/http_tool/api_tool.py +271 -0
- swiftmcp/core/tools/http_tool/parser.py +537 -0
- swiftmcp/core/tools/http_tool/ssrf_proxy.py +167 -0
- swiftmcp/core/tools/http_tool/tool_bundle.py +30 -0
- swiftmcp/core/tools/http_tool/tool_entities.py +122 -0
- swiftmcp/core/tools/mcp_tool/mcp_tool.py +52 -0
- swiftmcp/utils/file_db.py +75 -0
- swiftmcp-0.0.1.dist-info/METADATA +18 -0
- swiftmcp-0.0.1.dist-info/RECORD +20 -0
- swiftmcp-0.0.1.dist-info/WHEEL +5 -0
- swiftmcp-0.0.1.dist-info/entry_points.txt +2 -0
- swiftmcp-0.0.1.dist-info/licenses/LICENSE +674 -0
- swiftmcp-0.0.1.dist-info/top_level.txt +1 -0
- swiftmcp-0.0.1.dist-info/zip-safe +1 -0
|
@@ -0,0 +1,537 @@
|
|
|
1
|
+
import re
|
|
2
|
+
import uuid
|
|
3
|
+
from typing import Any, Dict, List, Tuple, Optional
|
|
4
|
+
from json import dumps as json_dumps
|
|
5
|
+
from json import loads as json_loads
|
|
6
|
+
from json.decoder import JSONDecodeError
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
from requests import get
|
|
10
|
+
from yaml import YAMLError, safe_load
|
|
11
|
+
|
|
12
|
+
from .tool_entities import MultilingualText, ToolParameter
|
|
13
|
+
from .tool_bundle import ApiToolBundle
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def parser_tool(json_data: dict) -> None:
|
|
17
|
+
"""Simple tool parser example"""
|
|
18
|
+
base_url = json_data["servers"][0]["url"]
|
|
19
|
+
route_path = list(json_data["paths"].keys())[0]
|
|
20
|
+
url = base_url + route_path
|
|
21
|
+
tool_description = json_data["paths"][route_path]["post"]["description"]
|
|
22
|
+
# Placeholder for future implementation
|
|
23
|
+
pass
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class ApiBasedToolSchemaParser:
|
|
27
|
+
"""Parser for API-based tool schemas"""
|
|
28
|
+
|
|
29
|
+
@staticmethod
|
|
30
|
+
def parse_openapi_to_tool_bundle(openapi: dict, extra_info: Optional[dict] = None,
|
|
31
|
+
warning: Optional[dict] = None) -> List[ApiToolBundle]:
|
|
32
|
+
"""
|
|
33
|
+
Parse OpenAPI specification to tool bundles.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
openapi: OpenAPI specification dictionary
|
|
37
|
+
extra_info: Additional information to include
|
|
38
|
+
warning: Warning messages dictionary
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
List of ApiToolBundle objects
|
|
42
|
+
"""
|
|
43
|
+
warning = warning or {}
|
|
44
|
+
extra_info = extra_info or {}
|
|
45
|
+
|
|
46
|
+
# Set description to extra_info
|
|
47
|
+
extra_info['description'] = openapi['info'].get('description', '')
|
|
48
|
+
|
|
49
|
+
if len(openapi['servers']) == 0:
|
|
50
|
+
raise ValueError('No server found in the OpenAPI specification.')
|
|
51
|
+
|
|
52
|
+
server_url = openapi['servers'][0]['url']
|
|
53
|
+
|
|
54
|
+
# List all interfaces
|
|
55
|
+
interfaces = []
|
|
56
|
+
for path, path_item in openapi['paths'].items():
|
|
57
|
+
methods = ['get', 'post', 'put', 'delete', 'patch', 'head', 'options', 'trace']
|
|
58
|
+
for method in methods:
|
|
59
|
+
if method in path_item:
|
|
60
|
+
interfaces.append({
|
|
61
|
+
'path': path,
|
|
62
|
+
'method': method,
|
|
63
|
+
'operation': path_item[method],
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
# Process all operations
|
|
67
|
+
bundles = []
|
|
68
|
+
for interface in interfaces:
|
|
69
|
+
# Convert parameters
|
|
70
|
+
parameters = []
|
|
71
|
+
if 'parameters' in interface['operation']:
|
|
72
|
+
for parameter in interface['operation']['parameters']:
|
|
73
|
+
tool_parameter = ToolParameter(
|
|
74
|
+
name=parameter['name'],
|
|
75
|
+
label=MultilingualText(
|
|
76
|
+
en_US=parameter['name'],
|
|
77
|
+
zh_Hans=parameter['name']
|
|
78
|
+
),
|
|
79
|
+
human_description=MultilingualText(
|
|
80
|
+
en_US=parameter.get('description', ''),
|
|
81
|
+
zh_Hans=parameter.get('description', '')
|
|
82
|
+
),
|
|
83
|
+
type=ToolParameter.ToolParameterType.STRING,
|
|
84
|
+
required=parameter.get('required', False),
|
|
85
|
+
form=ToolParameter.ToolParameterForm.LLM,
|
|
86
|
+
llm_description=parameter.get('description'),
|
|
87
|
+
default=parameter['schema']['default'] if 'schema' in parameter and 'default' in parameter['schema'] else None,
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
# Check if there is a type
|
|
91
|
+
param_type = ApiBasedToolSchemaParser._get_tool_parameter_type(parameter)
|
|
92
|
+
if param_type:
|
|
93
|
+
tool_parameter.type = param_type
|
|
94
|
+
|
|
95
|
+
parameters.append(tool_parameter)
|
|
96
|
+
|
|
97
|
+
# Handle request body
|
|
98
|
+
if 'requestBody' in interface['operation']:
|
|
99
|
+
parameters.extend(ApiBasedToolSchemaParser._parse_request_body(interface))
|
|
100
|
+
|
|
101
|
+
# Handle responses (returns)
|
|
102
|
+
returns = ApiBasedToolSchemaParser._parse_responses(interface, openapi)
|
|
103
|
+
|
|
104
|
+
# Check if parameters are duplicated
|
|
105
|
+
parameters_count = {}
|
|
106
|
+
for parameter in parameters:
|
|
107
|
+
parameters_count[parameter.name] = parameters_count.get(parameter.name, 0) + 1
|
|
108
|
+
|
|
109
|
+
for name, count in parameters_count.items():
|
|
110
|
+
if count > 1:
|
|
111
|
+
warning['duplicated_parameter'] = f'Parameter {name} is duplicated.'
|
|
112
|
+
|
|
113
|
+
# Ensure operation ID exists
|
|
114
|
+
if 'operationId' not in interface['operation']:
|
|
115
|
+
interface['operation']['operationId'] = ApiBasedToolSchemaParser._generate_operation_id(
|
|
116
|
+
interface['path'], interface['method']
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
bundles.append(ApiToolBundle(
|
|
120
|
+
server_url=server_url + interface['path'],
|
|
121
|
+
method=interface['method'],
|
|
122
|
+
summary=interface['operation'].get('description') or interface['operation'].get('summary'),
|
|
123
|
+
operation_id=interface['operation']['operationId'],
|
|
124
|
+
parameters=parameters,
|
|
125
|
+
returns=returns,
|
|
126
|
+
author='',
|
|
127
|
+
icon=None,
|
|
128
|
+
openapi=interface['operation'],
|
|
129
|
+
))
|
|
130
|
+
|
|
131
|
+
return bundles
|
|
132
|
+
|
|
133
|
+
@staticmethod
|
|
134
|
+
def _parse_request_body(interface: dict) -> List[ToolParameter]:
|
|
135
|
+
"""Parse request body parameters"""
|
|
136
|
+
parameters = []
|
|
137
|
+
request_body = interface['operation']['requestBody']
|
|
138
|
+
|
|
139
|
+
if 'content' not in request_body:
|
|
140
|
+
return parameters
|
|
141
|
+
|
|
142
|
+
for content_type, content in request_body['content'].items():
|
|
143
|
+
# Handle schema references
|
|
144
|
+
if 'schema' in content:
|
|
145
|
+
if '$ref' in content['schema']:
|
|
146
|
+
# Resolve reference
|
|
147
|
+
root = ApiBasedToolSchemaParser._resolve_reference(
|
|
148
|
+
content['schema']['$ref'], interface['operation']
|
|
149
|
+
)
|
|
150
|
+
interface['operation']['requestBody']['content'][content_type]['schema'] = root
|
|
151
|
+
|
|
152
|
+
# Parse body parameters
|
|
153
|
+
if 'schema' in interface['operation']['requestBody']['content'][content_type]:
|
|
154
|
+
body_schema = interface['operation']['requestBody']['content'][content_type]['schema']
|
|
155
|
+
required = body_schema.get('required', [])
|
|
156
|
+
properties = body_schema.get('properties', {})
|
|
157
|
+
|
|
158
|
+
for name, property_def in properties.items():
|
|
159
|
+
# Determine parameter type
|
|
160
|
+
type_flag = ApiBasedToolSchemaParser._get_property_type_flag(property_def.get('type'))
|
|
161
|
+
|
|
162
|
+
tool_param = ToolParameter(
|
|
163
|
+
name=name,
|
|
164
|
+
label=MultilingualText(en_US=name, zh_Hans=name),
|
|
165
|
+
human_description=MultilingualText(
|
|
166
|
+
en_US=property_def.get('description', ''),
|
|
167
|
+
zh_Hans=property_def.get('description', '')
|
|
168
|
+
),
|
|
169
|
+
type=type_flag,
|
|
170
|
+
required=name in required,
|
|
171
|
+
form=ToolParameter.ToolParameterForm.LLM,
|
|
172
|
+
llm_description=property_def.get('description', ''),
|
|
173
|
+
default=property_def.get('default', None),
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
# Check if there is a type
|
|
177
|
+
typ = ApiBasedToolSchemaParser._get_tool_parameter_type(property_def)
|
|
178
|
+
if typ:
|
|
179
|
+
tool_param.type = typ
|
|
180
|
+
parameters.append(tool_param)
|
|
181
|
+
|
|
182
|
+
return parameters
|
|
183
|
+
|
|
184
|
+
@staticmethod
|
|
185
|
+
def _parse_responses(interface: dict, openapi: dict) -> List[ToolParameter]:
|
|
186
|
+
"""Parse response parameters"""
|
|
187
|
+
returns = []
|
|
188
|
+
if 'responses' not in interface['operation']:
|
|
189
|
+
return returns
|
|
190
|
+
|
|
191
|
+
responses = interface['operation']['responses']
|
|
192
|
+
if '200' not in responses:
|
|
193
|
+
return returns
|
|
194
|
+
|
|
195
|
+
if 'content' not in responses['200']:
|
|
196
|
+
return returns
|
|
197
|
+
|
|
198
|
+
for content_type, content in responses['200']['content'].items():
|
|
199
|
+
# Handle schema references
|
|
200
|
+
if 'schema' in content:
|
|
201
|
+
if '$ref' in content['schema']:
|
|
202
|
+
# Resolve reference
|
|
203
|
+
root = ApiBasedToolSchemaParser._resolve_reference(
|
|
204
|
+
content['schema']['$ref'], openapi
|
|
205
|
+
)
|
|
206
|
+
# Note: There was a typo in the original code ('responsed' instead of 'responses')
|
|
207
|
+
interface['operation']['responses']['200']['content'][content_type]['schema'] = root
|
|
208
|
+
|
|
209
|
+
# Parse response schema
|
|
210
|
+
if 'schema' in interface['operation']['responses']['200']['content'][content_type]:
|
|
211
|
+
body_schema = interface['operation']['responses']['200']['content'][content_type]['schema']
|
|
212
|
+
required = body_schema.get('required', [])
|
|
213
|
+
properties = body_schema.get('properties', {})
|
|
214
|
+
|
|
215
|
+
for name, property_def in properties.items():
|
|
216
|
+
tool_param = ToolParameter(
|
|
217
|
+
name=name,
|
|
218
|
+
label=MultilingualText(en_US=name, zh_Hans=name),
|
|
219
|
+
human_description=MultilingualText(
|
|
220
|
+
en_US=property_def.get('description', ''),
|
|
221
|
+
zh_Hans=property_def.get('description', '')
|
|
222
|
+
),
|
|
223
|
+
type=ToolParameter.ToolParameterType.STRING,
|
|
224
|
+
required=name in required,
|
|
225
|
+
form=ToolParameter.ToolParameterForm.LLM,
|
|
226
|
+
llm_description=property_def.get('description', ''),
|
|
227
|
+
default=property_def.get('default', None),
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
# Check if there is a type
|
|
231
|
+
typ = ApiBasedToolSchemaParser._get_tool_parameter_type(property_def)
|
|
232
|
+
if typ:
|
|
233
|
+
tool_param.type = typ
|
|
234
|
+
returns.append(tool_param)
|
|
235
|
+
|
|
236
|
+
return returns
|
|
237
|
+
|
|
238
|
+
@staticmethod
|
|
239
|
+
def _resolve_reference(ref: str, root: dict) -> dict:
|
|
240
|
+
"""Resolve a JSON reference"""
|
|
241
|
+
reference = ref.split('/')[1:]
|
|
242
|
+
for ref_part in reference:
|
|
243
|
+
root = root[ref_part]
|
|
244
|
+
return root
|
|
245
|
+
|
|
246
|
+
@staticmethod
|
|
247
|
+
def _get_property_type_flag(type_str: Optional[str]) -> ToolParameter.ToolParameterType:
|
|
248
|
+
"""Get ToolParameterType from string"""
|
|
249
|
+
type_mapping = {
|
|
250
|
+
'int': ToolParameter.ToolParameterType.INT,
|
|
251
|
+
'bool': ToolParameter.ToolParameterType.BOOL,
|
|
252
|
+
'number': ToolParameter.ToolParameterType.NUMBER,
|
|
253
|
+
'float': ToolParameter.ToolParameterType.FLOAT,
|
|
254
|
+
'string': ToolParameter.ToolParameterType.STRING,
|
|
255
|
+
}
|
|
256
|
+
return type_mapping.get(type_str, ToolParameter.ToolParameterType.STRING)
|
|
257
|
+
|
|
258
|
+
@staticmethod
|
|
259
|
+
def _generate_operation_id(path: str, method: str) -> str:
|
|
260
|
+
"""Generate an operation ID from path and method"""
|
|
261
|
+
# Remove leading slash
|
|
262
|
+
if path.startswith('/'):
|
|
263
|
+
path = path[1:]
|
|
264
|
+
# Remove special characters
|
|
265
|
+
path = re.sub(r'[^a-zA-Z0-9_-]', '', path)
|
|
266
|
+
# Use UUID if path is empty
|
|
267
|
+
if not path:
|
|
268
|
+
path = str(uuid.uuid4())
|
|
269
|
+
return f'{path}_{method}'
|
|
270
|
+
|
|
271
|
+
@staticmethod
|
|
272
|
+
def _get_tool_parameter_type(parameter: dict) -> Optional[ToolParameter.ToolParameterType]:
|
|
273
|
+
"""Get tool parameter type from parameter definition"""
|
|
274
|
+
parameter = parameter or {}
|
|
275
|
+
typ = None
|
|
276
|
+
|
|
277
|
+
if 'type' in parameter:
|
|
278
|
+
typ = parameter['type']
|
|
279
|
+
elif 'schema' in parameter and 'type' in parameter['schema']:
|
|
280
|
+
typ = parameter['schema']['type']
|
|
281
|
+
|
|
282
|
+
type_mapping = {
|
|
283
|
+
'integer': ToolParameter.ToolParameterType.NUMBER,
|
|
284
|
+
'number': ToolParameter.ToolParameterType.NUMBER,
|
|
285
|
+
'boolean': ToolParameter.ToolParameterType.BOOLEAN,
|
|
286
|
+
'string': ToolParameter.ToolParameterType.STRING
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
return type_mapping.get(typ)
|
|
290
|
+
|
|
291
|
+
@staticmethod
|
|
292
|
+
def parse_openapi_yaml_to_tool_bundle(yaml: str, extra_info: Optional[dict] = None,
|
|
293
|
+
warning: Optional[dict] = None) -> List[ApiToolBundle]:
|
|
294
|
+
"""
|
|
295
|
+
Parse OpenAPI YAML to tool bundle.
|
|
296
|
+
|
|
297
|
+
Args:
|
|
298
|
+
yaml: The YAML string
|
|
299
|
+
extra_info: Additional information to include
|
|
300
|
+
warning: Warning messages dictionary
|
|
301
|
+
|
|
302
|
+
Returns:
|
|
303
|
+
List of ApiToolBundle objects
|
|
304
|
+
"""
|
|
305
|
+
warning = warning or {}
|
|
306
|
+
extra_info = extra_info or {}
|
|
307
|
+
|
|
308
|
+
openapi: dict = safe_load(yaml)
|
|
309
|
+
if openapi is None:
|
|
310
|
+
raise ValueError('Invalid OpenAPI YAML.')
|
|
311
|
+
|
|
312
|
+
return ApiBasedToolSchemaParser.parse_openapi_to_tool_bundle(
|
|
313
|
+
openapi, extra_info=extra_info, warning=warning
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
@staticmethod
|
|
317
|
+
def parse_swagger_to_openapi(swagger: dict, extra_info: Optional[dict] = None,
|
|
318
|
+
warning: Optional[dict] = None) -> dict:
|
|
319
|
+
"""
|
|
320
|
+
Parse Swagger specification to OpenAPI.
|
|
321
|
+
|
|
322
|
+
Args:
|
|
323
|
+
swagger: The Swagger dictionary
|
|
324
|
+
extra_info: Additional information to include
|
|
325
|
+
warning: Warning messages dictionary
|
|
326
|
+
|
|
327
|
+
Returns:
|
|
328
|
+
OpenAPI dictionary
|
|
329
|
+
"""
|
|
330
|
+
warning = warning or {}
|
|
331
|
+
extra_info = extra_info or {}
|
|
332
|
+
|
|
333
|
+
# Convert swagger to openapi
|
|
334
|
+
info = swagger.get('info', {
|
|
335
|
+
'title': 'Swagger',
|
|
336
|
+
'description': 'Swagger',
|
|
337
|
+
'version': '1.0.0'
|
|
338
|
+
})
|
|
339
|
+
|
|
340
|
+
servers = swagger.get('servers', [])
|
|
341
|
+
if len(servers) == 0:
|
|
342
|
+
raise ValueError('No server found in the Swagger specification.')
|
|
343
|
+
|
|
344
|
+
openapi = {
|
|
345
|
+
'openapi': '3.0.0',
|
|
346
|
+
'info': {
|
|
347
|
+
'title': info.get('title', 'Swagger'),
|
|
348
|
+
'description': info.get('description', 'Swagger'),
|
|
349
|
+
'version': info.get('version', '1.0.0')
|
|
350
|
+
},
|
|
351
|
+
'servers': swagger['servers'],
|
|
352
|
+
'paths': {},
|
|
353
|
+
'components': {
|
|
354
|
+
'schemas': {}
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
# Check paths
|
|
359
|
+
if 'paths' not in swagger or len(swagger['paths']) == 0:
|
|
360
|
+
raise ValueError('No paths found in the Swagger specification.')
|
|
361
|
+
|
|
362
|
+
# Convert paths
|
|
363
|
+
for path, path_item in swagger['paths'].items():
|
|
364
|
+
openapi['paths'][path] = {}
|
|
365
|
+
for method, operation in path_item.items():
|
|
366
|
+
if 'operationId' not in operation:
|
|
367
|
+
raise ValueError(f'No operationId found in operation {method} {path}.')
|
|
368
|
+
|
|
369
|
+
if not operation.get('summary') and not operation.get('description'):
|
|
370
|
+
warning['missing_summary'] = f'No summary or description found in operation {method} {path}.'
|
|
371
|
+
|
|
372
|
+
openapi['paths'][path][method] = {
|
|
373
|
+
'operationId': operation['operationId'],
|
|
374
|
+
'summary': operation.get('summary', ''),
|
|
375
|
+
'description': operation.get('description', ''),
|
|
376
|
+
'parameters': operation.get('parameters', []),
|
|
377
|
+
'responses': operation.get('responses', {}),
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
if 'requestBody' in operation:
|
|
381
|
+
openapi['paths'][path][method]['requestBody'] = operation['requestBody']
|
|
382
|
+
|
|
383
|
+
# Convert definitions
|
|
384
|
+
for name, definition in swagger.get('definitions', {}).items():
|
|
385
|
+
openapi['components']['schemas'][name] = definition
|
|
386
|
+
|
|
387
|
+
return openapi
|
|
388
|
+
|
|
389
|
+
@staticmethod
|
|
390
|
+
def parse_openai_plugin_json_to_tool_bundle(json_str: str, extra_info: Optional[dict] = None,
|
|
391
|
+
warning: Optional[dict] = None) -> List[ApiToolBundle]:
|
|
392
|
+
"""
|
|
393
|
+
Parse OpenAI plugin JSON to tool bundle.
|
|
394
|
+
|
|
395
|
+
Args:
|
|
396
|
+
json_str: The JSON string
|
|
397
|
+
extra_info: Additional information to include
|
|
398
|
+
warning: Warning messages dictionary
|
|
399
|
+
|
|
400
|
+
Returns:
|
|
401
|
+
List of ApiToolBundle objects
|
|
402
|
+
"""
|
|
403
|
+
warning = warning or {}
|
|
404
|
+
extra_info = extra_info or {}
|
|
405
|
+
|
|
406
|
+
try:
|
|
407
|
+
openai_plugin = json_loads(json_str)
|
|
408
|
+
api = openai_plugin['api']
|
|
409
|
+
api_url = api['url']
|
|
410
|
+
api_type = api['type']
|
|
411
|
+
except (KeyError, JSONDecodeError):
|
|
412
|
+
raise ValueError('Invalid OpenAI plugin JSON.')
|
|
413
|
+
|
|
414
|
+
if api_type != 'openapi':
|
|
415
|
+
raise ValueError('Only OpenAPI format is supported.')
|
|
416
|
+
|
|
417
|
+
# Get OpenAPI YAML
|
|
418
|
+
response = get(api_url, headers={
|
|
419
|
+
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) '
|
|
420
|
+
}, timeout=5)
|
|
421
|
+
|
|
422
|
+
if response.status_code != 200:
|
|
423
|
+
raise ValueError('Cannot get OpenAPI YAML from URL.')
|
|
424
|
+
|
|
425
|
+
return ApiBasedToolSchemaParser.parse_openapi_yaml_to_tool_bundle(
|
|
426
|
+
response.text, extra_info=extra_info, warning=warning
|
|
427
|
+
)
|
|
428
|
+
|
|
429
|
+
@staticmethod
|
|
430
|
+
def auto_parse_to_tool_bundle(content: str, extra_info: Optional[dict] = None,
|
|
431
|
+
warning: Optional[dict] = None) -> Tuple[List[ApiToolBundle], str]:
|
|
432
|
+
"""
|
|
433
|
+
Automatically parse content to tool bundle.
|
|
434
|
+
|
|
435
|
+
Args:
|
|
436
|
+
content: The content to parse
|
|
437
|
+
extra_info: Additional information to include
|
|
438
|
+
warning: Warning messages dictionary
|
|
439
|
+
|
|
440
|
+
Returns:
|
|
441
|
+
Tuple of (tool bundles, schema type)
|
|
442
|
+
"""
|
|
443
|
+
warning = warning or {}
|
|
444
|
+
extra_info = extra_info or {}
|
|
445
|
+
|
|
446
|
+
content = content.strip()
|
|
447
|
+
loaded_content = None
|
|
448
|
+
json_error = None
|
|
449
|
+
yaml_error = None
|
|
450
|
+
|
|
451
|
+
try:
|
|
452
|
+
loaded_content = json_loads(content)
|
|
453
|
+
except JSONDecodeError as e:
|
|
454
|
+
json_error = e
|
|
455
|
+
|
|
456
|
+
if loaded_content is None:
|
|
457
|
+
try:
|
|
458
|
+
loaded_content = safe_load(content)
|
|
459
|
+
except YAMLError as e:
|
|
460
|
+
yaml_error = e
|
|
461
|
+
|
|
462
|
+
if loaded_content is None:
|
|
463
|
+
raise ValueError(
|
|
464
|
+
f'Invalid API schema, schema is neither JSON nor YAML. '
|
|
465
|
+
f'JSON error: {str(json_error)}, YAML error: {str(yaml_error)}'
|
|
466
|
+
)
|
|
467
|
+
|
|
468
|
+
# Try parsing as OpenAPI
|
|
469
|
+
try:
|
|
470
|
+
openapi = ApiBasedToolSchemaParser.parse_openapi_to_tool_bundle(
|
|
471
|
+
loaded_content, extra_info=extra_info, warning=warning
|
|
472
|
+
)
|
|
473
|
+
schema_type = "openapi"
|
|
474
|
+
return openapi, schema_type
|
|
475
|
+
except Exception as e:
|
|
476
|
+
# Store the error for potential debugging
|
|
477
|
+
openapi_error = e
|
|
478
|
+
|
|
479
|
+
# Try parsing as Swagger
|
|
480
|
+
try:
|
|
481
|
+
converted_swagger = ApiBasedToolSchemaParser.parse_swagger_to_openapi(
|
|
482
|
+
loaded_content, extra_info=extra_info, warning=warning
|
|
483
|
+
)
|
|
484
|
+
schema_type = "swagger" # Using string instead of undefined ApiProviderSchemaType.SWAGGER.value
|
|
485
|
+
return ApiBasedToolSchemaParser.parse_openapi_to_tool_bundle(
|
|
486
|
+
converted_swagger, extra_info=extra_info, warning=warning
|
|
487
|
+
), schema_type
|
|
488
|
+
except Exception as e:
|
|
489
|
+
swagger_error = e
|
|
490
|
+
|
|
491
|
+
# Try parsing as OpenAI plugin
|
|
492
|
+
try:
|
|
493
|
+
openapi_plugin = ApiBasedToolSchemaParser.parse_openai_plugin_json_to_tool_bundle(
|
|
494
|
+
json_dumps(loaded_content), extra_info=extra_info, warning=warning
|
|
495
|
+
)
|
|
496
|
+
schema_type = "openai_plugin" # Using string instead of undefined ApiProviderSchemaType.OPENAI_PLUGIN.value
|
|
497
|
+
return openapi_plugin, schema_type
|
|
498
|
+
except Exception as e:
|
|
499
|
+
openapi_plugin_error = e
|
|
500
|
+
|
|
501
|
+
raise ValueError(
|
|
502
|
+
f'Invalid API schema. '
|
|
503
|
+
f'OpenAPI error: {str(openapi_error)}, '
|
|
504
|
+
f'Swagger error: {str(swagger_error)}, '
|
|
505
|
+
f'OpenAI plugin error: {str(openapi_plugin_error)}'
|
|
506
|
+
)
|
|
507
|
+
|
|
508
|
+
|
|
509
|
+
def parser_tool_from_path(tool_path: str, file_reader=None) -> Tuple[List[ApiToolBundle], str]:
|
|
510
|
+
"""
|
|
511
|
+
Parse tool from file path.
|
|
512
|
+
|
|
513
|
+
Args:
|
|
514
|
+
tool_path: Path to the tool file
|
|
515
|
+
file_reader: File reader function (defaults to file_db.read_json)
|
|
516
|
+
|
|
517
|
+
Returns:
|
|
518
|
+
Tuple of (tool bundles, schema type)
|
|
519
|
+
"""
|
|
520
|
+
if file_reader is None:
|
|
521
|
+
from swiftmcp.utils.file_db import AgentFileDB
|
|
522
|
+
file_db = AgentFileDB(db_path="tmp/test.db")
|
|
523
|
+
file_reader = file_db
|
|
524
|
+
|
|
525
|
+
content_json = file_reader.read_json(tool_path)
|
|
526
|
+
content = json.dumps(content_json)
|
|
527
|
+
return ApiBasedToolSchemaParser.auto_parse_to_tool_bundle(content)
|
|
528
|
+
|
|
529
|
+
|
|
530
|
+
if __name__ == "__main__":
|
|
531
|
+
tool_bundles, schema_type = parser_tool_from_path("data/tools/SampleTool.json")
|
|
532
|
+
server_url: str = tool_bundles[0].server_url
|
|
533
|
+
request_method: str = tool_bundles[0].method
|
|
534
|
+
summary: str = tool_bundles[0].summary
|
|
535
|
+
parameters: list = tool_bundles[0].parameters
|
|
536
|
+
returns: list = tool_bundles[0].returns
|
|
537
|
+
operation_id = tool_bundles[0].operation_id
|