godmode 0.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/LICENSE +21 -0
- package/dist/add-ZRNIX6DM.js +44 -0
- package/dist/chunk-EHS56XXY.js +163 -0
- package/dist/chunk-FBMQ3AC4.js +339 -0
- package/dist/chunk-I4WS4MMI.js +92 -0
- package/dist/index.js +613 -0
- package/dist/mcp-NZIEMQOM.js +58 -0
- package/dist/mcp-server.js +129 -0
- package/dist/prompt-DCFFOQ7K.js +96 -0
- package/package.json +46 -0
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
executeToString
|
|
4
|
+
} from "./chunk-I4WS4MMI.js";
|
|
5
|
+
import {
|
|
6
|
+
executeMcpTool
|
|
7
|
+
} from "./chunk-EHS56XXY.js";
|
|
8
|
+
|
|
9
|
+
// src/mcp-server.ts
|
|
10
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
11
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
12
|
+
import { ListToolsRequestSchema, CallToolRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
13
|
+
import fuzzysort from "fuzzysort";
|
|
14
|
+
function routeToToolName(route, type) {
|
|
15
|
+
if (type === "mcp" || type === "graphql") return route.path;
|
|
16
|
+
const segments = route.segments.filter((s) => !s.isParam).map((s) => s.value);
|
|
17
|
+
return `${route.method}_${segments.join("_")}`;
|
|
18
|
+
}
|
|
19
|
+
function routeResource(route) {
|
|
20
|
+
return route.segments.find((s) => !s.isParam)?.value || route.path;
|
|
21
|
+
}
|
|
22
|
+
function routeToInputSchema(route, manifest) {
|
|
23
|
+
const type = manifest.config.type;
|
|
24
|
+
if (type === "mcp") {
|
|
25
|
+
const mcpTools = manifest.config._mcpTools;
|
|
26
|
+
const tool = mcpTools?.find((t) => t.name === route.path);
|
|
27
|
+
return tool?.inputSchema || { type: "object", properties: {} };
|
|
28
|
+
}
|
|
29
|
+
if (type === "graphql") {
|
|
30
|
+
return { type: "object", properties: { query: { type: "string", description: "GraphQL query document" } }, required: ["query"] };
|
|
31
|
+
}
|
|
32
|
+
const properties = {};
|
|
33
|
+
const required = [];
|
|
34
|
+
for (const seg of route.segments) {
|
|
35
|
+
if (seg.isParam) {
|
|
36
|
+
properties[seg.value] = { type: "string", description: `Path parameter: ${seg.value}` };
|
|
37
|
+
required.push(seg.value);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
if (["post", "put", "patch"].includes(route.method)) {
|
|
41
|
+
properties["body"] = { type: "string", description: "JSON request body" };
|
|
42
|
+
}
|
|
43
|
+
return { type: "object", properties, ...required.length ? { required } : {} };
|
|
44
|
+
}
|
|
45
|
+
async function serveMcp(manifest, options = {}) {
|
|
46
|
+
const type = manifest.config.type;
|
|
47
|
+
let routes = manifest.routes;
|
|
48
|
+
if (options.method) {
|
|
49
|
+
const m = fuzzysort.go(options.method, ["get", "post", "put", "patch", "delete"])[0]?.target;
|
|
50
|
+
if (m) routes = routes.filter((r) => r.method === m);
|
|
51
|
+
}
|
|
52
|
+
if (options.filter) {
|
|
53
|
+
const resources = [...new Set(routes.map((r) => routeResource(r)))];
|
|
54
|
+
const matched = new Set(fuzzysort.go(options.filter, resources).map((r) => r.target));
|
|
55
|
+
routes = routes.filter((r) => matched.has(routeResource(r)));
|
|
56
|
+
}
|
|
57
|
+
const totalRoutes = manifest.routes.length;
|
|
58
|
+
const exposedRoutes = routes.length;
|
|
59
|
+
const filtered = totalRoutes !== exposedRoutes;
|
|
60
|
+
const hints = [];
|
|
61
|
+
if (filtered) {
|
|
62
|
+
hints.push(`Exposing ${exposedRoutes} of ${totalRoutes} routes.`);
|
|
63
|
+
} else if (totalRoutes > 50) {
|
|
64
|
+
hints.push(`${totalRoutes} routes exposed. Use --filter or --method with "godmode mcp ${manifest.name}" to narrow down.`);
|
|
65
|
+
}
|
|
66
|
+
const server = new Server(
|
|
67
|
+
{ name: manifest.name, version: manifest.specVersion || "0.0.1" },
|
|
68
|
+
{
|
|
69
|
+
capabilities: { tools: {} },
|
|
70
|
+
...hints.length ? { instructions: hints.join(" ") } : {}
|
|
71
|
+
}
|
|
72
|
+
);
|
|
73
|
+
const toolMap = /* @__PURE__ */ new Map();
|
|
74
|
+
for (const route of routes) {
|
|
75
|
+
const name = routeToToolName(route, type);
|
|
76
|
+
if (!toolMap.has(name)) {
|
|
77
|
+
toolMap.set(name, {
|
|
78
|
+
name,
|
|
79
|
+
description: route.summary || `${route.method.toUpperCase()} ${route.path}`,
|
|
80
|
+
inputSchema: routeToInputSchema(route, manifest),
|
|
81
|
+
route
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (filtered) {
|
|
86
|
+
process.stderr.write(`MCP server: ${manifest.name} - ${toolMap.size} tools (filtered from ${totalRoutes})
|
|
87
|
+
`);
|
|
88
|
+
}
|
|
89
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
90
|
+
tools: [...toolMap.values()].map(({ name, description, inputSchema }) => ({ name, description, inputSchema }))
|
|
91
|
+
}));
|
|
92
|
+
server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
93
|
+
const toolName = req.params.name;
|
|
94
|
+
const args = req.params.arguments || {};
|
|
95
|
+
const tool = toolMap.get(toolName);
|
|
96
|
+
if (!tool) {
|
|
97
|
+
return { content: [{ type: "text", text: `Unknown tool: ${toolName}` }], isError: true };
|
|
98
|
+
}
|
|
99
|
+
try {
|
|
100
|
+
let result;
|
|
101
|
+
if (type === "mcp") {
|
|
102
|
+
result = await executeMcpTool(manifest.config, toolName, args, {});
|
|
103
|
+
} else if (type === "graphql") {
|
|
104
|
+
result = await executeToString(manifest, { route: tool.route, params: {} }, {
|
|
105
|
+
headers: {},
|
|
106
|
+
query: {},
|
|
107
|
+
body: JSON.stringify({ query: args.query })
|
|
108
|
+
});
|
|
109
|
+
} else {
|
|
110
|
+
const params = {};
|
|
111
|
+
for (const seg of tool.route.segments) {
|
|
112
|
+
if (seg.isParam && args[seg.value]) params[seg.value] = args[seg.value];
|
|
113
|
+
}
|
|
114
|
+
result = await executeToString(manifest, { route: tool.route, params }, {
|
|
115
|
+
headers: {},
|
|
116
|
+
query: {},
|
|
117
|
+
body: args.body
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
return { content: [{ type: "text", text: result }] };
|
|
121
|
+
} catch (err) {
|
|
122
|
+
return { content: [{ type: "text", text: `Error: ${err.message}` }], isError: true };
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
await server.connect(new StdioServerTransport());
|
|
126
|
+
}
|
|
127
|
+
export {
|
|
128
|
+
serveMcp
|
|
129
|
+
};
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/prompt.ts
|
|
4
|
+
import prompts from "prompts";
|
|
5
|
+
import { writeFileSync } from "fs";
|
|
6
|
+
import { resolve } from "path";
|
|
7
|
+
import { stringify } from "yaml";
|
|
8
|
+
async function configWizard() {
|
|
9
|
+
const response = await prompts([
|
|
10
|
+
{
|
|
11
|
+
type: "text",
|
|
12
|
+
name: "name",
|
|
13
|
+
message: "API name",
|
|
14
|
+
hint: "e.g. stripe, github",
|
|
15
|
+
validate: (v) => v.length > 0 || "Required"
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
type: "text",
|
|
19
|
+
name: "description",
|
|
20
|
+
message: "Description"
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
type: "text",
|
|
24
|
+
name: "spec",
|
|
25
|
+
message: "OpenAPI spec URL or file path",
|
|
26
|
+
validate: (v) => v.length > 0 || "Required"
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
type: "text",
|
|
30
|
+
name: "url",
|
|
31
|
+
message: "Base URL",
|
|
32
|
+
hint: "e.g. https://api.stripe.com"
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
type: "confirm",
|
|
36
|
+
name: "hasAuth",
|
|
37
|
+
message: "Requires authentication?",
|
|
38
|
+
initial: true
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
type: (prev) => prev ? "text" : null,
|
|
42
|
+
name: "envVar",
|
|
43
|
+
message: "Environment variable for token",
|
|
44
|
+
initial: (_, values) => `${(values.name || "").toUpperCase().replace(/[^A-Z0-9]/g, "_")}_API_KEY`
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
type: (_, values) => values.hasAuth ? "select" : null,
|
|
48
|
+
name: "authType",
|
|
49
|
+
message: "Auth type",
|
|
50
|
+
choices: [
|
|
51
|
+
{ title: "Bearer token", value: "bearer" },
|
|
52
|
+
{ title: "API key header", value: "api-key" },
|
|
53
|
+
{ title: "Basic auth", value: "basic" }
|
|
54
|
+
],
|
|
55
|
+
initial: 0
|
|
56
|
+
}
|
|
57
|
+
], { onCancel: () => {
|
|
58
|
+
console.log("Aborted.");
|
|
59
|
+
process.exit(0);
|
|
60
|
+
} });
|
|
61
|
+
const config = {
|
|
62
|
+
name: response.name.charAt(0).toUpperCase() + response.name.slice(1),
|
|
63
|
+
type: "api",
|
|
64
|
+
spec: response.spec
|
|
65
|
+
};
|
|
66
|
+
if (response.description) config.description = response.description;
|
|
67
|
+
if (response.url) config.url = response.url;
|
|
68
|
+
if (response.envVar) {
|
|
69
|
+
config.auth = { env: response.envVar };
|
|
70
|
+
if (response.authType && response.authType !== "bearer") {
|
|
71
|
+
config.auth.type = response.authType;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
const yaml = stringify(config);
|
|
75
|
+
const filename = `${response.name.toLowerCase().replace(/[^a-z0-9]/g, "-")}.yaml`;
|
|
76
|
+
console.log(`
|
|
77
|
+
\x1B[2m${yaml}\x1B[0m`);
|
|
78
|
+
const { write } = await prompts({
|
|
79
|
+
type: "confirm",
|
|
80
|
+
name: "write",
|
|
81
|
+
message: `Write to ${filename}?`,
|
|
82
|
+
initial: true
|
|
83
|
+
});
|
|
84
|
+
if (!write) {
|
|
85
|
+
console.log("Aborted.");
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
const filepath = resolve(process.cwd(), filename);
|
|
89
|
+
writeFileSync(filepath, yaml);
|
|
90
|
+
console.log(`
|
|
91
|
+
Saved ${filepath}`);
|
|
92
|
+
console.log(`Run \x1B[1mgodmode add ${response.name.toLowerCase()}\x1B[0m to register it.`);
|
|
93
|
+
}
|
|
94
|
+
export {
|
|
95
|
+
configWizard
|
|
96
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "godmode",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "better than mcp",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "Tomas Sivicki",
|
|
8
|
+
"bin": {
|
|
9
|
+
"godmode": "./dist/index.js"
|
|
10
|
+
},
|
|
11
|
+
"exports": {
|
|
12
|
+
"./test/adapter": "./test/adapter.ts"
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist",
|
|
16
|
+
"LICENSE",
|
|
17
|
+
"README.md"
|
|
18
|
+
],
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"@hey-api/openapi-ts": "^0.94.0",
|
|
21
|
+
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
22
|
+
"fuzzysort": "^3.1.0",
|
|
23
|
+
"prompts": "^2.4.2",
|
|
24
|
+
"yaml": "^2.8.0"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@changesets/cli": "^2.30.0",
|
|
28
|
+
"@types/node": "^22.0.0",
|
|
29
|
+
"@types/prompts": "^2.4.9",
|
|
30
|
+
"tsup": "^8.0.0",
|
|
31
|
+
"typescript": "^5.5.0",
|
|
32
|
+
"vitest": "^4.1.0"
|
|
33
|
+
},
|
|
34
|
+
"engines": {
|
|
35
|
+
"node": ">=20.0.0"
|
|
36
|
+
},
|
|
37
|
+
"scripts": {
|
|
38
|
+
"build": "tsup",
|
|
39
|
+
"dev": "tsup --watch",
|
|
40
|
+
"start": "node dist/index.js",
|
|
41
|
+
"test": "vitest run",
|
|
42
|
+
"changeset": "changeset",
|
|
43
|
+
"version": "changeset version",
|
|
44
|
+
"release": "pnpm build && changeset publish"
|
|
45
|
+
}
|
|
46
|
+
}
|