Skip to main content
Glama
thana0623

prompts-mcp-server

by thana0623

log_dialog

Record a dialog with title, request, code changes, decisions, and todos into a daily log chain for persistent project context.

Instructions

【记录日志】记录一次对话到传递链(daily + recent-5 + summary-10 + log-state.json)。

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
titleYes对话简明标题
requestYes清洗后的用户需求
changesNo代码变更文件列表
decisionsNo本次技术决策
todosNo遗留待办项

Implementation Reference

  • src/index.ts:137-168 (registration)
    Tool registration: 'log_dialog' is registered in ListToolsRequestSchema with name, description, and inputSchema (title, request required; changes, decisions, todos optional).
    {
      name: 'log_dialog',
      description: '【记录日志】记录一次对话到传递链(daily + recent-5 + summary-10 + log-state.json)。',
      inputSchema: {
        type: 'object',
        properties: {
          title: {
            type: 'string',
            description: '对话简明标题',
          },
          request: {
            type: 'string',
            description: '清洗后的用户需求',
          },
          changes: {
            type: 'array',
            items: { type: 'string' },
            description: '代码变更文件列表',
          },
          decisions: {
            type: 'array',
            items: { type: 'string' },
            description: '本次技术决策',
          },
          todos: {
            type: 'array',
            items: { type: 'string' },
            description: '遗留待办项',
          },
        },
        required: ['title', 'request'],
      },
  • src/index.ts:246-247 (registration)
    Tool routing: 'log_dialog' is routed in CallToolRequestSchema switch statement to handleLogDialog method.
    case 'log_dialog':
      return this.handleLogDialog(args);
  • Main handler for log_dialog: validates required args (title, request), gets promptsDir, generates entry ID, then calls 5 helper methods to update daily log, recent-5, summary-10, log-state.json, and todos.md.
    private async handleLogDialog(args: any) {
      const title = typeof args?.title === 'string' ? args.title : '';
      const request = typeof args?.request === 'string' ? args.request : '';
      const changes: string[] = Array.isArray(args?.changes) ? args.changes : [];
      const decisions: string[] = Array.isArray(args?.decisions) ? args.decisions : [];
      const todos: string[] = Array.isArray(args?.todos) ? args.todos : [];
    
      if (!title || !request) {
        return {
          content: [{ type: 'text', text: '❌ "title" 和 "request" 是必填参数。' }],
          isError: true,
        };
      }
    
      try {
        const promptsDir = getPromptsDir();
        const today = new Date().toISOString().slice(0, 10);
        const entryId = this.getNextEntryId(promptsDir);
    
        // 1. 更新 daily 日志
        this.appendDailyLog(promptsDir, today, entryId, title, request, changes, decisions, todos);
    
        // 2. 更新 recent-5
        this.updateRecent5(promptsDir, entryId, today, title, request, changes, decisions, todos);
    
        // 3. 更新 summary-10
        this.updateSummary10(promptsDir, entryId, today, request, changes, decisions, todos);
    
        // 4. 更新 log-state.json
        this.updateLogState(promptsDir, entryId, today, request, changes, decisions, todos);
    
        // 5. 更新 todos.md(如果有待办)
        if (todos.length > 0) {
          this.appendTodos(promptsDir, todos);
        }
    
        return {
          content: [{
            type: 'text',
            text: `✅ 对话日志已记录。\n\n- Entry-${String(entryId).padStart(3, '0')}\n- 日期: ${today}\n- 标题: ${title}\n- daily: 已追加\n- recent-5: 已更新\n- summary-10: 已更新`,
          }],
        };
      } catch (error: any) {
        return {
          content: [{ type: 'text', text: `❌ 记录日志失败: ${error.message || error}` }],
          isError: true,
        };
      }
    }
  • Helper: reads nextEntryId from log-state.json to generate sequential entry IDs.
    private getNextEntryId(promptsDir: string): number {
      const statePath = path.join(promptsDir, 'log-state.json');
      try {
        if (fs.existsSync(statePath)) {
          const state = JSON.parse(fs.readFileSync(statePath, 'utf-8'));
          return state.nextEntryId || 1;
        }
      } catch { /* ignore */ }
      return 1;
    }
  • Helper methods that write to daily/<date>.md, recent-5.md, summary-10.md, log-state.json, and todos.md respectively to persist dialog log data.
    private appendDailyLog(
      promptsDir: string, today: string, entryId: number,
      title: string, request: string, changes: string[], decisions: string[], todos: string[]
    ) {
      const dailyDir = path.join(promptsDir, 'daily');
      if (!fs.existsSync(dailyDir)) fs.mkdirSync(dailyDir, { recursive: true });
    
      const dailyPath = path.join(dailyDir, `${today}.md`);
      const entry = [
        '',
        `## Entry-${String(entryId).padStart(3, '0')}`,
        `- 时间: ${new Date().toISOString()}`,
        `- 标题: ${title}`,
        `- 清洗后需求: ${request}`,
        changes.length > 0 ? `- 代码变更: ${changes.join(', ')}` : '',
        decisions.length > 0 ? `- 技术决策: ${decisions.join('; ')}` : '',
        todos.length > 0 ? `- 待办: ${todos.join('; ')}` : '',
        '',
      ].filter(Boolean).join('\n');
    
      fs.appendFileSync(dailyPath, entry, 'utf-8');
    }
    
    private updateRecent5(
      promptsDir: string, entryId: number, today: string,
      title: string, request: string, changes: string[], decisions: string[], todos: string[]
    ) {
      const recentPath = path.join(promptsDir, 'recent-5.md');
      const newEntry = [
        `## Entry-${String(entryId).padStart(3, '0')}`,
        `- 日期: ${today}`,
        `- 清洗后需求: ${request}`,
        changes.length > 0 ? `- 代码变更:\n${changes.map(c => `  - ${c}`).join('\n')}` : '- 代码变更: (无)',
        decisions.length > 0 ? `- 技术决策:\n${decisions.map(d => `  - ${d}`).join('\n')}` : '- 技术决策: (无)',
        todos.length > 0 ? `- 待办:\n${todos.map(t => `  - ${t}`).join('\n')}` : '- 待办: (无)',
        '',
      ].join('\n');
    
      // 读取现有内容,保留 header,追加新条目,只保留最近 5 条
      let content = '';
      if (fs.existsSync(recentPath)) {
        content = fs.readFileSync(recentPath, 'utf-8');
      }
    
      // 提取 header(第一个 ## 之前的内容)
      const headerMatch = content.match(/^.*?(?=\n## Entry-)/s);
      const header = headerMatch ? headerMatch[0].trim() : `# 最近 5 条对话与操作(动态窗口)\n\n> 规则:每次新增 1 条,超过 5 条时删除最旧 1 条,仅保留最近 5 条。\n`;
    
      // 提取现有条目
      const entries = content.split(/\n(?=## Entry-)/).filter(e => e.startsWith('## Entry-'));
      entries.push(newEntry);
    
      // 只保留最近 5 条
      const recentEntries = entries.slice(-5);
    
      const updated = `${header}\n\n${recentEntries.join('\n')}\n`;
      fs.writeFileSync(recentPath, updated, 'utf-8');
    }
    
    private updateSummary10(
      promptsDir: string, entryId: number, today: string,
      request: string, changes: string[], decisions: string[], todos: string[]
    ) {
      const summaryPath = path.join(promptsDir, 'summary-10.md');
      let content = '';
      if (fs.existsSync(summaryPath)) {
        content = fs.readFileSync(summaryPath, 'utf-8');
      }
    
      if (!content) {
        content = `# 近 10 条对话状态摘要(Stateful)\n\n## 窗口元数据\n- window_id: W-0001\n- 统计范围: Entry-001 ~ Entry-010\n- 当前已收录: 0 / 10\n\n## Stateful 摘要\n### Current State\n- 项目初始化完成。\n\n### Decisions Kept\n- (暂无)\n\n### Invalidated Decisions\n- (暂无)\n\n### Open TODO\n- (暂无)\n\n### Carry Forward\n- (暂无)\n`;
      }
    
      // 更新窗口计数
      const countMatch = content.match(/当前已收录:\s*(\d+)\s*\/\s*10/);
      let count = countMatch ? parseInt(countMatch[1]) : 0;
      count = Math.min(count + 1, 10);
    
      content = content.replace(/当前已收录:\s*\d+\s*\/\s*10/, `当前已收录: ${count} / 10`);
    
      // 更新 Current State
      const stateSection = content.match(/### Current State\n([\s\S]*?)(?=\n### Decisions Kept)/);
      if (stateSection) {
        const newState = `### Current State\n- Entry-${String(entryId).padStart(3, '0')} (${today}): ${request}\n- Window progress: ${count}/10`;
        content = content.replace(/### Current State\n[\s\S]*?(?=\n### Decisions Kept)/, newState + '\n');
      }
    
      // 更新 Decisions Kept
      if (decisions.length > 0) {
        const keptSection = content.match(/### Decisions Kept\n([\s\S]*?)(?=\n### Invalidated Decisions)/);
        if (keptSection) {
          const newDecisions = decisions.map(d => `- ${d}`).join('\n');
          const existingDecisions = keptSection[1].trim();
          if (existingDecisions === '(暂无)') {
            content = content.replace(/### Decisions Kept\n\(暂无\)/, `### Decisions Kept\n${newDecisions}`);
          } else {
            content = content.replace(/### Decisions Kept\n[\s\S]*?(?=\n### Invalidated Decisions)/,
              `### Decisions Kept\n${existingDecisions}\n${newDecisions}\n`);
          }
        }
      }
    
      // 更新 Open TODO
      if (todos.length > 0) {
        const todoSection = content.match(/### Open TODO\n([\s\S]*?)(?=\n### Carry Forward)/);
        if (todoSection) {
          const newTodos = todos.map(t => `- ${t}`).join('\n');
          const existingTodos = todoSection[1].trim();
          if (existingTodos === '(暂无)') {
            content = content.replace(/### Open TODO\n\(暂无\)/, `### Open TODO\n${newTodos}`);
          } else {
            content = content.replace(/### Open TODO\n[\s\S]*?(?=\n### Carry Forward)/,
              `### Open TODO\n${existingTodos}\n${newTodos}\n`);
          }
        }
      }
    
      fs.writeFileSync(summaryPath, content, 'utf-8');
    }
    
    private updateLogState(
      promptsDir: string, entryId: number, today: string,
      request: string, changes: string[], decisions: string[], todos: string[]
    ) {
      const statePath = path.join(promptsDir, 'log-state.json');
      let state: any = {
        nextEntryId: 1,
        windowId: 'W-0001',
        windowStartEntry: 1,
        windowCount: 0,
        windowEntries: [],
      };
    
      if (fs.existsSync(statePath)) {
        try {
          state = JSON.parse(fs.readFileSync(statePath, 'utf-8'));
        } catch { /* use default */ }
      }
    
      // 添加新条目
      state.windowEntries.push({
        id: entryId,
        date: today,
        request,
        changes,
        decisions,
        todos,
      });
    
      state.windowCount = state.windowEntries.length;
      state.nextEntryId = entryId + 1;
    
      // 如果达到 10 条,滚动窗口
      if (state.windowCount >= 10) {
        const windowNum = parseInt(state.windowId.replace('W-', '')) || 1;
        state.windowId = `W-${String(windowNum + 1).padStart(4, '0')}`;
        state.windowStartEntry = entryId + 1;
        state.windowCount = 0;
        state.windowEntries = [];
      }
    
      fs.writeFileSync(statePath, JSON.stringify(state, null, 2), 'utf-8');
    }
    
    private appendTodos(promptsDir: string, todos: string[]) {
      const todosPath = path.join(promptsDir, 'todos.md');
      let content = '';
      if (fs.existsSync(todosPath)) {
        content = fs.readFileSync(todosPath, 'utf-8');
      } else {
        content = `# 待办事项\n\n## 进行中\n\n*(暂无)*\n\n## 已完成\n\n*(暂无)*\n`;
      }
    
      const inProgressMarker = '## 进行中';
      const idx = content.indexOf(inProgressMarker);
      if (idx !== -1) {
        const afterMarker = content.indexOf('\n', idx) + 1;
        const newTodos = todos.map(t => `- [ ] ${t}`).join('\n');
        content = content.slice(0, afterMarker) + `\n${newTodos}` + content.slice(afterMarker);
      }
    
      fs.writeFileSync(todosPath, content, 'utf-8');
    }
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

The description names the storage locations (daily, recent-5, summary-10, log-state.json), providing some insight into side effects. However, it does not disclose potential overwrite behavior, concurrency issues, or required permissions.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Single sentence front-loading the action, but the parenthesized list of destinations is somewhat cryptic. Still, no wasted words.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

No output schema; description lacks information about return value, confirmation, or behavior on duplicate entries. Insufficient for an agent to fully understand the outcome.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the schema already describes parameters. The description adds no extra meaning or context for parameters, resulting in baseline score.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool records a dialog to specific destinations. However, it does not explicitly differentiate from sibling tool 'log_module', which may have similar functionality.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

No guidance on when to use this tool over alternatives like log_module or other siblings. Usage is implied but not clarified.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/thana0623/prompts-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server