🚀🚀🚀 MCP SDK 快速接入 DeepSeek 并添加工具!万万没想到MCP这么简单好用!

前言

上次发的文章因为遇到 bug 没有执行完,现在正常了,其实是我传错参数了,所以重新修正下!

这篇文章记录一下我用 MCP TypeScript SDK 实现一个自包含的 AI 聊天应用的过程:内部包含 MCP 服务器提供上下文,客户端拿上下文再去调 LLM 接口拿回答!

往期精彩推荐

正文

MCP 是什么?

简单说,MCP 是一个给 AI 应用提供上下文的标准协议。你可以把它理解成一个服务标准,它规定了"资源"和"工具"的接口规范,然后通过客户端连接这些接口,就可以组合出丰富的上下文数据。比如说资源可以是"当前时间"、"用户历史记录",工具可以是"数据库搜索"、"调用外部 API"。

它采用的是客户端-服务器架构,Server 暴露上下文能力,Client 拉取这些上下文,再拿去调语言模型生成回答,而 Transport 负责 ServerClient 的通信部分!

(AI 帮我画的图)

其中图片中的 Transport 层还分为:

  • StdioServerTransport:用于 CLI 工具对接 stdin/stdout
  • SSEServerTransport:用于HTTP通信
  • StdioClientTransport:客户端以子进程方式拉起服务端,这个不常用

另外,Server 层分为:

  • Server 基本类:原始的类,适合自己定制功能!
  • McpServer基于Server 封装好了可以快速使用的方法!

注意:基本类和封装类的接口有很大不同,具体请参看 README 文件!

安装依赖

用的是官方的 TypeScript SDK

仓库:github.com/modelcontex...

官网:modelcontextprotocol.io

bash 复制代码
npm install @modelcontextprotocol/sdk axios

DeepSeek 没有官方 SDK,用的是 HTTP API,所以需要 axios

记得把 API Key 放到 .env 或直接配置成环境变量,我用的 DEEPSEEK_API_KEY

实现一个 McpServer

我们先实现一个本地 McpServer,实现两个东西:

  • 当前时间(资源)
  • 本地"知识库"搜索(工具)

代码如下:

ts 复制代码
// src/server.js
import {
  McpServer,
  ResourceTemplate,
} from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";

const facts = [
  "公理1: 生存是文明的第一需要.",
  "公理2: 文明不断增长和扩张,但宇宙中的物质总量保持不变.",
].map((f) => f.toLowerCase());
try {
  const server = new McpServer({
    name: "mcp-cli-server",
    version: "1.0.0",
  });

  // 使用 Zod 定义工具的输入模式
  server.tool(
    "search_local_database",
   {
      query: z.string(),
    },
    async ({ query }) => {
      console.log("Tool called with query:", query);
      const queryTerms = query.toLowerCase().split(/\s+/);
      const results = facts.filter((fact) =>
        queryTerms.some((term) => fact.includes(term))
      );
      return {
        content: [
          {
            type: "text",
            text: results.length === 0 ? "未找到相关公理" : results.join("\n"),
          },
        ],
      };
    }
  );


  // 定义资源
  server.resource(
    "current_time",
    new ResourceTemplate("time://current", { list: undefined }),
    async (uri) => ({
      contents: [{ uri: uri.href, text: new Date().toLocaleString() }],
    })
  );

  await server.connect(new StdioServerTransport());
  console.log("Server is running...");
} catch (err) {
  console.error("Server connection failed:", err);
}

这样一来,我们的服务端就能通过 MCP 协议对外暴露两个上下文能力了。

配置 MCP Client

MCP 的客户端用来连接服务器并获取资源或调用工具:

ts 复制代码
// src/client.js;
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";

export async function createClient() {
  const client = new Client({
    name: "Demo",
    version: "1.0.0",
  });

  const transport = new StdioClientTransport({
    command: "node",
    args: ["src/server.js"],
  });

  try {
    await client.connect(transport);
    console.log("Client connected successfully");
  } catch (err) {
    console.error("Client connection failed:", err);
    throw err;
  }

  // 可选:添加客户端方法调用后的调试
  return client;
}

连上之后,我们就可以开始调用服务端的资源和工具了。

获取上下文

我们设定一个简单的逻辑:每次用户提问,客户端都会获取当前时间;如果问题里包含 公理,那就调用搜索工具查一下本地知识库:

ts 复制代码
async function getContext(client, question) {
  let currentTime = "";
  let additionalContext = "";

  try {
    const resources = await client.readResource(
      { uri: "time://current" },
      { timeout: 15000 }
    ); // 增加超时时间
    console.log("Resources response:", resources);
    currentTime = resources.contents[0]?.text ||
      new Date().toLocaleString(); // 注意:resources 直接包含 contents
  } catch (err) {
    console.error("Resource read error:", err);
    currentTime = new Date().toLocaleString();
  }

  if (question.toLowerCase().includes("公理")) {
    console.log("Searching for axioms...", question);
    try {
      const result = await client.getPrompt({
        name: "search_local_database",
        arguments: { query: question },
      });
      console.log("Tool result:", result);
      additionalContext = result?.[0]?.text || "No results found.";
    } catch (err) {
      console.error("Tool call error:", err);
      additionalContext = "Error searching database.";
    }
  }

  return { currentTime, additionalContext };
}

集成 DeepSeek,开始问答

DeepSeek 使用的是标准 OpenAI 接口风格,HTTP POST 请求即可。这里我们用 axios 调用:

js 复制代码
import axios from "axios";

async function askLLM(prompt) {
  try {
    console.log("Calling LLM with prompt:", prompt);
    const res = await axios.post(
      "https://api.deepseek.com/chat/completions",
      {
        model: "deepseek-chat",
        messages: [{ role: "user", content: prompt }],
        max_tokens: 2048,
        stream: false,
        temperature: 0.7,
      },
      {
        headers: {
          Authorization: `Bearer ${process.env.DEEPSEEK_API_KEY}`,
          "Content-Type": "application/json",
        },
        timeout: 1000000,
      }
    );
    console.log("LLM response:", res.data);
    return res.data.choices[0].message.content;
  } catch (err) {
    console.error("LLM error:", err);
    return "Error calling LLM.";
  }
}

完整的代码,包含用命令行做一个简单的交互界面:

ts 复制代码
// src/index.js
import readline from "readline";
import axios from "axios";
import { createClient } from "./client.js";
import { DEEPSEEK_API_KEY } from "./config.js";

async function askLLM(prompt) {
  try {
    console.log("Calling LLM with prompt:", prompt);
    const res = await axios.post(
      "https://api.deepseek.com/chat/completions",
      {
        model: "deepseek-chat",
        messages: [{ role: "user", content: prompt }],
        max_tokens: 2048,
        stream: false,
        temperature: 0.7,
      },
      {
        headers: {
          Authorization: `Bearer ${DEEPSEEK_API_KEY}`,
          "Content-Type": "application/json",
        },
        timeout: 1000000,
      }
    );
    return res.data.choices[0].message.content;
  } catch (err) {
    console.error("LLM error:", err);
    return "Error calling LLM.";
  }
}

async function getContext(client, question) {
  let currentTime = "";
  let additionalContext = "";

  try {
    const resources = await client.readResource(
      { uri: "time://current" },
      { timeout: 15000 }
    ); // 增加超时时间
    currentTime = resources.contents[0]?.text || new Date().toLocaleString(); // 注意:resources 直接包含 contents
  } catch (err) {
    console.error("Resource read error:", err);
    currentTime = new Date().toLocaleString();
  }

  if (question.toLowerCase().includes("公理")) {
    try {
      // const result = await client.getPrompt({
      //   name: "search_local_database",
      //   arguments: { query: question },
      // });
      
      const toolResult = await client.callTool({
        name: "search_local_database",
        arguments: { query: question },
      });
      console.log("Tool result:", toolResult);
      additionalContext = toolResult?.content?.[0]?.text || "No results found.";
    } catch (err) {
      console.error("Tool call error:", err);
      additionalContext = "Error searching database.";
    }
  }

  return { currentTime, additionalContext };
}

const rl = readline.createInterface({
  input: process.stdin,
  output: process.stdout,
});

const client = await createClient();

while (true) {
  const question = await new Promise((resolve) =>
    rl.question("You: ", resolve)
  );
  if (question.toLowerCase() === "exit") {
    console.log("Exiting...");
    rl.close();
    process.exit(0);
  }
  // 使用上下文
  const context = await getContext(client, question);
  // 不使用上下文
  // const context = {};
  const prompt = `Time: ${context.currentTime}\nContext: ${context.additionalContext}\nQ: ${question}\nA:`;
  console.log("Prompt:", prompt);
  const answer = await askLLM(prompt);
  console.log('Assistant:', answer);
}

接着在终端运行:

bash 复制代码
# 启动服务器
node src/server.js
bash 复制代码
# 启动客户端
node src/index.js

运行结果:

一些注意点

这个项目虽然小,但也踩了些坑,顺便分享几点:

  • MCP SDK 的 server 和 client 都是异步启动的,别忘了加上 await connect()
  • 工具的入参和 schema 必须严格匹配,否则会抛错。

下面是我的目录结构,做个参考吧!

css 复制代码
mcp-mini/
├── package.json
├── src/
│   ├── client.js
│   ├── server.js
│   └── index.js

最后

总的来说,MCP TypeScript SDK 用起来还是挺顺的,适合做一些轻量、模块化、支持上下文的 AI 应用。这种服务 + 客户端 + LLM 的组合模式挺适合本地测试,也方便后续扩展别的服务。

今天的分享就到这了,如果文章中有啥错误,欢迎指正!

往期精彩推荐

相关推荐
牛奶4 小时前
2026年大模型怎么选?前端人实用对比
前端·人工智能·ai编程
牛奶4 小时前
前端人为什么要学AI?
前端·人工智能·ai编程
KEEN的创享空间10 小时前
AI编程从0到1之10X提效(Vibe Coding 氛围式编码 )09篇
openai·ai编程
AlienZHOU11 小时前
为 AI Agent 编写高质量 Skill:Claude 官方指南
agent·ai编程·claude
恋猫de小郭11 小时前
移动端开发稳了?AI 目前还无法取代客户端开发,小红书的论文告诉你数据
前端·flutter·ai编程
KaneLogger13 小时前
【翻译】打造 Agent Skills 的最佳实践
agent·ai编程·claude
王小酱13 小时前
Everything Claude Code 文档
openai·ai编程·aiops
雮尘14 小时前
如何在非 Claude IDE (TARE、 Cursor、Antigravity 等)下使用 Agent Skills
前端·agent·ai编程
刘贺同学14 小时前
Day12-龙虾哥打工日记:OpenClaw 子 Agent 到底看到了什么?
aigc·ai编程
程序员鱼皮16 小时前
离大谱,我竟然在 VS Code 里做了个视频!
github·aigc·ai编程