保姆级MCP开发上手指南

超详细MCP开发指南,有手就会,开发属于自己的MCP吧。

什么是MCP

MCP官方文档modelcontextprotocol.io/quickstart

用户视角 :MCP与传统的Chat对话服务系统不同,它可以直接处理、编辑和分析用户本地的文件,就像拥有一位能够亲自处理本地文件的"AI PC私人助理"!

技术视角 :MCP(Model Context Protocol)本质上是一个允许AI模型安全访问外部工具和数据的协议。然而,它的意义远不止于此------它是连接AI能力与现实世界的桥梁。通过MCP,AI助手可以"看到 "文件系统、"操作 "数据库以及"使用 "各种工具,这极大地扩展了AI的实际应用范围。在稳定性方面,MCP拥有官方服务和社区贡献,已经建立了标准协议、软件开发工具包(SDK)和安全框架,同时也正在形成新的软件范式。举个例子,当您想将一些指定的后缀文件放入到一个新的文件夹时,只需要输入给MCP一行指令,便可以直接帮您省略终端输入命令行的流程

一、环境准备

step1: 检查环境

目前mcp官方提供了NodeJs 和 Python 两个语言版本的开发SDK,配套工具都很成熟。下面以Node举例。

js 复制代码
# 检查环境,node版本需要>=16,建议>=20
node --version

# 如果没有node,推荐安装nvm,进行node版本管理。
# 安装 nvm (Node 版本管理器)
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash

Step 2:创建项目存在以下三种方法

方法一: 使用create-typescript-server 工具快速初始化一个,快速记笔记的小工具(是一个通过mcp-server 链接 local data sources 的例子)

js 复制代码
# Create a new server in the directory `my-server`
npx @modelcontextprotocol/create-server my-server

# With options
npx @modelcontextprotocol/create-server my-server --name "My MCP Server" --description "A custom MCP server"

推荐使用这个方法。

方法二: 参考quickstart文档。 以下是基于node实现一个图片读取的MCP为例:

1、需要先创建文件:

js 复制代码
// 创建项目的文件夹
mkdir image-processor
cd image-processor

// 初始化项目(执行完后,项目中会初始化相关的文件)
npm init -y

// 创建开发文件(MCP的逻辑在这个文件编写)
mkdir src
touch src/index.ts

2、 配置 package.json 开发MCP时需要借助于一些外部依赖库的能力进行文件读写,需要在配置​package.json后,​npm install安装。

json 复制代码
{
  "name": "@block/image-processor",
  "version": "1.0.0",
  "description": "A MCP server for image",
  "type": "module",
  "bin": {
    "image-processor": "./build/index.js"
  },
  "scripts": {
    "build": "tsc && chmod +x build/index.js",
    "prepublishOnly": "npm run build"
  },
  "files": [
    "build"
  ],
  "dependencies": {
    "@modelcontextprotocol/sdk": "^1.0.4",
    "axios": "^1.7.9",
    "form-data": "^4.0.1",
    "fs": "^0.0.1-security",
    "os": "^0.1.2",
    "path": "^0.12.7",
    "zod": "^3.24.1",
    "zod-to-json-schema": "^3.24.1"
  },
  "devDependencies": {
    "@types/node": "^22.10.2",
    "typescript": "^5.7.2"
  },
  "publishConfig": {
    "access": "public"
  },
  "keywords": [
    "mcp",
    "image"
  ]
}

如果是基于​typescript实现,还需配置​tsconfig.json

json 复制代码
{
    "compilerOptions": {
      "target": "ES2022",
      "module": "Node16",
      "moduleResolution": "Node16",
      "outDir": "./build",
      "rootDir": "./src",
      "strict": true,
      "esModuleInterop": true,
      "skipLibCheck": true,
      "forceConsistentCasingInFileNames": true,
    },
    "include": ["src/**/*"],
    "exclude": ["node_modules"]
  }

方法三: 从官方的server汇总仓库里fork一个你感兴趣的server,然后在上面二开

二、 开发MCP

MCP的逻辑主要在​src/index.ts中实现。以图片阅读MCP为例,编写代码如下,代码逻辑在此不赘述。 关于你想尝试实现的任何功能,都可以利用AI coding完成

js 复制代码
#!/usr/bin/env node

import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { zodToJsonSchema } from "zod-to-json-schema";
import {
  CallToolRequestSchema,
  ListToolsRequestSchema,
  ToolSchema,
} from "@modelcontextprotocol/sdk/types.js";
import { z } from "zod";
import path from "path";
import fs from "fs/promises";
import os from "os";

const ToolInputSchema = ToolSchema.shape.inputSchema;
type ToolInput = z.infer<typeof ToolInputSchema>;

const args = process.argv.slice(2);
if (args.length === 0) {
  console.error(
    "Usage: mcp-server-filesystem <allowed-directory> [additional-directories...]"
  );
  process.exit(1);
}

// Normalize all paths consistently
function normalizePath(p: string): string {
  return path.normalize(p);
}

function expandHome(filepath: string): string {
  if (filepath.startsWith("~/") || filepath === "~") {
    return path.join(os.homedir(), filepath.slice(1));
  }
  return filepath;
}

// Store allowed directories in normalized form
const allowedDirectories = args.map((dir) =>
  normalizePath(path.resolve(expandHome(dir)))
);

// Validate that all directories exist and are accessible
await Promise.all(
  args.map(async (dir) => {
    try {
      const stats = await fs.stat(dir);
      if (!stats.isDirectory()) {
        console.error(`Error: ${dir} is not a directory`);
        process.exit(1);
      }
    } catch (error) {
      console.error(`Error accessing directory ${dir}:`, error);
      process.exit(1);
    }
  })
);

/** 读取图片的工具 */
const ReadImageArgsSchema = z.object({
  path: z.string(),
});

// Create server instance
const server = new Server(
  {
    name: "image-processor",
    version: "1.0.0",
  },
  {
    capabilities: {
      tools: {},
    },
  }
);

// List available tools
server.setRequestHandler(ListToolsRequestSchema, async () => {
  return {
    tools: [
      {
        name: "read-image",
        description: "通过图片路径读取图片",
        inputSchema: zodToJsonSchema(ReadImageArgsSchema) as ToolInput,
      },
    ],
  };
});

// Handle tool execution
server.setRequestHandler(CallToolRequestSchema, async (request) => {
  const { name, arguments: args } = request.params;

  try {
    if (name === "read-image") {
      const parsed = ReadImageArgsSchema.safeParse(args);
      if (!parsed.success) {
        throw new Error(`Invalid arguments for read_file: ${parsed.error}`);
      }
      // 获取图片路径
      const imagePath = parsed.data.path;
      // 读取图片文件为 Buffer
      const imageBuffer = await fs.readFile(imagePath);
      // 转换为 base64
      const base64String = imageBuffer.toString("base64");
      // 获取文件扩展名并确定 MIME 类型
      const ext = path.extname(imagePath).toLowerCase();
      const mimeType =
        {
          ".png": "image/png",
          ".jpg": "image/jpeg",
          ".jpeg": "image/jpeg",
          ".gif": "image/gif",
          ".webp": "image/webp",
        }[ext] || "application/octet-stream";

      return {
        content: [
          {
            type: "image",
            data: base64String,
            mimeType: mimeType,
          },
        ],
      };
    } else {
      throw new Error(`Unknown tool: ${name}`);
    }
  } catch (error) {
    console.error("Error in request handler:", error);

    if (error instanceof z.ZodError) {
      throw new Error(
        `Invalid arguments: ${error.errors
          .map((e) => `${e.path.join(".")}: ${e.message}`)
          .join(", ")}`
      );
    }
    throw new Error(`Failed to fetch image`);
  }
});

// Start the server
async function main() {
  const transport = new StdioServerTransport();
  await server.connect(transport);
  console.error("Image Processor Server running on stdio");
}

main().catch((error) => {
  console.error("Fatal error in main():", error);
  process.exit(1);
});

三、本地验证

3.1 本地打包构建

本地开发完成后,还需要打包构建,才能接入大模型客户端使用(比如Cursor等) 由于已经在​package.json配置了打包构建的相关信息,执行​npm run build,可以在src的同级目录发现一个新的 build 文件夹,内部包含一个index.js文件。

3.2 验证

方式一: 直接将本地构建的产物接入大模型客户端。在客户端配置文件中增加本地已经构建的MCP入口文件,看是否能正常使用该MCP。(如果仅需要自己本地使用,那么到这一步就可以了,不需要发布流程)

js 复制代码
// args 1为我本地项目的打包构建后的地址,args 2为用户给这个MCP提供的参数,代表提供权限的路径,使用方式与filesystem MCP一致
"image-processor": {
	"command": "node",
	"args": [
		"/Users/ruixingshi/project/image-processor/build/index.js",
		"/Users/ruixingshi/Documents"
	]
}

方式二: 使用​@modelcontextprotocol/inspector 连接本地开发好的MCP,在本地Server中看是否可以正常读取MCP的tool。在控制台执行命令:

js 复制代码
/* 全局安装 @modelcontextprotocol/inspector */
npm install -g @modelcontextprotocol/inspector 

/* 运行: */
npx @modelcontextprotocol/inspector node path/to/server/index.js args...


// 解释: path/to/server/index.js 是你的mcp工程在你终端的相对路径

// args 是需要传入的参数: 例如你写的MCP需要创建文档到你的电脑文档层 就是: /Users/xxx/Documents。 需要改为你自己电脑的路径


// 举例
npx @modelcontextprotocol/inspector /Users/xxx/project/build/index.js /Users/xxx/Documents

运行成功后会启动一个5173端口的界面,启动的本地页面http://localhost:5173

点击Connect之后 Run Tool,就可以进行调试了。

方式三: 在Claude桌面端中调试 目前Claude 注册需要国外手机号验证码,本文作者并未验证,后续更新。

四、发布

可以将本地代码发布为npm包,允许其他用户在大模型客户端上通过配置直接接入,无需手动下载任何代码。

js 复制代码
​npm publish

效果:

写一个记笔记的MCP,效果如下:

思考:

MCP这种以大模型工具为中心的交互范式,未来是有可能颠覆之前大家在各个web页面使用各种工具的范式的,我们需要积极拥抱和探索。

从前端的视角简单类比下:

Claude Desktop < ---------------> 浏览器

MCP server <---------------> 各种站点 & 工具(devtools\mep 等等)

本地数据/远端接口 <---------------> 后端接口

相关推荐
NAGNIP10 小时前
一文搞懂深度学习中的通用逼近定理!
人工智能·算法·面试
冬奇Lab11 小时前
一天一个开源项目(第36篇):EverMemOS - 跨 LLM 与平台的长时记忆 OS,让 Agent 会记忆更会推理
人工智能·开源·资讯
冬奇Lab11 小时前
OpenClaw 源码深度解析(一):Gateway——为什么需要一个"中枢"
人工智能·开源·源码阅读
AngelPP15 小时前
OpenClaw 架构深度解析:如何把 AI 助手搬到你的个人设备上
人工智能
宅小年15 小时前
Claude Code 换成了Kimi K2.5后,我再也回不去了
人工智能·ai编程·claude
九狼15 小时前
Flutter URL Scheme 跨平台跳转
人工智能·flutter·github
ZFSS15 小时前
Kimi Chat Completion API 申请及使用
前端·人工智能
天翼云开发者社区16 小时前
春节复工福利就位!天翼云息壤2500万Tokens免费送,全品类大模型一键畅玩!
人工智能·算力服务·息壤
知识浅谈16 小时前
教你如何用 Gemini 将课本图片一键转为精美 PPT
人工智能
Ray Liang17 小时前
被低估的量化版模型,小身材也能干大事
人工智能·ai·ai助手·mindx