Oasis's Cloud

一个人的首要责任,就是要有雄心。雄心是一种高尚的激情,它可以采取多种合理的形式。
—— 《一个数学家的辩白》

MCP 协议实践:鱼缸盐度计算

作者:oasis


开发环境准备

创建 weather 目录,通过 npm 进行项目初始化:

mkdir memorandum
cd memorandum

npm init -y

# 安装依赖
npm install @modelcontextprotocol/sdk zod@3
npm install -D @types/node typescript

修改 package.json 中的 scripts 属性:

{
  "scripts": {
    "build": "tsc && chmod +x build/index.js",
    "inspect": "npx @modelcontextprotocol/inspector node build/index.js"
  }
}

inspect 命令调用 MCP 官方提供的调试工具,方便在开始的时候对 MCP Server 提供的能力进行测试。下面是 inspector 的界面:

inspect

在 src 下创建 index.ts 文件,然后引入相关的代码:

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";

这里使用 stdio 进行 MCP Client 和 MCP Server 之间的交互。所以 import 了 StdioServerTransport

由于我们要实现的是 Tool,所以需要对 Tool 进行注册:

const server = new McpServer({
  name: "weather",
  version: "1.0.0",
});

server.registerTool(
  "get_weather",
  {
    title: "Get weather for a city",
    description:
      "Get current weather information for a city (supports cities worldwide including Beijing, Shanghai, etc.)",
    inputSchema: z.object({
      city: z
        .string()
        .describe(
          "The city name to get weather for (e.g., 'Beijing', 'Shanghai', 'New York')"
        ),
    }),
  },
  async (input) => {
    return {
      content: [
        {
          type: "text",
          text: "晴天☀️" + input.city,
        },
      ],
      structuredContent: {
        content: [
          {
            type: "text",
            text: "晴天☀️" + input.city,
          },
        ],
      }
    };
  }
);
第一次写 Demo,问题容易出现在 structuredContent 上。当工具定义了 outputSchema,此字段必须在结果中存在,并包含一个符合模式的 JSON 对象。

执行 pnpm build 后,将此服务配置到 Cursor 中:

"weather": {
      "command": "node",
      "args": ["demo/wather-mcp/build/index.js"]
    }
args 是命令的参数数组,由于通过 node 执行脚本,所以 args 中的文件路径应该是绝对路径。

现在根据 MCP 网站写的 Demo 已经可以正常运行了。接下来将在此基础上进行修改。

修改 index.ts

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import { getSalt } from "./utils.js";

const server = new McpServer({
  name: "fish-tank-calculator",
  version: "1.0.0",
});

server.registerTool(
  "get_salt",
  {
    title: "Get salt for a fish tank",
    description:
      "Get salt for a fish tank",
    inputSchema: z.object({
      length: z.number().describe("The length of the fish tank, cm"),
      width: z
        .number()
        .describe(
          "The width of the fish tank, cm"
        ),
      height: z
        .number()
        .describe(
          "The height of the fish tank, cm"
        ),
      salinity: z
        .number()
        .describe(
          "The salinity of the fish tank, in ‰"
        ),
    }),
  },
  async (input) => {
    const salt = getSalt(input.length, input.width, input.height, input.salinity);
    const result = `缸体盛水${input.length}cm × 宽${input.width}cm × 高${input.height}cm = ${input.length * input.width * input.height}L,需要${salt}g海盐。`;
    return {
      content: [
        {
          type: "text",
          text: result,
        },
      ],
      structuredContent: {
        content: [
          {
            type: "text",
            text: result,
          },
        ],
      }
    };
  }
);

async function main() {
  const transport = new StdioServerTransport();
  await server.connect(transport);
}

main().catch((error) => {
  console.error(error);
  process.exit(1);
});

src 目录下新增 utils.ts

export function getWaterVolumeInLiters(
  length: number,
  width: number,
  height: number
) {
  return Math.floor((length * width * height) / 1000);
}

export function getSalt(
  length: number,
  width: number,
  height: number,
  salinity: number
) {
  // 「容器水量(公升) = 长(cm) × 宽(cm) ×高(cm) ÷ 1000」
  // 「盐的份量(g) = 盐分浓度(%) × 容器水量(g) ÷ 100」
  const tankVolume = getWaterVolumeInLiters(length, width, height);
  return (salinity / 1000) * (tankVolume * 1000);
}

结果

总结

本文介绍了如何使用 MCP(Model Context Protocol)协议开发一个实用的工具服务器。主要内容包括:

  1. 开发环境搭建:通过 npm 初始化项目,安装 @modelcontextprotocol/sdkzod 等必要依赖,配置 TypeScript 编译和 MCP Inspector 调试工具。

  2. 基础 MCP Server 实现:使用 StdioServerTransport 作为传输层,通过 registerTool 方法注册工具。重点说明了 structuredContent 字段的重要性,当定义了 outputSchema 时,该字段必须存在并符合模式。

  3. 鱼缸盐度计算工具开发:将示例的天气查询工具改造为鱼缸盐度计算工具,实现了:

  4. 输入参数:鱼缸的长、宽、高(单位:cm)和盐度(单位:‰)
  5. 计算逻辑:根据容器水量公式和盐分浓度公式计算所需海盐重量
  6. 输出结果:返回包含计算结果的中文描述文本

  7. 配置与使用:将 MCP Server 配置到 Cursor 编辑器中,通过 JSON 配置文件指定命令和参数,实现与 AI 助手的集成。

通过这个实践案例,展示了 MCP 协议在实际场景中的应用,为开发自定义工具服务器提供了完整的参考示例。