@wootsup/mcp 0.1.0-rc.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.
Files changed (135) hide show
  1. package/CHANGELOG.md +91 -0
  2. package/LICENSE +21 -0
  3. package/README.md +179 -0
  4. package/SECURITY.md +163 -0
  5. package/dist/auth/keychain.d.ts +47 -0
  6. package/dist/auth/keychain.js +262 -0
  7. package/dist/auth/keychain.js.map +1 -0
  8. package/dist/auth/oauth-provider.d.ts +68 -0
  9. package/dist/auth/oauth-provider.js +232 -0
  10. package/dist/auth/oauth-provider.js.map +1 -0
  11. package/dist/auth/profiles.d.ts +52 -0
  12. package/dist/auth/profiles.js +200 -0
  13. package/dist/auth/profiles.js.map +1 -0
  14. package/dist/auth/token.d.ts +27 -0
  15. package/dist/auth/token.js +88 -0
  16. package/dist/auth/token.js.map +1 -0
  17. package/dist/index.d.ts +13 -0
  18. package/dist/index.js +137 -0
  19. package/dist/index.js.map +1 -0
  20. package/dist/install-skill.d.ts +23 -0
  21. package/dist/install-skill.js +73 -0
  22. package/dist/install-skill.js.map +1 -0
  23. package/dist/modules/apimapper/cache.d.ts +2 -0
  24. package/dist/modules/apimapper/cache.js +71 -0
  25. package/dist/modules/apimapper/cache.js.map +1 -0
  26. package/dist/modules/apimapper/client.d.ts +85 -0
  27. package/dist/modules/apimapper/client.js +523 -0
  28. package/dist/modules/apimapper/client.js.map +1 -0
  29. package/dist/modules/apimapper/connections.d.ts +2 -0
  30. package/dist/modules/apimapper/connections.js +406 -0
  31. package/dist/modules/apimapper/connections.js.map +1 -0
  32. package/dist/modules/apimapper/credential-sanitizer.d.ts +7 -0
  33. package/dist/modules/apimapper/credential-sanitizer.js +70 -0
  34. package/dist/modules/apimapper/credential-sanitizer.js.map +1 -0
  35. package/dist/modules/apimapper/credentials.d.ts +2 -0
  36. package/dist/modules/apimapper/credentials.js +258 -0
  37. package/dist/modules/apimapper/credentials.js.map +1 -0
  38. package/dist/modules/apimapper/diagnose.d.ts +18 -0
  39. package/dist/modules/apimapper/diagnose.js +305 -0
  40. package/dist/modules/apimapper/diagnose.js.map +1 -0
  41. package/dist/modules/apimapper/flows.d.ts +2 -0
  42. package/dist/modules/apimapper/flows.js +372 -0
  43. package/dist/modules/apimapper/flows.js.map +1 -0
  44. package/dist/modules/apimapper/get-skill.d.ts +4 -0
  45. package/dist/modules/apimapper/get-skill.js +88 -0
  46. package/dist/modules/apimapper/get-skill.js.map +1 -0
  47. package/dist/modules/apimapper/graph-builder.d.ts +47 -0
  48. package/dist/modules/apimapper/graph-builder.js +117 -0
  49. package/dist/modules/apimapper/graph-builder.js.map +1 -0
  50. package/dist/modules/apimapper/graph.d.ts +2 -0
  51. package/dist/modules/apimapper/graph.js +117 -0
  52. package/dist/modules/apimapper/graph.js.map +1 -0
  53. package/dist/modules/apimapper/index.d.ts +2 -0
  54. package/dist/modules/apimapper/index.js +43 -0
  55. package/dist/modules/apimapper/index.js.map +1 -0
  56. package/dist/modules/apimapper/inspect.d.ts +20 -0
  57. package/dist/modules/apimapper/inspect.js +86 -0
  58. package/dist/modules/apimapper/inspect.js.map +1 -0
  59. package/dist/modules/apimapper/library.d.ts +2 -0
  60. package/dist/modules/apimapper/library.js +237 -0
  61. package/dist/modules/apimapper/library.js.map +1 -0
  62. package/dist/modules/apimapper/license.d.ts +2 -0
  63. package/dist/modules/apimapper/license.js +142 -0
  64. package/dist/modules/apimapper/license.js.map +1 -0
  65. package/dist/modules/apimapper/local-sources.d.ts +2 -0
  66. package/dist/modules/apimapper/local-sources.js +123 -0
  67. package/dist/modules/apimapper/local-sources.js.map +1 -0
  68. package/dist/modules/apimapper/misc.d.ts +2 -0
  69. package/dist/modules/apimapper/misc.js +149 -0
  70. package/dist/modules/apimapper/misc.js.map +1 -0
  71. package/dist/modules/apimapper/node-schema.d.ts +217 -0
  72. package/dist/modules/apimapper/node-schema.js +218 -0
  73. package/dist/modules/apimapper/node-schema.js.map +1 -0
  74. package/dist/modules/apimapper/normalizers.d.ts +13 -0
  75. package/dist/modules/apimapper/normalizers.js +37 -0
  76. package/dist/modules/apimapper/normalizers.js.map +1 -0
  77. package/dist/modules/apimapper/onboarding.d.ts +51 -0
  78. package/dist/modules/apimapper/onboarding.js +201 -0
  79. package/dist/modules/apimapper/onboarding.js.map +1 -0
  80. package/dist/modules/apimapper/schema.d.ts +2 -0
  81. package/dist/modules/apimapper/schema.js +84 -0
  82. package/dist/modules/apimapper/schema.js.map +1 -0
  83. package/dist/modules/apimapper/settings.d.ts +2 -0
  84. package/dist/modules/apimapper/settings.js +157 -0
  85. package/dist/modules/apimapper/settings.js.map +1 -0
  86. package/dist/modules/apimapper/skill-resources.d.ts +4 -0
  87. package/dist/modules/apimapper/skill-resources.js +85 -0
  88. package/dist/modules/apimapper/skill-resources.js.map +1 -0
  89. package/dist/modules/apimapper/types.d.ts +111 -0
  90. package/dist/modules/apimapper/types.js +14 -0
  91. package/dist/modules/apimapper/types.js.map +1 -0
  92. package/dist/modules/apimapper/use-profile.d.ts +34 -0
  93. package/dist/modules/apimapper/use-profile.js +176 -0
  94. package/dist/modules/apimapper/use-profile.js.map +1 -0
  95. package/dist/modules/apimapper/workflows.d.ts +2 -0
  96. package/dist/modules/apimapper/workflows.js +301 -0
  97. package/dist/modules/apimapper/workflows.js.map +1 -0
  98. package/dist/platform/index.d.ts +71 -0
  99. package/dist/platform/index.js +377 -0
  100. package/dist/platform/index.js.map +1 -0
  101. package/dist/server-http.d.ts +22 -0
  102. package/dist/server-http.js +159 -0
  103. package/dist/server-http.js.map +1 -0
  104. package/dist/setup/detect-clients.d.ts +39 -0
  105. package/dist/setup/detect-clients.js +152 -0
  106. package/dist/setup/detect-clients.js.map +1 -0
  107. package/dist/setup/probe-handshake.d.ts +26 -0
  108. package/dist/setup/probe-handshake.js +159 -0
  109. package/dist/setup/probe-handshake.js.map +1 -0
  110. package/dist/setup/write-config.d.ts +25 -0
  111. package/dist/setup/write-config.js +247 -0
  112. package/dist/setup/write-config.js.map +1 -0
  113. package/dist/setup-cli.d.ts +49 -0
  114. package/dist/setup-cli.js +292 -0
  115. package/dist/setup-cli.js.map +1 -0
  116. package/dist/skill-instructions.d.ts +10 -0
  117. package/dist/skill-instructions.js +68 -0
  118. package/dist/skill-instructions.js.map +1 -0
  119. package/dist/transports/http.d.ts +29 -0
  120. package/dist/transports/http.js +267 -0
  121. package/dist/transports/http.js.map +1 -0
  122. package/dist/transports/stdio.d.ts +9 -0
  123. package/dist/transports/stdio.js +19 -0
  124. package/dist/transports/stdio.js.map +1 -0
  125. package/docs/architecture.md +140 -0
  126. package/docs/customgraph-internal-migration.md +210 -0
  127. package/docs/security.md +126 -0
  128. package/docs/tools.md +230 -0
  129. package/manifest.json +76 -0
  130. package/package.json +61 -0
  131. package/skills/apimapper/SKILL.md +57 -0
  132. package/skills/apimapper/reference/joomla.md +85 -0
  133. package/skills/apimapper/reference/oauth.md +94 -0
  134. package/skills/apimapper/reference/troubleshooting.md +123 -0
  135. package/skills/apimapper/reference/yootheme.md +96 -0
@@ -0,0 +1,159 @@
1
+ #!/usr/bin/env node
2
+ // src/server-http.ts — Phase 9.2 HTTP entry point.
3
+ //
4
+ // Boots the http+OAuth transport for the API Mapper MCP server. Used by:
5
+ // - `npm run start:http` (local dev / staging)
6
+ // - the future hosted service at mcp.wootsup.com (Phase 10+)
7
+ //
8
+ // The MCP handler bridges incoming JSON-RPC requests to a real
9
+ // SDK-managed McpServer via `createServer()` + `InMemoryTransport.createLinkedPair()`.
10
+ // Each HTTP request is serialised through the in-memory transport pair so
11
+ // the MCP server's full handler-registry runs (initialize, tools/list,
12
+ // tools/call all dispatch correctly). Tests can pass their own handler
13
+ // stub via `bootHttpServer({ mcpHandler })`.
14
+ import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js";
15
+ import { loadModules } from "@getimo/mcp-toolkit";
16
+ import { createHttpTransportServer } from "./transports/http.js";
17
+ import { createOAuthProvider } from "./auth/oauth-provider.js";
18
+ import { createServer } from "./index.js";
19
+ import { apimapperRestModule } from "./modules/apimapper/index.js";
20
+ const DEFAULT_PORT = (() => {
21
+ const env = process.env.APIMAPPER_HTTP_PORT;
22
+ const parsed = env ? Number.parseInt(env, 10) : NaN;
23
+ return Number.isFinite(parsed) ? parsed : 8901;
24
+ })();
25
+ /**
26
+ * Default MCP handler — closes Phase 9.2 "deferred SDK binding" gap
27
+ * (R7 audit finding 2026-05-18).
28
+ *
29
+ * Lazily boots a real McpServer + loads all api-mapper modules + connects
30
+ * it to one end of an InMemoryTransport pair. The HTTP handler-callback
31
+ * sends each incoming JSON-RPC request through the other end and awaits
32
+ * the matching response by id.
33
+ *
34
+ * This makes the HTTP transport functionally equivalent to stdio:
35
+ * initialize, tools/list, tools/call all dispatch through the same
36
+ * McpServer instance + tool registry that the stdio transport uses.
37
+ */
38
+ let mcpHandlerSingleton = null;
39
+ let mcpServerSingleton = null;
40
+ let mcpModuleStatuses = [];
41
+ async function getDefaultMcpHandler() {
42
+ if (mcpHandlerSingleton)
43
+ return mcpHandlerSingleton;
44
+ const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
45
+ const server = createServer();
46
+ mcpServerSingleton = server;
47
+ mcpModuleStatuses = await loadModules(server, [apimapperRestModule]);
48
+ await server.connect(serverTransport);
49
+ // The client end stays open for the lifetime of the HTTP server. Each
50
+ // HTTP request sends a JSON-RPC message in and registers a one-shot
51
+ // resolver keyed on the JSON-RPC id; the McpServer's reply lands on
52
+ // clientTransport.onmessage and resolves the matching pending promise.
53
+ await clientTransport.start();
54
+ const pending = new Map();
55
+ clientTransport.onmessage = (message) => {
56
+ const m = message;
57
+ if (m && m.id !== undefined && pending.has(m.id)) {
58
+ const resolve = pending.get(m.id);
59
+ pending.delete(m.id);
60
+ resolve(message);
61
+ }
62
+ // Server-initiated notifications (no id) are intentionally dropped —
63
+ // the HTTP transport is request/response only.
64
+ };
65
+ mcpHandlerSingleton = async (body) => {
66
+ const req = body;
67
+ // Notifications carry no id and expect no response. Forward + return
68
+ // 202-like empty body to caller.
69
+ if (req.id === undefined) {
70
+ try {
71
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- SDK message type is broader
72
+ await clientTransport.send(body);
73
+ }
74
+ catch {
75
+ /* ignore — notification fire-and-forget */
76
+ }
77
+ return { jsonrpc: "2.0", result: { ok: true } };
78
+ }
79
+ // Request/response: register resolver, send, await reply.
80
+ return new Promise((resolve) => {
81
+ const timer = setTimeout(() => {
82
+ if (pending.has(req.id)) {
83
+ pending.delete(req.id);
84
+ resolve({
85
+ jsonrpc: "2.0",
86
+ id: req.id,
87
+ error: { code: -32000, message: "HTTP→MCP dispatch timed out after 30s" },
88
+ });
89
+ }
90
+ }, 30_000);
91
+ pending.set(req.id, (msg) => {
92
+ clearTimeout(timer);
93
+ resolve(msg);
94
+ });
95
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- SDK message type is broader
96
+ void clientTransport.send(body).catch((err) => {
97
+ clearTimeout(timer);
98
+ pending.delete(req.id);
99
+ resolve({
100
+ jsonrpc: "2.0",
101
+ id: req.id,
102
+ error: { code: -32603, message: `dispatch failed: ${err.message}` },
103
+ });
104
+ });
105
+ });
106
+ };
107
+ return mcpHandlerSingleton;
108
+ }
109
+ /** Test-only — exposes the server + module load status for assertions. */
110
+ export function _testGetMcpServer() {
111
+ return { server: mcpServerSingleton, modules: mcpModuleStatuses };
112
+ }
113
+ export async function bootHttpServer(options = {}) {
114
+ const port = options.port ?? DEFAULT_PORT;
115
+ const oauth = createOAuthProvider();
116
+ const mcpHandler = options.mcpHandler ?? (await getDefaultMcpHandler());
117
+ const transport = await createHttpTransportServer({
118
+ port,
119
+ oauth,
120
+ mcpHandler,
121
+ publicBaseUrl: options.publicBaseUrl,
122
+ });
123
+ return { ...transport, oauth };
124
+ }
125
+ // ── CLI entry ──────────────────────────────────────────────────────────
126
+ // Only auto-boot when this file is executed as the process entrypoint
127
+ // (i.e. `node dist/server-http.js`), never when imported by tests.
128
+ const isEntry = (() => {
129
+ if (typeof process.argv[1] !== "string")
130
+ return false;
131
+ // import.meta.url is a file:// URL; argv[1] is a path. Map both to
132
+ // a comparable form.
133
+ try {
134
+ const argvUrl = new URL(`file://${process.argv[1]}`).href;
135
+ return argvUrl === import.meta.url;
136
+ }
137
+ catch {
138
+ return false;
139
+ }
140
+ })();
141
+ if (isEntry) {
142
+ bootHttpServer()
143
+ .then((s) => {
144
+ // eslint-disable-next-line no-console -- intentional startup log
145
+ console.error(`[apimapper-mcp] HTTP transport listening on ${s.url}`);
146
+ const shutdown = async () => {
147
+ await s.close();
148
+ process.exit(0);
149
+ };
150
+ process.on("SIGTERM", shutdown);
151
+ process.on("SIGINT", shutdown);
152
+ })
153
+ .catch((err) => {
154
+ // eslint-disable-next-line no-console -- intentional fatal log
155
+ console.error("[apimapper-mcp] HTTP boot failed:", err);
156
+ process.exit(1);
157
+ });
158
+ }
159
+ //# sourceMappingURL=server-http.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server-http.js","sourceRoot":"","sources":["../src/server-http.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,39 @@
1
+ import { dirname } from "node:path";
2
+ export type ClientId = "claude-desktop" | "claude-code" | "cursor" | "vscode" | "cline" | "codex";
3
+ export type ClientFormat = "json" | "toml";
4
+ export interface DetectedClient {
5
+ id: ClientId;
6
+ label: string;
7
+ configPath: string;
8
+ format: ClientFormat;
9
+ installed: boolean;
10
+ }
11
+ /**
12
+ * Injectable environment surface. `existsSync` is the only file-system
13
+ * dependency; everything else is pure-data lookup of OS/env.
14
+ */
15
+ export interface DetectEnv {
16
+ platform: () => NodeJS.Platform;
17
+ homedir: () => string;
18
+ env: Partial<Record<string, string>>;
19
+ existsSync: (p: string) => boolean;
20
+ }
21
+ interface ClientDef {
22
+ id: ClientId;
23
+ label: string;
24
+ format: ClientFormat;
25
+ /**
26
+ * Per-platform path-builders. The function receives a small ctx so it
27
+ * can compute paths from homedir and APPDATA without re-reading env.
28
+ */
29
+ pathBy: Record<"darwin" | "linux" | "win32" | "default", (ctx: PathCtx) => string>;
30
+ }
31
+ interface PathCtx {
32
+ home: string;
33
+ appdata: string;
34
+ }
35
+ /** Cross-platform path-join that respects whichever separator the home uses. */
36
+ declare function joinPath(home: string, ...parts: string[]): string;
37
+ export declare const CLIENT_DEFS: readonly ClientDef[];
38
+ export declare function detectClients(env?: DetectEnv): Promise<DetectedClient[]>;
39
+ export { joinPath, dirname };
@@ -0,0 +1,152 @@
1
+ // src/setup/detect-clients.ts — Phase 6.2.
2
+ //
3
+ // Per-OS auto-discovery of AI-client config-file locations for the six
4
+ // supported clients. All path logic is data-driven via CLIENT_DEFS so
5
+ // future clients are a single table entry, not a code change.
6
+ //
7
+ // The module accepts an injectable DetectEnv so that tests can pretend
8
+ // to be running on any OS without touching the real filesystem.
9
+ import { existsSync as realExistsSync } from "node:fs";
10
+ import { platform as realPlatform, homedir as realHomedir } from "node:os";
11
+ import { dirname } from "node:path";
12
+ const PSEP = (home) => (home.includes("\\") ? "\\" : "/");
13
+ /** Cross-platform path-join that respects whichever separator the home uses. */
14
+ function joinPath(home, ...parts) {
15
+ const sep = PSEP(home);
16
+ // We don't use node:path here because we want deterministic output for
17
+ // Windows-style homes on a posix test runner.
18
+ const acc = [home, ...parts]
19
+ .map((seg) => seg.replace(/[\\/]+$/, ""))
20
+ .filter((seg) => seg.length > 0);
21
+ return acc.join(sep);
22
+ }
23
+ export const CLIENT_DEFS = [
24
+ {
25
+ id: "claude-desktop",
26
+ label: "Claude Desktop",
27
+ format: "json",
28
+ pathBy: {
29
+ darwin: ({ home }) => joinPath(home, "Library", "Application Support", "Claude", "claude_desktop_config.json"),
30
+ linux: ({ home }) => joinPath(home, ".config", "Claude", "claude_desktop_config.json"),
31
+ win32: ({ appdata }) => joinPath(appdata, "Claude", "claude_desktop_config.json"),
32
+ default: ({ home }) => joinPath(home, ".config", "Claude", "claude_desktop_config.json"),
33
+ },
34
+ },
35
+ {
36
+ id: "claude-code",
37
+ label: "Claude Code",
38
+ format: "json",
39
+ pathBy: {
40
+ darwin: ({ home }) => joinPath(home, ".claude.json"),
41
+ linux: ({ home }) => joinPath(home, ".claude.json"),
42
+ win32: ({ home }) => joinPath(home, ".claude.json"),
43
+ default: ({ home }) => joinPath(home, ".claude.json"),
44
+ },
45
+ },
46
+ {
47
+ id: "cursor",
48
+ label: "Cursor",
49
+ format: "json",
50
+ pathBy: {
51
+ darwin: ({ home }) => joinPath(home, ".cursor", "mcp.json"),
52
+ linux: ({ home }) => joinPath(home, ".cursor", "mcp.json"),
53
+ win32: ({ home }) => joinPath(home, ".cursor", "mcp.json"),
54
+ default: ({ home }) => joinPath(home, ".cursor", "mcp.json"),
55
+ },
56
+ },
57
+ {
58
+ id: "vscode",
59
+ label: "VS Code",
60
+ format: "json",
61
+ pathBy: {
62
+ darwin: ({ home }) => joinPath(home, "Library", "Application Support", "Code", "User", "settings.json"),
63
+ linux: ({ home }) => joinPath(home, ".config", "Code", "User", "settings.json"),
64
+ win32: ({ appdata }) => joinPath(appdata, "Code", "User", "settings.json"),
65
+ default: ({ home }) => joinPath(home, ".config", "Code", "User", "settings.json"),
66
+ },
67
+ },
68
+ {
69
+ id: "cline",
70
+ label: "Cline",
71
+ format: "json",
72
+ pathBy: {
73
+ // Cline's canonical config path is not yet stable across releases;
74
+ // default to ~/.cline/config.json for now (overridable by hand).
75
+ darwin: ({ home }) => joinPath(home, ".cline", "config.json"),
76
+ linux: ({ home }) => joinPath(home, ".cline", "config.json"),
77
+ win32: ({ home }) => joinPath(home, ".cline", "config.json"),
78
+ default: ({ home }) => joinPath(home, ".cline", "config.json"),
79
+ },
80
+ },
81
+ {
82
+ id: "codex",
83
+ label: "Codex CLI",
84
+ format: "toml",
85
+ pathBy: {
86
+ darwin: ({ home }) => joinPath(home, ".codex", "config.toml"),
87
+ linux: ({ home }) => joinPath(home, ".codex", "config.toml"),
88
+ win32: ({ home }) => joinPath(home, ".codex", "config.toml"),
89
+ default: ({ home }) => joinPath(home, ".codex", "config.toml"),
90
+ },
91
+ },
92
+ ];
93
+ function getAppdata(env, home) {
94
+ const a = env.env.APPDATA;
95
+ if (a && a.length > 0)
96
+ return a;
97
+ // Fallback for Windows when APPDATA is unset — matches the conventional
98
+ // location relative to USERPROFILE/homedir.
99
+ return joinPath(home, "AppData", "Roaming");
100
+ }
101
+ function pickPath(def, plat, ctx) {
102
+ if (plat === "darwin")
103
+ return def.pathBy.darwin(ctx);
104
+ if (plat === "linux")
105
+ return def.pathBy.linux(ctx);
106
+ if (plat === "win32")
107
+ return def.pathBy.win32(ctx);
108
+ return def.pathBy.default(ctx);
109
+ }
110
+ function isInstalled(env, configPath) {
111
+ if (env.existsSync(configPath))
112
+ return true;
113
+ // Treat "parent dir exists" as a soft signal the client is installed —
114
+ // configs are often created on first MCP wire-up rather than at install.
115
+ const parent = (() => {
116
+ // dirname from node:path doesn't handle mixed separators well; do a
117
+ // manual split that respects whichever separator the path uses.
118
+ const sep = configPath.includes("\\") ? "\\" : "/";
119
+ const idx = configPath.lastIndexOf(sep);
120
+ if (idx < 0)
121
+ return null;
122
+ return configPath.slice(0, idx);
123
+ })();
124
+ if (parent && env.existsSync(parent))
125
+ return true;
126
+ return false;
127
+ }
128
+ const REAL_ENV = {
129
+ platform: () => realPlatform(),
130
+ homedir: () => realHomedir(),
131
+ env: process.env,
132
+ existsSync: (p) => realExistsSync(p),
133
+ };
134
+ export async function detectClients(env = REAL_ENV) {
135
+ const plat = env.platform();
136
+ const home = env.homedir();
137
+ const ctx = { home, appdata: getAppdata(env, home) };
138
+ const out = CLIENT_DEFS.map((def) => {
139
+ const configPath = pickPath(def, plat, ctx);
140
+ return {
141
+ id: def.id,
142
+ label: def.label,
143
+ configPath,
144
+ format: def.format,
145
+ installed: isInstalled(env, configPath),
146
+ };
147
+ });
148
+ return out;
149
+ }
150
+ // Re-export the helper for use by other setup modules (write-config etc.).
151
+ export { joinPath, dirname };
152
+ //# sourceMappingURL=detect-clients.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"detect-clients.js","sourceRoot":"","sources":["../../src/setup/detect-clients.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ import { Writable, Readable } from "node:stream";
2
+ export interface ProbeSpawnedProc {
3
+ stdin: Writable;
4
+ stdout: Readable;
5
+ kill: (signal?: NodeJS.Signals) => void;
6
+ on: (event: "exit" | "error", cb: (...args: unknown[]) => void) => void;
7
+ }
8
+ export type ProbeSpawnFn = (command: string, args: string[], env: Record<string, string>) => ProbeSpawnedProc;
9
+ export interface ProbeOptions {
10
+ command: string;
11
+ args: string[];
12
+ env?: Record<string, string>;
13
+ spawn?: ProbeSpawnFn;
14
+ timeoutMs?: number;
15
+ }
16
+ export interface ProbeResult {
17
+ ok: boolean;
18
+ tools?: string[];
19
+ error?: string;
20
+ }
21
+ export declare function probeHandshake(opts: ProbeOptions): Promise<ProbeResult>;
22
+ /**
23
+ * Roll back every undo handle in order. Errors are caught + ignored — the
24
+ * goal is to clean up as much as possible, not bail on the first failure.
25
+ */
26
+ export declare function rollbackAll(undos: Array<() => Promise<void>>): Promise<void>;
@@ -0,0 +1,159 @@
1
+ // src/setup/probe-handshake.ts — Phase 6.4.
2
+ //
3
+ // Spawn the MCP server via stdio with the same command/args/env the
4
+ // setup wizard just wrote to the client config, then perform a JSONRPC
5
+ // handshake (initialize + tools/list) and verify that the apimapper_health
6
+ // tool is registered. On failure, the caller invokes rollbackAll() to
7
+ // undo the config writes from Phase 6.3.
8
+ //
9
+ // The subprocess is abstracted as a ProbeSpawnFn so the unit tests can
10
+ // inject a deterministic fake. The default spawn (used in production)
11
+ // shells out to node:child_process.spawn.
12
+ import { spawn as realSpawn } from "node:child_process";
13
+ const PROTOCOL_VERSION = "2024-11-05";
14
+ const HEALTH_TOOL = "apimapper_health";
15
+ const DEFAULT_TIMEOUT_MS = 10_000;
16
+ function defaultSpawn(command, args, env) {
17
+ const child = realSpawn(command, args, {
18
+ env: { ...process.env, ...env },
19
+ stdio: ["pipe", "pipe", "pipe"],
20
+ });
21
+ return {
22
+ stdin: child.stdin,
23
+ stdout: child.stdout,
24
+ kill: (signal) => child.kill(signal),
25
+ on: (event, cb) => child.on(event, cb),
26
+ };
27
+ }
28
+ /**
29
+ * Drive a JSONRPC over stdio session with the spawned MCP. Sends
30
+ * `initialize` then `tools/list`, returns the tools array or rejects.
31
+ */
32
+ async function runHandshake(proc, timeoutMs) {
33
+ const pending = new Map();
34
+ let nextId = 1;
35
+ let buf = "";
36
+ proc.stdout.on("data", (chunk) => {
37
+ buf += chunk.toString("utf8");
38
+ // Process newline-delimited JSON.
39
+ // (MCP stdio transport uses newline-delimited JSONRPC.)
40
+ let i;
41
+ while ((i = buf.indexOf("\n")) >= 0) {
42
+ const line = buf.slice(0, i).trim();
43
+ buf = buf.slice(i + 1);
44
+ if (line.length === 0)
45
+ continue;
46
+ let msg = null;
47
+ try {
48
+ const raw = JSON.parse(line);
49
+ if (raw && typeof raw === "object" && !Array.isArray(raw)) {
50
+ msg = raw;
51
+ }
52
+ }
53
+ catch {
54
+ continue;
55
+ }
56
+ if (msg && typeof msg.id === "number" && pending.has(msg.id)) {
57
+ const p = pending.get(msg.id);
58
+ pending.delete(msg.id);
59
+ if (msg.error) {
60
+ p.reject(new Error(`JSONRPC error: ${JSON.stringify(msg.error)}`));
61
+ }
62
+ else {
63
+ p.resolve(msg.result);
64
+ }
65
+ }
66
+ }
67
+ });
68
+ function send(method, params) {
69
+ const id = nextId++;
70
+ const message = { jsonrpc: "2.0", id, method, params };
71
+ return new Promise((resolve, reject) => {
72
+ pending.set(id, { resolve, reject });
73
+ proc.stdin.write(`${JSON.stringify(message)}\n`);
74
+ });
75
+ }
76
+ // Wrap the whole handshake in a deadline.
77
+ const deadline = new Promise((_, reject) => {
78
+ setTimeout(() => reject(new Error("Handshake timed out")), timeoutMs);
79
+ });
80
+ const handshake = (async () => {
81
+ // 1. initialize
82
+ await Promise.race([
83
+ send("initialize", {
84
+ protocolVersion: PROTOCOL_VERSION,
85
+ capabilities: {},
86
+ clientInfo: { name: "apimapper-mcp-setup", version: "0.1.0" },
87
+ }),
88
+ deadline,
89
+ ]);
90
+ // 2. tools/list
91
+ const result = (await Promise.race([
92
+ send("tools/list", {}),
93
+ deadline,
94
+ ]));
95
+ if (!result || !Array.isArray(result.tools)) {
96
+ throw new Error("tools/list returned no tools array");
97
+ }
98
+ const names = result.tools
99
+ .map((t) => (typeof t?.name === "string" ? t.name : null))
100
+ .filter((n) => n !== null);
101
+ return names;
102
+ })();
103
+ return handshake;
104
+ }
105
+ export async function probeHandshake(opts) {
106
+ const spawnFn = opts.spawn ?? defaultSpawn;
107
+ const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
108
+ const env = opts.env ?? {};
109
+ let proc;
110
+ try {
111
+ proc = spawnFn(opts.command, opts.args, env);
112
+ }
113
+ catch (err) {
114
+ return {
115
+ ok: false,
116
+ error: `Failed to spawn MCP process: ${err instanceof Error ? err.message : String(err)}`,
117
+ };
118
+ }
119
+ try {
120
+ const tools = await runHandshake(proc, timeoutMs);
121
+ if (!tools.includes(HEALTH_TOOL)) {
122
+ return {
123
+ ok: false,
124
+ tools,
125
+ error: `Tools list missing ${HEALTH_TOOL} (got: ${tools.join(", ") || "<empty>"})`,
126
+ };
127
+ }
128
+ return { ok: true, tools };
129
+ }
130
+ catch (err) {
131
+ return {
132
+ ok: false,
133
+ error: err instanceof Error ? err.message : String(err),
134
+ };
135
+ }
136
+ finally {
137
+ try {
138
+ proc.kill();
139
+ }
140
+ catch {
141
+ /* best-effort */
142
+ }
143
+ }
144
+ }
145
+ /**
146
+ * Roll back every undo handle in order. Errors are caught + ignored — the
147
+ * goal is to clean up as much as possible, not bail on the first failure.
148
+ */
149
+ export async function rollbackAll(undos) {
150
+ for (const undo of undos) {
151
+ try {
152
+ await undo();
153
+ }
154
+ catch {
155
+ /* best-effort */
156
+ }
157
+ }
158
+ }
159
+ //# sourceMappingURL=probe-handshake.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"probe-handshake.js","sourceRoot":"","sources":["../../src/setup/probe-handshake.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ export declare const MANAGED_MARKER = "x-managed-by";
2
+ export declare const MANAGED_VALUE = "@wootsup/mcp";
3
+ export declare const TOML_MANAGED_COMMENT = "# @apimapper-managed";
4
+ export interface McpEntry {
5
+ command: string;
6
+ args: string[];
7
+ env?: Record<string, string>;
8
+ }
9
+ export interface WriteConfigResult {
10
+ /** True iff we actually added or updated our entry. */
11
+ added: boolean;
12
+ /**
13
+ * True iff an existing managed entry of the same key was found (and
14
+ * updated). False when the key was missing OR when an unmanaged entry
15
+ * blocked the write.
16
+ */
17
+ alreadyManaged: boolean;
18
+ /**
19
+ * Roll back the write — restore the file to its pre-write content.
20
+ * Safe to call even when `added=false`.
21
+ */
22
+ undo: () => Promise<void>;
23
+ }
24
+ export declare function writeJsonConfig(path: string, key: string, entry: McpEntry): Promise<WriteConfigResult>;
25
+ export declare function writeTomlConfig(path: string, key: string, entry: McpEntry): Promise<WriteConfigResult>;