@ppdocs/mcp 3.2.9 → 3.2.11

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.
@@ -4,4 +4,5 @@
4
4
  * kg_discussion_reply, kg_discussion_close_and_archive, kg_discussion_delete
5
5
  */
6
6
  import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
7
- export declare function registerDiscussionTools(server: McpServer, projectId: string): void;
7
+ import { type McpContext } from './shared.js';
8
+ export declare function registerDiscussionTools(server: McpServer, ctx: McpContext): void;
@@ -8,7 +8,7 @@ import { getClient } from '../storage/httpClient.js';
8
8
  import { decodeObjectStrings } from '../utils.js';
9
9
  import { wrap, safeTool } from './shared.js';
10
10
  import { DiscussionManager } from '../storage/discussion.js';
11
- export function registerDiscussionTools(server, projectId) {
11
+ export function registerDiscussionTools(server, ctx) {
12
12
  const client = () => getClient();
13
13
  server.tool('kg_discuss', '💬 跨项目讨论 — 发起、回复、归档协同讨论。action: list(列出活跃讨论)|read(读取详情)|create(发起)|reply(回复)|close(结案归档)|delete(删除)', {
14
14
  action: z.enum(['list', 'read', 'create', 'reply', 'close', 'delete'])
@@ -35,8 +35,8 @@ export function registerDiscussionTools(server, projectId) {
35
35
  const active = DiscussionManager.listActive();
36
36
  const cleanMsg = cleaned > 0 ? `\n⚠️ 已自动清理 ${cleaned} 条超过7天不活跃的讨论` : '';
37
37
  if (active.length === 0)
38
- return wrap(`当前无活跃的讨论 (本项目ID: ${projectId})${cleanMsg}`);
39
- return wrap(`本项目ID: ${projectId}\n活跃讨论区 (${active.length} 个):${cleanMsg}\n\n` + JSON.stringify(active, null, 2));
38
+ return wrap(`当前无活跃的讨论 (本项目ID: ${ctx.projectId})${cleanMsg}`);
39
+ return wrap(`本项目ID: ${ctx.projectId}\n活跃讨论区 (${active.length} 个):${cleanMsg}\n\n` + JSON.stringify(active, null, 2));
40
40
  }
41
41
  case 'read': {
42
42
  const readIds = decoded.ids || (decoded.id ? [decoded.id] : []);
@@ -57,15 +57,15 @@ export function registerDiscussionTools(server, projectId) {
57
57
  const count = DiscussionManager.activeCount();
58
58
  if (count >= 10)
59
59
  return wrap('❌ 活跃讨论已达上限(10条)');
60
- const id = DiscussionManager.create(decoded.title, projectId, decoded.participants, decoded.content);
61
- return wrap(`✅ 讨论已发起,ID: ${id}\n发起方: ${projectId}\n参与方: ${decoded.participants.join(', ')}`);
60
+ const id = DiscussionManager.create(decoded.title, ctx.projectId, decoded.participants, decoded.content);
61
+ return wrap(`✅ 讨论已发起,ID: ${id}\n发起方: ${ctx.projectId}\n参与方: ${decoded.participants.join(', ')}`);
62
62
  }
63
63
  case 'reply': {
64
64
  if (!decoded.id)
65
65
  return wrap('❌ reply 需要 id');
66
66
  if (!decoded.content)
67
67
  return wrap('❌ reply 需要 content');
68
- const success = DiscussionManager.reply(decoded.id, projectId, decoded.content, decoded.newSummary);
68
+ const success = DiscussionManager.reply(decoded.id, ctx.projectId, decoded.content, decoded.newSummary);
69
69
  return wrap(success ? `✅ 回复成功 (ID: ${decoded.id})` : `❌ 回复失败,讨论不存在或已关闭`);
70
70
  }
71
71
  case 'close': {
@@ -27,12 +27,12 @@ export function registerTools(server, projectId, user, onProjectChange) {
27
27
  registerStatusTool(server, ctx);
28
28
  // 📚 知识 (kg_doc + kg_tree + kg_projects + kg_rules)
29
29
  registerDocTools(server, ctx.projectId);
30
- registerProjectTools(server, ctx.projectId);
31
- registerRuleTools(server, ctx.projectId);
30
+ registerProjectTools(server, ctx);
31
+ registerRuleTools(server, ctx);
32
32
  // 📝 工作流 (kg_task + kg_files + kg_discuss)
33
33
  registerTaskTools(server, ctx.projectId, ctx.user);
34
34
  registerFileTools(server);
35
- registerDiscussionTools(server, ctx.projectId);
35
+ registerDiscussionTools(server, ctx);
36
36
  // 🔬 代码分析 (不变)
37
37
  registerAnalyzerTools(server, ctx.projectId);
38
38
  }
@@ -4,4 +4,5 @@
4
4
  * 删除: kg_create_project (桌面端操作)
5
5
  */
6
6
  import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
7
- export declare function registerProjectTools(server: McpServer, projectId: string): void;
7
+ import { type McpContext } from './shared.js';
8
+ export declare function registerProjectTools(server: McpServer, ctx: McpContext): void;
@@ -5,14 +5,14 @@
5
5
  */
6
6
  import { getClient } from '../storage/httpClient.js';
7
7
  import { wrap, safeTool } from './shared.js';
8
- export function registerProjectTools(server, projectId) {
8
+ export function registerProjectTools(server, ctx) {
9
9
  const client = () => getClient();
10
10
  server.tool('kg_projects', '📋 列出所有可访问的项目(返回名称和ID)。跨项目操作的第一步: 获取项目ID后用于其他工具的 targetProject 参数', {}, async () => safeTool(async () => {
11
11
  const projects = await client().crossListProjects();
12
12
  if (projects.length === 0)
13
13
  return wrap('暂无可访问的项目');
14
14
  const lines = projects.map(p => {
15
- const isCurrent = p.id === projectId ? ' ★当前' : '';
15
+ const isCurrent = p.id === ctx.projectId ? ' ★当前' : '';
16
16
  return `- ${p.name} (${p.id})${isCurrent}`;
17
17
  });
18
18
  return wrap(`可访问的项目 (${projects.length} 个):\n\n${lines.join('\n')}`);
@@ -4,4 +4,5 @@
4
4
  * 删除: kg_get_global_rules_meta, kg_save_global_rules_meta (管理员操作)
5
5
  */
6
6
  import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
7
- export declare function registerRuleTools(server: McpServer, projectId: string): void;
7
+ import { type McpContext } from './shared.js';
8
+ export declare function registerRuleTools(server: McpServer, ctx: McpContext): void;
@@ -7,7 +7,7 @@ import { z } from 'zod';
7
7
  import { getClient } from '../storage/httpClient.js';
8
8
  import { decodeObjectStrings, getRules, RULE_TYPE_LABELS } from '../utils.js';
9
9
  import { wrap, safeTool, crossPrefix } from './shared.js';
10
- export function registerRuleTools(server, projectId) {
10
+ export function registerRuleTools(server, ctx) {
11
11
  const client = () => getClient();
12
12
  server.tool('kg_rules', '📏 项目规则管理 — 读取或保存代码风格、审查规则等。action: get(读取规则)|save(保存规则)|get_meta(读取触发配置)|save_meta(保存触发配置)', {
13
13
  action: z.enum(['get', 'save', 'get_meta', 'save_meta'])
@@ -51,7 +51,7 @@ export function registerRuleTools(server, projectId) {
51
51
  return wrap(`暂无项目规则(项目: ${decoded.targetProject})`);
52
52
  return wrap(`${crossPrefix(decoded.targetProject)}${allRules.join('\n\n')}`);
53
53
  }
54
- const rules = await getRules(projectId, decoded.ruleType || undefined);
54
+ const rules = await getRules(ctx.projectId, decoded.ruleType || undefined);
55
55
  if (!rules || rules.trim() === '') {
56
56
  const meta = await client().getRulesMeta();
57
57
  const typeName = decoded.ruleType ? (meta[decoded.ruleType]?.label || decoded.ruleType) : '项目';
package/dist/web/ui.js CHANGED
@@ -74,9 +74,6 @@ label{font-size:12px;color:#64748b;font-weight:500;display:block;margin-bottom:4
74
74
  .tab.active{color:#60a5fa;border-color:#60a5fa}
75
75
  @keyframes pulse{0%,100%{opacity:1}50%{opacity:.4}}
76
76
  </style>
77
- <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
78
- <meta http-equiv="Pragma" content="no-cache">
79
- <meta http-equiv="Expires" content="0">
80
77
  </head>
81
78
  <body>
82
79
  <div class="app" id="app"></div>
@@ -92,7 +89,7 @@ function go(page,data){Object.assign(S,data||{});S.page=page;_lastDetailRid=null
92
89
  function toast(m,t){t=t||'ok';var e=document.getElementById('toast');e.textContent=m;e.className='toast '+t+' show';setTimeout(function(){e.className='toast'},3000)}
93
90
 
94
91
  // ============ API ============
95
- function api(path,opts){return fetch(path,opts).then(function(r){return r.json()}).then(function(d){console.log('[api]',path,d);return d})}
92
+ function api(path,opts){return fetch(path,opts).then(function(r){return r.json()})}
96
93
 
97
94
  // ============ Init ============
98
95
  function init(){
@@ -111,7 +108,7 @@ function init(){
111
108
  var entry=items[i].webkitGetAsEntry&&items[i].webkitGetAsEntry();
112
109
  if(entry&&entry.isDirectory){
113
110
  go('add',{addStep:1,addDir:entry.name});
114
- toast('📂 检测到 '+entry.name+',请补全绝对路径后发送授权');
111
+ toast('📂 请补全绝对路径,例如 D:/projects/'+entry.name);
115
112
  setTimeout(function(){var inp=document.getElementById('iDir');if(inp){inp.focus();inp.setSelectionRange(0,0)}},150);
116
113
  return;
117
114
  }
@@ -191,19 +188,22 @@ function renderAdd(){
191
188
  }
192
189
 
193
190
  function renderAddStep1(){
191
+ var dirName=S.addDir?S.addDir.replace(/\\\\/g,'/').split('/').filter(Boolean).pop():'';
192
+ var namePreview=dirName?'<div style="font-size:12px;color:#60a5fa;margin:-8px 0 12px">📂 项目名: <strong>'+dirName+'</strong></div>':'';
194
193
  return '<span class="back" onclick="cancelAuth();go(\\x27list\\x27)">← 返回项目列表</span>'+
195
- '<div class="card no-hover"><div class="section"><h2>➕ 添加项目</h2>'+
196
- '<p style="font-size:13px;color:#64748b;margin-bottom:12px">点击发送授权,在主机端 PPDocs 应用中选择项目并批准</p>'+
197
- '<div class="fg"><label>本地项目目录 (可选,MCP安装时需要)</label><input id="iDir" value="'+S.addDir+'" placeholder="Win: D:\\\\projects\\\\app Mac: /Users/xxx/app" oninput="S.addDir=this.value"></div>'+
194
+ '<div class="card no-hover"><div class="section"><h2>➕ 添加项目 — Step 1/2</h2>'+
195
+ '<p style="font-size:13px;color:#64748b;margin-bottom:12px">指定本地项目目录,以目录名作为项目名称</p>'+
196
+ '<div class="fg"><label>本地项目目录 (绝对路径)</label><input id="iDir" value="'+S.addDir+'" placeholder="例: D:/projects/my-app" oninput="S.addDir=this.value;_render()"></div>'+
197
+ namePreview+
198
198
  '<div class="btn-row"><button class="btn btn-p" onclick="doAuthStart()">📡 发送授权请求</button></div>'+
199
199
  '</div></div>';
200
200
  }
201
201
 
202
202
  function doAuthStart(){
203
- var dir=(document.getElementById('iDir')?document.getElementById('iDir').value:'').trim();
204
- if(dir&&!/^[A-Za-z]:[\\\\\\/]|^\\/|^~\//.test(dir)){toast('路径需为绝对路径(D:\\xxx 或 /Users/xxx)或留空','err');return}
203
+ var dir=document.getElementById('iDir').value.trim();
204
+ if(dir&&!/^[A-Za-z]:[\\\\\\/]|^\\//.test(dir)){toast('路径需为绝对路径(D:\\xxx 或 /Users/xxx)或留空','err');return}
205
205
  S.addDir=dir;
206
- api('/api/auth/start',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({localDir:dir||''})}).then(function(r){
206
+ api('/api/auth/start',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({localDir:dir})}).then(function(r){
207
207
  if(!r.ok){toast(r.error||'授权请求失败','err');return}
208
208
  S.authRequestId=r.requestId;S.authStartTime=Date.now();S.authStatus='pending';S.addStep=2;
209
209
  render();startAuthPoll();
@@ -304,7 +304,7 @@ function renderOverview(p){
304
304
  '<div class="stat"><span>同步: '+(p.connected?'🟢 运行中':'🔴 '+p.syncStatus)+' '+(p.lastSync?'('+p.lastSync+')':'')+'</span></div>'+
305
305
  '</div></div>'+
306
306
  '<div class="card no-hover"><div class="section"><h2>📚 知识图谱</h2>'+
307
- '<div class="tree-box" id="treeBox">加载中 ...</div></div></div>';
307
+ '<div class="tree-box" id="treeBox">加载中...</div></div></div>';
308
308
  }
309
309
 
310
310
  function renderMcpTab(p){
@@ -361,22 +361,11 @@ function renderPromptsTab(p){
361
361
 
362
362
  // ============ Data Loading ============
363
363
  function loadTree(rid){
364
- var box=document.getElementById('treeBox');
365
- if(!box) return;
366
- var xhr=new XMLHttpRequest();
367
- xhr.open('GET','/api/bind/'+rid+'/tree?_='+Date.now(),true);
368
- xhr.onload=function(){
369
- try{
370
- var r=JSON.parse(xhr.responseText);
371
- var txt=r.tree||'知识库为空';
372
- var NL=String.fromCharCode(10);
373
- if(r.docCount>0) txt='共 '+r.docCount+' 文档, '+r.dirCount+' 目录'+NL+NL+txt;
374
- if(r.readOnly) txt='🔒 只读模式'+NL+NL+txt;
375
- box.textContent=txt;
376
- }catch(e){box.textContent='解析失败: '+e}
377
- };
378
- xhr.onerror=function(){box.textContent='请求失败'};
379
- xhr.send();
364
+ var box=document.getElementById('treeBox');if(!box)return Promise.resolve();
365
+ return api('/api/bind/'+rid+'/tree').then(function(r){
366
+ var prefix=r.readOnly?'🔒 只读模式\\n\\n':'';
367
+ box.textContent=r.tree?(prefix+(r.docCount>0?''+r.docCount+' 文档, '+r.dirCount+' 目录\\n\\n':'')+r.tree):'知识库为空';
368
+ }).catch(function(e){box.textContent='加载失败: '+e});
380
369
  }
381
370
  function loadMcpStatus(rid){return api('/api/bind/'+rid+'/mcp').then(function(r){S.mcpStatus=r.platforms||{}}).catch(function(){S.mcpStatus={}})}
382
371
  function loadMcpAll(rid){return api('/api/bind/'+rid+'/mcp/all').then(function(r){S.mcpAll=r.servers||[]}).catch(function(){S.mcpAll=[]})}
@@ -469,11 +458,7 @@ render=function(){
469
458
  var _rid=S.projects[S.detailIdx]?S.projects[S.detailIdx].remoteId:null;
470
459
  if(S.page==='detail'&&_rid&&!_detailLoading&&_lastDetailRid!==_rid){
471
460
  _detailLoading=true;_lastDetailRid=_rid;
472
- loadMcpStatus(_rid);loadMcpAll(_rid);loadPrompts(_rid);
473
- setTimeout(function(){
474
- _detailLoading=false;
475
- loadTree(_rid);
476
- },100);
461
+ Promise.all([loadTree(_rid),loadMcpStatus(_rid),loadMcpAll(_rid),loadPrompts(_rid)]).then(function(){_detailLoading=false;_render()}).catch(function(){_detailLoading=false});
477
462
  }
478
463
  };
479
464
  setInterval(function(){
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ppdocs/mcp",
3
- "version": "3.2.9",
3
+ "version": "3.2.11",
4
4
  "description": "ppdocs MCP Server - Knowledge Graph for Claude",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",