Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
d28dc58
Update docstrings and fix README
h3xxit Aug 26, 2025
018806c
Merge pull request #52 from universal-tool-calling-protocol/dev
h3xxit Aug 27, 2025
0f2af7e
Merge pull request #53 from universal-tool-calling-protocol/dev
h3xxit Aug 27, 2025
d28c0af
Update documentation and fix MCP plugin
h3xxit Sep 7, 2025
7ba8b3c
Merge pull request #61 "Update CLI" from universal-tool-calling-proto…
h3xxit Sep 7, 2025
908cd40
Merge pull request #63 from universal-tool-calling-protocol/dev
h3xxit Sep 8, 2025
74a11e2
Merge pull request #69 from universal-tool-calling-protocol/dev
h3xxit Sep 21, 2025
03a4b9f
Merge pull request #70 from universal-tool-calling-protocol/dev
h3xxit Oct 7, 2025
8443cda
Merge branch 'dev'
h3xxit Oct 7, 2025
0150a3b
Merge branch 'dev'
h3xxit Oct 7, 2025
6e2c671
socket protocol updated to be compatible with 1.0v utcp
Oct 25, 2025
9cea90f
cubic fixes done
Oct 26, 2025
7016987
pinned mcp-use to use langchain 0.3.27
Oct 26, 2025
718b668
removed mcp denpendency on langchain
Oct 27, 2025
ca252e5
adding the langchain dependency for testing (temporary)
Oct 27, 2025
45793cf
remove langchain-core pin to resolve dependency conflict
Oct 27, 2025
662d07d
feat: Updated Graphql implementation to be compatible with UTCP 1.0v
Nov 6, 2025
3aed349
Added gql 'how to use' guide in the README.md
Nov 6, 2025
dca4d26
updated cubic comments for GraphQl
Nov 12, 2025
4a2aea4
Update comment on delimeter handling
Thuraabtech Nov 17, 2025
21cbab7
Merge branch 'dev' into feature/graphql-1.0v
h3xxit Nov 29, 2025
700ec92
added gRPC-gnmi protocol to utcp
Dec 14, 2025
70ff230
Fix copilot comments to gRPC protocol implementation
Dec 14, 2025
9bbb819
fixed cubic comments for gRPC gnmi protocol for UTCP -2
Dec 14, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 94 additions & 0 deletions plugins/communication_protocols/gnmi/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# UTCP gNMI Plugin

This plugin adds a gNMI (gRPC) communication protocol compatible with UTCP 1.0. It follows UTCP’s plugin architecture: a CallTemplate and serializer, a CommunicationProtocol for discovery and execution, and registration via the `utcp.plugins` entry point.

## Installation

- Ensure you have Python 3.10+
- Dependencies: `utcp`, `grpcio`, `protobuf`, `pydantic`, `aiohttp`
- Install in your environment (example if published):

```
pip install utcp-gnmi
```

## Registration

Register the plugin into UTCP’s registries:

```
from utcp_gnmi import register
register()
```

This registers:
- Protocol: `gnmi`
- Call template serializer: `gnmi`

## Configuration (UTCP 1.0)

Use `UtcpClientConfig.manual_call_templates` to declare gNMI providers and tools.

Example:

```
{
"manual_call_templates": [
{
"name": "routerA",
"call_template_type": "gnmi",
"target": "localhost:50051",
"use_tls": false,
"metadata": {"authorization": "Bearer ${API_TOKEN}"},
"metadata_fields": ["tenant-id"],
"operation": "get",
"stub_module": "gnmi_pb2_grpc",
"message_module": "gnmi_pb2"
}
]
}
```

Fields:
- `call_template_type`: must be `gnmi`
- `target`: gRPC host:port
- `use_tls`: boolean; TLS required unless localhost/127.0.0.1
- `metadata`: static key/value pairs added to gRPC metadata
- `metadata_fields`: dynamic keys populated from tool args
- `operation`: one of `capabilities`, `get`, `set`, `subscribe`
- `stub_module`/`message_module`: import paths to generated Python stubs

## Security

- Enforces TLS (`grpc.aio.secure_channel`) unless `target` is `localhost` or `127.0.0.1`
- Do not use insecure channels over public networks
- Prefer mTLS for production environments (future enhancement adds cert fields)

## Authentication

Supported via UTCP `Auth` model:
- API Key: injects into `authorization` (or custom) metadata
- Basic: `authorization: Basic <base64(user:pass)>`
- OAuth2: client credentials; token fetched via `aiohttp` and cached

## Operations

- `capabilities`: unary `Capabilities` RPC
- `get`: unary `Get` RPC; maps `paths` list into `GetRequest.path`
- `set`: unary `Set` RPC; maps `updates` list into `SetRequest.update`
- `subscribe`: streaming `Subscribe` RPC; yields responses as dicts

## Testing

Run tests:

```
python -m pytest plugins/communication_protocols/gnmi/tests/test_gnmi_plugin.py -q
```

The tests validate manual registration, tool presence (including `subscribe`), and serializer round-trip.

## Notes

- Tool discovery registers canonical gNMI operations (`capabilities/get/set/subscribe`)
- Reflection-based discovery and mTLS configuration can be added in follow-up PRs
32 changes: 32 additions & 0 deletions plugins/communication_protocols/gnmi/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
[build-system]
requires = ["setuptools>=61.0"]
build-backend = "setuptools.build_meta"

[project]
name = "utcp-gnmi"
version = "1.0.0"
authors = [
{ 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.
requires-python = ">=3.10"
dependencies = [
"pydantic>=2.0",
"protobuf>=4.21",
"grpcio>=1.60",
"utcp>=1.0",
"aiohttp>=3.8"
]
license = "MPL-2.0"

[project.optional-dependencies]
dev = [
"build",
"pytest",
"pytest-asyncio",
"pytest-cov",
]

[project.entry-points."utcp.plugins"]
gnmi = "utcp_gnmi:register"
12 changes: 12 additions & 0 deletions plugins/communication_protocols/gnmi/src/utcp_gnmi/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from utcp.plugins.discovery import register_communication_protocol, register_call_template
from utcp_gnmi.gnmi_communication_protocol import GnmiCommunicationProtocol
from utcp_gnmi.gnmi_call_template import GnmiCallTemplateSerializer

def register():
register_communication_protocol("gnmi", GnmiCommunicationProtocol())
register_call_template("gnmi", GnmiCallTemplateSerializer())

__all__ = [
"GnmiCommunicationProtocol",
"GnmiCallTemplateSerializer",
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from typing import Optional, Dict, List, Literal

from utcp.data.call_template import CallTemplate
from utcp.interfaces.serializer import Serializer
from utcp.exceptions import UtcpSerializerValidationError
import traceback

class GnmiCallTemplate(CallTemplate):
call_template_type: Literal["gnmi"] = "gnmi"
target: str
use_tls: bool = True
metadata: Optional[Dict[str, str]] = None
metadata_fields: Optional[List[str]] = None
operation: Literal["capabilities", "get", "set", "subscribe"] = "get"
stub_module: str = "gnmi_pb2_grpc"
message_module: str = "gnmi_pb2"
Comment on lines +8 to +16
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 GnmiCallTemplate class is missing auth serialization/validation similar to what GraphQLProvider has. GraphQLProvider includes @field_serializer("auth") and @field_validator("auth", mode="before") decorators (lines 34-47 in gql_call_template.py) to properly handle Auth object serialization and deserialization. Without these, the auth field may not serialize/deserialize correctly when going through the CallTemplate lifecycle.

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 apply changes based on this feedback to the current PR


class GnmiCallTemplateSerializer(Serializer[GnmiCallTemplate]):
def to_dict(self, obj: GnmiCallTemplate) -> dict:
return obj.model_dump()

def validate_dict(self, obj: dict) -> GnmiCallTemplate:
try:
return GnmiCallTemplate.model_validate(obj)
except Exception as e:
raise UtcpSerializerValidationError("Invalid GnmiCallTemplate: " + traceback.format_exc()) from e
Loading