暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

Dify mcp

        随着MCP协议爆火,Dify也增加了支持mcp的插件,本篇分为两个方面来介绍Dify mcp,首先是Dify通过mcp协议调用本地实现的mcp server;然后是Dify把自己的aget 或者流水线封装起来作为mcp server给外界使用。实现这些功能,需要在dify基础上有一个适配层,这个就是Dify的mcp插件。目前比较熟知的有下面四个插件,Mcp Agent策略、Agent 策略、MCP SSE 和mcp-server,其中前三个是调用外界mcp server的,最后一个是把Dify的能力封装成mcp server给外界使用的。下面我们重点介绍下MCP SSE和mcp-server
        首先我们在Dify的插件市场安装上述插件,然后开发一个mcp server
    package main
    import (
        "context"
        "encoding/json"
        "fmt"
        "io"
        "log"
        "net/http"
        "os"
        "github.com/mark3labs/mcp-go/mcp"
        "github.com/mark3labs/mcp-go/server"
    )
    func main() {
        // Create a new MCP server
        s := server.NewMCPServer(
            "Calculator Demo",
            "1.0.0",
            server.WithResourceCapabilities(truetrue),
            server.WithLogging(),
            server.WithRecovery(),
        )
        // 实现tools 工具
        calculatorTool := mcp.NewTool("calculate",
            mcp.WithDescription("Perform basic arithmetic operations"),
            mcp.WithString("operation",
                mcp.Required(),
                mcp.Description("The operation to perform (add, subtract, multiply, divide)"),
                mcp.Enum("add""subtract""multiply""divide"),
            ),
            mcp.WithNumber("x",
                mcp.Required(),
                mcp.Description("First number"),
            ),
            mcp.WithNumber("y",
                mcp.Required(),
                mcp.Description("Second number"),
            ),
        )
        //资源
        s.AddResource(mcp.NewResource("test://static/resource",
            "Static Resource",
            mcp.WithMIMEType("text/plain"),
        ), handleReadResource)
        //动态模板资源
        s.AddResourceTemplate(
            mcp.NewResourceTemplate(
                "test://dynamic/resource/{id}",
                "Dynamic Resource",
            ),
            handleResourceTemplate,
        )
        // Add the calculator handler
        s.AddTool(calculatorTool, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
            op := request.Params.Arguments["operation"].(string)
            x := request.Params.Arguments["x"].(float64)
            y := request.Params.Arguments["y"].(float64)
            var result float64
            switch op {
            case "add":
                result = x + y
            case "subtract":
                result = x - y
            case "multiply":
                result = x * y
            case "divide":
                if y == 0 {
                    return mcp.NewToolResultError("cannot divide by zero"), nil
                }
                result = x y
            }
            fmt.Println("call mcp tool success")
            return mcp.NewToolResultText(fmt.Sprintf("%.2f", result)), nil
        })
        sseServer := server.NewSSEServer(s,
            server.WithSSEContextFunc(authFromRequest),
        )
        if err := sseServer.Start(":8080"); err != nil {
            log.Fatalf("Server error: %v", err)
        }
    }
    func handleResourceTemplate(
        ctx context.Context,
        request mcp.ReadResourceRequest,
    ) ([]mcp.ResourceContents, error) {
        return []mcp.ResourceContents{
            mcp.TextResourceContents{
                URI:      request.Params.URI,
                MIMEType: "text/plain",
                Text:     fmt.Sprintf("动态模板:%+v ;", request),
            },
        }, nil
    }
    func handleReadResource(
        ctx context.Context,
        request mcp.ReadResourceRequest,
    ) ([]mcp.ResourceContents, error) {
        return []mcp.ResourceContents{
            mcp.TextResourceContents{
                URI:      "test://static/resource",
                MIMEType: "text/plain",
                Text:     fmt.Sprintf("模板 %+v ;", request),
            },
        }, nil
    }
    // authKey is a custom context key for storing the auth token.
    type authKey struct{}
    // withAuthKey adds an auth key to the context.
    func withAuthKey(ctx context.Context, auth string) context.Context {
        return context.WithValue(ctx, authKey{}, auth)
    }
    // authFromRequest extracts the auth token from the request headers.
    func authFromRequest(ctx context.Context, r *http.Request) context.Context {
        return withAuthKey(ctx, r.Header.Get("Authorization"))
    }
    // authFromEnv extracts the auth token from the environment
    func authFromEnv(ctx context.Context) context.Context {
        return withAuthKey(ctx, os.Getenv("API_KEY"))
    }
    // tokenFromContext extracts the auth token from the context.
    // This can be used by tools to extract the token regardless of the
    // transport being used by the server.
    func tokenFromContext(ctx context.Context) (stringerror) {
        auth, ok := ctx.Value(authKey{}).(string)
        if !ok {
            return "", fmt.Errorf("missing auth")
        }
        return auth, nil
    }
    type response struct {
        Args    map[string]interface{} `json:"args"`
        Headers map[string]string      `json:"headers"`
    }
    // makeRequest makes a request to httpbin.org including the auth token in the request
    // headers and the message in the query string.
    func makeRequest(ctx context.Context, message, token string) (*response, error) {
        req, err := http.NewRequestWithContext(ctx, "GET""https://httpbin.org/anything"nil)
        if err != nil {
            return nil, err
        }
        req.Header.Set("Authorization", token)
        query := req.URL.Query()
        query.Add("message", message)
        req.URL.RawQuery = query.Encode()
        resp, err := http.DefaultClient.Do(req)
        if err != nil {
            return nil, err
        }
        defer resp.Body.Close()
        body, err := io.ReadAll(resp.Body)
        if err != nil {
            return nil, err
        }
        var r *response
        if err := json.Unmarshal(body, &r); err != nil {
            return nil, err
        }
        return r, nil
    }
    // handleMakeAuthenticatedRequestTool is a tool that makes an authenticated request
    // using the token from the context.
    func handleMakeAuthenticatedRequestTool(
        ctx context.Context,
        request mcp.CallToolRequest,
    ) (*mcp.CallToolResult, error) {
        message, ok := request.Params.Arguments["message"].(string)
        if !ok {
            return nil, fmt.Errorf("missing message")
        }
        token, err := tokenFromContext(ctx)
        if err != nil {
            return nil, fmt.Errorf("missing token: %v", err)
        }
        // Now our tool can make a request with the token, irrespective of where it came from.
        resp, err := makeRequest(ctx, message, token)
        if err != nil {
            return nil, err
        }
        return mcp.NewToolResultText(fmt.Sprintf("%+v", resp)), nil
    }
    为了验证我们mcp server的可用性,我们使用工具来发现和使用下这个mcp sse server
      npx @modelcontextprotocol/inspector
      Need to install the following packages:
      @modelcontextprotocol/inspector
      Ok to proceed? (y) y
      需要注意的是这个包,需要依赖18.14以上的node
      https://github.com/modelcontextprotocol/inspector    
        nvm use v18.14.0
        安装成功后如下
          % npx @modelcontextprotocol/inspector
          Starting MCP inspector...
          ⚙️ Proxy server listening on port 6277
          🔍 MCP Inspector is up and running at http://127.0.0.1:6274 🚀
          启动我们的mcp server后,工具就可以发现我们的mcp server,然后就可以使用下
          安装完 mcp sse插件之后,我们点击插件,在插件上可以配置我们的mcp server的地址
          https://github.com/junjiem/dify-plugin-tools-mcp_sse   
          注意需要配置为http://host.docker.internal:8080/sse,因为docker for mac。配置成功后的效果如下
          然后在我们的聊天工具里尝试使用下mcp,需要注意的是DeepSeek1.5b不支持mcp,这里大模型选智普AI,实验结果如下
                  至此,Dify调用外界的mcp server 的流程介绍完毕,这也是我们多数场景需要使用的情况。如果我们在Dify中集成了特别厉害的功能,想在本地工具中使用,这个如何操作呢,可以使用mcp-server插件,将Dify的功能作为一个mcp server发布给外界使用。
              配置完成后我们得到了链接,需要注意的是,这个插件有bug,这里的
            http://localhost/e/fos7gt47jnofl9db}/mcp


              http://localhost/e/fos7gt47jnofl9db/mcp
              链接多了个}导致我们调用失败。然后我们使用mcp客户端工具调用下测试。https://github.com/CherryHQ/cherry-studio V1.2.9 及以上版本以获得更稳定的 MCP 功能支持。配置界面如下,如果能保存成功,说明已经调试成功了

              文章转载自golang算法架构leetcode技术php,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

              评论