@godpowers/mcp 2.6.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/README.md +37 -0
- package/bin/godpowers-mcp.js +147 -0
- package/lib/runtime.js +82 -0
- package/lib/server.js +26 -0
- package/lib/setup.js +133 -0
- package/lib/tools.js +202 -0
- package/package.json +52 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) Godpowers
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# @godpowers/mcp
|
|
2
|
+
|
|
3
|
+
- [DECISION] `@godpowers/mcp` is the first-party read-only MCP companion package for Godpowers.
|
|
4
|
+
- [DECISION] The main `godpowers` package stays dependency-free at runtime, and the MCP SDK dependency lives only in this companion package.
|
|
5
|
+
- [DECISION] Version 2.6.0 exposes five tools: `status`, `next`, `gate_check`, `lint_artifact`, and `trace_requirement`.
|
|
6
|
+
- [DECISION] Mutation tools are intentionally absent through the 3.0.0 release.
|
|
7
|
+
|
|
8
|
+
## Install
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npm install -g godpowers @godpowers/mcp
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Run
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
godpowers-mcp serve --project=.
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Codex Setup
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
godpowers-mcp setup --host=codex --project=.
|
|
24
|
+
godpowers-mcp setup --host=codex --project=. --write
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
- [DECISION] The first command prints a registration plan without writing files.
|
|
28
|
+
- [DECISION] The second command writes a managed `[mcp_servers.godpowers]` block to `~/.codex/config.toml`.
|
|
29
|
+
- [DECISION] No automatic host registration runs during package install.
|
|
30
|
+
|
|
31
|
+
## Tool Boundary
|
|
32
|
+
|
|
33
|
+
- [DECISION] `status` wraps `lib/dashboard.js` and returns rendered dashboard text plus structured status.
|
|
34
|
+
- [DECISION] `next` wraps `lib/dashboard.js` and returns the recommended next command from disk state.
|
|
35
|
+
- [DECISION] `gate_check` wraps `lib/gate.js` and returns the executable tier gate verdict.
|
|
36
|
+
- [DECISION] `lint_artifact` wraps `lib/artifact-linter.js` for one file inside the project root.
|
|
37
|
+
- [DECISION] `trace_requirement` wraps `lib/requirements.js` and returns requirement, roadmap, linkage, and ledger evidence.
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const path = require('path');
|
|
4
|
+
|
|
5
|
+
const server = require('../lib/server');
|
|
6
|
+
const setup = require('../lib/setup');
|
|
7
|
+
|
|
8
|
+
function parseArgs(argv, cwd = process.cwd()) {
|
|
9
|
+
const args = argv.slice(2);
|
|
10
|
+
const opts = {
|
|
11
|
+
command: 'serve',
|
|
12
|
+
project: cwd,
|
|
13
|
+
runtimeRoot: null,
|
|
14
|
+
host: 'codex',
|
|
15
|
+
write: false,
|
|
16
|
+
json: false,
|
|
17
|
+
homeDir: null,
|
|
18
|
+
help: false
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
if (args[0] && !args[0].startsWith('-')) {
|
|
22
|
+
opts.command = args[0];
|
|
23
|
+
args.shift();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
for (let i = 0; i < args.length; i++) {
|
|
27
|
+
const arg = args[i];
|
|
28
|
+
switch (arg) {
|
|
29
|
+
case '--project':
|
|
30
|
+
if (args[i + 1]) {
|
|
31
|
+
opts.project = path.resolve(args[i + 1]);
|
|
32
|
+
i++;
|
|
33
|
+
}
|
|
34
|
+
break;
|
|
35
|
+
case '--runtime-root':
|
|
36
|
+
if (args[i + 1]) {
|
|
37
|
+
opts.runtimeRoot = path.resolve(args[i + 1]);
|
|
38
|
+
i++;
|
|
39
|
+
}
|
|
40
|
+
break;
|
|
41
|
+
case '--host':
|
|
42
|
+
if (args[i + 1]) {
|
|
43
|
+
opts.host = args[i + 1];
|
|
44
|
+
i++;
|
|
45
|
+
}
|
|
46
|
+
break;
|
|
47
|
+
case '--home':
|
|
48
|
+
if (args[i + 1]) {
|
|
49
|
+
opts.homeDir = path.resolve(args[i + 1]);
|
|
50
|
+
i++;
|
|
51
|
+
}
|
|
52
|
+
break;
|
|
53
|
+
case '--write':
|
|
54
|
+
opts.write = true;
|
|
55
|
+
break;
|
|
56
|
+
case '--json':
|
|
57
|
+
opts.json = true;
|
|
58
|
+
break;
|
|
59
|
+
case '-h':
|
|
60
|
+
case '--help':
|
|
61
|
+
opts.help = true;
|
|
62
|
+
break;
|
|
63
|
+
default:
|
|
64
|
+
if (arg.startsWith('--project=')) {
|
|
65
|
+
opts.project = path.resolve(arg.slice('--project='.length));
|
|
66
|
+
} else if (arg.startsWith('--runtime-root=')) {
|
|
67
|
+
opts.runtimeRoot = path.resolve(arg.slice('--runtime-root='.length));
|
|
68
|
+
} else if (arg.startsWith('--host=')) {
|
|
69
|
+
opts.host = arg.slice('--host='.length);
|
|
70
|
+
} else if (arg.startsWith('--home=')) {
|
|
71
|
+
opts.homeDir = path.resolve(arg.slice('--home='.length));
|
|
72
|
+
}
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return opts;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function renderHelp() {
|
|
81
|
+
return [
|
|
82
|
+
'Godpowers MCP',
|
|
83
|
+
'',
|
|
84
|
+
'Usage:',
|
|
85
|
+
' godpowers-mcp serve --project=.',
|
|
86
|
+
' godpowers-mcp setup --host=codex --project=. --write',
|
|
87
|
+
'',
|
|
88
|
+
'Commands:',
|
|
89
|
+
' serve Run the read-only MCP server over stdio.',
|
|
90
|
+
' setup Print or write an explicit host registration.',
|
|
91
|
+
'',
|
|
92
|
+
'Options:',
|
|
93
|
+
' --project=<path> Project root read by MCP tools.',
|
|
94
|
+
' --runtime-root=<path> Godpowers runtime root for local checkouts.',
|
|
95
|
+
' --host=<name> Host registration target. Currently codex.',
|
|
96
|
+
' --home=<path> Home directory for setup tests or explicit installs.',
|
|
97
|
+
' --write Write setup output. Without this, setup is read-only.',
|
|
98
|
+
' --json Emit JSON for setup.',
|
|
99
|
+
' -h, --help Show this help.'
|
|
100
|
+
].join('\n');
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async function main() {
|
|
104
|
+
const opts = parseArgs(process.argv);
|
|
105
|
+
if (opts.help || opts.command === 'help') {
|
|
106
|
+
console.log(renderHelp());
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (opts.command === 'setup') {
|
|
111
|
+
const plan = setup.setupPlan({
|
|
112
|
+
host: opts.host,
|
|
113
|
+
projectRoot: opts.project,
|
|
114
|
+
runtimeRoot: opts.runtimeRoot,
|
|
115
|
+
homeDir: opts.homeDir
|
|
116
|
+
});
|
|
117
|
+
const result = opts.write ? setup.writeRegistration(plan) : plan;
|
|
118
|
+
if (opts.json) {
|
|
119
|
+
console.log(JSON.stringify(result, null, 2));
|
|
120
|
+
} else {
|
|
121
|
+
console.log(setup.render(result));
|
|
122
|
+
}
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (opts.command !== 'serve') {
|
|
127
|
+
console.error(`Unknown command: ${opts.command}`);
|
|
128
|
+
process.exit(1);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
await server.serveStdio({
|
|
132
|
+
projectRoot: opts.project,
|
|
133
|
+
runtimeRoot: opts.runtimeRoot
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (require.main === module) {
|
|
138
|
+
main().catch((error) => {
|
|
139
|
+
console.error(`Godpowers MCP failed: ${error.message}`);
|
|
140
|
+
process.exit(1);
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
module.exports = {
|
|
145
|
+
parseArgs,
|
|
146
|
+
renderHelp
|
|
147
|
+
};
|
package/lib/runtime.js
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
function exists(filePath) {
|
|
5
|
+
return fs.existsSync(filePath);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function isRuntimeRoot(root) {
|
|
9
|
+
if (!root) return false;
|
|
10
|
+
const pkgPath = path.join(root, 'package.json');
|
|
11
|
+
if (!exists(pkgPath)) return false;
|
|
12
|
+
if (!exists(path.join(root, 'lib', 'dashboard.js'))) return false;
|
|
13
|
+
try {
|
|
14
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
15
|
+
return pkg.name === 'godpowers';
|
|
16
|
+
} catch (error) {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function addCandidate(candidates, value) {
|
|
22
|
+
if (!value) return;
|
|
23
|
+
const resolved = path.resolve(value);
|
|
24
|
+
if (!candidates.includes(resolved)) candidates.push(resolved);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function candidateRoots(opts = {}) {
|
|
28
|
+
const candidates = [];
|
|
29
|
+
addCandidate(candidates, opts.runtimeRoot);
|
|
30
|
+
addCandidate(candidates, process.env.GODPOWERS_RUNTIME_ROOT);
|
|
31
|
+
addCandidate(candidates, path.resolve(__dirname, '..', '..', '..'));
|
|
32
|
+
addCandidate(candidates, process.cwd());
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
const pkgPath = require.resolve('godpowers/package.json', {
|
|
36
|
+
paths: [process.cwd(), __dirname]
|
|
37
|
+
});
|
|
38
|
+
addCandidate(candidates, path.dirname(pkgPath));
|
|
39
|
+
} catch (error) {
|
|
40
|
+
// Optional peer dependency. Local checkouts resolve through the candidates above.
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return candidates;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function resolveRuntimeRoot(opts = {}) {
|
|
47
|
+
for (const root of candidateRoots(opts)) {
|
|
48
|
+
if (isRuntimeRoot(root)) return root;
|
|
49
|
+
}
|
|
50
|
+
throw new Error('Could not find a Godpowers runtime root. Pass --runtime-root or install godpowers beside @godpowers/mcp.');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function requireRuntime(moduleName, opts = {}) {
|
|
54
|
+
const root = resolveRuntimeRoot(opts);
|
|
55
|
+
return require(path.join(root, 'lib', `${moduleName}.js`));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function resolveProject(projectRoot) {
|
|
59
|
+
return path.resolve(projectRoot || process.cwd());
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function resolveProjectFile(projectRoot, filePath) {
|
|
63
|
+
const root = resolveProject(projectRoot);
|
|
64
|
+
if (!filePath) throw new Error('file path is required');
|
|
65
|
+
const abs = path.isAbsolute(filePath)
|
|
66
|
+
? path.resolve(filePath)
|
|
67
|
+
: path.resolve(root, filePath);
|
|
68
|
+
const relative = path.relative(root, abs);
|
|
69
|
+
if (relative.startsWith('..') || path.isAbsolute(relative)) {
|
|
70
|
+
throw new Error('artifact path must stay inside the project root');
|
|
71
|
+
}
|
|
72
|
+
return abs;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
module.exports = {
|
|
76
|
+
isRuntimeRoot,
|
|
77
|
+
candidateRoots,
|
|
78
|
+
resolveRuntimeRoot,
|
|
79
|
+
requireRuntime,
|
|
80
|
+
resolveProject,
|
|
81
|
+
resolveProjectFile
|
|
82
|
+
};
|
package/lib/server.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
const { McpServer } = require('@modelcontextprotocol/sdk/server/mcp.js');
|
|
2
|
+
const { StdioServerTransport } = require('@modelcontextprotocol/sdk/server/stdio.js');
|
|
3
|
+
|
|
4
|
+
const pkg = require('../package.json');
|
|
5
|
+
const tools = require('./tools');
|
|
6
|
+
|
|
7
|
+
function createServer(opts = {}) {
|
|
8
|
+
const server = new McpServer({
|
|
9
|
+
name: 'godpowers-mcp',
|
|
10
|
+
version: pkg.version
|
|
11
|
+
});
|
|
12
|
+
tools.registerTools(server, opts);
|
|
13
|
+
return server;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async function serveStdio(opts = {}) {
|
|
17
|
+
const server = createServer(opts);
|
|
18
|
+
const transport = new StdioServerTransport();
|
|
19
|
+
await server.connect(transport);
|
|
20
|
+
return server;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
module.exports = {
|
|
24
|
+
createServer,
|
|
25
|
+
serveStdio
|
|
26
|
+
};
|
package/lib/setup.js
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const os = require('os');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
|
|
5
|
+
const pkg = require('../package.json');
|
|
6
|
+
|
|
7
|
+
const BEGIN = '# godpowers:mcp:begin';
|
|
8
|
+
const END = '# godpowers:mcp:end';
|
|
9
|
+
|
|
10
|
+
function resolveProject(projectRoot) {
|
|
11
|
+
return path.resolve(projectRoot || process.cwd());
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function serverCommand(projectRoot, version = pkg.version) {
|
|
15
|
+
return {
|
|
16
|
+
command: 'npx',
|
|
17
|
+
args: [
|
|
18
|
+
'-y',
|
|
19
|
+
'-p',
|
|
20
|
+
`godpowers@${version}`,
|
|
21
|
+
'-p',
|
|
22
|
+
`@godpowers/mcp@${version}`,
|
|
23
|
+
'godpowers-mcp',
|
|
24
|
+
'serve',
|
|
25
|
+
'--project',
|
|
26
|
+
resolveProject(projectRoot)
|
|
27
|
+
]
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function codexConfigPath(homeDir = os.homedir()) {
|
|
32
|
+
return path.join(homeDir, '.codex', 'config.toml');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function codexBlock(projectRoot, version = pkg.version) {
|
|
36
|
+
const command = serverCommand(projectRoot, version);
|
|
37
|
+
return [
|
|
38
|
+
BEGIN,
|
|
39
|
+
'[mcp_servers.godpowers]',
|
|
40
|
+
`command = ${JSON.stringify(command.command)}`,
|
|
41
|
+
`args = ${JSON.stringify(command.args)}`,
|
|
42
|
+
END
|
|
43
|
+
].join('\n');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function genericJson(projectRoot, version = pkg.version) {
|
|
47
|
+
const command = serverCommand(projectRoot, version);
|
|
48
|
+
return {
|
|
49
|
+
mcpServers: {
|
|
50
|
+
godpowers: command
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function setupPlan(opts = {}) {
|
|
56
|
+
const projectRoot = resolveProject(opts.projectRoot);
|
|
57
|
+
const host = opts.host || 'codex';
|
|
58
|
+
const homeDir = opts.homeDir || os.homedir();
|
|
59
|
+
const version = opts.version || pkg.version;
|
|
60
|
+
return {
|
|
61
|
+
host,
|
|
62
|
+
projectRoot,
|
|
63
|
+
version,
|
|
64
|
+
writes: false,
|
|
65
|
+
automaticRegistration: false,
|
|
66
|
+
codexConfigPath: codexConfigPath(homeDir),
|
|
67
|
+
command: serverCommand(projectRoot, version),
|
|
68
|
+
codexToml: codexBlock(projectRoot, version),
|
|
69
|
+
genericJson: genericJson(projectRoot, version)
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function replaceManagedBlock(current, block) {
|
|
74
|
+
const pattern = new RegExp(`${BEGIN}[\\s\\S]*?${END}`);
|
|
75
|
+
if (pattern.test(current)) {
|
|
76
|
+
return current.replace(pattern, block);
|
|
77
|
+
}
|
|
78
|
+
const prefix = current.trimEnd();
|
|
79
|
+
return `${prefix}${prefix ? '\n\n' : ''}${block}\n`;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function writeRegistration(plan) {
|
|
83
|
+
if (plan.host !== 'codex') {
|
|
84
|
+
throw new Error('Only codex registration writes are supported. Use setup without --write for a JSON snippet.');
|
|
85
|
+
}
|
|
86
|
+
const filePath = plan.codexConfigPath;
|
|
87
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
88
|
+
const current = fs.existsSync(filePath) ? fs.readFileSync(filePath, 'utf8') : '';
|
|
89
|
+
const next = replaceManagedBlock(current, plan.codexToml);
|
|
90
|
+
fs.writeFileSync(filePath, next);
|
|
91
|
+
return {
|
|
92
|
+
...plan,
|
|
93
|
+
writes: true,
|
|
94
|
+
written: filePath
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function render(plan) {
|
|
99
|
+
const lines = [
|
|
100
|
+
'Godpowers MCP setup',
|
|
101
|
+
'',
|
|
102
|
+
`Host: ${plan.host}`,
|
|
103
|
+
`Project: ${plan.projectRoot}`,
|
|
104
|
+
`Package: @godpowers/mcp@${plan.version}`,
|
|
105
|
+
'',
|
|
106
|
+
'Read-only server command:',
|
|
107
|
+
` ${plan.command.command} ${plan.command.args.join(' ')}`,
|
|
108
|
+
'',
|
|
109
|
+
'Codex config block:',
|
|
110
|
+
plan.codexToml,
|
|
111
|
+
'',
|
|
112
|
+
'Generic JSON registration:',
|
|
113
|
+
JSON.stringify(plan.genericJson, null, 2),
|
|
114
|
+
'',
|
|
115
|
+
plan.writes
|
|
116
|
+
? `Wrote explicit registration: ${plan.written}`
|
|
117
|
+
: 'No files written. Re-run setup with --write to opt in.'
|
|
118
|
+
];
|
|
119
|
+
return lines.join('\n');
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
module.exports = {
|
|
123
|
+
BEGIN,
|
|
124
|
+
END,
|
|
125
|
+
serverCommand,
|
|
126
|
+
codexConfigPath,
|
|
127
|
+
codexBlock,
|
|
128
|
+
genericJson,
|
|
129
|
+
setupPlan,
|
|
130
|
+
replaceManagedBlock,
|
|
131
|
+
writeRegistration,
|
|
132
|
+
render
|
|
133
|
+
};
|
package/lib/tools.js
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const z = require('zod/v4');
|
|
3
|
+
|
|
4
|
+
const runtime = require('./runtime');
|
|
5
|
+
|
|
6
|
+
const TOOL_NAMES = [
|
|
7
|
+
'status',
|
|
8
|
+
'next',
|
|
9
|
+
'gate_check',
|
|
10
|
+
'lint_artifact',
|
|
11
|
+
'trace_requirement'
|
|
12
|
+
];
|
|
13
|
+
|
|
14
|
+
function toolResult(value) {
|
|
15
|
+
return {
|
|
16
|
+
content: [
|
|
17
|
+
{
|
|
18
|
+
type: 'text',
|
|
19
|
+
text: JSON.stringify(value, null, 2)
|
|
20
|
+
}
|
|
21
|
+
],
|
|
22
|
+
structuredContent: value
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function toolError(error) {
|
|
27
|
+
return {
|
|
28
|
+
isError: true,
|
|
29
|
+
content: [
|
|
30
|
+
{
|
|
31
|
+
type: 'text',
|
|
32
|
+
text: error && error.message ? error.message : String(error)
|
|
33
|
+
}
|
|
34
|
+
]
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async function withErrors(fn) {
|
|
39
|
+
try {
|
|
40
|
+
return toolResult(await fn());
|
|
41
|
+
} catch (error) {
|
|
42
|
+
return toolError(error);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function projectRootFor(input, opts) {
|
|
47
|
+
return runtime.resolveProject(input.project || opts.projectRoot || process.cwd());
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function statusTool(input = {}, opts = {}) {
|
|
51
|
+
const projectRoot = projectRootFor(input, opts);
|
|
52
|
+
const dashboard = runtime.requireRuntime('dashboard', opts);
|
|
53
|
+
const result = dashboard.compute(projectRoot, { git: input.git !== false });
|
|
54
|
+
return {
|
|
55
|
+
project: projectRoot,
|
|
56
|
+
dashboard: result,
|
|
57
|
+
rendered: dashboard.render(result, { brief: Boolean(input.brief) })
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function nextTool(input = {}, opts = {}) {
|
|
62
|
+
const projectRoot = projectRootFor(input, opts);
|
|
63
|
+
const dashboard = runtime.requireRuntime('dashboard', opts);
|
|
64
|
+
const result = dashboard.compute(projectRoot, { git: input.git !== false });
|
|
65
|
+
return {
|
|
66
|
+
project: projectRoot,
|
|
67
|
+
next: result.next,
|
|
68
|
+
actionBrief: result.actionBrief,
|
|
69
|
+
dashboard: result
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function gateTool(input = {}, opts = {}) {
|
|
74
|
+
const projectRoot = projectRootFor(input, opts);
|
|
75
|
+
const gate = runtime.requireRuntime('gate', opts);
|
|
76
|
+
return gate.check({
|
|
77
|
+
tier: input.tier,
|
|
78
|
+
projectRoot
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function lintTool(input = {}, opts = {}) {
|
|
83
|
+
const projectRoot = projectRootFor(input, opts);
|
|
84
|
+
const linter = runtime.requireRuntime('artifact-linter', opts);
|
|
85
|
+
const artifactPath = runtime.resolveProjectFile(projectRoot, input.path);
|
|
86
|
+
const result = linter.lintFile(artifactPath, { projectRoot });
|
|
87
|
+
return {
|
|
88
|
+
project: projectRoot,
|
|
89
|
+
artifact: path.relative(projectRoot, artifactPath).split(path.sep).join('/'),
|
|
90
|
+
lint: {
|
|
91
|
+
type: result.type,
|
|
92
|
+
findings: result.findings,
|
|
93
|
+
summary: result.summary
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function traceRequirementTool(input = {}, opts = {}) {
|
|
99
|
+
const projectRoot = projectRootFor(input, opts);
|
|
100
|
+
const requirements = runtime.requireRuntime('requirements', opts);
|
|
101
|
+
const derived = requirements.derive(projectRoot);
|
|
102
|
+
const id = String(input.id || '').trim();
|
|
103
|
+
const requirement = derived.requirements.find((item) => item.id === id) || null;
|
|
104
|
+
const increment = requirement && requirement.increment
|
|
105
|
+
? derived.increments.find((item) => item.id === requirement.increment) || null
|
|
106
|
+
: null;
|
|
107
|
+
return {
|
|
108
|
+
project: projectRoot,
|
|
109
|
+
id,
|
|
110
|
+
found: Boolean(requirement),
|
|
111
|
+
requirement,
|
|
112
|
+
increment,
|
|
113
|
+
summary: derived.summary,
|
|
114
|
+
ledger: requirements.LEDGER_PATH
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function registerTools(server, opts = {}) {
|
|
119
|
+
server.registerTool('status', {
|
|
120
|
+
title: 'Godpowers status',
|
|
121
|
+
description: 'Read Godpowers dashboard state from disk.',
|
|
122
|
+
inputSchema: {
|
|
123
|
+
project: z.string().optional().describe('Project root. Defaults to the server project.'),
|
|
124
|
+
brief: z.boolean().optional().describe('Include compact rendered dashboard text.'),
|
|
125
|
+
git: z.boolean().optional().describe('Set false to skip git status checks.')
|
|
126
|
+
},
|
|
127
|
+
annotations: {
|
|
128
|
+
readOnlyHint: true,
|
|
129
|
+
destructiveHint: false,
|
|
130
|
+
idempotentHint: true
|
|
131
|
+
}
|
|
132
|
+
}, async (input) => withErrors(() => statusTool(input, opts)));
|
|
133
|
+
|
|
134
|
+
server.registerTool('next', {
|
|
135
|
+
title: 'Godpowers next',
|
|
136
|
+
description: 'Read the recommended next Godpowers command from disk state.',
|
|
137
|
+
inputSchema: {
|
|
138
|
+
project: z.string().optional().describe('Project root. Defaults to the server project.'),
|
|
139
|
+
git: z.boolean().optional().describe('Set false to skip git status checks.')
|
|
140
|
+
},
|
|
141
|
+
annotations: {
|
|
142
|
+
readOnlyHint: true,
|
|
143
|
+
destructiveHint: false,
|
|
144
|
+
idempotentHint: true
|
|
145
|
+
}
|
|
146
|
+
}, async (input) => withErrors(() => nextTool(input, opts)));
|
|
147
|
+
|
|
148
|
+
server.registerTool('gate_check', {
|
|
149
|
+
title: 'Godpowers gate check',
|
|
150
|
+
description: 'Run a read-only executable tier gate check.',
|
|
151
|
+
inputSchema: {
|
|
152
|
+
project: z.string().optional().describe('Project root. Defaults to the server project.'),
|
|
153
|
+
tier: z.enum(['prd', 'design', 'arch', 'roadmap', 'stack', 'repo', 'build', 'harden'])
|
|
154
|
+
.describe('Gate tier to check.')
|
|
155
|
+
},
|
|
156
|
+
annotations: {
|
|
157
|
+
readOnlyHint: true,
|
|
158
|
+
destructiveHint: false,
|
|
159
|
+
idempotentHint: true
|
|
160
|
+
}
|
|
161
|
+
}, async (input) => withErrors(() => gateTool(input, opts)));
|
|
162
|
+
|
|
163
|
+
server.registerTool('lint_artifact', {
|
|
164
|
+
title: 'Godpowers artifact lint',
|
|
165
|
+
description: 'Lint one artifact path inside the project root.',
|
|
166
|
+
inputSchema: {
|
|
167
|
+
project: z.string().optional().describe('Project root. Defaults to the server project.'),
|
|
168
|
+
path: z.string().describe('Artifact path relative to the project root.')
|
|
169
|
+
},
|
|
170
|
+
annotations: {
|
|
171
|
+
readOnlyHint: true,
|
|
172
|
+
destructiveHint: false,
|
|
173
|
+
idempotentHint: true
|
|
174
|
+
}
|
|
175
|
+
}, async (input) => withErrors(() => lintTool(input, opts)));
|
|
176
|
+
|
|
177
|
+
server.registerTool('trace_requirement', {
|
|
178
|
+
title: 'Godpowers requirement trace',
|
|
179
|
+
description: 'Trace one PRD requirement id to linkage and roadmap evidence.',
|
|
180
|
+
inputSchema: {
|
|
181
|
+
project: z.string().optional().describe('Project root. Defaults to the server project.'),
|
|
182
|
+
id: z.string().describe('Requirement id such as P-MUST-01.')
|
|
183
|
+
},
|
|
184
|
+
annotations: {
|
|
185
|
+
readOnlyHint: true,
|
|
186
|
+
destructiveHint: false,
|
|
187
|
+
idempotentHint: true
|
|
188
|
+
}
|
|
189
|
+
}, async (input) => withErrors(() => traceRequirementTool(input, opts)));
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
module.exports = {
|
|
193
|
+
TOOL_NAMES,
|
|
194
|
+
registerTools,
|
|
195
|
+
statusTool,
|
|
196
|
+
nextTool,
|
|
197
|
+
gateTool,
|
|
198
|
+
lintTool,
|
|
199
|
+
traceRequirementTool,
|
|
200
|
+
toolResult,
|
|
201
|
+
toolError
|
|
202
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@godpowers/mcp",
|
|
3
|
+
"version": "2.6.0",
|
|
4
|
+
"description": "Read-only MCP server for Godpowers runtime status, routing, gates, artifact linting, and requirement tracing.",
|
|
5
|
+
"bin": {
|
|
6
|
+
"godpowers-mcp": "./bin/godpowers-mcp.js"
|
|
7
|
+
},
|
|
8
|
+
"main": "./lib/server.js",
|
|
9
|
+
"type": "commonjs",
|
|
10
|
+
"scripts": {
|
|
11
|
+
"test": "node scripts/test-protocol.js",
|
|
12
|
+
"pack:check": "node scripts/check-package-contents.js"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"godpowers",
|
|
16
|
+
"mcp",
|
|
17
|
+
"model-context-protocol",
|
|
18
|
+
"ai-coding"
|
|
19
|
+
],
|
|
20
|
+
"author": "Godpowers",
|
|
21
|
+
"license": "MIT",
|
|
22
|
+
"repository": {
|
|
23
|
+
"type": "git",
|
|
24
|
+
"url": "git+https://github.com/aihxp/godpowers.git",
|
|
25
|
+
"directory": "packages/mcp"
|
|
26
|
+
},
|
|
27
|
+
"homepage": "https://github.com/aihxp/godpowers/tree/main/packages/mcp#readme",
|
|
28
|
+
"bugs": {
|
|
29
|
+
"url": "https://github.com/aihxp/godpowers/issues"
|
|
30
|
+
},
|
|
31
|
+
"engines": {
|
|
32
|
+
"node": ">=18.0.0"
|
|
33
|
+
},
|
|
34
|
+
"files": [
|
|
35
|
+
"bin/",
|
|
36
|
+
"lib/",
|
|
37
|
+
"README.md",
|
|
38
|
+
"LICENSE"
|
|
39
|
+
],
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
42
|
+
"zod": "^4.1.13"
|
|
43
|
+
},
|
|
44
|
+
"peerDependencies": {
|
|
45
|
+
"godpowers": ">=2.6.0"
|
|
46
|
+
},
|
|
47
|
+
"peerDependenciesMeta": {
|
|
48
|
+
"godpowers": {
|
|
49
|
+
"optional": true
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|