WebAssembly插件模板

一个用于在Go中构建MCP(Model Context Protocol)插件的WebAssembly插件模板,适用于hyper-mcp框架。

  • by hyper-mcp-rs
  • Go
  • 本地部署
  • 更新于
在 GitHub 查看 hyper-mcp-rs/go-plugin-template

使用说明

Go插件模板

一个WebAssembly插件模板,用于使用hyper-MCP框架在Go中构建MCP(模型上下文协议)插件。

概述

此模板提供了一个创建作为WebAssembly模块运行的MCP插件的入门项目。它包括实现MCP协议处理程序所需的所有依赖关系和样板代码。

项目结构

.
├── .github/workflows       # Sample workflows for Github Actions
|-- main.go                 # Plugin handler implementations
├── exports.go              # WASM export wrappers for handlers
├── imports.go              # Host function calls
├── types.go                # MCP protocol types
├── go.mod                  # Go module definition
├── go.sum                  # Go module checksums
├── Dockerfile              # Simple build for deploying a WASM
└── .gitignore              # Git ignore rules

入门指南

先决条件

  • 转到1.22或更高版本
  • TinyGo 0.40或更高版本
  • Docker(用于构建WASM)
  • clang 和 lld (用于WASM编译)

发展

  1. 克隆或使用此模板 启动插件项目

  2. 实现插件处理程序 在 main.go:

插件处理程序必须在不使用goroutines的情况下实现 除非 您修改了Dockerfile构建以删除 -scheduler=none 从蒂尼戈建造旗帜。请注意,不建议这样做,因为hyper-mcp通常会为您处理并发执行。

注: 你只需要实现与你的插件相关的处理程序。例如,如果你的插件只提供工具,那么只实现 ListTools() 和 CallTool()。所有其他处理程序都有开箱即用的默认实现。

  • ListTools() -描述可用工具
  • CallTool() -执行工具
  • ListResources() -列出可用资源
  • ReadResource() -读取资源内容
  • ListPrompts() -列出可用提示
  • GetPrompt() -获取提示详细信息
  • Complete() -提供自动完成建议
  • ListResourceTemplates() -列出资源模板
  • OnRootsListChanged() -处理根更改
  1. 本地建设 (WASM目标需要Docker):
     GOOS=wasip1 GOARCH=wasm tinygo build -no-debug -panic=trap -scheduler=none -o plugin.wasm
     docker build -t your-registry/your-plugin-name .
    

依赖项

模板使用:

  • extism/go pdk -Extism插件开发工具包
  • 用于JSON序列化和时间处理的标准Go库

插件处理函数

您的插件可以实现以下处理程序的任意组合。 只实现插件所需的处理程序 -该模板为其他所有内容提供了合理的默认值:

处理程序 用途 必需
ListTools() 声明可用工具 提供插件的工具
CallTool() 执行工具 提供插件的工具
ListResources() 声明可用资源 资源提供插件
ListResourceTemplates() 声明资源模板 动态资源插件
ReadResource() 读取资源内容 资源提供插件
ListPrompts() 声明可用提示 提示提供插件
GetPrompt() 检索特定提示 提示提供插件
Complete() 提供自动补全功能 支持补全的插件
OnRootsListChanged() 处理根更改 插件对根更改做出反应

示例:仅限工具的插件

如果你的插件只提供工具,你只需要实现:

func ListTools(input ListToolsRequest) (*ListToolsResult, error) {
    return &ListToolsResult{
        Tools: []Tool{
            {
                Name: "greet",
                Description: ptrString("Greet a person"),
                InputSchema: ToolSchema{
                    Type: "object",
                    Properties: map[string]interface{}{
                        "name": map[string]interface{}{
                            "type": "string",
                            "description": "The person's name",
                        },
                    },
                    Required: []string{"name"},
                },
            },
        },
    }, nil
}

func CallTool(input CallToolRequest) (*CallToolResult, error) {
    switch input.Request.Name {
    case "greet":
        name, ok := input.Request.Arguments["name"].(string)
        if !ok {
            return &CallToolResult{
                Content: []json.RawMessage{
                    []byte(`{"type":"text","text":"name argument required"}`),
                },
            }, nil
        }
        return &CallToolResult{
            Content: []json.RawMessage{
                []byte(fmt.Sprintf(`{"type":"text","text":"Hello, %s!"}`, name)),
            },
        }, nil
    default:
        return &CallToolResult{
            Content: []json.RawMessage{
                []byte(fmt.Sprintf(`{"type":"text","text":"Unknown tool: %s"}`, input.Request.Name)),
            },
        }, nil
    }
}

所有其他处理程序将使用其默认实现。

主机功能

您的插件可以调用这些宿主函数与客户端和MCP服务器进行交互。可通过直接函数调用 imports.go:

// Example usage
result, err := CreateElicitation(ElicitRequestParamWithTimeout{...})

用户交互

CreateElicitation(input ElicitRequestParamWithTimeout) (*ElicitResult, error)

通过客户端的启发界面请求用户输入。当您的插件在执行过程中需要用户指导、决策或确认时,请使用此功能。

result, err := CreateElicitation(ElicitRequestParamWithTimeout{
    Message: "Please provide your name",
    RequestedSchema: Schema{
        Type: "object",
        Properties: map[string]json.RawMessage{
            "name": json.RawMessage(`{"type":"string"}`),
        },
    },
    Timeout: ptrInt64(30000), // 30 second timeout
})

消息生成

CreateMessage(input CreateMessageRequestParam) (*CreateMessageResult, error)

通过客户端的采样接口请求消息创建。当您的插件需要人工智能辅助的智能文本生成或分析时,请使用此功能。

result, err := CreateMessage(CreateMessageRequestParam{
    MaxTokens: 1024,
    Messages: []json.RawMessage{
        // conversation history
    },
    SystemPrompt: ptrString("You are a helpful assistant"),
})

资源发现

ListRoots() (*ListRootsResult, error)

列出客户端的根目录或资源。使用此功能可以发现可用的根资源(通常是文件系统根),并了解插件可以访问的资源范围。

roots, err := ListRoots()
if err == nil {
    for _, root := range roots.Roots {
        fmt.Printf("Root: %s at %s\n", *root.Name, root.URI)
    }
}

日志记录

NotifyLoggingMessage(input LoggingMessageNotificationParam) error

向客户端发送诊断、信息、警告或错误消息。客户端的日志记录级别决定了要处理和显示哪些消息。

NotifyLoggingMessage(LoggingMessageNotificationParam{
    Level: LoggingLevelInfo,
    Logger: ptrString("my_plugin"),
    Data: json.RawMessage(`{"message": "Processing started"}`),
})

进度报告

NotifyProgress(input ProgressNotificationParam) error

报告长时间运行操作期间的进度。允许客户端向用户显示进度条或状态信息。

NotifyProgress(ProgressNotificationParam{
    Progress: 50,
    ProgressToken: "task-1",
    Total: ptrFloat64(100),
})

列表更改通知

当插件的可用项更改时通知客户端:

NotifyToolListChanged() error

  • 在添加、删除或修改可用工具时调用此命令

NotifyResourceListChanged() error

  • 在添加、删除或修改可用资源时调用此命令

NotifyPromptListChanged() error

  • 在添加、删除或修改可用提示时调用此命令

NotifyResourceUpdated(input ResourceUpdatedNotificationParam) error

  • 当您修改特定资源的内容时调用此命令
// When your plugin's tools change
NotifyToolListChanged()

// When a specific resource is updated
NotifyResourceUpdated(ResourceUpdatedNotificationParam{
    URI: "resource://my-resource",
})

示例:带有进度的交互式工具

func CallTool(input CallToolRequest) (*CallToolResult, error) {
    switch input.Request.Name {
    case "long_task":
        // Log start
        NotifyLoggingMessage(LoggingMessageNotificationParam{
            Level: LoggingLevelInfo,
            Data: json.RawMessage(`{"message": "Starting long task"}`),
        })

        // Do work with progress updates
        for i := 0; i < 10; i++ {
            // ... do work ...
            NotifyProgress(ProgressNotificationParam{
                Progress: float64((i + 1) * 10),
                ProgressToken: "task-1",
                Total: ptrFloat64(100),
            })
        }

        return &CallToolResult{
            Content: []json.RawMessage{
                []byte(`{"type":"text","text":"Task completed"}`),
            },
        }, nil
    default:
        return &CallToolResult{
            Content: []json.RawMessage{
                []byte(fmt.Sprintf(`{"type":"text","text":"Unknown tool: %s"}`, input.Request.Name)),
            },
        }, nil
    }
}

配电楼

使用Docker

包括 Dockerfile 只需封装您的WASM:

GOOS=wasip1 GOARCH=wasm tinygo build -no-debug -panic=trap -scheduler=none -o plugin.wasm
docker build -t your-registry/your-plugin-name .

Docker构建:

  1. 将Go代码编译为 wasip1 目标
  2. 创建仅包含已编译图像的最小图像 plugin.wasm
  3. 输出OCI兼容的容器映像

手动构建

要在没有Docker的情况下手动构建(需要TinyGo 0.40+):

# Build for WASM
GOOS=wasip1 GOARCH=wasm tinygo build -no-debug -panic=trap -scheduler=none -o plugin.wasm

# Result is at: plugin.wasm

实施指南

创建工具

下面是一个实现简单工具的示例:

func ListTools(input ListToolsRequest) (*ListToolsResult, error) {
    return &ListToolsResult{
        Tools: []Tool{
            {
                Name: "greet",
                Description: ptrString("Greet a person"),
                InputSchema: ToolSchema{
                    Type: "object",
                    Properties: map[string]interface{}{
                        "name": map[string]interface{}{
                            "type": "string",
                            "description": "The person's name",
                        },
                    },
                    Required: []string{"name"},
                },
            },
        },
    }, nil
}

func CallTool(input CallToolRequest) (*CallToolResult, error) {
    switch input.Request.Name {
    case "greet":
        name, ok := input.Request.Arguments["name"].(string)
        if !ok {
            return &CallToolResult{
                Content: []json.RawMessage{
                    []byte(`{"type":"text","text":"name argument required"}`),
                },
            }, nil
        }
        return &CallToolResult{
            Content: []json.RawMessage{
                []byte(fmt.Sprintf(`{"type":"text","text":"Hello, %s!"}`, name)),
            },
        }, nil
    default:
        return &CallToolResult{
            Content: []json.RawMessage{
                []byte(fmt.Sprintf(`{"type":"text","text":"Unknown tool: %s"}`, input.Request.Name)),
            },
        }, nil
    }
}

创建资源

实现资源的示例:

func ListResources(input ListResourcesRequest) (*ListResourcesResult, error) {
    return &ListResourcesResult{
        Resources: []Resource{
            {
                URI: "resource://example",
                Name: "Example Resource",
                Description: ptrString("An example resource"),
                MimeType: ptrString("text/plain"),
            },
        },
    }, nil
}

func ReadResource(input ReadResourceRequest) (*ReadResourceResult, error) {
    switch input.Request.URI {
    case "resource://example":
        return &ReadResourceResult{
            Contents: []json.RawMessage{
                []byte(`{"uri":"resource://example","mimeType":"text/plain","text":"Resource content here"}`),
            },
        }, nil
    default:
        return &ReadResourceResult{
            Contents: []json.RawMessage{
                []byte(fmt.Sprintf(`{"type":"text","text":"Unknown resource: %s"}`, input.Request.URI)),
            },
        }, nil
    }
}

Helper函数

该模板包括一些使用指针的有用辅助函数:

// Helper to create string pointers
func ptrString(s string) *string {
    return &s
}

// Helper to create int64 pointers
func ptrInt64(i int64) *int64 {
    return &i
}

// Helper to create float64 pointers
func ptrFloat64(f float64) *float64 {
    return &f
}

// Helper to create bool pointers
func ptrBool(b bool) *bool {
    return &b
}

hyper-mcp中的配置

构建并发布插件后,在hyper-mcp中配置它:

{
  "plugins": {
    "my_plugin": {
      "url": "oci://your-registry/your-plugin-name:latest"
    }
  }
}

对于本地开发/测试:

{
  "plugins": {
    "my_plugin": {
      "url": "file:///path/to/plugin.wasm"
    }
  }
}

测试

要在本地测试您的插件:

  1. 构建它: docker build -t my-plugin . && docker run --rm -v $(pwd):/workspace my-plugin cp /plugin.wasm /workspace/
  2. 更新hyper-mcp的配置以指向 file:// 统一资源定位符
  3. 使用以下命令启动hyper-mcp RUST_LOG=debug
  4. 通过Claude Desktop、Cursor IDE或其他MCP客户端进行测试

资源

许可证

与hyper-mcp相同-Apache 2.0