Add remote mcp server sdk (#1946)

This commit is contained in:
澄潭
2025-03-24 22:11:45 +08:00
committed by GitHub
parent f5d20b72e0
commit d9f16f7d5e
17 changed files with 1337 additions and 13 deletions

View File

@@ -0,0 +1,33 @@
# Build stage
FROM golang:1.24 AS builder
ARG SERVER_NAME=quark-search
ARG GOPROXY
WORKDIR /app
# Copy the server code
COPY ${SERVER_NAME}/ .
# Set GOPROXY if provided
RUN if [ -n "$GOPROXY" ]; then go env -w GOPROXY=${GOPROXY}; fi
# Build the WASM binary
RUN GOOS=wasip1 GOARCH=wasm go build -buildmode=c-shared -o main.wasm main.go
# Final stage
FROM scratch
ARG SERVER_NAME=quark-search
WORKDIR /
# Copy the WASM binary from the builder stage
COPY --from=builder /app/main.wasm /main.wasm
# Metadata
LABEL org.opencontainers.image.title="${SERVER_NAME}"
LABEL org.opencontainers.image.description="Higress MCP Server - ${SERVER_NAME}"
LABEL org.opencontainers.image.source="https://github.com/alibaba/higress"
# The WASM binary is the only artifact in the image

View File

@@ -0,0 +1,51 @@
# MCP Server Makefile
# Variables
SERVER_NAME ?= quark-search
REGISTRY ?= higress-registry.cn-hangzhou.cr.aliyuncs.com/plugins/
GO_VERSION ?= 1.24
BUILD_TIME := $(shell date "+%Y%m%d-%H%M%S")
COMMIT_ID := $(shell git rev-parse --short HEAD 2>/dev/null)
IMAGE_TAG = $(if $(strip $(SERVER_VERSION)),${SERVER_VERSION},${BUILD_TIME}-${COMMIT_ID})
IMG ?= ${REGISTRY}${SERVER_NAME}:${IMAGE_TAG}
GOPROXY := $(shell go env GOPROXY)
# Default target
.DEFAULT:
build:
@echo "Building WASM binary for ${SERVER_NAME}..."
cd ${SERVER_NAME} && GOOS=wasip1 GOARCH=wasm go build -buildmode=c-shared -o main.wasm main.go
@echo ""
@echo "Output WASM file: ${SERVER_NAME}/main.wasm"
# Build Docker image
build-image:
@echo "Building Docker image for ${SERVER_NAME}..."
docker build -t ${IMG} \
--build-arg SERVER_NAME=${SERVER_NAME} \
--build-arg GOPROXY=${GOPROXY} \
-f Dockerfile .
@echo ""
@echo "Image: ${IMG}"
# Build and push Docker image
build-push: build-image
docker push ${IMG}
# Clean build artifacts
clean:
rm -f ${SERVER_NAME}/main.wasm
# Help
help:
@echo "Available targets:"
@echo " build - Build WASM binary"
@echo " build-image - Build Docker image"
@echo " build-push - Build and push Docker image"
@echo " clean - Remove build artifacts"
@echo ""
@echo "Variables:"
@echo " SERVER_NAME - Name of the MCP server (default: ${SERVER_NAME})"
@echo " REGISTRY - Docker registry (default: ${REGISTRY})"
@echo " SERVER_VERSION - Version tag for the image (default: timestamp-commit)"
@echo " IMG - Full image name (default: ${IMG})"

View File

@@ -0,0 +1,247 @@
# MCP Server Implementation Guide
This guide explains how to implement a Model Context Protocol (MCP) server using the Higress WASM Go SDK. MCP servers provide tools and resources that extend the capabilities of AI assistants.
## Overview
An MCP server is a standalone application that communicates with AI assistants through the Model Context Protocol. It can provide:
- **Tools**: Functions that can be called by the AI to perform specific tasks
- **Resources**: Data that can be accessed by the AI
> **Note**: MCP server plugins require Higress version 2.1.0 or higher to be used.
## Project Structure
A typical MCP server project has the following structure:
```
my-mcp-server/
├── go.mod # Go module definition
├── go.sum # Go module checksums
├── main.go # Entry point that registers tools and resources
├── server/
│ └── server.go # Server configuration and parsing
└── tools/
└── my_tool.go # Tool implementation
```
## Server Configuration
The server configuration defines the parameters needed for the server to function. For example:
```go
// server/server.go
package server
import (
"encoding/json"
"errors"
"github.com/alibaba/higress/plugins/wasm-go/pkg/wrapper"
)
// Define your server configuration structure
type MyMCPServer struct {
ApiKey string `json:"apiKey"`
// Add other configuration fields as needed
}
// Validate the configuration
func (s MyMCPServer) ConfigHasError() error {
if s.ApiKey == "" {
return errors.New("missing api key")
}
return nil
}
// Parse configuration from JSON
func ParseFromConfig(configBytes []byte, server *MyMCPServer) error {
return json.Unmarshal(configBytes, server)
}
// Parse configuration from HTTP request
func ParseFromRequest(ctx wrapper.HttpContext, server *MyMCPServer) error {
return ctx.ParseMCPServerConfig(server)
}
```
## Tool Implementation
Each tool should be implemented as a struct with the following methods:
1. `Description()`: Returns a description of the tool
2. `InputSchema()`: Returns the JSON schema for the tool's input parameters
3. `Create()`: Creates a new instance of the tool with the provided parameters
4. `Call()`: Executes the tool's functionality
Example:
```go
// tools/my_tool.go
package tools
import (
"encoding/json"
"fmt"
"net/http"
"my-mcp-server/server"
"github.com/alibaba/higress/plugins/wasm-go/pkg/wrapper"
)
// Define your tool structure with input parameters
type MyTool struct {
Param1 string `json:"param1" jsonschema_description:"Description of param1" jsonschema:"example=example value"`
Param2 int `json:"param2,omitempty" jsonschema_description:"Description of param2" jsonschema:"default=5"`
}
// Description returns the description field for the MCP tool definition.
// This corresponds to the "description" field in the MCP tool JSON response,
// which provides a human-readable explanation of the tool's purpose and usage.
func (t MyTool) Description() string {
return `Detailed description of what this tool does and when to use it.`
}
// InputSchema returns the inputSchema field for the MCP tool definition.
// This corresponds to the "inputSchema" field in the MCP tool JSON response,
// which defines the JSON Schema for the tool's input parameters, including
// property types, descriptions, and required fields.
func (t MyTool) InputSchema() map[string]any {
return wrapper.ToInputSchema(&MyTool{})
}
// Create instantiates a new tool instance based on the input parameters
// from an MCP tool call. It deserializes the JSON parameters into a struct,
// applying default values for optional fields, and returns the configured tool instance.
func (t MyTool) Create(params []byte) wrapper.MCPTool[server.MyMCPServer] {
myTool := &MyTool{
Param2: 5, // Default value
}
json.Unmarshal(params, &myTool)
return myTool
}
// Call implements the core logic for handling an MCP tool call. This method is executed
// when the tool is invoked through the MCP framework. It processes the configured parameters,
// makes any necessary API requests, and formats the results to be returned to the caller.
func (t MyTool) Call(ctx wrapper.HttpContext, config server.MyMCPServer) error {
// Validate configuration
err := server.ParseFromRequest(ctx, &config)
if err != nil {
return err
}
err = config.ConfigHasError()
if err != nil {
return err
}
// Implement your tool's logic here
// ...
// Return results
ctx.SendMCPToolTextResult(fmt.Sprintf("Result: %s, %d", t.Param1, t.Param2))
return nil
}
```
## Main Entry Point
The main.go file is the entry point for your MCP server. It registers your tools and resources:
```go
// main.go
package main
import (
"my-mcp-server/server"
"my-mcp-server/tools"
"github.com/alibaba/higress/plugins/wasm-go/pkg/wrapper"
)
func main() {}
func init() {
wrapper.SetCtx(
"my-mcp-server", // Server name
wrapper.ParseRawConfig(server.ParseFromConfig),
wrapper.AddMCPTool("my_tool", tools.MyTool{}), // Register tools
// Add more tools as needed
)
}
```
## Dependencies
Your MCP server must use a specific version of the wasm-go SDK that supports Go 1.24's WebAssembly compilation features:
```bash
# Add the required dependency with the specific version tag
go get github.com/alibaba/higress/plugins/wasm-go@wasm-go-1.24
```
## Building the WASM Binary
To compile your Go code into a WebAssembly (WASM) file, use the following command:
```bash
GOOS=wasip1 GOARCH=wasm go build -buildmode=c-shared -o main.wasm main.go
```
This command sets the target operating system to `wasip1` (WebAssembly System Interface) and architecture to `wasm` (WebAssembly), then builds your code as a C-shared library and outputs it as `main.wasm`.
## Using the Makefile
A Makefile is provided to simplify the build process. It includes the following targets:
- `make build`: Builds the WASM binary for your MCP server
- `make build-image`: Builds a Docker image containing your MCP server
- `make build-push`: Builds and pushes the Docker image to a registry
- `make clean`: Removes build artifacts
- `make help`: Shows available targets and variables
You can customize the build by setting the following variables:
```bash
# Build with a custom server name
make SERVER_NAME=my-mcp-server build
# Build with a custom registry
make REGISTRY=my-registry.example.com/ build-image
# Build with a specific version tag
make SERVER_VERSION=1.0.0 build-image
```
## Testing
You can create unit tests for your tools to verify their functionality:
```go
// tools/my_tool_test.go
package tools
import (
"encoding/json"
"fmt"
"testing"
)
func TestMyToolInputSchema(t *testing.T) {
myTool := MyTool{}
schema := myTool.InputSchema()
schemaJSON, err := json.MarshalIndent(schema, "", " ")
if err != nil {
t.Fatalf("Failed to marshal schema to JSON: %v", err)
}
fmt.Printf("MyTool InputSchema:\n%s\n", string(schemaJSON))
if len(schema) == 0 {
t.Error("InputSchema returned an empty schema")
}
}
```

View File

@@ -0,0 +1,243 @@
# MCP 服务器实现指南
本指南介绍如何使用 Higress WASM Go SDK 实现 Model Context Protocol (MCP) 服务器。MCP 服务器提供工具和资源,扩展 AI 助手的能力。
## 概述
MCP 服务器是一个独立的应用程序,通过 Model Context Protocol 与 AI 助手通信。它可以提供:
- **工具**:可以被 AI 调用以执行特定任务的函数
- **资源**:可以被 AI 访问的数据
> **注意**MCP 服务器插件需要 Higress 2.1.0 或更高版本才能使用。
## 项目结构
一个典型的 MCP 服务器项目具有以下结构:
```
my-mcp-server/
├── go.mod # Go 模块定义
├── go.sum # Go 模块校验和
├── main.go # 注册工具和资源的入口点
├── server/
│ └── server.go # 服务器配置和解析
└── tools/
└── my_tool.go # 工具实现
```
## 服务器配置
服务器配置定义了服务器运行所需的参数。例如:
```go
// server/server.go
package server
import (
"encoding/json"
"errors"
"github.com/alibaba/higress/plugins/wasm-go/pkg/wrapper"
)
// 定义服务器配置结构
type MyMCPServer struct {
ApiKey string `json:"apiKey"`
// 根据需要添加其他配置字段
}
// 验证配置
func (s MyMCPServer) ConfigHasError() error {
if s.ApiKey == "" {
return errors.New("missing api key")
}
return nil
}
// 从 JSON 解析配置
func ParseFromConfig(configBytes []byte, server *MyMCPServer) error {
return json.Unmarshal(configBytes, server)
}
// 从 HTTP 请求解析配置
func ParseFromRequest(ctx wrapper.HttpContext, server *MyMCPServer) error {
return ctx.ParseMCPServerConfig(server)
}
```
## 工具实现
每个工具应该实现为一个具有以下方法的结构体:
1. `Description()`:返回工具的描述
2. `InputSchema()`:返回工具输入参数的 JSON schema
3. `Create()`:使用提供的参数创建工具的新实例
4. `Call()`:执行工具的功能
示例:
```go
// tools/my_tool.go
package tools
import (
"encoding/json"
"fmt"
"net/http"
"my-mcp-server/server"
"github.com/alibaba/higress/plugins/wasm-go/pkg/wrapper"
)
// 定义带有输入参数的工具结构
type MyTool struct {
Param1 string `json:"param1" jsonschema_description:"参数1的描述" jsonschema:"example=示例值"`
Param2 int `json:"param2,omitempty" jsonschema_description:"参数2的描述" jsonschema:"default=5"`
}
// Description 返回 MCP 工具定义的描述字段。
// 这对应于 MCP 工具 JSON 响应中的 "description" 字段,
// 提供了工具目的和用法的人类可读解释。
func (t MyTool) Description() string {
return `详细描述这个工具做什么以及何时使用它。`
}
// InputSchema 返回 MCP 工具定义的 inputSchema 字段。
// 这对应于 MCP 工具 JSON 响应中的 "inputSchema" 字段,
// 定义了工具输入参数的 JSON Schema包括属性类型、描述和必填字段。
func (t MyTool) InputSchema() map[string]any {
return wrapper.ToInputSchema(&MyTool{})
}
// Create 基于 MCP 工具调用的输入参数实例化一个新的工具实例。
// 它将 JSON 参数反序列化为结构体,为可选字段应用默认值,并返回配置好的工具实例。
func (t MyTool) Create(params []byte) wrapper.MCPTool[server.MyMCPServer] {
myTool := &MyTool{
Param2: 5, // 默认值
}
json.Unmarshal(params, &myTool)
return myTool
}
// Call 实现处理 MCP 工具调用的核心逻辑。当通过 MCP 框架调用工具时,执行此方法。
// 它处理配置的参数,进行必要的 API 请求,并格式化返回给调用者的结果。
func (t MyTool) Call(ctx wrapper.HttpContext, config server.MyMCPServer) error {
// 验证配置
err := server.ParseFromRequest(ctx, &config)
if err != nil {
return err
}
err = config.ConfigHasError()
if err != nil {
return err
}
// 在这里实现工具的逻辑
// ...
// 返回结果
ctx.SendMCPToolTextResult(fmt.Sprintf("结果: %s, %d", t.Param1, t.Param2))
return nil
}
```
## 主入口点
main.go 文件是 MCP 服务器的入口点。它注册工具和资源:
```go
// main.go
package main
import (
"my-mcp-server/server"
"my-mcp-server/tools"
"github.com/alibaba/higress/plugins/wasm-go/pkg/wrapper"
)
func main() {}
func init() {
wrapper.SetCtx(
"my-mcp-server", // 服务器名称
wrapper.ParseRawConfig(server.ParseFromConfig),
wrapper.AddMCPTool("my_tool", tools.MyTool{}), // 注册工具
// 根据需要添加更多工具
)
}
```
## 依赖项
您的 MCP 服务器必须使用支持 Go 1.24 WebAssembly 编译功能的特定版本的 wasm-go SDK
```bash
# 添加具有特定版本标签的必需依赖项
go get github.com/alibaba/higress/plugins/wasm-go@wasm-go-1.24
```
## 构建 WASM 二进制文件
要将 Go 代码编译为 WebAssembly (WASM) 文件,请使用以下命令:
```bash
GOOS=wasip1 GOARCH=wasm go build -buildmode=c-shared -o main.wasm main.go
```
此命令将目标操作系统设置为 `wasip1`WebAssembly 系统接口)和架构设置为 `wasm`WebAssembly然后将代码构建为 C 共享库并输出为 `main.wasm`
## 使用 Makefile
提供了 Makefile 以简化构建过程。它包括以下目标:
- `make build`:为 MCP 服务器构建 WASM 二进制文件
- `make build-image`:构建包含 MCP 服务器的 Docker 镜像
- `make build-push`:构建并将 Docker 镜像推送到注册表
- `make clean`:删除构建产物
- `make help`:显示可用的目标和变量
您可以通过设置以下变量来自定义构建:
```bash
# 使用自定义服务器名称构建
make SERVER_NAME=my-mcp-server build
# 使用自定义注册表构建
make REGISTRY=my-registry.example.com/ build-image
# 使用特定版本标签构建
make SERVER_VERSION=1.0.0 build-image
```
## 测试
您可以为工具创建单元测试以验证其功能:
```go
// tools/my_tool_test.go
package tools
import (
"encoding/json"
"fmt"
"testing"
)
func TestMyToolInputSchema(t *testing.T) {
myTool := MyTool{}
schema := myTool.InputSchema()
schemaJSON, err := json.MarshalIndent(schema, "", " ")
if err != nil {
t.Fatalf("无法将 schema 序列化为 JSON: %v", err)
}
fmt.Printf("MyTool InputSchema:\n%s\n", string(schemaJSON))
if len(schema) == 0 {
t.Error("InputSchema 返回了空 schema")
}
}

View File

@@ -0,0 +1,25 @@
module quark-search
go 1.24
toolchain go1.24.1
require (
github.com/alibaba/higress/plugins/wasm-go v1.4.4-0.20250324133957-dab499f6ade6
github.com/tidwall/gjson v1.17.3
)
require (
github.com/bahlo/generic-list-go v0.2.0 // indirect
github.com/buger/jsonparser v1.1.1 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20250323151219-d75620c61711 // indirect
github.com/invopop/jsonschema v0.13.0 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.0 // indirect
github.com/tidwall/resp v0.1.1 // indirect
github.com/tidwall/sjson v1.2.5 // indirect
github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

View File

@@ -0,0 +1,38 @@
github.com/alibaba/higress/plugins/wasm-go v1.4.4-0.20250324133957-dab499f6ade6 h1:/iHNur+B0lHmcy97XYwHb6QrnHJichzKs37gnTyGP3k=
github.com/alibaba/higress/plugins/wasm-go v1.4.4-0.20250324133957-dab499f6ade6/go.mod h1:csP9Mpkc+gVgbZsizCdcYSy0LJrQA+//RcnZBInyknc=
github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20250323151219-d75620c61711 h1:n5sZwSZWQ5uKS69hu50/0gliTFrIJ1w+g/FSdIIiZIs=
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20250323151219-d75620c61711/go.mod h1:tRI2LfMudSkKHhyv1uex3BWzcice2s/l8Ah8axporfA=
github.com/invopop/jsonschema v0.13.0 h1:KvpoAJWEjR3uD9Kbm2HWJmqsEaHt8lBUpd0qHcIi21E=
github.com/invopop/jsonschema v0.13.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/gjson v1.17.3 h1:bwWLZU7icoKRG+C+0PNwIKC6FCJO/Q3p2pZvuP0jN94=
github.com/tidwall/gjson v1.17.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tidwall/resp v0.1.1 h1:Ly20wkhqKTmDUPlyM1S7pWo5kk0tDu8OoC/vFArXmwE=
github.com/tidwall/resp v0.1.1/go.mod h1:3/FrruOBAxPTPtundW0VXgmsQ4ZBA0Aw714lVYgwFa0=
github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc=
github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@@ -0,0 +1,32 @@
// Copyright (c) 2022 Alibaba Group Holding Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"quark-search/server"
"quark-search/tools"
"github.com/alibaba/higress/plugins/wasm-go/pkg/wrapper"
)
func main() {}
func init() {
wrapper.SetCtx(
"quark-mcp-server",
wrapper.ParseRawConfig(server.ParseFromConfig),
wrapper.AddMCPTool("web_search", tools.WebSearch{}),
)
}

View File

@@ -0,0 +1,41 @@
// Copyright (c) 2022 Alibaba Group Holding Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package server
import (
"encoding/json"
"errors"
"github.com/alibaba/higress/plugins/wasm-go/pkg/wrapper"
)
type QuarkMCPServer struct {
ApiKey string `json:"apiKey"`
}
func (s QuarkMCPServer) ConfigHasError() error {
if s.ApiKey == "" {
return errors.New("missing api key")
}
return nil
}
func ParseFromConfig(configBytes []byte, server *QuarkMCPServer) error {
return json.Unmarshal(configBytes, server)
}
func ParseFromRequest(ctx wrapper.HttpContext, server *QuarkMCPServer) error {
return ctx.ParseMCPServerConfig(server)
}

View File

@@ -0,0 +1,130 @@
// Copyright (c) 2022 Alibaba Group Holding Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package tools
import (
"encoding/json"
"fmt"
"net/http"
"net/url"
"strings"
"quark-search/server"
"github.com/alibaba/higress/plugins/wasm-go/pkg/log"
"github.com/alibaba/higress/plugins/wasm-go/pkg/wrapper"
"github.com/tidwall/gjson"
)
type SearchResult struct {
Title string
Link string
Content string
}
func (result SearchResult) Valid() bool {
return result.Title != "" && result.Link != "" && result.Content != ""
}
func (result SearchResult) Format() string {
return fmt.Sprintf(`
## Title: %s
### Reference URL
%s
### Content
%s
`, result.Title, result.Link, result.Content)
}
type WebSearch struct {
Query string `json:"query" jsonschema_description:"Search query, please use Chinese" jsonschema:"example=黄金价格走势"`
ContentMode string `json:"contentMode,omitempty" jsonschema_description:"Return the level of content detail, choose to use summary or full text" jsonschema:"enum=full,enum=summary,default=summary"`
Number uint32 `json:"number,omitempty" jsonschema_description:"Number of results" jsonschema:"default=5"`
}
// Description returns the description field for the MCP tool definition.
// This corresponds to the "description" field in the MCP tool JSON response,
// which provides a human-readable explanation of the tool's purpose and usage.
func (t WebSearch) Description() string {
return `Performs a web search using the Quark Search API, ideal for general queries, news, articles, and online content.
Use this for broad information gathering, recent events, or when you need diverse web sources.
Because Quark search performs poorly for English searches, please use Chinese for the query parameters.`
}
// InputSchema returns the inputSchema field for the MCP tool definition.
// This corresponds to the "inputSchema" field in the MCP tool JSON response,
// which defines the JSON Schema for the tool's input parameters, including
// property types, descriptions, and required fields.
func (t WebSearch) InputSchema() map[string]any {
return wrapper.ToInputSchema(&WebSearch{})
}
// Create instantiates a new WebSearch tool instance based on the input parameters
// from an MCP tool call.
func (t WebSearch) Create(params []byte) wrapper.MCPTool[server.QuarkMCPServer] {
webSearch := &WebSearch{
ContentMode: "summary",
Number: 5,
}
json.Unmarshal(params, &webSearch)
return webSearch
}
// Call implements the core logic for handling an MCP tool call. This method is executed
// when the tool is invoked through the MCP framework. It processes the configured parameters,
// makes the actual API request to the service, parses the response,
// and formats the results to be returned to the caller.
func (t WebSearch) Call(ctx wrapper.HttpContext, config server.QuarkMCPServer) error {
err := server.ParseFromRequest(ctx, &config)
if err != nil {
log.Errorf("parse config from request failed, err:%s", err)
}
err = config.ConfigHasError()
if err != nil {
return err
}
return ctx.RouteCall(http.MethodGet, fmt.Sprintf("https://cloud-iqs.aliyuncs.com/search/genericSearch?query=%s", url.QueryEscape(t.Query)),
[][2]string{{"Accept", "application/json"},
{"X-API-Key", config.ApiKey}}, nil, func(statusCode int, responseHeaders http.Header, responseBody []byte) {
if statusCode != http.StatusOK {
ctx.OnMCPToolCallError(fmt.Errorf("quark search call failed, status: %d", statusCode))
return
}
jsonObj := gjson.ParseBytes(responseBody)
var results []string
for index, item := range jsonObj.Get("pageItems").Array() {
var content string
if t.ContentMode == "full" {
content = item.Get("markdownText").String()
if content == "" {
content = item.Get("mainText").String()
}
} else if t.ContentMode == "summary" {
content = item.Get("snippet").String()
}
result := SearchResult{
Title: item.Get("title").String(),
Link: item.Get("link").String(),
Content: content,
}
if result.Valid() && index < int(t.Number) {
results = append(results, result.Format())
}
}
ctx.SendMCPToolTextResult(fmt.Sprintf("# Search Results\n\n%s", strings.Join(results, "\n\n")))
})
}

View File

@@ -0,0 +1,45 @@
// Copyright (c) 2022 Alibaba Group Holding Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package tools
import (
"encoding/json"
"fmt"
"testing"
)
// TestWebSearchInputSchema tests the InputSchema method of WebSearch
// to verify that the JSON schema configuration is correct.
func TestWebSearchInputSchema(t *testing.T) {
// Create a WebSearch instance
webSearch := WebSearch{}
// Get the input schema
schema := webSearch.InputSchema()
// Marshal the schema to JSON for better readability
schemaJSON, err := json.MarshalIndent(schema, "", " ")
if err != nil {
t.Fatalf("Failed to marshal schema to JSON: %v", err)
}
// Print the schema
fmt.Printf("WebSearch InputSchema:\n%s\n", string(schemaJSON))
// Basic validation to ensure the schema is not empty
if len(schema) == 0 {
t.Error("InputSchema returned an empty schema")
}
}