普通视图

发现新文章,点击刷新页面。
今天 — 2025年4月19日首页

✈️ Colipot Agent + 🔥 MCP Tools = 让你的编程体验直接起飞🚀

2025年4月18日 21:43

前言

上篇文章主要介绍了使用 Mcp ts Sdk 搭建一个 MCP 应用,我们已经知道下面的概念和基本使用!

复习下 MCP 架构

我们并且在建立 Server 时添加了一个tool,根据关键字 公理 返回三体黑暗森林法则的两个公理!

命中了关键词

往期精彩推荐

正文

今天我们的主要目标是:将这个工具注册到 VsCode 中,并可以在 Agent 模式中使用它!

添加 Mcp Server

Ctrl+Shift+PCmd+Shift+P,输入并选择 MCP: Add Server

Ctrl+Shift+P

或者你也可以在 Colipot 中点击工具图标进入:

点击工具图标

添加 server

选择合适的类型

接着会让你选择服务类型,如果你是在本地,推荐使用 Stdio

选择合适的类型

  • Stdio 的优势Stdio 传输适合本地开发,避免网络复杂性,适合测试和调试。
  • 其他传输方式:虽然 VSCode 还支持 SSE(Server-Sent Events),但 Stdio 更适合本地场景

配置文件

根据自己的服务类型输入启动命令,比我上次我用的 js,这里会输入: node

node 或者 python

连续回车两次,第一次是自动生成的tool唯一标识,第二次会让你选择设置,建议选择workspace settings

workspace settings

VSCode 会在根目录下生成.vscode/mcp.json如下的配置文件:

配置文件

args 对应的是参数,比如执行文件目标地址!

使用

打开 Copilot Agent 视图,然后使用自然语言调用 MCP 服务器的工具。

识别到 tool

我们输入框里输入相关语句,VsCode 会自动查找对应的 tool

运行之后的结果

最后

通过 VsCode Colipot Agent 提供的能力,我们可以让我们的编辑期更加智能的为我们提供服务!

好了今天的分享就些了,十分感谢大家的支持,如果文章后中有错误的地方,欢迎指正!

往期精彩推荐

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

2025年4月18日 21:26

前言

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

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

往期精彩推荐

正文

MCP 是什么?

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

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

MCP 架构

(AI 帮我画的图)

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

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

另外,Server 层分为:

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

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

安装依赖

用的是官方的 TypeScript SDK

仓库:github.com/modelcontex…

官网:modelcontextprotocol.io

npm install @modelcontextprotocol/sdk axios

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

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

实现一个 McpServer

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

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

代码如下:

// 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 的客户端用来连接服务器并获取资源或调用工具:

// 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;
}

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

获取上下文

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

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 调用:

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.";
  }
}

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

// 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);
}

接着在终端运行:

# 启动服务器
node src/server.js
# 启动客户端
node src/index.js

运行结果:

未命中关键词

命中了关键词

一些注意点

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

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

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

mcp-mini/
├── package.json
├── src/
│   ├── client.js
│   ├── server.js
│   └── index.js

最后

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

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

往期精彩推荐

昨天以前首页

🚀惊了,这个国产软件居然这么牛,比 uniapp 还全能❤️

2025年4月17日 10:32

最近跟同事闲聊,大家都在吐槽一个问题: ! App 是越做越像平台了,但开发却越做越痛苦了。

你想加个活动页,产品说今晚上线;
你想做个业务扩展,运营说要不你再写个低代码工具;
你想适配鸿蒙,领导说最好做个 React Native 得了;

同事活成了“加班工具人”,App 也做成了臃肿的 “功能集成器”。

难道开发一个通用的 App ,就非得这么累吗?

于是,我们试着去找更轻、更灵活的解决方案。

我们只是想做一个“活动页托管方案”,不想每次上线都发版,更不想因为临时需求牵扯整个开发团队。

但随着调研的深入,我们发现这种痛点其实根本不是“活动页”本身,而是:App 缺乏一个**“包容性很强的容器”**。

比如:

  • 新功能不用频繁发版;
  • 能复用现有页面或者组件;
  • 可以独立上线,不干扰主应用。

我们对比了几个方向:

  • WebView + H5:快是快,但弱得可怕,尤其是 JSBridge 管理地狱,体验不佳;
  • 低代码平台:适合特定场景,但定制性不足,复杂页面性能堪忧;
  • RN/Flutter 微模块化:维护成本太高,涉及太多客户端改动。

直到我在调研中遇到了 FinClip,才意识到这事完全可以换个方式。

让人眼前一亮的 FinClip

FinClip 是什么?

一句话说完:把小程序能力,通用化、标准化,塞进任何 App。

从技术架构来说,FinClip 提供的是一个极其轻量的小程序容器 SDK(3MB都不到),可以直接嵌进你的 iOSAndroidHarmonyOS App,甚至 React NativeFluttermacOS、车机都能跑。

强大的能力

开发者只要写一套小程序代码,放进去就能运行。不用重新适配底层系统,也不用改框架结构。

而且它兼容微信/支付宝/抖音小程序语法,意味着你过去写的项目,可能几乎零改动就能跑起来。

于是,我们立刻拉了群,软磨硬泡,搞来了二组同事开发的活动页项目,

这个是开源的活动页

需要的同学请戳这里:github.com/FernAbby/H5…

导入之后

然后通过 FinClip Studio 打包上传,再嵌入 App

FinClip Studio,真的有点香

讲真,刚开始用 FinClip Studio,我也做好了“将就一下”的心理准备。

结果没想到是真香警告。

首先,新建项目一键生成模板,跟微信小程序开发工具 99% 像;

创建工程

你也可以和我一样选择导入已有的项目,

导入之后

其次,模拟器支持多终端调试,拖拉缩放,全程无需真机;

另外,发布打包一条龙服务,你只需要上传至云端后台:

上传

输入相关上传信息:

上传信息

等待上传成功即可!

上传成功

后台是平台运营的“指挥中心”

接下来的重头戏,需要我们登陆后台系统,

一个超级应用不是靠开发者单打独斗,而是靠多个角色协同。FinClip 的后台做得非常细腻,功能齐全,不管是开发还是运维同学,都可以轻松驾驭!

首页后台

小程序管理模块,不仅可以新建、管理前面上传的小程序,还可以体验预览版、发布审核;

首先,在隐私设置栏目里设置隐私保护指引:

配置隐私设置

然后我们就可以配置审核版本或者体验版本了!

审核流程

体验发布

接着我们就可以直接跳转到待办中心通过审核!

通过审核

除此之外,常用的灰度发布、权限范围、自动上架全都支持;

小程序详情

数据分析清晰易读,不需要 BI 工具也能看懂;

数据看板

页面数据

让你不再为如何做好运维而发愁!

用了一周的真实感受

流程

使用一周多了,整体的流程是这样的:

  1. 本地写代码,IDE 模拟器预览;
  2. 上传代码,后台提交审核;
  3. 设置灰度策略,用户扫码体验;
  4. 最终发布上线。

优点

我们没改动原生代码,甚至没有重新接入任何 SDK,只是增加一个容器模块 + 几行配置。

团队有个原来的 RN 老项目,直接用 FinClip 的容器跑起来,居然都不用重写,兼容度真的惊人。

缺点

但是缺点也有:

比如,导入已有项目会进行检测,并且明确的告知用户,其实可以后台默认执行,用户体验会更好!

导入

检测过程

另外最主要的是,后台和编辑器的登陆状态是临时的,不会长期保持!每次登陆挺麻烦的

彩蛋

首先,FinClip 贴心的内置了 AI 助手,你使用过程遇到的任何问题都可以在这里找到答案!

内置的 AI 助手

最重要的是,FinClip 提供了基于接口的 AI 能力,可以通过 RAG 技术为小程序注入了智能化能力,涵盖内容生成、上下文交互、数据分析等多方面功能。

这不仅提升了用户体验,也为开发者提供了便捷的 AI 集成工具,进一步增强了 FinClip 生态的竞争力!

基于 RAG 的 AI 能力

总结

如果再给我造一次 App 的机会,我一定毫不犹豫地选择 FinClip

当我们从“做功能”切换到“建生态”,思路就会完全不一样:

  • App 不再是“巨石应用”,而是一个个业务模块的拼图
  • 小程序就像“微服务 UI 化”,能独立更新、上线、下架
  • 技术架构也从“一体化耦合”变成“解耦 + 动态加载”

FinClip 帮助开发者从“重复搬砖” 变成 “生态平台管理员”!

如果你也有和我一样的困惑,你也可以试试:

  • 把一个已有的活动页,用 FinClip 打包成小程序;
  • 嵌进你现有 App 中,再用 FinClip Studio 发布版本;
  • 后台配置白名单,手机扫码预览。

1 天内,你就能体验一把“做平台”的感觉。

时代正在变化。我们不该再为“发布一个功能”耗尽精力,而应该把更多时间留给真正重要的东西 —— 创新、体验、增长。

FinClip 不只是工具,更是重构开发者角色的机会。

你准备好了吗?

❌
❌