@ppdocs/mcp 3.1.9 → 3.1.10

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/dist/cli.d.ts CHANGED
@@ -3,3 +3,5 @@
3
3
  * 命令: init - 初始化项目配置 + 安装工作流模板 + 自动注册 MCP
4
4
  */
5
5
  export declare function runCli(args: string[]): boolean;
6
+ /** 安装项目模板文件 (供授权后自动配置调用) */
7
+ export declare function setupProjectFiles(cwd: string, apiUrl: string): void;
package/dist/cli.js CHANGED
@@ -181,8 +181,8 @@ async function initProject(opts) {
181
181
  installClaudeTemplates(cwd); // Default to Claude templates
182
182
  }
183
183
  }
184
- // 自动检测并注册 MCP
185
- const registered = autoRegisterMcp(apiUrl, opts.user);
184
+ // 自动检测并注册 MCP (如果已写入 Antigravity 配置,跳过 gemini CLI 注册避免冲突)
185
+ const registered = autoRegisterMcp(apiUrl, opts.user, detectedIdes.includes('antigravity'));
186
186
  // 如果没有检测到任何 AI CLI,并且也没有检测到配置文件夹,创建 .mcp.json 作为备用
187
187
  if (!registered && !hasIdeDir) {
188
188
  createMcpJson(cwd, apiUrl);
@@ -280,7 +280,7 @@ function execSilent(cmd) {
280
280
  catch { /* ignore */ }
281
281
  }
282
282
  /** 自动检测 AI CLI 并注册 MCP */
283
- function autoRegisterMcp(apiUrl, user) {
283
+ function autoRegisterMcp(apiUrl, user, skipGemini = false) {
284
284
  const detected = [];
285
285
  const serverName = 'ppdocs-kg';
286
286
  // 检测 Claude CLI (不传环境变量,MCP启动时读取.ppdocs)
@@ -324,7 +324,7 @@ function autoRegisterMcp(apiUrl, user) {
324
324
  }
325
325
  }
326
326
  // 检测 Gemini CLI
327
- if (commandExists('gemini')) {
327
+ if (commandExists('gemini') && !skipGemini) {
328
328
  detected.push('Gemini');
329
329
  try {
330
330
  console.log(`✅ Detected Gemini CLI, registering MCP...`);
@@ -344,33 +344,39 @@ function autoRegisterMcp(apiUrl, user) {
344
344
  return true;
345
345
  }
346
346
  /** 在指定路径创建或更新 mcp.json 配置 */
347
- function createMcpConfigAt(mcpPath, apiUrl) {
347
+ function createMcpConfigAt(mcpPath, apiUrl, options) {
348
348
  let mcpConfig = {};
349
349
  if (fs.existsSync(mcpPath)) {
350
350
  try {
351
351
  mcpConfig = JSON.parse(fs.readFileSync(mcpPath, 'utf-8'));
352
352
  }
353
353
  catch {
354
- // ignore parse error
354
+ // 解析失败时中止,避免清空已有配置
355
+ console.log(`⚠️ ${mcpPath} JSON格式无效,跳过MCP配置写入(请手动修复后重试)`);
356
+ return;
355
357
  }
356
358
  }
357
359
  // Windows 需要 cmd /c 包装才能执行 npx
358
360
  const isWindows = process.platform === 'win32';
359
361
  // 对于 Mac/Linux,IDE 可能没有用户的完整 PATH,导致找不到 npx
362
+ // 使用实际 PATH 值拼接,JSON 中的 $PATH 不会被展开
363
+ const macExtraPaths = '/usr/local/bin:/opt/homebrew/bin:/home/linuxbrew/.linuxbrew/bin';
364
+ const actualPath = `${process.env.PATH || '/usr/bin:/bin'}:${macExtraPaths}`;
360
365
  const ppdocsServer = isWindows
361
366
  ? {
362
367
  command: 'cmd',
363
368
  args: ['/c', 'npx', '-y', '@ppdocs/mcp@latest'],
364
- env: { "PPDOCS_API_URL": apiUrl }
369
+ ...(options?.noEnv ? {} : { env: { "PPDOCS_API_URL": apiUrl } })
365
370
  }
366
371
  : {
367
372
  command: 'npx',
368
373
  args: ['-y', '@ppdocs/mcp@latest'],
369
- env: {
370
- "PPDOCS_API_URL": apiUrl,
371
- // 强行注入常见路径,防止找不到 node/npx
372
- "PATH": "$PATH:/usr/local/bin:/opt/homebrew/bin:/home/linuxbrew/.linuxbrew/bin:$HOME/.nvm/versions/node/current/bin:$HOME/.local/share/fnm/aliases/default/bin"
373
- }
374
+ ...(options?.noEnv ? { env: { "PATH": actualPath } } : {
375
+ env: {
376
+ "PPDOCS_API_URL": apiUrl,
377
+ "PATH": actualPath
378
+ }
379
+ })
374
380
  };
375
381
  mcpConfig.mcpServers = {
376
382
  ...(mcpConfig.mcpServers || {}),
@@ -522,8 +528,8 @@ function installCursorTemplates(cwd, apiUrl) {
522
528
  }
523
529
  /** 安装 Antigravity (Gemini IDE) 模板 */
524
530
  function installAntigravityTemplates(cwd, apiUrl) {
525
- // 1. 填充 .gemini/settings.json
526
- createMcpConfigAt(path.join(cwd, '.gemini', 'settings.json'), apiUrl);
531
+ // 1. 填充 .gemini/settings.json (noEnv: 依赖 .ppdocs 文件,不传 PPDOCS_API_URL 避免覆盖)
532
+ createMcpConfigAt(path.join(cwd, '.gemini', 'settings.json'), apiUrl, { noEnv: true });
527
533
  // 2. 生成 AGENTS.md
528
534
  generateAgentsMd(cwd);
529
535
  // 3. 安装斜杠命令 → .agents/workflows/ (Antigravity 的 slash command 机制)
@@ -587,3 +593,23 @@ function detectIDEs(cwd) {
587
593
  ides.push('kiro');
588
594
  return ides;
589
595
  }
596
+ /** 安装项目模板文件 (供授权后自动配置调用) */
597
+ export function setupProjectFiles(cwd, apiUrl) {
598
+ const detectedIdes = detectIDEs(cwd);
599
+ if (detectedIdes.includes('antigravity')) {
600
+ installAntigravityTemplates(cwd, apiUrl);
601
+ }
602
+ if (detectedIdes.includes('cursor')) {
603
+ installCursorTemplates(cwd, apiUrl);
604
+ }
605
+ if (detectedIdes.includes('kiro')) {
606
+ installKiroTemplates(cwd, apiUrl);
607
+ }
608
+ if (detectedIdes.includes('claude')) {
609
+ installClaudeTemplates(cwd);
610
+ }
611
+ // 如果没检测到任何 IDE,默认安装 Claude 模板
612
+ if (detectedIdes.length === 0) {
613
+ installClaudeTemplates(cwd);
614
+ }
615
+ }
package/dist/config.d.ts CHANGED
@@ -1,16 +1,22 @@
1
1
  /**
2
2
  * ppdocs MCP Config
3
- * 读取配置: 环境变量 > .ppdocs 文件
3
+ * 读取配置: 环境变量 > .ppdocs 文件 > 自动发现 > 授权请求
4
4
  */
5
5
  export interface PpdocsConfig {
6
6
  apiUrl: string;
7
7
  projectId: string;
8
8
  user: string;
9
+ source: 'env' | 'file' | 'discover' | 'auth';
10
+ /** 原始连接参数 (discover/auth 填充, 供持久化使用) */
11
+ connection?: {
12
+ host: string;
13
+ port: number;
14
+ password: string;
15
+ };
9
16
  }
10
17
  export declare const PPDOCS_CONFIG_FILE = ".ppdocs";
11
- /** 生成随机用户名 (8位字母数字) */
18
+ /** 生成随机用户名 */
12
19
  export declare function generateUser(): string;
13
- /**
14
- * 加载配置 (优先级: 环境变量 > .ppdocs 文件)
15
- */
16
- export declare function loadConfig(): PpdocsConfig;
20
+ /** 写入 .ppdocs 文件 (需 config.connection 存在) */
21
+ export declare function writePpdocsFile(config: PpdocsConfig): void;
22
+ export declare function loadConfig(): Promise<PpdocsConfig>;
package/dist/config.js CHANGED
@@ -1,72 +1,172 @@
1
1
  /**
2
2
  * ppdocs MCP Config
3
- * 读取配置: 环境变量 > .ppdocs 文件
3
+ * 读取配置: 环境变量 > .ppdocs 文件 > 自动发现 > 授权请求
4
4
  */
5
5
  import * as fs from 'fs';
6
6
  import * as path from 'path';
7
+ import * as os from 'os';
7
8
  export const PPDOCS_CONFIG_FILE = '.ppdocs';
8
- /** 生成随机用户名 (8位字母数字) */
9
+ /** 生成随机用户名 */
9
10
  export function generateUser() {
10
11
  const chars = 'abcdefghjkmnpqrstuvwxyz23456789';
11
12
  return Array.from({ length: 8 }, () => chars[Math.floor(Math.random() * chars.length)]).join('');
12
13
  }
13
- /**
14
- * 从 .ppdocs 文件读取配置
15
- */
14
+ // ============ 静态配置读取 ============
15
+ function readEnvConfig() {
16
+ const apiUrl = process.env.PPDOCS_API_URL;
17
+ if (!apiUrl)
18
+ return null;
19
+ const match = apiUrl.match(/\/api\/([^/]+)\/[^/]+\/?$/);
20
+ return { apiUrl, projectId: match?.[1] || 'unknown', user: process.env.PPDOCS_USER || generateUser(), source: 'env' };
21
+ }
16
22
  function readPpdocsFile() {
17
- const cwd = process.cwd();
18
- const configPath = path.join(cwd, '.ppdocs');
19
- if (!fs.existsSync(configPath)) {
23
+ const configPath = path.join(process.cwd(), '.ppdocs');
24
+ if (!fs.existsSync(configPath))
20
25
  return null;
21
- }
22
26
  try {
23
- const content = fs.readFileSync(configPath, 'utf-8');
24
- const config = JSON.parse(content);
25
- if (!config.api || !config.projectId || !config.key) {
27
+ const c = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
28
+ if (!c.api || !c.projectId || !c.key)
26
29
  return null;
30
+ return { apiUrl: `${c.api}/api/${c.projectId}/${c.key}`, projectId: c.projectId, user: c.user || generateUser(), source: 'file' };
31
+ }
32
+ catch {
33
+ return null;
34
+ }
35
+ }
36
+ async function findLocalServer() {
37
+ for (const host of ['localhost', '127.0.0.1', '10.0.0.176']) {
38
+ for (const port of [20001]) {
39
+ try {
40
+ const res = await fetch(`http://${host}:${port}/health`, { signal: AbortSignal.timeout(2000) });
41
+ if (res.ok)
42
+ return { host, port, base: `http://${host}:${port}` };
43
+ }
44
+ catch {
45
+ continue;
46
+ }
27
47
  }
48
+ }
49
+ return null;
50
+ }
51
+ // ============ 自动发现 ============
52
+ async function autoDiscoverConfig(server) {
53
+ const cwd = process.cwd().replace(/\\/g, '/').toLowerCase();
54
+ try {
55
+ const res = await fetch(`${server.base}/api/projects`, { signal: AbortSignal.timeout(2000) });
56
+ if (!res.ok)
57
+ return null;
58
+ const { data: projects } = await res.json();
59
+ if (!Array.isArray(projects))
60
+ return null;
61
+ const match = projects.find((p) => p.projectPath && cwd.startsWith(p.projectPath.replace(/\\/g, '/').toLowerCase()));
62
+ if (!match)
63
+ return null;
64
+ const metaRes = await fetch(`${server.base}/api/projects/${match.id}/meta`, { signal: AbortSignal.timeout(2000) });
65
+ if (!metaRes.ok)
66
+ return null;
67
+ const { data: meta } = await metaRes.json();
68
+ if (!meta?.password)
69
+ return null;
70
+ console.error(`[AutoDiscovery] 匹配项目: ${match.name} (${match.id})`);
28
71
  return {
29
- apiUrl: `${config.api}/api/${config.projectId}/${config.key}`,
30
- projectId: config.projectId,
31
- user: config.user || generateUser(),
72
+ apiUrl: `${server.base}/api/${match.id}/${meta.password}`,
73
+ projectId: match.id,
74
+ user: `auto-${generateUser().slice(0, 4)}`,
75
+ source: 'discover',
76
+ connection: { host: server.host, port: server.port, password: meta.password },
32
77
  };
33
78
  }
34
79
  catch {
35
80
  return null;
36
81
  }
37
82
  }
38
- /**
39
- * 从环境变量读取配置
40
- */
41
- function readEnvConfig() {
42
- const apiUrl = process.env.PPDOCS_API_URL;
43
- if (!apiUrl)
83
+ // ============ 授权请求 ============
84
+ async function requestAuthConfig(server) {
85
+ try {
86
+ const reqRes = await fetch(`${server.base}/api/auth/request`, {
87
+ method: 'POST',
88
+ headers: { 'Content-Type': 'application/json' },
89
+ body: JSON.stringify({ cwd: process.cwd(), hostname: os.hostname() }),
90
+ });
91
+ if (!reqRes.ok)
92
+ return null;
93
+ const { data } = await reqRes.json();
94
+ if (!data?.requestId)
95
+ return null;
96
+ console.error(`[Auth] 等待桌面端授权... (请在 ppdocs 桌面端确认)`);
97
+ for (let i = 0; i < 150; i++) {
98
+ await new Promise(r => setTimeout(r, 2000));
99
+ const pollRes = await fetch(`${server.base}/api/auth/poll/${data.requestId}`, { signal: AbortSignal.timeout(3000) });
100
+ if (!pollRes.ok)
101
+ continue;
102
+ const { data: poll } = await pollRes.json();
103
+ if (poll?.status === 'approved' && poll.result) {
104
+ const r = poll.result;
105
+ console.error(`[Auth] 已授权: ${r.project_name} (${r.project_id})`);
106
+ return {
107
+ apiUrl: `http://${r.api_host}:${r.api_port}/api/${r.project_id}/${r.password}`,
108
+ projectId: r.project_id,
109
+ user: `auto-${generateUser().slice(0, 4)}`,
110
+ source: 'auth',
111
+ connection: { host: r.api_host, port: r.api_port, password: r.password },
112
+ };
113
+ }
114
+ if (poll?.status === 'rejected') {
115
+ console.error('[Auth] 授权被拒绝');
116
+ return null;
117
+ }
118
+ if (poll?.status === 'expired') {
119
+ console.error('[Auth] 授权超时');
120
+ return null;
121
+ }
122
+ }
123
+ console.error('[Auth] 轮询超时');
44
124
  return null;
45
- // URL 格式: http://localhost:20001/api/{projectId}/{password}
46
- const match = apiUrl.match(/\/api\/([^/]+)\/[^/]+\/?$/);
47
- const projectId = match?.[1] || 'unknown';
48
- const user = process.env.PPDOCS_USER || generateUser();
49
- return { apiUrl, projectId, user };
125
+ }
126
+ catch {
127
+ return null;
128
+ }
50
129
  }
51
- /**
52
- * 加载配置 (优先级: 环境变量 > .ppdocs 文件)
53
- */
54
- export function loadConfig() {
55
- // 1. 尝试环境变量
130
+ // ============ 持久化 ============
131
+ /** 写入 .ppdocs 文件 ( config.connection 存在) */
132
+ export function writePpdocsFile(config) {
133
+ if (!config.connection)
134
+ return;
135
+ const configPath = path.join(process.cwd(), '.ppdocs');
136
+ if (fs.existsSync(configPath))
137
+ return;
138
+ const { host, port, password } = config.connection;
139
+ fs.writeFileSync(configPath, JSON.stringify({
140
+ api: `http://${host}:${port}`,
141
+ projectId: config.projectId,
142
+ key: password,
143
+ user: config.user,
144
+ }, null, 2), 'utf-8');
145
+ console.error(`[Config] 已保存 .ppdocs`);
146
+ }
147
+ // ============ 入口 ============
148
+ export async function loadConfig() {
149
+ // 1. 环境变量
56
150
  const envConfig = readEnvConfig();
57
151
  if (envConfig)
58
152
  return envConfig;
59
- // 2. 尝试 .ppdocs 文件
153
+ // 2. .ppdocs 文件
60
154
  const fileConfig = readPpdocsFile();
61
155
  if (fileConfig)
62
156
  return fileConfig;
63
- // 3. 报错
157
+ // 3-4. 需要服务器 → 只扫一次
158
+ const server = await findLocalServer();
159
+ if (server) {
160
+ const discoverConfig = await autoDiscoverConfig(server);
161
+ if (discoverConfig)
162
+ return discoverConfig;
163
+ const authConfig = await requestAuthConfig(server);
164
+ if (authConfig)
165
+ return authConfig;
166
+ }
167
+ // 5. 全部失败
64
168
  console.error('ERROR: ppdocs config not found');
65
- console.error('');
66
- console.error('Option 1: Run init command in your project directory:');
67
- console.error(' npx @ppdocs/mcp init -p <projectId> -k <key>');
68
- console.error('');
69
- console.error('Option 2: Set environment variable:');
70
- console.error(' PPDOCS_API_URL=http://localhost:20001/api/{projectId}/{key}');
169
+ console.error(' Option 1: npx @ppdocs/mcp init -p <projectId> -k <key>');
170
+ console.error(' Option 2: Ensure ppdocs desktop app is running');
71
171
  process.exit(1);
72
172
  }
package/dist/index.js CHANGED
@@ -13,8 +13,8 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
13
13
  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
- import { runCli } from './cli.js';
17
- import { loadConfig } from './config.js';
16
+ import { runCli, setupProjectFiles } from './cli.js';
17
+ import { loadConfig, writePpdocsFile } 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'));
@@ -26,7 +26,17 @@ if (args.length > 0 && runCli(args)) {
26
26
  }
27
27
  // 运行 MCP 服务
28
28
  async function main() {
29
- const config = loadConfig();
29
+ const config = await loadConfig();
30
+ // 自动持久化: 发现/授权后写入 .ppdocs + 安装模板
31
+ if ((config.source === 'discover' || config.source === 'auth') && config.connection) {
32
+ try {
33
+ writePpdocsFile(config);
34
+ setupProjectFiles(process.cwd(), config.apiUrl);
35
+ }
36
+ catch (e) {
37
+ console.error('[AutoSetup] 自动配置失败:', e);
38
+ }
39
+ }
30
40
  initClient(config.apiUrl);
31
41
  const server = new McpServer({ name: `ppdocs [${config.projectId}]`, version: VERSION }, { capabilities: { tools: {} } });
32
42
  registerTools(server, config.projectId, config.user);
@@ -21,12 +21,16 @@ export function registerProjectTools(server, projectId) {
21
21
  }));
22
22
  // 创建项目
23
23
  server.tool('kg_create_project', '创建新项目。返回项目ID和密码(密码用于MCP连接)', {
24
- id: z.string().describe('项目ID(英文/数字/中文,如"my-app")'),
24
+ id: z.string().optional().describe('项目ID(可选,不填则自动生成随机ID)'),
25
25
  name: z.string().describe('项目名称'),
26
26
  description: z.string().optional().describe('项目简介'),
27
27
  projectPath: z.string().optional().describe('项目源码路径(本地绝对路径)'),
28
28
  }, async (args) => safeTool(async () => {
29
- const result = await client().createProject(args.id, args.name, args.description, args.projectPath || process.cwd());
29
+ // 如果未提供 ID,自动生成随机ID
30
+ const chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
31
+ const randomPart = Array.from({ length: 8 }, () => chars[Math.floor(Math.random() * chars.length)]).join('');
32
+ const id = args.id || `p-${randomPart}`;
33
+ const result = await client().createProject(id, args.name, args.description, args.projectPath || process.cwd());
30
34
  return wrap(`✅ 项目创建成功\n\n- 项目ID: ${result.project.id}\n- 项目名: ${result.project.name}\n- 密码: ${result.password}\n\nMCP 连接地址: http://<服务器IP>:20001/api/${result.project.id}/${result.password}`);
31
35
  }));
32
36
  // 更新根文档 (项目介绍)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ppdocs/mcp",
3
- "version": "3.1.9",
3
+ "version": "3.1.10",
4
4
  "description": "ppdocs MCP Server - Knowledge Graph for Claude",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",