diff --git a/.github/workflows/pkg-pr-new.yml b/.github/workflows/pkg-pr-new.yml
index daa9984d5..9b58afdb8 100644
--- a/.github/workflows/pkg-pr-new.yml
+++ b/.github/workflows/pkg-pr-new.yml
@@ -3,11 +3,13 @@ on:
push:
branches: [main]
paths:
+ - 'packages/mcp-server/**'
- 'packages/site-components/**'
- 'packages/theme-generator/**'
pull_request:
branches: [main]
paths:
+ - 'packages/mcp-server/**'
- 'packages/site-components/**'
- 'packages/theme-generator/**'
- 'site/**'
@@ -30,7 +32,9 @@ jobs:
- run: pnpm --filter '@tdesign/site-components' --filter '@tdesign/theme-generator' run build
- - run: pnpm dlx pkg-pr-new publish './packages/site-components' './packages/theme-generator'
+ - run: pnpm --filter tdesign-mcp-server run build:mcp
+
+ - run: pnpm dlx pkg-pr-new publish './packages/site-components' './packages/theme-generator' './packages/mcp-server'
build-tdesign-vue-site:
if: github.repository == 'Tencent/tdesign' && github.event_name == 'pull_request'
diff --git a/.gitignore b/.gitignore
index 0edfaf6b9..96da86aed 100644
--- a/.gitignore
+++ b/.gitignore
@@ -16,6 +16,8 @@ dist
results
static_site
lib
+packages/mcp-server/docs
## test
-coverage
\ No newline at end of file
+coverage
+packages/mcp-server/scripts/dom/snapshots/**/__snapshots__
\ No newline at end of file
diff --git a/package.json b/package.json
index 23f78dde1..3e40687fb 100644
--- a/package.json
+++ b/package.json
@@ -11,7 +11,10 @@
"site": "pnpm run build:components && pnpm run --filter tdesign-site build",
"site:preview": "pnpm run build:components && pnpm run --filter tdesign-site site:preview",
"auto-release-collection": "pnpm run --filter auto-release-collection start",
- "prepare": "husky install"
+ "prepare": "husky install",
+ "build:mcp": "pnpm run --filter tdesign-mcp-server build:mcp",
+ "build:mcp-docs": "pnpm run --filter tdesign-mcp-server build:docs+dom",
+ "start:mcp": "pnpm run --filter tdesign-mcp-server start:mcp-http"
},
"license": "MIT",
"dependencies": {
diff --git a/packages/mcp-server/.env b/packages/mcp-server/.env
new file mode 100644
index 000000000..f9ab63b01
--- /dev/null
+++ b/packages/mcp-server/.env
@@ -0,0 +1,6 @@
+# =============================================
+# 设为 local,从项目的 docs 文件夹读取
+# 设为 online,从 CDN 上获取文档
+# =============================================
+
+DOCS_SOURCE_MODE = 'online'
diff --git a/packages/mcp-server/.eslintrc.js b/packages/mcp-server/.eslintrc.js
new file mode 100644
index 000000000..80abeded0
--- /dev/null
+++ b/packages/mcp-server/.eslintrc.js
@@ -0,0 +1,8 @@
+module.exports = {
+ root: true,
+ extends: ["plugin:@typescript-eslint/recommended"],
+ parser: "@typescript-eslint/parser",
+ plugins: ["@typescript-eslint"],
+ ignorePatterns: ["dist", "node_modules"],
+ rules: {},
+};
diff --git a/packages/mcp-server/DEVELOPMENT.md b/packages/mcp-server/DEVELOPMENT.md
new file mode 100644
index 000000000..b4cb1782e
--- /dev/null
+++ b/packages/mcp-server/DEVELOPMENT.md
@@ -0,0 +1,82 @@
+#
开发指南
+
+## 🏠 项目架构
+
+```bash
+# 在仓库根目录安装依赖
+pnpm install
+
+#(可选)更新文档
+pnpm run --filter tdesign-mcp-server build:docs+dom
+```
+
+- 由于该项目在提取文档时,如果找不到本地仓库的话,会自动启动 clone 其它组件库的流程。
+- 默认会在**当前 tdesign 仓库的同级目录**下查找/克隆各端框架仓库(如 `tdesign-react`)。
+- 在 mono / 沙盒 / CI 环境中,可以通过环境变量 `TD_REPOS_ROOT` 显式指定一个目录用于存放这些仓库(例如工作区内的空目录);未设置时自动 clone 会先创建默认目录。
+
+```bash
+📄 .env # 环境变量(仅用于本地调试)
+📁 packages/mcp-server
+├── 📁 src # 核心业务逻辑
+│ ├── 📁 tools
+│ └── 📁 prompts
+├── 📁 common # 通用工具函数(源码目录)
+├── 📁 scripts # 自动化脚本(源码目录)
+└── 📁 docs # 已整理的文档(脚本生成,不入库)
+```
+
+### ⚠️ Apple Silicon 环境说明
+
+如果你使用 Apple Silicon(M系列)Mac,需要额外安装 Rosetta 才能运行小程序快照构建脚本(`build:snap:mini`)。
+
+- 因为底层依赖的 `miniprogram-simulate` → `miniprogram-compiler` 仅提供了 `x86_64` 架构的二进制文件,需要进行转译。
+
+```bash
+softwareupdate --install-rosetta --agree-to-license
+```
+
+## 🕹️ 本地启动
+
+| 环境变量 | 说明 | 可选值 | 默认值 |
+|:---|:---|:---|:---|
+| `DOCS_SOURCE_MODE` | 文档数据来源 | `online`(CDN)
`local`(docs 文件夹) | `online` |
+| `TD_REPOS_ROOT` | tdesign 系列仓库(tdesign-react 等)的公共目录 | 任意绝对路径 | 当前 tdesign 仓库的同级目录 |
+
+### Stdio
+
+```bash
+npm run build:mcp-stdio
+```
+
+```json
+{
+ "mcpServers": {
+ "tdesign-mcp-server": {
+ "command": "node",
+ "args": ["your_path_to/tdesign/packages/mcp-server/dist/stdio.js"],
+ "env": {
+ "DOCS_SOURCE_MODE": "local"
+ }
+ }
+ }
+}
+```
+### StreamableHttp
+
+```bash
+npm run build:mcp-http
+npm run start:mcp-http
+```
+
+```json
+{
+ "mcpServers": {
+ "tdesign-mcp-server": {
+ "url": "localhost:9000/mcp",
+ "env": {
+ "DOCS_SOURCE_MODE": "local"
+ }
+ }
+ }
+}
+```
diff --git a/packages/mcp-server/README.md b/packages/mcp-server/README.md
new file mode 100644
index 000000000..5c76b50ef
--- /dev/null
+++ b/packages/mcp-server/README.md
@@ -0,0 +1,35 @@
+#
TDesign MCP Server
+
+## 🎉 功能介绍
+
+### 🔨 支持框架
+- 桌面端:`react` / `vue-next` / `vue`
+- 移动端:`mobile-react` / `mobile-vue` / `miniprogram` / `uniapp`
+- AI 对话:`react-chat` / `vue-next-chat` / `miniprogram-chat` / `uniapp-chat`
+
+### 🔨 支持工具
+- [x] `search-icon`:搜索可用的图标列表,适用场景:图标查找与名称确认
+- [x] `get-component-docs`:获取组件的文档,适用场景:代码生成和代码转换
+- [x] `get-component-dom`:获取组件的 DOM 结构,适用场景:转换自定义的 CSS 样式
+- [x] `get-component-list`:获取所有可用的组件列表,适用场景:选择合适组件进行组合实现不存在的功能
+- [x] `get-component-changelog`:获取组件的变更日志,适用场景:组件库版本升级
+
+## 📦 使用说明
+```json
+{
+ "mcpServers": { // 或 servers(根据不同的 MCP 客户端决定)
+ "tdesign-mcp-server": {
+ "command": "npx",
+ "args": ["-y", "tdesign-mcp-server@latest"]
+ }
+ }
+}
+```
+
+> 💡 如果在 MCP 客户端中连接失败,可以在终端手动输入 `npx tdesign-mcp-server`,并反馈对应的报错信息在 issue。
+
+## 🔮 开发指南
+
+
+
+详见[文档](DEVELOPMENT.md)
\ No newline at end of file
diff --git a/packages/mcp-server/common/config.ts b/packages/mcp-server/common/config.ts
new file mode 100644
index 000000000..435969408
--- /dev/null
+++ b/packages/mcp-server/common/config.ts
@@ -0,0 +1,18 @@
+import { createRequire } from "module";
+import path from "path";
+import { getDirname } from "./toolkit";
+
+const __dirname = getDirname(import.meta.url);
+
+export const isProduction = process.env.NODE_ENV === "production";
+
+if (!isProduction) {
+ // 避免打包时把 dotenv 也打入产物
+ const require = createRequire(import.meta.url); // 兼容 cjs
+ const dotenv = require("dotenv");
+ dotenv.config({ path: path.join(__dirname, "../../.env"), quiet: true });
+}
+
+export const DOCS_SOURCE_MODE = process.env.DOCS_SOURCE_MODE as "online" | "local";
+
+export const isLocalMode = DOCS_SOURCE_MODE === "local";
diff --git a/packages/mcp-server/common/framework.ts b/packages/mcp-server/common/framework.ts
new file mode 100644
index 000000000..444e35489
--- /dev/null
+++ b/packages/mcp-server/common/framework.ts
@@ -0,0 +1,50 @@
+export const addTdPrefix = (suffix: string) => `tdesign-${suffix}`;
+
+export const PLATFORMS = {
+ web: "web",
+ mobile: "mobile"
+} as const;
+export type Platform = (typeof PLATFORMS)[keyof typeof PLATFORMS];
+
+export const WEB_FRAMEWORKS = ["react", "react-chat", "vue", "vue-next", "vue-next-chat"] as const;
+
+export const MOBILE_FRAMEWORKS = [
+ "mobile-react",
+ "mobile-vue",
+ "miniprogram",
+ "miniprogram-chat",
+ "uniapp",
+ "uniapp-chat"
+] as const;
+
+export const FRAMEWORKS = [...WEB_FRAMEWORKS, ...MOBILE_FRAMEWORKS] as const;
+export type Framework = (typeof FRAMEWORKS)[number];
+
+// wxss 内容置空,聚焦组件本身
+export const MINIPROGRAM_DEMO_EXTS = [".wxml", ".js", ".json"] as const;
+
+/* 组件库是否为大仓结构 */
+const MONOREPO: Framework[] = ["react", "vue-next", "miniprogram"] as const;
+export const isMonorepo = (fw: Framework) => MONOREPO.includes(fw);
+
+export const isMiniProgram = (fw: Framework) => ["miniprogram", "miniprogram-chat"].includes(fw);
+
+export const isUniapp = (fw: Framework) => ["uniapp", "uniapp-chat"].includes(fw);
+
+export const isChatFramework = (fw: Framework) => fw.includes("chat") || fw === "uniapp-chat";
+
+const SPECIAL_CHAT_COMPONENTS = ["attachments"];
+export const isChatComponent = (name: string) => name.startsWith("chat-") || SPECIAL_CHAT_COMPONENTS.includes(name);
+
+export const getSuffixByFramework = (fw: Framework) => (fw.includes("react") ? "tsx" : "vue");
+
+/** uniapp 相关框架的源仓库映射 */
+export const getUniappSourceFramework = (fw: Framework): Framework => (fw === "uniapp-chat" ? "uniapp" : "uniapp");
+
+export const getPlatformByFramework = (fw: Framework): Platform => {
+ if ((MOBILE_FRAMEWORKS as readonly Framework[]).includes(fw)) {
+ return PLATFORMS.mobile;
+ }
+
+ return PLATFORMS.web;
+};
diff --git a/packages/mcp-server/common/index.ts b/packages/mcp-server/common/index.ts
new file mode 100644
index 000000000..a40dc92c5
--- /dev/null
+++ b/packages/mcp-server/common/index.ts
@@ -0,0 +1,4 @@
+export * from "./framework";
+export * from "./parser";
+export * from "./toolkit";
+export * from "./config";
\ No newline at end of file
diff --git a/packages/mcp-server/common/parser.ts b/packages/mcp-server/common/parser.ts
new file mode 100644
index 000000000..f0625481e
--- /dev/null
+++ b/packages/mcp-server/common/parser.ts
@@ -0,0 +1,216 @@
+type Rule = [RegExp, string];
+export type RuleMap = Record;
+
+const MD_LINK_RULE: Rule = [/[,,。.]?\s*\[([^\]]+)\]\([^\)]+\)/g, ""];
+const DANGLING_PUNCT_RULE: Rule = [/[,,]\s*[。.](?!\.)/g, "。"];
+const ATTR_INLINE_STYLE_RULE: Rule = [/\s*:?style="([^"]*)"/g, ""];
+const LINK_RULE: Rule = [/https?[^"]*/g, ""];
+const SPACE_RULE: Rule = [/\s+/g, " "];
+const SPACE_BETWEEN_TAG: Rule = [/\s*(<|>)\s*/g, "$1"];
+
+const COMMON_RULE_MAP: RuleMap = {
+ extraLine: [/^\s*\n\s*\n/gm, "\n"],
+ blankHeadTail: [/^(\s*\n)+|(\s*\n)+$/g, ""]
+};
+
+export const API_RULE_MAP: RuleMap = {
+ // 只提取 ## API 后面的段落
+ base: [/^[\s\S]*?^## API\s*\n?/m, ""],
+ mdLink: MD_LINK_RULE,
+ danglingPunct: DANGLING_PUNCT_RULE,
+ ...COMMON_RULE_MAP
+};
+
+export const DEMO_RULE_MAP: RuleMap = {
+ meta: [/---\n([\s\S]*?)\n---/, ""],
+ style: [/\s