从零开始:打造基于MCP的API封装服务

创建MCP服务器

我们选择用nodejs来实现bitfactory-mcp这个MCP服务器。

使用MCP官方提供的命令行工具来创建MCP服务器:

sql 复制代码
npx @modelcontextprotocol/create-server bitfactory-mcp

在终端执行上面命令,按照提示输入要创建的MCP服务器信息:

进入创建的MCP服务器目录,安装项目依赖:

打开创建好的MCP服务器,可以看到默认生成的项目代码结构:

src/index.ts 是 MCP 服务器的源码文件,在此文件实现 MCP 服务器的业务功能,build/js 是 MCP 服务器源码编译后的可执行文件,调试阶段和发布上线,都要用到此文件。

调试MCP服务器

在 MCP 服务器项目目录下,运行 npm run watch 启动一个监听服务,监听 src/index.ts 源码文件的内容变动,并实时编译成 build/index.js 可执行文件。

在 MCP 服务器项目目录下,运行 npm run inspector,实际执行的命令是:

此命令用到了 MCP 官方开发的一个调试工具,运行 MCP 服务器可执行文件,连接到 MCP 服务器进行功能调试。

点击调试面板运行地址,进入 MCP 服务器调试面板。

在 MCP 服务器调试面板,点左侧的 Connect 可以连接到 MCP 服务器进行调试,显示 Connected 表示连接成功。

可以设置 MCP 服务器启动参数 Arguments 和环境变量 Environment Variables,在 MCP 服务器实现逻辑中可以读取这两部分的参数值。

MCP 服务器调试面板右侧主要用于请求 MCP 服务器内部定义的资源(Resources)、提示词(Prompts)、工具(Tools)等内容。

我们可以在右侧的 Tools 栏目点 List Tools 获取 MCP 服务器内部实现的所有工具(Tools)。

实现MCP服务器业务逻辑

  1. 定义MCP服务器信息

在src/index.ts文件,通过new Server创建MCP服务器信息(实现tools能力)。

vbscript 复制代码
const server = new Server(
  {
    name: "bitfactory-mcp",
    version: "0.0.1",
  },
  {
    capabilities: {
      tools: {},
    },
  }
);
  1. 定义MCP服务器工具列表

修改 src/index.ts 文件中默认生成的获取工具列表(ListTools)的逻辑,定义一个 getAccount 方法,写上功能描述,说明这个工具(Tool)的作用是获取链上某个账户的信息。

getAccount 工具的参数依据星火链API文档来定义,其中apikey是必填项,api-secret根据用户定义来决定是否要填写,勾选就必须传,否则就不需要。

星火链获取链上某个账户的信息API文档如下图:

ListTools 的定义如下:

php 复制代码
server.setRequestHandler(ListToolsRequestSchema, async () => {
  return {
    tools: [
      {
        name: "getAccount",
        description: "获取链上某个账户的信息",
        inputSchema: {
          type: "object",
          properties: {
            apiKey: {
              type: "string",
              description: "API key for authentication"
            },
            apiSecret: {
              type: "string",
              description: "header apiSecret"
            },
            address: {
              type: "string",
              description: "账户地址"
            },
            domainid: {
              type: "string",
              description: "对应域节点的信息"
            },
            height: {
              type: "number",
              description: "选填,指定查询某一区块高度时的账户状态"
            },
            key: {
              type: "string",
              description: "查询账户中metadatas下的某个关键字数据"
            }
            
          },
          required: ["apiKey", "apiSecret", "address", "domainid", "height", "key"]
        }
      }
]
  };
});
  1. 实现MCP服务器工具(Tool)逻辑

我们先创建一个新的文件,src/client.ts,定义一个BitFactoryClient类,实现sendGet和getAccount方法,其中sendGet方法是查询类(GET请求)API的通用方法,getAccount方法是获取链上某个账户信息的方法。

typescript 复制代码
export class BitFactoryClient{
    
private readonly apiUrl: string;

/**
   * Create a new Query client
   * @param apiUrl - The API URL
   */
constructor({ apiUrl }: { apiUrl: string }) {
    this.apiUrl = apiUrl;
  }
/**
   * Send a query request
   * @param apiSecret - The API secret (optional)
   * @returns The response from the API
   */
async sendGet({ apiSecret, path, params }: { apiSecret?: string, path?: string, params?: Record<string, string | number | boolean> } = {}): Promise<string> {
    try {
      const headers: Record<string, string> = {
        'accept': '*/*'
      };
      if (apiSecret) {
        headers['api-secret'] = apiSecret.trim();
      }
      
      let url = this.apiUrl;
      if (path) {
        url += `/${path}`;
      }
      if (params) {
        const queryParams = new URLSearchParams();
        Object.entries(params).forEach(([key, value]) => {
          if (value !== undefined) {
            queryParams.append(key, String(value));
          }
        });
        const queryString = queryParams.toString();
        if (queryString) {
          url += `?${queryString}`;
        }
      }
      const response = await fetch(url, {
        method: "GET",
        headers
      });
    
      const responseText = await response.text();
      
      if (!response.ok) {
        throw new Error(`Request failed with status ${response.status}: ${response.statusText}\nResponse: ${responseText}`);
      }
      return responseText;
      
    } catch (error: unknown) {
      if (error instanceof Error) {
        throw new Error(`Query request failed: ${error.message}`);
      }
      throw new Error('Query request failed: Unknown error');
    }
  }
  
/**
 * Get account information
 * @param apiSecret - The API secret
 * @param address - The account address
 * @param domainid - The domain ID
 * @param height - The block height
 * @param key - The key to query
 * @returns The account information
 */
async getAccount({apiSecret, address, domainid, height, key }: 
  { apiSecret: string, address: string, domainid?: string, height?: number, key?: string }): Promise<string> {
    try {
      if (!address) {
        throw new Error("Account address is required");
      }
      const params: Record<string, string | number> = {
        address
      };
      
      if (domainid !== undefined) {
        params.domainid = domainid;
      }
      if (height !== undefined) {
        params.height = height;
      }
      if (key !== undefined) {
        params.key = key;
      }
      const responseText = await this.sendGet({
        apiSecret: apiSecret, 
        path: 'getAccount',
        params
      });
      return responseText;
      
    } catch (error: unknown) {
      if (error instanceof Error) {
        throw new Error(`Get chain account failed: ${error.message}`);
      }
      throw new Error('Get chain account failed: Unknown error');
    }
 }

然后,修改CallTool的逻辑,接到getAccount请求时,先获取参数,校验必填参数后,再调用QueryClient的getAccount方法。

typescript 复制代码
server.setRequestHandler(CallToolRequestSchema, async (request) => {
  switch (request.params.name) {
    case "getAccount": {
      const args = request.params.arguments;
      if (!args || typeof args !== 'object') {
        throw new Error("Invalid arguments provided");
      }
      
      const apiKey = String(args.apiKey || "").trim();
      const apiSecret = String(args.apiSecret || "").trim();
      const address = String(args.address || "").trim();
      const domainid = args.domainid !== undefined ? String(args.domainid) : undefined;
      const height = args.height !== undefined ? Number(args.height) : undefined;
      const key = args.key !== undefined ? String(args.key) : undefined;
      if (!apiKey) {
        throw new Error("apiKey is required");
      }
      if (!address) {
        throw new Error("address is required");
      }
      
      const baseUrl = `https://bif-testnet.bitfactory.cn/base/${apiKey}`;
      const bitFactoryClient = new BitFactoryClient({ apiUrl: baseUrl });
      
      try {
        const result = await bitFactoryClient.getAccount({apiSecret, address, domainid, height, key });
        const jsonResult = JSON.parse(result);
        return {
          content: [{
            type: "text",
            text: JSON.stringify(jsonResult, null, 2)
          }]
        };
      } catch (error: unknown) {
        if (error instanceof Error) {
          throw new Error(`Get chain account failed: ${error.message}`);
        }
        throw new Error('Get chain account failed: Unknown error');
      }
}
    default:
      throw new Error("Unknown tool");
  }
});
  1. 调试MCP服务器工具(Tool)

在调试工具选择 getAccount工具,填入参数,点击 Run Tool,查看工具的调用结果:

在MCP客户端测试

使用Cursor编辑器,作为 MCP 客户端。来测试我们开发的 bitfactory-mcp 服务器。

首先,打开 Cursor 的 MCP 服务器配置文件。

json 复制代码
{
  "mcpServers": {
    "bitfactory-mcp":{
      "command":"node",
      "args":["build/index.js 的绝对路径"]
    }
  }
}

写入连接 bitfactory-mcp 的配置:

args 里面填写 bitfactory-mcp 服务编译后文件的绝对路径。

在 Cursor 的 MCP 配置面板,可以看到bitfactory-mcp 服务已经成功运行,并且获取到了可用的 Tools。

打开 Cursor AI 对话面板,选择 Agent 模式,输入以下或类似内容:

Cursor 会加载所有已配置的可用的 Tools 进行意图识别,识别到意图是获取链上账户信息,会自动调用 bitfactory-mcp 服务的 getAccount 工具。

调用结果如下:

相关推荐
zfelix5 小时前
别在 MCP、Skills、Subagent 里挑一个——它们根本不是同一层的东西
mcp
deepseek235 小时前
Tenable联合OpenAI做AI Inspector:第三方Agent、Skill与MCP组件如何过供应链验收
人工智能·ai agent·mcp
AIGC大时代5 小时前
Claude 科研栈拆解:Connectors 给文献视力,Skills 把 SOP 变成可调用流程
claude·学术写作·mcp·agent skills·科研工作流
xrlfreedom7 小时前
大厂 MCP 面试实录:桌面客户端 stdio MCP Server 调试与安全加固方案设计
docker·结构化输出·mcp·提示注入防护
xrlfreedom7 小时前
大厂 MCP 面试实录:Tool 调用身份认证与最小权限设计
mcp·rag 知识库·向量检索与重排·typescript mcp sdk
Akiyama_Mio-Kon8 小时前
CVE-2026-85654 深度解读:DynamoDB MCP Server 如何把数据模型风险带到 CDK 部署宿主
aws·dynamodb·cdk·mcp·cve-2026-85654·lac·agent 安全
gsls2008081 天前
告别 Vault 的复杂度:用 Go 标准库给 Windows 凭据管理器装上 MCP
windows·golang·mcp
极小狐1 天前
极狐GitLab Duo 功能更新:扩展 MCP 工具集、支持 MR 事件触发
运维·gitlab·agent·mr·极狐gitlab·mcp·极狐gitlab duo
xrlfreedom2 天前
大厂 MCP 面试实录:基于 OpenTelemetry 与 OAuth 2.1 的可重复集成测试方案设计
opentelemetry·mcp·oauth 2.1
吴佳浩3 天前
为什么每个人最终都会使用 Agent?从 LLM 到 Agent,看懂 AI 为什么一定会走向执行时代
llm·agent·mcp