@harness-engineering/mcp-server 0.5.0 → 0.5.1

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.
@@ -1,6 +1,7 @@
1
1
  export async function getStateResource(projectRoot) {
2
2
  try {
3
- const { loadState } = await import('@harness-engineering/core');
3
+ const { loadState, migrateToStreams } = await import('@harness-engineering/core');
4
+ await migrateToStreams(projectRoot);
4
5
  const result = await loadState(projectRoot);
5
6
  if (result.ok) {
6
7
  return JSON.stringify(result.value, null, 2);
@@ -5,6 +5,7 @@ import { validateToolDefinition, handleValidateProject } from './tools/validate.
5
5
  import { checkDependenciesDefinition, handleCheckDependencies } from './tools/architecture.js';
6
6
  import { checkDocsDefinition, handleCheckDocs, validateKnowledgeMapDefinition, handleValidateKnowledgeMap, } from './tools/docs.js';
7
7
  import { detectEntropyDefinition, handleDetectEntropy, applyFixesDefinition, handleApplyFixes, } from './tools/entropy.js';
8
+ import { checkPerformanceDefinition, handleCheckPerformance, getPerfBaselinesDefinition, handleGetPerfBaselines, updatePerfBaselinesDefinition, handleUpdatePerfBaselines, getCriticalPathsDefinition, handleGetCriticalPaths, } from './tools/performance.js';
8
9
  import { generateLinterDefinition, handleGenerateLinter, validateLinterConfigDefinition, handleValidateLinterConfig, } from './tools/linter.js';
9
10
  import { initProjectDefinition, handleInitProject } from './tools/init.js';
10
11
  import { listPersonasDefinition, handleListPersonas, generatePersonaArtifactsDefinition, handleGeneratePersonaArtifacts, runPersonaDefinition, handleRunPersona, } from './tools/persona.js';
@@ -14,7 +15,7 @@ import { getSkillsResource } from './resources/skills.js';
14
15
  import { getRulesResource } from './resources/rules.js';
15
16
  import { getProjectResource } from './resources/project.js';
16
17
  import { getLearningsResource } from './resources/learnings.js';
17
- import { manageStateDefinition, handleManageState, manageHandoffDefinition, handleManageHandoff, } from './tools/state.js';
18
+ import { manageStateDefinition, handleManageState, manageHandoffDefinition, handleManageHandoff, listStreamsDefinition, handleListStreams, } from './tools/state.js';
18
19
  import { createSelfReviewDefinition, handleCreateSelfReview, analyzeDiffDefinition, handleAnalyzeDiff, requestPeerReviewDefinition, handleRequestPeerReview, } from './tools/feedback.js';
19
20
  import { checkPhaseGateDefinition, handleCheckPhaseGate } from './tools/phase-gate.js';
20
21
  import { validateCrossCheckDefinition, handleValidateCrossCheck } from './tools/cross-check.js';
@@ -23,6 +24,7 @@ import { getStateResource } from './resources/state.js';
23
24
  import { queryGraphDefinition, handleQueryGraph, searchSimilarDefinition, handleSearchSimilar, findContextForDefinition, handleFindContextFor, getRelationshipsDefinition, handleGetRelationships, getImpactDefinition, handleGetImpact, ingestSourceDefinition, handleIngestSource, } from './tools/graph.js';
24
25
  import { getGraphResource, getEntitiesResource, getRelationshipsResource, } from './resources/graph.js';
25
26
  import { generateAgentDefinitionsDefinition, handleGenerateAgentDefinitions, } from './tools/agent-definitions.js';
27
+ import { runSecurityScanDefinition, handleRunSecurityScan } from './tools/security.js';
26
28
  const TOOL_DEFINITIONS = [
27
29
  validateToolDefinition,
28
30
  checkDependenciesDefinition,
@@ -55,6 +57,12 @@ const TOOL_DEFINITIONS = [
55
57
  getImpactDefinition,
56
58
  ingestSourceDefinition,
57
59
  generateAgentDefinitionsDefinition,
60
+ runSecurityScanDefinition,
61
+ checkPerformanceDefinition,
62
+ getPerfBaselinesDefinition,
63
+ updatePerfBaselinesDefinition,
64
+ getCriticalPathsDefinition,
65
+ listStreamsDefinition,
58
66
  ];
59
67
  const TOOL_HANDLERS = {
60
68
  validate_project: handleValidateProject,
@@ -88,6 +96,12 @@ const TOOL_HANDLERS = {
88
96
  get_impact: handleGetImpact,
89
97
  ingest_source: handleIngestSource,
90
98
  generate_agent_definitions: handleGenerateAgentDefinitions,
99
+ run_security_scan: handleRunSecurityScan,
100
+ check_performance: handleCheckPerformance,
101
+ get_perf_baselines: handleGetPerfBaselines,
102
+ update_perf_baselines: handleUpdatePerfBaselines,
103
+ get_critical_paths: handleGetCriticalPaths,
104
+ list_streams: handleListStreams,
91
105
  };
92
106
  const RESOURCE_DEFINITIONS = [
93
107
  {
@@ -0,0 +1,115 @@
1
+ export declare const checkPerformanceDefinition: {
2
+ name: string;
3
+ description: string;
4
+ inputSchema: {
5
+ type: "object";
6
+ properties: {
7
+ path: {
8
+ type: string;
9
+ description: string;
10
+ };
11
+ type: {
12
+ type: string;
13
+ enum: string[];
14
+ description: string;
15
+ };
16
+ };
17
+ required: string[];
18
+ };
19
+ };
20
+ export declare function handleCheckPerformance(input: {
21
+ path: string;
22
+ type?: string;
23
+ }): Promise<import("../utils/result-adapter.js").McpToolResponse>;
24
+ export declare const getPerfBaselinesDefinition: {
25
+ name: string;
26
+ description: string;
27
+ inputSchema: {
28
+ type: "object";
29
+ properties: {
30
+ path: {
31
+ type: string;
32
+ description: string;
33
+ };
34
+ };
35
+ required: string[];
36
+ };
37
+ };
38
+ export declare function handleGetPerfBaselines(input: {
39
+ path: string;
40
+ }): Promise<import("../utils/result-adapter.js").McpToolResponse>;
41
+ export declare const updatePerfBaselinesDefinition: {
42
+ name: string;
43
+ description: string;
44
+ inputSchema: {
45
+ type: "object";
46
+ properties: {
47
+ path: {
48
+ type: string;
49
+ description: string;
50
+ };
51
+ commitHash: {
52
+ type: string;
53
+ description: string;
54
+ };
55
+ results: {
56
+ type: string;
57
+ description: string;
58
+ items: {
59
+ type: string;
60
+ properties: {
61
+ name: {
62
+ type: string;
63
+ };
64
+ file: {
65
+ type: string;
66
+ };
67
+ opsPerSec: {
68
+ type: string;
69
+ };
70
+ meanMs: {
71
+ type: string;
72
+ };
73
+ p99Ms: {
74
+ type: string;
75
+ };
76
+ marginOfError: {
77
+ type: string;
78
+ };
79
+ };
80
+ required: string[];
81
+ };
82
+ };
83
+ };
84
+ required: string[];
85
+ };
86
+ };
87
+ export declare function handleUpdatePerfBaselines(input: {
88
+ path: string;
89
+ commitHash: string;
90
+ results: Array<{
91
+ name: string;
92
+ file: string;
93
+ opsPerSec: number;
94
+ meanMs: number;
95
+ p99Ms: number;
96
+ marginOfError: number;
97
+ }>;
98
+ }): Promise<import("../utils/result-adapter.js").McpToolResponse>;
99
+ export declare const getCriticalPathsDefinition: {
100
+ name: string;
101
+ description: string;
102
+ inputSchema: {
103
+ type: "object";
104
+ properties: {
105
+ path: {
106
+ type: string;
107
+ description: string;
108
+ };
109
+ };
110
+ required: string[];
111
+ };
112
+ };
113
+ export declare function handleGetCriticalPaths(input: {
114
+ path: string;
115
+ }): Promise<import("../utils/result-adapter.js").McpToolResponse>;
@@ -0,0 +1,175 @@
1
+ import * as path from 'path';
2
+ import { Ok } from '@harness-engineering/core';
3
+ import { resultToMcpResponse } from '../utils/result-adapter.js';
4
+ export const checkPerformanceDefinition = {
5
+ name: 'check_performance',
6
+ description: 'Run performance checks: structural complexity, coupling metrics, and size budgets',
7
+ inputSchema: {
8
+ type: 'object',
9
+ properties: {
10
+ path: { type: 'string', description: 'Path to project root' },
11
+ type: {
12
+ type: 'string',
13
+ enum: ['structural', 'coupling', 'size', 'all'],
14
+ description: 'Type of performance check (default: all)',
15
+ },
16
+ },
17
+ required: ['path'],
18
+ },
19
+ };
20
+ export async function handleCheckPerformance(input) {
21
+ try {
22
+ const { EntropyAnalyzer } = await import('@harness-engineering/core');
23
+ const typeFilter = input.type ?? 'all';
24
+ const analyzer = new EntropyAnalyzer({
25
+ rootDir: path.resolve(input.path),
26
+ analyze: {
27
+ complexity: typeFilter === 'all' || typeFilter === 'structural',
28
+ coupling: typeFilter === 'all' || typeFilter === 'coupling',
29
+ sizeBudget: typeFilter === 'all' || typeFilter === 'size',
30
+ },
31
+ });
32
+ // Load graph data if available
33
+ let graphOptions;
34
+ try {
35
+ const { loadGraphStore } = await import('../utils/graph-loader.js');
36
+ const store = await loadGraphStore(path.resolve(input.path));
37
+ if (store) {
38
+ const { GraphComplexityAdapter, GraphCouplingAdapter } = await import('@harness-engineering/graph');
39
+ const complexityAdapter = new GraphComplexityAdapter(store);
40
+ const couplingAdapter = new GraphCouplingAdapter(store);
41
+ graphOptions = {
42
+ graphComplexityData: complexityAdapter.computeComplexityHotspots(),
43
+ graphCouplingData: couplingAdapter.computeCouplingData(),
44
+ };
45
+ }
46
+ }
47
+ catch {
48
+ // Graph not available — proceed without
49
+ }
50
+ const result = await analyzer.analyze(graphOptions);
51
+ return resultToMcpResponse(result);
52
+ }
53
+ catch (error) {
54
+ return {
55
+ content: [
56
+ {
57
+ type: 'text',
58
+ text: `Error: ${error instanceof Error ? error.message : String(error)}`,
59
+ },
60
+ ],
61
+ isError: true,
62
+ };
63
+ }
64
+ }
65
+ // --- get_perf_baselines ---
66
+ export const getPerfBaselinesDefinition = {
67
+ name: 'get_perf_baselines',
68
+ description: 'Read current performance baselines from .harness/perf/baselines.json',
69
+ inputSchema: {
70
+ type: 'object',
71
+ properties: {
72
+ path: { type: 'string', description: 'Path to project root' },
73
+ },
74
+ required: ['path'],
75
+ },
76
+ };
77
+ export async function handleGetPerfBaselines(input) {
78
+ try {
79
+ const { BaselineManager } = await import('@harness-engineering/core');
80
+ const manager = new BaselineManager(path.resolve(input.path));
81
+ const baselines = manager.load();
82
+ return resultToMcpResponse(Ok(baselines ?? { version: 1, updatedAt: '', updatedFrom: '', benchmarks: {} }));
83
+ }
84
+ catch (error) {
85
+ return {
86
+ content: [
87
+ {
88
+ type: 'text',
89
+ text: `Error: ${error instanceof Error ? error.message : String(error)}`,
90
+ },
91
+ ],
92
+ isError: true,
93
+ };
94
+ }
95
+ }
96
+ // --- update_perf_baselines ---
97
+ export const updatePerfBaselinesDefinition = {
98
+ name: 'update_perf_baselines',
99
+ description: 'Update performance baselines from benchmark results. Run benchmarks first via CLI.',
100
+ inputSchema: {
101
+ type: 'object',
102
+ properties: {
103
+ path: { type: 'string', description: 'Path to project root' },
104
+ commitHash: { type: 'string', description: 'Current commit hash for baseline tracking' },
105
+ results: {
106
+ type: 'array',
107
+ description: 'Array of benchmark results to save as baselines',
108
+ items: {
109
+ type: 'object',
110
+ properties: {
111
+ name: { type: 'string' },
112
+ file: { type: 'string' },
113
+ opsPerSec: { type: 'number' },
114
+ meanMs: { type: 'number' },
115
+ p99Ms: { type: 'number' },
116
+ marginOfError: { type: 'number' },
117
+ },
118
+ required: ['name', 'file', 'opsPerSec', 'meanMs', 'p99Ms', 'marginOfError'],
119
+ },
120
+ },
121
+ },
122
+ required: ['path', 'commitHash', 'results'],
123
+ },
124
+ };
125
+ export async function handleUpdatePerfBaselines(input) {
126
+ try {
127
+ const { BaselineManager } = await import('@harness-engineering/core');
128
+ const manager = new BaselineManager(path.resolve(input.path));
129
+ manager.save(input.results, input.commitHash);
130
+ const updated = manager.load();
131
+ return resultToMcpResponse(Ok(updated));
132
+ }
133
+ catch (error) {
134
+ return {
135
+ content: [
136
+ {
137
+ type: 'text',
138
+ text: `Error: ${error instanceof Error ? error.message : String(error)}`,
139
+ },
140
+ ],
141
+ isError: true,
142
+ };
143
+ }
144
+ }
145
+ // --- get_critical_paths ---
146
+ export const getCriticalPathsDefinition = {
147
+ name: 'get_critical_paths',
148
+ description: 'List performance-critical functions from @perf-critical annotations and graph inference',
149
+ inputSchema: {
150
+ type: 'object',
151
+ properties: {
152
+ path: { type: 'string', description: 'Path to project root' },
153
+ },
154
+ required: ['path'],
155
+ },
156
+ };
157
+ export async function handleGetCriticalPaths(input) {
158
+ try {
159
+ const { CriticalPathResolver } = await import('@harness-engineering/core');
160
+ const resolver = new CriticalPathResolver(path.resolve(input.path));
161
+ const result = await resolver.resolve();
162
+ return resultToMcpResponse(Ok(result));
163
+ }
164
+ catch (error) {
165
+ return {
166
+ content: [
167
+ {
168
+ type: 'text',
169
+ text: `Error: ${error instanceof Error ? error.message : String(error)}`,
170
+ },
171
+ ],
172
+ isError: true,
173
+ };
174
+ }
175
+ }
@@ -0,0 +1,42 @@
1
+ export declare const runSecurityScanDefinition: {
2
+ name: string;
3
+ description: string;
4
+ inputSchema: {
5
+ type: "object";
6
+ properties: {
7
+ path: {
8
+ type: string;
9
+ description: string;
10
+ };
11
+ files: {
12
+ type: string;
13
+ items: {
14
+ type: string;
15
+ };
16
+ description: string;
17
+ };
18
+ strict: {
19
+ type: string;
20
+ description: string;
21
+ };
22
+ };
23
+ required: string[];
24
+ };
25
+ };
26
+ export declare function handleRunSecurityScan(input: {
27
+ path: string;
28
+ files?: string[];
29
+ strict?: boolean;
30
+ }): Promise<{
31
+ content: {
32
+ type: "text";
33
+ text: string;
34
+ }[];
35
+ isError?: undefined;
36
+ } | {
37
+ content: {
38
+ type: "text";
39
+ text: string;
40
+ }[];
41
+ isError: boolean;
42
+ }>;
@@ -0,0 +1,87 @@
1
+ import * as path from 'path';
2
+ // ============ run_security_scan ============
3
+ export const runSecurityScanDefinition = {
4
+ name: 'run_security_scan',
5
+ description: 'Run the built-in security scanner on a project or specific files. Detects secrets, injection, XSS, weak crypto, and other vulnerabilities.',
6
+ inputSchema: {
7
+ type: 'object',
8
+ properties: {
9
+ path: { type: 'string', description: 'Path to project root' },
10
+ files: {
11
+ type: 'array',
12
+ items: { type: 'string' },
13
+ description: 'Optional list of specific files to scan. If omitted, scans all source files.',
14
+ },
15
+ strict: {
16
+ type: 'boolean',
17
+ description: 'Override strict mode — promotes all warnings to errors',
18
+ },
19
+ },
20
+ required: ['path'],
21
+ },
22
+ };
23
+ export async function handleRunSecurityScan(input) {
24
+ try {
25
+ const core = await import('@harness-engineering/core');
26
+ const projectRoot = path.resolve(input.path);
27
+ // Load config from project
28
+ let configData = {};
29
+ try {
30
+ const fs = await import('node:fs');
31
+ const configPath = path.join(projectRoot, 'harness.config.json');
32
+ if (fs.existsSync(configPath)) {
33
+ const raw = fs.readFileSync(configPath, 'utf-8');
34
+ const parsed = JSON.parse(raw);
35
+ configData = parsed.security ?? {};
36
+ }
37
+ }
38
+ catch {
39
+ // No config file — use defaults
40
+ }
41
+ // Apply overrides
42
+ if (input.strict !== undefined) {
43
+ configData.strict = input.strict;
44
+ }
45
+ const securityConfig = core.parseSecurityConfig(configData);
46
+ const scanner = new core.SecurityScanner(securityConfig);
47
+ scanner.configureForProject(projectRoot);
48
+ let filesToScan;
49
+ if (input.files && input.files.length > 0) {
50
+ filesToScan = input.files.map((f) => path.resolve(projectRoot, f));
51
+ }
52
+ else {
53
+ // Use core's glob utility to find source files
54
+ const { globFiles } = await import('../utils/glob-helper.js');
55
+ filesToScan = await globFiles(projectRoot, securityConfig.exclude);
56
+ }
57
+ const result = await scanner.scanFiles(filesToScan);
58
+ return {
59
+ content: [
60
+ {
61
+ type: 'text',
62
+ text: JSON.stringify({
63
+ ...result,
64
+ summary: {
65
+ errors: result.findings.filter((f) => f.severity === 'error')
66
+ .length,
67
+ warnings: result.findings.filter((f) => f.severity === 'warning').length,
68
+ info: result.findings.filter((f) => f.severity === 'info')
69
+ .length,
70
+ },
71
+ }),
72
+ },
73
+ ],
74
+ };
75
+ }
76
+ catch (error) {
77
+ return {
78
+ content: [
79
+ {
80
+ type: 'text',
81
+ text: `Error: ${error instanceof Error ? error.message : String(error)}`,
82
+ },
83
+ ],
84
+ isError: true,
85
+ };
86
+ }
87
+ }
@@ -33,6 +33,10 @@ export declare const manageStateDefinition: {
33
33
  type: string;
34
34
  description: string;
35
35
  };
36
+ stream: {
37
+ type: string;
38
+ description: string;
39
+ };
36
40
  };
37
41
  required: string[];
38
42
  };
@@ -45,6 +49,7 @@ export declare function handleManageState(input: {
45
49
  outcome?: string;
46
50
  description?: string;
47
51
  failureType?: string;
52
+ stream?: string;
48
53
  }): Promise<import("../utils/result-adapter.js").McpToolResponse>;
49
54
  export declare const manageHandoffDefinition: {
50
55
  name: string;
@@ -65,6 +70,10 @@ export declare const manageHandoffDefinition: {
65
70
  type: string;
66
71
  description: string;
67
72
  };
73
+ stream: {
74
+ type: string;
75
+ description: string;
76
+ };
68
77
  };
69
78
  required: string[];
70
79
  };
@@ -73,4 +82,22 @@ export declare function handleManageHandoff(input: {
73
82
  path: string;
74
83
  action: 'save' | 'load';
75
84
  handoff?: unknown;
85
+ stream?: string;
86
+ }): Promise<import("../utils/result-adapter.js").McpToolResponse>;
87
+ export declare const listStreamsDefinition: {
88
+ name: string;
89
+ description: string;
90
+ inputSchema: {
91
+ type: "object";
92
+ properties: {
93
+ path: {
94
+ type: string;
95
+ description: string;
96
+ };
97
+ };
98
+ required: string[];
99
+ };
100
+ };
101
+ export declare function handleListStreams(input: {
102
+ path: string;
76
103
  }): Promise<import("../utils/result-adapter.js").McpToolResponse>;
@@ -19,6 +19,10 @@ export const manageStateDefinition = {
19
19
  outcome: { type: 'string', description: 'Outcome associated with the learning' },
20
20
  description: { type: 'string', description: 'Failure description (required for failure)' },
21
21
  failureType: { type: 'string', description: 'Type of failure (required for failure)' },
22
+ stream: {
23
+ type: 'string',
24
+ description: 'Stream name to target (auto-resolves from branch if omitted)',
25
+ },
22
26
  },
23
27
  required: ['path', 'action'],
24
28
  },
@@ -29,7 +33,7 @@ export async function handleManageState(input) {
29
33
  const projectPath = path.resolve(input.path);
30
34
  switch (input.action) {
31
35
  case 'show': {
32
- const result = await loadState(projectPath);
36
+ const result = await loadState(projectPath, input.stream);
33
37
  return resultToMcpResponse(result);
34
38
  }
35
39
  case 'learn': {
@@ -41,7 +45,7 @@ export async function handleManageState(input) {
41
45
  isError: true,
42
46
  };
43
47
  }
44
- const result = await appendLearning(projectPath, input.learning, input.skillName, input.outcome);
48
+ const result = await appendLearning(projectPath, input.learning, input.skillName, input.outcome, input.stream);
45
49
  if (!result.ok)
46
50
  return resultToMcpResponse(result);
47
51
  return resultToMcpResponse(Ok({ recorded: true }));
@@ -66,19 +70,19 @@ export async function handleManageState(input) {
66
70
  isError: true,
67
71
  };
68
72
  }
69
- const result = await appendFailure(projectPath, input.description, input.skillName ?? 'unknown', input.failureType);
73
+ const result = await appendFailure(projectPath, input.description, input.skillName ?? 'unknown', input.failureType, input.stream);
70
74
  if (!result.ok)
71
75
  return resultToMcpResponse(result);
72
76
  return resultToMcpResponse(Ok({ recorded: true }));
73
77
  }
74
78
  case 'archive': {
75
- const result = await archiveFailures(projectPath);
79
+ const result = await archiveFailures(projectPath, input.stream);
76
80
  if (!result.ok)
77
81
  return resultToMcpResponse(result);
78
82
  return resultToMcpResponse(Ok({ archived: true }));
79
83
  }
80
84
  case 'reset': {
81
- const result = await saveState(projectPath, { ...DEFAULT_STATE });
85
+ const result = await saveState(projectPath, { ...DEFAULT_STATE }, input.stream);
82
86
  if (!result.ok)
83
87
  return resultToMcpResponse(result);
84
88
  return resultToMcpResponse(Ok({ reset: true }));
@@ -121,6 +125,10 @@ export const manageHandoffDefinition = {
121
125
  description: 'Action to perform',
122
126
  },
123
127
  handoff: { type: 'object', description: 'Handoff data to save (required for save)' },
128
+ stream: {
129
+ type: 'string',
130
+ description: 'Stream name to target (auto-resolves from branch if omitted)',
131
+ },
124
132
  },
125
133
  required: ['path', 'action'],
126
134
  },
@@ -139,11 +147,11 @@ export async function handleManageHandoff(input) {
139
147
  isError: true,
140
148
  };
141
149
  }
142
- const result = await saveHandoff(projectPath, input.handoff);
150
+ const result = await saveHandoff(projectPath, input.handoff, input.stream);
143
151
  return resultToMcpResponse(result.ok ? Ok({ saved: true }) : result);
144
152
  }
145
153
  case 'load': {
146
- const result = await loadHandoff(projectPath);
154
+ const result = await loadHandoff(projectPath, input.stream);
147
155
  return resultToMcpResponse(result);
148
156
  }
149
157
  default: {
@@ -166,3 +174,40 @@ export async function handleManageHandoff(input) {
166
174
  };
167
175
  }
168
176
  }
177
+ // ── list_streams ──────────────────────────────────────────────────────
178
+ export const listStreamsDefinition = {
179
+ name: 'list_streams',
180
+ description: 'List known state streams with branch associations and last-active timestamps',
181
+ inputSchema: {
182
+ type: 'object',
183
+ properties: {
184
+ path: { type: 'string', description: 'Path to project root' },
185
+ },
186
+ required: ['path'],
187
+ },
188
+ };
189
+ export async function handleListStreams(input) {
190
+ try {
191
+ const { listStreams, loadStreamIndex } = await import('@harness-engineering/core');
192
+ const projectPath = path.resolve(input.path);
193
+ const indexResult = await loadStreamIndex(projectPath);
194
+ const streamsResult = await listStreams(projectPath);
195
+ if (!streamsResult.ok)
196
+ return resultToMcpResponse(streamsResult);
197
+ return resultToMcpResponse(Ok({
198
+ activeStream: indexResult.ok ? indexResult.value.activeStream : null,
199
+ streams: streamsResult.value,
200
+ }));
201
+ }
202
+ catch (error) {
203
+ return {
204
+ content: [
205
+ {
206
+ type: 'text',
207
+ text: `Error: ${error instanceof Error ? error.message : String(error)}`,
208
+ },
209
+ ],
210
+ isError: true,
211
+ };
212
+ }
213
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Find source files recursively, skipping excluded paths.
3
+ * Zero external dependencies — uses only node:fs.
4
+ */
5
+ export declare function globFiles(rootDir: string, exclude?: string[]): Promise<string[]>;
@@ -0,0 +1,71 @@
1
+ import * as fs from 'node:fs/promises';
2
+ import * as path from 'node:path';
3
+ const SOURCE_EXTENSIONS = new Set(['.ts', '.tsx', '.js', '.jsx', '.go', '.py']);
4
+ /**
5
+ * Convert a simple glob pattern to a regex for matching file paths.
6
+ * Handles: ** (any path), * (any segment), and literal strings.
7
+ */
8
+ function globToRegex(pattern) {
9
+ const escaped = pattern
10
+ .replace(/[.+^${}()|[\]\\]/g, '\\$&') // escape regex chars
11
+ .replace(/\*\*/g, '§§') // temp placeholder for **
12
+ .replace(/\*/g, '[^/]*') // * matches within segment
13
+ .replace(/§§/g, '.*'); // ** matches across segments
14
+ return new RegExp(escaped);
15
+ }
16
+ /**
17
+ * Check if a file path matches any of the exclude patterns.
18
+ */
19
+ function isExcluded(relativePath, excludeRegexes) {
20
+ for (const regex of excludeRegexes) {
21
+ if (regex.test(relativePath))
22
+ return true;
23
+ }
24
+ return false;
25
+ }
26
+ // Directories that are always skipped for performance
27
+ const SKIP_DIRS = new Set(['node_modules', '.git', 'dist', '.next', '.nuxt', '__pycache__']);
28
+ /**
29
+ * Find source files recursively, skipping excluded paths.
30
+ * Zero external dependencies — uses only node:fs.
31
+ */
32
+ export async function globFiles(rootDir, exclude) {
33
+ const patterns = exclude ?? [
34
+ '**/node_modules/**',
35
+ '**/dist/**',
36
+ '**/*.test.ts',
37
+ '**/fixtures/**',
38
+ ];
39
+ const excludeRegexes = patterns.map(globToRegex);
40
+ const files = [];
41
+ async function walk(dir) {
42
+ let entries;
43
+ try {
44
+ entries = await fs.readdir(dir, { withFileTypes: true });
45
+ }
46
+ catch {
47
+ return; // Permission denied or missing directory
48
+ }
49
+ for (const entry of entries) {
50
+ const fullPath = path.join(dir, entry.name);
51
+ const relativePath = path.relative(rootDir, fullPath);
52
+ if (entry.isDirectory()) {
53
+ if (SKIP_DIRS.has(entry.name))
54
+ continue;
55
+ if (isExcluded(relativePath + '/', excludeRegexes))
56
+ continue;
57
+ await walk(fullPath);
58
+ }
59
+ else if (entry.isFile()) {
60
+ const ext = path.extname(entry.name);
61
+ if (!SOURCE_EXTENSIONS.has(ext))
62
+ continue;
63
+ if (isExcluded(relativePath, excludeRegexes))
64
+ continue;
65
+ files.push(fullPath);
66
+ }
67
+ }
68
+ }
69
+ await walk(rootDir);
70
+ return files;
71
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@harness-engineering/mcp-server",
3
- "version": "0.5.0",
3
+ "version": "0.5.1",
4
4
  "description": "MCP server for Harness Engineering toolkit",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -23,9 +23,9 @@
23
23
  "zod": "^3.22.0",
24
24
  "yaml": "^2.3.0",
25
25
  "handlebars": "^4.7.0",
26
+ "@harness-engineering/graph": "0.2.1",
26
27
  "@harness-engineering/core": "0.8.0",
27
- "@harness-engineering/graph": "0.2.0",
28
- "@harness-engineering/cli": "1.6.0",
28
+ "@harness-engineering/cli": "1.6.1",
29
29
  "@harness-engineering/linter-gen": "0.1.0",
30
30
  "@harness-engineering/types": "0.1.0"
31
31
  },