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.
@@ -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
+ }