@harness-engineering/mcp-server 0.1.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.
- package/LICENSE +21 -0
- package/dist/bin/harness-mcp.d.ts +2 -0
- package/dist/bin/harness-mcp.js +6 -0
- package/dist/src/index.d.ts +3 -0
- package/dist/src/index.js +3 -0
- package/dist/src/resources/learnings.d.ts +1 -0
- package/dist/src/resources/learnings.js +14 -0
- package/dist/src/resources/project.d.ts +1 -0
- package/dist/src/resources/project.js +9 -0
- package/dist/src/resources/rules.d.ts +1 -0
- package/dist/src/resources/rules.js +36 -0
- package/dist/src/resources/skills.d.ts +1 -0
- package/dist/src/resources/skills.js +33 -0
- package/dist/src/server.d.ts +17 -0
- package/dist/src/server.js +125 -0
- package/dist/src/tools/agent.d.ts +79 -0
- package/dist/src/tools/agent.js +96 -0
- package/dist/src/tools/architecture.d.ts +17 -0
- package/dist/src/tools/architecture.js +45 -0
- package/dist/src/tools/docs.d.ts +39 -0
- package/dist/src/tools/docs.js +65 -0
- package/dist/src/tools/entropy.d.ts +39 -0
- package/dist/src/tools/entropy.js +81 -0
- package/dist/src/tools/init.d.ts +33 -0
- package/dist/src/tools/init.js +55 -0
- package/dist/src/tools/linter.d.ts +63 -0
- package/dist/src/tools/linter.js +65 -0
- package/dist/src/tools/persona.d.ts +59 -0
- package/dist/src/tools/persona.js +106 -0
- package/dist/src/tools/skill.d.ts +38 -0
- package/dist/src/tools/skill.js +46 -0
- package/dist/src/tools/validate.d.ts +22 -0
- package/dist/src/tools/validate.js +85 -0
- package/dist/src/utils/config-resolver.d.ts +7 -0
- package/dist/src/utils/config-resolver.js +18 -0
- package/dist/src/utils/paths.d.ts +3 -0
- package/dist/src/utils/paths.js +33 -0
- package/dist/src/utils/result-adapter.d.ts +11 -0
- package/dist/src/utils/result-adapter.js +11 -0
- package/package.json +56 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export declare const detectEntropyDefinition: {
|
|
2
|
+
name: string;
|
|
3
|
+
description: string;
|
|
4
|
+
inputSchema: {
|
|
5
|
+
type: "object";
|
|
6
|
+
properties: {
|
|
7
|
+
path: {
|
|
8
|
+
type: string;
|
|
9
|
+
description: string;
|
|
10
|
+
};
|
|
11
|
+
};
|
|
12
|
+
required: string[];
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
export declare function handleDetectEntropy(input: {
|
|
16
|
+
path: string;
|
|
17
|
+
}): Promise<import("../utils/result-adapter.js").McpToolResponse>;
|
|
18
|
+
export declare const applyFixesDefinition: {
|
|
19
|
+
name: string;
|
|
20
|
+
description: string;
|
|
21
|
+
inputSchema: {
|
|
22
|
+
type: "object";
|
|
23
|
+
properties: {
|
|
24
|
+
path: {
|
|
25
|
+
type: string;
|
|
26
|
+
description: string;
|
|
27
|
+
};
|
|
28
|
+
dryRun: {
|
|
29
|
+
type: string;
|
|
30
|
+
description: string;
|
|
31
|
+
};
|
|
32
|
+
};
|
|
33
|
+
required: string[];
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
export declare function handleApplyFixes(input: {
|
|
37
|
+
path: string;
|
|
38
|
+
dryRun?: boolean;
|
|
39
|
+
}): Promise<import("../utils/result-adapter.js").McpToolResponse>;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import * as path from 'path';
|
|
2
|
+
import { Ok } from '@harness-engineering/core';
|
|
3
|
+
import { resultToMcpResponse } from '../utils/result-adapter.js';
|
|
4
|
+
export const detectEntropyDefinition = {
|
|
5
|
+
name: 'detect_entropy',
|
|
6
|
+
description: 'Detect documentation drift, dead code, and pattern violations',
|
|
7
|
+
inputSchema: {
|
|
8
|
+
type: 'object',
|
|
9
|
+
properties: {
|
|
10
|
+
path: { type: 'string', description: 'Path to project root' },
|
|
11
|
+
},
|
|
12
|
+
required: ['path'],
|
|
13
|
+
},
|
|
14
|
+
};
|
|
15
|
+
export async function handleDetectEntropy(input) {
|
|
16
|
+
try {
|
|
17
|
+
const { EntropyAnalyzer } = await import('@harness-engineering/core');
|
|
18
|
+
const analyzer = new EntropyAnalyzer({
|
|
19
|
+
rootDir: path.resolve(input.path),
|
|
20
|
+
analyze: { drift: true, deadCode: true, patterns: true },
|
|
21
|
+
});
|
|
22
|
+
const result = await analyzer.analyze();
|
|
23
|
+
return resultToMcpResponse(result);
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
return {
|
|
27
|
+
content: [
|
|
28
|
+
{
|
|
29
|
+
type: 'text',
|
|
30
|
+
text: `Error: ${error instanceof Error ? error.message : String(error)}`,
|
|
31
|
+
},
|
|
32
|
+
],
|
|
33
|
+
isError: true,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
export const applyFixesDefinition = {
|
|
38
|
+
name: 'apply_fixes',
|
|
39
|
+
description: 'Auto-fix detected entropy issues',
|
|
40
|
+
inputSchema: {
|
|
41
|
+
type: 'object',
|
|
42
|
+
properties: {
|
|
43
|
+
path: { type: 'string', description: 'Path to project root' },
|
|
44
|
+
dryRun: { type: 'boolean', description: 'Preview fixes without applying' },
|
|
45
|
+
},
|
|
46
|
+
required: ['path'],
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
export async function handleApplyFixes(input) {
|
|
50
|
+
try {
|
|
51
|
+
const { EntropyAnalyzer, createFixes, applyFixes } = await import('@harness-engineering/core');
|
|
52
|
+
const analyzer = new EntropyAnalyzer({
|
|
53
|
+
rootDir: path.resolve(input.path),
|
|
54
|
+
analyze: { drift: true, deadCode: true, patterns: true },
|
|
55
|
+
});
|
|
56
|
+
const analysisResult = await analyzer.analyze();
|
|
57
|
+
if (!analysisResult.ok)
|
|
58
|
+
return resultToMcpResponse(analysisResult);
|
|
59
|
+
const deadCode = analysisResult.value.deadCode;
|
|
60
|
+
if (!deadCode) {
|
|
61
|
+
return resultToMcpResponse(Ok({ fixes: [], applied: 0 }));
|
|
62
|
+
}
|
|
63
|
+
const fixes = createFixes(deadCode, {});
|
|
64
|
+
if (input.dryRun) {
|
|
65
|
+
return { content: [{ type: 'text', text: JSON.stringify(fixes) }] };
|
|
66
|
+
}
|
|
67
|
+
const applied = await applyFixes(fixes, {});
|
|
68
|
+
return resultToMcpResponse(applied);
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
return {
|
|
72
|
+
content: [
|
|
73
|
+
{
|
|
74
|
+
type: 'text',
|
|
75
|
+
text: `Error: ${error instanceof Error ? error.message : String(error)}`,
|
|
76
|
+
},
|
|
77
|
+
],
|
|
78
|
+
isError: true,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export declare const initProjectDefinition: {
|
|
2
|
+
name: string;
|
|
3
|
+
description: string;
|
|
4
|
+
inputSchema: {
|
|
5
|
+
type: "object";
|
|
6
|
+
properties: {
|
|
7
|
+
path: {
|
|
8
|
+
type: string;
|
|
9
|
+
description: string;
|
|
10
|
+
};
|
|
11
|
+
name: {
|
|
12
|
+
type: string;
|
|
13
|
+
description: string;
|
|
14
|
+
};
|
|
15
|
+
level: {
|
|
16
|
+
type: string;
|
|
17
|
+
enum: string[];
|
|
18
|
+
description: string;
|
|
19
|
+
};
|
|
20
|
+
framework: {
|
|
21
|
+
type: string;
|
|
22
|
+
description: string;
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
required: string[];
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
export declare function handleInitProject(input: {
|
|
29
|
+
path: string;
|
|
30
|
+
name?: string;
|
|
31
|
+
level?: string;
|
|
32
|
+
framework?: string;
|
|
33
|
+
}): Promise<import("../utils/result-adapter.js").McpToolResponse>;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import * as path from 'path';
|
|
2
|
+
import { resultToMcpResponse } from '../utils/result-adapter.js';
|
|
3
|
+
import { resolveTemplatesDir } from '../utils/paths.js';
|
|
4
|
+
export const initProjectDefinition = {
|
|
5
|
+
name: 'init_project',
|
|
6
|
+
description: 'Scaffold a new harness engineering project from a template',
|
|
7
|
+
inputSchema: {
|
|
8
|
+
type: 'object',
|
|
9
|
+
properties: {
|
|
10
|
+
path: { type: 'string', description: 'Target directory' },
|
|
11
|
+
name: { type: 'string', description: 'Project name' },
|
|
12
|
+
level: {
|
|
13
|
+
type: 'string',
|
|
14
|
+
enum: ['basic', 'intermediate', 'advanced'],
|
|
15
|
+
description: 'Adoption level',
|
|
16
|
+
},
|
|
17
|
+
framework: { type: 'string', description: 'Framework overlay (e.g., nextjs)' },
|
|
18
|
+
},
|
|
19
|
+
required: ['path'],
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
export async function handleInitProject(input) {
|
|
23
|
+
try {
|
|
24
|
+
// Import TemplateEngine - ensure CLI exports it
|
|
25
|
+
const { TemplateEngine } = await import('@harness-engineering/cli');
|
|
26
|
+
const templatesDir = resolveTemplatesDir();
|
|
27
|
+
const engine = new TemplateEngine(templatesDir);
|
|
28
|
+
const level = input.level ?? 'basic';
|
|
29
|
+
const resolveResult = engine.resolveTemplate(level, input.framework);
|
|
30
|
+
if (!resolveResult.ok)
|
|
31
|
+
return resultToMcpResponse(resolveResult);
|
|
32
|
+
const renderResult = engine.render(resolveResult.value, {
|
|
33
|
+
projectName: input.name ?? path.basename(input.path),
|
|
34
|
+
level,
|
|
35
|
+
framework: input.framework,
|
|
36
|
+
});
|
|
37
|
+
if (!renderResult.ok)
|
|
38
|
+
return resultToMcpResponse(renderResult);
|
|
39
|
+
const writeResult = engine.write(renderResult.value, path.resolve(input.path), {
|
|
40
|
+
overwrite: false,
|
|
41
|
+
});
|
|
42
|
+
return resultToMcpResponse(writeResult);
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
return {
|
|
46
|
+
content: [
|
|
47
|
+
{
|
|
48
|
+
type: 'text',
|
|
49
|
+
text: `Init failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
50
|
+
},
|
|
51
|
+
],
|
|
52
|
+
isError: true,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
export declare const generateLinterDefinition: {
|
|
2
|
+
name: string;
|
|
3
|
+
description: string;
|
|
4
|
+
inputSchema: {
|
|
5
|
+
type: "object";
|
|
6
|
+
properties: {
|
|
7
|
+
configPath: {
|
|
8
|
+
type: string;
|
|
9
|
+
description: string;
|
|
10
|
+
};
|
|
11
|
+
outputDir: {
|
|
12
|
+
type: string;
|
|
13
|
+
description: string;
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
required: string[];
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
export declare function handleGenerateLinter(input: {
|
|
20
|
+
configPath: string;
|
|
21
|
+
outputDir?: string;
|
|
22
|
+
}): Promise<{
|
|
23
|
+
content: {
|
|
24
|
+
type: "text";
|
|
25
|
+
text: string;
|
|
26
|
+
}[];
|
|
27
|
+
isError?: undefined;
|
|
28
|
+
} | {
|
|
29
|
+
content: {
|
|
30
|
+
type: "text";
|
|
31
|
+
text: string;
|
|
32
|
+
}[];
|
|
33
|
+
isError: boolean;
|
|
34
|
+
}>;
|
|
35
|
+
export declare const validateLinterConfigDefinition: {
|
|
36
|
+
name: string;
|
|
37
|
+
description: string;
|
|
38
|
+
inputSchema: {
|
|
39
|
+
type: "object";
|
|
40
|
+
properties: {
|
|
41
|
+
configPath: {
|
|
42
|
+
type: string;
|
|
43
|
+
description: string;
|
|
44
|
+
};
|
|
45
|
+
};
|
|
46
|
+
required: string[];
|
|
47
|
+
};
|
|
48
|
+
};
|
|
49
|
+
export declare function handleValidateLinterConfig(input: {
|
|
50
|
+
configPath: string;
|
|
51
|
+
}): Promise<{
|
|
52
|
+
content: {
|
|
53
|
+
type: "text";
|
|
54
|
+
text: string;
|
|
55
|
+
}[];
|
|
56
|
+
isError?: undefined;
|
|
57
|
+
} | {
|
|
58
|
+
content: {
|
|
59
|
+
type: "text";
|
|
60
|
+
text: string;
|
|
61
|
+
}[];
|
|
62
|
+
isError: boolean;
|
|
63
|
+
}>;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
export const generateLinterDefinition = {
|
|
2
|
+
name: 'generate_linter',
|
|
3
|
+
description: 'Generate an ESLint rule from YAML configuration',
|
|
4
|
+
inputSchema: {
|
|
5
|
+
type: 'object',
|
|
6
|
+
properties: {
|
|
7
|
+
configPath: { type: 'string', description: 'Path to harness-linter.yml' },
|
|
8
|
+
outputDir: { type: 'string', description: 'Output directory for generated rule' },
|
|
9
|
+
},
|
|
10
|
+
required: ['configPath'],
|
|
11
|
+
},
|
|
12
|
+
};
|
|
13
|
+
export async function handleGenerateLinter(input) {
|
|
14
|
+
try {
|
|
15
|
+
const { generate } = await import('@harness-engineering/linter-gen');
|
|
16
|
+
const result = await generate({ configPath: input.configPath, outputDir: input.outputDir });
|
|
17
|
+
if ('success' in result && result.success) {
|
|
18
|
+
return { content: [{ type: 'text', text: JSON.stringify(result) }] };
|
|
19
|
+
}
|
|
20
|
+
return { content: [{ type: 'text', text: JSON.stringify(result) }], isError: true };
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
return {
|
|
24
|
+
content: [
|
|
25
|
+
{
|
|
26
|
+
type: 'text',
|
|
27
|
+
text: `Error: ${error instanceof Error ? error.message : String(error)}`,
|
|
28
|
+
},
|
|
29
|
+
],
|
|
30
|
+
isError: true,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
export const validateLinterConfigDefinition = {
|
|
35
|
+
name: 'validate_linter_config',
|
|
36
|
+
description: 'Validate a harness-linter.yml configuration file',
|
|
37
|
+
inputSchema: {
|
|
38
|
+
type: 'object',
|
|
39
|
+
properties: {
|
|
40
|
+
configPath: { type: 'string', description: 'Path to harness-linter.yml' },
|
|
41
|
+
},
|
|
42
|
+
required: ['configPath'],
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
export async function handleValidateLinterConfig(input) {
|
|
46
|
+
try {
|
|
47
|
+
const { validate } = await import('@harness-engineering/linter-gen');
|
|
48
|
+
const result = await validate({ configPath: input.configPath });
|
|
49
|
+
if ('success' in result && result.success) {
|
|
50
|
+
return { content: [{ type: 'text', text: JSON.stringify(result) }] };
|
|
51
|
+
}
|
|
52
|
+
return { content: [{ type: 'text', text: JSON.stringify(result) }], isError: true };
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
return {
|
|
56
|
+
content: [
|
|
57
|
+
{
|
|
58
|
+
type: 'text',
|
|
59
|
+
text: `Error: ${error instanceof Error ? error.message : String(error)}`,
|
|
60
|
+
},
|
|
61
|
+
],
|
|
62
|
+
isError: true,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
export declare const listPersonasDefinition: {
|
|
2
|
+
name: string;
|
|
3
|
+
description: string;
|
|
4
|
+
inputSchema: {
|
|
5
|
+
type: "object";
|
|
6
|
+
properties: {};
|
|
7
|
+
};
|
|
8
|
+
};
|
|
9
|
+
export declare function handleListPersonas(): Promise<import("../utils/result-adapter.js").McpToolResponse>;
|
|
10
|
+
export declare const generatePersonaArtifactsDefinition: {
|
|
11
|
+
name: string;
|
|
12
|
+
description: string;
|
|
13
|
+
inputSchema: {
|
|
14
|
+
type: "object";
|
|
15
|
+
properties: {
|
|
16
|
+
name: {
|
|
17
|
+
type: string;
|
|
18
|
+
description: string;
|
|
19
|
+
};
|
|
20
|
+
only: {
|
|
21
|
+
type: string;
|
|
22
|
+
enum: string[];
|
|
23
|
+
description: string;
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
required: string[];
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
export declare function handleGeneratePersonaArtifacts(input: {
|
|
30
|
+
name: string;
|
|
31
|
+
only?: string;
|
|
32
|
+
}): Promise<import("../utils/result-adapter.js").McpToolResponse>;
|
|
33
|
+
export declare const runPersonaDefinition: {
|
|
34
|
+
name: string;
|
|
35
|
+
description: string;
|
|
36
|
+
inputSchema: {
|
|
37
|
+
type: "object";
|
|
38
|
+
properties: {
|
|
39
|
+
persona: {
|
|
40
|
+
type: string;
|
|
41
|
+
description: string;
|
|
42
|
+
};
|
|
43
|
+
path: {
|
|
44
|
+
type: string;
|
|
45
|
+
description: string;
|
|
46
|
+
};
|
|
47
|
+
dryRun: {
|
|
48
|
+
type: string;
|
|
49
|
+
description: string;
|
|
50
|
+
};
|
|
51
|
+
};
|
|
52
|
+
required: string[];
|
|
53
|
+
};
|
|
54
|
+
};
|
|
55
|
+
export declare function handleRunPersona(input: {
|
|
56
|
+
persona: string;
|
|
57
|
+
path?: string;
|
|
58
|
+
dryRun?: boolean;
|
|
59
|
+
}): Promise<import("../utils/result-adapter.js").McpToolResponse>;
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import * as path from 'path';
|
|
2
|
+
import { Ok, Err } from '@harness-engineering/core';
|
|
3
|
+
import { resultToMcpResponse } from '../utils/result-adapter.js';
|
|
4
|
+
import { resolvePersonasDir } from '../utils/paths.js';
|
|
5
|
+
export const listPersonasDefinition = {
|
|
6
|
+
name: 'list_personas',
|
|
7
|
+
description: 'List available agent personas',
|
|
8
|
+
inputSchema: { type: 'object', properties: {} },
|
|
9
|
+
};
|
|
10
|
+
export async function handleListPersonas() {
|
|
11
|
+
const { listPersonas } = await import('@harness-engineering/cli');
|
|
12
|
+
const result = listPersonas(resolvePersonasDir());
|
|
13
|
+
return resultToMcpResponse(result);
|
|
14
|
+
}
|
|
15
|
+
export const generatePersonaArtifactsDefinition = {
|
|
16
|
+
name: 'generate_persona_artifacts',
|
|
17
|
+
description: 'Generate runtime config, AGENTS.md fragment, and CI workflow from a persona',
|
|
18
|
+
inputSchema: {
|
|
19
|
+
type: 'object',
|
|
20
|
+
properties: {
|
|
21
|
+
name: { type: 'string', description: 'Persona name (e.g., architecture-enforcer)' },
|
|
22
|
+
only: {
|
|
23
|
+
type: 'string',
|
|
24
|
+
enum: ['runtime', 'agents-md', 'ci'],
|
|
25
|
+
description: 'Generate only a specific artifact type',
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
required: ['name'],
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
export async function handleGeneratePersonaArtifacts(input) {
|
|
32
|
+
const { loadPersona, generateRuntime, generateAgentsMd, generateCIWorkflow } = await import('@harness-engineering/cli');
|
|
33
|
+
const filePath = path.join(resolvePersonasDir(), `${input.name}.yaml`);
|
|
34
|
+
const personaResult = loadPersona(filePath);
|
|
35
|
+
if (!personaResult.ok)
|
|
36
|
+
return resultToMcpResponse(personaResult);
|
|
37
|
+
const persona = personaResult.value;
|
|
38
|
+
const artifacts = {};
|
|
39
|
+
if (!input.only || input.only === 'runtime') {
|
|
40
|
+
const r = generateRuntime(persona);
|
|
41
|
+
if (r.ok)
|
|
42
|
+
artifacts.runtime = r.value;
|
|
43
|
+
}
|
|
44
|
+
if (!input.only || input.only === 'agents-md') {
|
|
45
|
+
const r = generateAgentsMd(persona);
|
|
46
|
+
if (r.ok)
|
|
47
|
+
artifacts.agentsMd = r.value;
|
|
48
|
+
}
|
|
49
|
+
if (!input.only || input.only === 'ci') {
|
|
50
|
+
const r = generateCIWorkflow(persona, 'github');
|
|
51
|
+
if (r.ok)
|
|
52
|
+
artifacts.ciWorkflow = r.value;
|
|
53
|
+
}
|
|
54
|
+
return resultToMcpResponse(Ok(artifacts));
|
|
55
|
+
}
|
|
56
|
+
export const runPersonaDefinition = {
|
|
57
|
+
name: 'run_persona',
|
|
58
|
+
description: 'Execute all commands defined in a persona and return aggregated results',
|
|
59
|
+
inputSchema: {
|
|
60
|
+
type: 'object',
|
|
61
|
+
properties: {
|
|
62
|
+
persona: { type: 'string', description: 'Persona name (e.g., architecture-enforcer)' },
|
|
63
|
+
path: { type: 'string', description: 'Path to project root' },
|
|
64
|
+
dryRun: { type: 'boolean', description: 'Preview without side effects' },
|
|
65
|
+
},
|
|
66
|
+
required: ['persona'],
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
export async function handleRunPersona(input) {
|
|
70
|
+
const { loadPersona, runPersona } = await import('@harness-engineering/cli');
|
|
71
|
+
const filePath = path.join(resolvePersonasDir(), `${input.persona}.yaml`);
|
|
72
|
+
const personaResult = loadPersona(filePath);
|
|
73
|
+
if (!personaResult.ok)
|
|
74
|
+
return resultToMcpResponse(personaResult);
|
|
75
|
+
const projectPath = input.path ? path.resolve(input.path) : process.cwd();
|
|
76
|
+
const ALLOWED_COMMANDS = new Set([
|
|
77
|
+
'validate',
|
|
78
|
+
'check-deps',
|
|
79
|
+
'check-docs',
|
|
80
|
+
'cleanup',
|
|
81
|
+
'fix-drift',
|
|
82
|
+
'add',
|
|
83
|
+
]);
|
|
84
|
+
const executor = async (command) => {
|
|
85
|
+
if (!ALLOWED_COMMANDS.has(command)) {
|
|
86
|
+
return Err(new Error(`Unknown harness command: ${command}`));
|
|
87
|
+
}
|
|
88
|
+
try {
|
|
89
|
+
const { execFileSync } = await import('node:child_process');
|
|
90
|
+
const args = ['harness', command];
|
|
91
|
+
if (input.dryRun)
|
|
92
|
+
args.push('--dry-run');
|
|
93
|
+
const output = execFileSync('npx', args, {
|
|
94
|
+
cwd: projectPath,
|
|
95
|
+
stdio: 'pipe',
|
|
96
|
+
timeout: personaResult.value.config.timeout,
|
|
97
|
+
});
|
|
98
|
+
return Ok(output.toString());
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
return Err(new Error(`${command} failed: ${error instanceof Error ? error.message : String(error)}`));
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
const report = await runPersona(personaResult.value, executor);
|
|
105
|
+
return resultToMcpResponse(Ok(report));
|
|
106
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export declare const runSkillDefinition: {
|
|
2
|
+
name: string;
|
|
3
|
+
description: string;
|
|
4
|
+
inputSchema: {
|
|
5
|
+
type: "object";
|
|
6
|
+
properties: {
|
|
7
|
+
skill: {
|
|
8
|
+
type: string;
|
|
9
|
+
description: string;
|
|
10
|
+
};
|
|
11
|
+
path: {
|
|
12
|
+
type: string;
|
|
13
|
+
description: string;
|
|
14
|
+
};
|
|
15
|
+
complexity: {
|
|
16
|
+
type: string;
|
|
17
|
+
enum: string[];
|
|
18
|
+
description: string;
|
|
19
|
+
};
|
|
20
|
+
phase: {
|
|
21
|
+
type: string;
|
|
22
|
+
description: string;
|
|
23
|
+
};
|
|
24
|
+
party: {
|
|
25
|
+
type: string;
|
|
26
|
+
description: string;
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
required: string[];
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
export declare function handleRunSkill(input: {
|
|
33
|
+
skill: string;
|
|
34
|
+
path?: string;
|
|
35
|
+
complexity?: 'auto' | 'light' | 'full';
|
|
36
|
+
phase?: string;
|
|
37
|
+
party?: boolean;
|
|
38
|
+
}): Promise<import("../utils/result-adapter.js").McpToolResponse>;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import { Ok, Err } from '@harness-engineering/core';
|
|
4
|
+
import { resultToMcpResponse } from '../utils/result-adapter.js';
|
|
5
|
+
import { resolveSkillsDir } from '../utils/paths.js';
|
|
6
|
+
export const runSkillDefinition = {
|
|
7
|
+
name: 'run_skill',
|
|
8
|
+
description: 'Load and return the content of a skill (SKILL.md), optionally with project state context',
|
|
9
|
+
inputSchema: {
|
|
10
|
+
type: 'object',
|
|
11
|
+
properties: {
|
|
12
|
+
skill: { type: 'string', description: 'Skill name (e.g., harness-tdd)' },
|
|
13
|
+
path: { type: 'string', description: 'Path to project root for state context injection' },
|
|
14
|
+
complexity: {
|
|
15
|
+
type: 'string',
|
|
16
|
+
enum: ['auto', 'light', 'full'],
|
|
17
|
+
description: 'Complexity level for scale-adaptive rigor',
|
|
18
|
+
},
|
|
19
|
+
phase: { type: 'string', description: 'Start at a specific phase (re-entry)' },
|
|
20
|
+
party: { type: 'boolean', description: 'Enable multi-perspective evaluation' },
|
|
21
|
+
},
|
|
22
|
+
required: ['skill'],
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
export async function handleRunSkill(input) {
|
|
26
|
+
const skillsDir = resolveSkillsDir();
|
|
27
|
+
const skillDir = path.join(skillsDir, input.skill);
|
|
28
|
+
if (!fs.existsSync(skillDir)) {
|
|
29
|
+
return resultToMcpResponse(Err(new Error(`Skill not found: ${input.skill}`)));
|
|
30
|
+
}
|
|
31
|
+
const skillMdPath = path.join(skillDir, 'SKILL.md');
|
|
32
|
+
if (!fs.existsSync(skillMdPath)) {
|
|
33
|
+
return resultToMcpResponse(Err(new Error(`SKILL.md not found for skill: ${input.skill}`)));
|
|
34
|
+
}
|
|
35
|
+
let content = fs.readFileSync(skillMdPath, 'utf-8');
|
|
36
|
+
// Optionally inject project state context
|
|
37
|
+
if (input.path) {
|
|
38
|
+
const projectPath = path.resolve(input.path);
|
|
39
|
+
const stateFile = path.join(projectPath, '.harness', 'state.json');
|
|
40
|
+
if (fs.existsSync(stateFile)) {
|
|
41
|
+
const stateContent = fs.readFileSync(stateFile, 'utf-8');
|
|
42
|
+
content += `\n\n---\n## Project State\n\`\`\`json\n${stateContent}\n\`\`\`\n`;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return resultToMcpResponse(Ok(content));
|
|
46
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export declare const validateToolDefinition: {
|
|
2
|
+
name: string;
|
|
3
|
+
description: string;
|
|
4
|
+
inputSchema: {
|
|
5
|
+
type: "object";
|
|
6
|
+
properties: {
|
|
7
|
+
path: {
|
|
8
|
+
type: string;
|
|
9
|
+
description: string;
|
|
10
|
+
};
|
|
11
|
+
};
|
|
12
|
+
required: string[];
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
export declare function handleValidateProject(input: {
|
|
16
|
+
path: string;
|
|
17
|
+
}): Promise<{
|
|
18
|
+
content: {
|
|
19
|
+
type: "text";
|
|
20
|
+
text: string;
|
|
21
|
+
}[];
|
|
22
|
+
}>;
|