@ppdocs/mcp 3.13.0 → 3.13.1
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.
- package/README.md +11 -12
- package/dist/cli.js +56 -41
- package/dist/config.d.ts +23 -3
- package/dist/config.js +94 -36
- package/dist/index.d.ts +1 -1
- package/dist/index.js +45 -17
- package/dist/retainedCoreMatrix.d.ts +19 -0
- package/dist/retainedCoreMatrix.js +40 -0
- package/dist/storage/httpClient.d.ts +6 -75
- package/dist/storage/httpClient.js +5 -177
- package/dist/storage/types.d.ts +0 -35
- package/dist/tools/index.d.ts +2 -3
- package/dist/tools/index.js +2 -5
- package/dist/tools/shared.d.ts +2 -2
- package/dist/tools/shared.js +1 -1
- package/package.json +1 -1
- package/templates/AGENT.md +2 -5
- package/templates/README.md +0 -0
- package/templates/commands/init.md +0 -0
- package/templates/commands/pp/Zero_Defec_Genesis.md +0 -0
- package/templates/commands/pp/diagnose.md +0 -0
- package/templates/commands/pp/init.md +2 -1
- package/templates/commands/pp/review.md +0 -0
- package/templates/commands/pp/sync.md +2 -1
- package/templates/cursorrules.md +2 -5
- package/templates/hooks/SystemPrompt.md +1 -1
- package/templates/hooks/hook.py +0 -0
- package/templates/kiro-rules/ppdocs.md +2 -5
- package/dist/tools/init.d.ts +0 -12
- package/dist/tools/init.js +0 -89
package/README.md
CHANGED
|
@@ -64,12 +64,12 @@ npx @ppdocs/mcp init -p <项目ID> -k <密钥>
|
|
|
64
64
|
**Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
|
|
65
65
|
|
|
66
66
|
```json
|
|
67
|
-
{
|
|
68
|
-
"mcpServers": {
|
|
69
|
-
"ppdocs": {
|
|
70
|
-
"command": "npx",
|
|
71
|
-
"args": ["-y", "@ppdocs/mcp"],
|
|
72
|
-
"env": {
|
|
67
|
+
{
|
|
68
|
+
"mcpServers": {
|
|
69
|
+
"ppdocs-kg": {
|
|
70
|
+
"command": "npx",
|
|
71
|
+
"args": ["-y", "@ppdocs/mcp"],
|
|
72
|
+
"env": {
|
|
73
73
|
"PPDOCS_API_URL": "http://localhost:20001/api/项目ID/密码"
|
|
74
74
|
}
|
|
75
75
|
}
|
|
@@ -109,12 +109,11 @@ npx @ppdocs/mcp init -p <projectId> -k <key> --codex
|
|
|
109
109
|
|
|
110
110
|
### 核心工具
|
|
111
111
|
|
|
112
|
-
| 工具 | 说明 |
|
|
113
|
-
|------|------|
|
|
114
|
-
| `
|
|
115
|
-
| `
|
|
116
|
-
| `
|
|
117
|
-
| `kg_workflow` | Markdown 文档工作流管理 |
|
|
112
|
+
| 工具 | 说明 |
|
|
113
|
+
|------|------|
|
|
114
|
+
| `kg_status` | 查看项目仪表盘与流程图概况 |
|
|
115
|
+
| `kg_flowchart` | 统一的知识图谱入口,负责查询、更新、关系分析 |
|
|
116
|
+
| `kg_workflow` | Markdown 文档工作流管理 |
|
|
118
117
|
| `kg_task` | 任务管理 |
|
|
119
118
|
| `kg_ref` | 外部参考资料(URL拉取、查看、删除) |
|
|
120
119
|
| `code_scan` | 代码扫描 |
|
package/dist/cli.js
CHANGED
|
@@ -11,8 +11,26 @@ import { generateUser } from './config.js';
|
|
|
11
11
|
import { PpdocsApiClient } from './storage/httpClient.js';
|
|
12
12
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
13
13
|
const TEMPLATES_DIR = path.join(__dirname, '..', 'templates');
|
|
14
|
+
const CANONICAL_MCP_SERVER_NAME = 'ppdocs-kg';
|
|
15
|
+
const LEGACY_MCP_SERVER_PATTERNS = [
|
|
16
|
+
/^ppdocs$/,
|
|
17
|
+
/^ppdocs-mcp$/,
|
|
18
|
+
/^prodocs$/,
|
|
19
|
+
/^ppdocs-kg-.+$/,
|
|
20
|
+
];
|
|
21
|
+
function isLegacyPpdocsServerName(name) {
|
|
22
|
+
return name === CANONICAL_MCP_SERVER_NAME || LEGACY_MCP_SERVER_PATTERNS.some(pattern => pattern.test(name));
|
|
23
|
+
}
|
|
24
|
+
function stripLegacyPpdocsServers(mcpServers) {
|
|
25
|
+
return Object.fromEntries(Object.entries(mcpServers).filter(([name]) => !isLegacyPpdocsServerName(name)));
|
|
26
|
+
}
|
|
27
|
+
function removePpdocsServerAliases(cli) {
|
|
28
|
+
for (const name of [CANONICAL_MCP_SERVER_NAME, 'ppdocs', 'ppdocs-mcp', 'prodocs']) {
|
|
29
|
+
execSilent(`${cli} mcp remove ${name}`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
14
32
|
function parseArgs(args) {
|
|
15
|
-
const opts = { port:
|
|
33
|
+
const opts = { port: 20000, api: 'localhost', codex: false };
|
|
16
34
|
for (let i = 0; i < args.length; i++) {
|
|
17
35
|
const arg = args[i];
|
|
18
36
|
if (arg === '-p' || arg === '--project') {
|
|
@@ -101,7 +119,7 @@ Options:
|
|
|
101
119
|
-p, --project Project ID (required)
|
|
102
120
|
-k, --key API key (required)
|
|
103
121
|
-u, --user User name for logs (optional, auto-generated)
|
|
104
|
-
--port API port (default:
|
|
122
|
+
--port API port (default: 20000)
|
|
105
123
|
--api API host (default: localhost)
|
|
106
124
|
--codex Codex mode: generate AGENTS.md instead of .claude/
|
|
107
125
|
--from <id> Pull IDE configs from a template project
|
|
@@ -255,15 +273,12 @@ function execSilent(cmd) {
|
|
|
255
273
|
}
|
|
256
274
|
catch { /* ignore */ }
|
|
257
275
|
}
|
|
258
|
-
/** 自动检测 AI CLI 并注册全局 MCP
|
|
276
|
+
/** 自动检测 AI CLI 并注册全局 MCP,仅注入项目根目录 hint 以便运行时定位 .ppdocs */
|
|
259
277
|
function autoRegisterMcp(cwd, apiUrl, user, skipGemini = false) {
|
|
260
278
|
const detected = [];
|
|
261
279
|
const serverName = 'ppdocs-kg';
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
`PPDOCS_USER=${user}`,
|
|
265
|
-
`PPDOCS_PROJECT_ROOT=${cwd}`,
|
|
266
|
-
];
|
|
280
|
+
// 不再注入 PPDOCS_PROJECT_ROOT — 全局注册时依赖运行时 CWD 自动定位 .ppdocs
|
|
281
|
+
const envPairs = [];
|
|
267
282
|
/** 用 spawnSync + args 数组注册 MCP,避免 Windows cmd.exe 引号解析问题 */
|
|
268
283
|
function mcpAdd(cli, envFlag) {
|
|
269
284
|
const args = ['mcp', 'add'];
|
|
@@ -273,8 +288,14 @@ function autoRegisterMcp(cwd, apiUrl, user, skipGemini = false) {
|
|
|
273
288
|
// macOS/Linux: 必须注入 PATH,否则 Claude Code 启动 MCP 时可能找不到 npx/node
|
|
274
289
|
// (Claude Code 进程的 PATH 通常不含 /opt/homebrew/bin 等 Homebrew/nvm 路径)
|
|
275
290
|
if (process.platform !== 'win32') {
|
|
276
|
-
const
|
|
277
|
-
const
|
|
291
|
+
const home = process.env.HOME || '';
|
|
292
|
+
const extraPaths = [
|
|
293
|
+
'/usr/local/bin', '/opt/homebrew/bin', '/home/linuxbrew/.linuxbrew/bin',
|
|
294
|
+
`${home}/.local/bin`, // pip/pipx (Linux)
|
|
295
|
+
`${home}/.nvm/versions/node`, // nvm hint (resolved via process.env.PATH)
|
|
296
|
+
`${home}/.fnm/aliases/default/bin`, // fnm
|
|
297
|
+
].join(':');
|
|
298
|
+
const fullPath = `${process.env.PATH || '/usr/bin:/bin'}:${extraPaths}`;
|
|
278
299
|
args.push(envFlag, `PATH=${fullPath}`);
|
|
279
300
|
}
|
|
280
301
|
args.push(serverName, '--', 'npx', '-y', '@ppdocs/mcp@latest');
|
|
@@ -286,12 +307,12 @@ function autoRegisterMcp(cwd, apiUrl, user, skipGemini = false) {
|
|
|
286
307
|
if (commandExists('claude')) {
|
|
287
308
|
detected.push('Claude');
|
|
288
309
|
try {
|
|
289
|
-
|
|
310
|
+
removePpdocsServerAliases('claude');
|
|
290
311
|
mcpAdd('claude', '-e');
|
|
291
312
|
}
|
|
292
313
|
catch {
|
|
293
314
|
try {
|
|
294
|
-
|
|
315
|
+
removePpdocsServerAliases('claude');
|
|
295
316
|
mcpAdd('claude', '-e');
|
|
296
317
|
}
|
|
297
318
|
catch {
|
|
@@ -303,7 +324,7 @@ function autoRegisterMcp(cwd, apiUrl, user, skipGemini = false) {
|
|
|
303
324
|
if (commandExists('codex')) {
|
|
304
325
|
detected.push('Codex');
|
|
305
326
|
try {
|
|
306
|
-
|
|
327
|
+
removePpdocsServerAliases('codex');
|
|
307
328
|
mcpAdd('codex', '--env');
|
|
308
329
|
}
|
|
309
330
|
catch {
|
|
@@ -314,7 +335,7 @@ function autoRegisterMcp(cwd, apiUrl, user, skipGemini = false) {
|
|
|
314
335
|
if (commandExists('opencode')) {
|
|
315
336
|
detected.push('OpenCode');
|
|
316
337
|
try {
|
|
317
|
-
|
|
338
|
+
removePpdocsServerAliases('opencode');
|
|
318
339
|
mcpAdd('opencode', '-e');
|
|
319
340
|
}
|
|
320
341
|
catch {
|
|
@@ -346,7 +367,7 @@ function autoRegisterMcp(cwd, apiUrl, user, skipGemini = false) {
|
|
|
346
367
|
console.log(`✅ Registered MCP for: ${detected.join(', ')}`);
|
|
347
368
|
return true;
|
|
348
369
|
}
|
|
349
|
-
/** 在指定路径创建或更新 mcp.json
|
|
370
|
+
/** 在指定路径创建或更新 mcp.json 配置,仅保留 PATH + 可选项目根目录 hint */
|
|
350
371
|
function createMcpConfigAt(mcpPath, apiUrl, options) {
|
|
351
372
|
let mcpConfig = {};
|
|
352
373
|
if (fs.existsSync(mcpPath)) {
|
|
@@ -359,25 +380,30 @@ function createMcpConfigAt(mcpPath, apiUrl, options) {
|
|
|
359
380
|
}
|
|
360
381
|
}
|
|
361
382
|
const isWindows = process.platform === 'win32';
|
|
362
|
-
const
|
|
363
|
-
const
|
|
364
|
-
|
|
383
|
+
const home = process.env.HOME || '';
|
|
384
|
+
const extraPaths = [
|
|
385
|
+
'/usr/local/bin', '/opt/homebrew/bin', '/home/linuxbrew/.linuxbrew/bin',
|
|
386
|
+
`${home}/.local/bin`, // pip/pipx (Linux)
|
|
387
|
+
`${home}/.nvm/versions/node`, // nvm hint
|
|
388
|
+
`${home}/.fnm/aliases/default/bin`, // fnm
|
|
389
|
+
].join(':');
|
|
390
|
+
const actualPath = `${process.env.PATH || '/usr/bin:/bin'}:${extraPaths}`;
|
|
391
|
+
// 构建 env: 非 Windows 补 PATH + 注入项目根目录 hint(如有)
|
|
365
392
|
const env = {};
|
|
366
393
|
if (!isWindows)
|
|
367
394
|
env.PATH = actualPath;
|
|
368
|
-
if (!options?.noEnv) {
|
|
369
|
-
env.
|
|
370
|
-
if (options?.user)
|
|
371
|
-
env.PPDOCS_USER = options.user;
|
|
372
|
-
if (options?.projectRoot)
|
|
373
|
-
env.PPDOCS_PROJECT_ROOT = options.projectRoot;
|
|
395
|
+
if (!options?.noEnv && options?.projectRoot) {
|
|
396
|
+
env.PPDOCS_PROJECT_ROOT = options.projectRoot;
|
|
374
397
|
}
|
|
375
398
|
const ppdocsServer = isWindows
|
|
376
399
|
? { command: 'cmd', args: ['/c', 'npx', '-y', '@ppdocs/mcp@latest'], ...(Object.keys(env).length > 0 && { env }) }
|
|
377
400
|
: { command: 'npx', args: ['-y', '@ppdocs/mcp@latest'], env };
|
|
401
|
+
const existingServers = (mcpConfig.mcpServers && typeof mcpConfig.mcpServers === 'object'
|
|
402
|
+
? mcpConfig.mcpServers
|
|
403
|
+
: {});
|
|
378
404
|
mcpConfig.mcpServers = {
|
|
379
|
-
...(
|
|
380
|
-
|
|
405
|
+
...stripLegacyPpdocsServers(existingServers),
|
|
406
|
+
[CANONICAL_MCP_SERVER_NAME]: ppdocsServer,
|
|
381
407
|
};
|
|
382
408
|
// 确保父目录存在
|
|
383
409
|
const parentDir = path.dirname(mcpPath);
|
|
@@ -411,7 +437,9 @@ function copyDirRecursive(src, dest) {
|
|
|
411
437
|
function generateHooksConfig(cwd) {
|
|
412
438
|
// 使用绝对路径,避免 CWD 不一致导致找不到脚本
|
|
413
439
|
const hookPath = path.join(cwd, '.claude', 'hooks', 'hook.py').replace(/\\/g, '/');
|
|
414
|
-
|
|
440
|
+
// Linux/macOS 优先使用 python3 (Ubuntu 20.04+ 默认无 python 命令)
|
|
441
|
+
const pythonCmd = process.platform === 'win32' ? 'python' : 'python3';
|
|
442
|
+
const command = `${pythonCmd} ${hookPath}`;
|
|
415
443
|
return {
|
|
416
444
|
hooks: {
|
|
417
445
|
UserPromptSubmit: [
|
|
@@ -425,29 +453,16 @@ function generateHooksConfig(cwd) {
|
|
|
425
453
|
/** 生成 MCP 权限配置 (允许全部 ppdocs-kg 工具) */
|
|
426
454
|
function generateMcpPermissions() {
|
|
427
455
|
const mcpMethods = [
|
|
428
|
-
// 初始化 + 导航
|
|
429
|
-
'kg_init',
|
|
430
456
|
'kg_status',
|
|
431
|
-
// 知识管理
|
|
432
|
-
'kg_projects',
|
|
433
457
|
'kg_workflow',
|
|
434
|
-
// 工作流
|
|
435
458
|
'kg_task',
|
|
436
|
-
'kg_files',
|
|
437
|
-
'kg_discuss',
|
|
438
459
|
'kg_ref',
|
|
439
|
-
// 流程图
|
|
440
460
|
'kg_flowchart',
|
|
441
|
-
// 文档查询
|
|
442
|
-
'kg_doc',
|
|
443
|
-
// 协作
|
|
444
|
-
'kg_meeting',
|
|
445
|
-
// 代码分析
|
|
446
461
|
'code_scan',
|
|
447
462
|
'code_smart_context',
|
|
448
463
|
'code_full_path',
|
|
449
464
|
];
|
|
450
|
-
return mcpMethods.map(m => `
|
|
465
|
+
return mcpMethods.map(m => `mcp__${CANONICAL_MCP_SERVER_NAME}__${m}`);
|
|
451
466
|
}
|
|
452
467
|
/** 安装 Claude Code 模板 */
|
|
453
468
|
function installClaudeTemplates(cwd) {
|
package/dist/config.d.ts
CHANGED
|
@@ -1,16 +1,36 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* ppdocs MCP Config
|
|
3
|
-
* 读取配置:
|
|
3
|
+
* 读取配置: 项目根目录 .ppdocs 是唯一正式绑定源
|
|
4
4
|
*/
|
|
5
5
|
export interface PpdocsConfig {
|
|
6
6
|
apiUrl: string;
|
|
7
7
|
projectId: string;
|
|
8
8
|
user: string;
|
|
9
9
|
agentId: string;
|
|
10
|
-
source: '
|
|
10
|
+
source: 'file';
|
|
11
|
+
configPath: string;
|
|
12
|
+
projectRoot: string;
|
|
13
|
+
searchRoot: string;
|
|
14
|
+
warnings: string[];
|
|
11
15
|
}
|
|
12
16
|
export declare const PPDOCS_CONFIG_FILE = ".ppdocs";
|
|
17
|
+
declare const REQUIRED_BINDING_FIELDS: readonly ["api", "projectId", "key"];
|
|
18
|
+
type RequiredBindingField = typeof REQUIRED_BINDING_FIELDS[number];
|
|
19
|
+
export interface PpdocsBindingDiagnostic {
|
|
20
|
+
reason: 'not_found' | 'invalid_json' | 'missing_fields';
|
|
21
|
+
searchRoot: string;
|
|
22
|
+
hintedProjectRoot?: string;
|
|
23
|
+
configPath?: string;
|
|
24
|
+
missingFields?: RequiredBindingField[];
|
|
25
|
+
warnings: string[];
|
|
26
|
+
}
|
|
27
|
+
export declare class PpdocsBindingError extends Error {
|
|
28
|
+
diagnostic: PpdocsBindingDiagnostic;
|
|
29
|
+
constructor(diagnostic: PpdocsBindingDiagnostic);
|
|
30
|
+
}
|
|
13
31
|
/** 生成随机用户名 */
|
|
14
32
|
export declare function generateUser(): string;
|
|
15
33
|
export declare function generateAgentId(): string;
|
|
16
|
-
export declare function
|
|
34
|
+
export declare function findPpdocsFile(startDir?: string): string | null;
|
|
35
|
+
export declare function loadConfig(startDir?: string): Promise<PpdocsConfig>;
|
|
36
|
+
export {};
|
package/dist/config.js
CHANGED
|
@@ -1,10 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* ppdocs MCP Config
|
|
3
|
-
* 读取配置:
|
|
3
|
+
* 读取配置: 项目根目录 .ppdocs 是唯一正式绑定源
|
|
4
4
|
*/
|
|
5
5
|
import * as fs from 'fs';
|
|
6
6
|
import * as path from 'path';
|
|
7
7
|
export const PPDOCS_CONFIG_FILE = '.ppdocs';
|
|
8
|
+
const REQUIRED_BINDING_FIELDS = ['api', 'projectId', 'key'];
|
|
9
|
+
export class PpdocsBindingError extends Error {
|
|
10
|
+
diagnostic;
|
|
11
|
+
constructor(diagnostic) {
|
|
12
|
+
super(createBindingErrorMessage(diagnostic));
|
|
13
|
+
this.name = 'PpdocsBindingError';
|
|
14
|
+
this.diagnostic = diagnostic;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
8
17
|
/** 生成随机用户名 */
|
|
9
18
|
export function generateUser() {
|
|
10
19
|
const chars = 'abcdefghjkmnpqrstuvwxyz23456789';
|
|
@@ -25,20 +34,44 @@ export function generateAgentId() {
|
|
|
25
34
|
return _cachedAgentId;
|
|
26
35
|
}
|
|
27
36
|
// ============ 配置读取 ============
|
|
28
|
-
function
|
|
29
|
-
const
|
|
30
|
-
if (
|
|
31
|
-
|
|
32
|
-
|
|
37
|
+
function buildWarnings() {
|
|
38
|
+
const warnings = [];
|
|
39
|
+
if (process.env.PPDOCS_API_URL) {
|
|
40
|
+
warnings.push('PPDOCS_API_URL is ignored at runtime; .ppdocs is the canonical binding source.');
|
|
41
|
+
}
|
|
42
|
+
return warnings;
|
|
43
|
+
}
|
|
44
|
+
function isNonEmptyString(value) {
|
|
45
|
+
return typeof value === 'string' && value.trim().length > 0;
|
|
46
|
+
}
|
|
47
|
+
function normalizeApiBase(api) {
|
|
48
|
+
return api.replace(/\/+$/, '');
|
|
49
|
+
}
|
|
50
|
+
function createBindingErrorMessage(diagnostic) {
|
|
51
|
+
switch (diagnostic.reason) {
|
|
52
|
+
case 'not_found':
|
|
53
|
+
return `No ${PPDOCS_CONFIG_FILE} binding found from ${diagnostic.searchRoot}`;
|
|
54
|
+
case 'invalid_json':
|
|
55
|
+
return `${PPDOCS_CONFIG_FILE} is not valid JSON at ${diagnostic.configPath}`;
|
|
56
|
+
case 'missing_fields':
|
|
57
|
+
return `${PPDOCS_CONFIG_FILE} is missing required fields: ${(diagnostic.missingFields || []).join(', ')}`;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function resolveSearchRoot(startDir = process.cwd()) {
|
|
61
|
+
const hintedProjectRoot = process.env.PPDOCS_PROJECT_ROOT?.trim();
|
|
62
|
+
const candidate = path.resolve(hintedProjectRoot || startDir);
|
|
63
|
+
if (fs.existsSync(candidate) && fs.statSync(candidate).isFile()) {
|
|
64
|
+
return {
|
|
65
|
+
searchRoot: path.dirname(candidate),
|
|
66
|
+
hintedProjectRoot: hintedProjectRoot ? candidate : undefined,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
33
69
|
return {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
user: process.env.PPDOCS_USER || generateUser(),
|
|
37
|
-
agentId: process.env.PPDOCS_AGENT_ID || generateAgentId(),
|
|
38
|
-
source: 'env',
|
|
70
|
+
searchRoot: candidate,
|
|
71
|
+
hintedProjectRoot: hintedProjectRoot ? candidate : undefined,
|
|
39
72
|
};
|
|
40
73
|
}
|
|
41
|
-
function findPpdocsFile(startDir = process.cwd()) {
|
|
74
|
+
export function findPpdocsFile(startDir = process.cwd()) {
|
|
42
75
|
let currentDir = path.resolve(startDir);
|
|
43
76
|
while (true) {
|
|
44
77
|
const configPath = path.join(currentDir, PPDOCS_CONFIG_FILE);
|
|
@@ -52,33 +85,58 @@ function findPpdocsFile(startDir = process.cwd()) {
|
|
|
52
85
|
currentDir = parentDir;
|
|
53
86
|
}
|
|
54
87
|
}
|
|
55
|
-
function
|
|
56
|
-
const hintedRoot = process.env.PPDOCS_PROJECT_ROOT;
|
|
57
|
-
const configPath = hintedRoot
|
|
58
|
-
? path.join(path.resolve(hintedRoot), PPDOCS_CONFIG_FILE)
|
|
59
|
-
: findPpdocsFile();
|
|
60
|
-
if (!configPath || !fs.existsSync(configPath))
|
|
61
|
-
return null;
|
|
88
|
+
function parsePpdocsFile(configPath, searchRoot, warnings) {
|
|
62
89
|
try {
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
90
|
+
const raw = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
91
|
+
const missingFields = REQUIRED_BINDING_FIELDS.filter((field) => !isNonEmptyString(raw[field]));
|
|
92
|
+
if (missingFields.length > 0) {
|
|
93
|
+
throw new PpdocsBindingError({
|
|
94
|
+
reason: 'missing_fields',
|
|
95
|
+
searchRoot,
|
|
96
|
+
configPath,
|
|
97
|
+
missingFields,
|
|
98
|
+
warnings,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
const projectRoot = path.dirname(configPath);
|
|
102
|
+
return {
|
|
103
|
+
apiUrl: `${normalizeApiBase(raw.api)}/api/${raw.projectId}/${raw.key}`,
|
|
104
|
+
projectId: raw.projectId,
|
|
105
|
+
user: isNonEmptyString(raw.user) ? raw.user : generateUser(),
|
|
106
|
+
agentId: isNonEmptyString(raw.agentId)
|
|
107
|
+
? raw.agentId
|
|
108
|
+
: (process.env.PPDOCS_AGENT_ID || generateAgentId()),
|
|
109
|
+
source: 'file',
|
|
110
|
+
configPath,
|
|
111
|
+
projectRoot,
|
|
112
|
+
searchRoot,
|
|
113
|
+
warnings,
|
|
114
|
+
};
|
|
67
115
|
}
|
|
68
|
-
catch {
|
|
69
|
-
|
|
116
|
+
catch (error) {
|
|
117
|
+
if (error instanceof PpdocsBindingError) {
|
|
118
|
+
throw error;
|
|
119
|
+
}
|
|
120
|
+
throw new PpdocsBindingError({
|
|
121
|
+
reason: 'invalid_json',
|
|
122
|
+
searchRoot,
|
|
123
|
+
configPath,
|
|
124
|
+
warnings,
|
|
125
|
+
});
|
|
70
126
|
}
|
|
71
127
|
}
|
|
72
128
|
// ============ 入口 ============
|
|
73
|
-
export async function loadConfig() {
|
|
74
|
-
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
129
|
+
export async function loadConfig(startDir = process.cwd()) {
|
|
130
|
+
const warnings = buildWarnings();
|
|
131
|
+
const { searchRoot, hintedProjectRoot } = resolveSearchRoot(startDir);
|
|
132
|
+
const configPath = findPpdocsFile(searchRoot);
|
|
133
|
+
if (!configPath) {
|
|
134
|
+
throw new PpdocsBindingError({
|
|
135
|
+
reason: 'not_found',
|
|
136
|
+
searchRoot,
|
|
137
|
+
hintedProjectRoot,
|
|
138
|
+
warnings,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
return parsePpdocsFile(configPath, searchRoot, warnings);
|
|
84
142
|
}
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*
|
|
5
5
|
* 使用方式:
|
|
6
6
|
* 1. CLI 初始化: npx @ppdocs/mcp init -p <projectId> -k <key>
|
|
7
|
-
* 2. MCP 服务:
|
|
7
|
+
* 2. MCP 服务: 启动时自动读取项目根目录 .ppdocs
|
|
8
8
|
*/
|
|
9
9
|
import * as fs from 'fs';
|
|
10
10
|
import * as path from 'path';
|
|
@@ -14,7 +14,7 @@ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
|
|
|
14
14
|
import { registerTools } from './tools/index.js';
|
|
15
15
|
import { initClient } from './storage/httpClient.js';
|
|
16
16
|
import { runCli } from './cli.js';
|
|
17
|
-
import { loadConfig,
|
|
17
|
+
import { loadConfig, PpdocsBindingError } from './config.js';
|
|
18
18
|
// 从 package.json 读取版本号
|
|
19
19
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
20
20
|
const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json'), 'utf-8'));
|
|
@@ -64,6 +64,32 @@ async function checkConnectivity(apiUrl) {
|
|
|
64
64
|
clearTimeout(timer);
|
|
65
65
|
}
|
|
66
66
|
}
|
|
67
|
+
function logBindingSuccess(config) {
|
|
68
|
+
console.error(`ppdocs MCP v${VERSION} | project: ${config.projectId} | user: ${config.user} | source: ${config.source}`);
|
|
69
|
+
console.error(`[binding] search root: ${config.searchRoot}`);
|
|
70
|
+
console.error(`[binding] config file: ${config.configPath}`);
|
|
71
|
+
console.error(`[binding] project root: ${config.projectRoot}`);
|
|
72
|
+
for (const warning of config.warnings) {
|
|
73
|
+
console.error(`[binding] warning: ${warning}`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
function logBindingFailure(error) {
|
|
77
|
+
console.error(`ppdocs MCP v${VERSION} | ❌ startup aborted: ${error.message}`);
|
|
78
|
+
console.error(`[binding] search root: ${error.diagnostic.searchRoot}`);
|
|
79
|
+
if (error.diagnostic.hintedProjectRoot) {
|
|
80
|
+
console.error(`[binding] PPDOCS_PROJECT_ROOT hint: ${error.diagnostic.hintedProjectRoot}`);
|
|
81
|
+
}
|
|
82
|
+
if (error.diagnostic.configPath) {
|
|
83
|
+
console.error(`[binding] config file: ${error.diagnostic.configPath}`);
|
|
84
|
+
}
|
|
85
|
+
if (error.diagnostic.missingFields?.length) {
|
|
86
|
+
console.error(`[binding] missing fields: ${error.diagnostic.missingFields.join(', ')}`);
|
|
87
|
+
}
|
|
88
|
+
for (const warning of error.diagnostic.warnings) {
|
|
89
|
+
console.error(`[binding] warning: ${warning}`);
|
|
90
|
+
}
|
|
91
|
+
console.error(`[binding] fix: create a project-root .ppdocs file before starting the MCP server.`);
|
|
92
|
+
}
|
|
67
93
|
// 检查是否为 CLI 命令
|
|
68
94
|
const args = process.argv.slice(2);
|
|
69
95
|
if (args.length > 0) {
|
|
@@ -73,24 +99,26 @@ if (args.length > 0) {
|
|
|
73
99
|
}
|
|
74
100
|
// 运行 MCP 服务
|
|
75
101
|
async function main() {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
initClient(config.apiUrl);
|
|
80
|
-
console.error(`ppdocs MCP v${VERSION} | project: ${config.projectId} | user: ${config.user} | source: ${config.source}`);
|
|
81
|
-
// 启动时连通性检查 (非阻塞, 3秒超时)
|
|
82
|
-
checkConnectivity(config.apiUrl).catch(() => { });
|
|
102
|
+
let config;
|
|
103
|
+
try {
|
|
104
|
+
config = await loadConfig();
|
|
83
105
|
}
|
|
84
|
-
|
|
85
|
-
|
|
106
|
+
catch (error) {
|
|
107
|
+
if (error instanceof PpdocsBindingError) {
|
|
108
|
+
logBindingFailure(error);
|
|
109
|
+
process.exit(1);
|
|
110
|
+
}
|
|
111
|
+
throw error;
|
|
86
112
|
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
113
|
+
initClient(config.apiUrl);
|
|
114
|
+
logBindingSuccess(config);
|
|
115
|
+
// 启动时连通性检查 (非阻塞, 3秒超时)
|
|
116
|
+
checkConnectivity(config.apiUrl).catch(() => { });
|
|
117
|
+
const projectId = config.projectId;
|
|
118
|
+
const user = config.user;
|
|
119
|
+
const agentId = config.agentId;
|
|
90
120
|
const server = new McpServer({ name: `ppdocs [${projectId}]`, version: VERSION }, { capabilities: { tools: {} } });
|
|
91
|
-
registerTools(server, projectId, user, agentId
|
|
92
|
-
console.error(`[kg_init] 项目已切换: ${newProjectId}`);
|
|
93
|
-
});
|
|
121
|
+
registerTools(server, projectId, user, agentId);
|
|
94
122
|
const transport = new StdioServerTransport();
|
|
95
123
|
await server.connect(transport);
|
|
96
124
|
const shutdown = () => { process.exit(0); };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export declare const RETAINED_CORE_MATRIX: {
|
|
2
|
+
readonly retainedTools: {
|
|
3
|
+
readonly kg_status: true;
|
|
4
|
+
readonly kg_flowchart: readonly ["list", "get", "search", "get_relations", "find_path", "get_node", "update_node", "delete_node", "batch_add", "bind", "unbind", "orphans", "health", "create_chart", "delete_chart"];
|
|
5
|
+
readonly kg_workflow: readonly ["list", "get", "save", "delete"];
|
|
6
|
+
readonly kg_task: readonly ["create", "get", "update", "archive", "delete"];
|
|
7
|
+
readonly kg_ref: readonly ["list", "get", "fetch", "delete"];
|
|
8
|
+
readonly code_tools: readonly ["code_scan", "code_smart_context", "code_full_path"];
|
|
9
|
+
};
|
|
10
|
+
readonly removed: {
|
|
11
|
+
readonly tools: readonly ["kg_init", "kg_discuss", "kg_meeting", "kg_files", "kg_projects"];
|
|
12
|
+
readonly referenceActions: readonly ["save", "read_file", "import_file", "reimport", "refetch-copyin", "copy-in"];
|
|
13
|
+
readonly clientSurfaces: readonly ["pitfalls"];
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
export type RetainedToolName = keyof typeof RETAINED_CORE_MATRIX.retainedTools;
|
|
17
|
+
export declare function getRetainedActions(tool: Exclude<RetainedToolName, 'kg_status'>): readonly string[];
|
|
18
|
+
export declare function isRemovedTool(tool: string): boolean;
|
|
19
|
+
export declare function isRemovedReferenceAction(action: string): boolean;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export const RETAINED_CORE_MATRIX = {
|
|
2
|
+
retainedTools: {
|
|
3
|
+
kg_status: true,
|
|
4
|
+
kg_flowchart: [
|
|
5
|
+
'list',
|
|
6
|
+
'get',
|
|
7
|
+
'search',
|
|
8
|
+
'get_relations',
|
|
9
|
+
'find_path',
|
|
10
|
+
'get_node',
|
|
11
|
+
'update_node',
|
|
12
|
+
'delete_node',
|
|
13
|
+
'batch_add',
|
|
14
|
+
'bind',
|
|
15
|
+
'unbind',
|
|
16
|
+
'orphans',
|
|
17
|
+
'health',
|
|
18
|
+
'create_chart',
|
|
19
|
+
'delete_chart',
|
|
20
|
+
],
|
|
21
|
+
kg_workflow: ['list', 'get', 'save', 'delete'],
|
|
22
|
+
kg_task: ['create', 'get', 'update', 'archive', 'delete'],
|
|
23
|
+
kg_ref: ['list', 'get', 'fetch', 'delete'],
|
|
24
|
+
code_tools: ['code_scan', 'code_smart_context', 'code_full_path'],
|
|
25
|
+
},
|
|
26
|
+
removed: {
|
|
27
|
+
tools: ['kg_init', 'kg_discuss', 'kg_meeting', 'kg_files', 'kg_projects'],
|
|
28
|
+
referenceActions: ['save', 'read_file', 'import_file', 'reimport', 'refetch-copyin', 'copy-in'],
|
|
29
|
+
clientSurfaces: ['pitfalls'],
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
export function getRetainedActions(tool) {
|
|
33
|
+
return RETAINED_CORE_MATRIX.retainedTools[tool];
|
|
34
|
+
}
|
|
35
|
+
export function isRemovedTool(tool) {
|
|
36
|
+
return RETAINED_CORE_MATRIX.removed.tools.includes(tool);
|
|
37
|
+
}
|
|
38
|
+
export function isRemovedReferenceAction(action) {
|
|
39
|
+
return RETAINED_CORE_MATRIX.removed.referenceActions.includes(action);
|
|
40
|
+
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*
|
|
5
5
|
* API URL 格式: http://localhost:20099/api/:projectId/:password/...
|
|
6
6
|
*/
|
|
7
|
-
import type { Task, TaskSummary, TaskLogType, TaskExperience, FileInfo, WorkflowSummary, WorkflowDoc, WorkflowSelector, Reference, FetchResult
|
|
7
|
+
import type { Task, TaskSummary, TaskLogType, TaskExperience, FileInfo, WorkflowSummary, WorkflowDoc, WorkflowSelector, Reference, FetchResult } from './types.js';
|
|
8
8
|
export declare class PpdocsApiClient {
|
|
9
9
|
private baseUrl;
|
|
10
10
|
private serverUrl;
|
|
@@ -26,32 +26,9 @@ export declare class PpdocsApiClient {
|
|
|
26
26
|
deleteWorkflow(workflowId: string, scope: 'global' | 'project'): Promise<boolean>;
|
|
27
27
|
listReferences(): Promise<Reference[]>;
|
|
28
28
|
getReference(refId: string): Promise<Reference | null>;
|
|
29
|
-
saveReference(reference: Reference): Promise<boolean>;
|
|
30
29
|
deleteReference(refId: string): Promise<boolean>;
|
|
31
|
-
readReferenceFile(path: string): Promise<string>;
|
|
32
|
-
/** 复制本地文件到参考文件库, 返回相对路径列表 */
|
|
33
|
-
copyReferenceFiles(paths: string[], targetDir?: string): Promise<string[]>;
|
|
34
30
|
/** 从 URL 拉取文档内容并保存为参考 */
|
|
35
31
|
fetchRefUrl(url: string, refId?: string): Promise<FetchResult>;
|
|
36
|
-
/** 导入本地文件内容到参考系统 */
|
|
37
|
-
importLocalFile(refId: string, sourcePath: string): Promise<FileImportResult>;
|
|
38
|
-
listPitfalls(): Promise<PitfallSummary[]>;
|
|
39
|
-
getPitfall(pitfallId: string): Promise<Pitfall | null>;
|
|
40
|
-
createPitfall(input: {
|
|
41
|
-
title: string;
|
|
42
|
-
category?: string;
|
|
43
|
-
symptom?: string;
|
|
44
|
-
root_cause?: string;
|
|
45
|
-
solution?: string;
|
|
46
|
-
prevention?: string;
|
|
47
|
-
severity?: string;
|
|
48
|
-
related_nodes?: string[];
|
|
49
|
-
source_task?: string;
|
|
50
|
-
tags?: string[];
|
|
51
|
-
}): Promise<Pitfall>;
|
|
52
|
-
savePitfall(pitfall: Pitfall): Promise<boolean>;
|
|
53
|
-
deletePitfall(pitfallId: string): Promise<boolean>;
|
|
54
|
-
searchPitfalls(query: string, status?: string, category?: string): Promise<PitfallSummary[]>;
|
|
55
32
|
listTasks(status?: 'active' | 'archived'): Promise<TaskSummary[]>;
|
|
56
33
|
getTask(taskId: string, mode?: 'smart' | 'full'): Promise<Task | null>;
|
|
57
34
|
createTask(task: {
|
|
@@ -74,31 +51,6 @@ export declare class PpdocsApiClient {
|
|
|
74
51
|
deleteFlowchart(chartId: string): Promise<unknown>;
|
|
75
52
|
getFlowchartOrphans(): Promise<unknown[]>;
|
|
76
53
|
getFlowchartHealth(): Promise<unknown[]>;
|
|
77
|
-
/** 列出所有可访问的项目 */
|
|
78
|
-
crossListProjects(): Promise<{
|
|
79
|
-
id: string;
|
|
80
|
-
name: string;
|
|
81
|
-
description: string;
|
|
82
|
-
updatedAt: string;
|
|
83
|
-
}[]>;
|
|
84
|
-
/** 列出项目文件 */
|
|
85
|
-
listFiles(dir?: string): Promise<FileInfo[]>;
|
|
86
|
-
/** 读取项目文件 */
|
|
87
|
-
readFile(filePath: string): Promise<string>;
|
|
88
|
-
/** 下载项目文件或目录 (zip) → 自动解压到指定目录或 temp 目录 */
|
|
89
|
-
download(remotePath: string, localPath?: string): Promise<{
|
|
90
|
-
localPath: string;
|
|
91
|
-
fileCount: number;
|
|
92
|
-
}>;
|
|
93
|
-
/** 跨项目: 列出文件 */
|
|
94
|
-
crossListFiles(target: string, dir?: string): Promise<FileInfo[]>;
|
|
95
|
-
/** 跨项目: 读取文件 */
|
|
96
|
-
crossReadFile(target: string, filePath: string): Promise<string>;
|
|
97
|
-
/** 跨项目: 下载文件或目录 */
|
|
98
|
-
crossDownload(target: string, remotePath: string, localPath?: string): Promise<{
|
|
99
|
-
localPath: string;
|
|
100
|
-
fileCount: number;
|
|
101
|
-
}>;
|
|
102
54
|
/** 扫描项目代码, 构建/更新索引 */
|
|
103
55
|
analyzerScan(projectPath: string, force?: boolean): Promise<unknown>;
|
|
104
56
|
/** 智能上下文: 代码+文档+规则+任务全关联 */
|
|
@@ -106,26 +58,6 @@ export declare class PpdocsApiClient {
|
|
|
106
58
|
/** 全关联路径: 两个符号之间的代码+文档路径 */
|
|
107
59
|
analyzerFullPath(projectPath: string, symbolA: string, symbolB: string, maxDepth?: number): Promise<unknown>;
|
|
108
60
|
private publicRequest;
|
|
109
|
-
/** 列出活跃讨论 (公开路由) */
|
|
110
|
-
discussionList(): Promise<unknown>;
|
|
111
|
-
/** 创建讨论 (公开路由) */
|
|
112
|
-
discussionCreate(title: string, initiator: string, participants: string[], content: string, msgSummary?: string): Promise<{
|
|
113
|
-
id: string;
|
|
114
|
-
}>;
|
|
115
|
-
/** 批量读取讨论 (公开路由, 支持已读追踪) */
|
|
116
|
-
discussionReadByIds(ids: string[], reader?: string, mode?: string): Promise<unknown>;
|
|
117
|
-
/** 列出所有讨论 (含历史, 按参与方筛选) */
|
|
118
|
-
discussionListAll(participant?: string): Promise<unknown>;
|
|
119
|
-
/** 回复讨论 (公开路由) */
|
|
120
|
-
discussionReply(id: string, sender: string, content: string, msgSummary?: string, newSummary?: string): Promise<boolean>;
|
|
121
|
-
/** 完成讨论 (公开路由, 标记为 completed) */
|
|
122
|
-
discussionComplete(id: string): Promise<boolean>;
|
|
123
|
-
/** 归档关闭讨论 (需项目认证) */
|
|
124
|
-
discussionClose(id: string, conclusion: string): Promise<{
|
|
125
|
-
archived_path?: string;
|
|
126
|
-
}>;
|
|
127
|
-
/** 删除讨论 (公开路由) */
|
|
128
|
-
discussionDelete(id: string): Promise<boolean>;
|
|
129
61
|
/** 列出公共文件 */
|
|
130
62
|
publicFilesList(dir?: string): Promise<FileInfo[]>;
|
|
131
63
|
/** 读取公共文件 (文本) */
|
|
@@ -141,12 +73,11 @@ export declare class PpdocsApiClient {
|
|
|
141
73
|
localPath: string;
|
|
142
74
|
fileCount: number;
|
|
143
75
|
}>;
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
meetingStatus(): Promise<unknown>;
|
|
76
|
+
crossListFiles(targetProjectId: string): Promise<FileInfo[]>;
|
|
77
|
+
crossDownload(targetProjectId: string, remotePath: string, localPath?: string): Promise<{
|
|
78
|
+
localPath: string;
|
|
79
|
+
fileCount: number;
|
|
80
|
+
}>;
|
|
150
81
|
}
|
|
151
82
|
export declare function initClient(apiUrl: string): void;
|
|
152
83
|
export declare function getClient(): PpdocsApiClient;
|
|
@@ -146,28 +146,11 @@ export class PpdocsApiClient {
|
|
|
146
146
|
return null;
|
|
147
147
|
}
|
|
148
148
|
}
|
|
149
|
-
async saveReference(reference) {
|
|
150
|
-
await this.request(`/refs/${encodeURIComponent(reference.id)}`, {
|
|
151
|
-
method: 'PUT',
|
|
152
|
-
body: JSON.stringify(reference),
|
|
153
|
-
});
|
|
154
|
-
return true;
|
|
155
|
-
}
|
|
156
149
|
async deleteReference(refId) {
|
|
157
150
|
return this.request(`/refs/${encodeURIComponent(refId)}`, {
|
|
158
151
|
method: 'DELETE',
|
|
159
152
|
});
|
|
160
153
|
}
|
|
161
|
-
async readReferenceFile(path) {
|
|
162
|
-
return this.request(`/refs/files/${cleanPath(path)}`);
|
|
163
|
-
}
|
|
164
|
-
/** 复制本地文件到参考文件库, 返回相对路径列表 */
|
|
165
|
-
async copyReferenceFiles(paths, targetDir = '.') {
|
|
166
|
-
return this.request('/refs/copy-in', {
|
|
167
|
-
method: 'POST',
|
|
168
|
-
body: JSON.stringify({ paths, target_dir: targetDir }),
|
|
169
|
-
});
|
|
170
|
-
}
|
|
171
154
|
/** 从 URL 拉取文档内容并保存为参考 */
|
|
172
155
|
async fetchRefUrl(url, refId) {
|
|
173
156
|
return this.request('/refs/fetch-url', {
|
|
@@ -175,49 +158,6 @@ export class PpdocsApiClient {
|
|
|
175
158
|
body: JSON.stringify({ url, ref_id: refId }),
|
|
176
159
|
});
|
|
177
160
|
}
|
|
178
|
-
/** 导入本地文件内容到参考系统 */
|
|
179
|
-
async importLocalFile(refId, sourcePath) {
|
|
180
|
-
return this.request('/refs/import-file', {
|
|
181
|
-
method: 'POST',
|
|
182
|
-
body: JSON.stringify({ ref_id: refId, source_path: sourcePath }),
|
|
183
|
-
});
|
|
184
|
-
}
|
|
185
|
-
// ============ 踩坑经验 API ============
|
|
186
|
-
async listPitfalls() {
|
|
187
|
-
return this.request('/pitfalls');
|
|
188
|
-
}
|
|
189
|
-
async getPitfall(pitfallId) {
|
|
190
|
-
try {
|
|
191
|
-
return await this.request(`/pitfalls/${encodeURIComponent(pitfallId)}`);
|
|
192
|
-
}
|
|
193
|
-
catch {
|
|
194
|
-
return null;
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
async createPitfall(input) {
|
|
198
|
-
return this.request('/pitfalls', {
|
|
199
|
-
method: 'POST',
|
|
200
|
-
body: JSON.stringify(input),
|
|
201
|
-
});
|
|
202
|
-
}
|
|
203
|
-
async savePitfall(pitfall) {
|
|
204
|
-
await this.request(`/pitfalls/${encodeURIComponent(pitfall.id)}`, {
|
|
205
|
-
method: 'PUT',
|
|
206
|
-
body: JSON.stringify(pitfall),
|
|
207
|
-
});
|
|
208
|
-
return true;
|
|
209
|
-
}
|
|
210
|
-
async deletePitfall(pitfallId) {
|
|
211
|
-
return this.request(`/pitfalls/${encodeURIComponent(pitfallId)}`, {
|
|
212
|
-
method: 'DELETE',
|
|
213
|
-
});
|
|
214
|
-
}
|
|
215
|
-
async searchPitfalls(query, status, category) {
|
|
216
|
-
return this.request('/pitfalls/search', {
|
|
217
|
-
method: 'POST',
|
|
218
|
-
body: JSON.stringify({ query, status, category }),
|
|
219
|
-
});
|
|
220
|
-
}
|
|
221
161
|
// ============ 任务管理 ============
|
|
222
162
|
async listTasks(status) {
|
|
223
163
|
const query = status ? `?status=${status}` : '';
|
|
@@ -330,39 +270,6 @@ export class PpdocsApiClient {
|
|
|
330
270
|
async getFlowchartHealth() {
|
|
331
271
|
return this.request('/flowcharts/health');
|
|
332
272
|
}
|
|
333
|
-
// ============ 跨项目只读访问 ============
|
|
334
|
-
/** 列出所有可访问的项目 */
|
|
335
|
-
async crossListProjects() {
|
|
336
|
-
return this.request('/cross/projects');
|
|
337
|
-
}
|
|
338
|
-
// ============ 项目文件访问 ============
|
|
339
|
-
/** 列出项目文件 */
|
|
340
|
-
async listFiles(dir) {
|
|
341
|
-
const query = dir ? `?dir=${encodeURIComponent(dir)}` : '';
|
|
342
|
-
return this.request(`/files${query}`);
|
|
343
|
-
}
|
|
344
|
-
/** 读取项目文件 */
|
|
345
|
-
async readFile(filePath) {
|
|
346
|
-
return this.request(`/files/${cleanPath(filePath)}`);
|
|
347
|
-
}
|
|
348
|
-
/** 下载项目文件或目录 (zip) → 自动解压到指定目录或 temp 目录 */
|
|
349
|
-
async download(remotePath, localPath) {
|
|
350
|
-
return fetchAndExtractZip(`${this.baseUrl}/files-download/${cleanPath(remotePath)}`, localPath);
|
|
351
|
-
}
|
|
352
|
-
// ============ 跨项目文件访问 (只读) ============
|
|
353
|
-
/** 跨项目: 列出文件 */
|
|
354
|
-
async crossListFiles(target, dir) {
|
|
355
|
-
const query = dir ? `?dir=${encodeURIComponent(dir)}` : '';
|
|
356
|
-
return this.request(`/cross/${encodeURIComponent(target)}/files${query}`);
|
|
357
|
-
}
|
|
358
|
-
/** 跨项目: 读取文件 */
|
|
359
|
-
async crossReadFile(target, filePath) {
|
|
360
|
-
return this.request(`/cross/${encodeURIComponent(target)}/files/${cleanPath(filePath)}`);
|
|
361
|
-
}
|
|
362
|
-
/** 跨项目: 下载文件或目录 */
|
|
363
|
-
async crossDownload(target, remotePath, localPath) {
|
|
364
|
-
return fetchAndExtractZip(`${this.baseUrl}/cross/${encodeURIComponent(target)}/files-download/${cleanPath(remotePath)}`, localPath);
|
|
365
|
-
}
|
|
366
273
|
// ============ 代码分析引擎 ============
|
|
367
274
|
/** 扫描项目代码, 构建/更新索引 */
|
|
368
275
|
async analyzerScan(projectPath, force = false) {
|
|
@@ -400,58 +307,6 @@ export class PpdocsApiClient {
|
|
|
400
307
|
throw new Error(json.error || 'Unknown error');
|
|
401
308
|
return json.data;
|
|
402
309
|
}
|
|
403
|
-
// ============ 讨论系统 ============
|
|
404
|
-
/** 列出活跃讨论 (公开路由) */
|
|
405
|
-
async discussionList() {
|
|
406
|
-
return this.publicRequest('/api/discussions');
|
|
407
|
-
}
|
|
408
|
-
/** 创建讨论 (公开路由) */
|
|
409
|
-
async discussionCreate(title, initiator, participants, content, msgSummary) {
|
|
410
|
-
return this.publicRequest('/api/discussions', {
|
|
411
|
-
method: 'POST',
|
|
412
|
-
body: JSON.stringify({ title, initiator, participants, content, msg_summary: msgSummary }),
|
|
413
|
-
});
|
|
414
|
-
}
|
|
415
|
-
/** 批量读取讨论 (公开路由, 支持已读追踪) */
|
|
416
|
-
async discussionReadByIds(ids, reader, mode) {
|
|
417
|
-
return this.publicRequest('/api/discussions/read', {
|
|
418
|
-
method: 'POST',
|
|
419
|
-
body: JSON.stringify({ ids, reader, mode }),
|
|
420
|
-
});
|
|
421
|
-
}
|
|
422
|
-
/** 列出所有讨论 (含历史, 按参与方筛选) */
|
|
423
|
-
async discussionListAll(participant) {
|
|
424
|
-
return this.publicRequest('/api/discussions/all', {
|
|
425
|
-
method: 'POST',
|
|
426
|
-
body: JSON.stringify({ participant }),
|
|
427
|
-
});
|
|
428
|
-
}
|
|
429
|
-
/** 回复讨论 (公开路由) */
|
|
430
|
-
async discussionReply(id, sender, content, msgSummary, newSummary) {
|
|
431
|
-
return this.publicRequest(`/api/discussions/${encodeURIComponent(id)}/reply`, {
|
|
432
|
-
method: 'POST',
|
|
433
|
-
body: JSON.stringify({ sender, content, msg_summary: msgSummary, new_summary: newSummary }),
|
|
434
|
-
});
|
|
435
|
-
}
|
|
436
|
-
/** 完成讨论 (公开路由, 标记为 completed) */
|
|
437
|
-
async discussionComplete(id) {
|
|
438
|
-
return this.publicRequest(`/api/discussions/${encodeURIComponent(id)}/complete`, {
|
|
439
|
-
method: 'POST',
|
|
440
|
-
});
|
|
441
|
-
}
|
|
442
|
-
/** 归档关闭讨论 (需项目认证) */
|
|
443
|
-
async discussionClose(id, conclusion) {
|
|
444
|
-
return this.request(`/discussions/${encodeURIComponent(id)}/close`, {
|
|
445
|
-
method: 'POST',
|
|
446
|
-
body: JSON.stringify({ conclusion }),
|
|
447
|
-
});
|
|
448
|
-
}
|
|
449
|
-
/** 删除讨论 (公开路由) */
|
|
450
|
-
async discussionDelete(id) {
|
|
451
|
-
return this.publicRequest(`/api/discussions/${encodeURIComponent(id)}`, {
|
|
452
|
-
method: 'DELETE',
|
|
453
|
-
});
|
|
454
|
-
}
|
|
455
310
|
// ============ 公共文件池 ============
|
|
456
311
|
/** 列出公共文件 */
|
|
457
312
|
async publicFilesList(dir) {
|
|
@@ -486,39 +341,12 @@ export class PpdocsApiClient {
|
|
|
486
341
|
async publicFilesDownload(remotePath, localPath) {
|
|
487
342
|
return fetchAndExtractZip(`${this.serverUrl}/api/public-files/download/${cleanPath(remotePath)}`, localPath);
|
|
488
343
|
}
|
|
489
|
-
// ============
|
|
490
|
-
async
|
|
491
|
-
return this.request(
|
|
492
|
-
method: 'POST',
|
|
493
|
-
body: JSON.stringify({ agent_id: agentId, agent_type: agentType }),
|
|
494
|
-
});
|
|
495
|
-
}
|
|
496
|
-
async meetingLeave(agentId) {
|
|
497
|
-
return this.request('/meeting/leave', {
|
|
498
|
-
method: 'POST',
|
|
499
|
-
body: JSON.stringify({ agent_id: agentId }),
|
|
500
|
-
});
|
|
501
|
-
}
|
|
502
|
-
async meetingPost(agentId, content, msgType = 'status') {
|
|
503
|
-
return this.request('/meeting/post', {
|
|
504
|
-
method: 'POST',
|
|
505
|
-
body: JSON.stringify({ agent_id: agentId, content, msg_type: msgType }),
|
|
506
|
-
});
|
|
507
|
-
}
|
|
508
|
-
async meetingClaim(agentId, filePath) {
|
|
509
|
-
return this.request('/meeting/claim', {
|
|
510
|
-
method: 'POST',
|
|
511
|
-
body: JSON.stringify({ agent_id: agentId, file_path: filePath }),
|
|
512
|
-
});
|
|
513
|
-
}
|
|
514
|
-
async meetingRelease(agentId, filePath) {
|
|
515
|
-
return this.request('/meeting/release', {
|
|
516
|
-
method: 'POST',
|
|
517
|
-
body: JSON.stringify({ agent_id: agentId, file_path: filePath }),
|
|
518
|
-
});
|
|
344
|
+
// ============ 跨项目文件 (CLI 模板拉取保留能力) ============
|
|
345
|
+
async crossListFiles(targetProjectId) {
|
|
346
|
+
return this.request(`/cross/${encodeURIComponent(targetProjectId)}/files`);
|
|
519
347
|
}
|
|
520
|
-
async
|
|
521
|
-
return this.
|
|
348
|
+
async crossDownload(targetProjectId, remotePath, localPath) {
|
|
349
|
+
return fetchAndExtractZip(`${this.baseUrl}/cross/${encodeURIComponent(targetProjectId)}/files-download/${cleanPath(remotePath)}`, localPath);
|
|
522
350
|
}
|
|
523
351
|
}
|
|
524
352
|
// ============ 模块级 API ============
|
package/dist/storage/types.d.ts
CHANGED
|
@@ -73,14 +73,6 @@ export interface FileImportRecord {
|
|
|
73
73
|
contentPreview: string;
|
|
74
74
|
fileType: string;
|
|
75
75
|
}
|
|
76
|
-
export interface FileImportResult {
|
|
77
|
-
refId: string;
|
|
78
|
-
sourcePath: string;
|
|
79
|
-
storedFile: string;
|
|
80
|
-
contentLength: number;
|
|
81
|
-
contentPreview: string;
|
|
82
|
-
importCount: number;
|
|
83
|
-
}
|
|
84
76
|
export interface FileInfo {
|
|
85
77
|
name: string;
|
|
86
78
|
path: string;
|
|
@@ -130,30 +122,3 @@ export interface TaskSummary {
|
|
|
130
122
|
updated_at: string;
|
|
131
123
|
last_log?: string;
|
|
132
124
|
}
|
|
133
|
-
export interface Pitfall {
|
|
134
|
-
id: string;
|
|
135
|
-
title: string;
|
|
136
|
-
category: string;
|
|
137
|
-
symptom: string;
|
|
138
|
-
root_cause: string;
|
|
139
|
-
solution: string;
|
|
140
|
-
prevention: string;
|
|
141
|
-
related_nodes: string[];
|
|
142
|
-
source_task?: string;
|
|
143
|
-
severity: string;
|
|
144
|
-
status: string;
|
|
145
|
-
tags: string[];
|
|
146
|
-
created_at: string;
|
|
147
|
-
updated_at: string;
|
|
148
|
-
resolved_at?: string;
|
|
149
|
-
}
|
|
150
|
-
export interface PitfallSummary {
|
|
151
|
-
id: string;
|
|
152
|
-
title: string;
|
|
153
|
-
category: string;
|
|
154
|
-
severity: string;
|
|
155
|
-
status: string;
|
|
156
|
-
tags: string[];
|
|
157
|
-
created_at: string;
|
|
158
|
-
updated_at: string;
|
|
159
|
-
}
|
package/dist/tools/index.d.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* MCP 工具注册入口
|
|
3
|
-
*
|
|
3
|
+
* 保留工具注册入口
|
|
4
4
|
*
|
|
5
|
-
* kg_init — 项目初始化
|
|
6
5
|
* kg_status — 仪表盘
|
|
7
6
|
* kg_flowchart — 知识图谱核心
|
|
8
7
|
* kg_workflow — AI 工作流
|
|
@@ -11,4 +10,4 @@
|
|
|
11
10
|
* code_scan/code_smart_context/code_full_path — 代码分析 (可选)
|
|
12
11
|
*/
|
|
13
12
|
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
14
|
-
export declare function registerTools(server: McpServer, projectId: string, user: string, agentId: string
|
|
13
|
+
export declare function registerTools(server: McpServer, projectId: string, user: string, agentId: string): void;
|
package/dist/tools/index.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* MCP 工具注册入口
|
|
3
|
-
*
|
|
3
|
+
* 保留工具注册入口
|
|
4
4
|
*
|
|
5
|
-
* kg_init — 项目初始化
|
|
6
5
|
* kg_status — 仪表盘
|
|
7
6
|
* kg_flowchart — 知识图谱核心
|
|
8
7
|
* kg_workflow — AI 工作流
|
|
@@ -11,16 +10,14 @@
|
|
|
11
10
|
* code_scan/code_smart_context/code_full_path — 代码分析 (可选)
|
|
12
11
|
*/
|
|
13
12
|
import { createContext } from './shared.js';
|
|
14
|
-
import { registerInitTool } from './init.js';
|
|
15
13
|
import { registerStatusTool } from './kg_status.js';
|
|
16
14
|
import { registerWorkflowTools } from './workflow.js';
|
|
17
15
|
import { registerTaskTools } from './tasks.js';
|
|
18
16
|
import { registerReferenceTools } from './refs.js';
|
|
19
17
|
import { registerAnalyzerTools } from './analyzer.js';
|
|
20
18
|
import { registerFlowchartTools } from './flowchart.js';
|
|
21
|
-
export function registerTools(server, projectId, user, agentId
|
|
19
|
+
export function registerTools(server, projectId, user, agentId) {
|
|
22
20
|
const ctx = createContext(projectId, user, agentId);
|
|
23
|
-
registerInitTool(server, ctx, onProjectChange || (() => { }));
|
|
24
21
|
registerStatusTool(server, ctx);
|
|
25
22
|
registerWorkflowTools(server, ctx);
|
|
26
23
|
registerTaskTools(server, ctx);
|
package/dist/tools/shared.d.ts
CHANGED
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
* MCP 工具共享模块
|
|
3
3
|
* 合并原 helpers.ts + 新增 safeTool / withCross 模式
|
|
4
4
|
*/
|
|
5
|
-
/**
|
|
5
|
+
/** MCP 工具共享上下文 */
|
|
6
6
|
export interface McpContext {
|
|
7
7
|
projectId: string;
|
|
8
8
|
user: string;
|
|
9
9
|
agentId: string;
|
|
10
10
|
}
|
|
11
|
-
/**
|
|
11
|
+
/** 创建共享上下文对象 */
|
|
12
12
|
export declare function createContext(projectId: string, user: string, agentId: string): McpContext;
|
|
13
13
|
export declare function wrap(text: string): {
|
|
14
14
|
content: {
|
package/dist/tools/shared.js
CHANGED
package/package.json
CHANGED
package/templates/AGENT.md
CHANGED
|
@@ -44,13 +44,10 @@
|
|
|
44
44
|
| `kg_flowchart(batch_add, nodes, edges)` | 新增模块时批量创建节点 |
|
|
45
45
|
| `kg_flowchart(create_chart)` | 为复杂模块创建子流程图 |
|
|
46
46
|
|
|
47
|
-
###
|
|
47
|
+
### 外部参考
|
|
48
48
|
| 工具 | 用途 |
|
|
49
49
|
|:---|:---|
|
|
50
|
-
| `
|
|
51
|
-
| `kg_meeting(join/post/status)` | 多 AI 协作会议 |
|
|
52
|
-
| `kg_files(list/read/download)` | 项目文件管理 |
|
|
53
|
-
| `kg_ref(list/get/save)` | 外部参考资料管理 |
|
|
50
|
+
| `kg_ref(list|get|fetch|delete)` | 外部参考资料管理 |
|
|
54
51
|
|
|
55
52
|
## 核心原则
|
|
56
53
|
|
package/templates/README.md
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -27,11 +27,12 @@
|
|
|
27
27
|
|
|
28
28
|
### Phase 0: 环境准备
|
|
29
29
|
```
|
|
30
|
-
kg_init() → 仅在自动连接失效时手动兜底
|
|
31
30
|
kg_status() → 查看仪表盘 (流程图/任务/状态)
|
|
32
31
|
kg_flowchart(action:"list") → 已有流程图列表
|
|
33
32
|
kg_flowchart(action:"get") → 主图结构总览
|
|
34
33
|
```
|
|
34
|
+
若 `kg_status()` 失败,先检查当前项目目录下的 `.ppdocs` 绑定是否存在且可用。
|
|
35
|
+
|
|
35
36
|
如果已有图谱 → 提示用户选择: 增量补充 or 重建
|
|
36
37
|
|
|
37
38
|
### Phase 1: 代码侦察
|
|
File without changes
|
|
@@ -29,11 +29,12 @@
|
|
|
29
29
|
|
|
30
30
|
**1.1 获取图谱现状**
|
|
31
31
|
```
|
|
32
|
-
kg_init() → 仅在自动连接失效时手动兜底
|
|
33
32
|
kg_flowchart(action:"list") → 所有流程图
|
|
34
33
|
kg_flowchart(action:"get") → 主图节点+连线
|
|
35
34
|
kg_flowchart(action:"search", query:"关键词") → 快速定位相关节点
|
|
36
35
|
```
|
|
36
|
+
若工具调用失败,先检查当前项目目录下的 `.ppdocs` 绑定是否存在且可用。
|
|
37
|
+
|
|
37
38
|
|
|
38
39
|
**1.2 获取代码变更**
|
|
39
40
|
```
|
package/templates/cursorrules.md
CHANGED
|
@@ -44,13 +44,10 @@
|
|
|
44
44
|
| `kg_flowchart(batch_add, nodes, edges)` | 新增模块时批量创建节点 |
|
|
45
45
|
| `kg_flowchart(create_chart)` | 为复杂模块创建子流程图 |
|
|
46
46
|
|
|
47
|
-
###
|
|
47
|
+
### 外部参考
|
|
48
48
|
| 工具 | 用途 |
|
|
49
49
|
|:---|:---|
|
|
50
|
-
| `
|
|
51
|
-
| `kg_meeting(join/post/status)` | 多 AI 协作会议 |
|
|
52
|
-
| `kg_files(list/read/download)` | 项目文件管理 |
|
|
53
|
-
| `kg_ref(list/get/save)` | 外部参考资料管理 |
|
|
50
|
+
| `kg_ref(list|get|fetch|delete)` | 外部参考资料管理 |
|
|
54
51
|
|
|
55
52
|
## 核心原则
|
|
56
53
|
|
package/templates/hooks/hook.py
CHANGED
|
File without changes
|
|
@@ -44,13 +44,10 @@
|
|
|
44
44
|
| `kg_flowchart(batch_add, nodes, edges)` | 新增模块时批量创建节点 |
|
|
45
45
|
| `kg_flowchart(create_chart)` | 为复杂模块创建子流程图 |
|
|
46
46
|
|
|
47
|
-
###
|
|
47
|
+
### 外部参考
|
|
48
48
|
| 工具 | 用途 |
|
|
49
49
|
|:---|:---|
|
|
50
|
-
| `
|
|
51
|
-
| `kg_meeting(join/post/status)` | 多 AI 协作会议 |
|
|
52
|
-
| `kg_files(list/read/download)` | 项目文件管理 |
|
|
53
|
-
| `kg_ref(list/get/save)` | 外部参考资料管理 |
|
|
50
|
+
| `kg_ref(list|get|fetch|delete)` | 外部参考资料管理 |
|
|
54
51
|
|
|
55
52
|
## 核心原则
|
|
56
53
|
|
package/dist/tools/init.d.ts
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* kg_init 工具 — 项目上下文初始化
|
|
3
|
-
*
|
|
4
|
-
* 解决全局共用 MCP 进程时的多项目隔离问题:
|
|
5
|
-
* - 智能体调用 kg_init 传入 projectPath
|
|
6
|
-
* - MCP 从该路径读取 .ppdocs 配置
|
|
7
|
-
* - 重新初始化 HTTP Client 指向正确项目
|
|
8
|
-
* - 更新共享 McpContext,所有工具自动获取新 projectId
|
|
9
|
-
*/
|
|
10
|
-
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
11
|
-
import { type McpContext } from './shared.js';
|
|
12
|
-
export declare function registerInitTool(server: McpServer, ctx: McpContext, onProjectChange: (newProjectId: string, newApiUrl: string) => void): void;
|
package/dist/tools/init.js
DELETED
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* kg_init 工具 — 项目上下文初始化
|
|
3
|
-
*
|
|
4
|
-
* 解决全局共用 MCP 进程时的多项目隔离问题:
|
|
5
|
-
* - 智能体调用 kg_init 传入 projectPath
|
|
6
|
-
* - MCP 从该路径读取 .ppdocs 配置
|
|
7
|
-
* - 重新初始化 HTTP Client 指向正确项目
|
|
8
|
-
* - 更新共享 McpContext,所有工具自动获取新 projectId
|
|
9
|
-
*/
|
|
10
|
-
import * as fs from 'fs';
|
|
11
|
-
import * as path from 'path';
|
|
12
|
-
import { z } from 'zod';
|
|
13
|
-
import { initClient } from '../storage/httpClient.js';
|
|
14
|
-
import { generateAgentId } from '../config.js';
|
|
15
|
-
import { wrap, safeTool } from './shared.js';
|
|
16
|
-
// 当前会话的 API URL (用于幂等检查)
|
|
17
|
-
let currentApiUrl = null;
|
|
18
|
-
function findPpdocsPathFrom(dir) {
|
|
19
|
-
let currentDir = path.resolve(dir);
|
|
20
|
-
while (true) {
|
|
21
|
-
const configPath = path.join(currentDir, '.ppdocs');
|
|
22
|
-
if (fs.existsSync(configPath) && fs.statSync(configPath).isFile()) {
|
|
23
|
-
return configPath;
|
|
24
|
-
}
|
|
25
|
-
const parentDir = path.dirname(currentDir);
|
|
26
|
-
if (parentDir === currentDir) {
|
|
27
|
-
return null;
|
|
28
|
-
}
|
|
29
|
-
currentDir = parentDir;
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
/** 尝试从指定目录读取 .ppdocs 配置 */
|
|
33
|
-
function readPpdocsFrom(dir) {
|
|
34
|
-
const configPath = findPpdocsPathFrom(dir);
|
|
35
|
-
if (!configPath)
|
|
36
|
-
return null;
|
|
37
|
-
try {
|
|
38
|
-
const c = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
39
|
-
if (!c.api || !c.projectId || !c.key)
|
|
40
|
-
return null;
|
|
41
|
-
return {
|
|
42
|
-
apiUrl: `${c.api}/api/${c.projectId}/${c.key}`,
|
|
43
|
-
projectId: c.projectId,
|
|
44
|
-
user: c.user || 'mcp-agent',
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
catch {
|
|
48
|
-
return null;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
export function registerInitTool(server, ctx, onProjectChange) {
|
|
52
|
-
server.tool('kg_init', '项目上下文初始化。读取指定目录的 .ppdocs 配置,切换 MCP 连接到对应项目。首次对话必须调用。不传 projectPath 则使用当前工作目录', {
|
|
53
|
-
projectPath: z.string().optional().describe('项目根目录的绝对路径(含 .ppdocs 文件),不传则使用 cwd'),
|
|
54
|
-
}, async ({ projectPath }) => safeTool(async () => {
|
|
55
|
-
const targetDir = projectPath || process.cwd();
|
|
56
|
-
// 1. 尝试从目标目录读取 .ppdocs
|
|
57
|
-
const config = readPpdocsFrom(targetDir);
|
|
58
|
-
if (!config) {
|
|
59
|
-
return wrap(`❌ 未在 ${targetDir} 找到 .ppdocs 配置文件\n\n` +
|
|
60
|
-
`请先在项目目录中运行:\n` +
|
|
61
|
-
` npx @ppdocs/mcp init -p <projectId> -k <key>\n\n` +
|
|
62
|
-
`或通过 WebUI 绑定项目:\n` +
|
|
63
|
-
` npx @ppdocs/mcp webui`);
|
|
64
|
-
}
|
|
65
|
-
// 2. 幂等检查
|
|
66
|
-
if (currentApiUrl === config.apiUrl) {
|
|
67
|
-
return wrap(`✅ 项目上下文已就绪 (无需切换)\n\n` +
|
|
68
|
-
`📂 项目: ${config.projectId}\n` +
|
|
69
|
-
`🔗 API: ${config.apiUrl.replace(/\/[^/]+$/, '/***')}\n` +
|
|
70
|
-
`👤 用户: ${config.user}`);
|
|
71
|
-
}
|
|
72
|
-
// 3. 重新初始化 HTTP Client
|
|
73
|
-
initClient(config.apiUrl);
|
|
74
|
-
currentApiUrl = config.apiUrl;
|
|
75
|
-
// 4. 更新共享上下文 — 所有工具立刻获得新 projectId
|
|
76
|
-
ctx.projectId = config.projectId;
|
|
77
|
-
ctx.user = config.user;
|
|
78
|
-
// agentId: 优先环境变量 > 保留当前值(进程启动时已生成)
|
|
79
|
-
ctx.agentId = process.env.PPDOCS_AGENT_ID || ctx.agentId || generateAgentId();
|
|
80
|
-
// 5. 通知主进程
|
|
81
|
-
onProjectChange(config.projectId, config.apiUrl);
|
|
82
|
-
return wrap(`✅ 项目上下文已初始化\n\n` +
|
|
83
|
-
`📂 项目: ${config.projectId}\n` +
|
|
84
|
-
`📁 路径: ${targetDir}\n` +
|
|
85
|
-
`🔗 API: ${config.apiUrl.replace(/\/[^/]+$/, '/***')}\n` +
|
|
86
|
-
`👤 用户: ${config.user}\n\n` +
|
|
87
|
-
`所有后续工具调用将自动连接到此项目。`);
|
|
88
|
-
}));
|
|
89
|
-
}
|