Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Comments

Close side panel

Added gRPC gnmi protocol to UTCP#82

Open
Thuraabtech wants to merge 24 commits intouniversal-tool-calling-protocol:devuniversal-tool-calling-protocol/python-utcp:devfrom
Thuraabtech:feat/gRPC_gnmi-1.0vThuraabtech/python-utcp:feat/gRPC_gnmi-1.0vCopy head branch name to clipboard
Open

Added gRPC gnmi protocol to UTCP#82
Thuraabtech wants to merge 24 commits intouniversal-tool-calling-protocol:devuniversal-tool-calling-protocol/python-utcp:devfrom
Thuraabtech:feat/gRPC_gnmi-1.0vThuraabtech/python-utcp:feat/gRPC_gnmi-1.0vCopy head branch name to clipboard

Conversation

@Thuraabtech
Copy link
Contributor

@Thuraabtech Thuraabtech commented Dec 14, 2025

This PR adds a gNMI (gRPC) protocol plugin fully compatible with UTCP 1.0 and its protocol/call-template architecture. It implements discovery, calling (unary), streaming subscribe, authentication, registration, and security alignment consistent with other UTCP plugins.

Protocol Implementation

  • Added GnmiCommunicationProtocol conforming to UTCP’s CommunicationProtocol interface

    • Discovery registers canonical gNMI operations as UTCP Tools: capabilities/get/set/subscribe at plugins/communication_protocols/gnmi/src/utcp_gnmi/gnmi_communication_protocol.py:28–61
    • Enforces TLS unless target is localhost at plugins/communication_protocols/gnmi/src/utcp_gnmi/gnmi_communication_protocol.py:16–26
    • Implements call_tool() for unary RPCs (Capabilities/Get/Set) using grpc.aio at plugins/communication_protocols/gnmi/src/utcp_gnmi/gnmi_communication_protocol.py:81–130
    • Implements call_tool_streaming() for subscribe with async iteration at plugins/communication_protocols/gnmi/src/utcp_gnmi/gnmi_communication_protocol.py:132–134 and following
  • Auth injection into gRPC metadata

    • API Key, Basic, OAuth2 (client credentials) mapped to authorization and custom keys at plugins/communication_protocols/gnmi/src/utcp_gnmi/gnmi_communication_protocol.py:86–104
    • OAuth2 helper for token retrieval at plugins/communication_protocols/gnmi/src/utcp_gnmi/gnmi_communication_protocol.py:106–134
  • Protobuf request binding

    • Fixed Get path construction ( Path.elem appended correctly) at plugins/communication_protocols/gnmi/src/utcp_gnmi/gnmi_communication_protocol.py:102–114
    • Set builds Update entries with TypedValue at plugins/communication_protocols/gnmi/src/utcp_gnmi/gnmi_communication_protocol.py:116–126
      Call Template & Serializer
  • Introduced GnmiCallTemplate with:

    • call_template_type: "gnmi" , target , use_tls , metadata , metadata_fields , operation , stub_module , message_module at plugins/communication_protocols/gnmi/src/utcp_gnmi/gnmi_call_template.py:9–17
    • operation supports "capabilities" | "get" | "set" | "subscribe" at plugins/communication_protocols/gnmi/src/utcp_gnmi/gnmi_call_template.py:15
  • Added GnmiCallTemplateSerializer that validates and serializes the template at plugins/communication_protocols/gnmi/src/utcp_gnmi/gnmi_call_template.py:19–27
    Registration

  • Registers both protocol and call template via UTCP plugin system:

    • plugins/communication_protocols/gnmi/src/utcp_gnmi/init.py:5–12
  • Packaging entry point configured:

    • plugins/communication_protocols/gnmi/pyproject.toml:47–49
      Security & Validation
  • TLS enforcement unless target is localhost / 127.0.0.1 at plugins/communication_protocols/gnmi/src/utcp_gnmi/gnmi_communication_protocol.py:16–26

  • Merges static metadata and whitelisted dynamic metadata_fields into gRPC call metadata at plugins/communication_protocols/gnmi/src/utcp_gnmi/gnmi_communication_protocol.py:73–80

  • Proper error propagation via UTCP client routing in core/src/utcp/implementations/utcp_client_implementation.py:176–197

Testing

  • Run: python -m pytest plugins/communication_protocols/gnmi/tests/test_gnmi_plugin.py -q
  • Tests:
    • Verifies manual registration and presence of tools (including subscribe )
    • Validates serializer round-trip for GnmiCallTemplate
  • Result: 2 passed; deprecation warnings only

Summary by cubic

Adds a gNMI (gRPC) communication protocol plugin for UTCP 1.0 with secure discovery, unary calls (Capabilities/Get/Set), and streaming Subscribe. Also modernizes GraphQL and socket protocols for UTCP 1.0 compatibility and better reliability.

  • New Features

    • gNMI plugin: tools for capabilities/get/set/subscribe, TLS required except localhost, auth via API key/Basic/OAuth2, dynamic metadata fields, protobuf request binding, registration and serializer, tests included.
  • Refactors

    • GraphQL: moved to UTCP 1.0 protocol interface with schema introspection discovery, HTTPS/localhost enforcement, auth and header merging, streaming wrapper, README and tests.
    • Socket (TCP): added interpret_escape_sequences flag for delimiters, clearer errors on unknown framing, test/script hardening.
    • UDP: safer tool_call_template normalization with validation and logged fallbacks.
    • Dependencies: MCP plugin updates langchain to >=0.3.27,<0.4.0.

Written for commit 9bbb819. Summary will update automatically on new commits.

h3xxit and others added 22 commits August 26, 2025 17:03
…tool-calling-protocol/dev

Add docs and update http to 1.0.2
…tool-calling-protocol/dev

Fix response json parsing when content type is wrong
…om universal-tool-calling-protocol/dev

Update CLI
…tool-calling-protocol/dev

Plugin updates
…tool-calling-protocol/dev

  Add WebSocket transport implementation for real-time communication …
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings December 14, 2025 18:52
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds a gNMI (gRPC Network Management Interface) communication protocol plugin to UTCP 1.0, alongside improvements to existing protocol plugins (GraphQL, TCP/UDP sockets) and dependency updates. The gNMI plugin enables network device management via gRPC, implementing discovery, unary calls (capabilities/get/set), streaming subscriptions, and authentication compatible with UTCP's architecture.

Key Changes:

  • New gNMI protocol plugin with support for TLS, OAuth2/Basic/API key auth, and streaming subscriptions
  • GraphQL plugin refactored to UTCP 1.0 architecture with proper registration and improved header handling
  • Socket protocols enhanced with better exception handling, delimiter escape sequence control, and error validation
  • Dependency relaxation for MCP plugin's langchain requirement

Reviewed changes

Copilot reviewed 16 out of 16 changed files in this pull request and generated 15 comments.

Show a summary per file
File Description
plugins/communication_protocols/gnmi/src/utcp_gnmi/gnmi_communication_protocol.py Core gNMI protocol implementation with gRPC channel management, metadata/auth handling, and operation routing
plugins/communication_protocols/gnmi/src/utcp_gnmi/gnmi_call_template.py gNMI call template and serializer definitions
plugins/communication_protocols/gnmi/src/utcp_gnmi/__init__.py Plugin registration entry point
plugins/communication_protocols/gnmi/tests/test_gnmi_plugin.py Tests for manual registration and serializer roundtrip
plugins/communication_protocols/gnmi/pyproject.toml Package configuration with gRPC/protobuf dependencies
plugins/communication_protocols/gql/src/utcp_gql/gql_communication_protocol.py Refactored GraphQL protocol to UTCP 1.0 with improved auth and header field mapping
plugins/communication_protocols/gql/src/utcp_gql/gql_call_template.py Added auth serialization/validation to GraphQL provider
plugins/communication_protocols/gql/src/utcp_gql/__init__.py Plugin registration for GraphQL protocol
plugins/communication_protocols/gql/tests/test_graphql_protocol.py New comprehensive tests for GraphQL protocol registration and tool calling
plugins/communication_protocols/gql/README.md Documentation for GraphQL plugin usage
plugins/communication_protocols/socket/src/utcp_socket/tcp_communication_protocol.py Enhanced delimiter handling with escape sequence interpretation flag and unknown framing strategy validation
plugins/communication_protocols/socket/src/utcp_socket/tcp_call_template.py Added interpret_escape_sequences field for delimiter configuration
plugins/communication_protocols/socket/src/utcp_socket/udp_communication_protocol.py Improved exception handling with specific exception types and logging
plugins/communication_protocols/socket/tests/test_tcp_communication_protocol.py Added explanatory comments for exception handling in test server
plugins/communication_protocols/mcp/pyproject.toml Relaxed langchain dependency from exact pin to range constraint
scripts/socket_sanity.py Added explanatory comments for exception handling and updated success message
Comments suppressed due to low confidence (1)

plugins/communication_protocols/gnmi/src/utcp_gnmi/gnmi_communication_protocol.py:144

  • Variable mode is not used.
            mode = tool_args.get("mode", "stream").upper()

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

from utcp.data.auth_implementations.basic_auth import BasicAuth
from utcp.data.auth_implementations.oauth2_auth import OAuth2Auth

class GnmiCommunicationProtocol(CommunicationProtocol):
Copy link

Copilot AI Dec 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The class is missing an __init__ method to initialize the _oauth_tokens dictionary that would be used for OAuth2 token caching (referenced in comment ID 006). Without this initialization, the _handle_oauth2 method will fail with an AttributeError when it tries to access self._oauth_tokens. Add an __init__ method that initializes self._oauth_tokens = {}.

Suggested change
class GnmiCommunicationProtocol(CommunicationProtocol):
class GnmiCommunicationProtocol(CommunicationProtocol):
def __init__(self):
super().__init__()
self._oauth_tokens = {}

Copilot uses AI. Check for mistakes.
{ name = "UTCP Contributors" },
]
description = "UTCP gNMI communication protocol plugin over gRPC"
readme = "README.md"
Copy link

Copilot AI Dec 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The pyproject.toml references a README.md file that does not exist in the gnmi plugin directory. This will cause packaging issues. Either create a README.md file with appropriate documentation (similar to the GraphQL plugin's README) or remove the readme field from the project configuration.

Suggested change
readme = "README.md"

Copilot uses AI. Check for mistakes.
Comment on lines 161 to 218
async def call_tool_streaming(self, caller, tool_name: str, tool_args: Dict[str, Any], tool_call_template: CallTemplate) -> AsyncGenerator[Any, None]:
if not isinstance(tool_call_template, GnmiCallTemplate):
raise ValueError("GnmiCommunicationProtocol can only be used with GnmiCallTemplate")
if tool_call_template.operation != "subscribe":
result = await self.call_tool(caller, tool_name, tool_args, tool_call_template)
yield result
return
grpc = importlib.import_module("grpc")
aio = importlib.import_module("grpc.aio")
json_format = importlib.import_module("google.protobuf.json_format")
stub_mod = importlib.import_module(tool_call_template.stub_module)
msg_mod = importlib.import_module(tool_call_template.message_module)
target = tool_call_template.target
if tool_call_template.use_tls:
creds = grpc.ssl_channel_credentials()
channel = aio.secure_channel(target, creds)
else:
channel = aio.insecure_channel(target)
stub = None
for attr in dir(stub_mod):
if attr.endswith("Stub"):
stub_cls = getattr(stub_mod, attr)
stub = stub_cls(channel)
break
if stub is None:
raise ValueError("gNMI stub not found in stub_module")
metadata: List[tuple[str, str]] = []
if tool_call_template.metadata:
metadata.extend([(k, v) for k, v in tool_call_template.metadata.items()])
if tool_call_template.metadata_fields:
for k in tool_call_template.metadata_fields:
if k in tool_args:
metadata.append((k, str(tool_args[k])))
if tool_call_template.auth:
if isinstance(tool_call_template.auth, ApiKeyAuth):
if tool_call_template.auth.api_key:
metadata.append((tool_call_template.auth.var_name or "authorization", tool_call_template.auth.api_key))
elif isinstance(tool_call_template.auth, BasicAuth):
import base64
token = base64.b64encode(f"{tool_call_template.auth.username}:{tool_call_template.auth.password}".encode()).decode()
metadata.append(("authorization", f"Basic {token}"))
elif isinstance(tool_call_template.auth, OAuth2Auth):
token = await self._handle_oauth2(tool_call_template.auth)
metadata.append(("authorization", f"Bearer {token}"))
req = getattr(msg_mod, "SubscribeRequest")()
sub_list = getattr(msg_mod, "SubscriptionList")()
paths = tool_args.get("paths", [])
for p in paths:
path_msg = getattr(msg_mod, "Path")()
for elem in [e for e in p.strip("/").split("/") if e]:
pe = getattr(msg_mod, "PathElem")(name=elem)
path_msg.elem.append(pe)
sub = getattr(msg_mod, "Subscription")(path=path_msg)
sub_list.subscription.append(sub)
req.subscribe.CopyFrom(sub_list)
call = stub.Subscribe(req, metadata=metadata)
async for resp in call:
yield json_format.MessageToDict(resp)
Copy link

Copilot AI Dec 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The gRPC channel created on lines 174-178 is never closed. gRPC channels should be properly closed after use to avoid resource leaks. Consider wrapping the channel usage in a try-finally block or using async context manager pattern to ensure the channel is closed even if the streaming operation is interrupted.

Copilot uses AI. Check for mistakes.
req = getattr(msg_mod, "SubscribeRequest")()
sub_list = getattr(msg_mod, "SubscriptionList")()
mode = tool_args.get("mode", "stream").upper()
sub_list.mode = getattr(msg_mod, "SubscriptionList".upper(), None) or 0
Copy link

Copilot AI Dec 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The getattr(msg_mod, "SubscriptionList".upper(), None) is incorrect and will always evaluate to None. It attempts to get an attribute named "SUBSCRIPTIONLIST" (all uppercase string literal) instead of the subscription mode enum value. This should likely retrieve the subscription mode from the message module based on the mode variable, such as getattr(msg_mod, f"SubscriptionList.Mode.{mode}", 0) or similar, depending on the protobuf structure.

Suggested change
sub_list.mode = getattr(msg_mod, "SubscriptionList".upper(), None) or 0
# Set the mode enum value correctly
try:
mode_enum = getattr(getattr(msg_mod, "SubscriptionList"), "Mode")
sub_list.mode = getattr(mode_enum, mode, 0)
except AttributeError:
sub_list.mode = 0

Copilot uses AI. Check for mistakes.
Comment on lines 20 to 22
if manual_call_template.use_tls:
pass
else:
Copy link

Copilot AI Dec 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The empty pass statement in the TLS branch serves no purpose and makes the code harder to read. Consider removing it or adding a comment explaining why TLS is always allowed.

Suggested change
if manual_call_template.use_tls:
pass
else:
if not manual_call_template.use_tls:

Copilot uses AI. Check for mistakes.
@@ -0,0 +1,27 @@
from typing import Optional, Dict, List, Literal
from pydantic import Field
Copy link

Copilot AI Dec 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Import of 'Field' is not used.

Suggested change
from pydantic import Field

Copilot uses AI. Check for mistakes.
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

7 issues found across 16 files

Prompt for AI agents (all 7 issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="plugins/communication_protocols/gql/README.md">

<violation number="1" location="plugins/communication_protocols/gql/README.md:12">
P1: Incorrect package name in installation instructions. The package name is `utcp-gql` (as defined in pyproject.toml), not `gql`. The `gql` package is a dependency, not the plugin itself.</violation>
</file>

<file name="plugins/communication_protocols/gnmi/src/utcp_gnmi/gnmi_communication_protocol.py">

<violation number="1" location="plugins/communication_protocols/gnmi/src/utcp_gnmi/gnmi_communication_protocol.py:23">
P1: [gpt-5.2] The localhost check is bypassable via `startswith(...)` (e.g., `localhost.evil.com`). Parse the hostname and compare exact loopback hosts before allowing insecure channels.</violation>

<violation number="2" location="plugins/communication_protocols/gnmi/src/utcp_gnmi/gnmi_communication_protocol.py:103">
P2: Resource leak: gRPC channel is created but never closed. Consider using `async with` context manager or explicitly calling `await channel.close()` after the RPC call completes to prevent connection leaks.</violation>

<violation number="3" location="plugins/communication_protocols/gnmi/src/utcp_gnmi/gnmi_communication_protocol.py:137">
P1: [gpt-5.2] TypedValue(json_val=...) is constructed with a Python string, which is not valid for gNMI JSON bytes fields and can break Set RPCs. Serialize values to real JSON bytes (prefer json_ietf_val when available) or fall back to string_val.</violation>

<violation number="4" location="plugins/communication_protocols/gnmi/src/utcp_gnmi/gnmi_communication_protocol.py:145">
P1: [gpt-5.2] Subscribe `mode` is effectively ignored due to an incorrect `getattr(...)` target; this will likely always set mode to 0. Map the requested mode to the generated enum constant (with a safe default).</violation>
</file>

<file name="plugins/communication_protocols/gql/src/utcp_gql/gql_communication_protocol.py">

<violation number="1" location="plugins/communication_protocols/gql/src/utcp_gql/gql_communication_protocol.py:193">
P2: [gpt-5.2] Declaring every variable as `String` can break calls for non-String argument types (ID/Int/Boolean/input objects). Infer variable types from the fetched schema (or otherwise avoid hard-coding String).</violation>

<violation number="2" location="plugins/communication_protocols/gql/src/utcp_gql/gql_communication_protocol.py:210">
P2: [gpt-5.2] call_tool_streaming currently isn’t truly streaming (it yields once). At minimum, fail fast for subscription operation_type or implement real GraphQL subscription streaming.</violation>
</file>

Reply to cubic to teach it or ask questions. Re-run a review with @cubic-dev-ai review this PR

plugins/communication_protocols/gql/README.md Show resolved Hide resolved
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 17 out of 17 changed files in this pull request and generated 10 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +14 to +16
class GnmiCommunicationProtocol(CommunicationProtocol):
def __init__(self):
self._oauth_tokens: Dict[str, Dict[str, Any]] = {}
Copy link

Copilot AI Dec 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The GnmiCommunicationProtocol class is missing a close() method. The GraphQL protocol implements this method (line 228-229 in gql_communication_protocol.py) to clean up OAuth tokens. For consistency and proper resource cleanup, the gNMI protocol should also implement this method.

Copilot uses AI. Check for mistakes.
Comment on lines +177 to +179
raise ValueError("Unsupported gNMI operation")
else:
raise ValueError("Unsupported gNMI operation")
Copy link

Copilot AI Dec 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error message "Unsupported gNMI operation" is generic and doesn't indicate what operation was provided. Consider including the actual operation value in the error message for easier debugging, e.g., f"Unsupported gNMI operation: {op}".

Suggested change
raise ValueError("Unsupported gNMI operation")
else:
raise ValueError("Unsupported gNMI operation")
raise ValueError(f"Unsupported gNMI operation: {op}")
else:
raise ValueError(f"Unsupported gNMI operation: {op}")

Copilot uses AI. Check for mistakes.
Comment on lines 102 to 105
except (UtcpSerializerValidationError, ValueError) as e:
# Fallback to manual template if validation fails, but log details
logger.exception("Failed to validate existing tool_call_template; falling back to manual template")
normalized["tool_call_template"] = manual_call_template
Copy link

Copilot AI Dec 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While catching specific exception types (UtcpSerializerValidationError, ValueError) is better than a bare except Exception, you should not suppress the variable e in the except clause. The caught exception is logged via logger.exception() which will include the traceback, but explicitly using the exception variable would be more clear. Also, the except clause on line 112 is inconsistent - it only catches UtcpSerializerValidationError, not ValueError. Consider standardizing the exception handling across both branches.

Copilot uses AI. Check for mistakes.
Comment on lines +14 to +31
@pytest.mark.asyncio
async def test_register_manual_and_tools():
register()
client = await UtcpClient.create(config={
"manual_call_templates": [
{
"name": "routerA",
"call_template_type": "gnmi",
"target": "localhost:50051",
"use_tls": False,
"operation": "get"
}
]
})
tools = await client.config.tool_repository.get_tools()
names = [t.name for t in tools]
assert any(n.startswith("routerA.") for n in names)
assert any(n.endswith("subscribe") for n in names)
Copy link

Copilot AI Dec 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test suite lacks coverage for the core call_tool and call_tool_streaming methods in GnmiCommunicationProtocol. While test_register_manual_and_tools validates tool registration, there are no tests for actual RPC calls (capabilities, get, set) or streaming subscribe. The GraphQL plugin includes comprehensive tests that call tools (lines 103-110 in test_graphql_protocol.py). Consider adding similar tests with mocked gRPC stubs to verify the request building and response handling logic.

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot l request to apply changes based on this feedback to the current PR

@Thuraabtech
Copy link
Contributor Author

@h3xxit review these changes and let me know if there is something to update.

@h3xxit
Copy link
Member

h3xxit commented Feb 15, 2026

@cubic-dev-ai review this PR

@cubic-dev-ai
Copy link
Contributor

cubic-dev-ai bot commented Feb 15, 2026

@cubic-dev-ai review this PR

@h3xxit I have started the AI code review. It will take a few minutes to complete.

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

3 issues found across 17 files

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="plugins/communication_protocols/gql/src/utcp_gql/gql_communication_protocol.py">

<violation number="1" location="plugins/communication_protocols/gql/src/utcp_gql/gql_communication_protocol.py:202">
P2: GraphQL variable types are inferred only from Python runtime values and default to String, which will break schemas expecting lists, input objects, or ID types. This can cause GraphQL validation errors for many operations.</violation>

<violation number="2" location="plugins/communication_protocols/gql/src/utcp_gql/gql_communication_protocol.py:209">
P2: GraphQL queries built here omit a selection set, which will fail validation for any root field that returns an object/list type. Since the protocol no longer accepts a caller-provided query, object-returning tools cannot be executed successfully.</violation>
</file>

<file name="plugins/communication_protocols/gnmi/src/utcp_gnmi/gnmi_communication_protocol.py">

<violation number="1" location="plugins/communication_protocols/gnmi/src/utcp_gnmi/gnmi_communication_protocol.py:147">
P2: gNMI path parsing ignores list keys, so paths like interface[name=eth0] are encoded with the brackets in PathElem.name and no key map, preventing access to specific list instances.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

elif isinstance(v, float):
t = "Float"
else:
t = "String"
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Feb 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: GraphQL variable types are inferred only from Python runtime values and default to String, which will break schemas expecting lists, input objects, or ID types. This can cause GraphQL validation errors for many operations.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At plugins/communication_protocols/gql/src/utcp_gql/gql_communication_protocol.py, line 202:

<comment>GraphQL variable types are inferred only from Python runtime values and default to String, which will break schemas expecting lists, input objects, or ID types. This can cause GraphQL validation errors for many operations.</comment>

<file context>
@@ -39,98 +58,172 @@ async def _handle_oauth2(self, auth: OAuth2Auth) -> str:
+                elif isinstance(v, float):
+                    t = "Float"
+                else:
+                    t = "String"
+                defs.append(f"${k}: {t}")
+            arg_str = ", ".join(defs)
</file context>
Fix with Cubic

arg_pass = f"({arg_pass})" if arg_pass else ""
gql_str = f"{op_type} {var_defs} {{ {tool_name}{arg_pass} }}"

gql_str = f"{op_type} {var_defs} {{ {base_tool_name}{arg_pass} }}"
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Feb 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: GraphQL queries built here omit a selection set, which will fail validation for any root field that returns an object/list type. Since the protocol no longer accepts a caller-provided query, object-returning tools cannot be executed successfully.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At plugins/communication_protocols/gql/src/utcp_gql/gql_communication_protocol.py, line 209:

<comment>GraphQL queries built here omit a selection set, which will fail validation for any root field that returns an object/list type. Since the protocol no longer accepts a caller-provided query, object-returning tools cannot be executed successfully.</comment>

<file context>
@@ -39,98 +58,172 @@ async def _handle_oauth2(self, auth: OAuth2Auth) -> str:
             arg_pass = f"({arg_pass})" if arg_pass else ""
-            gql_str = f"{op_type} {var_defs} {{ {tool_name}{arg_pass} }}"
+
+            gql_str = f"{op_type} {var_defs} {{ {base_tool_name}{arg_pass} }}"
             document = gql_query(gql_str)
-            result = await session.execute(document, variable_values=tool_args)
</file context>
Fix with Cubic

for p in paths:
path_msg = getattr(msg_mod, "Path")()
for elem in [e for e in p.strip("/").split("/") if e]:
pe = getattr(msg_mod, "PathElem")(name=elem)
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Feb 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: gNMI path parsing ignores list keys, so paths like interface[name=eth0] are encoded with the brackets in PathElem.name and no key map, preventing access to specific list instances.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At plugins/communication_protocols/gnmi/src/utcp_gnmi/gnmi_communication_protocol.py, line 147:

<comment>gNMI path parsing ignores list keys, so paths like interface[name=eth0] are encoded with the brackets in PathElem.name and no key map, preventing access to specific list instances.</comment>

<file context>
@@ -0,0 +1,299 @@
+                for p in paths:
+                    path_msg = getattr(msg_mod, "Path")()
+                    for elem in [e for e in p.strip("/").split("/") if e]:
+                        pe = getattr(msg_mod, "PathElem")(name=elem)
+                        path_msg.elem.append(pe)
+                    req.path.append(path_msg)
</file context>
Fix with Cubic

Comment on lines +18 to +24
def _load_gnmi_modules(self, tool_call_template: GnmiCallTemplate) -> tuple[Any, Any, Any, Any, Any]:
grpc = importlib.import_module("grpc")
aio = importlib.import_module("grpc.aio")
json_format = importlib.import_module("google.protobuf.json_format")
stub_mod = importlib.import_module(tool_call_template.stub_module)
msg_mod = importlib.import_module(tool_call_template.message_module)
return grpc, aio, json_format, stub_mod, msg_mod
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are these modules imported here? Can some of these not be done at the top level?

)
tools.append(tool)

manual = UtcpManual(manual_version="1.0.0", tools=tools)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The manual version should come from the manual endpoint. You can be opinionated on how the UtcpManual should be transfered accross the network, and ideally keept the transfered object as close to the original UtcpManual as possible, so that the user on the other side can basically just expose an endpoint that returns this UtcpManual json/object

@h3xxit
Copy link
Member

h3xxit commented Feb 15, 2026

@Thuraabtech please take a look at the comments from me and the ones from cubic. Also make sure to merge dev and solve the merge conflicts.

Once that is addressed the rest looks good! Ping me again and I will merge and publish this. Thank you very much for the contribution!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

Morty Proxy This is a proxified and sanitized view of the page, visit original site.