Skip to content

Conversation

@Thuraabtech
Copy link
Contributor

@Thuraabtech Thuraabtech commented Nov 6, 2025

This PR migrates the GraphQL plugin to be fully compatible with UTCP 1.0's protocol and call-template architecture. The plugin now implements proper discovery, calling, authentication, and registration flows according to UTCP specifications.

Protocol Implementation

  • Added GraphQLCommunicationProtocol conforming to UTCP 1.0's CommunicationProtocol interface
  • Implemented discover_tools() via schema introspection (queries/mutations/subscriptions → UTCP Tools)
  • Implemented call_tool() with variable injection and selection set support
  • Added stream_tool() stub for future subscription support

Call Template & Serializer

  • Introduced GraphQLProvider model with url, operation_type, headers, header_fields, and optional auth
  • Added GraphQLProviderSerializer that:
  • Validates provider configuration
  • Builds GraphQL client and transport
  • Supports ApiKeyAuth, BasicAuth, and OAuth2Auth (client credentials with token caching)
  • Merges static and whitelisted dynamic headers

Registration

python

def register():
    register_communication_protocol("graphql", GraphQLCommunicationProtocol)
    register_call_template("graphql", GraphQLProviderSerializer)

Security & Validation

  • Enforces secure endpoints: https:// or http://localhost/http://127.0.0.1 only
  • Minimal selection sets to prevent oversized responses
  • Proper error propagation through UTCP

Testing

Run: python -m pytest plugins/communication_protocols/gql/tests/test_graphql_protocol.py -v

  • Uses a fake GraphQL client/transport to verify discovery and tool calls for query and mutation.

  • Monkeypatches query building to avoid strict parsing in the test environment.

  • Ran the test with python -m pytest -q and confirmed it passes.


Summary by cubic

Migrated the GraphQL plugin to UTCP 1.0 with schema-based discovery, authenticated calls, and safe header handling. Also aligned UDP/TCP socket plugins with the 1.0 call-template protocol, added tests/docs, and updated CI to run them.

  • New Features

    • Implemented GraphQLCommunicationProtocol with introspection-based discovery (queries, mutations, subscriptions).
    • Added call_tool with variable support and a single-chunk streaming fallback.
    • Introduced GraphQLProvider + serializer; supports ApiKey, Basic, OAuth2 (token caching) and merges static headers with whitelisted dynamic header_fields.
    • Enforced secure endpoints (HTTPS or localhost) and improved error propagation.
    • Registered the protocol and call template via utcp_gql.register() and updated README with usage.
    • Updated UDP/TCP transports to UTCP 1.0 manual/call-template flow; added unit tests and a socket sanity script; CI now runs socket tests.
  • Dependencies

    • Pinned langchain==0.3.27 for MCP plugin tests.

Written for commit 3aed349. Summary will update automatically on new commits.

h3xxit and others added 18 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 …
Copilot AI review requested due to automatic review settings November 6, 2025 21:49
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 three new communication protocol plugins (Socket/UDP, Socket/TCP, and GraphQL) to UTCP 1.0, migrating them from the legacy ClientTransportInterface to the new CommunicationProtocol interface. The changes include comprehensive test coverage, documentation, and CI integration.

  • Implements UDP and TCP socket protocols with support for multiple framing strategies (delimiter, fixed-length, stream)
  • Implements GraphQL protocol with schema introspection and authentication support
  • Adds legacy tool_provider to tool_call_template conversion logic for backward compatibility
  • Updates CI workflow to include socket plugin tests

Reviewed Changes

Copilot reviewed 18 out of 18 changed files in this pull request and generated 22 comments.

Show a summary per file
File Description
plugins/communication_protocols/socket/src/utcp_socket/udp_communication_protocol.py Migrated UDP transport to CommunicationProtocol interface with call template normalization
plugins/communication_protocols/socket/src/utcp_socket/tcp_communication_protocol.py Migrated TCP transport to CommunicationProtocol interface with framing strategy fixes
plugins/communication_protocols/socket/src/utcp_socket/udp_call_template.py Added UDPProvider call template and serializer
plugins/communication_protocols/socket/src/utcp_socket/tcp_call_template.py Added TCPProvider call template and serializer with delimiter documentation updates
plugins/communication_protocols/socket/src/utcp_socket/__init__.py Plugin registration for TCP/UDP protocols and call templates
plugins/communication_protocols/socket/tests/test_udp_communication_protocol.py Unit tests for UDP protocol with legacy conversion scenarios
plugins/communication_protocols/socket/tests/test_tcp_communication_protocol.py Unit tests for TCP protocol with legacy conversion scenarios
plugins/communication_protocols/socket/pyproject.toml Added plugin entry point configuration
plugins/communication_protocols/socket/README.md Added comprehensive setup and testing documentation
plugins/communication_protocols/gql/src/utcp_gql/gql_communication_protocol.py Migrated GraphQL transport to CommunicationProtocol with header field support
plugins/communication_protocols/gql/src/utcp_gql/gql_call_template.py Added GraphQLProvider call template with auth serialization
plugins/communication_protocols/gql/src/utcp_gql/__init__.py Plugin registration for GraphQL protocol
plugins/communication_protocols/gql/tests/test_graphql_protocol.py Integration tests for GraphQL protocol with mocked schema
plugins/communication_protocols/gql/README.md Added plugin documentation with usage examples
plugins/communication_protocols/mcp/pyproject.toml Added langchain dependency for MCP plugin
socket_plugin_test.py Sanity check script for plugin registration
scripts/socket_sanity.py End-to-end integration test with mock servers
.github/workflows/test.yml Updated CI to include socket plugin tests

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

Comment on lines +100 to +118
try:
if "tool_call_template" in normalized and normalized["tool_call_template"] is not None:
try:
ctpl = CallTemplateSerializer().validate_dict(normalized["tool_call_template"]) # type: ignore
normalized["tool_call_template"] = ctpl
except Exception:
normalized["tool_call_template"] = manual_call_template
elif "tool_provider" in normalized and normalized["tool_provider"] is not None:
try:
ctpl = TCPProviderSerializer().validate_dict(normalized["tool_provider"]) # type: ignore
normalized.pop("tool_provider", None)
normalized["tool_call_template"] = ctpl
except Exception:
normalized.pop("tool_provider", None)
normalized["tool_call_template"] = manual_call_template
else:
normalized["tool_call_template"] = manual_call_template
except Exception:
normalized["tool_call_template"] = manual_call_template
Copy link

Copilot AI Nov 6, 2025

Choose a reason for hiding this comment

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

Multiple broad except Exception blocks catch and suppress all exceptions without re-raising or providing specific error details. This makes debugging difficult. Consider either: (1) catching specific exception types, (2) logging the full traceback with logger.exception(), or (3) re-raising the exception after logging to allow proper error propagation.

Copilot uses AI. Check for mistakes.
elif provider.framing_strategy == "delimiter":
# Add delimiter after the message
delimiter = provider.message_delimiter or "\\x00"
delimiter = provider.message_delimiter or "\x00"
Copy link

Copilot AI Nov 6, 2025

Choose a reason for hiding this comment

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

The default delimiter has been changed from "\\x00" (escaped backslash) to "\x00" (null character). This changes the semantic meaning - the old code expected the literal string \x00 (4 characters: backslash, x, 0, 0), while the new code expects a single null byte. Ensure this is intentional and that all servers using this protocol are updated accordingly, as this is a breaking change in the wire protocol.

Suggested change
delimiter = provider.message_delimiter or "\x00"
delimiter = provider.message_delimiter or "\\x00"

Copilot uses AI. Check for mistakes.
"utcp>=1.0",
"mcp-use>=1.3"
"mcp-use>=1.3",
"langchain==0.3.27",
Copy link

Copilot AI Nov 6, 2025

Choose a reason for hiding this comment

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

The langchain==0.3.27 dependency appears to be pinned to a specific patch version. Using exact version pins (==) rather than compatible version ranges (e.g., >=0.3.27,<0.4.0) can cause dependency conflicts and makes it harder to get security updates. Consider using a more flexible version specification unless there's a specific reason for the exact pin.

Suggested change
"langchain==0.3.27",
"langchain>=0.3.27,<0.4.0",

Copilot uses AI. Check for mistakes.
Comment on lines +6 to +40
Getting Started

Installation

```bash
pip install gql
```

Registration

```python
import utcp_gql
utcp_gql.register()
```

How To Use

- Ensure the plugin is imported and registered: `import utcp_gql; utcp_gql.register()`.
- Add a manual in your client config:
```json
{
"name": "my_graph",
"call_template_type": "graphql",
"url": "https://your.graphql/endpoint",
"operation_type": "query",
"headers": { "x-client": "utcp" },
"header_fields": ["x-session-id"]
}
```
- Call a tool:
```python
await client.call_tool("my_graph.someQuery", {"id": "123", "x-session-id": "abc"})
```

Notes
Copy link

Copilot AI Nov 6, 2025

Choose a reason for hiding this comment

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

Section headers "Getting Started", "Installation", "Registration", "How To Use", and "Notes" are missing heading markers. They should be formatted with ## to render properly as section headers in Markdown (e.g., ## Getting Started, ## Installation, etc.).

Copilot uses AI. Check for mistakes.
Comment on lines +94 to +116
normalized = dict(tool_data)
try:
if "tool_call_template" in normalized and normalized["tool_call_template"] is not None:
# Validate via generic CallTemplate serializer (type-dispatched)
try:
ctpl = CallTemplateSerializer().validate_dict(normalized["tool_call_template"]) # type: ignore
normalized["tool_call_template"] = ctpl
except Exception:
# Fallback to manual template if validation fails
normalized["tool_call_template"] = manual_call_template
elif "tool_provider" in normalized and normalized["tool_provider"] is not None:
# Convert legacy provider -> call template
try:
ctpl = UDPProviderSerializer().validate_dict(normalized["tool_provider"]) # type: ignore
normalized.pop("tool_provider", None)
normalized["tool_call_template"] = ctpl
except Exception:
normalized.pop("tool_provider", None)
normalized["tool_call_template"] = manual_call_template
else:
normalized["tool_call_template"] = manual_call_template
except Exception:
normalized["tool_call_template"] = manual_call_template
Copy link

Copilot AI Nov 6, 2025

Choose a reason for hiding this comment

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

Multiple broad except Exception blocks catch and suppress all exceptions without re-raising or providing specific error details. This makes debugging difficult. Consider either: (1) catching specific exception types, (2) logging the full traceback with logger.exception(), or (3) re-raising the exception after logging to allow proper error propagation.

Copilot uses AI. Check for mistakes.
from utcp_gql.gql_call_template import GraphQLProvider
from utcp_gql import gql_communication_protocol as gql_module

from utcp.data.tool import Tool
Copy link

Copilot AI Nov 6, 2025

Choose a reason for hiding this comment

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

Import of 'Tool' is not used.

Suggested change
from utcp.data.tool import Tool

Copilot uses AI. Check for mistakes.
parsed = None
try:
parsed = json.loads(msg)
except Exception:
Copy link

Copilot AI Nov 6, 2025

Choose a reason for hiding this comment

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

'except' clause does nothing but pass and there is no explanatory comment.

Suggested change
except Exception:
except Exception:
# Ignore JSON parsing errors; non-JSON input will be handled below

Copilot uses AI. Check for mistakes.
finally:
try:
conn.shutdown(socket.SHUT_RDWR)
except Exception:
Copy link

Copilot AI Nov 6, 2025

Choose a reason for hiding this comment

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

'except' clause does nothing but pass and there is no explanatory comment.

Suggested change
except Exception:
except Exception:
# Ignore errors if socket is already closed or shutdown fails

Copilot uses AI. Check for mistakes.
try:
# Read any incoming data to simulate request handling
await reader.read(1024)
except Exception:
Copy link

Copilot AI Nov 6, 2025

Choose a reason for hiding this comment

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

'except' clause does nothing but pass and there is no explanatory comment.

Suggested change
except Exception:
except Exception:
# Ignore exceptions during read (e.g., client disconnects), as this is a test server.

Copilot uses AI. Check for mistakes.
try:
writer.close()
await writer.wait_closed()
except Exception:
Copy link

Copilot AI Nov 6, 2025

Choose a reason for hiding this comment

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

'except' clause does nothing but pass and there is no explanatory comment.

Suggested change
except Exception:
except Exception:
# Ignore exceptions during writer close; connection may already be closed or in error state.

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.

3 issues found across 18 files

Prompt for AI agents (all 3 issues)

Understand the root cause of the following 3 issues and fix them.


<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:189">
Header fields copied from tool_args are still included when constructing the GraphQL query, so values like `x-session-id` become invalid GraphQL variables/arguments and the call fails. Filter out header_fields before generating arg_str/arg_pass.</violation>
</file>

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

<violation number="1" location="plugins/communication_protocols/socket/README.md:16">
This editable install command points pip at a PyPI package named `core` instead of the local `./core` directory, so the setup instructions fail. Please prefix the path with ./ to target the local project.</violation>
</file>

<file name="plugins/communication_protocols/mcp/pyproject.toml">

<violation number="1" location="plugins/communication_protocols/mcp/pyproject.toml:19">
`langchain==0.3.27` is added as a dependency, but there are no imports or references to `langchain` anywhere in the repo. Pulling in this large dependency without usage bloats installations and risks conflicts from its transitive requirements. Please drop it unless there is code that actually depends on it.</violation>
</file>

React with 👍 or 👎 to teach cubic. Mention @cubic-dev-ai to give feedback, ask questions, or re-run the review.

# Strip manual prefix if present (client prefixes at save time)
base_tool_name = tool_name.split(".", 1)[-1] if "." in tool_name else tool_name

arg_str = ", ".join(f"${k}: String" for k in tool_args.keys())
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Nov 6, 2025

Choose a reason for hiding this comment

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

Header fields copied from tool_args are still included when constructing the GraphQL query, so values like x-session-id become invalid GraphQL variables/arguments and the call fails. Filter out header_fields before generating arg_str/arg_pass.

Prompt for AI agents
Address the following comment on plugins/communication_protocols/gql/src/utcp_gql/gql_communication_protocol.py at line 189:

<comment>Header fields copied from tool_args are still included when constructing the GraphQL query, so values like `x-session-id` become invalid GraphQL variables/arguments and the call fails. Filter out header_fields before generating arg_str/arg_pass.</comment>

<file context>
@@ -39,98 +57,155 @@ async def _handle_oauth2(self, auth: OAuth2Auth) -&gt; str:
+            # Strip manual prefix if present (client prefixes at save time)
+            base_tool_name = tool_name.split(&quot;.&quot;, 1)[-1] if &quot;.&quot; in tool_name else tool_name
+
+            arg_str = &quot;, &quot;.join(f&quot;${k}: String&quot; for k in tool_args.keys())
             var_defs = f&quot;({arg_str})&quot; if arg_str else &quot;&quot;
-            arg_pass = &#39;, &#39;.join(f&quot;{k}: ${k}&quot; for k in tool_args.keys())
</file context>
Fix with Cubic


```bash
pip install -e "core[dev]"
pip install -e plugins/communication_protocols/socket[dev]
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Nov 6, 2025

Choose a reason for hiding this comment

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

This editable install command points pip at a PyPI package named core instead of the local ./core directory, so the setup instructions fail. Please prefix the path with ./ to target the local project.

Prompt for AI agents
Address the following comment on plugins/communication_protocols/socket/README.md at line 16:

<comment>This editable install command points pip at a PyPI package named `core` instead of the local `./core` directory, so the setup instructions fail. Please prefix the path with ./ to target the local project.</comment>

<file context>
@@ -1 +1,44 @@
+
+```bash
+pip install -e &quot;core[dev]&quot;
+pip install -e plugins/communication_protocols/socket[dev]
+```
+
</file context>
Fix with Cubic

"utcp>=1.0",
"mcp-use>=1.3"
"mcp-use>=1.3",
"langchain==0.3.27",
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Nov 6, 2025

Choose a reason for hiding this comment

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

langchain==0.3.27 is added as a dependency, but there are no imports or references to langchain anywhere in the repo. Pulling in this large dependency without usage bloats installations and risks conflicts from its transitive requirements. Please drop it unless there is code that actually depends on it.

Prompt for AI agents
Address the following comment on plugins/communication_protocols/mcp/pyproject.toml at line 19:

<comment>`langchain==0.3.27` is added as a dependency, but there are no imports or references to `langchain` anywhere in the repo. Pulling in this large dependency without usage bloats installations and risks conflicts from its transitive requirements. Please drop it unless there is code that actually depends on it.</comment>

<file context>
@@ -15,7 +15,8 @@ dependencies = [
     &quot;utcp&gt;=1.0&quot;,
-    &quot;mcp-use&gt;=1.3&quot;
+    &quot;mcp-use&gt;=1.3&quot;,
+    &quot;langchain==0.3.27&quot;,
 ]
 classifiers = [
</file context>
Fix with Cubic

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