@ppdocs/mcp 3.2.3 → 3.2.5
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.js +3 -10
- package/dist/config.d.ts +2 -10
- package/dist/config.js +12 -93
- package/dist/index.js +2 -12
- package/dist/web/server.js +33 -13
- package/dist/web/ui.js +481 -467
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -346,8 +346,8 @@ function autoRegisterMcp(apiUrl, user, skipGemini = false) {
|
|
|
346
346
|
console.log(`✅ Registered MCP for: ${detected.join(', ')}`);
|
|
347
347
|
return true;
|
|
348
348
|
}
|
|
349
|
-
/** 在指定路径创建或更新 mcp.json 配置 */
|
|
350
|
-
function createMcpConfigAt(mcpPath,
|
|
349
|
+
/** 在指定路径创建或更新 mcp.json 配置 (不传 PPDOCS_API_URL,依赖 cwd/.ppdocs 文件) */
|
|
350
|
+
function createMcpConfigAt(mcpPath, _apiUrl, _options) {
|
|
351
351
|
let mcpConfig = {};
|
|
352
352
|
if (fs.existsSync(mcpPath)) {
|
|
353
353
|
try {
|
|
@@ -362,24 +362,17 @@ function createMcpConfigAt(mcpPath, apiUrl, options) {
|
|
|
362
362
|
// Windows 需要 cmd /c 包装才能执行 npx
|
|
363
363
|
const isWindows = process.platform === 'win32';
|
|
364
364
|
// 对于 Mac/Linux,IDE 可能没有用户的完整 PATH,导致找不到 npx
|
|
365
|
-
// 使用实际 PATH 值拼接,JSON 中的 $PATH 不会被展开
|
|
366
365
|
const macExtraPaths = '/usr/local/bin:/opt/homebrew/bin:/home/linuxbrew/.linuxbrew/bin';
|
|
367
366
|
const actualPath = `${process.env.PATH || '/usr/bin:/bin'}:${macExtraPaths}`;
|
|
368
367
|
const ppdocsServer = isWindows
|
|
369
368
|
? {
|
|
370
369
|
command: 'cmd',
|
|
371
370
|
args: ['/c', 'npx', '-y', '@ppdocs/mcp@latest'],
|
|
372
|
-
...(options?.noEnv ? {} : { env: { "PPDOCS_API_URL": apiUrl } })
|
|
373
371
|
}
|
|
374
372
|
: {
|
|
375
373
|
command: 'npx',
|
|
376
374
|
args: ['-y', '@ppdocs/mcp@latest'],
|
|
377
|
-
|
|
378
|
-
env: {
|
|
379
|
-
"PPDOCS_API_URL": apiUrl,
|
|
380
|
-
"PATH": actualPath
|
|
381
|
-
}
|
|
382
|
-
})
|
|
375
|
+
env: { "PATH": actualPath }
|
|
383
376
|
};
|
|
384
377
|
mcpConfig.mcpServers = {
|
|
385
378
|
...(mcpConfig.mcpServers || {}),
|
package/dist/config.d.ts
CHANGED
|
@@ -1,22 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* ppdocs MCP Config
|
|
3
|
-
* 读取配置: 环境变量 > .ppdocs 文件 >
|
|
3
|
+
* 读取配置: 环境变量 > .ppdocs 文件 > 提示去 Agent WebUI 绑定
|
|
4
4
|
*/
|
|
5
5
|
export interface PpdocsConfig {
|
|
6
6
|
apiUrl: string;
|
|
7
7
|
projectId: string;
|
|
8
8
|
user: string;
|
|
9
|
-
source: 'env' | 'file'
|
|
10
|
-
/** 原始连接参数 (discover/auth 填充, 供持久化使用) */
|
|
11
|
-
connection?: {
|
|
12
|
-
host: string;
|
|
13
|
-
port: number;
|
|
14
|
-
password: string;
|
|
15
|
-
};
|
|
9
|
+
source: 'env' | 'file';
|
|
16
10
|
}
|
|
17
11
|
export declare const PPDOCS_CONFIG_FILE = ".ppdocs";
|
|
18
12
|
/** 生成随机用户名 */
|
|
19
13
|
export declare function generateUser(): string;
|
|
20
|
-
/** 写入 .ppdocs 文件 (需 config.connection 存在) */
|
|
21
|
-
export declare function writePpdocsFile(config: PpdocsConfig): void;
|
|
22
14
|
export declare function loadConfig(): Promise<PpdocsConfig>;
|
package/dist/config.js
CHANGED
|
@@ -1,17 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* ppdocs MCP Config
|
|
3
|
-
* 读取配置: 环境变量 > .ppdocs 文件 >
|
|
3
|
+
* 读取配置: 环境变量 > .ppdocs 文件 > 提示去 Agent WebUI 绑定
|
|
4
4
|
*/
|
|
5
5
|
import * as fs from 'fs';
|
|
6
6
|
import * as path from 'path';
|
|
7
|
-
import * as os from 'os';
|
|
8
7
|
export const PPDOCS_CONFIG_FILE = '.ppdocs';
|
|
9
8
|
/** 生成随机用户名 */
|
|
10
9
|
export function generateUser() {
|
|
11
10
|
const chars = 'abcdefghjkmnpqrstuvwxyz23456789';
|
|
12
11
|
return Array.from({ length: 8 }, () => chars[Math.floor(Math.random() * chars.length)]).join('');
|
|
13
12
|
}
|
|
14
|
-
// ============
|
|
13
|
+
// ============ 配置读取 ============
|
|
15
14
|
function readEnvConfig() {
|
|
16
15
|
const apiUrl = process.env.PPDOCS_API_URL;
|
|
17
16
|
if (!apiUrl)
|
|
@@ -33,104 +32,24 @@ function readPpdocsFile() {
|
|
|
33
32
|
return null;
|
|
34
33
|
}
|
|
35
34
|
}
|
|
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
|
-
}
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
return null;
|
|
50
|
-
}
|
|
51
|
-
// ============ 授权请求 ============
|
|
52
|
-
async function requestAuthConfig(server) {
|
|
53
|
-
try {
|
|
54
|
-
const reqRes = await fetch(`${server.base}/api/auth/request`, {
|
|
55
|
-
method: 'POST',
|
|
56
|
-
headers: { 'Content-Type': 'application/json' },
|
|
57
|
-
body: JSON.stringify({ cwd: process.cwd(), hostname: os.hostname() }),
|
|
58
|
-
});
|
|
59
|
-
if (!reqRes.ok)
|
|
60
|
-
return null;
|
|
61
|
-
const { data } = await reqRes.json();
|
|
62
|
-
if (!data?.requestId)
|
|
63
|
-
return null;
|
|
64
|
-
console.error(`[Auth] 等待桌面端授权... (请在 ppdocs 桌面端确认)`);
|
|
65
|
-
for (let i = 0; i < 150; i++) {
|
|
66
|
-
await new Promise(r => setTimeout(r, 2000));
|
|
67
|
-
const pollRes = await fetch(`${server.base}/api/auth/poll/${data.requestId}`, { signal: AbortSignal.timeout(3000) });
|
|
68
|
-
if (!pollRes.ok)
|
|
69
|
-
continue;
|
|
70
|
-
const { data: poll } = await pollRes.json();
|
|
71
|
-
if (poll?.status === 'approved' && poll.result) {
|
|
72
|
-
const r = poll.result;
|
|
73
|
-
console.error(`[Auth] 已授权: ${r.project_name} (${r.project_id})`);
|
|
74
|
-
return {
|
|
75
|
-
apiUrl: `http://${r.api_host}:${r.api_port}/api/${r.project_id}/${r.password}`,
|
|
76
|
-
projectId: r.project_id,
|
|
77
|
-
user: `auto-${generateUser().slice(0, 4)}`,
|
|
78
|
-
source: 'auth',
|
|
79
|
-
connection: { host: r.api_host, port: r.api_port, password: r.password },
|
|
80
|
-
};
|
|
81
|
-
}
|
|
82
|
-
if (poll?.status === 'rejected') {
|
|
83
|
-
console.error('[Auth] 授权被拒绝');
|
|
84
|
-
return null;
|
|
85
|
-
}
|
|
86
|
-
if (poll?.status === 'expired') {
|
|
87
|
-
console.error('[Auth] 授权超时');
|
|
88
|
-
return null;
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
console.error('[Auth] 轮询超时');
|
|
92
|
-
return null;
|
|
93
|
-
}
|
|
94
|
-
catch {
|
|
95
|
-
return null;
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
// ============ 持久化 ============
|
|
99
|
-
/** 写入 .ppdocs 文件 (需 config.connection 存在) */
|
|
100
|
-
export function writePpdocsFile(config) {
|
|
101
|
-
if (!config.connection)
|
|
102
|
-
return;
|
|
103
|
-
const configPath = path.join(process.cwd(), '.ppdocs');
|
|
104
|
-
if (fs.existsSync(configPath))
|
|
105
|
-
return;
|
|
106
|
-
const { host, port, password } = config.connection;
|
|
107
|
-
fs.writeFileSync(configPath, JSON.stringify({
|
|
108
|
-
api: `http://${host}:${port}`,
|
|
109
|
-
projectId: config.projectId,
|
|
110
|
-
key: password,
|
|
111
|
-
user: config.user,
|
|
112
|
-
}, null, 2), 'utf-8');
|
|
113
|
-
console.error(`[Config] 已保存 .ppdocs`);
|
|
114
|
-
}
|
|
115
35
|
// ============ 入口 ============
|
|
116
36
|
export async function loadConfig() {
|
|
117
37
|
// 1. 环境变量
|
|
118
38
|
const envConfig = readEnvConfig();
|
|
119
39
|
if (envConfig)
|
|
120
40
|
return envConfig;
|
|
121
|
-
// 2. .ppdocs 文件
|
|
41
|
+
// 2. .ppdocs 文件 (由 Agent WebUI 绑定时自动生成)
|
|
122
42
|
const fileConfig = readPpdocsFile();
|
|
123
43
|
if (fileConfig)
|
|
124
44
|
return fileConfig;
|
|
125
|
-
//
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
console.error('
|
|
134
|
-
console.error(' 请确保 ppdocs 桌面端正在运行,或手动配置 PPDOCS_API_URL 环境变量');
|
|
45
|
+
// 无配置 → 提示用户去 Agent WebUI 绑定
|
|
46
|
+
console.error('ERROR: 未找到 .ppdocs 配置');
|
|
47
|
+
console.error('');
|
|
48
|
+
console.error(' 请在 Agent WebUI 绑定此项目:');
|
|
49
|
+
console.error(' 1. 启动 Agent: npx @ppdocs/mcp webui');
|
|
50
|
+
console.error(' 2. 打开浏览器: http://localhost:20010');
|
|
51
|
+
console.error(' 3. 添加项目 → 选择本地目录和远程知识库 → 自动生成 .ppdocs');
|
|
52
|
+
console.error('');
|
|
53
|
+
console.error(' 或手动设置环境变量: PPDOCS_API_URL=http://host:port/api/projectId/key');
|
|
135
54
|
process.exit(1);
|
|
136
55
|
}
|
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
|
|
17
|
-
import { loadConfig
|
|
16
|
+
import { runCli } from './cli.js';
|
|
17
|
+
import { loadConfig } 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'));
|
|
@@ -35,16 +35,6 @@ if (args.length > 0) {
|
|
|
35
35
|
// 运行 MCP 服务
|
|
36
36
|
async function main() {
|
|
37
37
|
const config = await loadConfig();
|
|
38
|
-
// 自动持久化: 发现/授权后写入 .ppdocs + 安装模板
|
|
39
|
-
if (config.source === 'auth' && config.connection) {
|
|
40
|
-
try {
|
|
41
|
-
writePpdocsFile(config);
|
|
42
|
-
setupProjectFiles(process.cwd(), config.apiUrl);
|
|
43
|
-
}
|
|
44
|
-
catch (e) {
|
|
45
|
-
console.error('[AutoSetup] 自动配置失败:', e);
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
38
|
initClient(config.apiUrl);
|
|
49
39
|
const server = new McpServer({ name: `ppdocs [${config.projectId}]`, version: VERSION }, { capabilities: { tools: {} } });
|
|
50
40
|
registerTools(server, config.projectId, config.user);
|
package/dist/web/server.js
CHANGED
|
@@ -39,6 +39,25 @@ export function saveAgentConfig(config) {
|
|
|
39
39
|
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
40
40
|
fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
41
41
|
}
|
|
42
|
+
/** 写入 .ppdocs 到项目目录,供 MCP 直接读取 */
|
|
43
|
+
function writePpdocsToProject(binding, host, port) {
|
|
44
|
+
if (!binding.localDir || !binding.remote.password)
|
|
45
|
+
return;
|
|
46
|
+
const configPath = path.join(binding.localDir, '.ppdocs');
|
|
47
|
+
const content = {
|
|
48
|
+
api: `http://${host}:${port}`,
|
|
49
|
+
projectId: binding.remote.id,
|
|
50
|
+
key: binding.remote.password,
|
|
51
|
+
user: `agent-${binding.localName.slice(0, 8)}`,
|
|
52
|
+
};
|
|
53
|
+
try {
|
|
54
|
+
fs.writeFileSync(configPath, JSON.stringify(content, null, 2), 'utf-8');
|
|
55
|
+
console.log(`[Config] 已写入 ${configPath}`);
|
|
56
|
+
}
|
|
57
|
+
catch (e) {
|
|
58
|
+
console.error(`[Config] 写入 .ppdocs 失败: ${e}`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
42
61
|
const state = {
|
|
43
62
|
hostConnected: false,
|
|
44
63
|
projectStatus: new Map(),
|
|
@@ -57,8 +76,11 @@ export function setProjectStatus(remoteId, status) {
|
|
|
57
76
|
export function startWebServer(webPort) {
|
|
58
77
|
const app = express();
|
|
59
78
|
app.use(express.json());
|
|
60
|
-
//
|
|
79
|
+
app.disable('etag'); // 禁用 ETag,避免 304 缓存导致前端数据不更新
|
|
80
|
+
// GET / → UI (禁止浏览器缓存,确保加载最新版本)
|
|
61
81
|
app.get('/', (_req, res) => {
|
|
82
|
+
res.set('Cache-Control', 'no-store, no-cache, must-revalidate');
|
|
83
|
+
res.set('Pragma', 'no-cache');
|
|
62
84
|
res.type('html').send(getAgentHtml());
|
|
63
85
|
});
|
|
64
86
|
// GET /api/status → 全局+各项目状态
|
|
@@ -140,10 +162,7 @@ export function startWebServer(webPort) {
|
|
|
140
162
|
// POST /api/auth/start → 发起授权请求 (代理转发到主机)
|
|
141
163
|
app.post('/api/auth/start', async (req, res) => {
|
|
142
164
|
const { localDir } = req.body;
|
|
143
|
-
|
|
144
|
-
res.json({ ok: false, error: '缺少本地目录' });
|
|
145
|
-
return;
|
|
146
|
-
}
|
|
165
|
+
// localDir 可选,用于 APP 端显示来源信息
|
|
147
166
|
const config = loadAgentConfig();
|
|
148
167
|
if (!config?.host) {
|
|
149
168
|
res.json({ ok: false, error: '请先配置主机' });
|
|
@@ -210,8 +229,8 @@ export function startWebServer(webPort) {
|
|
|
210
229
|
// POST /api/bind → 绑定项目 (授权通过后调用)
|
|
211
230
|
app.post('/api/bind', async (req, res) => {
|
|
212
231
|
const { localDir, remoteId, remoteName, password } = req.body;
|
|
213
|
-
if (!
|
|
214
|
-
res.json({ ok: false, error: '
|
|
232
|
+
if (!remoteId) {
|
|
233
|
+
res.json({ ok: false, error: '缺少 remoteId' });
|
|
215
234
|
return;
|
|
216
235
|
}
|
|
217
236
|
const config = loadAgentConfig();
|
|
@@ -225,14 +244,17 @@ export function startWebServer(webPort) {
|
|
|
225
244
|
return;
|
|
226
245
|
}
|
|
227
246
|
const binding = {
|
|
228
|
-
localDir,
|
|
229
|
-
localName: path.basename(localDir),
|
|
247
|
+
localDir: localDir || '',
|
|
248
|
+
localName: localDir ? path.basename(localDir) : (remoteName || remoteId),
|
|
230
249
|
remote: { id: remoteId, name: remoteName || remoteId, password: password || '' },
|
|
231
250
|
sync: { enabled: true, intervalSec: 15 },
|
|
232
251
|
createdAt: new Date().toISOString(),
|
|
233
252
|
};
|
|
234
253
|
config.projects.push(binding);
|
|
235
254
|
saveAgentConfig(config);
|
|
255
|
+
// 有本地目录时才写 .ppdocs
|
|
256
|
+
if (localDir)
|
|
257
|
+
writePpdocsToProject(binding, config.host, config.port);
|
|
236
258
|
// 通知 Agent 主进程启动同步
|
|
237
259
|
if (state.onBind)
|
|
238
260
|
state.onBind(binding);
|
|
@@ -557,17 +579,15 @@ function hasMcpInJson(filePath, serverName) {
|
|
|
557
579
|
return false;
|
|
558
580
|
}
|
|
559
581
|
}
|
|
560
|
-
/** 构建 MCP server 配置对象 */
|
|
561
|
-
function buildMcpServerConfig(
|
|
582
|
+
/** 构建 MCP server 配置对象 (不传 PPDOCS_API_URL,依赖 cwd/.ppdocs 文件自动识别项目) */
|
|
583
|
+
function buildMcpServerConfig(_apiUrl, isWindows) {
|
|
562
584
|
return isWindows ? {
|
|
563
585
|
command: 'cmd',
|
|
564
586
|
args: ['/c', 'npx', '-y', '@ppdocs/mcp@latest'],
|
|
565
|
-
env: { PPDOCS_API_URL: apiUrl },
|
|
566
587
|
} : {
|
|
567
588
|
command: 'npx',
|
|
568
589
|
args: ['-y', '@ppdocs/mcp@latest'],
|
|
569
590
|
env: {
|
|
570
|
-
PPDOCS_API_URL: apiUrl,
|
|
571
591
|
PATH: `${process.env.PATH || '/usr/bin:/bin'}:/usr/local/bin:/opt/homebrew/bin`,
|
|
572
592
|
},
|
|
573
593
|
};
|