@harness-engineering/mcp-server 0.2.0 → 0.3.2

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.
@@ -6,12 +6,20 @@ export async function getStateResource(projectRoot) {
6
6
  return JSON.stringify(result.value, null, 2);
7
7
  }
8
8
  return JSON.stringify({
9
- schemaVersion: 1, position: {}, decisions: [], blockers: [], progress: {},
9
+ schemaVersion: 1,
10
+ position: {},
11
+ decisions: [],
12
+ blockers: [],
13
+ progress: {},
10
14
  });
11
15
  }
12
16
  catch {
13
17
  return JSON.stringify({
14
- schemaVersion: 1, position: {}, decisions: [], blockers: [], progress: {},
18
+ schemaVersion: 1,
19
+ position: {},
20
+ decisions: [],
21
+ blockers: [],
22
+ progress: {},
15
23
  });
16
24
  }
17
25
  }
@@ -9,7 +9,7 @@ import { generateLinterDefinition, handleGenerateLinter, validateLinterConfigDef
9
9
  import { initProjectDefinition, handleInitProject } from './tools/init.js';
10
10
  import { listPersonasDefinition, handleListPersonas, generatePersonaArtifactsDefinition, handleGeneratePersonaArtifacts, runPersonaDefinition, handleRunPersona, } from './tools/persona.js';
11
11
  import { addComponentDefinition, handleAddComponent, runAgentTaskDefinition, handleRunAgentTask, } from './tools/agent.js';
12
- import { runSkillDefinition, handleRunSkill, createSkillDefinition, handleCreateSkill } from './tools/skill.js';
12
+ import { runSkillDefinition, handleRunSkill, createSkillDefinition, handleCreateSkill, } from './tools/skill.js';
13
13
  import { getSkillsResource } from './resources/skills.js';
14
14
  import { getRulesResource } from './resources/rules.js';
15
15
  import { getProjectResource } from './resources/project.js';
@@ -18,6 +18,7 @@ import { manageStateDefinition, handleManageState, manageHandoffDefinition, hand
18
18
  import { createSelfReviewDefinition, handleCreateSelfReview, analyzeDiffDefinition, handleAnalyzeDiff, requestPeerReviewDefinition, handleRequestPeerReview, } from './tools/feedback.js';
19
19
  import { checkPhaseGateDefinition, handleCheckPhaseGate } from './tools/phase-gate.js';
20
20
  import { validateCrossCheckDefinition, handleValidateCrossCheck } from './tools/cross-check.js';
21
+ import { generateSlashCommandsDefinition, handleGenerateSlashCommands, } from './tools/generate-slash-commands.js';
21
22
  import { getStateResource } from './resources/state.js';
22
23
  const TOOL_DEFINITIONS = [
23
24
  validateToolDefinition,
@@ -43,6 +44,7 @@ const TOOL_DEFINITIONS = [
43
44
  checkPhaseGateDefinition,
44
45
  validateCrossCheckDefinition,
45
46
  createSkillDefinition,
47
+ generateSlashCommandsDefinition,
46
48
  ];
47
49
  const TOOL_HANDLERS = {
48
50
  validate_project: handleValidateProject,
@@ -68,6 +70,7 @@ const TOOL_HANDLERS = {
68
70
  check_phase_gate: handleCheckPhaseGate,
69
71
  validate_cross_check: handleValidateCrossCheck,
70
72
  create_skill: handleCreateSkill,
73
+ generate_slash_commands: handleGenerateSlashCommands,
71
74
  };
72
75
  const RESOURCE_DEFINITIONS = [
73
76
  {
@@ -21,7 +21,7 @@ export async function handleCheckDependencies(input) {
21
21
  try {
22
22
  const { validateDependencies, TypeScriptParser } = await import('@harness-engineering/core');
23
23
  const config = configResult.value;
24
- const rawLayers = config.layers ?? [];
24
+ const rawLayers = (Array.isArray(config.layers) ? config.layers : []);
25
25
  const layers = rawLayers.map((l) => ({
26
26
  name: l.name,
27
27
  patterns: [l.pattern],
@@ -6,8 +6,14 @@ export const validateCrossCheckDefinition = {
6
6
  type: 'object',
7
7
  properties: {
8
8
  path: { type: 'string', description: 'Path to project root directory' },
9
- specsDir: { type: 'string', description: 'Specs directory relative to project root (default: docs/specs)' },
10
- plansDir: { type: 'string', description: 'Plans directory relative to project root (default: docs/plans)' },
9
+ specsDir: {
10
+ type: 'string',
11
+ description: 'Specs directory relative to project root (default: docs/specs)',
12
+ },
13
+ plansDir: {
14
+ type: 'string',
15
+ description: 'Plans directory relative to project root (default: docs/plans)',
16
+ },
11
17
  },
12
18
  required: ['path'],
13
19
  },
@@ -28,7 +34,12 @@ export async function handleValidateCrossCheck(input) {
28
34
  }
29
35
  catch (error) {
30
36
  return {
31
- content: [{ type: 'text', text: `Error: ${error instanceof Error ? error.message : String(error)}` }],
37
+ content: [
38
+ {
39
+ type: 'text',
40
+ text: `Error: ${error instanceof Error ? error.message : String(error)}`,
41
+ },
42
+ ],
32
43
  isError: true,
33
44
  };
34
45
  }
@@ -34,7 +34,12 @@ export async function handleDetectEntropy(input) {
34
34
  }
35
35
  catch (error) {
36
36
  return {
37
- content: [{ type: 'text', text: `Error: ${error instanceof Error ? error.message : String(error)}` }],
37
+ content: [
38
+ {
39
+ type: 'text',
40
+ text: `Error: ${error instanceof Error ? error.message : String(error)}`,
41
+ },
42
+ ],
38
43
  isError: true,
39
44
  };
40
45
  }
@@ -72,13 +77,22 @@ export async function handleApplyFixes(input) {
72
77
  const applied = await applyFixes(fixes, {});
73
78
  if (!applied.ok)
74
79
  return resultToMcpResponse(applied);
75
- return { content: [{ type: 'text', text: JSON.stringify({ ...applied.value, suggestions }) }] };
80
+ return {
81
+ content: [
82
+ { type: 'text', text: JSON.stringify({ ...applied.value, suggestions }) },
83
+ ],
84
+ };
76
85
  }
77
86
  return resultToMcpResponse(Ok({ fixes: [], applied: 0, suggestions }));
78
87
  }
79
88
  catch (error) {
80
89
  return {
81
- content: [{ type: 'text', text: `Error: ${error instanceof Error ? error.message : String(error)}` }],
90
+ content: [
91
+ {
92
+ type: 'text',
93
+ text: `Error: ${error instanceof Error ? error.message : String(error)}`,
94
+ },
95
+ ],
82
96
  isError: true,
83
97
  };
84
98
  }
@@ -0,0 +1,37 @@
1
+ import type { McpToolResponse } from '../utils/result-adapter.js';
2
+ export declare const generateSlashCommandsDefinition: {
3
+ name: string;
4
+ description: string;
5
+ inputSchema: {
6
+ type: "object";
7
+ properties: {
8
+ platforms: {
9
+ type: string;
10
+ description: string;
11
+ };
12
+ global: {
13
+ type: string;
14
+ description: string;
15
+ };
16
+ output: {
17
+ type: string;
18
+ description: string;
19
+ };
20
+ skillsDir: {
21
+ type: string;
22
+ description: string;
23
+ };
24
+ dryRun: {
25
+ type: string;
26
+ description: string;
27
+ };
28
+ };
29
+ };
30
+ };
31
+ export declare function handleGenerateSlashCommands(input: {
32
+ platforms?: string;
33
+ global?: boolean;
34
+ output?: string;
35
+ skillsDir?: string;
36
+ dryRun?: boolean;
37
+ }): Promise<McpToolResponse>;
@@ -0,0 +1,51 @@
1
+ import { Ok, Err } from '@harness-engineering/core';
2
+ import { generateSlashCommands } from '@harness-engineering/cli';
3
+ import { resultToMcpResponse } from '../utils/result-adapter.js';
4
+ export const generateSlashCommandsDefinition = {
5
+ name: 'generate_slash_commands',
6
+ description: 'Generate native slash commands for Claude Code and Gemini CLI from harness skill metadata',
7
+ inputSchema: {
8
+ type: 'object',
9
+ properties: {
10
+ platforms: {
11
+ type: 'string',
12
+ description: 'Comma-separated platforms: claude-code,gemini-cli (default: both)',
13
+ },
14
+ global: {
15
+ type: 'boolean',
16
+ description: 'Write to global config directories (~/.claude/commands/, ~/.gemini/commands/)',
17
+ },
18
+ output: {
19
+ type: 'string',
20
+ description: 'Custom output directory',
21
+ },
22
+ skillsDir: {
23
+ type: 'string',
24
+ description: 'Skills directory to scan',
25
+ },
26
+ dryRun: {
27
+ type: 'boolean',
28
+ description: 'Show what would change without writing files',
29
+ },
30
+ },
31
+ },
32
+ };
33
+ export async function handleGenerateSlashCommands(input) {
34
+ try {
35
+ const platforms = (input.platforms ?? 'claude-code,gemini-cli')
36
+ .split(',')
37
+ .map((p) => p.trim());
38
+ const results = generateSlashCommands({
39
+ platforms,
40
+ global: input.global ?? false,
41
+ output: input.output,
42
+ skillsDir: input.skillsDir ?? '',
43
+ dryRun: input.dryRun ?? false,
44
+ yes: true,
45
+ });
46
+ return resultToMcpResponse(Ok(JSON.stringify(results, null, 2)));
47
+ }
48
+ catch (error) {
49
+ return resultToMcpResponse(Err(error instanceof Error ? error : new Error(String(error))));
50
+ }
51
+ }
@@ -21,7 +21,12 @@ export async function handleCheckPhaseGate(input) {
21
21
  }
22
22
  catch (error) {
23
23
  return {
24
- content: [{ type: 'text', text: `Error: ${error instanceof Error ? error.message : String(error)}` }],
24
+ content: [
25
+ {
26
+ type: 'text',
27
+ text: `Error: ${error instanceof Error ? error.message : String(error)}`,
28
+ },
29
+ ],
25
30
  isError: true,
26
31
  };
27
32
  }
@@ -56,8 +56,12 @@ export const createSkillDefinition = {
56
56
  cognitiveMode: {
57
57
  type: 'string',
58
58
  enum: [
59
- 'adversarial-reviewer', 'constructive-architect', 'meticulous-implementer',
60
- 'diagnostic-investigator', 'advisory-guide', 'meticulous-verifier',
59
+ 'adversarial-reviewer',
60
+ 'constructive-architect',
61
+ 'meticulous-implementer',
62
+ 'diagnostic-investigator',
63
+ 'advisory-guide',
64
+ 'meticulous-verifier',
61
65
  ],
62
66
  description: 'Cognitive mode (default: constructive-architect)',
63
67
  },
@@ -78,7 +82,12 @@ export async function handleCreateSkill(input) {
78
82
  }
79
83
  catch (error) {
80
84
  return {
81
- content: [{ type: 'text', text: `Error: ${error instanceof Error ? error.message : String(error)}` }],
85
+ content: [
86
+ {
87
+ type: 'text',
88
+ text: `Error: ${error instanceof Error ? error.message : String(error)}`,
89
+ },
90
+ ],
82
91
  isError: true,
83
92
  };
84
93
  }
@@ -34,7 +34,8 @@ export async function handleValidateProject(input) {
34
34
  const core = await import('@harness-engineering/core');
35
35
  if (typeof core.validateFileStructure === 'function' &&
36
36
  Array.isArray(config.conventions)) {
37
- const structureResult = await core.validateFileStructure(projectPath, config.conventions);
37
+ const conventions = config.conventions;
38
+ const structureResult = await core.validateFileStructure(projectPath, conventions);
38
39
  if (structureResult.ok) {
39
40
  checks.structure = structureResult.value.valid ? 'pass' : 'fail';
40
41
  if (!structureResult.value.valid) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@harness-engineering/mcp-server",
3
- "version": "0.2.0",
3
+ "version": "0.3.2",
4
4
  "description": "MCP server for Harness Engineering toolkit",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -23,10 +23,10 @@
23
23
  "zod": "^3.22.0",
24
24
  "yaml": "^2.3.0",
25
25
  "handlebars": "^4.7.0",
26
- "@harness-engineering/core": "0.5.0",
27
- "@harness-engineering/cli": "1.1.1",
28
- "@harness-engineering/types": "0.0.0",
29
- "@harness-engineering/linter-gen": "0.1.0"
26
+ "@harness-engineering/core": "0.6.0",
27
+ "@harness-engineering/cli": "1.2.2",
28
+ "@harness-engineering/linter-gen": "0.1.0",
29
+ "@harness-engineering/types": "0.0.1"
30
30
  },
31
31
  "devDependencies": {
32
32
  "@types/node": "^20.0.0",
@@ -50,6 +50,7 @@
50
50
  "build": "tsc",
51
51
  "test": "vitest run",
52
52
  "test:watch": "vitest",
53
+ "lint": "eslint src",
53
54
  "typecheck": "tsc --noEmit",
54
55
  "clean": "rm -rf dist"
55
56
  }