使用Go语言基于MCP协议打造AI选股工具

【TypeScript】三分钟让 Trae、Cursor 用上你自己的 MCP

一文弄懂用Go实现自己的MCP服务

什么是MCP

Model Context Protocol 即MCP,是一种协议,它允许大型语言模型LLMs访问自定义的工具和服务。

比如可以使用trae中的智能体作为MCP客户端可以选择想 MCP Server 发起请求,以使用它们提供的工具。你可以自行添加 MCP Server,并添加到自定义的智能体中来使用

在 trae 中,MCP Server 支持三种传输类型:stdio 传输,SSE 传输,Streamable HTTP 传输

技术实现

编辑工具:vscode

插件:cline

go代码参考:使用Go语言基于MCP协议打造AI选股工具

//mcptest.go

package main

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"strings"
	"time"

	"github.com/mark3labs/mcp-go/mcp"
	"github.com/mark3labs/mcp-go/server"
	"github.com/spf13/cast"
	"github.com/tidwall/gjson"
)

type StockSearchRequest struct {
	KeyWord                string        `json:"KeyWord"`
	PageSize               int           `json:"pageSize"`
	PageNo                 int           `json:"pageNo"`
	Fingerprint            string        `json:"fingerprint"`
	MatchWord              string        `json:"matchWord"`
	ShareToGuba            bool          `json:"shareToGuba"`
	Timestamp              string        `json:"timestamp"`
	RequestID              string        `json:"requestId"`
	RemovedConditionIDList []interface{} `json:"removedConditionIdList"`
	OwnSelectAll           bool          `json:"ownSelectAll"`
	NeedCorrect            bool          `json:"needCorrect"`
	Client                 string        `json:"client"`
	DxInfo                 []interface{} `json:"dxInfo"`
	Biz                    string        `json:"biz"`
	Gids                   []interface{} `json:"gids"`
}

func main() {
	mcpServer := server.NewMCPServer("mcp-server-ai-stock", "1.0.0", server.WithToolCapabilities(false))
	queryStocks := mcp.NewTool("query_stocks",
		mcp.WithDescription("智能选股:描述你期望的股票(自然语言选股),返回股票的最新交易数据。"),
		mcp.WithString("keyword",
			mcp.Description("选股条件,例:\n"+
				"1. 股票或板块名称:宁德时代、比亚迪、新能源、AI、白酒\n"+
				"2. 技术指标:MACD、5日均线、RSI、KDJ\n"+
				"3. 财务条件:ROE>15%、净利润增长率>30%\n"+
				"4. 复合条件:MACD金叉且成交量放大;股价在5日均线上方;不要ST股及不要退市股;不要科创板;不要北交所"),
		),
	)
	mcpServer.AddTool(queryStocks, queryStocksHandler)
	if err := server.ServeStdio(mcpServer); err != nil {
		panic(err)
	}
}

func queryStocksHandler(ctx context.Context, request mcp.CallToolRequest) (res *mcp.CallToolResult, err error) {
	keyword := cast.ToString(cast.ToStringMap(request.Params.Arguments)["keyword"])
	requestBody, err := json.Marshal(&StockSearchRequest{
		PageNo:                 1,
		PageSize:               50,
		KeyWord:                keyword,
		Fingerprint:            "6d09ac0e7326d9299bb77ac2090dadd9",
		Gids:                   []interface{}{},
		Timestamp:              cast.ToString(time.Now().UnixMilli()),
		RequestID:              "jVZgmtMhiPYtiDb7g2yferYT9aCrY1TA1758537136613",
		NeedCorrect:            true,
		RemovedConditionIDList: []interface{}{},
		DxInfo:                 []interface{}{},
		Biz:                    "web_ai_select_stocks",
		Client:                 "web",
	})
	if err != nil {
		return &mcp.CallToolResult{Content: []mcp.Content{mcp.NewTextContent(fmt.Sprintf("序列化请求失败: %v", err))}}, nil
	}
	client := &http.Client{Timeout: 30 * time.Second}
	req, err := http.NewRequestWithContext(ctx, "POST", "https://np-tjxg-g.eastmoney.com/api/smart-tag/stock/v3/pw/search-code", bytes.NewBuffer(requestBody))
	if err != nil {
		return &mcp.CallToolResult{Content: []mcp.Content{mcp.NewTextContent(fmt.Sprintf("创建请求失败: %v", err))}}, nil
	}
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Origin", "https://xuangu.eastmoney.com")
	req.Header.Set("Referer", "https://xuangu.eastmoney.com/")
	req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:140.0) Gecko/20100101 Firefox/140.0")
	resp, err := client.Do(req)
	if err != nil {
		return &mcp.CallToolResult{Content: []mcp.Content{mcp.NewTextContent(fmt.Sprintf("请求失败: %v", err))}}, nil
	}
	defer func() { _ = resp.Body.Close() }()
	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return &mcp.CallToolResult{Content: []mcp.Content{mcp.NewTextContent(fmt.Sprintf("读取响应失败: %v", err))}}, nil
	}
	g := gjson.ParseBytes(body)
	if g.Get("code").Int() != 100 {
		return &mcp.CallToolResult{Content: []mcp.Content{mcp.NewTextContent(fmt.Sprintf("响应非100: %s", string(body)))}}, nil
	}
	if g.Get("data.result.total").Int() <= 0 {
		return &mcp.CallToolResult{Content: []mcp.Content{mcp.NewTextContent("未找到符合条件的股票")}}, nil
	}
	content := parseSearchCodeResponse(g)
	return &mcp.CallToolResult{Content: []mcp.Content{mcp.NewTextContent(content)}}, nil
}

func parseSearchCodeResponse(g gjson.Result) string {
	var (
		columns []string
		b       strings.Builder
	)

	_, _ = b.WriteString("## 筛选的股票数据\n")
	g.Get("data.result.columns").ForEach(func(_, o gjson.Result) bool {
		columns = append(columns, o.Get("key").String())                             // 英文字段
		_, _ = b.WriteString(o.Get("title").String() + o.Get("unit").String() + "|") // 中文表头
		return true
	})
	g.Get("data.result.dataList").ForEach(func(_, o gjson.Result) bool {
		_, _ = b.WriteString("\n|")
		for _, column := range columns {
			_, _ = b.WriteString(strings.ReplaceAll(o.Get(column).String(), "|", ",") + "|")
		}
		return true
	})
	return b.String()
}

上述代码实际是请求 东方财富 的开发API

执行命令,生成服务器端可执行文件

go build -o mcp-server mcptest.go

在这里插入图片描述

安装好cline插件之后,需要配置连接mcp服务器的脚本

在这里插入图片描述

增加如下配置

{
  "mcpServers": {
    "mcp-calculator-server": {
      "command": "/Users/yourusername/Workspace/go/src/mcp-server/mcp-server"
    }
  }
}

配置成功后即可看到如下信息

在这里插入图片描述

结果

输入 “宁德时代”

会看到自动调用本地的mcp-server

在这里插入图片描述

输出

在这里插入图片描述

智能体美化后输出

在这里插入图片描述

输入 “MACD金叉且市盈率小于30”

在这里插入图片描述

输出

在这里插入图片描述

美化

在这里插入图片描述

Logo

更多推荐