@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.
- package/CHANGELOG.md +91 -0
- package/LICENSE +21 -0
- package/README.md +179 -0
- package/SECURITY.md +163 -0
- package/dist/auth/keychain.d.ts +47 -0
- package/dist/auth/keychain.js +262 -0
- package/dist/auth/keychain.js.map +1 -0
- package/dist/auth/oauth-provider.d.ts +68 -0
- package/dist/auth/oauth-provider.js +232 -0
- package/dist/auth/oauth-provider.js.map +1 -0
- package/dist/auth/profiles.d.ts +52 -0
- package/dist/auth/profiles.js +200 -0
- package/dist/auth/profiles.js.map +1 -0
- package/dist/auth/token.d.ts +27 -0
- package/dist/auth/token.js +88 -0
- package/dist/auth/token.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +137 -0
- package/dist/index.js.map +1 -0
- package/dist/install-skill.d.ts +23 -0
- package/dist/install-skill.js +73 -0
- package/dist/install-skill.js.map +1 -0
- package/dist/modules/apimapper/cache.d.ts +2 -0
- package/dist/modules/apimapper/cache.js +71 -0
- package/dist/modules/apimapper/cache.js.map +1 -0
- package/dist/modules/apimapper/client.d.ts +85 -0
- package/dist/modules/apimapper/client.js +523 -0
- package/dist/modules/apimapper/client.js.map +1 -0
- package/dist/modules/apimapper/connections.d.ts +2 -0
- package/dist/modules/apimapper/connections.js +406 -0
- package/dist/modules/apimapper/connections.js.map +1 -0
- package/dist/modules/apimapper/credential-sanitizer.d.ts +7 -0
- package/dist/modules/apimapper/credential-sanitizer.js +70 -0
- package/dist/modules/apimapper/credential-sanitizer.js.map +1 -0
- package/dist/modules/apimapper/credentials.d.ts +2 -0
- package/dist/modules/apimapper/credentials.js +258 -0
- package/dist/modules/apimapper/credentials.js.map +1 -0
- package/dist/modules/apimapper/diagnose.d.ts +18 -0
- package/dist/modules/apimapper/diagnose.js +305 -0
- package/dist/modules/apimapper/diagnose.js.map +1 -0
- package/dist/modules/apimapper/flows.d.ts +2 -0
- package/dist/modules/apimapper/flows.js +372 -0
- package/dist/modules/apimapper/flows.js.map +1 -0
- package/dist/modules/apimapper/get-skill.d.ts +4 -0
- package/dist/modules/apimapper/get-skill.js +88 -0
- package/dist/modules/apimapper/get-skill.js.map +1 -0
- package/dist/modules/apimapper/graph-builder.d.ts +47 -0
- package/dist/modules/apimapper/graph-builder.js +117 -0
- package/dist/modules/apimapper/graph-builder.js.map +1 -0
- package/dist/modules/apimapper/graph.d.ts +2 -0
- package/dist/modules/apimapper/graph.js +117 -0
- package/dist/modules/apimapper/graph.js.map +1 -0
- package/dist/modules/apimapper/index.d.ts +2 -0
- package/dist/modules/apimapper/index.js +43 -0
- package/dist/modules/apimapper/index.js.map +1 -0
- package/dist/modules/apimapper/inspect.d.ts +20 -0
- package/dist/modules/apimapper/inspect.js +86 -0
- package/dist/modules/apimapper/inspect.js.map +1 -0
- package/dist/modules/apimapper/library.d.ts +2 -0
- package/dist/modules/apimapper/library.js +237 -0
- package/dist/modules/apimapper/library.js.map +1 -0
- package/dist/modules/apimapper/license.d.ts +2 -0
- package/dist/modules/apimapper/license.js +142 -0
- package/dist/modules/apimapper/license.js.map +1 -0
- package/dist/modules/apimapper/local-sources.d.ts +2 -0
- package/dist/modules/apimapper/local-sources.js +123 -0
- package/dist/modules/apimapper/local-sources.js.map +1 -0
- package/dist/modules/apimapper/misc.d.ts +2 -0
- package/dist/modules/apimapper/misc.js +149 -0
- package/dist/modules/apimapper/misc.js.map +1 -0
- package/dist/modules/apimapper/node-schema.d.ts +217 -0
- package/dist/modules/apimapper/node-schema.js +218 -0
- package/dist/modules/apimapper/node-schema.js.map +1 -0
- package/dist/modules/apimapper/normalizers.d.ts +13 -0
- package/dist/modules/apimapper/normalizers.js +37 -0
- package/dist/modules/apimapper/normalizers.js.map +1 -0
- package/dist/modules/apimapper/onboarding.d.ts +51 -0
- package/dist/modules/apimapper/onboarding.js +201 -0
- package/dist/modules/apimapper/onboarding.js.map +1 -0
- package/dist/modules/apimapper/schema.d.ts +2 -0
- package/dist/modules/apimapper/schema.js +84 -0
- package/dist/modules/apimapper/schema.js.map +1 -0
- package/dist/modules/apimapper/settings.d.ts +2 -0
- package/dist/modules/apimapper/settings.js +157 -0
- package/dist/modules/apimapper/settings.js.map +1 -0
- package/dist/modules/apimapper/skill-resources.d.ts +4 -0
- package/dist/modules/apimapper/skill-resources.js +85 -0
- package/dist/modules/apimapper/skill-resources.js.map +1 -0
- package/dist/modules/apimapper/types.d.ts +111 -0
- package/dist/modules/apimapper/types.js +14 -0
- package/dist/modules/apimapper/types.js.map +1 -0
- package/dist/modules/apimapper/use-profile.d.ts +34 -0
- package/dist/modules/apimapper/use-profile.js +176 -0
- package/dist/modules/apimapper/use-profile.js.map +1 -0
- package/dist/modules/apimapper/workflows.d.ts +2 -0
- package/dist/modules/apimapper/workflows.js +301 -0
- package/dist/modules/apimapper/workflows.js.map +1 -0
- package/dist/platform/index.d.ts +71 -0
- package/dist/platform/index.js +377 -0
- package/dist/platform/index.js.map +1 -0
- package/dist/server-http.d.ts +22 -0
- package/dist/server-http.js +159 -0
- package/dist/server-http.js.map +1 -0
- package/dist/setup/detect-clients.d.ts +39 -0
- package/dist/setup/detect-clients.js +152 -0
- package/dist/setup/detect-clients.js.map +1 -0
- package/dist/setup/probe-handshake.d.ts +26 -0
- package/dist/setup/probe-handshake.js +159 -0
- package/dist/setup/probe-handshake.js.map +1 -0
- package/dist/setup/write-config.d.ts +25 -0
- package/dist/setup/write-config.js +247 -0
- package/dist/setup/write-config.js.map +1 -0
- package/dist/setup-cli.d.ts +49 -0
- package/dist/setup-cli.js +292 -0
- package/dist/setup-cli.js.map +1 -0
- package/dist/skill-instructions.d.ts +10 -0
- package/dist/skill-instructions.js +68 -0
- package/dist/skill-instructions.js.map +1 -0
- package/dist/transports/http.d.ts +29 -0
- package/dist/transports/http.js +267 -0
- package/dist/transports/http.js.map +1 -0
- package/dist/transports/stdio.d.ts +9 -0
- package/dist/transports/stdio.js +19 -0
- package/dist/transports/stdio.js.map +1 -0
- package/docs/architecture.md +140 -0
- package/docs/customgraph-internal-migration.md +210 -0
- package/docs/security.md +126 -0
- package/docs/tools.md +230 -0
- package/manifest.json +76 -0
- package/package.json +61 -0
- package/skills/apimapper/SKILL.md +57 -0
- package/skills/apimapper/reference/joomla.md +85 -0
- package/skills/apimapper/reference/oauth.md +94 -0
- package/skills/apimapper/reference/troubleshooting.md +123 -0
- package/skills/apimapper/reference/yootheme.md +96 -0
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
// src/auth/keychain.ts — Phase 5.1.
|
|
2
|
+
//
|
|
3
|
+
// Two storage backends behind a single Keychain interface:
|
|
4
|
+
//
|
|
5
|
+
// - SystemKeychain: thin wrapper around @napi-rs/keyring 1.3.x (`Entry`).
|
|
6
|
+
// Uses the OS keychain — macOS Keychain, Linux Secret Service / DBus,
|
|
7
|
+
// Windows DPAPI.
|
|
8
|
+
//
|
|
9
|
+
// - EncryptedFileKeychain: AES-256-GCM encrypted JSON file at
|
|
10
|
+
// `<configDir>/keychain.enc.json`. Used as fallback when the OS keychain
|
|
11
|
+
// isn't accessible (locked-down macOS sessions, minimal Linux without
|
|
12
|
+
// DBus, etc.).
|
|
13
|
+
//
|
|
14
|
+
// `createKeychain()` tries SystemKeychain first and falls back to the file
|
|
15
|
+
// impl on any throw. The encryption key is derived via PBKDF2 from a constant
|
|
16
|
+
// in-binary phrase + a per-install salt (random 32 bytes) stored beside the
|
|
17
|
+
// vault at `<configDir>/.salt`. This is NOT a strong defense against a local
|
|
18
|
+
// attacker with filesystem access, but it (a) prevents accidental leakage
|
|
19
|
+
// through casual file inspection, and (b) is no worse than the alternative
|
|
20
|
+
// (raw plaintext) for environments that already failed the system-keychain
|
|
21
|
+
// probe.
|
|
22
|
+
import { randomBytes, pbkdf2Sync, createCipheriv, createDecipheriv, } from "node:crypto";
|
|
23
|
+
import { mkdirSync, existsSync, readFileSync, writeFileSync, } from "node:fs";
|
|
24
|
+
import { createRequire } from "node:module";
|
|
25
|
+
import { join } from "node:path";
|
|
26
|
+
// ── SystemKeychain ───────────────────────────────────────────────────────
|
|
27
|
+
const SERVICE_NAME = "@wootsup/mcp";
|
|
28
|
+
// Defer the import error to construction time; tests on environments without
|
|
29
|
+
// the native binary (rare — package ships prebuilts for all major OSes) can
|
|
30
|
+
// fall through to the file impl via `createKeychain()`.
|
|
31
|
+
//
|
|
32
|
+
// We dynamic-require here because the napi binding initialises native code on
|
|
33
|
+
// load; deferring keeps unrelated tests fast and lets `createKeychain()` catch
|
|
34
|
+
// failures.
|
|
35
|
+
export class SystemKeychain {
|
|
36
|
+
service;
|
|
37
|
+
constructor(service = SERVICE_NAME) {
|
|
38
|
+
this.service = service;
|
|
39
|
+
// Touch the binding eagerly so ctor throws on broken envs — that's the
|
|
40
|
+
// signal `createKeychain()` uses to fall back.
|
|
41
|
+
//
|
|
42
|
+
// The binding's `new Entry(service, username)` does no I/O itself but
|
|
43
|
+
// requires the native lib to be loadable.
|
|
44
|
+
loadKeyringModule();
|
|
45
|
+
}
|
|
46
|
+
async set(ref, value) {
|
|
47
|
+
const keyring = loadKeyringModule();
|
|
48
|
+
const entry = new keyring.Entry(this.service, ref);
|
|
49
|
+
entry.setPassword(value);
|
|
50
|
+
}
|
|
51
|
+
async get(ref) {
|
|
52
|
+
const keyring = loadKeyringModule();
|
|
53
|
+
const entry = new keyring.Entry(this.service, ref);
|
|
54
|
+
try {
|
|
55
|
+
const v = entry.getPassword();
|
|
56
|
+
// The 1.3.x binding returns null when there's no entry. Other failure
|
|
57
|
+
// modes throw (e.g. Ambiguous on Linux with duplicate secrets).
|
|
58
|
+
return v ?? null;
|
|
59
|
+
}
|
|
60
|
+
catch (e) {
|
|
61
|
+
// Treat "no entry" as a normal miss; rethrow any other failure.
|
|
62
|
+
if (isNoEntryError(e))
|
|
63
|
+
return null;
|
|
64
|
+
throw e;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
async delete(ref) {
|
|
68
|
+
const keyring = loadKeyringModule();
|
|
69
|
+
const entry = new keyring.Entry(this.service, ref);
|
|
70
|
+
try {
|
|
71
|
+
entry.deletePassword();
|
|
72
|
+
}
|
|
73
|
+
catch (e) {
|
|
74
|
+
// Idempotent: missing entry is fine.
|
|
75
|
+
if (isNoEntryError(e))
|
|
76
|
+
return;
|
|
77
|
+
throw e;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
let _keyringMod;
|
|
82
|
+
function loadKeyringModule() {
|
|
83
|
+
if (_keyringMod)
|
|
84
|
+
return _keyringMod;
|
|
85
|
+
// The napi binding ships CJS and we want a synchronous throw at
|
|
86
|
+
// SystemKeychain ctor time, so we use createRequire (statically
|
|
87
|
+
// imported above) to load the CJS bridge from an ESM module.
|
|
88
|
+
//
|
|
89
|
+
// 2026-05-18 fix: the previous implementation used `require("node:module")`
|
|
90
|
+
// inside this function — but `require` is not defined globally in ESM,
|
|
91
|
+
// which made the SystemKeychain path fail with "require is not defined"
|
|
92
|
+
// before it could even try the keyring binding. The customer-facing
|
|
93
|
+
// symptom was every fresh install falling back to the file keychain.
|
|
94
|
+
const req = createRequire(import.meta.url);
|
|
95
|
+
_keyringMod = req("@napi-rs/keyring");
|
|
96
|
+
return _keyringMod;
|
|
97
|
+
}
|
|
98
|
+
function isNoEntryError(e) {
|
|
99
|
+
if (!(e instanceof Error))
|
|
100
|
+
return false;
|
|
101
|
+
// The 1.3.x bindings surface NoEntry as an Error whose message contains
|
|
102
|
+
// "NoEntry" or "no entry" depending on platform. Be liberal.
|
|
103
|
+
return /no\s*entry/i.test(e.message);
|
|
104
|
+
}
|
|
105
|
+
// ── EncryptedFileKeychain ────────────────────────────────────────────────
|
|
106
|
+
// AES-GCM parameters: 256-bit key, 96-bit IV (NIST-recommended), 128-bit tag.
|
|
107
|
+
const KEY_LEN = 32;
|
|
108
|
+
const IV_LEN = 12;
|
|
109
|
+
const TAG_LEN = 16;
|
|
110
|
+
const SALT_LEN = 32;
|
|
111
|
+
const PBKDF2_ITERS = 200_000;
|
|
112
|
+
const PBKDF2_DIGEST = "sha256";
|
|
113
|
+
// Constant in-binary phrase mixed with the per-install salt. Not a secret in
|
|
114
|
+
// the cryptographic sense (it's checked into the repo), but ensures the
|
|
115
|
+
// derived key is unique to this product even if two unrelated tools share the
|
|
116
|
+
// same salt file path by accident.
|
|
117
|
+
const KDF_PHRASE = "apimapper-mcp/v1/keychain";
|
|
118
|
+
export class EncryptedFileKeychain {
|
|
119
|
+
dir;
|
|
120
|
+
vaultPath;
|
|
121
|
+
saltPath;
|
|
122
|
+
cachedKey;
|
|
123
|
+
// In-memory mutex to serialise writes — vitest hits concurrent set() in the
|
|
124
|
+
// "concurrent writes" test and we don't want a torn JSON file.
|
|
125
|
+
writeChain = Promise.resolve();
|
|
126
|
+
constructor(configDir) {
|
|
127
|
+
this.dir = configDir;
|
|
128
|
+
this.vaultPath = join(configDir, "keychain.enc.json");
|
|
129
|
+
this.saltPath = join(configDir, ".salt");
|
|
130
|
+
if (!existsSync(this.dir)) {
|
|
131
|
+
mkdirSync(this.dir, { recursive: true });
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
deriveKey() {
|
|
135
|
+
if (this.cachedKey)
|
|
136
|
+
return this.cachedKey;
|
|
137
|
+
let salt;
|
|
138
|
+
if (existsSync(this.saltPath)) {
|
|
139
|
+
salt = readFileSync(this.saltPath);
|
|
140
|
+
if (salt.length !== SALT_LEN) {
|
|
141
|
+
// Corrupt salt — regenerate. Old entries become unreadable; that's
|
|
142
|
+
// an acceptable failure mode for a recovery flow.
|
|
143
|
+
salt = randomBytes(SALT_LEN);
|
|
144
|
+
writeFileSync(this.saltPath, salt, { mode: 0o600 });
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
salt = randomBytes(SALT_LEN);
|
|
149
|
+
writeFileSync(this.saltPath, salt, { mode: 0o600 });
|
|
150
|
+
}
|
|
151
|
+
this.cachedKey = pbkdf2Sync(KDF_PHRASE, salt, PBKDF2_ITERS, KEY_LEN, PBKDF2_DIGEST);
|
|
152
|
+
return this.cachedKey;
|
|
153
|
+
}
|
|
154
|
+
readVault() {
|
|
155
|
+
if (!existsSync(this.vaultPath))
|
|
156
|
+
return {};
|
|
157
|
+
try {
|
|
158
|
+
const raw = readFileSync(this.vaultPath, "utf8");
|
|
159
|
+
if (!raw)
|
|
160
|
+
return {};
|
|
161
|
+
const parsed = JSON.parse(raw);
|
|
162
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
163
|
+
return {};
|
|
164
|
+
}
|
|
165
|
+
return parsed;
|
|
166
|
+
}
|
|
167
|
+
catch {
|
|
168
|
+
// Corrupt vault file: surface as empty rather than crashing — the
|
|
169
|
+
// surrounding flow can re-prompt for the token and overwrite.
|
|
170
|
+
return {};
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
writeVault(vault) {
|
|
174
|
+
writeFileSync(this.vaultPath, JSON.stringify(vault, null, 2), {
|
|
175
|
+
mode: 0o600,
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
encrypt(plaintext) {
|
|
179
|
+
const key = this.deriveKey();
|
|
180
|
+
const iv = randomBytes(IV_LEN);
|
|
181
|
+
const cipher = createCipheriv("aes-256-gcm", key, iv);
|
|
182
|
+
const ct = Buffer.concat([
|
|
183
|
+
cipher.update(plaintext, "utf8"),
|
|
184
|
+
cipher.final(),
|
|
185
|
+
]);
|
|
186
|
+
const tag = cipher.getAuthTag();
|
|
187
|
+
return {
|
|
188
|
+
iv: iv.toString("base64"),
|
|
189
|
+
ciphertext: ct.toString("base64"),
|
|
190
|
+
tag: tag.toString("base64"),
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
decrypt(rec) {
|
|
194
|
+
try {
|
|
195
|
+
const key = this.deriveKey();
|
|
196
|
+
const iv = Buffer.from(rec.iv, "base64");
|
|
197
|
+
const ct = Buffer.from(rec.ciphertext, "base64");
|
|
198
|
+
const tag = Buffer.from(rec.tag, "base64");
|
|
199
|
+
if (iv.length !== IV_LEN || tag.length !== TAG_LEN)
|
|
200
|
+
return null;
|
|
201
|
+
const decipher = createDecipheriv("aes-256-gcm", key, iv);
|
|
202
|
+
decipher.setAuthTag(tag);
|
|
203
|
+
const pt = Buffer.concat([decipher.update(ct), decipher.final()]);
|
|
204
|
+
return pt.toString("utf8");
|
|
205
|
+
}
|
|
206
|
+
catch {
|
|
207
|
+
// Auth-tag mismatch or any other crypto error → return null so callers
|
|
208
|
+
// see "not found" rather than a hard error.
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
set(ref, value) {
|
|
213
|
+
// Serialise on the per-instance write chain to avoid torn JSON. Callers
|
|
214
|
+
// can still hit us concurrently — each set() waits for the prior one.
|
|
215
|
+
const next = this.writeChain.then(() => {
|
|
216
|
+
const vault = this.readVault();
|
|
217
|
+
vault[ref] = this.encrypt(value);
|
|
218
|
+
this.writeVault(vault);
|
|
219
|
+
});
|
|
220
|
+
this.writeChain = next.catch(() => undefined);
|
|
221
|
+
return next;
|
|
222
|
+
}
|
|
223
|
+
async get(ref) {
|
|
224
|
+
const vault = this.readVault();
|
|
225
|
+
const rec = vault[ref];
|
|
226
|
+
if (!rec)
|
|
227
|
+
return null;
|
|
228
|
+
return this.decrypt(rec);
|
|
229
|
+
}
|
|
230
|
+
delete(ref) {
|
|
231
|
+
const next = this.writeChain.then(() => {
|
|
232
|
+
const vault = this.readVault();
|
|
233
|
+
if (!(ref in vault))
|
|
234
|
+
return;
|
|
235
|
+
delete vault[ref];
|
|
236
|
+
this.writeVault(vault);
|
|
237
|
+
});
|
|
238
|
+
this.writeChain = next.catch(() => undefined);
|
|
239
|
+
return next;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
export async function createKeychain(opts) {
|
|
243
|
+
const Ctor = opts.systemKeychainCtor ?? SystemKeychain;
|
|
244
|
+
try {
|
|
245
|
+
// Some envs throw on ctor; others throw on first read/write. We can only
|
|
246
|
+
// catch ctor failures synchronously here — read/write failures bubble up
|
|
247
|
+
// to the caller and are not auto-fallback (by design: silent fallback
|
|
248
|
+
// mid-session would mask data-loss).
|
|
249
|
+
const kc = new Ctor(opts.service ?? SERVICE_NAME);
|
|
250
|
+
return kc;
|
|
251
|
+
}
|
|
252
|
+
catch (e) {
|
|
253
|
+
if (!opts.silent) {
|
|
254
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
255
|
+
// eslint-disable-next-line no-console -- intentional one-shot stderr warning
|
|
256
|
+
console.warn(`[apimapper-mcp] System keychain unavailable (${msg}); ` +
|
|
257
|
+
`falling back to AES-GCM-encrypted file at ${opts.configDir}/keychain.enc.json`);
|
|
258
|
+
}
|
|
259
|
+
return new EncryptedFileKeychain(opts.configDir);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
//# sourceMappingURL=keychain.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keychain.js","sourceRoot":"","sources":["../../src/auth/keychain.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
export interface OAuthClientRegistration {
|
|
2
|
+
client_id: string;
|
|
3
|
+
client_id_issued_at: number;
|
|
4
|
+
redirect_uris: string[];
|
|
5
|
+
client_name?: string;
|
|
6
|
+
token_endpoint_auth_method: "none";
|
|
7
|
+
grant_types: ["authorization_code", "refresh_token"];
|
|
8
|
+
response_types: ["code"];
|
|
9
|
+
}
|
|
10
|
+
export interface IssueCodeArgs {
|
|
11
|
+
client_id: string;
|
|
12
|
+
redirect_uri: string;
|
|
13
|
+
code_challenge: string;
|
|
14
|
+
code_challenge_method: "S256" | "plain";
|
|
15
|
+
scope: string;
|
|
16
|
+
state?: string;
|
|
17
|
+
}
|
|
18
|
+
export interface ExchangeCodeArgs {
|
|
19
|
+
client_id: string;
|
|
20
|
+
code: string;
|
|
21
|
+
redirect_uri: string;
|
|
22
|
+
code_verifier: string;
|
|
23
|
+
}
|
|
24
|
+
export interface RefreshArgs {
|
|
25
|
+
client_id: string;
|
|
26
|
+
refresh_token: string;
|
|
27
|
+
}
|
|
28
|
+
export interface TokenResponse {
|
|
29
|
+
access_token: string;
|
|
30
|
+
token_type: "Bearer";
|
|
31
|
+
expires_in: number;
|
|
32
|
+
refresh_token: string;
|
|
33
|
+
scope: string;
|
|
34
|
+
}
|
|
35
|
+
export interface AccessTokenInfo {
|
|
36
|
+
client_id: string;
|
|
37
|
+
scope: string;
|
|
38
|
+
expires_at: number;
|
|
39
|
+
}
|
|
40
|
+
export interface OAuthProviderOptions {
|
|
41
|
+
/** Clock function returning unix seconds. Default: Date.now()/1000. */
|
|
42
|
+
now?: () => number;
|
|
43
|
+
/** Authorization code TTL (default 600s = 10 min). */
|
|
44
|
+
codeTtlSeconds?: number;
|
|
45
|
+
/** Access token TTL (default 86400s = 24 h). */
|
|
46
|
+
tokenTtlSeconds?: number;
|
|
47
|
+
/** Refresh token TTL (default 604800s = 7 d). */
|
|
48
|
+
refreshTtlSeconds?: number;
|
|
49
|
+
}
|
|
50
|
+
export interface OAuthProvider {
|
|
51
|
+
registerClient(args: {
|
|
52
|
+
redirect_uris: string[];
|
|
53
|
+
client_name?: string;
|
|
54
|
+
}): OAuthClientRegistration;
|
|
55
|
+
getClient(client_id: string): OAuthClientRegistration | null;
|
|
56
|
+
issueCode(args: IssueCodeArgs): string;
|
|
57
|
+
exchangeCode(args: ExchangeCodeArgs): TokenResponse;
|
|
58
|
+
refreshToken(args: RefreshArgs): TokenResponse;
|
|
59
|
+
verifyAccessToken(token: string): AccessTokenInfo | null;
|
|
60
|
+
sweep(): number;
|
|
61
|
+
stats(): {
|
|
62
|
+
clients: number;
|
|
63
|
+
codes: number;
|
|
64
|
+
tokens: number;
|
|
65
|
+
refreshes: number;
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
export declare function createOAuthProvider(options?: OAuthProviderOptions): OAuthProvider;
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
// src/auth/oauth-provider.ts — Phase 9.2.
|
|
2
|
+
//
|
|
3
|
+
// Minimal in-memory OAuth 2.0 Authorization-Code + PKCE + Dynamic Client
|
|
4
|
+
// Registration (DCR) provider for the remote HTTP MCP transport.
|
|
5
|
+
//
|
|
6
|
+
// References:
|
|
7
|
+
// - RFC 6749 (OAuth 2.0)
|
|
8
|
+
// - RFC 7636 (PKCE)
|
|
9
|
+
// - RFC 7591 (Dynamic Client Registration)
|
|
10
|
+
// - workers-oauth-provider (Cloudflare reference, in-memory variant)
|
|
11
|
+
//
|
|
12
|
+
// Storage backend is process-local Maps with TTL-based expiry — the
|
|
13
|
+
// hosted production service (mcp.wootsup.com) is expected to swap in a
|
|
14
|
+
// persistent backend, but for unit/integration tests + the local DXT
|
|
15
|
+
// bundle, in-memory is sufficient.
|
|
16
|
+
//
|
|
17
|
+
// Security notes:
|
|
18
|
+
// - Tokens are 32 random bytes hex-encoded (no JWT — keeps the surface
|
|
19
|
+
// tiny and prevents misuse of unverified payloads).
|
|
20
|
+
// - Authorization codes are 32 random bytes hex; single-use; bound to
|
|
21
|
+
// client_id + redirect_uri + code_challenge.
|
|
22
|
+
// - PKCE S256 is REQUIRED; "plain" is rejected.
|
|
23
|
+
// - On refresh, the old refresh token is invalidated (rotation).
|
|
24
|
+
import { randomBytes, createHash, timingSafeEqual } from "node:crypto";
|
|
25
|
+
// ── Helpers ────────────────────────────────────────────────────────────
|
|
26
|
+
function randomId(prefix) {
|
|
27
|
+
return `${prefix}${randomBytes(12).toString("hex")}`;
|
|
28
|
+
}
|
|
29
|
+
function randomOpaque() {
|
|
30
|
+
return randomBytes(32).toString("hex");
|
|
31
|
+
}
|
|
32
|
+
/** Constant-time compare to thwart timing oracles on token / verifier lookups. */
|
|
33
|
+
function safeEquals(a, b) {
|
|
34
|
+
const ba = Buffer.from(a);
|
|
35
|
+
const bb = Buffer.from(b);
|
|
36
|
+
if (ba.length !== bb.length)
|
|
37
|
+
return false;
|
|
38
|
+
return timingSafeEqual(ba, bb);
|
|
39
|
+
}
|
|
40
|
+
function s256(input) {
|
|
41
|
+
return createHash("sha256").update(input).digest("base64url");
|
|
42
|
+
}
|
|
43
|
+
// ── Factory ────────────────────────────────────────────────────────────
|
|
44
|
+
export function createOAuthProvider(options = {}) {
|
|
45
|
+
const now = options.now ?? (() => Math.floor(Date.now() / 1000));
|
|
46
|
+
const codeTtl = options.codeTtlSeconds ?? 600;
|
|
47
|
+
const tokenTtl = options.tokenTtlSeconds ?? 86_400;
|
|
48
|
+
const refreshTtl = options.refreshTtlSeconds ?? 7 * 86_400;
|
|
49
|
+
const clients = new Map();
|
|
50
|
+
const codes = new Map();
|
|
51
|
+
const tokens = new Map();
|
|
52
|
+
const refreshes = new Map();
|
|
53
|
+
function registerClient(args) {
|
|
54
|
+
if (!Array.isArray(args.redirect_uris) || args.redirect_uris.length === 0) {
|
|
55
|
+
throw new Error("invalid_redirect_uri: at least one redirect_uri required");
|
|
56
|
+
}
|
|
57
|
+
for (const uri of args.redirect_uris) {
|
|
58
|
+
if (typeof uri !== "string" || uri.length === 0) {
|
|
59
|
+
throw new Error("invalid_redirect_uri");
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
const reg = {
|
|
63
|
+
client_id: randomId("cli_"),
|
|
64
|
+
client_id_issued_at: now(),
|
|
65
|
+
redirect_uris: [...args.redirect_uris],
|
|
66
|
+
client_name: args.client_name,
|
|
67
|
+
token_endpoint_auth_method: "none", // public client (mobile/native/AI)
|
|
68
|
+
grant_types: ["authorization_code", "refresh_token"],
|
|
69
|
+
response_types: ["code"],
|
|
70
|
+
};
|
|
71
|
+
clients.set(reg.client_id, reg);
|
|
72
|
+
return reg;
|
|
73
|
+
}
|
|
74
|
+
function getClient(client_id) {
|
|
75
|
+
return clients.get(client_id) ?? null;
|
|
76
|
+
}
|
|
77
|
+
function issueCode(args) {
|
|
78
|
+
const client = clients.get(args.client_id);
|
|
79
|
+
if (!client)
|
|
80
|
+
throw new Error("unknown_client");
|
|
81
|
+
if (!client.redirect_uris.includes(args.redirect_uri)) {
|
|
82
|
+
throw new Error("invalid_redirect_uri");
|
|
83
|
+
}
|
|
84
|
+
if (!args.code_challenge || typeof args.code_challenge !== "string") {
|
|
85
|
+
throw new Error("invalid_request: code_challenge required");
|
|
86
|
+
}
|
|
87
|
+
if (args.code_challenge_method !== "S256") {
|
|
88
|
+
// Reject "plain" — S256 is mandatory for safety.
|
|
89
|
+
throw new Error("invalid_request: code_challenge_method must be S256");
|
|
90
|
+
}
|
|
91
|
+
const code = randomId("code_");
|
|
92
|
+
codes.set(code, {
|
|
93
|
+
client_id: args.client_id,
|
|
94
|
+
redirect_uri: args.redirect_uri,
|
|
95
|
+
code_challenge: args.code_challenge,
|
|
96
|
+
code_challenge_method: args.code_challenge_method,
|
|
97
|
+
scope: args.scope,
|
|
98
|
+
expires_at: now() + codeTtl,
|
|
99
|
+
used: false,
|
|
100
|
+
});
|
|
101
|
+
return code;
|
|
102
|
+
}
|
|
103
|
+
function exchangeCode(args) {
|
|
104
|
+
const rec = codes.get(args.code);
|
|
105
|
+
if (!rec)
|
|
106
|
+
throw new Error("invalid_grant: code not found");
|
|
107
|
+
if (rec.used)
|
|
108
|
+
throw new Error("invalid_grant: code already used");
|
|
109
|
+
if (rec.expires_at < now())
|
|
110
|
+
throw new Error("invalid_grant: code expired");
|
|
111
|
+
if (rec.client_id !== args.client_id) {
|
|
112
|
+
throw new Error("invalid_grant: client_id mismatch");
|
|
113
|
+
}
|
|
114
|
+
if (rec.redirect_uri !== args.redirect_uri) {
|
|
115
|
+
throw new Error("invalid_grant: redirect_uri mismatch");
|
|
116
|
+
}
|
|
117
|
+
const challengeFromVerifier = s256(args.code_verifier);
|
|
118
|
+
if (!safeEquals(challengeFromVerifier, rec.code_challenge)) {
|
|
119
|
+
throw new Error("invalid_grant: code_verifier mismatch");
|
|
120
|
+
}
|
|
121
|
+
rec.used = true;
|
|
122
|
+
const accessToken = randomOpaque();
|
|
123
|
+
const refreshToken = randomOpaque();
|
|
124
|
+
tokens.set(accessToken, {
|
|
125
|
+
client_id: rec.client_id,
|
|
126
|
+
scope: rec.scope,
|
|
127
|
+
expires_at: now() + tokenTtl,
|
|
128
|
+
});
|
|
129
|
+
refreshes.set(refreshToken, {
|
|
130
|
+
client_id: rec.client_id,
|
|
131
|
+
scope: rec.scope,
|
|
132
|
+
expires_at: now() + refreshTtl,
|
|
133
|
+
revoked: false,
|
|
134
|
+
});
|
|
135
|
+
return {
|
|
136
|
+
access_token: accessToken,
|
|
137
|
+
refresh_token: refreshToken,
|
|
138
|
+
token_type: "Bearer",
|
|
139
|
+
expires_in: tokenTtl,
|
|
140
|
+
scope: rec.scope,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
function refreshToken(args) {
|
|
144
|
+
const rec = refreshes.get(args.refresh_token);
|
|
145
|
+
if (!rec)
|
|
146
|
+
throw new Error("invalid_grant: refresh_token not found");
|
|
147
|
+
if (rec.revoked)
|
|
148
|
+
throw new Error("invalid_grant: refresh_token revoked");
|
|
149
|
+
if (rec.expires_at < now()) {
|
|
150
|
+
throw new Error("invalid_grant: refresh_token expired");
|
|
151
|
+
}
|
|
152
|
+
if (rec.client_id !== args.client_id) {
|
|
153
|
+
throw new Error("invalid_grant: client_id mismatch");
|
|
154
|
+
}
|
|
155
|
+
// Rotate: revoke the old refresh token, mint a new pair.
|
|
156
|
+
rec.revoked = true;
|
|
157
|
+
const accessToken = randomOpaque();
|
|
158
|
+
const newRefresh = randomOpaque();
|
|
159
|
+
tokens.set(accessToken, {
|
|
160
|
+
client_id: rec.client_id,
|
|
161
|
+
scope: rec.scope,
|
|
162
|
+
expires_at: now() + tokenTtl,
|
|
163
|
+
});
|
|
164
|
+
refreshes.set(newRefresh, {
|
|
165
|
+
client_id: rec.client_id,
|
|
166
|
+
scope: rec.scope,
|
|
167
|
+
expires_at: now() + refreshTtl,
|
|
168
|
+
revoked: false,
|
|
169
|
+
});
|
|
170
|
+
return {
|
|
171
|
+
access_token: accessToken,
|
|
172
|
+
refresh_token: newRefresh,
|
|
173
|
+
token_type: "Bearer",
|
|
174
|
+
expires_in: tokenTtl,
|
|
175
|
+
scope: rec.scope,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
function verifyAccessToken(token) {
|
|
179
|
+
const rec = tokens.get(token);
|
|
180
|
+
if (!rec)
|
|
181
|
+
return null;
|
|
182
|
+
if (rec.expires_at < now())
|
|
183
|
+
return null;
|
|
184
|
+
return {
|
|
185
|
+
client_id: rec.client_id,
|
|
186
|
+
scope: rec.scope,
|
|
187
|
+
expires_at: rec.expires_at,
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
function sweep() {
|
|
191
|
+
const t = now();
|
|
192
|
+
let evicted = 0;
|
|
193
|
+
for (const [k, v] of codes) {
|
|
194
|
+
if (v.expires_at < t || v.used) {
|
|
195
|
+
codes.delete(k);
|
|
196
|
+
evicted++;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
for (const [k, v] of tokens) {
|
|
200
|
+
if (v.expires_at < t) {
|
|
201
|
+
tokens.delete(k);
|
|
202
|
+
evicted++;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
for (const [k, v] of refreshes) {
|
|
206
|
+
if (v.expires_at < t || v.revoked) {
|
|
207
|
+
refreshes.delete(k);
|
|
208
|
+
evicted++;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
return evicted;
|
|
212
|
+
}
|
|
213
|
+
function stats() {
|
|
214
|
+
return {
|
|
215
|
+
clients: clients.size,
|
|
216
|
+
codes: codes.size,
|
|
217
|
+
tokens: tokens.size,
|
|
218
|
+
refreshes: refreshes.size,
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
return {
|
|
222
|
+
registerClient,
|
|
223
|
+
getClient,
|
|
224
|
+
issueCode,
|
|
225
|
+
exchangeCode,
|
|
226
|
+
refreshToken,
|
|
227
|
+
verifyAccessToken,
|
|
228
|
+
sweep,
|
|
229
|
+
stats,
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
//# sourceMappingURL=oauth-provider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauth-provider.js","sourceRoot":"","sources":["../../src/auth/oauth-provider.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
export type ProfilePlatform = "wordpress" | "joomla" | "standalone";
|
|
2
|
+
export interface LastVerifiedIdentity {
|
|
3
|
+
plugin_version: string;
|
|
4
|
+
plugin_hash: string;
|
|
5
|
+
capabilities: string[];
|
|
6
|
+
}
|
|
7
|
+
export interface Profile {
|
|
8
|
+
/** Unique short name (e.g. "dev-wp", "client-x-prod"). */
|
|
9
|
+
name: string;
|
|
10
|
+
/** Base URL of the host. WordPress = wp-root, Joomla = site-root. */
|
|
11
|
+
siteUrl: string;
|
|
12
|
+
/** Platform kind — drives URL construction in the Platform abstraction. */
|
|
13
|
+
platform: ProfilePlatform;
|
|
14
|
+
/** Ref into the Keychain pointing at the raw amk_live_... token. */
|
|
15
|
+
keychainRef: string;
|
|
16
|
+
/**
|
|
17
|
+
* Snapshot of the last successful /identity probe — used to detect plugin
|
|
18
|
+
* drift on `apimapper_use_profile`. Optional because freshly added
|
|
19
|
+
* profiles haven't been probed yet.
|
|
20
|
+
*/
|
|
21
|
+
lastVerifiedIdentity?: LastVerifiedIdentity;
|
|
22
|
+
/** ISO 8601 timestamp of when the profile was first added. */
|
|
23
|
+
addedAt: string;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Resolve the canonical config directory for apimapper-mcp:
|
|
27
|
+
*
|
|
28
|
+
* - macOS/Linux: $XDG_CONFIG_HOME/apimapper-mcp/ if set, else
|
|
29
|
+
* $HOME/.config/apimapper-mcp/
|
|
30
|
+
* - Windows: %APPDATA%/apimapper-mcp/
|
|
31
|
+
*
|
|
32
|
+
* Falls back to $HOME/.apimapper-mcp/ if none of the env vars are set
|
|
33
|
+
* (degenerate environment — better than failing).
|
|
34
|
+
*/
|
|
35
|
+
export declare function resolveConfigDir(): string;
|
|
36
|
+
export declare class ProfileStore {
|
|
37
|
+
private readonly dir;
|
|
38
|
+
private readonly profilesPath;
|
|
39
|
+
private readonly activePath;
|
|
40
|
+
constructor(configDir?: string);
|
|
41
|
+
private readAll;
|
|
42
|
+
private writeAll;
|
|
43
|
+
private readActiveName;
|
|
44
|
+
private writeActiveName;
|
|
45
|
+
list(): Promise<Profile[]>;
|
|
46
|
+
get(name: string): Promise<Profile | null>;
|
|
47
|
+
add(profile: Profile): Promise<void>;
|
|
48
|
+
remove(name: string): Promise<boolean>;
|
|
49
|
+
update(name: string, partial: Partial<Profile>): Promise<void>;
|
|
50
|
+
setActive(name: string): Promise<void>;
|
|
51
|
+
getActive(): Promise<Profile | null>;
|
|
52
|
+
}
|