mcp-adr-analysis-server 2.13.0 → 2.14.0

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.
@@ -19,7 +19,7 @@ import { getEnhancedModeDefault, getKnowledgeEnhancementDefault, } from './utils
19
19
  import { StateReinforcementManager } from './utils/state-reinforcement-manager.js';
20
20
  import { ConversationMemoryManager } from './utils/conversation-memory-manager.js';
21
21
  import { MemoryEntityManager } from './utils/memory-entity-manager.js';
22
- import { RootManager } from './utils/root-manager.js';
22
+ // RootManager import removed — ADR-023 (#1673)
23
23
  import { ServerContextGenerator } from './utils/server-context-generator.js';
24
24
  import { createNoOpContext } from './types/tool-context.js';
25
25
  import { getToolListForMCP } from './tools/tool-dispatcher.js';
@@ -84,7 +84,7 @@ export class McpAdrAnalysisServer {
84
84
  stateReinforcementManager;
85
85
  conversationMemoryManager;
86
86
  memoryEntityManager;
87
- rootManager;
87
+ // rootManager removed — only consumers were removed host-native handlers, ADR-023 (#1673)
88
88
  contextGenerator;
89
89
  constructor() {
90
90
  // Load and validate configuration
@@ -94,8 +94,7 @@ export class McpAdrAnalysisServer {
94
94
  this.stateReinforcementManager = new StateReinforcementManager(this.kgManager);
95
95
  this.conversationMemoryManager = new ConversationMemoryManager(this.kgManager);
96
96
  this.memoryEntityManager = new MemoryEntityManager();
97
- // Initialize root manager for file access control
98
- this.rootManager = new RootManager(this.config.projectPath, this.config.adrDirectory);
97
+ // rootManager init removed — ADR-023 (#1673)
99
98
  // Initialize server context generator
100
99
  this.contextGenerator = new ServerContextGenerator();
101
100
  // Print configuration summary
@@ -590,157 +589,7 @@ export class McpAdrAnalysisServer {
590
589
  /**
591
590
  * Tool implementations
592
591
  */
593
- async checkAIExecutionStatus(_args) {
594
- try {
595
- const { loadAIConfig, isAIExecutionEnabled } = await import('./config/ai-config.js');
596
- const config = loadAIConfig();
597
- const hasApiKey = !!config.apiKey;
598
- const isEnabled = isAIExecutionEnabled(config);
599
- let reason = undefined;
600
- if (config.executionMode !== 'ce-mcp') {
601
- if (!hasApiKey) {
602
- reason = 'Missing OPENROUTER_API_KEY environment variable';
603
- }
604
- else if (config.executionMode !== 'full') {
605
- reason = `EXECUTION_MODE is '${config.executionMode}', should be 'full'`;
606
- }
607
- }
608
- const status = {
609
- isEnabled,
610
- hasApiKey,
611
- executionMode: config.executionMode,
612
- model: config.defaultModel,
613
- reason,
614
- };
615
- return {
616
- content: [
617
- {
618
- type: 'text',
619
- text: `# AI Execution Status Diagnostic
620
-
621
- > **Note**: As of Phase 5, this server uses CE-MCP (Claude-Enriched MCP) mode by default.
622
- > In CE-MCP mode, tools return orchestration directives that the host LLM executes directly,
623
- > eliminating the need for external API calls. The configuration below applies to legacy mode only.
624
-
625
- ## Current Configuration
626
- - **AI Execution Enabled**: ${status.isEnabled ? '✅ YES' : '❌ NO'}
627
- - **Has API Key**: ${status.hasApiKey ? '✅ YES' : '❌ NO'}
628
- - **Execution Mode**: ${status.executionMode}
629
- - **AI Model**: ${status.model}
630
-
631
- ${
632
- // CE-MCP is the DEFAULT mode and needs no API key. Reporting the absence of
633
- // one as an "Issue Detected" -- immediately after saying no external API call
634
- // is needed -- told every new user to go and buy a key they do not need.
635
- // Legacy configuration is only a problem for someone who asked for it. (#1414)
636
- status.executionMode === 'ce-mcp'
637
- ? `## ✅ Running in CE-MCP mode
638
-
639
- No API key is required. Tools return orchestration directives that your host LLM
640
- executes directly, which is why **AI Execution Enabled: NO** above is expected and
641
- correct rather than a fault.
642
-
643
- Note that only some tools currently return a directive; the rest return their
644
- result directly. See ADR-014 and ADR-021.
645
-
646
- Legacy OpenRouter execution is off by design. You only need the configuration
647
- below if you deliberately want that older path.`
648
- : status.reason
649
- ? `## ⚠️ Issue Detected
650
- **Problem**: ${status.reason}
651
-
652
- ## Solution
653
- ${!status.hasApiKey
654
- ? `
655
- 1. Get an OpenRouter API key from https://openrouter.ai/keys
656
- 2. Add it to your MCP configuration:
657
- \`\`\`json
658
- {
659
- "mcpServers": {
660
- "adr-analysis": {
661
- "command": "mcp-adr-analysis-server",
662
- "env": {
663
- "OPENROUTER_API_KEY": "your_api_key_here",
664
- "EXECUTION_MODE": "full",
665
- "AI_MODEL": "anthropic/claude-3-sonnet"
666
- }
667
- }
668
- }
669
- }
670
- \`\`\`
671
- 3. Restart your MCP client (Claude Desktop, etc.)
672
- `
673
- : status.executionMode !== 'full'
674
- ? `
675
- 1. Update your MCP configuration to set EXECUTION_MODE to "full":
676
- \`\`\`json
677
- {
678
- "mcpServers": {
679
- "adr-analysis": {
680
- "env": {
681
- "EXECUTION_MODE": "full"
682
- }
683
- }
684
- }
685
- }
686
- \`\`\`
687
- 2. Restart your MCP client
688
- `
689
- : ''}`
690
- : `## ✅ Configuration Looks Good!
691
-
692
- AI execution is properly configured. Tools should return actual results instead of prompts.
693
-
694
- If you're still seeing prompts instead of results, try:
695
- 1. Restart your MCP client
696
- 2. Check your OpenRouter API key has sufficient credits
697
- 3. Verify network connectivity to OpenRouter.ai`}
698
-
699
- ${status.executionMode === 'ce-mcp'
700
- ? `## Environment Variables (CE-MCP mode)
701
- No environment variables are required. CE-MCP mode is the default and works
702
- without any API keys or execution-mode flags.
703
-
704
- To switch to legacy OpenRouter execution instead, set:
705
- - **OPENROUTER_API_KEY**: Your OpenRouter API key
706
- - **EXECUTION_MODE**: \`full\`
707
- - **AI_MODEL**: AI model to use (optional, defaults to claude-3-sonnet)`
708
- : `## Environment Variables Expected
709
- - **OPENROUTER_API_KEY**: Your OpenRouter API key
710
- - **EXECUTION_MODE**: Set to "full" for AI execution
711
- - **AI_MODEL**: AI model to use (optional, defaults to claude-3-sonnet)
712
-
713
- ## Testing
714
- After fixing the configuration, try calling \`suggest_adrs\` - it should return actual ADR suggestions instead of prompts.`}
715
- `,
716
- },
717
- ],
718
- };
719
- }
720
- catch (error) {
721
- return {
722
- content: [
723
- {
724
- type: 'text',
725
- text: `# AI Execution Status Check Failed
726
-
727
- **Error**: ${error instanceof Error ? error.message : String(error)}
728
-
729
- This diagnostic tool checks the execution configuration. The server defaults to
730
- CE-MCP mode, which requires no API key — so if you have not set any environment
731
- variables, this error is unlikely to be a configuration problem.
732
-
733
- ## Manual Check (legacy OpenRouter mode only)
734
- If you explicitly set EXECUTION_MODE=full, verify these environment variables:
735
- - OPENROUTER_API_KEY
736
- - EXECUTION_MODE=full
737
- - AI_MODEL (optional)
738
- `,
739
- },
740
- ],
741
- };
742
- }
743
- }
592
+ // checkAIExecutionStatus removed — host-native, ADR-023 (#1673)
744
593
  async getWorkflowGuidance(args) {
745
594
  const { goal, projectContext, availableAssets = [], timeframe = 'thorough_review', primaryConcerns = [], } = args;
746
595
  try {
@@ -3718,146 +3567,11 @@ Please provide:
3718
3567
  process.exit(0);
3719
3568
  });
3720
3569
  }
3721
- /**
3722
- * File system tool implementations
3723
- */
3724
- /**
3725
- * List accessible roots (MCP best practice)
3726
- */
3727
- async listRoots() {
3728
- const roots = this.rootManager.listRoots();
3729
- const rootsList = roots
3730
- .map(r => `### ${r.name}\n\n**Path**: \`${r.path}\`\n\n**Description**: ${r.description}\n`)
3731
- .join('\n');
3732
- return {
3733
- content: [
3734
- {
3735
- type: 'text',
3736
- text: `# Available File System Roots\n\nThese are the directories that can be accessed by this MCP server.\n\n${rootsList}\nUse these paths with \`read_directory\` and \`read_file\` tools to explore and access files.`,
3737
- },
3738
- ],
3739
- };
3740
- }
3741
- /**
3742
- * Read directory contents (MCP best practice for autonomous file discovery)
3743
- */
3744
- async readDirectory(args) {
3745
- const targetPath = args.path || this.config.projectPath;
3746
- try {
3747
- const path = await import('path');
3748
- const resolvedPath = path.resolve(targetPath);
3749
- // Security check: ensure path is within accessible roots
3750
- if (!this.rootManager.isPathAllowed(resolvedPath)) {
3751
- const roots = this.rootManager.listRoots();
3752
- const rootList = roots.map(r => ` - ${r.name}: ${r.path}`).join('\n');
3753
- throw new McpAdrError(`Access denied: Path '${targetPath}' is outside accessible roots.\n\nAccessible roots:\n${rootList}\n\nUse list_roots tool for more details.`, 'ACCESS_DENIED');
3754
- }
3755
- const { readdir } = await import('fs/promises');
3756
- const entries = await readdir(resolvedPath, { withFileTypes: true });
3757
- const files = entries.filter(e => e.isFile()).map(e => `📄 ${e.name}`);
3758
- const dirs = entries.filter(e => e.isDirectory()).map(e => `📁 ${e.name}/`);
3759
- const root = this.rootManager.getRootForPath(resolvedPath);
3760
- const relPath = this.rootManager.getRelativePathFromRoot(resolvedPath);
3761
- const filesList = files.length > 0 ? files.join('\n') : '(no files)';
3762
- const dirsList = dirs.length > 0 ? dirs.join('\n') : '(no directories)';
3763
- return {
3764
- content: [
3765
- {
3766
- type: 'text',
3767
- text: `# Directory: ${targetPath}\n\n**Root**: ${root?.name}\n**Relative Path**: ${relPath || '/'}\n**Full Path**: \`${resolvedPath}\`\n\n## Directories (${dirs.length})\n\n${dirsList}\n\n## Files (${files.length})\n\n${filesList}`,
3768
- },
3769
- ],
3770
- };
3771
- }
3772
- catch (error) {
3773
- throw new McpAdrError(`Failed to read directory: ${error instanceof Error ? error.message : String(error)}`, 'DIRECTORY_READ_ERROR');
3774
- }
3775
- }
3776
- async readFile(args) {
3777
- const { filePath } = args;
3778
- try {
3779
- const fs = await import('fs/promises');
3780
- const path = await import('path');
3781
- // Resolve path (handle both relative and absolute paths)
3782
- const safePath = path.resolve(filePath);
3783
- // Security check: ensure path is within accessible roots
3784
- if (!this.rootManager.isPathAllowed(safePath)) {
3785
- const roots = this.rootManager.listRoots();
3786
- const rootList = roots.map(r => ` - ${r.name}: ${r.path}`).join('\n');
3787
- throw new McpAdrError(`Access denied: Path '${filePath}' is outside accessible roots.\n\nAccessible roots:\n${rootList}\n\nUse list_roots tool for more details.`, 'ACCESS_DENIED');
3788
- }
3789
- const content = await fs.readFile(safePath, 'utf-8');
3790
- return {
3791
- content: [
3792
- {
3793
- type: 'text',
3794
- text: content,
3795
- },
3796
- ],
3797
- };
3798
- }
3799
- catch (error) {
3800
- throw new McpAdrError(`Failed to read file: ${error instanceof Error ? error.message : String(error)}`, 'FILE_READ_ERROR');
3801
- }
3802
- }
3803
- async writeFile(args) {
3804
- const { filePath, content } = args;
3805
- try {
3806
- const fs = await import('fs/promises');
3807
- const path = await import('path');
3808
- // Resolve path relative to project path for security
3809
- const safePath = path.resolve(this.config.projectPath, filePath);
3810
- // Security check: ensure path is within project directory
3811
- if (!safePath.startsWith(this.config.projectPath)) {
3812
- throw new McpAdrError('Access denied: Path is outside project directory', 'ACCESS_DENIED');
3813
- }
3814
- // Ensure directory exists
3815
- await fs.mkdir(path.dirname(safePath), { recursive: true });
3816
- // Write file
3817
- await fs.writeFile(safePath, content, 'utf-8');
3818
- return {
3819
- content: [
3820
- {
3821
- type: 'text',
3822
- text: `Successfully wrote to ${filePath}`,
3823
- },
3824
- ],
3825
- };
3826
- }
3827
- catch (error) {
3828
- throw new McpAdrError(`Failed to write file: ${error instanceof Error ? error.message : String(error)}`, 'FILE_WRITE_ERROR');
3829
- }
3830
- }
3831
- async listDirectory(args) {
3832
- const { path: dirPath } = args;
3833
- try {
3834
- const fs = await import('fs/promises');
3835
- const path = await import('path');
3836
- // Resolve path relative to project path for security
3837
- const safePath = path.resolve(this.config.projectPath, dirPath);
3838
- // Security check: ensure path is within project directory
3839
- if (!safePath.startsWith(this.config.projectPath)) {
3840
- throw new McpAdrError('Access denied: Path is outside project directory', 'ACCESS_DENIED');
3841
- }
3842
- const entries = await fs.readdir(safePath, { withFileTypes: true });
3843
- const fileList = entries.map(entry => ({
3844
- name: entry.name,
3845
- type: entry.isDirectory() ? 'directory' : 'file',
3846
- path: path.join(dirPath, entry.name),
3847
- }));
3848
- return {
3849
- content: [
3850
- {
3851
- type: 'text',
3852
- text: JSON.stringify(fileList, null, 2),
3853
- },
3854
- ],
3855
- };
3856
- }
3857
- catch (error) {
3858
- throw new McpAdrError(`Failed to list directory: ${error instanceof Error ? error.message : String(error)}`, 'DIRECTORY_LIST_ERROR');
3859
- }
3860
- }
3570
+ // listRoots removed — host-native, ADR-023 (#1673)
3571
+ // readDirectory removed — host-native, ADR-023 (#1673)
3572
+ // readFile removed — host-native, ADR-023 (#1673)
3573
+ // writeFile removed — host-native, ADR-023 (#1673)
3574
+ // listDirectory removed — host-native, ADR-023 (#1673)
3861
3575
  async generateDeploymentGuidance(args) {
3862
3576
  try {
3863
3577
  const { generateDeploymentGuidance } = await import('./tools/deployment-guidance-tool.js');
@@ -4062,113 +3776,7 @@ This tool has been deprecated and replaced with memory-centric health scoring.
4062
3776
  throw new McpAdrError(`Server context generation failed: ${error instanceof Error ? error.message : String(error)}`, 'SERVER_CONTEXT_ERROR');
4063
3777
  }
4064
3778
  }
4065
- /**
4066
- * Get Current Datetime Tool
4067
- * Returns the current date and time in various formats for ADR generation and timestamping
4068
- */
4069
- async getCurrentDatetime(args) {
4070
- const { timezone = 'UTC', format = 'all', includeTimestamp = true } = args;
4071
- try {
4072
- const now = new Date();
4073
- // Format date for different timezones
4074
- const formatOptions = {
4075
- timeZone: timezone,
4076
- year: 'numeric',
4077
- month: '2-digit',
4078
- day: '2-digit',
4079
- hour: '2-digit',
4080
- minute: '2-digit',
4081
- second: '2-digit',
4082
- hour12: false,
4083
- };
4084
- // Get date parts for the specified timezone
4085
- const formatter = new Intl.DateTimeFormat('en-CA', {
4086
- timeZone: timezone,
4087
- year: 'numeric',
4088
- month: '2-digit',
4089
- day: '2-digit',
4090
- });
4091
- const parts = formatter.formatToParts(now);
4092
- const year = parts.find(p => p.type === 'year')?.value || '';
4093
- const month = parts.find(p => p.type === 'month')?.value || '';
4094
- const day = parts.find(p => p.type === 'day')?.value || '';
4095
- // ADR date format: YYYY-MM-DD
4096
- const adrDate = `${year}-${month}-${day}`;
4097
- // ISO 8601 format
4098
- const isoDate = now.toISOString();
4099
- // Human-readable format
4100
- const humanFormatter = new Intl.DateTimeFormat('en-US', {
4101
- ...formatOptions,
4102
- weekday: 'long',
4103
- month: 'long',
4104
- });
4105
- const humanDate = humanFormatter.format(now);
4106
- // Build response based on requested format
4107
- let responseData = {};
4108
- switch (format) {
4109
- case 'iso':
4110
- responseData = { iso: isoDate };
4111
- break;
4112
- case 'human':
4113
- responseData = { human: humanDate };
4114
- break;
4115
- case 'adr':
4116
- responseData = { adr: adrDate };
4117
- break;
4118
- case 'all':
4119
- default:
4120
- responseData = {
4121
- iso: isoDate,
4122
- human: humanDate,
4123
- adr: adrDate,
4124
- timezone: timezone,
4125
- };
4126
- }
4127
- if (includeTimestamp) {
4128
- responseData['timestamp'] = now.getTime();
4129
- }
4130
- const responseText = `# Current Date and Time
4131
-
4132
- ## Formats
4133
- ${format === 'all' || format === 'iso' ? `- **ISO 8601**: ${isoDate}` : ''}
4134
- ${format === 'all' || format === 'human' ? `- **Human Readable**: ${humanDate}` : ''}
4135
- ${format === 'all' || format === 'adr' ? `- **ADR Date (YYYY-MM-DD)**: ${adrDate}` : ''}
4136
- ${format === 'all' ? `- **Timezone**: ${timezone}` : ''}
4137
- ${includeTimestamp ? `- **Unix Timestamp (ms)**: ${now.getTime()}` : ''}
4138
-
4139
- ## Usage for ADR Generation
4140
-
4141
- When generating ADRs, use the ADR date format (\`${adrDate}\`) in the Date field:
4142
-
4143
- \`\`\`markdown
4144
- # ADR-XXXX: [Title]
4145
-
4146
- ## Date
4147
- ${adrDate}
4148
-
4149
- ## Status
4150
- Proposed
4151
- \`\`\`
4152
-
4153
- ## Raw Data
4154
-
4155
- \`\`\`json
4156
- ${JSON.stringify(responseData, null, 2)}
4157
- \`\`\`
4158
- `;
4159
- return {
4160
- content: [
4161
- {
4162
- type: 'text',
4163
- text: responseText,
4164
- },
4165
- ],
4166
- };
4167
- }
4168
- catch (error) {
4169
- throw new McpAdrError(`Failed to get current datetime: ${error instanceof Error ? error.message : String(error)}`, 'DATETIME_ERROR');
4170
- }
4171
- }
3779
+ // getCurrentDatetime removed — host-native, ADR-023 (#1673)
4172
3780
  /**
4173
3781
  * Set Project Path Tool - Dynamic Session Configuration
4174
3782
  *
@@ -4194,8 +3802,7 @@ ${JSON.stringify(responseData, null, 2)}
4194
3802
  }
4195
3803
  // Update the server configuration
4196
3804
  this.config.projectPath = absolutePath;
4197
- // Re-initialize the RootManager with the new path
4198
- this.rootManager = new RootManager(absolutePath, this.config.adrDirectory);
3805
+ // rootManager re-init removed — ADR-023 (#1673)
4199
3806
  this.logger.info(`Project path changed: ${previousPath} -> ${absolutePath}`);
4200
3807
  const responseText = `# Project Path Updated
4201
3808
 
@@ -4227,112 +3834,5 @@ To make this permanent, set the \`PROJECT_PATH\` environment variable.
4227
3834
  throw new McpAdrError(`Failed to set project path: ${error instanceof Error ? error.message : String(error)}`, 'SET_PROJECT_PATH_ERROR');
4228
3835
  }
4229
3836
  }
4230
- /**
4231
- * Load Prompt Tool - CE-MCP Phase 4 Lazy Loading
4232
- *
4233
- * @description Loads prompts on-demand instead of eagerly loading all prompts at startup.
4234
- * This reduces token usage by ~96% (from 28K tokens to ~1K on-demand).
4235
- *
4236
- * @param args - Load prompt arguments
4237
- * @param args.promptName - Name of the prompt to load
4238
- * @param args.section - Optional section within the prompt
4239
- * @param args.estimateOnly - If true, returns only token estimate
4240
- * @returns Promise<CallToolResult> - Loaded prompt content or estimate
4241
- */
4242
- async loadPrompt(args) {
4243
- const { promptName, section, estimateOnly = false } = args;
4244
- // Import the prompt catalog dynamically to enable lazy loading
4245
- const { getPromptLoader, getPromptMetadata, calculateTokenSavings } = await import('./prompts/prompt-catalog.js');
4246
- const metadata = getPromptMetadata(promptName);
4247
- if (!metadata) {
4248
- const availablePrompts = [
4249
- 'adr-suggestion',
4250
- 'deployment-analysis',
4251
- 'environment-analysis',
4252
- 'research-question',
4253
- 'rule-generation',
4254
- 'analysis',
4255
- 'research-integration',
4256
- 'validated-pattern',
4257
- 'security',
4258
- ];
4259
- return {
4260
- content: [
4261
- {
4262
- type: 'text',
4263
- text: JSON.stringify({
4264
- success: false,
4265
- error: `Unknown prompt: ${promptName}`,
4266
- availablePrompts,
4267
- hint: 'Use one of the available prompt names listed above',
4268
- }, null, 2),
4269
- },
4270
- ],
4271
- isError: true,
4272
- };
4273
- }
4274
- // If estimate only, return token information without loading
4275
- if (estimateOnly) {
4276
- const loader = getPromptLoader();
4277
- const estimatedTokens = loader.getEstimatedTokens(promptName, section);
4278
- const savings = calculateTokenSavings([promptName]);
4279
- return {
4280
- content: [
4281
- {
4282
- type: 'text',
4283
- text: JSON.stringify({
4284
- success: true,
4285
- promptName,
4286
- section: section || 'full',
4287
- estimatedTokens,
4288
- metadata: {
4289
- file: metadata.file,
4290
- category: metadata.category,
4291
- totalTokens: metadata.tokens,
4292
- availableSections: metadata.sections,
4293
- loadOnDemand: metadata.loadOnDemand,
4294
- },
4295
- tokenSavings: {
4296
- loaded: savings.loaded,
4297
- total: savings.total,
4298
- saved: savings.saved,
4299
- percentage: `${savings.percentage}%`,
4300
- },
4301
- }, null, 2),
4302
- },
4303
- ],
4304
- };
4305
- }
4306
- // Load the prompt content
4307
- try {
4308
- const loader = getPromptLoader();
4309
- const content = await loader.loadPrompt(promptName, section);
4310
- return {
4311
- content: [
4312
- {
4313
- type: 'text',
4314
- text: `# Loaded Prompt: ${promptName}${section ? ` (section: ${section})` : ''}
4315
-
4316
- ## Metadata
4317
- - **Category**: ${metadata.category}
4318
- - **Estimated Tokens**: ${metadata.tokens}
4319
- - **Available Sections**: ${metadata.sections.join(', ')}
4320
-
4321
- ## Content
4322
-
4323
- ${content}
4324
-
4325
- ---
4326
-
4327
- *Loaded on-demand via CE-MCP lazy loading system. Token savings: ~96% compared to eager loading all prompts.*
4328
- `,
4329
- },
4330
- ],
4331
- };
4332
- }
4333
- catch (error) {
4334
- throw new McpAdrError(`Failed to load prompt '${promptName}'${section ? ` section '${section}'` : ''}: ${error instanceof Error ? error.message : String(error)}`, 'PROMPT_LOAD_ERROR');
4335
- }
4336
- }
4337
3837
  }
4338
3838
  //# sourceMappingURL=mcp-adr-analysis-server.js.map