@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,68 @@
1
+ // src/skill-instructions.ts — Reads the bundled apimapper skill's SKILL.md
2
+ // and returns the body (post-frontmatter) truncated to 500 chars, for use
3
+ // as the McpServer `instructions` field.
4
+ //
5
+ // Falls back to a one-line default if the file is missing or unreadable.
6
+ import { readFileSync, existsSync } from "node:fs";
7
+ import { fileURLToPath } from "node:url";
8
+ import { dirname, join, resolve } from "node:path";
9
+ export const FALLBACK_INSTRUCTIONS = "Use this MCP to manage API Mapper flows, connections, and sources across WordPress + Joomla.";
10
+ const MAX_LENGTH = 500;
11
+ /**
12
+ * Loads MCP server instructions from skills/apimapper/SKILL.md.
13
+ *
14
+ * @param rootDir - Override the package root directory (used by tests).
15
+ * Defaults to the directory two levels up from this file
16
+ * (i.e. the package root), so that `skills/apimapper/SKILL.md`
17
+ * resolves correctly both in dev (src/) and in build (dist/).
18
+ */
19
+ export function loadSkillInstructions(rootDir) {
20
+ const baseDir = rootDir ?? defaultRoot();
21
+ const skillPath = join(baseDir, "skills", "apimapper", "SKILL.md");
22
+ if (!existsSync(skillPath)) {
23
+ return FALLBACK_INSTRUCTIONS;
24
+ }
25
+ let raw;
26
+ try {
27
+ raw = readFileSync(skillPath, "utf8");
28
+ }
29
+ catch {
30
+ return FALLBACK_INSTRUCTIONS;
31
+ }
32
+ const body = stripFrontmatter(raw).trim();
33
+ if (body.length === 0) {
34
+ return FALLBACK_INSTRUCTIONS;
35
+ }
36
+ return body.slice(0, MAX_LENGTH);
37
+ }
38
+ /**
39
+ * Strips a YAML frontmatter block (`---\n...\n---\n`) from the start of the
40
+ * input. Returns the original string if no frontmatter is detected.
41
+ *
42
+ * Tiny inline parser — gray-matter would pull in dependencies we don't need.
43
+ */
44
+ function stripFrontmatter(input) {
45
+ if (!input.startsWith("---")) {
46
+ return input;
47
+ }
48
+ // Find the closing fence. Must be at line start.
49
+ const closing = input.indexOf("\n---", 3);
50
+ if (closing < 0) {
51
+ return input;
52
+ }
53
+ // Advance past the closing fence and the newline that follows it.
54
+ const after = input.indexOf("\n", closing + 4);
55
+ if (after < 0) {
56
+ // Closing fence is the last line; return empty body.
57
+ return "";
58
+ }
59
+ return input.slice(after + 1);
60
+ }
61
+ function defaultRoot() {
62
+ // This file lives at <pkg-root>/src/skill-instructions.ts (or
63
+ // <pkg-root>/dist/skill-instructions.js after build). In both cases,
64
+ // the package root is one directory up.
65
+ const here = dirname(fileURLToPath(import.meta.url));
66
+ return resolve(here, "..");
67
+ }
68
+ //# sourceMappingURL=skill-instructions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skill-instructions.js","sourceRoot":"","sources":["../src/skill-instructions.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAC3E,0EAA0E;AAC1E,yCAAyC;AACzC,EAAE;AACF,yEAAyE;AAEzE,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEnD,MAAM,CAAC,MAAM,qBAAqB,GAChC,8FAA8F,CAAC;AAEjG,MAAM,UAAU,GAAG,GAAG,CAAC;AAEvB;;;;;;;GAOG;AACH,MAAM,UAAU,qBAAqB,CAAC,OAAgB;IACpD,MAAM,OAAO,GAAG,OAAO,IAAI,WAAW,EAAE,CAAC;IACzC,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,UAAU,CAAC,CAAC;IAEnE,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QAC3B,OAAO,qBAAqB,CAAC;IAC/B,CAAC;IAED,IAAI,GAAW,CAAC;IAChB,IAAI,CAAC;QACH,GAAG,GAAG,YAAY,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;IACxC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,qBAAqB,CAAC;IAC/B,CAAC;IAED,MAAM,IAAI,GAAG,gBAAgB,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;IAC1C,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACtB,OAAO,qBAAqB,CAAC;IAC/B,CAAC;IACD,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC;AACnC,CAAC;AAED;;;;;GAKG;AACH,SAAS,gBAAgB,CAAC,KAAa;IACrC,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,KAAK,CAAC,EAAE,CAAC;QAC7B,OAAO,KAAK,CAAC;IACf,CAAC;IACD,iDAAiD;IACjD,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;IAC1C,IAAI,OAAO,GAAG,CAAC,EAAE,CAAC;QAChB,OAAO,KAAK,CAAC;IACf,CAAC;IACD,kEAAkE;IAClE,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,GAAG,CAAC,CAAC,CAAC;IAC/C,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;QACd,qDAAqD;QACrD,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,OAAO,KAAK,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC;AAChC,CAAC;AAED,SAAS,WAAW;IAClB,8DAA8D;IAC9D,qEAAqE;IACrE,wCAAwC;IACxC,MAAM,IAAI,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;IACrD,OAAO,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;AAC7B,CAAC"}
@@ -0,0 +1,29 @@
1
+ import { type Server } from "node:http";
2
+ import type { OAuthProvider } from "../auth/oauth-provider.js";
3
+ export interface HttpAuthInfo {
4
+ client_id: string;
5
+ scope: string;
6
+ }
7
+ export type McpHandler = (body: unknown, authInfo: HttpAuthInfo | null) => Promise<unknown>;
8
+ export interface HttpTransportOptions {
9
+ /** TCP port to bind. Use 0 for an ephemeral port. */
10
+ port: number;
11
+ /** OAuth provider state — typically a singleton per process. */
12
+ oauth: OAuthProvider;
13
+ /** MCP routing callback. Receives parsed JSON body + auth info. */
14
+ mcpHandler: McpHandler;
15
+ /**
16
+ * Public base URL used in OAuth metadata. Default: derived from the
17
+ * bound address (`http://127.0.0.1:<port>`).
18
+ */
19
+ publicBaseUrl?: string;
20
+ }
21
+ export interface HttpTransportServer {
22
+ /** http.Server instance. */
23
+ server: Server;
24
+ /** Resolved base URL (with port). */
25
+ url: string;
26
+ /** Stop accepting new connections + close existing keep-alives. */
27
+ close(): Promise<void>;
28
+ }
29
+ export declare function createHttpTransportServer(options: HttpTransportOptions): Promise<HttpTransportServer>;
@@ -0,0 +1,267 @@
1
+ // src/transports/http.ts — Phase 9.2.
2
+ //
3
+ // Native node:http server that exposes:
4
+ //
5
+ // GET /health — readiness probe
6
+ // GET /.well-known/oauth-authorization-server — RFC 8414 metadata
7
+ // POST /register — RFC 7591 Dynamic Client Registration
8
+ // GET /authorize — OAuth 2.0 Authorization endpoint
9
+ // POST /token — OAuth 2.0 Token endpoint
10
+ // POST /mcp — MCP JSON-RPC (Bearer-gated)
11
+ //
12
+ // The MCP routing is intentionally pluggable via the `mcpHandler` callback:
13
+ // this file is responsible ONLY for HTTP framing + OAuth auth. The boot
14
+ // entry `src/server-http.ts` glues this transport to a real MCP server by
15
+ // supplying a handler that uses the SDK's StreamableHTTPServerTransport.
16
+ //
17
+ // Why hand-rolled HTTP instead of Express? We already have @modelcontextprotocol/sdk
18
+ // + zod + clack as runtime deps; adding Express would balloon the DXT bundle
19
+ // for ~50 lines of routing logic we own outright.
20
+ import { createServer as createHttpServer } from "node:http";
21
+ // ── Helpers ────────────────────────────────────────────────────────────
22
+ function send(res, status, body, extraHeaders = {}) {
23
+ const payload = typeof body === "string" ? body : JSON.stringify(body);
24
+ res.writeHead(status, {
25
+ "content-type": typeof body === "string" ? "text/plain; charset=utf-8" : "application/json",
26
+ "content-length": String(Buffer.byteLength(payload)),
27
+ ...extraHeaders,
28
+ });
29
+ res.end(payload);
30
+ }
31
+ function sendError(res, status, error, description) {
32
+ const body = { error };
33
+ if (description)
34
+ body.error_description = description;
35
+ send(res, status, body);
36
+ }
37
+ async function readBody(req) {
38
+ return new Promise((resolve, reject) => {
39
+ const chunks = [];
40
+ req.on("data", (c) => chunks.push(c));
41
+ req.on("end", () => resolve(Buffer.concat(chunks).toString("utf8")));
42
+ req.on("error", reject);
43
+ });
44
+ }
45
+ function parseJson(raw) {
46
+ if (!raw)
47
+ return {};
48
+ try {
49
+ return JSON.parse(raw);
50
+ }
51
+ catch {
52
+ return null;
53
+ }
54
+ }
55
+ function getBearer(req) {
56
+ const hdr = req.headers["authorization"];
57
+ if (typeof hdr !== "string")
58
+ return null;
59
+ const m = /^Bearer\s+(.+)$/i.exec(hdr);
60
+ return m ? m[1] : null;
61
+ }
62
+ // ── Factory ────────────────────────────────────────────────────────────
63
+ export async function createHttpTransportServer(options) {
64
+ const { oauth, mcpHandler } = options;
65
+ // We compute the public base URL lazily — for port: 0 we need it after
66
+ // listen() resolves.
67
+ let publicBaseUrl = options.publicBaseUrl ?? "";
68
+ const server = createHttpServer(async (req, res) => {
69
+ try {
70
+ await handle(req, res);
71
+ }
72
+ catch (err) {
73
+ // Never let the listener throw — surface as a 500 with a sanitized body.
74
+ const msg = err instanceof Error ? err.message : "unknown_error";
75
+ sendError(res, 500, "server_error", msg);
76
+ }
77
+ });
78
+ async function handle(req, res) {
79
+ const url = new URL(req.url ?? "/", publicBaseUrl || "http://127.0.0.1");
80
+ const path = url.pathname;
81
+ const method = (req.method ?? "GET").toUpperCase();
82
+ // ── /health ──────────────────────────────────────────────────────
83
+ if (path === "/health" && method === "GET") {
84
+ send(res, 200, { status: "ok", transport: "http" });
85
+ return;
86
+ }
87
+ // ── /.well-known/oauth-authorization-server ──────────────────────
88
+ if (path === "/.well-known/oauth-authorization-server" &&
89
+ method === "GET") {
90
+ send(res, 200, {
91
+ issuer: publicBaseUrl,
92
+ authorization_endpoint: `${publicBaseUrl}/authorize`,
93
+ token_endpoint: `${publicBaseUrl}/token`,
94
+ registration_endpoint: `${publicBaseUrl}/register`,
95
+ response_types_supported: ["code"],
96
+ grant_types_supported: ["authorization_code", "refresh_token"],
97
+ code_challenge_methods_supported: ["S256"],
98
+ token_endpoint_auth_methods_supported: ["none"],
99
+ scopes_supported: ["read", "write", "admin"],
100
+ });
101
+ return;
102
+ }
103
+ // ── POST /register (RFC 7591 DCR) ────────────────────────────────
104
+ if (path === "/register" && method === "POST") {
105
+ const raw = await readBody(req);
106
+ const body = parseJson(raw);
107
+ if (body === null || typeof body !== "object") {
108
+ sendError(res, 400, "invalid_client_metadata", "body must be JSON");
109
+ return;
110
+ }
111
+ const b = body;
112
+ const redirectUris = Array.isArray(b.redirect_uris)
113
+ ? b.redirect_uris.filter((u) => typeof u === "string")
114
+ : [];
115
+ const clientName = typeof b.client_name === "string" ? b.client_name : undefined;
116
+ try {
117
+ const reg = oauth.registerClient({
118
+ redirect_uris: redirectUris,
119
+ client_name: clientName,
120
+ });
121
+ send(res, 201, reg);
122
+ }
123
+ catch (err) {
124
+ const msg = err instanceof Error ? err.message : "invalid_client_metadata";
125
+ sendError(res, 400, "invalid_client_metadata", msg);
126
+ }
127
+ return;
128
+ }
129
+ // ── GET /authorize ───────────────────────────────────────────────
130
+ if (path === "/authorize" && method === "GET") {
131
+ const q = url.searchParams;
132
+ const clientId = q.get("client_id") ?? "";
133
+ const redirectUri = q.get("redirect_uri") ?? "";
134
+ const responseType = q.get("response_type") ?? "";
135
+ const codeChallenge = q.get("code_challenge") ?? "";
136
+ const codeChallengeMethod = q.get("code_challenge_method") ?? "";
137
+ const scope = q.get("scope") ?? "";
138
+ const state = q.get("state") ?? undefined;
139
+ if (responseType !== "code") {
140
+ sendError(res, 400, "unsupported_response_type");
141
+ return;
142
+ }
143
+ const client = oauth.getClient(clientId);
144
+ if (!client) {
145
+ sendError(res, 400, "unknown_client");
146
+ return;
147
+ }
148
+ if (!client.redirect_uris.includes(redirectUri)) {
149
+ sendError(res, 400, "invalid_redirect_uri");
150
+ return;
151
+ }
152
+ if (codeChallengeMethod !== "S256") {
153
+ sendError(res, 400, "invalid_request", "code_challenge_method must be S256");
154
+ return;
155
+ }
156
+ let code;
157
+ try {
158
+ code = oauth.issueCode({
159
+ client_id: clientId,
160
+ redirect_uri: redirectUri,
161
+ code_challenge: codeChallenge,
162
+ code_challenge_method: "S256",
163
+ scope,
164
+ state,
165
+ });
166
+ }
167
+ catch (err) {
168
+ const msg = err instanceof Error ? err.message : "invalid_request";
169
+ sendError(res, 400, "invalid_request", msg);
170
+ return;
171
+ }
172
+ const cbUrl = new URL(redirectUri);
173
+ cbUrl.searchParams.set("code", code);
174
+ if (state !== undefined)
175
+ cbUrl.searchParams.set("state", state);
176
+ res.writeHead(302, { location: cbUrl.toString() });
177
+ res.end();
178
+ return;
179
+ }
180
+ // ── POST /token ──────────────────────────────────────────────────
181
+ if (path === "/token" && method === "POST") {
182
+ const raw = await readBody(req);
183
+ const params = new URLSearchParams(raw);
184
+ const grantType = params.get("grant_type") ?? "";
185
+ try {
186
+ if (grantType === "authorization_code") {
187
+ const tokens = oauth.exchangeCode({
188
+ client_id: params.get("client_id") ?? "",
189
+ code: params.get("code") ?? "",
190
+ redirect_uri: params.get("redirect_uri") ?? "",
191
+ code_verifier: params.get("code_verifier") ?? "",
192
+ });
193
+ send(res, 200, tokens);
194
+ return;
195
+ }
196
+ if (grantType === "refresh_token") {
197
+ const tokens = oauth.refreshToken({
198
+ client_id: params.get("client_id") ?? "",
199
+ refresh_token: params.get("refresh_token") ?? "",
200
+ });
201
+ send(res, 200, tokens);
202
+ return;
203
+ }
204
+ sendError(res, 400, "unsupported_grant_type");
205
+ }
206
+ catch (err) {
207
+ const msg = err instanceof Error ? err.message : "invalid_grant";
208
+ sendError(res, 400, "invalid_grant", msg);
209
+ }
210
+ return;
211
+ }
212
+ // ── POST /mcp ────────────────────────────────────────────────────
213
+ if (path === "/mcp" && method === "POST") {
214
+ const token = getBearer(req);
215
+ if (!token) {
216
+ send(res, 401, { error: "unauthorized", error_description: "Bearer token required" }, {
217
+ "www-authenticate": `Bearer realm="${publicBaseUrl}", error="invalid_token"`,
218
+ });
219
+ return;
220
+ }
221
+ const info = oauth.verifyAccessToken(token);
222
+ if (!info) {
223
+ send(res, 401, {
224
+ error: "invalid_token",
225
+ error_description: "Bearer token unknown or expired",
226
+ }, {
227
+ "www-authenticate": `Bearer realm="${publicBaseUrl}", error="invalid_token"`,
228
+ });
229
+ return;
230
+ }
231
+ const raw = await readBody(req);
232
+ const body = parseJson(raw);
233
+ if (body === null) {
234
+ sendError(res, 400, "invalid_request", "body must be JSON");
235
+ return;
236
+ }
237
+ const result = await mcpHandler(body, {
238
+ client_id: info.client_id,
239
+ scope: info.scope,
240
+ });
241
+ send(res, 200, result);
242
+ return;
243
+ }
244
+ // ── 404 fallback ─────────────────────────────────────────────────
245
+ sendError(res, 404, "not_found", `no route for ${method} ${path}`);
246
+ }
247
+ // Bind + resolve base URL
248
+ await new Promise((resolve) => server.listen(options.port, resolve));
249
+ const addr = server.address();
250
+ if (!publicBaseUrl) {
251
+ publicBaseUrl = `http://127.0.0.1:${addr.port}`;
252
+ }
253
+ else if (publicBaseUrl === "http://127.0.0.1" ||
254
+ publicBaseUrl === "http://localhost") {
255
+ publicBaseUrl = `${publicBaseUrl}:${addr.port}`;
256
+ }
257
+ return {
258
+ server,
259
+ url: publicBaseUrl,
260
+ close: () => new Promise((resolve, reject) => {
261
+ server.close((err) => (err ? reject(err) : resolve()));
262
+ // Force-close keep-alive sockets so tests don't hang.
263
+ server.closeAllConnections?.();
264
+ }),
265
+ };
266
+ }
267
+ //# sourceMappingURL=http.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http.js","sourceRoot":"","sources":["../../src/transports/http.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ /**
3
+ * Connect the given McpServer to a fresh `StdioServerTransport`.
4
+ *
5
+ * Resolves once the SDK has finished its handshake. Rejects with the
6
+ * underlying SDK error if `server.connect()` throws — callers should treat
7
+ * a rejection as a fatal startup error.
8
+ */
9
+ export declare function connectStdio(server: McpServer): Promise<void>;
@@ -0,0 +1,19 @@
1
+ // src/transports/stdio.ts — Phase 9.1.
2
+ //
3
+ // Thin factory that connects an McpServer to a StdioServerTransport so the
4
+ // process speaks JSON-RPC over stdin/stdout. Extracted from src/index.ts as
5
+ // part of the multi-transport split — Task 9.2 introduces an HTTP variant
6
+ // using the same `connect*` shape.
7
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
8
+ /**
9
+ * Connect the given McpServer to a fresh `StdioServerTransport`.
10
+ *
11
+ * Resolves once the SDK has finished its handshake. Rejects with the
12
+ * underlying SDK error if `server.connect()` throws — callers should treat
13
+ * a rejection as a fatal startup error.
14
+ */
15
+ export async function connectStdio(server) {
16
+ const transport = new StdioServerTransport();
17
+ await server.connect(transport);
18
+ }
19
+ //# sourceMappingURL=stdio.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stdio.js","sourceRoot":"","sources":["../../src/transports/stdio.ts"],"names":[],"mappings":"AAAA,uCAAuC;AACvC,EAAE;AACF,2EAA2E;AAC3E,4EAA4E;AAC5E,0EAA0E;AAC1E,mCAAmC;AAGnC,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AAEjF;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,MAAiB;IAClD,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;AAClC,CAAC"}
@@ -0,0 +1,140 @@
1
+ # Architecture
2
+
3
+ `@wootsup/mcp` is the MCP server side of the API Mapper product.
4
+ This document is intentionally short — for the full design, see the
5
+ internal plan-doc `~/Projekte/getimo/40-Plans/active/
6
+ 2026-05-18-apimapper-mcp-implementation-plan.md` (vault, not public).
7
+
8
+ ## System diagram
9
+
10
+ ```
11
+ ┌──────────────────────────────────────────────────────────────┐
12
+ │ AI client │
13
+ │ (Claude Desktop · Claude Code · Cursor · VS Code · ChatGPT) │
14
+ └─────────────┬─────────────────────────────────┬──────────────┘
15
+ │ stdio │ HTTP+OAuth2 PKCE
16
+ │ (npx / DXT) │ (server-http.ts)
17
+ ▼ ▼
18
+ ┌──────────────────────────────────────────────────────────────┐
19
+ │ @wootsup/mcp (this package) │
20
+ │ │
21
+ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────────┐ │
22
+ │ │ Transport │→ │ Tool │→ │ Platform router │ │
23
+ │ │ stdio / │ │ registry │ │ (WP / Joomla) │ │
24
+ │ │ HTTP+OAuth │ │ (~76 tools) │ │ │ │
25
+ │ └──────────────┘ └──────────────┘ └──────────────────┘ │
26
+ │ │ ▲ │ │
27
+ │ │ │ │ │
28
+ │ ▼ │ ▼ │
29
+ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────────┐ │
30
+ │ │ Setup CLI │ │ Bundled │ │ HTTP client │ │
31
+ │ │ (clack) │ │ skill │ │ + sanitizer │ │
32
+ │ └──────┬───────┘ └──────────────┘ └────────┬─────────┘ │
33
+ │ │ keychain / profile │ │
34
+ │ ▼ │ │
35
+ │ ┌──────────────┐ │ │
36
+ │ │ Keychain │ │ │
37
+ │ │ + Profiles │ │ │
38
+ │ └──────────────┘ │ │
39
+ └────────────────────────────────────────────────┼─────────────┘
40
+
41
+ │ HTTPS + Bearer
42
+ │ (amk_live_…)
43
+
44
+ ┌──────────────────────────────────────────────────────────────┐
45
+ │ API Mapper plugin (WordPress or Joomla) │
46
+ │ │
47
+ │ WordPress: /wp-json/api-mapper/v1/{mcp,connections,...} │
48
+ │ Joomla: /?option=com_apimapper&task=mcp.{...} │
49
+ │ │
50
+ │ HMAC-SHA256 verify → scope check → handler │
51
+ └──────────────────────────────────────────────────────────────┘
52
+ ```
53
+
54
+ ## Five components
55
+
56
+ 1. **Transport layer** (`src/transports/`)
57
+ - `stdio.ts` — default; pipes JSON-RPC over stdin/stdout. Spawned
58
+ by the AI client.
59
+ - `http.ts` + `server-http.ts` — HTTP+SSE transport behind an
60
+ OAuth 2.0 PKCE-required authorization server. Bearer tokens are
61
+ site-bound; cross-origin requests are rejected.
62
+
63
+ 2. **Tool registry** (`src/modules/apimapper/`)
64
+ - 15 register-files (connections, credentials, flows, graph,
65
+ local-sources, schema, library, cache, settings, license, misc,
66
+ workflows, get-skill, onboarding, inspect, diagnose,
67
+ use-profile) plus 1 entry-point (`index.ts`).
68
+ - Each tool follows the `@getimo/mcp-toolkit` Goldstandard:
69
+ annotations, structured errors, `formatResult` / `autoFormatTable`,
70
+ pagination clamps, destructive confirm-guards.
71
+
72
+ 3. **Platform router** (`src/modules/apimapper/client.ts`)
73
+ - Detects WP vs. Joomla from site URL + `APIMAPPER_PLATFORM` env.
74
+ - Dispatches to `/wp-json/api-mapper/v1/...` (WP) or
75
+ `index.php?option=com_apimapper&task=...` (Joomla).
76
+ - Joomla envelope unwrap is handled here transparently.
77
+
78
+ 4. **Auth / keychain / profiles** (`src/auth/`)
79
+ - `keychain.ts` — `@napi-rs/keyring` wrapper with file-fallback.
80
+ - `profiles.ts` — named profiles (`APIMAPPER_PROFILE=staging` ↔
81
+ `APIMAPPER_PROFILE=prod`).
82
+ - `token.ts` — `amk_*` token decoding (payload only — signature is
83
+ opaque, verified server-side).
84
+ - `credential-sanitizer.ts` — strips secrets from every response.
85
+
86
+ 5. **Setup + skills** (`src/setup-cli.ts`, `skills/apimapper/`)
87
+ - `@clack/prompts` interactive wizard.
88
+ - Auto-detects AI clients, patches their MCP config idempotently.
89
+ - Ships the skill files (`SKILL.md` + 4 references) into
90
+ `~/.claude/skills/apimapper/`.
91
+
92
+ ## Auth flow (token issuance)
93
+
94
+ 1. User runs `npx @wootsup/mcp setup`.
95
+ 2. Wizard asks for site URL + token.
96
+ 3. Wizard calls `GET <site>/api-mapper/v1/mcp/identity` and verifies
97
+ the returned `siteSignature` against the public `key_id` carried
98
+ in the token payload.
99
+ 4. User confirms the displayed site title + URL.
100
+ 5. Wizard writes the token to OS keychain (or `0600` file fallback)
101
+ under a profile name (default: `default`).
102
+ 6. Wizard patches the AI client's MCP config to launch
103
+ `npx -y @wootsup/mcp` with `APIMAPPER_PROFILE=<name>` in env.
104
+
105
+ At runtime, the server reads the token from the keychain on every
106
+ launch — tokens never live in the client config file.
107
+
108
+ ## Multi-transport rationale
109
+
110
+ | | stdio | HTTP+OAuth |
111
+ |---|---|---|
112
+ | Where it runs | User's machine | User's machine or remote |
113
+ | Auth | Bearer from keychain | OAuth 2.0 PKCE bound to site |
114
+ | Use case | Local dev, single user | Remote AI agents (future) |
115
+ | Discovery | Static (one-tool-set) | Dynamic per session |
116
+
117
+ Both paths feed the same tool registry; the only difference is the
118
+ transport adapter wired into `createServer()`.
119
+
120
+ ## Multi-platform rationale
121
+
122
+ The same REST contract is implemented in both the WordPress plugin
123
+ and the Joomla extension. The router maps tool calls to whichever
124
+ URL shape the user's platform speaks. This means:
125
+
126
+ - One MCP server binary, two backends.
127
+ - Every tool works on both platforms (parity test in
128
+ `apimapper_platform_parity`).
129
+ - Users can move a flow between WP and Joomla via
130
+ `apimapper_flow_export` / `apimapper_flow_import` without manual
131
+ rewriting.
132
+
133
+ ## Further reading
134
+
135
+ - [`security.md`](./security.md) — threat model + mitigations.
136
+ - [`tools.md`](./tools.md) — full tool inventory.
137
+ - [`../skills/apimapper/SKILL.md`](../skills/apimapper/SKILL.md) —
138
+ task-oriented usage guide.
139
+ - Internal: `~/Projekte/getimo/40-Plans/active/
140
+ 2026-05-18-apimapper-mcp-implementation-plan.md` (full design plan).