@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.
- package/README.md +18 -15
- 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/analyzer.d.ts +1 -4
- package/dist/tools/analyzer.js +4 -7
- package/dist/tools/flowchart.js +1 -4
- package/dist/tools/index.d.ts +8 -10
- package/dist/tools/index.js +8 -32
- package/dist/tools/kg_status.d.ts +1 -1
- package/dist/tools/kg_status.js +4 -6
- package/dist/tools/refs.js +38 -172
- package/dist/tools/shared.d.ts +2 -2
- package/dist/tools/shared.js +1 -1
- package/dist/tools/tasks.d.ts +1 -2
- package/dist/tools/tasks.js +32 -47
- package/dist/tools/workflow.js +2 -3
- 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/discussion.d.ts +0 -15
- package/dist/tools/discussion.js +0 -264
- package/dist/tools/doc_query.d.ts +0 -10
- package/dist/tools/doc_query.js +0 -185
- package/dist/tools/files.d.ts +0 -6
- package/dist/tools/files.js +0 -107
- package/dist/tools/init.d.ts +0 -12
- package/dist/tools/init.js +0 -89
- package/dist/tools/meeting.d.ts +0 -7
- package/dist/tools/meeting.js +0 -97
- package/dist/tools/pitfalls.d.ts +0 -6
- package/dist/tools/pitfalls.js +0 -190
- package/dist/tools/projects.d.ts +0 -7
- 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
|
-
| `
|
|
115
|
-
| `
|
|
116
|
-
| `
|
|
117
|
-
| `kg_workflow` | Markdown 文档工作流管理 |
|
|
112
|
+
| 工具 | 说明 |
|
|
113
|
+
|------|------|
|
|
114
|
+
| `kg_status` | 查看项目仪表盘与流程图概况 |
|
|
115
|
+
| `kg_flowchart` | 统一的知识图谱入口,负责查询、更新、关系分析 |
|
|
116
|
+
| `kg_workflow` | Markdown 文档工作流管理 |
|
|
118
117
|
| `kg_task` | 任务管理 |
|
|
119
|
-
| `
|
|
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:
|
|
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
|
+
}
|