@ppdocs/mcp 3.0.8 → 3.1.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.
@@ -72,8 +72,8 @@ export declare class PpdocsApiClient {
72
72
  listFiles(dir?: string): Promise<FileInfo[]>;
73
73
  /** 读取项目文件 */
74
74
  readFile(filePath: string): Promise<string>;
75
- /** 下载项目目录 (zip) → 自动解压到 temp 目录 */
76
- downloadDir(dir: string): Promise<{
75
+ /** 下载项目文件或目录 (zip) → 自动解压到指定目录或 temp 目录 */
76
+ download(remotePath: string, localPath?: string): Promise<{
77
77
  localPath: string;
78
78
  fileCount: number;
79
79
  }>;
@@ -87,8 +87,8 @@ export declare class PpdocsApiClient {
87
87
  crossListFiles(target: string, dir?: string): Promise<FileInfo[]>;
88
88
  /** 跨项目: 读取文件 */
89
89
  crossReadFile(target: string, filePath: string): Promise<string>;
90
- /** 跨项目: 下载目录 */
91
- crossDownloadDir(target: string, dir: string): Promise<{
90
+ /** 跨项目: 下载文件或目录 */
91
+ crossDownload(target: string, remotePath: string, localPath?: string): Promise<{
92
92
  localPath: string;
93
93
  fileCount: number;
94
94
  }>;
@@ -137,13 +137,13 @@ export declare function crossGetDocsByStatus(target: string, statusList: string[
137
137
  export declare function crossGetRules(target: string, ruleType: string): Promise<string[]>;
138
138
  export declare function listFiles(dir?: string): Promise<FileInfo[]>;
139
139
  export declare function readFile(filePath: string): Promise<string>;
140
- export declare function downloadDir(dir: string): Promise<{
140
+ export declare function download(remotePath: string, localPath?: string): Promise<{
141
141
  localPath: string;
142
142
  fileCount: number;
143
143
  }>;
144
144
  export declare function crossListFiles(target: string, dir?: string): Promise<FileInfo[]>;
145
145
  export declare function crossReadFile(target: string, filePath: string): Promise<string>;
146
- export declare function crossDownloadDir(target: string, dir: string): Promise<{
146
+ export declare function crossDownload(target: string, remotePath: string, localPath?: string): Promise<{
147
147
  localPath: string;
148
148
  fileCount: number;
149
149
  }>;
@@ -14,8 +14,8 @@ const EXCLUDED_DIRS = new Set([
14
14
  'node_modules', '.git', 'target', 'dist', 'build', '.next',
15
15
  '__pycache__', '.venv', 'venv', '.idea', '.vs', '.vscode',
16
16
  ]);
17
- /** 下载 zip 并解压到 temp 目录 */
18
- async function fetchAndExtractZip(url) {
17
+ /** 下载 zip 并解压到指定目录或 temp 目录 */
18
+ async function fetchAndExtractZip(url, localPath) {
19
19
  const controller = new AbortController();
20
20
  const timeout = setTimeout(() => controller.abort(), 60000);
21
21
  try {
@@ -29,10 +29,10 @@ async function fetchAndExtractZip(url) {
29
29
  const path = await import('path');
30
30
  const fs = await import('fs');
31
31
  const AdmZip = (await import('adm-zip')).default;
32
- const tempDir = path.join(os.tmpdir(), `ppdocs-files-${Date.now()}`);
33
- fs.mkdirSync(tempDir, { recursive: true });
32
+ const targetDir = localPath || path.join(os.tmpdir(), `ppdocs-files-${Date.now()}`);
33
+ fs.mkdirSync(targetDir, { recursive: true });
34
34
  const zip = new AdmZip(buffer);
35
- zip.extractAllTo(tempDir, true);
35
+ zip.extractAllTo(targetDir, true);
36
36
  let fileCount = 0;
37
37
  const countFiles = (dirPath) => {
38
38
  for (const entry of fs.readdirSync(dirPath, { withFileTypes: true })) {
@@ -42,8 +42,8 @@ async function fetchAndExtractZip(url) {
42
42
  fileCount++;
43
43
  }
44
44
  };
45
- countFiles(tempDir);
46
- return { localPath: tempDir, fileCount };
45
+ countFiles(targetDir);
46
+ return { localPath: targetDir, fileCount };
47
47
  }
48
48
  catch (err) {
49
49
  if (err instanceof Error && err.name === 'AbortError') {
@@ -393,9 +393,9 @@ export class PpdocsApiClient {
393
393
  async readFile(filePath) {
394
394
  return this.request(`/files/${cleanPath(filePath)}`);
395
395
  }
396
- /** 下载项目目录 (zip) → 自动解压到 temp 目录 */
397
- async downloadDir(dir) {
398
- return fetchAndExtractZip(`${this.baseUrl}/files-download/${cleanPath(dir)}`);
396
+ /** 下载项目文件或目录 (zip) → 自动解压到指定目录或 temp 目录 */
397
+ async download(remotePath, localPath) {
398
+ return fetchAndExtractZip(`${this.baseUrl}/files-download/${cleanPath(remotePath)}`, localPath);
399
399
  }
400
400
  /** 上传本地目录到中心服务器 (打包 zip → POST) */
401
401
  async uploadFiles(localDir, remoteDir) {
@@ -460,9 +460,9 @@ export class PpdocsApiClient {
460
460
  async crossReadFile(target, filePath) {
461
461
  return this.request(`/cross/${encodeURIComponent(target)}/files/${cleanPath(filePath)}`);
462
462
  }
463
- /** 跨项目: 下载目录 */
464
- async crossDownloadDir(target, dir) {
465
- return fetchAndExtractZip(`${this.baseUrl}/cross/${encodeURIComponent(target)}/files-download/${cleanPath(dir)}`);
463
+ /** 跨项目: 下载文件或目录 */
464
+ async crossDownload(target, remotePath, localPath) {
465
+ return fetchAndExtractZip(`${this.baseUrl}/cross/${encodeURIComponent(target)}/files-download/${cleanPath(remotePath)}`, localPath);
466
466
  }
467
467
  }
468
468
  // ============ 模块级 API ============
@@ -566,8 +566,8 @@ export async function listFiles(dir) {
566
566
  export async function readFile(filePath) {
567
567
  return getClient().readFile(filePath);
568
568
  }
569
- export async function downloadDir(dir) {
570
- return getClient().downloadDir(dir);
569
+ export async function download(remotePath, localPath) {
570
+ return getClient().download(remotePath, localPath);
571
571
  }
572
572
  export async function crossListFiles(target, dir) {
573
573
  return getClient().crossListFiles(target, dir);
@@ -575,8 +575,8 @@ export async function crossListFiles(target, dir) {
575
575
  export async function crossReadFile(target, filePath) {
576
576
  return getClient().crossReadFile(target, filePath);
577
577
  }
578
- export async function crossDownloadDir(target, dir) {
579
- return getClient().crossDownloadDir(target, dir);
578
+ export async function crossDownload(target, remotePath, localPath) {
579
+ return getClient().crossDownload(target, remotePath, localPath);
580
580
  }
581
581
  export async function uploadFiles(localDir, remoteDir) {
582
582
  return getClient().uploadFiles(localDir, remoteDir);
@@ -558,17 +558,18 @@ export function registerTools(server, projectId, _user) {
558
558
  return wrap(`❌ ${String(e)}`);
559
559
  }
560
560
  });
561
- // 下载项目目录 (zip → 自动解压)
562
- server.tool('project_download_dir', '下载整个源码目录到本地临时目录(自动打包zip解压)。适合需要本地复用代码的场景,仅浏览代码请用project_read_file。跨项目时先用kg_list_projects获取项目ID', {
563
- dir: z.string().describe('目录路径(如"src")'),
561
+ // 下载项目文件或目录 (zip → 自动解压)
562
+ server.tool('project_download', '下载项目源码到本地(自动识别文件/目录,打包zip解压)。目标项目需已上传文件或关联源码目录。跨项目时先用kg_list_projects获取项目ID', {
563
+ remotePath: z.string().describe('远程路径,可以是目录(如"src")或单个文件(如"src/main.ts")'),
564
+ localPath: z.string().optional().describe('本地保存路径(不填则保存到系统临时目录,目录不存在会自动创建)'),
564
565
  targetProject: z.string().optional().describe('目标项目ID或名称(不填=当前项目)。ID可从kg_list_projects获取')
565
566
  }, async (args) => {
566
567
  try {
567
568
  const result = args.targetProject
568
- ? await storage.crossDownloadDir(args.targetProject, args.dir)
569
- : await storage.downloadDir(args.dir);
569
+ ? await storage.crossDownload(args.targetProject, args.remotePath, args.localPath)
570
+ : await storage.download(args.remotePath, args.localPath);
570
571
  const prefix = args.targetProject ? `[跨项目: ${args.targetProject}]\n\n` : '';
571
- return wrap(`${prefix}✅ 目录已下载并解压\n\n- 本地路径: ${result.localPath}\n- 文件数量: ${result.fileCount}\n\n可直接读取该路径下的文件`);
572
+ return wrap(`${prefix}✅ 已下载并解压\n\n- 本地路径: ${result.localPath}\n- 文件数量: ${result.fileCount}`);
572
573
  }
573
574
  catch (e) {
574
575
  return wrap(`❌ ${String(e)}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ppdocs/mcp",
3
- "version": "3.0.8",
3
+ "version": "3.1.0",
4
4
  "description": "ppdocs MCP Server - Knowledge Graph for Claude",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",