Tool Plugin — Go

A complete example of a remote tool plugin that provides a get_weather tool to the agent.

Project Setup

mkdir weather-plugin && cd weather-plugin
go mod init github.com/yourorg/weather-plugin

Generate Go code from the proto files:

protoc --go_out=. --go-grpc_out=. \
  proto/plugin.proto proto/tool.proto

Or copy the generated code from gen/agentplatform/v1/ in the AgentPlatform repository.

Full Source — main.go

package main

import (
	"context"
	"encoding/json"
	"fmt"
	"log"
	"net"
	"os"

	"google.golang.org/grpc"

	pb "github.com/dbb1dev/agentplatform/gen/agentplatform/v1"
)

func main() {
	port := os.Getenv("PORT")
	if port == "" {
		port = "9010"
	}

	lis, err := net.Listen("tcp", ":"+port)
	if err != nil {
		log.Fatalf("failed to listen: %v", err)
	}

	s := grpc.NewServer()
	srv := &server{}
	pb.RegisterPluginServer(s, srv)
	pb.RegisterToolProviderServer(s, srv)

	log.Printf("weather-plugin listening on :%s", port)
	if err := s.Serve(lis); err != nil {
		log.Fatalf("failed to serve: %v", err)
	}
}

// server implements both PluginServer and ToolProviderServer.
type server struct {
	pb.UnimplementedPluginServer
	pb.UnimplementedToolProviderServer
}

// ---------------------------------------------------------------------------
// Plugin service — required by all plugins
// ---------------------------------------------------------------------------

func (s *server) Register(_ context.Context, req *pb.RegisterRequest) (*pb.RegisterResponse, error) {
	return &pb.RegisterResponse{
		Name:         "weather",
		Capabilities: []pb.Capability{pb.Capability_TOOL_PROVIDER},
		Metadata:     map[string]string{"version": "1.0.0"},
	}, nil
}

func (s *server) Health(_ context.Context, _ *pb.HealthRequest) (*pb.HealthResponse, error) {
	return &pb.HealthResponse{Status: pb.Status_SERVING}, nil
}

const manifestHCL = `plugin "weather" {
  version     = "1.0.0"
  description = "Provides current weather data for any city."
  author      = "yourorg"
  icon        = "cloud"
  type        = "remote"

  config_schema {
    field "api_key_env" {
      type        = "string"
      required    = true
      placeholder = "e.g. WEATHER_API_KEY"
      description = "Environment variable containing your weather API key"
    }

    field "unit" {
      type    = "string"
      options = ["fahrenheit", "celsius"]
      default = "fahrenheit"
      description = "Temperature unit"
    }

    field "max_retries" {
      type    = "number"
      default = "3"
      description = "Maximum retry attempts for API calls"
    }

    field "debug" {
      type    = "bool"
      default = "false"
      description = "Enable debug logging"
    }
  }
}
`

func (s *server) GetManifest(_ context.Context, _ *pb.GetManifestRequest) (*pb.GetManifestResponse, error) {
	return &pb.GetManifestResponse{ManifestHcl: manifestHCL}, nil
}

// ---------------------------------------------------------------------------
// ToolProvider service — exposes tools to the agent
// ---------------------------------------------------------------------------

func (s *server) ListTools(_ context.Context, _ *pb.ListToolsRequest) (*pb.ListToolsResponse, error) {
	return &pb.ListToolsResponse{
		Tools: []*pb.ToolDefinition{
			{
				Name:        "get_weather",
				Description: "Get the current weather for a city.",
				ParametersJsonSchema: `{
					"type": "object",
					"properties": {
						"city": {
							"type": "string",
							"description": "City name (e.g. 'San Francisco')"
						}
					},
					"required": ["city"]
				}`,
			},
		},
	}, nil
}

func (s *server) ExecuteTool(_ context.Context, req *pb.ExecuteToolRequest) (*pb.ExecuteToolResponse, error) {
	if req.GetName() != "get_weather" {
		return &pb.ExecuteToolResponse{
			ResultJson: fmt.Sprintf(`{"error":"unknown tool: %s"}`, req.GetName()),
			IsError:    true,
		}, nil
	}

	// Parse arguments
	var args struct {
		City string `json:"city"`
	}
	if err := json.Unmarshal([]byte(req.GetArgumentsJson()), &args); err != nil {
		return &pb.ExecuteToolResponse{
			ResultJson: `{"error":"invalid arguments"}`,
			IsError:    true,
		}, nil
	}

	// In a real plugin you'd call a weather API here.
	result, _ := json.Marshal(map[string]any{
		"city":        args.City,
		"temperature": 72,
		"unit":        "F",
		"condition":   "Sunny",
	})

	return &pb.ExecuteToolResponse{
		ResultJson: string(result),
	}, nil
}

Run It

go run main.go
# weather-plugin listening on :9010

Register in Workspace

Add to your workspace HCL config:

workspace "my-workspace" {
  plugin "weather" {
    source  = "remote://localhost:9010"
    version = "1.0.0"
  }
}

Start the workspace. The agent now has access to the get_weather tool and will call it when users ask about weather.

How It Works

  1. Platform connects and calls Register — your plugin reports TOOL_PROVIDER capability
  2. Platform calls ListTools — receives your tool definitions with JSON Schema parameters
  3. When the agent decides to use get_weather, platform calls ExecuteTool with the arguments
  4. Your plugin returns the result as JSON — the agent incorporates it into its response