@schilling.mark.a/software-methodology 1.0.0 → 1.0.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.
- package/.github/copilot-instructions.md +159 -0
- package/README.md +172 -6
- package/docs/story-map/backbone.md +141 -0
- package/docs/story-map/releases/r1-walking-skeleton.md +152 -0
- package/docs/story-map/user-tasks/ACT-001-task-001.md +45 -0
- package/docs/story-map/user-tasks/ACT-001-task-002.md +48 -0
- package/docs/story-map/user-tasks/ACT-002-task-001.md +47 -0
- package/docs/story-map/user-tasks/ACT-002-task-002.md +47 -0
- package/docs/story-map/user-tasks/ACT-002-task-003.md +46 -0
- package/docs/story-map/user-tasks/ACT-003-task-001.md +47 -0
- package/docs/story-map/user-tasks/ACT-003-task-002.md +46 -0
- package/docs/story-map/user-tasks/ACT-003-task-003.md +49 -0
- package/docs/story-map/user-tasks/ACT-003-task-004.md +47 -0
- package/docs/story-map/user-tasks/ACT-004-task-001.md +48 -0
- package/docs/story-map/user-tasks/ACT-004-task-002.md +49 -0
- package/docs/story-map/user-tasks/ACT-004-task-003.md +47 -0
- package/docs/story-map/user-tasks/ACT-005-task-001.md +47 -0
- package/docs/story-map/user-tasks/ACT-005-task-002.md +48 -0
- package/docs/story-map/user-tasks/ACT-005-task-003.md +48 -0
- package/docs/story-map/user-tasks/ACT-005-task-004.md +48 -0
- package/docs/story-map/user-tasks/ACT-006-task-001.md +47 -0
- package/docs/story-map/user-tasks/ACT-006-task-002.md +46 -0
- package/docs/story-map/user-tasks/ACT-006-task-003.md +47 -0
- package/docs/story-map/user-tasks/ACT-006-task-004.md +46 -0
- package/docs/story-map/user-tasks/ACT-007-task-001.md +48 -0
- package/docs/story-map/user-tasks/ACT-007-task-002.md +47 -0
- package/docs/story-map/user-tasks/ACT-007-task-003.md +47 -0
- package/docs/story-map/user-tasks/ACT-007-task-004.md +48 -0
- package/docs/story-map/user-tasks/ACT-008-task-001.md +48 -0
- package/docs/story-map/user-tasks/ACT-008-task-002.md +48 -0
- package/docs/story-map/user-tasks/ACT-008-task-003.md +47 -0
- package/docs/story-map/user-tasks/ACT-008-task-004.md +48 -0
- package/docs/story-map/walking-skeleton.md +95 -0
- package/docs/value-proposition-canvas.md +171 -0
- package/features/mcp-server/query-vpc.feature +48 -0
- package/features/mcp-server/read-reference.feature +41 -0
- package/features/mcp-server/read-skill.feature +33 -0
- package/features/mcp-server/search-guidance.feature +42 -0
- package/features/mcp-server/suggest-next-step.feature +61 -0
- package/features/mcp-server/validate-gherkin.feature +54 -0
- package/mcp-server/QUICKSTART.md +172 -0
- package/mcp-server/README.md +171 -0
- package/mcp-server/dist/index.d.ts +12 -0
- package/mcp-server/dist/index.js +296 -0
- package/mcp-server/dist/repository.d.ts +59 -0
- package/mcp-server/dist/repository.js +211 -0
- package/mcp-server/dist/tools/gherkin-validator.d.ts +16 -0
- package/mcp-server/dist/tools/gherkin-validator.js +152 -0
- package/mcp-server/dist/tools/guidance-searcher.d.ts +11 -0
- package/mcp-server/dist/tools/guidance-searcher.js +34 -0
- package/mcp-server/dist/tools/next-step-suggester.d.ts +16 -0
- package/mcp-server/dist/tools/next-step-suggester.js +210 -0
- package/mcp-server/dist/tools/reference-reader.d.ts +17 -0
- package/mcp-server/dist/tools/reference-reader.js +57 -0
- package/mcp-server/dist/tools/skill-reader.d.ts +17 -0
- package/mcp-server/dist/tools/skill-reader.js +38 -0
- package/mcp-server/dist/tools/vpc-querier.d.ts +37 -0
- package/mcp-server/dist/tools/vpc-querier.js +158 -0
- package/mcp-server/package.json +42 -0
- package/mcp-server/src/index.ts +331 -0
- package/mcp-server/src/repository.ts +254 -0
- package/mcp-server/src/tools/gherkin-validator.ts +206 -0
- package/mcp-server/src/tools/guidance-searcher.ts +42 -0
- package/mcp-server/src/tools/next-step-suggester.ts +243 -0
- package/mcp-server/src/tools/reference-reader.ts +71 -0
- package/mcp-server/src/tools/skill-reader.ts +47 -0
- package/mcp-server/src/tools/vpc-querier.ts +201 -0
- package/mcp-server/tsconfig.json +17 -0
- package/package.json +8 -2
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
export class ReferenceReader {
|
|
2
|
+
repository;
|
|
3
|
+
constructor(repository) {
|
|
4
|
+
this.repository = repository;
|
|
5
|
+
}
|
|
6
|
+
async readReference(referencePath) {
|
|
7
|
+
// Parse path like "clean-code/solid.md"
|
|
8
|
+
const parts = referencePath.split("/");
|
|
9
|
+
if (parts.length !== 2) {
|
|
10
|
+
throw new Error("Reference path must be in format 'skill-name/reference-name.md'");
|
|
11
|
+
}
|
|
12
|
+
const [skillName, referenceName] = parts;
|
|
13
|
+
try {
|
|
14
|
+
const content = await this.repository.readReference(skillName, referenceName);
|
|
15
|
+
return {
|
|
16
|
+
content: [
|
|
17
|
+
{
|
|
18
|
+
type: "text",
|
|
19
|
+
text: content,
|
|
20
|
+
},
|
|
21
|
+
],
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
const references = await this.repository.listReferences(skillName);
|
|
26
|
+
if (references.length === 0) {
|
|
27
|
+
throw new Error(`Skill '${skillName}' has no reference documents or doesn't exist`);
|
|
28
|
+
}
|
|
29
|
+
const availableRefs = references.map((r) => r.name).join(", ");
|
|
30
|
+
throw new Error(`Reference '${referenceName}' not found in skill '${skillName}'. Available references: ${availableRefs}`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
async listReferences(skillName) {
|
|
34
|
+
const references = await this.repository.listReferences(skillName);
|
|
35
|
+
if (references.length === 0) {
|
|
36
|
+
return {
|
|
37
|
+
content: [
|
|
38
|
+
{
|
|
39
|
+
type: "text",
|
|
40
|
+
text: `Skill '${skillName}' has no reference documents.`,
|
|
41
|
+
},
|
|
42
|
+
],
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
const text = references
|
|
46
|
+
.map((ref) => `- ${ref.name}`)
|
|
47
|
+
.join("\n");
|
|
48
|
+
return {
|
|
49
|
+
content: [
|
|
50
|
+
{
|
|
51
|
+
type: "text",
|
|
52
|
+
text: `# References for ${skillName} (${references.length})\n\n${text}`,
|
|
53
|
+
},
|
|
54
|
+
],
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { MethodologyRepository } from "../repository.js";
|
|
2
|
+
export declare class SkillReader {
|
|
3
|
+
private repository;
|
|
4
|
+
constructor(repository: MethodologyRepository);
|
|
5
|
+
readSkill(skillName: string): Promise<{
|
|
6
|
+
content: {
|
|
7
|
+
type: "text";
|
|
8
|
+
text: string;
|
|
9
|
+
}[];
|
|
10
|
+
}>;
|
|
11
|
+
listSkills(): Promise<{
|
|
12
|
+
content: {
|
|
13
|
+
type: "text";
|
|
14
|
+
text: string;
|
|
15
|
+
}[];
|
|
16
|
+
}>;
|
|
17
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export class SkillReader {
|
|
2
|
+
repository;
|
|
3
|
+
constructor(repository) {
|
|
4
|
+
this.repository = repository;
|
|
5
|
+
}
|
|
6
|
+
async readSkill(skillName) {
|
|
7
|
+
try {
|
|
8
|
+
const content = await this.repository.readSkill(skillName);
|
|
9
|
+
return {
|
|
10
|
+
content: [
|
|
11
|
+
{
|
|
12
|
+
type: "text",
|
|
13
|
+
text: content,
|
|
14
|
+
},
|
|
15
|
+
],
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
catch (error) {
|
|
19
|
+
const skills = await this.repository.listSkills();
|
|
20
|
+
const availableSkills = skills.map((s) => s.name).join(", ");
|
|
21
|
+
throw new Error(`Skill '${skillName}' not found. Available skills: ${availableSkills}`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
async listSkills() {
|
|
25
|
+
const skills = await this.repository.listSkills();
|
|
26
|
+
const text = skills
|
|
27
|
+
.map((skill) => `**${skill.displayName}** (${skill.name}):\n ${skill.description}`)
|
|
28
|
+
.join("\n\n");
|
|
29
|
+
return {
|
|
30
|
+
content: [
|
|
31
|
+
{
|
|
32
|
+
type: "text",
|
|
33
|
+
text: `# Available Skills (${skills.length})\n\n${text}`,
|
|
34
|
+
},
|
|
35
|
+
],
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { MethodologyRepository } from "../repository.js";
|
|
2
|
+
export declare class VPCQuerier {
|
|
3
|
+
private repository;
|
|
4
|
+
constructor(repository: MethodologyRepository);
|
|
5
|
+
getSegments(): Promise<{
|
|
6
|
+
content: {
|
|
7
|
+
type: "text";
|
|
8
|
+
text: string;
|
|
9
|
+
}[];
|
|
10
|
+
}>;
|
|
11
|
+
getCustomerJobs(segmentName: string): Promise<{
|
|
12
|
+
content: {
|
|
13
|
+
type: "text";
|
|
14
|
+
text: string;
|
|
15
|
+
}[];
|
|
16
|
+
}>;
|
|
17
|
+
getPains(segmentName: string): Promise<{
|
|
18
|
+
content: {
|
|
19
|
+
type: "text";
|
|
20
|
+
text: string;
|
|
21
|
+
}[];
|
|
22
|
+
}>;
|
|
23
|
+
getGains(segmentName: string): Promise<{
|
|
24
|
+
content: {
|
|
25
|
+
type: "text";
|
|
26
|
+
text: string;
|
|
27
|
+
}[];
|
|
28
|
+
}>;
|
|
29
|
+
search(query: string): Promise<{
|
|
30
|
+
content: {
|
|
31
|
+
type: "text";
|
|
32
|
+
text: string;
|
|
33
|
+
}[];
|
|
34
|
+
}>;
|
|
35
|
+
private parseSegments;
|
|
36
|
+
private extractList;
|
|
37
|
+
}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
export class VPCQuerier {
|
|
2
|
+
repository;
|
|
3
|
+
constructor(repository) {
|
|
4
|
+
this.repository = repository;
|
|
5
|
+
}
|
|
6
|
+
async getSegments() {
|
|
7
|
+
const vpc = await this.repository.readVPC();
|
|
8
|
+
const segments = this.parseSegments(vpc);
|
|
9
|
+
const text = segments.map((s) => `- ${s.name}`).join("\n");
|
|
10
|
+
return {
|
|
11
|
+
content: [
|
|
12
|
+
{
|
|
13
|
+
type: "text",
|
|
14
|
+
text: `# Customer Segments (${segments.length})\n\n${text}`,
|
|
15
|
+
},
|
|
16
|
+
],
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
async getCustomerJobs(segmentName) {
|
|
20
|
+
const vpc = await this.repository.readVPC();
|
|
21
|
+
const segments = this.parseSegments(vpc);
|
|
22
|
+
const segment = segments.find((s) => s.name === segmentName);
|
|
23
|
+
if (!segment) {
|
|
24
|
+
const available = segments.map((s) => s.name).join(", ");
|
|
25
|
+
throw new Error(`Segment '${segmentName}' not found. Available segments: ${available}`);
|
|
26
|
+
}
|
|
27
|
+
const text = segment.customerJobs
|
|
28
|
+
.map((job, i) => `${i + 1}. ${job}`)
|
|
29
|
+
.join("\n");
|
|
30
|
+
return {
|
|
31
|
+
content: [
|
|
32
|
+
{
|
|
33
|
+
type: "text",
|
|
34
|
+
text: `# Customer Jobs for ${segmentName}\n\n${text}`,
|
|
35
|
+
},
|
|
36
|
+
],
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
async getPains(segmentName) {
|
|
40
|
+
const vpc = await this.repository.readVPC();
|
|
41
|
+
const segments = this.parseSegments(vpc);
|
|
42
|
+
const segment = segments.find((s) => s.name === segmentName);
|
|
43
|
+
if (!segment) {
|
|
44
|
+
const available = segments.map((s) => s.name).join(", ");
|
|
45
|
+
throw new Error(`Segment '${segmentName}' not found. Available segments: ${available}`);
|
|
46
|
+
}
|
|
47
|
+
const text = segment.pains
|
|
48
|
+
.map((pain, i) => `${i + 1}. ${pain}`)
|
|
49
|
+
.join("\n");
|
|
50
|
+
return {
|
|
51
|
+
content: [
|
|
52
|
+
{
|
|
53
|
+
type: "text",
|
|
54
|
+
text: `# Pains for ${segmentName}\n\n${text}`,
|
|
55
|
+
},
|
|
56
|
+
],
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
async getGains(segmentName) {
|
|
60
|
+
const vpc = await this.repository.readVPC();
|
|
61
|
+
const segments = this.parseSegments(vpc);
|
|
62
|
+
const segment = segments.find((s) => s.name === segmentName);
|
|
63
|
+
if (!segment) {
|
|
64
|
+
const available = segments.map((s) => s.name).join(", ");
|
|
65
|
+
throw new Error(`Segment '${segmentName}' not found. Available segments: ${available}`);
|
|
66
|
+
}
|
|
67
|
+
const text = segment.gains
|
|
68
|
+
.map((gain, i) => `${i + 1}. ${gain}`)
|
|
69
|
+
.join("\n");
|
|
70
|
+
return {
|
|
71
|
+
content: [
|
|
72
|
+
{
|
|
73
|
+
type: "text",
|
|
74
|
+
text: `# Gains for ${segmentName}\n\n${text}`,
|
|
75
|
+
},
|
|
76
|
+
],
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
async search(query) {
|
|
80
|
+
const vpc = await this.repository.readVPC();
|
|
81
|
+
const segments = this.parseSegments(vpc);
|
|
82
|
+
const results = [];
|
|
83
|
+
const queryLower = query.toLowerCase();
|
|
84
|
+
for (const segment of segments) {
|
|
85
|
+
for (const pain of segment.pains) {
|
|
86
|
+
if (pain.toLowerCase().includes(queryLower)) {
|
|
87
|
+
results.push({ segment: segment.name, type: "pain", text: pain });
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
for (const gain of segment.gains) {
|
|
91
|
+
if (gain.toLowerCase().includes(queryLower)) {
|
|
92
|
+
results.push({ segment: segment.name, type: "gain", text: gain });
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
if (results.length === 0) {
|
|
97
|
+
return {
|
|
98
|
+
content: [
|
|
99
|
+
{
|
|
100
|
+
type: "text",
|
|
101
|
+
text: `No results found for '${query}' in Value Proposition Canvas.`,
|
|
102
|
+
},
|
|
103
|
+
],
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
const text = results
|
|
107
|
+
.map((r) => `**[${r.segment}] ${r.type.toUpperCase()}:** ${r.text}`)
|
|
108
|
+
.join("\n\n");
|
|
109
|
+
return {
|
|
110
|
+
content: [
|
|
111
|
+
{
|
|
112
|
+
type: "text",
|
|
113
|
+
text: `# VPC Search Results for '${query}'\n\n${text}`,
|
|
114
|
+
},
|
|
115
|
+
],
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
parseSegments(vpcContent) {
|
|
119
|
+
const segments = [];
|
|
120
|
+
const segmentRegex = /## Segment: (.+?)\n/g;
|
|
121
|
+
let match;
|
|
122
|
+
const segmentStarts = [];
|
|
123
|
+
while ((match = segmentRegex.exec(vpcContent)) !== null) {
|
|
124
|
+
segmentStarts.push({ name: match[1], index: match.index });
|
|
125
|
+
}
|
|
126
|
+
for (let i = 0; i < segmentStarts.length; i++) {
|
|
127
|
+
const start = segmentStarts[i];
|
|
128
|
+
const end = i + 1 < segmentStarts.length ? segmentStarts[i + 1].index : vpcContent.length;
|
|
129
|
+
const segmentContent = vpcContent.slice(start.index, end);
|
|
130
|
+
segments.push({
|
|
131
|
+
name: start.name,
|
|
132
|
+
customerJobs: this.extractList(segmentContent, "#### Customer Jobs"),
|
|
133
|
+
pains: this.extractList(segmentContent, "#### Pains"),
|
|
134
|
+
gains: this.extractList(segmentContent, "#### Gains"),
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
return segments;
|
|
138
|
+
}
|
|
139
|
+
extractList(content, heading) {
|
|
140
|
+
const headingIndex = content.indexOf(heading);
|
|
141
|
+
if (headingIndex === -1)
|
|
142
|
+
return [];
|
|
143
|
+
const afterHeading = content.slice(headingIndex + heading.length);
|
|
144
|
+
const nextHeadingIndex = afterHeading.search(/####|###|##/);
|
|
145
|
+
const section = nextHeadingIndex === -1
|
|
146
|
+
? afterHeading
|
|
147
|
+
: afterHeading.slice(0, nextHeadingIndex);
|
|
148
|
+
const lines = section.split("\n");
|
|
149
|
+
const items = [];
|
|
150
|
+
for (const line of lines) {
|
|
151
|
+
const match = line.match(/^\d+\.\s+\*\*(.+?)\*\*\s*—\s*(.+)/);
|
|
152
|
+
if (match) {
|
|
153
|
+
items.push(`${match[1]} — ${match[2]}`);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return items;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@schilling.mark.a/software-methodology-mcp",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "MCP server providing structured access to software methodology skills, references, and guidance",
|
|
6
|
+
"bin": {
|
|
7
|
+
"software-methodology-mcp": "./dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "tsc",
|
|
11
|
+
"watch": "tsc --watch",
|
|
12
|
+
"prepare": "npm run build",
|
|
13
|
+
"test": "jest",
|
|
14
|
+
"test:watch": "jest --watch"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"mcp",
|
|
18
|
+
"mcp-server",
|
|
19
|
+
"methodology",
|
|
20
|
+
"software-development",
|
|
21
|
+
"bdd",
|
|
22
|
+
"atdd",
|
|
23
|
+
"clean-code"
|
|
24
|
+
],
|
|
25
|
+
"author": "Mark Schilling",
|
|
26
|
+
"license": "MIT",
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"@modelcontextprotocol/sdk": "^0.5.0"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@types/node": "^20.11.0",
|
|
32
|
+
"typescript": "^5.3.3",
|
|
33
|
+
"jest": "^29.7.0",
|
|
34
|
+
"@types/jest": "^29.5.11"
|
|
35
|
+
},
|
|
36
|
+
"files": [
|
|
37
|
+
"dist/",
|
|
38
|
+
"../docs/**/*.md",
|
|
39
|
+
"../**/SKILL.md",
|
|
40
|
+
"../**/references/**/*.md"
|
|
41
|
+
]
|
|
42
|
+
}
|
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Software Methodology MCP Server
|
|
5
|
+
*
|
|
6
|
+
* Provides structured access to software methodology documentation:
|
|
7
|
+
* - 11 skills (product-strategy through continuous-improvement)
|
|
8
|
+
* - Reference documents for each skill
|
|
9
|
+
* - Value Proposition Canvas queries
|
|
10
|
+
* - Guidance search across all content
|
|
11
|
+
* - Next-step suggestions based on current context
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
15
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
16
|
+
import {
|
|
17
|
+
CallToolRequestSchema,
|
|
18
|
+
ListToolsRequestSchema,
|
|
19
|
+
ListResourcesRequestSchema,
|
|
20
|
+
ReadResourceRequestSchema,
|
|
21
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
22
|
+
import { MethodologyRepository } from "./repository.js";
|
|
23
|
+
import { SkillReader } from "./tools/skill-reader.js";
|
|
24
|
+
import { ReferenceReader } from "./tools/reference-reader.js";
|
|
25
|
+
import { VPCQuerier } from "./tools/vpc-querier.js";
|
|
26
|
+
import { GuidanceSearcher } from "./tools/guidance-searcher.js";
|
|
27
|
+
import { NextStepSuggester } from "./tools/next-step-suggester.js";
|
|
28
|
+
import { GherkinValidator } from "./tools/gherkin-validator.js";
|
|
29
|
+
|
|
30
|
+
const server = new Server(
|
|
31
|
+
{
|
|
32
|
+
name: "software-methodology",
|
|
33
|
+
version: "0.1.0",
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
capabilities: {
|
|
37
|
+
tools: {},
|
|
38
|
+
resources: {},
|
|
39
|
+
},
|
|
40
|
+
}
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
// Initialize repository
|
|
44
|
+
const repository = new MethodologyRepository();
|
|
45
|
+
|
|
46
|
+
// Initialize tools
|
|
47
|
+
const skillReader = new SkillReader(repository);
|
|
48
|
+
const referenceReader = new ReferenceReader(repository);
|
|
49
|
+
const vpcQuerier = new VPCQuerier(repository);
|
|
50
|
+
const guidanceSearcher = new GuidanceSearcher(repository);
|
|
51
|
+
const nextStepSuggester = new NextStepSuggester(repository);
|
|
52
|
+
const gherkinValidator = new GherkinValidator(repository);
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* List all available tools
|
|
56
|
+
*/
|
|
57
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
58
|
+
return {
|
|
59
|
+
tools: [
|
|
60
|
+
{
|
|
61
|
+
name: "read_skill",
|
|
62
|
+
description: "Read main SKILL.md documentation for a specific skill",
|
|
63
|
+
inputSchema: {
|
|
64
|
+
type: "object",
|
|
65
|
+
properties: {
|
|
66
|
+
skill_name: {
|
|
67
|
+
type: "string",
|
|
68
|
+
description: "Name of the skill (e.g., 'atdd-workflow', 'clean-code', 'product-strategy')",
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
required: ["skill_name"],
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
name: "list_skills",
|
|
76
|
+
description: "List all available skills in the methodology",
|
|
77
|
+
inputSchema: {
|
|
78
|
+
type: "object",
|
|
79
|
+
properties: {},
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
name: "read_reference",
|
|
84
|
+
description: "Read a specific reference document from a skill",
|
|
85
|
+
inputSchema: {
|
|
86
|
+
type: "object",
|
|
87
|
+
properties: {
|
|
88
|
+
reference_path: {
|
|
89
|
+
type: "string",
|
|
90
|
+
description: "Path to reference (e.g., 'clean-code/solid.md', 'atdd-workflow/red-phase.md')",
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
required: ["reference_path"],
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
name: "list_references",
|
|
98
|
+
description: "List all reference documents for a specific skill",
|
|
99
|
+
inputSchema: {
|
|
100
|
+
type: "object",
|
|
101
|
+
properties: {
|
|
102
|
+
skill_name: {
|
|
103
|
+
type: "string",
|
|
104
|
+
description: "Name of the skill",
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
required: ["skill_name"],
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
name: "query_vpc_segments",
|
|
112
|
+
description: "Get all customer segments from Value Proposition Canvas",
|
|
113
|
+
inputSchema: {
|
|
114
|
+
type: "object",
|
|
115
|
+
properties: {},
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
name: "query_vpc_customer_jobs",
|
|
120
|
+
description: "Get customer jobs for a specific segment",
|
|
121
|
+
inputSchema: {
|
|
122
|
+
type: "object",
|
|
123
|
+
properties: {
|
|
124
|
+
segment: {
|
|
125
|
+
type: "string",
|
|
126
|
+
description: "Customer segment name (e.g., 'Individual Developer')",
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
required: ["segment"],
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
name: "query_vpc_pains",
|
|
134
|
+
description: "Get pains for a specific segment",
|
|
135
|
+
inputSchema: {
|
|
136
|
+
type: "object",
|
|
137
|
+
properties: {
|
|
138
|
+
segment: {
|
|
139
|
+
type: "string",
|
|
140
|
+
description: "Customer segment name",
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
required: ["segment"],
|
|
144
|
+
},
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
name: "query_vpc_gains",
|
|
148
|
+
description: "Get gains for a specific segment",
|
|
149
|
+
inputSchema: {
|
|
150
|
+
type: "object",
|
|
151
|
+
properties: {
|
|
152
|
+
segment: {
|
|
153
|
+
type: "string",
|
|
154
|
+
description: "Customer segment name",
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
required: ["segment"],
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
name: "search_vpc",
|
|
162
|
+
description: "Search for specific terms across VPC pains and gains",
|
|
163
|
+
inputSchema: {
|
|
164
|
+
type: "object",
|
|
165
|
+
properties: {
|
|
166
|
+
query: {
|
|
167
|
+
type: "string",
|
|
168
|
+
description: "Search term",
|
|
169
|
+
},
|
|
170
|
+
},
|
|
171
|
+
required: ["query"],
|
|
172
|
+
},
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
name: "search_guidance",
|
|
176
|
+
description: "Search across all skills and references for relevant guidance",
|
|
177
|
+
inputSchema: {
|
|
178
|
+
type: "object",
|
|
179
|
+
properties: {
|
|
180
|
+
query: {
|
|
181
|
+
type: "string",
|
|
182
|
+
description: "What you're looking for (e.g., 'test before code', 'many parameters', 'hard to test')",
|
|
183
|
+
},
|
|
184
|
+
},
|
|
185
|
+
required: ["query"],
|
|
186
|
+
},
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
name: "suggest_next_step",
|
|
190
|
+
description: "Get suggestion for next methodology step based on current project state",
|
|
191
|
+
inputSchema: {
|
|
192
|
+
type: "object",
|
|
193
|
+
properties: {
|
|
194
|
+
project_path: {
|
|
195
|
+
type: "string",
|
|
196
|
+
description: "Path to project directory (optional, defaults to current directory)",
|
|
197
|
+
},
|
|
198
|
+
},
|
|
199
|
+
},
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
name: "validate_gherkin",
|
|
203
|
+
description: "Validate a Gherkin feature file against methodology best practices",
|
|
204
|
+
inputSchema: {
|
|
205
|
+
type: "object",
|
|
206
|
+
properties: {
|
|
207
|
+
content: {
|
|
208
|
+
type: "string",
|
|
209
|
+
description: "Gherkin feature file content to validate",
|
|
210
|
+
},
|
|
211
|
+
},
|
|
212
|
+
required: ["content"],
|
|
213
|
+
},
|
|
214
|
+
},
|
|
215
|
+
],
|
|
216
|
+
};
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Handle tool execution
|
|
221
|
+
*/
|
|
222
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
223
|
+
const { name, arguments: args } = request.params;
|
|
224
|
+
|
|
225
|
+
try {
|
|
226
|
+
switch (name) {
|
|
227
|
+
case "read_skill":
|
|
228
|
+
return await skillReader.readSkill(args?.skill_name as string);
|
|
229
|
+
|
|
230
|
+
case "list_skills":
|
|
231
|
+
return await skillReader.listSkills();
|
|
232
|
+
|
|
233
|
+
case "read_reference":
|
|
234
|
+
return await referenceReader.readReference(args?.reference_path as string);
|
|
235
|
+
|
|
236
|
+
case "list_references":
|
|
237
|
+
return await referenceReader.listReferences(args?.skill_name as string);
|
|
238
|
+
|
|
239
|
+
case "query_vpc_segments":
|
|
240
|
+
return await vpcQuerier.getSegments();
|
|
241
|
+
|
|
242
|
+
case "query_vpc_customer_jobs":
|
|
243
|
+
return await vpcQuerier.getCustomerJobs(args?.segment as string);
|
|
244
|
+
|
|
245
|
+
case "query_vpc_pains":
|
|
246
|
+
return await vpcQuerier.getPains(args?.segment as string);
|
|
247
|
+
|
|
248
|
+
case "query_vpc_gains":
|
|
249
|
+
return await vpcQuerier.getGains(args?.segment as string);
|
|
250
|
+
|
|
251
|
+
case "search_vpc":
|
|
252
|
+
return await vpcQuerier.search(args?.query as string);
|
|
253
|
+
|
|
254
|
+
case "search_guidance":
|
|
255
|
+
return await guidanceSearcher.search(args?.query as string);
|
|
256
|
+
|
|
257
|
+
case "suggest_next_step":
|
|
258
|
+
return await nextStepSuggester.suggest(args?.project_path as string | undefined);
|
|
259
|
+
|
|
260
|
+
case "validate_gherkin":
|
|
261
|
+
return await gherkinValidator.validate(args?.content as string);
|
|
262
|
+
|
|
263
|
+
default:
|
|
264
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
265
|
+
}
|
|
266
|
+
} catch (error) {
|
|
267
|
+
return {
|
|
268
|
+
content: [
|
|
269
|
+
{
|
|
270
|
+
type: "text",
|
|
271
|
+
text: `Error: ${error instanceof Error ? error.message : String(error)}`,
|
|
272
|
+
},
|
|
273
|
+
],
|
|
274
|
+
isError: true,
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* List resources (alternative access pattern to tools)
|
|
281
|
+
*/
|
|
282
|
+
server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
283
|
+
const skills = await repository.listSkills();
|
|
284
|
+
|
|
285
|
+
return {
|
|
286
|
+
resources: skills.map((skill) => ({
|
|
287
|
+
uri: `methodology://skill/${skill.name}`,
|
|
288
|
+
mimeType: "text/markdown",
|
|
289
|
+
name: skill.displayName,
|
|
290
|
+
description: skill.description,
|
|
291
|
+
})),
|
|
292
|
+
};
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Read resource
|
|
297
|
+
*/
|
|
298
|
+
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
299
|
+
const uri = request.params.uri;
|
|
300
|
+
|
|
301
|
+
if (uri.startsWith("methodology://skill/")) {
|
|
302
|
+
const skillName = uri.replace("methodology://skill/", "");
|
|
303
|
+
const result = await skillReader.readSkill(skillName);
|
|
304
|
+
|
|
305
|
+
return {
|
|
306
|
+
contents: [
|
|
307
|
+
{
|
|
308
|
+
uri,
|
|
309
|
+
mimeType: "text/markdown",
|
|
310
|
+
text: result.content[0].text,
|
|
311
|
+
},
|
|
312
|
+
],
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
throw new Error(`Unknown resource URI: ${uri}`);
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Start the server
|
|
321
|
+
*/
|
|
322
|
+
async function main() {
|
|
323
|
+
const transport = new StdioServerTransport();
|
|
324
|
+
await server.connect(transport);
|
|
325
|
+
console.error("Software Methodology MCP server running on stdio");
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
main().catch((error) => {
|
|
329
|
+
console.error("Fatal error in main():", error);
|
|
330
|
+
process.exit(1);
|
|
331
|
+
});
|