nocfo-cli 1.4.0__tar.gz → 1.4.2__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.
Files changed (34) hide show
  1. {nocfo_cli-1.4.0 → nocfo_cli-1.4.2}/PKG-INFO +1 -1
  2. {nocfo_cli-1.4.0 → nocfo_cli-1.4.2}/pyproject.toml +1 -1
  3. {nocfo_cli-1.4.0 → nocfo_cli-1.4.2}/src/nocfo_toolkit/mcp/contract_validation.py +4 -2
  4. {nocfo_cli-1.4.0 → nocfo_cli-1.4.2}/src/nocfo_toolkit/mcp/server.py +33 -17
  5. {nocfo_cli-1.4.0 → nocfo_cli-1.4.2}/LICENSE +0 -0
  6. {nocfo_cli-1.4.0 → nocfo_cli-1.4.2}/README.md +0 -0
  7. {nocfo_cli-1.4.0 → nocfo_cli-1.4.2}/src/nocfo_toolkit/__init__.py +0 -0
  8. {nocfo_cli-1.4.0 → nocfo_cli-1.4.2}/src/nocfo_toolkit/api_client.py +0 -0
  9. {nocfo_cli-1.4.0 → nocfo_cli-1.4.2}/src/nocfo_toolkit/cli/__init__.py +0 -0
  10. {nocfo_cli-1.4.0 → nocfo_cli-1.4.2}/src/nocfo_toolkit/cli/app.py +0 -0
  11. {nocfo_cli-1.4.0 → nocfo_cli-1.4.2}/src/nocfo_toolkit/cli/commands/__init__.py +0 -0
  12. {nocfo_cli-1.4.0 → nocfo_cli-1.4.2}/src/nocfo_toolkit/cli/commands/_helpers.py +0 -0
  13. {nocfo_cli-1.4.0 → nocfo_cli-1.4.2}/src/nocfo_toolkit/cli/commands/accounts.py +0 -0
  14. {nocfo_cli-1.4.0 → nocfo_cli-1.4.2}/src/nocfo_toolkit/cli/commands/auth.py +0 -0
  15. {nocfo_cli-1.4.0 → nocfo_cli-1.4.2}/src/nocfo_toolkit/cli/commands/businesses.py +0 -0
  16. {nocfo_cli-1.4.0 → nocfo_cli-1.4.2}/src/nocfo_toolkit/cli/commands/contacts.py +0 -0
  17. {nocfo_cli-1.4.0 → nocfo_cli-1.4.2}/src/nocfo_toolkit/cli/commands/documents.py +0 -0
  18. {nocfo_cli-1.4.0 → nocfo_cli-1.4.2}/src/nocfo_toolkit/cli/commands/files.py +0 -0
  19. {nocfo_cli-1.4.0 → nocfo_cli-1.4.2}/src/nocfo_toolkit/cli/commands/invoices.py +0 -0
  20. {nocfo_cli-1.4.0 → nocfo_cli-1.4.2}/src/nocfo_toolkit/cli/commands/products.py +0 -0
  21. {nocfo_cli-1.4.0 → nocfo_cli-1.4.2}/src/nocfo_toolkit/cli/commands/purchase_invoices.py +0 -0
  22. {nocfo_cli-1.4.0 → nocfo_cli-1.4.2}/src/nocfo_toolkit/cli/commands/reports.py +0 -0
  23. {nocfo_cli-1.4.0 → nocfo_cli-1.4.2}/src/nocfo_toolkit/cli/commands/schema.py +0 -0
  24. {nocfo_cli-1.4.0 → nocfo_cli-1.4.2}/src/nocfo_toolkit/cli/commands/tags.py +0 -0
  25. {nocfo_cli-1.4.0 → nocfo_cli-1.4.2}/src/nocfo_toolkit/cli/commands/user.py +0 -0
  26. {nocfo_cli-1.4.0 → nocfo_cli-1.4.2}/src/nocfo_toolkit/cli/context.py +0 -0
  27. {nocfo_cli-1.4.0 → nocfo_cli-1.4.2}/src/nocfo_toolkit/cli/output.py +0 -0
  28. {nocfo_cli-1.4.0 → nocfo_cli-1.4.2}/src/nocfo_toolkit/config.py +0 -0
  29. {nocfo_cli-1.4.0 → nocfo_cli-1.4.2}/src/nocfo_toolkit/mcp/__init__.py +0 -0
  30. {nocfo_cli-1.4.0 → nocfo_cli-1.4.2}/src/nocfo_toolkit/mcp/auth.py +0 -0
  31. {nocfo_cli-1.4.0 → nocfo_cli-1.4.2}/src/nocfo_toolkit/mcp/error_handling.py +0 -0
  32. {nocfo_cli-1.4.0 → nocfo_cli-1.4.2}/src/nocfo_toolkit/mcp/http_error_capture.py +0 -0
  33. {nocfo_cli-1.4.0 → nocfo_cli-1.4.2}/src/nocfo_toolkit/mcp/middleware.py +0 -0
  34. {nocfo_cli-1.4.0 → nocfo_cli-1.4.2}/src/nocfo_toolkit/openapi.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nocfo-cli
3
- Version: 1.4.0
3
+ Version: 1.4.2
4
4
  Summary: NoCFO CLI, MCP server, and Cursor skill toolkit.
5
5
  License: MIT
6
6
  License-File: LICENSE
@@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api"
4
4
 
5
5
  [tool.poetry]
6
6
  name = "nocfo-cli"
7
- version = "1.4.0"
7
+ version = "1.4.2"
8
8
  description = "NoCFO CLI, MCP server, and Cursor skill toolkit."
9
9
  authors = ["NoCFO"]
10
10
  readme = "README.md"
@@ -13,6 +13,7 @@ from nocfo_toolkit.mcp.server import (
13
13
  MCP_OPENAPI_ROUTE_MAPS,
14
14
  apply_mcp_operation_metadata,
15
15
  apply_mcp_namespace_names,
16
+ restore_openapi_output_schema,
16
17
  )
17
18
  from nocfo_toolkit.openapi import filter_mcp_spec
18
19
 
@@ -35,6 +36,7 @@ class MCPContractValidationResult:
35
36
  def _apply_component_mcp_metadata(route: Any, component: Any) -> None:
36
37
  apply_mcp_namespace_names(route, component)
37
38
  apply_mcp_operation_metadata(route, component)
39
+ restore_openapi_output_schema(route, component)
38
40
 
39
41
 
40
42
  def _get_base_url(openapi_spec: dict[str, Any]) -> str:
@@ -146,7 +148,7 @@ def _build_result(
146
148
  def validate_openapi_mcp_contract(
147
149
  openapi_spec: dict[str, Any],
148
150
  *,
149
- validate_output: bool = True,
151
+ validate_output: bool = False,
150
152
  ) -> MCPContractValidationResult:
151
153
  """Validate that MCP-tagged operations map cleanly to FastMCP components.
152
154
 
@@ -186,7 +188,7 @@ def validate_openapi_mcp_contract(
186
188
  def assert_openapi_mcp_contract_valid(
187
189
  openapi_spec: dict[str, Any],
188
190
  *,
189
- validate_output: bool = True,
191
+ validate_output: bool = False,
190
192
  ) -> MCPContractValidationResult:
191
193
  """Assert MCP contract validity and return the validation result."""
192
194
  result = validate_openapi_mcp_contract(
@@ -32,6 +32,8 @@ from nocfo_toolkit.openapi import (
32
32
  load_openapi_spec,
33
33
  )
34
34
 
35
+ from fastmcp.utilities.openapi import extract_output_schema_from_responses
36
+
35
37
  if TYPE_CHECKING:
36
38
  from fastmcp import FastMCP
37
39
 
@@ -64,26 +66,16 @@ def _normalize_mcp_namespace_token(value: str) -> str:
64
66
  def build_mcp_component_name(
65
67
  operation_id: str, extensions: dict[str, Any] | None
66
68
  ) -> str:
67
- """Build MCP name ``<x-mcp-namespace>_<operation_id_with_dots_as_underscores>``.
69
+ """Build MCP component name from ``operation_id``.
68
70
 
69
- Namespace is read from the OpenAPI operation extension ``x-mcp-namespace`` (set in
70
- the backend via ``mcp_extend_schema``). If missing, ``operation_id`` is
71
- returned unchanged.
71
+ Namespace extension values are preserved in metadata but are not used for
72
+ display-name generation to keep backend ``operationId`` as the single source
73
+ of truth.
72
74
  """
75
+ del extensions
73
76
  if not operation_id or not str(operation_id).strip():
74
77
  return operation_id
75
- raw = (extensions or {}).get(X_MCP_NAMESPACE)
76
- if not isinstance(raw, str) or not raw.strip():
77
- return operation_id
78
- ns = _normalize_mcp_namespace_token(raw)
79
- if not ns:
80
- return operation_id
81
- # Avoid duplicating the namespace if operation_id already starts with it.
82
- # Example: operation_id "invoicing.contact.create" with ns "invoicing"
83
- # should become "invoicing_contact_create", not "invoicing_invoicing_contact_create".
84
- if operation_id.startswith(f"{ns}."):
85
- operation_id = operation_id[len(ns) + 1 :]
86
- return f"{ns}_{operation_id.replace('.', '_')}"
78
+ return operation_id.replace(".", "_")
87
79
 
88
80
 
89
81
  def _resource_uri_with_name(uri: str | AnyUrl, display_name: str) -> AnyUrl:
@@ -133,6 +125,29 @@ def apply_mcp_operation_metadata(route: Any, component: Any) -> None:
133
125
  component.meta = meta
134
126
 
135
127
 
128
+ def restore_openapi_output_schema(route: Any, component: Any) -> None:
129
+ """Restore the real OpenAPI output schema on tools for agent-facing metadata.
130
+
131
+ When ``validate_output=False`` FastMCP replaces the output schema with a
132
+ permissive fallback so that runtime responses are never rejected. This
133
+ function re-derives the accurate schema from the route and writes it back
134
+ onto the tool so that ``tools/list`` still exposes precise type information
135
+ to the agent — without enforcing it at call time.
136
+ """
137
+ if not isinstance(component, Tool):
138
+ return
139
+ responses = getattr(route, "responses", None)
140
+ if responses is None:
141
+ return
142
+ real_schema = extract_output_schema_from_responses(
143
+ responses,
144
+ getattr(route, "response_schemas", None),
145
+ getattr(route, "openapi_version", None),
146
+ )
147
+ if real_schema is not None:
148
+ component.output_schema = real_schema
149
+
150
+
136
151
  def _get_server_instructions(openapi_spec: dict[str, Any]) -> str | None:
137
152
  """Read backend-owned MCP server instructions from OpenAPI root extensions."""
138
153
  instructions = openapi_spec.get(X_NOCFO_MCP_SERVER_INSTRUCTIONS)
@@ -229,6 +244,7 @@ def create_server(
229
244
  def component_mapper(route: Any, component: Any) -> None:
230
245
  apply_mcp_namespace_names(route, component)
231
246
  apply_mcp_operation_metadata(route, component)
247
+ restore_openapi_output_schema(route, component)
232
248
  if opts.auth_mode == "oauth" and isinstance(
233
249
  component, (Tool, OpenAPIResource, OpenAPIResourceTemplate)
234
250
  ):
@@ -246,7 +262,7 @@ def create_server(
246
262
  middleware=[MCPToolErrorMiddleware()],
247
263
  route_maps=MCP_OPENAPI_ROUTE_MAPS,
248
264
  mcp_component_fn=component_mapper,
249
- validate_output=True,
265
+ validate_output=False,
250
266
  )
251
267
 
252
268
 
File without changes
File without changes