@muleiwu/mdoc-mcp 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,11 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="GoImports">
4
+ <option name="excludedPackages">
5
+ <array>
6
+ <option value="github.com/pkg/errors" />
7
+ <option value="golang.org/x/net/context" />
8
+ </array>
9
+ </option>
10
+ </component>
11
+ </project>
@@ -0,0 +1,9 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <module type="WEB_MODULE" version="4">
3
+ <component name="Go" enabled="true" />
4
+ <component name="NewModuleRootManager">
5
+ <content url="file://$MODULE_DIR$" />
6
+ <orderEntry type="inheritedJdk" />
7
+ <orderEntry type="sourceFolder" forTests="false" />
8
+ </component>
9
+ </module>
@@ -0,0 +1,8 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="ProjectModuleManager">
4
+ <modules>
5
+ <module fileurl="file://$PROJECT_DIR$/.idea/mdoc-mcp.iml" filepath="$PROJECT_DIR$/.idea/mdoc-mcp.iml" />
6
+ </modules>
7
+ </component>
8
+ </project>
package/.idea/vcs.xml ADDED
@@ -0,0 +1,6 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="VcsDirectoryMappings">
4
+ <mapping directory="" vcs="Git" />
5
+ </component>
6
+ </project>
package/README.md CHANGED
@@ -1,15 +1,17 @@
1
+ English | [简体中文](README.zh-CN.md)
2
+
1
3
  # mdoc-mcp
2
4
 
3
- [mdoc](https://mdoc.cc) MCP (Model Context Protocol) Server,让 AI 能够直接读取 mdoc 文档的目录结构和文章内容。
5
+ An MCP (Model Context Protocol) Server for [mdoc](https://mdoc.cc), enabling AI to directly read the table of contents and article content from mdoc documentation.
4
6
 
5
- ## 功能
7
+ ## Features
6
8
 
7
- - **`get_manifest`** — 获取文档目录清单(Markdown 格式),包含所有文章的层级结构和链接
8
- - **`get_article_content`** — 获取文章的原始 Markdown 内容
9
+ - **`get_manifest`** — Retrieve the document manifest (Markdown format), including the hierarchical structure and links of all articles
10
+ - **`get_article_content`** — Retrieve the raw Markdown content of an article
9
11
 
10
- ## 安装与配置
12
+ ## Installation & Configuration
11
13
 
12
- ### 1. 克隆并构建
14
+ ### 1. Clone and Build
13
15
 
14
16
  ```bash
15
17
  git clone https://github.com/muleiwu/mdoc-mcp.git
@@ -18,85 +20,84 @@ npm install
18
20
  npm run build
19
21
  ```
20
22
 
21
- ### 2. 配置 API Key
22
-
23
- 在 [mdoc](https://mdoc.cc) 用户设置页面创建 API Key,获取 `AccessKeyID` 和 `SecretAccessKey`。
23
+ ### 2. Configure Personal Access Token
24
24
 
25
- 鉴权使用 AWS Signature Version 4 协议,与服务端完全兼容。
25
+ Create a Personal Access Token (PAT) on the [mdoc](https://mdoc.cc) user settings page. The token format is `mdoc_pat_*`.
26
26
 
27
- ### 3. Cursor / Claude Desktop 中配置
27
+ ### 3. Configure in Cursor / Claude Desktop
28
28
 
29
- 编辑 MCP 配置文件(如 `~/.cursor/mcp.json` Claude Desktop 的配置),添加:
29
+ Edit the MCP configuration file (e.g. `~/.cursor/mcp.json` or the Claude Desktop config) and add:
30
30
 
31
31
  ```json
32
32
  {
33
33
  "mcpServers": {
34
34
  "mdoc": {
35
- "command": "node",
36
- "args": ["/path/to/mdoc-mcp/dist/index.js"],
35
+ "command": "npx",
36
+ "args": [
37
+ "-y",
38
+ "@muleiwu/mdoc-mcp"
39
+ ],
37
40
  "env": {
38
- "MDOC_ACCESS_KEY_ID": "your_access_key_id",
39
- "MDOC_SECRET_ACCESS_KEY": "your_secret_access_key"
41
+ "MDOC_ACCESS_TOKEN": "mdoc_pat_your_token_here"
40
42
  }
41
43
  }
42
44
  }
43
45
  }
44
46
  ```
45
47
 
46
- 如果使用私有部署的 mdoc,额外添加:
48
+ For a self-hosted mdoc instance, additionally add:
47
49
 
48
50
  ```json
49
51
  "MDOC_API_BASE_URL": "https://your-mdoc-instance.com"
50
52
  ```
51
53
 
52
- ## 工具说明
54
+ ## Tools
53
55
 
54
- ### `get_manifest` — 获取文档目录
56
+ ### `get_manifest` — Get Document Manifest
55
57
 
56
- 获取文档的完整目录清单,AI 可据此了解文档结构并选择要读取的文章。
58
+ Retrieves the full table of contents for a document. AI can use this to understand the document structure and select articles to read.
57
59
 
58
- **参数**(提供 `url` `orgSlug` + `docSlug` 之一):
60
+ **Parameters** (provide either `url` or `orgSlug` + `docSlug`):
59
61
 
60
- | 参数 | 类型 | 说明 |
61
- |------|------|------|
62
- | `url` | string (可选) | mdoc 网址,如 `https://mdoc.cc/mliev/1ms` 或带版本 `https://mdoc.cc/mliev/1ms/v1.0.0` |
63
- | `orgSlug` | string (可选) | 组织标识,如 `mliev` |
64
- | `docSlug` | string (可选) | 文档标识,如 `1ms` |
65
- | `version` | string (可选) | 版本名称,如 `v1.0.0`,不指定则使用默认版本 |
62
+ | Parameter | Type | Description |
63
+ |-----------|------|-------------|
64
+ | `url` | string (optional) | mdoc URL, e.g. `https://mdoc.cc/mliev/1ms` or with version `https://mdoc.cc/mliev/1ms/v1.0.0` |
65
+ | `orgSlug` | string (optional) | Organization identifier, e.g. `mliev` |
66
+ | `docSlug` | string (optional) | Document identifier, e.g. `1ms` |
67
+ | `version` | string (optional) | Version name, e.g. `v1.0.0`. Uses the default version if not specified. |
66
68
 
67
- **返回**:Markdown 格式的文档目录,包含文章链接(可直接用于 `get_article_content`)
69
+ **Returns**: The document table of contents in Markdown format, including article links (usable directly with `get_article_content`)
68
70
 
69
- ### `get_article_content` — 获取文章内容
71
+ ### `get_article_content` — Get Article Content
70
72
 
71
- 获取指定文章的原始 Markdown 内容。
73
+ Retrieves the raw Markdown content of a specified article.
72
74
 
73
- **参数**(提供 `url` `orgSlug` + `docSlug` + `articleId` 之一):
75
+ **Parameters** (provide either `url` or `orgSlug` + `docSlug` + `articleId`):
74
76
 
75
- | 参数 | 类型 | 说明 |
76
- |------|------|------|
77
- | `url` | string (可选) | 网址或 manifest 中的 content.md 链接 |
78
- | `orgSlug` | string (可选) | 组织标识 |
79
- | `docSlug` | string (可选) | 文档标识 |
80
- | `articleId` | string (可选) | 文章 ID |
81
- | `version` | string (可选) | 版本名称 |
77
+ | Parameter | Type | Description |
78
+ |-----------|------|-------------|
79
+ | `url` | string (optional) | Article URL or a content.md link from the manifest |
80
+ | `orgSlug` | string (optional) | Organization identifier |
81
+ | `docSlug` | string (optional) | Document identifier |
82
+ | `articleId` | string (optional) | Article ID |
83
+ | `version` | string (optional) | Version name |
82
84
 
83
- 支持的 `url` 格式:
84
- - `https://mdoc.cc/mliev/1ms/v1.0.0/16`(mdoc 网址,第 4 段为 articleId
85
- - `https://mdoc.cc/openapi/organizations/mliev/documents/1ms/articles/16/content.md`(manifest 返回的 API 链接)
85
+ Supported `url` formats:
86
+ - `https://mdoc.cc/mliev/1ms/v1.0.0/16` (mdoc URL where the 4th segment is the articleId)
87
+ - `https://mdoc.cc/openapi/organizations/mliev/documents/1ms/articles/16/content.md` (API link returned by the manifest)
86
88
 
87
- ## 典型使用流程
89
+ ## Typical Workflow
88
90
 
89
- 1. AI 调用 `get_manifest` 获取文档目录
90
- 2. 从目录中找到相关文章的链接
91
- 3. AI 调用 `get_article_content` 读取具体文章内容
91
+ 1. AI calls `get_manifest` to retrieve the document table of contents
92
+ 2. Locate the relevant article link from the manifest
93
+ 3. AI calls `get_article_content` to read the specific article content
92
94
 
93
- ## 环境变量
95
+ ## Environment Variables
94
96
 
95
- | 变量 | 必需 | 默认值 | 说明 |
96
- |------|------|--------|------|
97
- | `MDOC_ACCESS_KEY_ID` | | — | API Key Access Key ID |
98
- | `MDOC_SECRET_ACCESS_KEY` | | | API Key Secret Access Key |
99
- | `MDOC_API_BASE_URL` | 否 | `https://mdoc.cc` | API 基础地址(私有部署时使用) |
97
+ | Variable | Required | Default | Description |
98
+ |----------|----------|---------|-------------|
99
+ | `MDOC_ACCESS_TOKEN` | Yes | — | Personal Access Token (format: `mdoc_pat_*`) |
100
+ | `MDOC_API_BASE_URL` | No | `https://mdoc.cc` | API base URL (for self-hosted instances) |
100
101
 
101
102
  ## License
102
103
 
@@ -0,0 +1,104 @@
1
+ [English](README.md) | 简体中文
2
+
3
+ # mdoc-mcp
4
+
5
+ [mdoc](https://mdoc.cc) 的 MCP (Model Context Protocol) Server,让 AI 能够直接读取 mdoc 文档的目录结构和文章内容。
6
+
7
+ ## 功能
8
+
9
+ - **`get_manifest`** — 获取文档目录清单(Markdown 格式),包含所有文章的层级结构和链接
10
+ - **`get_article_content`** — 获取文章的原始 Markdown 内容
11
+
12
+ ## 安装与配置
13
+
14
+ ### 1. 克隆并构建
15
+
16
+ ```bash
17
+ git clone https://github.com/muleiwu/mdoc-mcp.git
18
+ cd mdoc-mcp
19
+ npm install
20
+ npm run build
21
+ ```
22
+
23
+ ### 2. 配置个人访问令牌
24
+
25
+ 在 [mdoc](https://mdoc.cc) 用户设置页面创建个人访问令牌(PAT),令牌格式为 `mdoc_pat_*`。
26
+
27
+ ### 3. 在 Cursor / Claude Desktop 中配置
28
+
29
+ 编辑 MCP 配置文件(如 `~/.cursor/mcp.json` 或 Claude Desktop 的配置),添加:
30
+
31
+ ```json
32
+ {
33
+ "mcpServers": {
34
+ "mdoc": {
35
+ "command": "npx",
36
+ "args": [
37
+ "-y",
38
+ "@muleiwu/mdoc-mcp"
39
+ ],
40
+ "env": {
41
+ "MDOC_ACCESS_TOKEN": "mdoc_pat_your_token_here"
42
+ }
43
+ }
44
+ }
45
+ }
46
+ ```
47
+
48
+ 如果使用私有部署的 mdoc,额外添加:
49
+
50
+ ```json
51
+ "MDOC_API_BASE_URL": "https://your-mdoc-instance.com"
52
+ ```
53
+
54
+ ## 工具说明
55
+
56
+ ### `get_manifest` — 获取文档目录
57
+
58
+ 获取文档的完整目录清单,AI 可据此了解文档结构并选择要读取的文章。
59
+
60
+ **参数**(提供 `url` 或 `orgSlug` + `docSlug` 之一):
61
+
62
+ | 参数 | 类型 | 说明 |
63
+ |------|------|------|
64
+ | `url` | string (可选) | mdoc 网址,如 `https://mdoc.cc/mliev/1ms` 或带版本 `https://mdoc.cc/mliev/1ms/v1.0.0` |
65
+ | `orgSlug` | string (可选) | 组织标识,如 `mliev` |
66
+ | `docSlug` | string (可选) | 文档标识,如 `1ms` |
67
+ | `version` | string (可选) | 版本名称,如 `v1.0.0`,不指定则使用默认版本 |
68
+
69
+ **返回**:Markdown 格式的文档目录,包含文章链接(可直接用于 `get_article_content`)
70
+
71
+ ### `get_article_content` — 获取文章内容
72
+
73
+ 获取指定文章的原始 Markdown 内容。
74
+
75
+ **参数**(提供 `url` 或 `orgSlug` + `docSlug` + `articleId` 之一):
76
+
77
+ | 参数 | 类型 | 说明 |
78
+ |------|------|------|
79
+ | `url` | string (可选) | 网址或 manifest 中的 content.md 链接 |
80
+ | `orgSlug` | string (可选) | 组织标识 |
81
+ | `docSlug` | string (可选) | 文档标识 |
82
+ | `articleId` | string (可选) | 文章 ID |
83
+ | `version` | string (可选) | 版本名称 |
84
+
85
+ 支持的 `url` 格式:
86
+ - `https://mdoc.cc/mliev/1ms/v1.0.0/16`(mdoc 网址,第 4 段为 articleId)
87
+ - `https://mdoc.cc/openapi/organizations/mliev/documents/1ms/articles/16/content.md`(manifest 返回的 API 链接)
88
+
89
+ ## 典型使用流程
90
+
91
+ 1. AI 调用 `get_manifest` 获取文档目录
92
+ 2. 从目录中找到相关文章的链接
93
+ 3. AI 调用 `get_article_content` 读取具体文章内容
94
+
95
+ ## 环境变量
96
+
97
+ | 变量 | 必需 | 默认值 | 说明 |
98
+ |------|------|--------|------|
99
+ | `MDOC_ACCESS_TOKEN` | 是 | — | 个人访问令牌(格式:`mdoc_pat_*`) |
100
+ | `MDOC_API_BASE_URL` | 否 | `https://mdoc.cc` | API 基础地址(私有部署时使用) |
101
+
102
+ ## License
103
+
104
+ MIT
package/dist/client.d.ts CHANGED
@@ -1,6 +1,5 @@
1
1
  export interface MdocClientConfig {
2
- accessKeyId: string;
3
- secretAccessKey: string;
2
+ accessToken: string;
4
3
  baseUrl: string;
5
4
  }
6
5
  export interface RequestOptions {
@@ -9,12 +8,7 @@ export interface RequestOptions {
9
8
  }
10
9
  export declare class MdocClient {
11
10
  private config;
12
- private readonly service;
13
- private readonly region;
14
11
  constructor(config: MdocClientConfig);
15
- /**
16
- * 发起带 AWS Signature V4 签名的 GET 请求
17
- */
18
12
  get(options: RequestOptions): Promise<string>;
19
13
  }
20
14
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,gBAAgB;IAC/B,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAChC;AA4ED,qBAAa,UAAU;IACrB,OAAO,CAAC,MAAM,CAAmB;IACjC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAU;IAClC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAe;gBAE1B,MAAM,EAAE,gBAAgB;IAIpC;;OAEG;IACG,GAAG,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC;CA+DpD;AAED;;GAEG;AACH,wBAAgB,mBAAmB,IAAI,UAAU,CAahD"}
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,gBAAgB;IAC/B,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAChC;AAWD,qBAAa,UAAU;IACrB,OAAO,CAAC,MAAM,CAAmB;gBAErB,MAAM,EAAE,gBAAgB;IAI9B,GAAG,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC;CA8CpD;AAED;;GAEG;AACH,wBAAgB,mBAAmB,IAAI,UAAU,CAShD"}
package/dist/client.js CHANGED
@@ -1,44 +1,5 @@
1
- import { createHmac, createHash } from 'crypto';
2
1
  import { request as httpsRequest } from 'https';
3
2
  import { request as httpRequest } from 'http';
4
- function hmac(key, data) {
5
- return createHmac('sha256', key).update(data).digest();
6
- }
7
- function sha256Hex(data) {
8
- return createHash('sha256').update(data).digest('hex');
9
- }
10
- /**
11
- * 构建 AWS Signature V4 Authorization 头
12
- *
13
- * 注意:Go 的 net/http 服务器会将 Host 头从 req.Header 移到 req.Host,
14
- * 导致 gohttpsig 的 CanonicalizeHeaders 无法找到 host 的值。
15
- * 因此这里签名时不包含 host 头,只签 x-amz-date。
16
- */
17
- function buildAuthHeader(accessKeyId, secretAccessKey, method, path, query, dateTime, service, region) {
18
- const dateShort = dateTime.slice(0, 8);
19
- // 只签 x-amz-date(不含 host,因为服务端 Go HTTP 会将 Host 头移出 req.Header)
20
- const signedHeaders = 'x-amz-date';
21
- const canonicalHeaders = `x-amz-date:${dateTime}\n`;
22
- const payloadHash = 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'; // SHA256("")
23
- const canonicalRequest = [
24
- method,
25
- path,
26
- query,
27
- canonicalHeaders,
28
- signedHeaders,
29
- payloadHash,
30
- ].join('\n');
31
- const credentialScope = `${dateShort}/${region}/${service}/aws4_request`;
32
- const stringToSign = [
33
- 'AWS4-HMAC-SHA256',
34
- dateTime,
35
- credentialScope,
36
- sha256Hex(canonicalRequest),
37
- ].join('\n');
38
- const signingKey = hmac(hmac(hmac(hmac(`AWS4${secretAccessKey}`, dateShort), region), service), 'aws4_request');
39
- const signature = createHmac('sha256', signingKey).update(stringToSign).digest('hex');
40
- return `AWS4-HMAC-SHA256 Credential=${accessKeyId}/${credentialScope}, SignedHeaders=${signedHeaders}, Signature=${signature}`;
41
- }
42
3
  function readBody(res) {
43
4
  return new Promise((resolve, reject) => {
44
5
  const chunks = [];
@@ -49,14 +10,9 @@ function readBody(res) {
49
10
  }
50
11
  export class MdocClient {
51
12
  config;
52
- service = 'mdoc';
53
- region = 'us-east-1';
54
13
  constructor(config) {
55
14
  this.config = config;
56
15
  }
57
- /**
58
- * 发起带 AWS Signature V4 签名的 GET 请求
59
- */
60
16
  async get(options) {
61
17
  const url = new URL(this.config.baseUrl);
62
18
  const isHttps = url.protocol === 'https:';
@@ -64,7 +20,6 @@ export class MdocClient {
64
20
  const port = url.port
65
21
  ? parseInt(url.port)
66
22
  : isHttps ? 443 : 80;
67
- // 构建查询字符串(按 key 排序,符合 AWS 规范)
68
23
  let queryString = '';
69
24
  if (options.query && Object.keys(options.query).length > 0) {
70
25
  const filtered = Object.entries(options.query).filter(([, v]) => v !== undefined && v !== '');
@@ -74,10 +29,6 @@ export class MdocClient {
74
29
  .join('&');
75
30
  }
76
31
  const fullPath = queryString ? `${options.path}?${queryString}` : options.path;
77
- // 生成时间戳(ISO8601 格式,去除特殊字符)
78
- const dateTime = new Date().toISOString().replace(/[-:]/g, '').replace(/\.\d{3}/, '');
79
- // 构建 Authorization 头
80
- const authHeader = buildAuthHeader(this.config.accessKeyId, this.config.secretAccessKey, 'GET', options.path, queryString, dateTime, this.service, this.region);
81
32
  return new Promise((resolve, reject) => {
82
33
  const reqOptions = {
83
34
  hostname: host,
@@ -85,8 +36,7 @@ export class MdocClient {
85
36
  path: fullPath,
86
37
  method: 'GET',
87
38
  headers: {
88
- 'X-Amz-Date': dateTime,
89
- 'Authorization': authHeader,
39
+ 'Authorization': `Bearer ${this.config.accessToken}`,
90
40
  },
91
41
  };
92
42
  const req = (isHttps ? httpsRequest : httpRequest)(reqOptions, async (res) => {
@@ -107,15 +57,11 @@ export class MdocClient {
107
57
  * 从环境变量创建客户端实例
108
58
  */
109
59
  export function createClientFromEnv() {
110
- const accessKeyId = process.env.MDOC_ACCESS_KEY_ID;
111
- const secretAccessKey = process.env.MDOC_SECRET_ACCESS_KEY;
60
+ const accessToken = process.env.MDOC_ACCESS_TOKEN;
112
61
  const baseUrl = process.env.MDOC_API_BASE_URL ?? 'https://mdoc.cc';
113
- if (!accessKeyId) {
114
- throw new Error('缺少环境变量 MDOC_ACCESS_KEY_ID');
115
- }
116
- if (!secretAccessKey) {
117
- throw new Error('缺少环境变量 MDOC_SECRET_ACCESS_KEY');
62
+ if (!accessToken) {
63
+ throw new Error('缺少环境变量 MDOC_ACCESS_TOKEN');
118
64
  }
119
- return new MdocClient({ accessKeyId, secretAccessKey, baseUrl });
65
+ return new MdocClient({ accessToken, baseUrl });
120
66
  }
121
67
  //# sourceMappingURL=client.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@muleiwu/mdoc-mcp",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "MCP server for mdoc document reading via OpenAPI",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -1,8 +0,0 @@
1
- {
2
- "permissions": {
3
- "allow": [
4
- "Bash(git add:*)",
5
- "Bash(git commit:*)"
6
- ]
7
- }
8
- }
package/.env.example DELETED
@@ -1,8 +0,0 @@
1
- # mdoc API Key(必需)
2
- # 在 mdoc 用户设置页面创建 API Key 后获取
3
- MDOC_ACCESS_KEY_ID=your_access_key_id_here
4
- MDOC_SECRET_ACCESS_KEY=your_secret_access_key_here
5
-
6
- # API 基础地址(可选,默认 https://mdoc.cc)
7
- # 如果使用私有部署,请修改此地址
8
- # MDOC_API_BASE_URL=https://mdoc.cc
package/src/client.ts DELETED
@@ -1,184 +0,0 @@
1
- import { createHmac, createHash } from 'crypto';
2
- import { request as httpsRequest } from 'https';
3
- import { request as httpRequest } from 'http';
4
- import { IncomingMessage } from 'http';
5
-
6
- export interface MdocClientConfig {
7
- accessKeyId: string;
8
- secretAccessKey: string;
9
- baseUrl: string;
10
- }
11
-
12
- export interface RequestOptions {
13
- path: string;
14
- query?: Record<string, string>;
15
- }
16
-
17
- function hmac(key: Buffer | string, data: string): Buffer {
18
- return createHmac('sha256', key).update(data).digest();
19
- }
20
-
21
- function sha256Hex(data: string): string {
22
- return createHash('sha256').update(data).digest('hex');
23
- }
24
-
25
- /**
26
- * 构建 AWS Signature V4 Authorization 头
27
- *
28
- * 注意:Go 的 net/http 服务器会将 Host 头从 req.Header 移到 req.Host,
29
- * 导致 gohttpsig 的 CanonicalizeHeaders 无法找到 host 的值。
30
- * 因此这里签名时不包含 host 头,只签 x-amz-date。
31
- */
32
- function buildAuthHeader(
33
- accessKeyId: string,
34
- secretAccessKey: string,
35
- method: string,
36
- path: string,
37
- query: string,
38
- dateTime: string,
39
- service: string,
40
- region: string
41
- ): string {
42
- const dateShort = dateTime.slice(0, 8);
43
-
44
- // 只签 x-amz-date(不含 host,因为服务端 Go HTTP 会将 Host 头移出 req.Header)
45
- const signedHeaders = 'x-amz-date';
46
- const canonicalHeaders = `x-amz-date:${dateTime}\n`;
47
- const payloadHash = 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'; // SHA256("")
48
-
49
- const canonicalRequest = [
50
- method,
51
- path,
52
- query,
53
- canonicalHeaders,
54
- signedHeaders,
55
- payloadHash,
56
- ].join('\n');
57
-
58
- const credentialScope = `${dateShort}/${region}/${service}/aws4_request`;
59
- const stringToSign = [
60
- 'AWS4-HMAC-SHA256',
61
- dateTime,
62
- credentialScope,
63
- sha256Hex(canonicalRequest),
64
- ].join('\n');
65
-
66
- const signingKey = hmac(
67
- hmac(
68
- hmac(
69
- hmac(`AWS4${secretAccessKey}`, dateShort),
70
- region
71
- ),
72
- service
73
- ),
74
- 'aws4_request'
75
- );
76
-
77
- const signature = createHmac('sha256', signingKey).update(stringToSign).digest('hex');
78
-
79
- return `AWS4-HMAC-SHA256 Credential=${accessKeyId}/${credentialScope}, SignedHeaders=${signedHeaders}, Signature=${signature}`;
80
- }
81
-
82
- function readBody(res: IncomingMessage): Promise<string> {
83
- return new Promise((resolve, reject) => {
84
- const chunks: Buffer[] = [];
85
- res.on('data', (chunk: Buffer) => chunks.push(chunk));
86
- res.on('end', () => resolve(Buffer.concat(chunks).toString('utf-8')));
87
- res.on('error', reject);
88
- });
89
- }
90
-
91
- export class MdocClient {
92
- private config: MdocClientConfig;
93
- private readonly service = 'mdoc';
94
- private readonly region = 'us-east-1';
95
-
96
- constructor(config: MdocClientConfig) {
97
- this.config = config;
98
- }
99
-
100
- /**
101
- * 发起带 AWS Signature V4 签名的 GET 请求
102
- */
103
- async get(options: RequestOptions): Promise<string> {
104
- const url = new URL(this.config.baseUrl);
105
- const isHttps = url.protocol === 'https:';
106
- const host = url.hostname;
107
- const port = url.port
108
- ? parseInt(url.port)
109
- : isHttps ? 443 : 80;
110
-
111
- // 构建查询字符串(按 key 排序,符合 AWS 规范)
112
- let queryString = '';
113
- if (options.query && Object.keys(options.query).length > 0) {
114
- const filtered = Object.entries(options.query).filter(([, v]) => v !== undefined && v !== '');
115
- queryString = filtered
116
- .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`)
117
- .sort()
118
- .join('&');
119
- }
120
-
121
- const fullPath = queryString ? `${options.path}?${queryString}` : options.path;
122
-
123
- // 生成时间戳(ISO8601 格式,去除特殊字符)
124
- const dateTime = new Date().toISOString().replace(/[-:]/g, '').replace(/\.\d{3}/, '');
125
-
126
- // 构建 Authorization 头
127
- const authHeader = buildAuthHeader(
128
- this.config.accessKeyId,
129
- this.config.secretAccessKey,
130
- 'GET',
131
- options.path,
132
- queryString,
133
- dateTime,
134
- this.service,
135
- this.region
136
- );
137
-
138
- return new Promise((resolve, reject) => {
139
- const reqOptions = {
140
- hostname: host,
141
- port,
142
- path: fullPath,
143
- method: 'GET',
144
- headers: {
145
- 'X-Amz-Date': dateTime,
146
- 'Authorization': authHeader,
147
- } as Record<string, string>,
148
- };
149
-
150
- const req = (isHttps ? httpsRequest : httpRequest)(reqOptions, async (res) => {
151
- const body = await readBody(res);
152
- const statusCode = res.statusCode ?? 0;
153
-
154
- if (statusCode >= 400) {
155
- reject(new Error(`HTTP ${statusCode}: ${body}`));
156
- return;
157
- }
158
-
159
- resolve(body);
160
- });
161
-
162
- req.on('error', reject);
163
- req.end();
164
- });
165
- }
166
- }
167
-
168
- /**
169
- * 从环境变量创建客户端实例
170
- */
171
- export function createClientFromEnv(): MdocClient {
172
- const accessKeyId = process.env.MDOC_ACCESS_KEY_ID;
173
- const secretAccessKey = process.env.MDOC_SECRET_ACCESS_KEY;
174
- const baseUrl = process.env.MDOC_API_BASE_URL ?? 'https://mdoc.cc';
175
-
176
- if (!accessKeyId) {
177
- throw new Error('缺少环境变量 MDOC_ACCESS_KEY_ID');
178
- }
179
- if (!secretAccessKey) {
180
- throw new Error('缺少环境变量 MDOC_SECRET_ACCESS_KEY');
181
- }
182
-
183
- return new MdocClient({ accessKeyId, secretAccessKey, baseUrl });
184
- }
package/src/index.ts DELETED
@@ -1,127 +0,0 @@
1
- #!/usr/bin/env node
2
- import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
- import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
- import * as z from 'zod/v4';
5
- import { createClientFromEnv } from './client.js';
6
- import { getManifest } from './tools/get-manifest.js';
7
- import { getArticleContent } from './tools/get-article-content.js';
8
-
9
- const server = new McpServer({
10
- name: 'mdoc-mcp',
11
- version: '0.1.0',
12
- });
13
-
14
- // Tool: get_manifest
15
- server.registerTool(
16
- 'get_manifest',
17
- {
18
- title: '获取文档目录清单',
19
- description:
20
- '获取 mdoc 文档的目录清单(Markdown 格式),包含所有文章的标题和链接。' +
21
- '可以通过 mdoc.cc 网址或 orgSlug/docSlug 参数指定文档。' +
22
- '示例网址格式:https://mdoc.cc/mliev/1ms 或 https://mdoc.cc/mliev/1ms/v1.0.0',
23
- inputSchema: {
24
- url: z
25
- .string()
26
- .optional()
27
- .describe(
28
- 'mdoc 文档网址,如 https://mdoc.cc/mliev/1ms 或 https://mdoc.cc/mliev/1ms/v1.0.0。' +
29
- '提供 url 时,orgSlug 和 docSlug 可省略。'
30
- ),
31
- orgSlug: z
32
- .string()
33
- .optional()
34
- .describe('组织标识,如 mliev。当未提供 url 时必填。'),
35
- docSlug: z
36
- .string()
37
- .optional()
38
- .describe('文档标识,如 1ms。当未提供 url 时必填。'),
39
- version: z
40
- .string()
41
- .optional()
42
- .describe('版本名称,如 v1.0.0。不指定则使用文档默认版本。'),
43
- },
44
- },
45
- async (args) => {
46
- try {
47
- const client = createClientFromEnv();
48
- const content = await getManifest(client, args);
49
- return {
50
- content: [{ type: 'text', text: content }],
51
- };
52
- } catch (error) {
53
- const message = error instanceof Error ? error.message : String(error);
54
- return {
55
- content: [{ type: 'text', text: `错误: ${message}` }],
56
- isError: true,
57
- };
58
- }
59
- }
60
- );
61
-
62
- // Tool: get_article_content
63
- server.registerTool(
64
- 'get_article_content',
65
- {
66
- title: '获取文章内容',
67
- description:
68
- '获取 mdoc 文档中某篇文章的原始 Markdown 内容。' +
69
- '可通过 mdoc.cc 网址(含 articleId)、manifest 返回的 content.md 链接,' +
70
- '或 orgSlug/docSlug/articleId 参数指定文章。' +
71
- '示例网址格式:https://mdoc.cc/mliev/1ms/v1.0.0/16',
72
- inputSchema: {
73
- url: z
74
- .string()
75
- .optional()
76
- .describe(
77
- '文章网址,支持两种格式:\n' +
78
- '1. mdoc.cc 网址:https://mdoc.cc/mliev/1ms/v1.0.0/16\n' +
79
- '2. manifest 中返回的 content.md API 链接,如 ' +
80
- 'https://mdoc.cc/openapi/organizations/mliev/documents/1ms/articles/16/content.md'
81
- ),
82
- orgSlug: z
83
- .string()
84
- .optional()
85
- .describe('组织标识,如 mliev。当未提供 url 时必填。'),
86
- docSlug: z
87
- .string()
88
- .optional()
89
- .describe('文档标识,如 1ms。当未提供 url 时必填。'),
90
- articleId: z
91
- .string()
92
- .optional()
93
- .describe('文章 ID,如 16。当未提供 url 时必填。'),
94
- version: z
95
- .string()
96
- .optional()
97
- .describe('版本名称,如 v1.0.0。不指定则使用文档默认版本。'),
98
- },
99
- },
100
- async (args) => {
101
- try {
102
- const client = createClientFromEnv();
103
- const content = await getArticleContent(client, args);
104
- return {
105
- content: [{ type: 'text', text: content }],
106
- };
107
- } catch (error) {
108
- const message = error instanceof Error ? error.message : String(error);
109
- return {
110
- content: [{ type: 'text', text: `错误: ${message}` }],
111
- isError: true,
112
- };
113
- }
114
- }
115
- );
116
-
117
- async function main() {
118
- const transport = new StdioServerTransport();
119
- await server.connect(transport);
120
- // 使用 stderr 避免干扰 stdio 传输协议
121
- process.stderr.write('mdoc MCP server 已启动(stdio 模式)\n');
122
- }
123
-
124
- main().catch((error) => {
125
- process.stderr.write(`启动失败: ${error}\n`);
126
- process.exit(1);
127
- });
@@ -1,66 +0,0 @@
1
- import { MdocClient } from '../client.js';
2
- import { parseMdocUrl } from '../url-parser.js';
3
-
4
- export interface GetArticleContentInput {
5
- url?: string;
6
- orgSlug?: string;
7
- docSlug?: string;
8
- articleId?: string;
9
- version?: string;
10
- }
11
-
12
- /**
13
- * 获取文章的原始 Markdown 内容
14
- * 对应 API: GET /openapi/organizations/:orgSlug/documents/:docSlug/articles/:articleId/content.md
15
- *
16
- * url 参数支持两种格式:
17
- * 1. mdoc.cc 网址: https://mdoc.cc/:orgSlug/:docSlug/:version/:articleId
18
- * 2. API content URL: .../openapi/organizations/.../articles/:articleId/content.md
19
- */
20
- export async function getArticleContent(
21
- client: MdocClient,
22
- input: GetArticleContentInput
23
- ): Promise<string> {
24
- let orgSlug: string;
25
- let docSlug: string;
26
- let articleId: string;
27
- let version: string | undefined = input.version;
28
-
29
- if (input.url) {
30
- const parsed = parseMdocUrl(input.url);
31
-
32
- if (!parsed.articleId) {
33
- throw new Error(
34
- `无法从 URL 中解析出 articleId,请确认 URL 格式正确(如 https://mdoc.cc/org/doc/v1.0.0/16),` +
35
- `或单独提供 orgSlug、docSlug 和 articleId 参数`
36
- );
37
- }
38
-
39
- orgSlug = parsed.orgSlug;
40
- docSlug = parsed.docSlug;
41
- articleId = parsed.articleId;
42
- if (!version && parsed.version) {
43
- version = parsed.version;
44
- }
45
- } else if (input.orgSlug && input.docSlug && input.articleId) {
46
- orgSlug = input.orgSlug;
47
- docSlug = input.docSlug;
48
- articleId = input.articleId;
49
- } else {
50
- throw new Error(
51
- '请提供 url 参数,或同时提供 orgSlug、docSlug 和 articleId 参数'
52
- );
53
- }
54
-
55
- const path =
56
- `/openapi/organizations/${encodeURIComponent(orgSlug)}` +
57
- `/documents/${encodeURIComponent(docSlug)}` +
58
- `/articles/${encodeURIComponent(articleId)}/content.md`;
59
-
60
- const query: Record<string, string> = {};
61
- if (version) {
62
- query['version'] = version;
63
- }
64
-
65
- return client.get({ path, query });
66
- }
@@ -1,45 +0,0 @@
1
- import { MdocClient } from '../client.js';
2
- import { parseMdocUrl } from '../url-parser.js';
3
-
4
- export interface GetManifestInput {
5
- url?: string;
6
- orgSlug?: string;
7
- docSlug?: string;
8
- version?: string;
9
- }
10
-
11
- /**
12
- * 获取文档目录清单(Markdown 格式)
13
- * 对应 API: GET /openapi/organizations/:orgSlug/documents/:docSlug/manifest
14
- */
15
- export async function getManifest(
16
- client: MdocClient,
17
- input: GetManifestInput
18
- ): Promise<string> {
19
- let orgSlug: string;
20
- let docSlug: string;
21
- let version: string | undefined = input.version;
22
-
23
- if (input.url) {
24
- const parsed = parseMdocUrl(input.url);
25
- orgSlug = parsed.orgSlug;
26
- docSlug = parsed.docSlug;
27
- // URL 中解析到的版本优先级低于显式传入的 version 参数
28
- if (!version && parsed.version) {
29
- version = parsed.version;
30
- }
31
- } else if (input.orgSlug && input.docSlug) {
32
- orgSlug = input.orgSlug;
33
- docSlug = input.docSlug;
34
- } else {
35
- throw new Error('请提供 url 参数,或同时提供 orgSlug 和 docSlug 参数');
36
- }
37
-
38
- const path = `/openapi/organizations/${encodeURIComponent(orgSlug)}/documents/${encodeURIComponent(docSlug)}/manifest`;
39
- const query: Record<string, string> = {};
40
- if (version) {
41
- query['version'] = version;
42
- }
43
-
44
- return client.get({ path, query });
45
- }
package/src/url-parser.ts DELETED
@@ -1,114 +0,0 @@
1
- /**
2
- * mdoc URL 解析器
3
- *
4
- * 支持以下格式:
5
- * 1. mdoc.cc 网站 URL:
6
- * - https://mdoc.cc/:orgSlug/:docSlug
7
- * - https://mdoc.cc/:orgSlug/:docSlug/:version
8
- * - https://mdoc.cc/:orgSlug/:docSlug/:version/:articleId
9
- *
10
- * 2. OpenAPI content URL (从 manifest 返回的链接):
11
- * - .../openapi/organizations/:orgSlug/documents/:docSlug/articles/:articleId/content.md
12
- */
13
-
14
- export interface ParsedMdocUrl {
15
- orgSlug: string;
16
- docSlug: string;
17
- version?: string;
18
- articleId?: string;
19
- }
20
-
21
- /**
22
- * 判断字符串是否看起来像版本号(以字母开头,如 v1.0.0,或纯数字组成的版本)
23
- * 版本号特征:包含字母前缀或包含点号
24
- */
25
- function looksLikeVersion(segment: string): boolean {
26
- return /^v\d/.test(segment) || /^\d+\.\d+/.test(segment);
27
- }
28
-
29
- /**
30
- * 判断字符串是否看起来像文章 ID(纯数字)
31
- */
32
- function looksLikeArticleId(segment: string): boolean {
33
- return /^\d+$/.test(segment);
34
- }
35
-
36
- /**
37
- * 解析 mdoc URL,提取参数
38
- * @throws {Error} 如果 URL 格式无法识别
39
- */
40
- export function parseMdocUrl(rawUrl: string): ParsedMdocUrl {
41
- // 补全协议头
42
- if (!rawUrl.startsWith('http://') && !rawUrl.startsWith('https://')) {
43
- rawUrl = 'https://' + rawUrl;
44
- }
45
-
46
- let url: URL;
47
- try {
48
- url = new URL(rawUrl);
49
- } catch {
50
- throw new Error(`无效的 URL 格式: ${rawUrl}`);
51
- }
52
-
53
- const pathname = url.pathname;
54
- // 去掉首尾斜杠,拆分路径段
55
- const segments = pathname.replace(/^\/|\/$/g, '').split('/').filter(Boolean);
56
-
57
- // 尝试匹配 OpenAPI content URL:
58
- // /openapi/organizations/:orgSlug/documents/:docSlug/articles/:articleId/content.md
59
- const openapiMatch = pathname.match(
60
- /\/openapi\/organizations\/([^/]+)\/documents\/([^/]+)\/articles\/([^/]+)\/content\.md/
61
- );
62
- if (openapiMatch) {
63
- return {
64
- orgSlug: openapiMatch[1],
65
- docSlug: openapiMatch[2],
66
- articleId: openapiMatch[3],
67
- };
68
- }
69
-
70
- // 尝试匹配 OpenAPI manifest URL:
71
- // /openapi/organizations/:orgSlug/documents/:docSlug/manifest
72
- const manifestMatch = pathname.match(
73
- /\/openapi\/organizations\/([^/]+)\/documents\/([^/]+)\/manifest/
74
- );
75
- if (manifestMatch) {
76
- const version = url.searchParams.get('version') ?? undefined;
77
- return {
78
- orgSlug: manifestMatch[1],
79
- docSlug: manifestMatch[2],
80
- version,
81
- };
82
- }
83
-
84
- // 解析 mdoc.cc 风格的网址: /:orgSlug/:docSlug[/:version[/:articleId]]
85
- if (segments.length < 2) {
86
- throw new Error(
87
- `URL 路径段不足,期望格式: /:orgSlug/:docSlug[/:version[/:articleId]],实际路径: ${pathname}`
88
- );
89
- }
90
-
91
- const [orgSlug, docSlug, seg3, seg4] = segments;
92
- let version: string | undefined;
93
- let articleId: string | undefined;
94
-
95
- if (seg3) {
96
- if (looksLikeArticleId(seg3) && !seg4) {
97
- // 只有纯数字且没有第四段:当做 articleId
98
- articleId = seg3;
99
- } else if (looksLikeVersion(seg3)) {
100
- version = seg3;
101
- if (seg4) {
102
- articleId = seg4;
103
- }
104
- } else {
105
- // 第三段既不像版本也不像 articleId,作为 version 兜底处理
106
- version = seg3;
107
- if (seg4) {
108
- articleId = seg4;
109
- }
110
- }
111
- }
112
-
113
- return { orgSlug, docSlug, version, articleId };
114
- }
package/tsconfig.json DELETED
@@ -1,17 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2022",
4
- "module": "Node16",
5
- "moduleResolution": "Node16",
6
- "outDir": "./dist",
7
- "rootDir": "./src",
8
- "strict": true,
9
- "esModuleInterop": true,
10
- "skipLibCheck": true,
11
- "declaration": true,
12
- "declarationMap": true,
13
- "sourceMap": true
14
- },
15
- "include": ["src/**/*"],
16
- "exclude": ["node_modules", "dist"]
17
- }