@ppdocs/mcp 3.12.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.
Files changed (51) hide show
  1. package/README.md +18 -15
  2. package/dist/cli.js +56 -41
  3. package/dist/config.d.ts +23 -3
  4. package/dist/config.js +94 -36
  5. package/dist/index.d.ts +1 -1
  6. package/dist/index.js +45 -17
  7. package/dist/retainedCoreMatrix.d.ts +19 -0
  8. package/dist/retainedCoreMatrix.js +40 -0
  9. package/dist/storage/httpClient.d.ts +6 -75
  10. package/dist/storage/httpClient.js +5 -177
  11. package/dist/storage/types.d.ts +0 -35
  12. package/dist/tools/analyzer.d.ts +1 -4
  13. package/dist/tools/analyzer.js +4 -7
  14. package/dist/tools/flowchart.js +1 -4
  15. package/dist/tools/index.d.ts +8 -10
  16. package/dist/tools/index.js +8 -32
  17. package/dist/tools/kg_status.d.ts +1 -1
  18. package/dist/tools/kg_status.js +4 -6
  19. package/dist/tools/refs.js +38 -172
  20. package/dist/tools/shared.d.ts +2 -2
  21. package/dist/tools/shared.js +1 -1
  22. package/dist/tools/tasks.d.ts +1 -2
  23. package/dist/tools/tasks.js +32 -47
  24. package/dist/tools/workflow.js +2 -3
  25. package/package.json +1 -1
  26. package/templates/AGENT.md +2 -5
  27. package/templates/README.md +0 -0
  28. package/templates/commands/init.md +0 -0
  29. package/templates/commands/pp/Zero_Defec_Genesis.md +0 -0
  30. package/templates/commands/pp/diagnose.md +0 -0
  31. package/templates/commands/pp/init.md +2 -1
  32. package/templates/commands/pp/review.md +0 -0
  33. package/templates/commands/pp/sync.md +2 -1
  34. package/templates/cursorrules.md +2 -5
  35. package/templates/hooks/SystemPrompt.md +1 -1
  36. package/templates/hooks/hook.py +0 -0
  37. package/templates/kiro-rules/ppdocs.md +2 -5
  38. package/dist/tools/discussion.d.ts +0 -15
  39. package/dist/tools/discussion.js +0 -264
  40. package/dist/tools/doc_query.d.ts +0 -10
  41. package/dist/tools/doc_query.js +0 -185
  42. package/dist/tools/files.d.ts +0 -6
  43. package/dist/tools/files.js +0 -107
  44. package/dist/tools/init.d.ts +0 -12
  45. package/dist/tools/init.js +0 -89
  46. package/dist/tools/meeting.d.ts +0 -7
  47. package/dist/tools/meeting.js +0 -97
  48. package/dist/tools/pitfalls.d.ts +0 -6
  49. package/dist/tools/pitfalls.js +0 -190
  50. package/dist/tools/projects.d.ts +0 -7
  51. package/dist/tools/projects.js +0 -19
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,16 +109,13 @@ npx @ppdocs/mcp init -p <projectId> -k <key> --codex
109
109
 
110
110
  ### 核心工具
111
111
 
112
- | 工具 | 说明 |
113
- |------|------|
114
- | `kg_init` | 初始化项目连接与上下文 |
115
- | `kg_status` | 查看项目仪表盘与流程图概况 |
116
- | `kg_flowchart` | 统一的知识图谱入口,负责查询、更新、关系分析 |
117
- | `kg_workflow` | Markdown 文档工作流管理 |
112
+ | 工具 | 说明 |
113
+ |------|------|
114
+ | `kg_status` | 查看项目仪表盘与流程图概况 |
115
+ | `kg_flowchart` | 统一的知识图谱入口,负责查询、更新、关系分析 |
116
+ | `kg_workflow` | Markdown 文档工作流管理 |
118
117
  | `kg_task` | 任务管理 |
119
- | `kg_files` | 项目文件读写/上传下载 |
120
- | `kg_discuss` | 讨论区 |
121
- | `kg_meeting` | 多 AI 协作会议 |
118
+ | `kg_ref` | 外部参考资料(URL拉取、查看、删除) |
122
119
  | `code_scan` | 代码扫描 |
123
120
  | `code_smart_context` | 获取符号的智能上下文 |
124
121
  | `code_full_path` | 获取两个符号之间的全路径 |
@@ -207,6 +204,12 @@ kg_flowchart(action:"update_node", chartId:"main", nodeId:"n_storage",
207
204
 
208
205
  ## 更新日志
209
206
 
207
+ ### v3.3.0
208
+ - 移除 6 个低使用率工具: `kg_doc`, `kg_discuss`, `kg_meeting`, `kg_files`, `kg_projects`, `kg_pitfall`
209
+ - `kg_ref` 精简为 4 个 action: `list|get|fetch|delete`
210
+ - 所有工具描述精简,减少 token 开销
211
+ - 移除前端孤立组件: FloatingDiscussion, PitfallRecord, ImpactGraph
212
+
210
213
  ### v3.2.36
211
214
  - 统一为 `flowchart-first` MCP 接口模型
212
215
  - `kg_flowchart` 新增 `search` / `get_relations` / `find_path`
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: 20099, api: 'localhost', codex: false };
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: 20099)
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
- const envPairs = [
263
- `PPDOCS_API_URL=${apiUrl}`,
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 macExtraPaths = '/usr/local/bin:/opt/homebrew/bin:/home/linuxbrew/.linuxbrew/bin';
277
- const fullPath = `${process.env.PATH || '/usr/bin:/bin'}:${macExtraPaths}`;
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
- execSilent(`claude mcp remove ${serverName}`);
310
+ removePpdocsServerAliases('claude');
290
311
  mcpAdd('claude', '-e');
291
312
  }
292
313
  catch {
293
314
  try {
294
- execSilent(`claude mcp remove ${serverName}`);
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
- execSilent(`codex mcp remove ${serverName}`);
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
- execSilent(`opencode mcp remove ${serverName}`);
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 配置,注入 PPDOCS_* 环境变量实现启动即就绪 */
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 macExtraPaths = '/usr/local/bin:/opt/homebrew/bin:/home/linuxbrew/.linuxbrew/bin';
363
- const actualPath = `${process.env.PATH || '/usr/bin:/bin'}:${macExtraPaths}`;
364
- // 构建 env: 非 Windows 补 PATH + 注入项目绑定变量
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.PPDOCS_API_URL = apiUrl;
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
- ...(mcpConfig.mcpServers || {}),
380
- "ppdocs-kg": ppdocsServer,
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
- const command = `python ${hookPath}`;
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 => `mcp__ppdocs-kg__${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
- * 读取配置: 环境变量 > .ppdocs 文件 > 提示去 Agent WebUI 绑定
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: 'env' | 'file';
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 loadConfig(): Promise<PpdocsConfig | null>;
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
- * 读取配置: 环境变量 > .ppdocs 文件 > 提示去 Agent WebUI 绑定
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 readEnvConfig() {
29
- const apiUrl = process.env.PPDOCS_API_URL;
30
- if (!apiUrl)
31
- return null;
32
- const match = apiUrl.match(/\/api\/([^/]+)\/[^/]+\/?$/);
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
- apiUrl,
35
- projectId: match?.[1] || 'unknown',
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 readPpdocsFile() {
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 c = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
64
- if (!c.api || !c.projectId || !c.key)
65
- return null;
66
- return { apiUrl: `${c.api}/api/${c.projectId}/${c.key}`, projectId: c.projectId, user: c.user || generateUser(), agentId: c.agentId || process.env.PPDOCS_AGENT_ID || generateAgentId(), source: 'file' };
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
- return null;
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
- // 1. 环境变量
75
- const envConfig = readEnvConfig();
76
- if (envConfig)
77
- return envConfig;
78
- // 2. .ppdocs 文件 (由 init 命令生成)
79
- const fileConfig = readPpdocsFile();
80
- if (fileConfig)
81
- return fileConfig;
82
- // 无配置 → 返回 null,由调用方决定行为 (MCP 模式下等待 kg_init)
83
- return null;
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
@@ -4,6 +4,6 @@
4
4
  *
5
5
  * 使用方式:
6
6
  * 1. CLI 初始化: npx @ppdocs/mcp init -p <projectId> -k <key>
7
- * 2. MCP 服务: 自动读取 .ppdocs 配置或 PPDOCS_API_URL 环境变量
7
+ * 2. MCP 服务: 启动时自动读取项目根目录 .ppdocs
8
8
  */
9
9
  export {};
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 服务: 自动读取 .ppdocs 配置或 PPDOCS_API_URL 环境变量
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, generateAgentId } from './config.js';
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
- const config = await loadConfig();
77
- // 有配置 → 自动初始化 (环境变量 / .ppdocs)
78
- if (config) {
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
- else {
85
- console.error(`ppdocs MCP v${VERSION} | ⏳ 等待 kg_init 初始化项目上下文...`);
106
+ catch (error) {
107
+ if (error instanceof PpdocsBindingError) {
108
+ logBindingFailure(error);
109
+ process.exit(1);
110
+ }
111
+ throw error;
86
112
  }
87
- const projectId = config?.projectId || 'pending';
88
- const user = config?.user || 'agent';
89
- const agentId = config?.agentId || process.env.PPDOCS_AGENT_ID || generateAgentId();
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, (newProjectId, newApiUrl) => {
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
+ }