ftl

package module
v0.1.2-0...-72334e3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 25, 2025 License: Apache-2.0 Imports: 6 Imported by: 0

README

FTL Go SDK

Version: 0.1.1

A lightweight SDK for building MCP (Model Context Protocol) tools with Go using the Spin framework.

Go Reference Go Report Card GitHub release (latest SemVer)

Installation

Latest Version
go get github.com/fastertools/ftl/sdk/go@latest
Specific Version
go get github.com/fastertools/ftl/sdk/go@v0.1.0
Development Version
go get github.com/fastertools/ftl/sdk/go@main

Requirements

  • Go 1.23+
  • TinyGo 0.30.0+ (for WASI compilation)
  • Spin CLI (for running tools)

Quick Start

Create a tool with the FTL CLI:

ftl init my-app
ftl add my-go-tool -l go
package main

import (
    ftl "github.com/fastertools/ftl/sdk/go"
)

func init() {
    ftl.CreateTools(map[string]ftl.ToolDefinition{
        "echo": {
            Description: "Echo the input message",
            InputSchema: map[string]interface{}{
                "type": "object",
                "properties": map[string]interface{}{
                    "message": map[string]interface{}{
                        "type":        "string",
                        "description": "The message to echo",
                    },
                },
                "required": []string{"message"},
            },
            Handler: func(input map[string]interface{}) ftl.ToolResponse {
                message, _ := input["message"].(string)
                return ftl.Text("Echo: " + message)
            },
        },
    })
}

func main() {}

Build with TinyGo:

tinygo build -o echo.wasm -target=wasi main.go

API Reference

Creating Tools
ftl.CreateTools(tools map[string]ftl.ToolDefinition)

Creates a Spin HTTP handler that implements the MCP protocol for the provided tools.

Tool Definition
type ToolDefinition struct {
    Name         string                   // Optional explicit tool name
    Title        string                   // Optional human-readable title
    Description  string                   // Tool description
    InputSchema  map[string]interface{}   // JSON Schema for input
    OutputSchema map[string]interface{}   // Optional output schema
    Annotations  *ToolAnnotations         // Optional behavior hints
    Meta         map[string]interface{}   // Optional metadata
    Handler      ToolHandler              // Handler function
}
Response Helpers
// Simple text response
ftl.Text("Hello, world!")

// Formatted text response
ftl.Textf("Hello, %s!", name)

// Error response
ftl.Error("Something went wrong")

// Formatted error response
ftl.Errorf("Failed to process: %v", err)

// Response with structured data
ftl.WithStructured("Success", map[string]interface{}{
    "result": 42,
})
Content Types
// Text content
ftl.TextContent("Hello", nil)

// Image content
ftl.ImageContent(base64Data, "image/png", nil)

// Audio content
ftl.AudioContent(base64Data, "audio/wav", nil)

// Resource content
ftl.ResourceContent(&ftl.ResourceContents{
    URI:      "file://example.txt",
    MimeType: "text/plain",
    Text:     "File contents",
}, nil)

Advanced Example

package main

import (
    "encoding/json"
    "fmt"
    "strings"
    
    ftl "github.com/fastertools/ftl/sdk/go"
)

func init() {
    ftl.CreateTools(map[string]ftl.ToolDefinition{
        "text_tools": {
            Name:        "text_tools",
            Description: "Various text manipulation tools",
            InputSchema: map[string]interface{}{
                "type": "object",
                "properties": map[string]interface{}{
                    "operation": map[string]interface{}{
                        "type": "string",
                        "enum": []string{"uppercase", "lowercase", "reverse"},
                        "description": "The operation to perform",
                    },
                    "text": map[string]interface{}{
                        "type":        "string",
                        "description": "The text to manipulate",
                    },
                },
                "required": []string{"operation", "text"},
            },
            Handler: textToolsHandler,
        },
        "json_formatter": {
            Description: "Format JSON data",
            InputSchema: map[string]interface{}{
                "type": "object",
                "properties": map[string]interface{}{
                    "json": map[string]interface{}{
                        "type":        "string",
                        "description": "JSON string to format",
                    },
                    "indent": map[string]interface{}{
                        "type":        "boolean",
                        "description": "Whether to indent the output",
                        "default":     true,
                    },
                },
                "required": []string{"json"},
            },
            Handler: jsonFormatterHandler,
        },
    })
}

func textToolsHandler(input map[string]interface{}) ftl.ToolResponse {
    operation, _ := input["operation"].(string)
    text, _ := input["text"].(string)
    
    var result string
    switch operation {
    case "uppercase":
        result = strings.ToUpper(text)
    case "lowercase":
        result = strings.ToLower(text)
    case "reverse":
        runes := []rune(text)
        for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 {
            runes[i], runes[j] = runes[j], runes[i]
        }
        result = string(runes)
    default:
        return ftl.Errorf("Unknown operation: %s", operation)
    }
    
    return ftl.WithStructured(
        fmt.Sprintf("Applied %s operation", operation),
        map[string]interface{}{
            "original": text,
            "result":   result,
            "operation": operation,
        },
    )
}

func jsonFormatterHandler(input map[string]interface{}) ftl.ToolResponse {
    jsonStr, _ := input["json"].(string)
    indent := true
    if val, ok := input["indent"].(bool); ok {
        indent = val
    }
    
    var data interface{}
    if err := json.Unmarshal([]byte(jsonStr), &data); err != nil {
        return ftl.Errorf("Invalid JSON: %v", err)
    }
    
    var formatted []byte
    var err error
    if indent {
        formatted, err = json.MarshalIndent(data, "", "  ")
    } else {
        formatted, err = json.Marshal(data)
    }
    
    if err != nil {
        return ftl.Errorf("Failed to format JSON: %v", err)
    }
    
    return ftl.Text(string(formatted))
}

func main() {}

Building and Running

  1. Build your tool with TinyGo:

    tinygo build -o tool.wasm -target=wasi main.go
    
  2. Add to your spin.toml:

    [[component]]
    id = "my-go-tool"
    source = "tool.wasm"
    [component.trigger]
    route = "/tools/my-go-tool/..."
    
  3. Run with Spin:

    spin up
    

Best Practices

  1. Error Handling: Always validate input and return clear error messages
  2. Schema Definition: Provide complete JSON schemas for better tool discovery
  3. Memory Usage: Be mindful of memory constraints in WASI environments
  4. Tool Naming: Use descriptive names that clearly indicate the tool's purpose
  5. Documentation: Include descriptions for all tools and parameters

Limitations

  • Must use TinyGo for WASI compilation (standard Go compiler not supported)
  • Limited to packages compatible with TinyGo and WASI
  • No goroutines or certain runtime features due to WASI constraints

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add some amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request
Development
# Clone the repository
git clone https://github.com/fastertools/ftl.git
cd ftl/sdk/go

# Install development dependencies
make dev-deps

# Run tests
make test

# Run linting
make lint

# Run all quality checks
make quality

Changelog

See CHANGELOG.md for a list of changes in each release.

License

Apache License 2.0 - see LICENSE for details.

Documentation

Overview

Package ftl provides a zero-dependency SDK for building MCP tools with Go.

This SDK provides a thin layer over Spin Go SDK to implement the Model Context Protocol (MCP) for FTL tools.

Index

Constants

View Source
const (
	ContentTypeText     = "text"
	ContentTypeImage    = "image"
	ContentTypeAudio    = "audio"
	ContentTypeResource = "resource"
)

Content type constants

Variables

This section is empty.

Functions

func CreateTools

func CreateTools(tools map[string]ToolDefinition)

CreateTools creates a Spin HTTP handler for MCP tools.

Example:

func init() {
    CreateTools(map[string]ToolDefinition{
        "echo": {
            Description: "Echo the input",
            InputSchema: map[string]interface{}{
                "type": "object",
                "properties": map[string]interface{}{
                    "message": map[string]interface{}{
                        "type": "string",
                        "description": "The message to echo",
                    },
                },
                "required": []string{"message"},
            },
            Handler: func(input map[string]interface{}) ToolResponse {
                message, _ := input["message"].(string)
                return Text(fmt.Sprintf("Echo: %s", message))
            },
        },
    })
}

func main() {}

func IsAudioContent

func IsAudioContent(c *ToolContent) bool

IsAudioContent checks if content is audio type

func IsImageContent

func IsImageContent(c *ToolContent) bool

IsImageContent checks if content is image type

func IsResourceContent

func IsResourceContent(c *ToolContent) bool

IsResourceContent checks if content is resource type

func IsTextContent

func IsTextContent(c *ToolContent) bool

IsTextContent checks if content is text type

Types

type ContentAnnotations

type ContentAnnotations struct {
	// Target audience for this content
	Audience []string `json:"audience,omitempty"`

	// Priority of this content (0.0 to 1.0)
	Priority float64 `json:"priority,omitempty"`
}

ContentAnnotations provides metadata for content items

type ResourceContents

type ResourceContents struct {
	// URI of the resource
	URI string `json:"uri"`

	// MIME type of the resource
	MimeType string `json:"mimeType,omitempty"`

	// Text content of the resource
	Text string `json:"text,omitempty"`

	// Base64-encoded binary content of the resource
	Blob string `json:"blob,omitempty"`
}

ResourceContents represents resource data

type ToolAnnotations

type ToolAnnotations struct {
	// Optional title annotation
	Title string `json:"title,omitempty"`

	// Hint that the tool is read-only (doesn't modify state)
	ReadOnlyHint bool `json:"readOnlyHint,omitempty"`

	// Hint that the tool may perform destructive operations
	DestructiveHint bool `json:"destructiveHint,omitempty"`

	// Hint that the tool is idempotent (same input → same output)
	IdempotentHint bool `json:"idempotentHint,omitempty"`

	// Hint that the tool accepts open-world inputs
	OpenWorldHint bool `json:"openWorldHint,omitempty"`
}

ToolAnnotations provides hints about tool behavior

type ToolContent

type ToolContent struct {
	// Content type discriminator
	Type string `json:"type"`

	// Text content (for type="text")
	Text string `json:"text,omitempty"`

	// Base64-encoded data (for type="image" or "audio")
	Data string `json:"data,omitempty"`

	// MIME type (for type="image" or "audio")
	MimeType string `json:"mimeType,omitempty"`

	// Resource contents (for type="resource")
	Resource *ResourceContents `json:"resource,omitempty"`

	// Optional annotations for this content
	Annotations *ContentAnnotations `json:"annotations,omitempty"`
}

ToolContent represents content that can be returned by tools

func AudioContent

func AudioContent(data, mimeType string, annotations *ContentAnnotations) ToolContent

AudioContent creates an audio content item

func ImageContent

func ImageContent(data, mimeType string, annotations *ContentAnnotations) ToolContent

ImageContent creates an image content item

func ResourceContent

func ResourceContent(resource *ResourceContents, annotations *ContentAnnotations) ToolContent

ResourceContent creates a resource content item

func TextContent

func TextContent(text string, annotations *ContentAnnotations) ToolContent

TextContent creates a text content item

type ToolDefinition

type ToolDefinition struct {
	// Optional explicit tool name (overrides the map key)
	Name string

	// Optional human-readable title for the tool
	Title string

	// Optional description of what the tool does
	Description string

	// JSON Schema describing the expected input parameters
	InputSchema map[string]interface{}

	// Optional JSON Schema describing the output format
	OutputSchema map[string]interface{}

	// Optional annotations providing hints about tool behavior
	Annotations *ToolAnnotations

	// Optional metadata for tool-specific extensions
	Meta map[string]interface{}

	// Handler function for tool execution
	Handler ToolHandler
}

ToolDefinition defines a tool's configuration

type ToolHandler

type ToolHandler func(input map[string]interface{}) ToolResponse

ToolHandler is the function signature for tool handlers

type ToolMetadata

type ToolMetadata struct {
	// The name of the tool (must be unique within the gateway)
	Name string `json:"name"`

	// Optional human-readable title for the tool
	Title string `json:"title,omitempty"`

	// Optional description of what the tool does
	Description string `json:"description,omitempty"`

	// JSON Schema describing the expected input parameters
	InputSchema map[string]interface{} `json:"inputSchema"`

	// Optional JSON Schema describing the output format
	OutputSchema map[string]interface{} `json:"outputSchema,omitempty"`

	// Optional annotations providing hints about tool behavior
	Annotations *ToolAnnotations `json:"annotations,omitempty"`

	// Optional metadata for tool-specific extensions
	Meta map[string]interface{} `json:"_meta,omitempty"`
}

ToolMetadata represents tool metadata returned by GET requests

type ToolResponse

type ToolResponse struct {
	// Array of content items returned by the tool
	Content []ToolContent `json:"content"`

	// Optional structured content matching the outputSchema
	StructuredContent interface{} `json:"structuredContent,omitempty"`

	// Indicates if this response represents an error
	IsError bool `json:"isError,omitempty"`
}

ToolResponse represents the response format for tool execution

func Error

func Error(err string) ToolResponse

Error creates an error response

func Errorf

func Errorf(format string, args ...interface{}) ToolResponse

Errorf creates a formatted error response

func Text

func Text(text string) ToolResponse

Text creates a simple text response

func Textf

func Textf(format string, args ...interface{}) ToolResponse

Textf creates a formatted text response

func WithStructured

func WithStructured(text string, structured interface{}) ToolResponse

WithStructured creates a response with structured content

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL