Skip to content

Commit 629621e

Browse files
authored
Merge pull request #76 from universal-tool-calling-protocol/dev
Socket and GraphQL Plugins
2 parents 0150a3b + d4d8ede commit 629621e

21 files changed

+1549
-638
lines changed

.github/workflows/test.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,11 @@ jobs:
3131
pip install -e plugins/communication_protocols/http[dev]
3232
pip install -e plugins/communication_protocols/mcp[dev]
3333
pip install -e plugins/communication_protocols/text[dev]
34+
pip install -e plugins/communication_protocols/socket[dev]
3435
3536
- name: Run tests with pytest
3637
run: |
37-
pytest core/tests/ plugins/communication_protocols/cli/tests/ plugins/communication_protocols/http/tests/ plugins/communication_protocols/mcp/tests/ plugins/communication_protocols/text/tests/ --doctest-modules --junitxml=junit/test-results.xml --cov=core/src/utcp --cov-report=xml --cov-report=html
38+
pytest core/tests/ plugins/communication_protocols/cli/tests/ plugins/communication_protocols/http/tests/ plugins/communication_protocols/mcp/tests/ plugins/communication_protocols/text/tests/ plugins/communication_protocols/socket/tests/ --doctest-modules --junitxml=junit/test-results.xml --cov=core/src/utcp --cov-report=xml --cov-report=html
3839
3940
- name: Upload coverage reports to Codecov
4041
uses: codecov/codecov-action@v3
Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,47 @@
1-
Find the UTCP readme at https://github.com/universal-tool-calling-protocol/python-utcp.
1+
2+
# UTCP GraphQL Communication Protocol Plugin
3+
4+
This plugin integrates GraphQL as a UTCP 1.0 communication protocol and call template. It supports discovery via schema introspection, authenticated calls, and header handling.
5+
6+
## Getting Started
7+
8+
### Installation
9+
10+
```bash
11+
pip install gql
12+
```
13+
14+
### Registration
15+
16+
```python
17+
import utcp_gql
18+
utcp_gql.register()
19+
```
20+
21+
## How To Use
22+
23+
- Ensure the plugin is imported and registered: `import utcp_gql; utcp_gql.register()`.
24+
- Add a manual in your client config:
25+
```json
26+
{
27+
"name": "my_graph",
28+
"call_template_type": "graphql",
29+
"url": "https://your.graphql/endpoint",
30+
"operation_type": "query",
31+
"headers": { "x-client": "utcp" },
32+
"header_fields": ["x-session-id"]
33+
}
34+
```
35+
- Call a tool:
36+
```python
37+
await client.call_tool("my_graph.someQuery", {"id": "123", "x-session-id": "abc"})
38+
```
39+
40+
## Notes
41+
42+
- Tool names are prefixed by the manual name (e.g., `my_graph.someQuery`).
43+
- Headers merge static `headers` plus whitelisted dynamic fields from `header_fields`.
44+
- Supported auth: API key, Basic auth, OAuth2 (client-credentials).
45+
- Security: only `https://` or `http://localhost`/`http://127.0.0.1` endpoints.
46+
47+
For UTCP core docs, see https://github.com/universal-tool-calling-protocol/python-utcp.

plugins/communication_protocols/gql/old_tests/test_graphql_transport.py

Lines changed: 0 additions & 129 deletions
This file was deleted.

plugins/communication_protocols/gql/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ version = "1.0.2"
88
authors = [
99
{ name = "UTCP Contributors" },
1010
]
11-
description = "UTCP communication protocol plugin for GraphQL. (Work in progress)"
11+
description = "UTCP communication protocol plugin for GraphQL."
1212
readme = "README.md"
1313
requires-python = ">=3.10"
1414
dependencies = [
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
from utcp.plugins.discovery import register_communication_protocol, register_call_template
2+
3+
from .gql_communication_protocol import GraphQLCommunicationProtocol
4+
from .gql_call_template import GraphQLCallTemplate, GraphQLCallTemplateSerializer
5+
6+
7+
def register():
8+
register_communication_protocol("graphql", GraphQLCommunicationProtocol())
9+
register_call_template("graphql", GraphQLCallTemplateSerializer())
Lines changed: 67 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,22 @@
11
from utcp.data.call_template import CallTemplate
2-
from utcp.data.auth import Auth
2+
from utcp.data.auth import Auth, AuthSerializer
3+
from utcp.interfaces.serializer import Serializer
4+
from utcp.exceptions import UtcpSerializerValidationError
5+
import traceback
36
from typing import Dict, List, Optional, Literal
4-
from pydantic import Field
7+
from pydantic import Field, field_serializer, field_validator
58

6-
class GraphQLProvider(CallTemplate):
9+
class GraphQLCallTemplate(CallTemplate):
710
"""Provider configuration for GraphQL-based tools.
811
912
Enables communication with GraphQL endpoints supporting queries, mutations,
1013
and subscriptions. Provides flexible query execution with custom headers
1114
and authentication.
1215
16+
For maximum flexibility, use the `query` field to provide a complete GraphQL
17+
query string with proper selection sets and variable types. This allows agents
18+
to call any existing GraphQL endpoint without limitations.
19+
1320
Attributes:
1421
call_template_type: Always "graphql" for GraphQL providers.
1522
url: The GraphQL endpoint URL.
@@ -18,6 +25,23 @@ class GraphQLProvider(CallTemplate):
1825
auth: Optional authentication configuration.
1926
headers: Optional static headers to include in requests.
2027
header_fields: List of tool argument names to map to HTTP request headers.
28+
query: Custom GraphQL query string with full control over selection sets
29+
and variable types. Example: 'query GetUser($id: ID!) { user(id: $id) { id name } }'
30+
variable_types: Map of variable names to GraphQL types for auto-generated queries.
31+
Example: {'id': 'ID!', 'limit': 'Int'}. Defaults to 'String' if not specified.
32+
33+
Example:
34+
# Full flexibility with custom query
35+
template = GraphQLCallTemplate(
36+
url="https://api.example.com/graphql",
37+
query="query GetUser($id: ID!) { user(id: $id) { id name email } }",
38+
)
39+
40+
# Auto-generation with proper types
41+
template = GraphQLCallTemplate(
42+
url="https://api.example.com/graphql",
43+
variable_types={"limit": "Int", "active": "Boolean"},
44+
)
2145
"""
2246

2347
call_template_type: Literal["graphql"] = "graphql"
@@ -27,3 +51,43 @@ class GraphQLProvider(CallTemplate):
2751
auth: Optional[Auth] = None
2852
headers: Optional[Dict[str, str]] = None
2953
header_fields: Optional[List[str]] = Field(default=None, description="List of input fields to be sent as request headers for the initial connection.")
54+
query: Optional[str] = Field(
55+
default=None,
56+
description="Custom GraphQL query/mutation string. Use $varName syntax for variables. "
57+
"If provided, this takes precedence over auto-generation. "
58+
"Example: 'query GetUser($id: ID!) { user(id: $id) { id name email } }'"
59+
)
60+
variable_types: Optional[Dict[str, str]] = Field(
61+
default=None,
62+
description="Map of variable names to GraphQL types for auto-generated queries. "
63+
"Example: {'id': 'ID!', 'limit': 'Int', 'active': 'Boolean'}. "
64+
"Defaults to 'String' if not specified."
65+
)
66+
67+
@field_serializer("auth")
68+
def serialize_auth(self, auth: Optional[Auth]):
69+
if auth is None:
70+
return None
71+
return AuthSerializer().to_dict(auth)
72+
73+
@field_validator("auth", mode="before")
74+
@classmethod
75+
def validate_auth(cls, v: Optional[Auth | dict]):
76+
if v is None:
77+
return None
78+
if isinstance(v, Auth):
79+
return v
80+
return AuthSerializer().validate_dict(v)
81+
82+
83+
class GraphQLCallTemplateSerializer(Serializer[GraphQLCallTemplate]):
84+
def to_dict(self, obj: GraphQLCallTemplate) -> dict:
85+
return obj.model_dump()
86+
87+
def validate_dict(self, data: dict) -> GraphQLCallTemplate:
88+
try:
89+
return GraphQLCallTemplate.model_validate(data)
90+
except Exception as e:
91+
raise UtcpSerializerValidationError(
92+
f"Invalid GraphQLCallTemplate: {e}\n{traceback.format_exc()}"
93+
)

0 commit comments

Comments
 (0)