universal-mcp 0.1.21rc2__py3-none-any.whl → 0.1.22rc1__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.
- universal_mcp/applications/__init__.py +0 -1
- universal_mcp/integrations/integration.py +4 -8
- universal_mcp/servers/server.py +35 -30
- universal_mcp/tools/adapters.py +21 -0
- universal_mcp/tools/manager.py +122 -37
- universal_mcp/utils/agentr.py +3 -13
- universal_mcp/utils/docstring_parser.py +18 -64
- universal_mcp/utils/openapi/api_splitter.py +250 -132
- universal_mcp/utils/openapi/openapi.py +134 -117
- universal_mcp/utils/openapi/preprocessor.py +272 -29
- {universal_mcp-0.1.21rc2.dist-info → universal_mcp-0.1.22rc1.dist-info}/METADATA +2 -1
- {universal_mcp-0.1.21rc2.dist-info → universal_mcp-0.1.22rc1.dist-info}/RECORD +15 -15
- {universal_mcp-0.1.21rc2.dist-info → universal_mcp-0.1.22rc1.dist-info}/WHEEL +0 -0
- {universal_mcp-0.1.21rc2.dist-info → universal_mcp-0.1.22rc1.dist-info}/entry_points.txt +0 -0
- {universal_mcp-0.1.21rc2.dist-info → universal_mcp-0.1.22rc1.dist-info}/licenses/LICENSE +0 -0
@@ -40,8 +40,6 @@ def parse_docstring(docstring: str | None) -> dict[str, Any]:
|
|
40
40
|
current_key: str | None = None
|
41
41
|
current_desc_lines: list[str] = []
|
42
42
|
|
43
|
-
# Pattern to capture item key and the start of its description
|
44
|
-
# Matches "key:" or "key (type):" followed by description
|
45
43
|
key_pattern = re.compile(r"^\s*([\w\.]+)\s*(?:\(.*\))?:\s*(.*)")
|
46
44
|
|
47
45
|
def finalize_current_item():
|
@@ -56,12 +54,11 @@ def parse_docstring(docstring: str | None) -> dict[str, Any]:
|
|
56
54
|
if desc:
|
57
55
|
raises[current_key] = desc
|
58
56
|
elif current_section == "returns":
|
59
|
-
|
57
|
+
if desc:
|
58
|
+
returns = desc
|
60
59
|
elif current_section == "tags":
|
61
|
-
|
62
|
-
tags.clear() # Clear existing tags in case of multiple tag sections (unlikely but safe)
|
60
|
+
tags.clear()
|
63
61
|
tags.extend([tag.strip() for tag in desc.split(",") if tag.strip()])
|
64
|
-
# 'other' sections are ignored in the final output
|
65
62
|
|
66
63
|
def check_for_section_header(line: str) -> tuple[bool, str | None, str]:
|
67
64
|
"""Checks if a line is a recognized section header."""
|
@@ -77,21 +74,17 @@ def parse_docstring(docstring: str | None) -> dict[str, Any]:
|
|
77
74
|
section_type = "raises"
|
78
75
|
elif stripped_lower in ("tags:",):
|
79
76
|
section_type = "tags"
|
80
|
-
# Allow "Raises Description:" or "Tags content:"
|
81
77
|
elif stripped_lower.startswith(("raises ", "errors ", "exceptions ")):
|
82
78
|
section_type = "raises"
|
83
|
-
|
84
|
-
parts = re.split(r"[:\s]+", line.strip(), maxsplit=1) # B034: Use keyword maxsplit
|
79
|
+
parts = re.split(r"[:\s]+", line.strip(), maxsplit=1)
|
85
80
|
if len(parts) > 1:
|
86
81
|
header_content = parts[1].strip()
|
87
82
|
elif stripped_lower.startswith(("tags",)):
|
88
83
|
section_type = "tags"
|
89
|
-
|
90
|
-
parts = re.split(r"[:\s]+", line.strip(), maxsplit=1) # B034: Use keyword maxsplit
|
84
|
+
parts = re.split(r"[:\s]+", line.strip(), maxsplit=1)
|
91
85
|
if len(parts) > 1:
|
92
86
|
header_content = parts[1].strip()
|
93
87
|
|
94
|
-
# Identify other known sections, but don't store their content
|
95
88
|
elif stripped_lower.endswith(":") and stripped_lower[:-1] in (
|
96
89
|
"attributes",
|
97
90
|
"see also",
|
@@ -112,38 +105,22 @@ def parse_docstring(docstring: str | None) -> dict[str, Any]:
|
|
112
105
|
for line in lines:
|
113
106
|
stripped_line = line.strip()
|
114
107
|
original_indentation = len(line) - len(line.lstrip(" "))
|
115
|
-
|
116
108
|
is_new_section_header, new_section_type_this_line, header_content_this_line = check_for_section_header(line)
|
117
|
-
|
118
109
|
should_finalize_previous = False
|
119
110
|
|
120
|
-
# --- Summary Handling ---
|
121
111
|
if in_summary:
|
122
112
|
if not stripped_line or is_new_section_header:
|
123
|
-
# Empty line or section header marks the end of the summary
|
124
113
|
in_summary = False
|
125
114
|
summary = " ".join(summary_lines).strip()
|
126
|
-
summary_lines = []
|
115
|
+
summary_lines = []
|
127
116
|
|
128
117
|
if not stripped_line:
|
129
|
-
# If the line was just empty, continue to the next line
|
130
|
-
# The new_section_header check will happen on the next iteration if it exists
|
131
118
|
continue
|
132
|
-
# If it was a header, fall through to section handling below
|
133
119
|
|
134
120
|
else:
|
135
|
-
# Still in summary, append line
|
136
121
|
summary_lines.append(stripped_line)
|
137
|
-
continue
|
138
|
-
|
139
|
-
# --- Section and Item Handling ---
|
122
|
+
continue
|
140
123
|
|
141
|
-
# Decide if the previous item/section block should be finalized BEFORE processing the current line
|
142
|
-
# Finalize if:
|
143
|
-
# 1. A new section header is encountered.
|
144
|
-
# 2. An empty line is encountered AFTER we've started collecting content for an item or section.
|
145
|
-
# 3. In 'args' or 'raises', we encounter a line that looks like a new key: value pair, or a non-indented line.
|
146
|
-
# 4. In 'returns', 'tags', or 'other', we encounter a non-indented line after collecting content.
|
147
124
|
if (
|
148
125
|
is_new_section_header
|
149
126
|
or (not stripped_line and (current_desc_lines or current_key is not None))
|
@@ -160,20 +137,16 @@ def parse_docstring(docstring: str | None) -> dict[str, Any]:
|
|
160
137
|
)
|
161
138
|
):
|
162
139
|
should_finalize_previous = True
|
163
|
-
elif
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
140
|
+
elif (
|
141
|
+
current_section in ["args", "raises"]
|
142
|
+
and current_key is not None
|
143
|
+
or current_section in ["returns", "tags", "other"]
|
144
|
+
and current_desc_lines
|
145
|
+
):
|
146
|
+
pass
|
147
|
+
|
171
148
|
if should_finalize_previous:
|
172
149
|
finalize_current_item()
|
173
|
-
# Reset state after finalizing the previous item/section block
|
174
|
-
# If it was a new section header, reset everything
|
175
|
-
# If it was an end-of-item/block signal within a section, reset key and description lines
|
176
|
-
# (The condition for resetting key here is complex but matches the original logic)
|
177
150
|
if is_new_section_header or (
|
178
151
|
current_section in ["args", "raises"]
|
179
152
|
and current_key is not None
|
@@ -181,49 +154,30 @@ def parse_docstring(docstring: str | None) -> dict[str, Any]:
|
|
181
154
|
and (not stripped_line or original_indentation == 0)
|
182
155
|
):
|
183
156
|
current_key = None
|
184
|
-
current_desc_lines = []
|
157
|
+
current_desc_lines = []
|
185
158
|
|
186
|
-
# --- Process the current line ---
|
187
|
-
|
188
|
-
# If the current line is a section header
|
189
159
|
if is_new_section_header:
|
190
160
|
current_section = new_section_type_this_line
|
191
161
|
if header_content_this_line:
|
192
|
-
# Add content immediately following the header on the same line
|
193
162
|
current_desc_lines.append(header_content_this_line)
|
194
|
-
continue
|
163
|
+
continue
|
195
164
|
|
196
|
-
# If the line is empty, and not a section header (handled above), skip it
|
197
165
|
if not stripped_line:
|
198
166
|
continue
|
199
167
|
|
200
|
-
# If we are inside a section, process the line's content
|
201
168
|
if current_section == "args" or current_section == "raises":
|
202
169
|
match = key_pattern.match(line)
|
203
170
|
if match:
|
204
|
-
# Found a new key: value item within args/raises
|
205
171
|
current_key = match.group(1)
|
206
|
-
current_desc_lines = [match.group(2).strip()]
|
172
|
+
current_desc_lines = [match.group(2).strip()]
|
207
173
|
elif current_key is not None:
|
208
|
-
# Not a new key, but processing an existing item - append to description
|
209
174
|
current_desc_lines.append(stripped_line)
|
210
|
-
# Lines that don't match key_pattern and occur when current_key is None
|
211
|
-
# within args/raises are effectively ignored by this block, which seems
|
212
|
-
# consistent with needing a key: description format.
|
213
175
|
|
214
176
|
elif current_section in ["returns", "tags", "other"]:
|
215
|
-
# In these sections, all non-empty, non-header lines are description lines
|
216
177
|
current_desc_lines.append(stripped_line)
|
217
178
|
|
218
|
-
# --- Finalization after loop ---
|
219
|
-
# Finalize any pending item/section block that was being collected
|
220
179
|
finalize_current_item()
|
221
180
|
|
222
|
-
# If the docstring only had a summary (no empty line or section header)
|
223
|
-
# ensure the summary is captured. This check is technically redundant
|
224
|
-
# because summary is finalized upon hitting the first empty line or header,
|
225
|
-
# or falls through to the final finalize call if neither occurs.
|
226
|
-
# Keeping it for clarity, though the logic flow should cover it.
|
227
181
|
if in_summary:
|
228
182
|
summary = " ".join(summary_lines).strip()
|
229
183
|
|