nocfo-cli 1.4.4__tar.gz → 1.4.5__tar.gz
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.
- {nocfo_cli-1.4.4 → nocfo_cli-1.4.5}/PKG-INFO +1 -1
- {nocfo_cli-1.4.4 → nocfo_cli-1.4.5}/pyproject.toml +1 -1
- {nocfo_cli-1.4.4 → nocfo_cli-1.4.5}/src/nocfo_toolkit/mcp/middleware.py +16 -2
- {nocfo_cli-1.4.4 → nocfo_cli-1.4.5}/src/nocfo_toolkit/mcp/server.py +33 -24
- {nocfo_cli-1.4.4 → nocfo_cli-1.4.5}/LICENSE +0 -0
- {nocfo_cli-1.4.4 → nocfo_cli-1.4.5}/README.md +0 -0
- {nocfo_cli-1.4.4 → nocfo_cli-1.4.5}/src/nocfo_toolkit/__init__.py +0 -0
- {nocfo_cli-1.4.4 → nocfo_cli-1.4.5}/src/nocfo_toolkit/api_client.py +0 -0
- {nocfo_cli-1.4.4 → nocfo_cli-1.4.5}/src/nocfo_toolkit/cli/__init__.py +0 -0
- {nocfo_cli-1.4.4 → nocfo_cli-1.4.5}/src/nocfo_toolkit/cli/app.py +0 -0
- {nocfo_cli-1.4.4 → nocfo_cli-1.4.5}/src/nocfo_toolkit/cli/commands/__init__.py +0 -0
- {nocfo_cli-1.4.4 → nocfo_cli-1.4.5}/src/nocfo_toolkit/cli/commands/_helpers.py +0 -0
- {nocfo_cli-1.4.4 → nocfo_cli-1.4.5}/src/nocfo_toolkit/cli/commands/accounts.py +0 -0
- {nocfo_cli-1.4.4 → nocfo_cli-1.4.5}/src/nocfo_toolkit/cli/commands/auth.py +0 -0
- {nocfo_cli-1.4.4 → nocfo_cli-1.4.5}/src/nocfo_toolkit/cli/commands/businesses.py +0 -0
- {nocfo_cli-1.4.4 → nocfo_cli-1.4.5}/src/nocfo_toolkit/cli/commands/contacts.py +0 -0
- {nocfo_cli-1.4.4 → nocfo_cli-1.4.5}/src/nocfo_toolkit/cli/commands/documents.py +0 -0
- {nocfo_cli-1.4.4 → nocfo_cli-1.4.5}/src/nocfo_toolkit/cli/commands/files.py +0 -0
- {nocfo_cli-1.4.4 → nocfo_cli-1.4.5}/src/nocfo_toolkit/cli/commands/invoices.py +0 -0
- {nocfo_cli-1.4.4 → nocfo_cli-1.4.5}/src/nocfo_toolkit/cli/commands/products.py +0 -0
- {nocfo_cli-1.4.4 → nocfo_cli-1.4.5}/src/nocfo_toolkit/cli/commands/purchase_invoices.py +0 -0
- {nocfo_cli-1.4.4 → nocfo_cli-1.4.5}/src/nocfo_toolkit/cli/commands/reports.py +0 -0
- {nocfo_cli-1.4.4 → nocfo_cli-1.4.5}/src/nocfo_toolkit/cli/commands/schema.py +0 -0
- {nocfo_cli-1.4.4 → nocfo_cli-1.4.5}/src/nocfo_toolkit/cli/commands/tags.py +0 -0
- {nocfo_cli-1.4.4 → nocfo_cli-1.4.5}/src/nocfo_toolkit/cli/commands/user.py +0 -0
- {nocfo_cli-1.4.4 → nocfo_cli-1.4.5}/src/nocfo_toolkit/cli/context.py +0 -0
- {nocfo_cli-1.4.4 → nocfo_cli-1.4.5}/src/nocfo_toolkit/cli/output.py +0 -0
- {nocfo_cli-1.4.4 → nocfo_cli-1.4.5}/src/nocfo_toolkit/config.py +0 -0
- {nocfo_cli-1.4.4 → nocfo_cli-1.4.5}/src/nocfo_toolkit/mcp/__init__.py +0 -0
- {nocfo_cli-1.4.4 → nocfo_cli-1.4.5}/src/nocfo_toolkit/mcp/auth.py +0 -0
- {nocfo_cli-1.4.4 → nocfo_cli-1.4.5}/src/nocfo_toolkit/mcp/contract_validation.py +0 -0
- {nocfo_cli-1.4.4 → nocfo_cli-1.4.5}/src/nocfo_toolkit/mcp/error_handling.py +0 -0
- {nocfo_cli-1.4.4 → nocfo_cli-1.4.5}/src/nocfo_toolkit/mcp/http_error_capture.py +0 -0
- {nocfo_cli-1.4.4 → nocfo_cli-1.4.5}/src/nocfo_toolkit/openapi.py +0 -0
|
@@ -83,9 +83,23 @@ class MCPToolErrorMiddleware(Middleware):
|
|
|
83
83
|
normalized.get("summary"),
|
|
84
84
|
)
|
|
85
85
|
raise ToolError(json.dumps(normalized, ensure_ascii=True)) from exc
|
|
86
|
-
except Exception:
|
|
86
|
+
except Exception as exc:
|
|
87
87
|
logger.exception("MCP tool call crashed tool=%s", tool_name)
|
|
88
|
-
|
|
88
|
+
captured = get_last_http_error()
|
|
89
|
+
if captured:
|
|
90
|
+
normalized = normalize_http_error(
|
|
91
|
+
tool_name=tool_name,
|
|
92
|
+
status_code=captured.get("status_code"),
|
|
93
|
+
payload=captured.get("payload"),
|
|
94
|
+
fallback_message=f"{type(exc).__name__}: {exc}",
|
|
95
|
+
)
|
|
96
|
+
else:
|
|
97
|
+
normalized = {
|
|
98
|
+
"tool": tool_name,
|
|
99
|
+
"error_type": "tool_error",
|
|
100
|
+
"summary": f"{type(exc).__name__}: {exc}",
|
|
101
|
+
}
|
|
102
|
+
raise ToolError(json.dumps(normalized, ensure_ascii=True)) from exc
|
|
89
103
|
finally:
|
|
90
104
|
clear_last_http_error()
|
|
91
105
|
|
|
@@ -15,6 +15,7 @@ from fastmcp.server.providers.openapi.components import (
|
|
|
15
15
|
)
|
|
16
16
|
from fastmcp.server.providers.openapi.routing import MCPType, RouteMap
|
|
17
17
|
from fastmcp.tools.tool import Tool
|
|
18
|
+
from mcp import types as mcp_types
|
|
18
19
|
|
|
19
20
|
from nocfo_toolkit.config import AUTH_HEADER_SCHEME, ToolkitConfig
|
|
20
21
|
from nocfo_toolkit.mcp.auth import (
|
|
@@ -125,26 +126,34 @@ def apply_mcp_operation_metadata(route: Any, component: Any) -> None:
|
|
|
125
126
|
component.meta = meta
|
|
126
127
|
|
|
127
128
|
|
|
128
|
-
|
|
129
|
+
def patch_mcp_runtime_output_validation(server: FastMCP) -> None:
|
|
130
|
+
"""Disable SDK output validation at runtime without changing listed schemas.
|
|
129
131
|
|
|
132
|
+
MCP SDK validates tool outputs against ``outputSchema`` unconditionally in the
|
|
133
|
+
low-level ``call_tool`` handler. We patch the low-level lookup used only by that
|
|
134
|
+
validation path to return a cloned tool definition with ``outputSchema=None``.
|
|
135
|
+
``tools/list`` responses are unaffected and keep the original schema intact.
|
|
136
|
+
"""
|
|
137
|
+
lowlevel = getattr(server, "_mcp_server", None)
|
|
138
|
+
if lowlevel is None:
|
|
139
|
+
return
|
|
140
|
+
if getattr(lowlevel, "_nocfo_runtime_output_validation_patched", False):
|
|
141
|
+
return
|
|
130
142
|
|
|
131
|
-
|
|
132
|
-
"""Recursively strip strict validation constraints from a JSON schema.
|
|
143
|
+
original_get_cached_tool_definition = lowlevel._get_cached_tool_definition
|
|
133
144
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
return {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
return [_strip_validation_constraints(item) for item in schema]
|
|
147
|
-
return schema
|
|
145
|
+
async def _get_cached_tool_definition_without_output_schema(
|
|
146
|
+
tool_name: str,
|
|
147
|
+
) -> mcp_types.Tool | None:
|
|
148
|
+
tool = await original_get_cached_tool_definition(tool_name)
|
|
149
|
+
if tool is None or tool.outputSchema is None:
|
|
150
|
+
return tool
|
|
151
|
+
return tool.model_copy(update={"outputSchema": None})
|
|
152
|
+
|
|
153
|
+
lowlevel._get_cached_tool_definition = (
|
|
154
|
+
_get_cached_tool_definition_without_output_schema
|
|
155
|
+
)
|
|
156
|
+
lowlevel._nocfo_runtime_output_validation_patched = True
|
|
148
157
|
|
|
149
158
|
|
|
150
159
|
def restore_openapi_output_schema(route: Any, component: Any) -> None:
|
|
@@ -156,11 +165,9 @@ def restore_openapi_output_schema(route: Any, component: Any) -> None:
|
|
|
156
165
|
onto the tool so that ``tools/list`` still exposes precise type information
|
|
157
166
|
to the agent.
|
|
158
167
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
restored schema has strict value constraints (``enum``, ``const``)
|
|
163
|
-
stripped while keeping full structural and type information.
|
|
168
|
+
Runtime output validation bypass is handled separately in
|
|
169
|
+
:func:`patch_mcp_runtime_output_validation`, so the listed schema can remain
|
|
170
|
+
fully backend-authored (including enums and other constraints).
|
|
164
171
|
"""
|
|
165
172
|
if not isinstance(component, Tool):
|
|
166
173
|
return
|
|
@@ -173,7 +180,7 @@ def restore_openapi_output_schema(route: Any, component: Any) -> None:
|
|
|
173
180
|
getattr(route, "openapi_version", None),
|
|
174
181
|
)
|
|
175
182
|
if real_schema is not None:
|
|
176
|
-
component.output_schema =
|
|
183
|
+
component.output_schema = real_schema
|
|
177
184
|
|
|
178
185
|
|
|
179
186
|
def _get_server_instructions(openapi_spec: dict[str, Any]) -> str | None:
|
|
@@ -282,7 +289,7 @@ def create_server(
|
|
|
282
289
|
required_scopes=opts.required_scopes,
|
|
283
290
|
)
|
|
284
291
|
|
|
285
|
-
|
|
292
|
+
server = FastMCP.from_openapi(
|
|
286
293
|
openapi_spec=filtered_spec,
|
|
287
294
|
client=client,
|
|
288
295
|
name=opts.name,
|
|
@@ -293,6 +300,8 @@ def create_server(
|
|
|
293
300
|
mcp_component_fn=component_mapper,
|
|
294
301
|
validate_output=False,
|
|
295
302
|
)
|
|
303
|
+
patch_mcp_runtime_output_validation(server)
|
|
304
|
+
return server
|
|
296
305
|
|
|
297
306
|
|
|
298
307
|
def run_server(
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|