@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 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
- | `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
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: 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
+ }
@@ -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, FileImportResult, Pitfall, PitfallSummary } from './types.js';
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
- meetingJoin(agentId: string, agentType: string): Promise<unknown>;
145
- meetingLeave(agentId: string): Promise<unknown>;
146
- meetingPost(agentId: string, content: string, msgType?: string): Promise<unknown>;
147
- meetingClaim(agentId: string, filePath: string): Promise<unknown>;
148
- meetingRelease(agentId: string, filePath: string): Promise<unknown>;
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
- // ============ 多AI协作会议 ============
490
- async meetingJoin(agentId, agentType) {
491
- return this.request('/meeting/join', {
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 meetingStatus() {
521
- return this.request('/meeting/status');
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 ============
@@ -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
- }
@@ -1,8 +1,7 @@
1
1
  /**
2
2
  * MCP 工具注册入口
3
- * 7 个工具, 6 个子模块
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, onProjectChange?: (newProjectId: string, newApiUrl: string) => void): void;
13
+ export declare function registerTools(server: McpServer, projectId: string, user: string, agentId: string): void;
@@ -1,8 +1,7 @@
1
1
  /**
2
2
  * MCP 工具注册入口
3
- * 7 个工具, 6 个子模块
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, onProjectChange) {
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);
@@ -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
- /** 创建可变上下文对象 (工具闭包捕获此对象的引用,kg_init 更新其属性) */
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: {
@@ -2,7 +2,7 @@
2
2
  * MCP 工具共享模块
3
3
  * 合并原 helpers.ts + 新增 safeTool / withCross 模式
4
4
  */
5
- /** 创建可变上下文对象 (工具闭包捕获此对象的引用,kg_init 更新其属性) */
5
+ /** 创建共享上下文对象 */
6
6
  export function createContext(projectId, user, agentId) {
7
7
  return { projectId, user, agentId };
8
8
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ppdocs/mcp",
3
- "version": "3.13.0",
3
+ "version": "3.13.1",
4
4
  "description": "ppdocs MCP Server - Knowledge Graph for Claude",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -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
- | `kg_discuss(create/reply/list)` | AI 实例讨论 |
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
 
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
  ```
@@ -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
- | `kg_discuss(create/reply/list)` | AI 实例讨论 |
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
 
@@ -1,7 +1,7 @@
1
1
  你是严谨的软件架构师,围绕**用户知识图谱软件**工作,确保每个变动有据可查,每次成功沉淀为知识节点。
2
2
 
3
3
  ## 会话启动检查
4
- 每次会话开始时, 执行 `kg_discuss({ action: "list" })` 检查是否有涉及本项目的待回复讨论。如有, 主动提醒用户: "发现 N 条待处理讨论, 是否需要查看?"
4
+ 每次会话开始时, 执行 `kg_status()` 获取当前项目总览;如需流程指导,再执行 `kg_workflow()` 查看可用工作流。
5
5
 
6
6
  ## 核心宪法
7
7
  | 原则 | 要求 |
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
- | `kg_discuss(create/reply/list)` | AI 实例讨论 |
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
 
@@ -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;
@@ -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
- }