-
Notifications
You must be signed in to change notification settings - Fork 38
GraphQL Plugin: UTCP 1.0 Migration #75
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
GraphQL Plugin: UTCP 1.0 Migration #75
Conversation
…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 Update docs
…tool-calling-protocol/dev Plugin updates
…tool-calling-protocol/dev Add WebSocket transport implementation for real-time communication …
There was a problem hiding this 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_providertotool_call_templateconversion 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.
| 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 |
Copilot
AI
Nov 6, 2025
There was a problem hiding this comment.
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.
| elif provider.framing_strategy == "delimiter": | ||
| # Add delimiter after the message | ||
| delimiter = provider.message_delimiter or "\\x00" | ||
| delimiter = provider.message_delimiter or "\x00" |
Copilot
AI
Nov 6, 2025
There was a problem hiding this comment.
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.
| delimiter = provider.message_delimiter or "\x00" | |
| delimiter = provider.message_delimiter or "\\x00" |
| "utcp>=1.0", | ||
| "mcp-use>=1.3" | ||
| "mcp-use>=1.3", | ||
| "langchain==0.3.27", |
Copilot
AI
Nov 6, 2025
There was a problem hiding this comment.
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.
| "langchain==0.3.27", | |
| "langchain>=0.3.27,<0.4.0", |
| 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 |
Copilot
AI
Nov 6, 2025
There was a problem hiding this comment.
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.).
| 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 |
Copilot
AI
Nov 6, 2025
There was a problem hiding this comment.
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.
| from utcp_gql.gql_call_template import GraphQLProvider | ||
| from utcp_gql import gql_communication_protocol as gql_module | ||
|
|
||
| from utcp.data.tool import Tool |
Copilot
AI
Nov 6, 2025
There was a problem hiding this comment.
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.
| from utcp.data.tool import Tool |
| parsed = None | ||
| try: | ||
| parsed = json.loads(msg) | ||
| except Exception: |
Copilot
AI
Nov 6, 2025
There was a problem hiding this comment.
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.
| except Exception: | |
| except Exception: | |
| # Ignore JSON parsing errors; non-JSON input will be handled below |
| finally: | ||
| try: | ||
| conn.shutdown(socket.SHUT_RDWR) | ||
| except Exception: |
Copilot
AI
Nov 6, 2025
There was a problem hiding this comment.
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.
| except Exception: | |
| except Exception: | |
| # Ignore errors if socket is already closed or shutdown fails |
| try: | ||
| # Read any incoming data to simulate request handling | ||
| await reader.read(1024) | ||
| except Exception: |
Copilot
AI
Nov 6, 2025
There was a problem hiding this comment.
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.
| except Exception: | |
| except Exception: | |
| # Ignore exceptions during read (e.g., client disconnects), as this is a test server. |
| try: | ||
| writer.close() | ||
| await writer.wait_closed() | ||
| except Exception: |
Copilot
AI
Nov 6, 2025
There was a problem hiding this comment.
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.
| except Exception: | |
| except Exception: | |
| # Ignore exceptions during writer close; connection may already be closed or in error state. |
There was a problem hiding this 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()) |
There was a problem hiding this comment.
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) -> str:
+ # 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())
var_defs = f"({arg_str})" if arg_str else ""
- arg_pass = ', '.join(f"{k}: ${k}" for k in tool_args.keys())
</file context>
|
|
||
| ```bash | ||
| pip install -e "core[dev]" | ||
| pip install -e plugins/communication_protocols/socket[dev] |
There was a problem hiding this comment.
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 "core[dev]"
+pip install -e plugins/communication_protocols/socket[dev]
+```
+
</file context>
| "utcp>=1.0", | ||
| "mcp-use>=1.3" | ||
| "mcp-use>=1.3", | ||
| "langchain==0.3.27", |
There was a problem hiding this comment.
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 = [
"utcp>=1.0",
- "mcp-use>=1.3"
+ "mcp-use>=1.3",
+ "langchain==0.3.27",
]
classifiers = [
</file context>
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
Call Template & Serializer
Registration
python
Security & Validation
Testing
Run:
python -m pytest plugins/communication_protocols/gql/tests/test_graphql_protocol.py -vUses 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 -qand 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
Dependencies
Written for commit 3aed349. Summary will update automatically on new commits.