@introspection-ai/recipes 0.18.0 → 0.19.0
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/dist/inspect.d.ts +1 -0
- package/dist/inspect.d.ts.map +1 -1
- package/dist/inspect.js +3 -0
- package/dist/inspect.js.map +1 -1
- package/dist/mcp-chunks/{auth-command-ZLGYIEUY.js → auth-command-3K7V3VCW.js} +31 -35
- package/dist/mcp-chunks/call-arguments-TX3F2TEE.js +15 -0
- package/dist/mcp-chunks/{call-command-LKTKSYTH.js → call-command-OFPHPFM5.js} +36 -34
- package/dist/mcp-chunks/chunk-2FSDGCHW.js +3299 -0
- package/dist/mcp-chunks/{chunk-BV56DXPW.js → chunk-2JHHONW4.js} +1 -1
- package/dist/mcp-chunks/chunk-2MEHGB7D.js +63 -0
- package/dist/mcp-chunks/chunk-3IZQW4H4.js +929 -0
- package/dist/mcp-chunks/{chunk-MZ2NARG7.js → chunk-54EWGONA.js} +5 -5
- package/dist/mcp-chunks/{chunk-LQQEYD76.js → chunk-55JIHDU3.js} +1 -1
- package/dist/mcp-chunks/chunk-64EP2BVO.js +39 -0
- package/dist/mcp-chunks/{chunk-LS7PMWSR.js → chunk-6D6KPSXN.js} +1 -1
- package/dist/mcp-chunks/{chunk-2XL7MG7R.js → chunk-7A63PAGP.js} +15 -9
- package/dist/mcp-chunks/chunk-7DH57OKF.js +28 -0
- package/dist/mcp-chunks/{chunk-BTLAWHTA.js → chunk-7RDLPFIO.js} +2 -2
- package/dist/mcp-chunks/chunk-C2PQFLWS.js +422 -0
- package/dist/mcp-chunks/chunk-CA5WFJZA.js +197 -0
- package/dist/mcp-chunks/{chunk-LNH5LAJD.js → chunk-CNDY7WB4.js} +2 -2
- package/dist/mcp-chunks/{chunk-UKYNQGNE.js → chunk-DEUU7WSN.js} +34 -8
- package/dist/mcp-chunks/{chunk-LKIBAYKU.js → chunk-DXPHJFSA.js} +25 -35
- package/dist/mcp-chunks/{chunk-J6LYIZXN.js → chunk-E6RVTWJY.js} +1 -1
- package/dist/mcp-chunks/{chunk-7FHEBT5G.js → chunk-E6T37ED6.js} +18 -13
- package/dist/mcp-chunks/chunk-ETWN4IIA.js +57 -0
- package/dist/mcp-chunks/{chunk-YVZKMV5H.js → chunk-F3LL4EBL.js} +1 -1
- package/dist/mcp-chunks/chunk-FWP4RACK.js +42 -0
- package/dist/mcp-chunks/{chunk-6W5QFASN.js → chunk-GFYTST6N.js} +1 -1
- package/dist/mcp-chunks/{chunk-TEUKFFQL.js → chunk-GIF7LOYU.js} +2 -2
- package/dist/mcp-chunks/{chunk-QXJUA3ND.js → chunk-GSZOK5TS.js} +828 -7
- package/dist/mcp-chunks/chunk-H7F5RZXS.js +26 -0
- package/dist/mcp-chunks/{chunk-ECIVB7P4.js → chunk-HAO6PL7Z.js} +2 -37
- package/dist/mcp-chunks/{chunk-WQJZMCJN.js → chunk-HTZ2URXW.js} +5 -3
- package/dist/mcp-chunks/{chunk-BWHIA4PC.js → chunk-JAKI24KR.js} +1 -1
- package/dist/mcp-chunks/{chunk-PWYFKGKG.js → chunk-KSGYBW3F.js} +180 -89
- package/dist/mcp-chunks/{chunk-SJCCBNZ4.js → chunk-KSMY7PXI.js} +1 -1
- package/dist/mcp-chunks/{chunk-2U4BSDC4.js → chunk-L4VKBFYI.js} +48 -29
- package/dist/mcp-chunks/{chunk-LEZSACDE.js → chunk-LICH4Q7A.js} +4 -4
- package/dist/mcp-chunks/{chunk-PIIYJR44.js → chunk-LNSUME65.js} +29 -33
- package/dist/mcp-chunks/{chunk-D3MRAZ6H.js → chunk-LWVFSQYX.js} +13974 -15562
- package/dist/mcp-chunks/{chunk-7KA2VJVZ.js → chunk-NCXEJ35U.js} +10 -5
- package/dist/mcp-chunks/{chunk-5QOSCSQB.js → chunk-NZOIX6XW.js} +2 -2
- package/dist/mcp-chunks/{chunk-XANPGTLI.js → chunk-P3RN22HQ.js} +11 -4
- package/dist/mcp-chunks/{chunk-IABRYROW.js → chunk-PKOJCYNA.js} +3 -3
- package/dist/mcp-chunks/{chunk-BNV3TPE4.js → chunk-QJACOI2W.js} +5 -13
- package/dist/mcp-chunks/{chunk-UNKB4SBV.js → chunk-RBVYCKCD.js} +2 -2
- package/dist/mcp-chunks/{chunk-DHBE7UBH.js → chunk-SB2UUURE.js} +12 -42
- package/dist/mcp-chunks/{chunk-4SQKWYFF.js → chunk-SFREPU3D.js} +1 -1
- package/dist/mcp-chunks/{chunk-BZIQOWE6.js → chunk-SRZZQ6TL.js} +35 -36
- package/dist/mcp-chunks/{chunk-M3TPODXI.js → chunk-UENXT3NE.js} +1 -1
- package/dist/mcp-chunks/chunk-UHOKCTZT.js +37 -0
- package/dist/mcp-chunks/{chunk-E22DYOW5.js → chunk-WWO5WGH5.js} +5 -5
- package/dist/mcp-chunks/{chunk-6XCZGU3G.js → chunk-WXBPMFSN.js} +149 -192
- package/dist/mcp-chunks/{chunk-XQK2JSW4.js → chunk-XRGLP3Z2.js} +14 -2
- package/dist/mcp-chunks/{chunk-L7NTQVYN.js → chunk-XTL4MHFH.js} +28 -10
- package/dist/mcp-chunks/{chunk-N46NEFAF.js → chunk-XUF6KARU.js} +1 -1
- package/dist/mcp-chunks/{chunk-WD6BOD24.js → chunk-YCYSQA5V.js} +23 -4
- package/dist/mcp-chunks/chunk-YPJ5EHXR.js +17394 -0
- package/dist/mcp-chunks/{chunk-NRAJX5E4.js → chunk-YSPT6F7K.js} +3 -3
- package/dist/mcp-chunks/{chunk-7UDVSOOF.js → chunk-YTKRAHO6.js} +54 -46
- package/dist/mcp-chunks/chunk-ZBFZ5UFE.js +67 -0
- package/dist/mcp-chunks/{cli-UV4QJZZM.js → cli-E4P6SZLQ.js} +89 -76
- package/dist/mcp-chunks/client-2EJC4PLL.js +18 -0
- package/dist/mcp-chunks/{config-command-NLAGKID5.js → config-command-MWF3RHPQ.js} +38 -59
- package/dist/mcp-chunks/daemon-command-6CJ26MKI.js +33 -0
- package/dist/mcp-chunks/{dist-LK3SHAMX.js → dist-ZIPUENTM.js} +527 -469
- package/dist/mcp-chunks/{emit-ts-command-VBXVXO6V.js → emit-ts-command-GOVJJ64A.js} +16 -13
- package/dist/mcp-chunks/{generate-cli-runner-6CAZZA3P.js → generate-cli-runner-L5OQIMEY.js} +242 -247
- package/dist/mcp-chunks/{inspect-cli-command-3ZORTOR7.js → inspect-cli-command-7OMMREPE.js} +10 -9
- package/dist/mcp-chunks/{launch-YKRXHXJI.js → launch-BWXPLPB7.js} +1 -1
- package/dist/mcp-chunks/{lifecycle-XVKRHPH3.js → lifecycle-B5UC65PT.js} +1 -1
- package/dist/mcp-chunks/{list-command-2MEGGNFQ.js → list-command-ZO4HVFQK.js} +74 -43
- package/dist/mcp-chunks/{output-utils-NKESLAJ6.js → output-utils-M3TJ2N4M.js} +3 -3
- package/dist/mcp-chunks/{prompt-B1Yc1NPt-VGOTWJMN.js → prompt-CH6TK0bC-C7OTKGRD.js} +1 -3
- package/dist/mcp-chunks/{record-command-YXHTTTT5.js → record-command-VD3HUQIC.js} +5 -4
- package/dist/mcp-chunks/replay-command-AX6IN7YD.js +42 -0
- package/dist/mcp-chunks/{resource-command-OO4NYF5C.js → resource-command-JEZ2DW2G.js} +10 -10
- package/dist/mcp-chunks/{result-utils-MOHHLNM7.js → result-utils-3YHYGS45.js} +4 -4
- package/dist/mcp-chunks/runtime-HSYVOME7.js +37 -0
- package/dist/mcp-chunks/{runtime-wrapper-RPWDIDBF.js → runtime-wrapper-UREU7GC3.js} +7 -3
- package/dist/mcp-chunks/serve-command-M47YZ6JN.js +16018 -0
- package/dist/mcp-chunks/{timeouts-W2XWCCXR.js → timeouts-THZRYP4B.js} +3 -1
- package/dist/mcp-chunks/{vault-command-PIYIUEZL.js → vault-command-53P7MLCB.js} +7 -6
- package/dist/mcp-chunks/webapi-PHEQZQKH.js +3316 -0
- package/dist/mcp-client.js +11 -1
- package/dist/mcp-client.js.map +1 -1
- package/dist/mcp-daemon-client.d.ts.map +1 -1
- package/dist/mcp-daemon-client.js +3 -0
- package/dist/mcp-daemon-client.js.map +1 -1
- package/dist/mcp-daemon-protocol.d.ts +6 -0
- package/dist/mcp-daemon-protocol.d.ts.map +1 -1
- package/dist/mcp-daemon-protocol.js.map +1 -1
- package/dist/mcp-daemon.js +43 -22
- package/dist/mcp-daemon.js.map +1 -1
- package/dist/mcp-run-worker.js +29 -22
- package/dist/mcp-trace-context.d.ts +6 -0
- package/dist/mcp-trace-context.d.ts.map +1 -0
- package/dist/mcp-trace-context.js +31 -0
- package/dist/mcp-trace-context.js.map +1 -0
- package/dist/model-binding.d.ts.map +1 -1
- package/dist/model-binding.js +8 -4
- package/dist/model-binding.js.map +1 -1
- package/dist/pi-extension.d.ts.map +1 -1
- package/dist/pi-extension.js +12 -0
- package/dist/pi-extension.js.map +1 -1
- package/dist/recipe/resolve.d.ts +2 -0
- package/dist/recipe/resolve.d.ts.map +1 -1
- package/dist/recipe/resolve.js +2 -1
- package/dist/recipe/resolve.js.map +1 -1
- package/dist/recipe-agent.d.ts +5 -2
- package/dist/recipe-agent.d.ts.map +1 -1
- package/dist/recipe-agent.js +20 -4
- package/dist/recipe-agent.js.map +1 -1
- package/dist/recipe-model.d.ts +7 -1
- package/dist/recipe-model.d.ts.map +1 -1
- package/dist/recipe-model.js +89 -20
- package/dist/recipe-model.js.map +1 -1
- package/dist/recipe-session.d.ts +14 -0
- package/dist/recipe-session.d.ts.map +1 -0
- package/dist/recipe-session.js +181 -0
- package/dist/recipe-session.js.map +1 -0
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +23 -1
- package/dist/session.js.map +1 -1
- package/docs/agent-composition.md +46 -22
- package/docs/pi-extension.md +9 -3
- package/docs/recipe-format.md +41 -5
- package/package.json +7 -7
- package/vendor/introspection-recipe-check/darwin-arm64/introspection-recipe-check +0 -0
- package/vendor/introspection-recipe-check/darwin-x64/introspection-recipe-check +0 -0
- package/vendor/introspection-recipe-check/linux-arm64/introspection-recipe-check +0 -0
- package/vendor/introspection-recipe-check/linux-x64/introspection-recipe-check +0 -0
- package/vendor/introspection-recipe-check/linux-x64-musl/introspection-recipe-check +0 -0
- package/vendor/introspection-recipe-check/win32-x64/introspection-recipe-check.exe +0 -0
- package/dist/mcp-chunks/call-arguments-LU3W6D3K.js +0 -13
- package/dist/mcp-chunks/chunk-HKRJ6O36.js +0 -1076
- package/dist/mcp-chunks/chunk-NNOEGEUZ.js +0 -17273
- package/dist/mcp-chunks/chunk-P3PSC54P.js +0 -869
- package/dist/mcp-chunks/chunk-TATVT5IT.js +0 -194
- package/dist/mcp-chunks/chunk-YHQUTU6L.js +0 -571
- package/dist/mcp-chunks/client-S7RQUG5U.js +0 -13
- package/dist/mcp-chunks/daemon-command-U242Z6VY.js +0 -26
- package/dist/mcp-chunks/replay-command-25HJ6AQU.js +0 -85
- package/dist/mcp-chunks/runtime-5WJ3G25Y.js +0 -33
- package/dist/mcp-chunks/serve-command-DHXJZMMN.js +0 -3302
|
@@ -0,0 +1,929 @@
|
|
|
1
|
+
import { createRequire as __createRequire } from "node:module"; const require = __createRequire(import.meta.url);
|
|
2
|
+
import {
|
|
3
|
+
clearVaultEntry,
|
|
4
|
+
clearVaultTokensIfMatching,
|
|
5
|
+
getOAuthVaultPath,
|
|
6
|
+
isStoredOAuthClientInformation,
|
|
7
|
+
isStoredOAuthTokens,
|
|
8
|
+
loadVaultEntryForRecovery,
|
|
9
|
+
reconcileVaultServerUrl,
|
|
10
|
+
sameOAuthClientGeneration,
|
|
11
|
+
sameOAuthClientValue,
|
|
12
|
+
sameOAuthTokenGeneration,
|
|
13
|
+
sameOAuthTokenValue,
|
|
14
|
+
saveVaultEntry,
|
|
15
|
+
withHiddenOAuthClientGeneration,
|
|
16
|
+
withHiddenOAuthTokenGeneration,
|
|
17
|
+
withOAuthClientGeneration,
|
|
18
|
+
withOAuthTokenGeneration
|
|
19
|
+
} from "./chunk-C2PQFLWS.js";
|
|
20
|
+
import {
|
|
21
|
+
checkResourceAllowed,
|
|
22
|
+
discoverOAuthServerInfo,
|
|
23
|
+
refreshAuthorization,
|
|
24
|
+
resourceUrlFromServerUrl
|
|
25
|
+
} from "./chunk-YPJ5EHXR.js";
|
|
26
|
+
import {
|
|
27
|
+
readJsonFile,
|
|
28
|
+
withFileLock,
|
|
29
|
+
writeJsonFile,
|
|
30
|
+
writeTextFileAtomic
|
|
31
|
+
} from "./chunk-7A63PAGP.js";
|
|
32
|
+
import {
|
|
33
|
+
legacyMcporterDir
|
|
34
|
+
} from "./chunk-FWP4RACK.js";
|
|
35
|
+
|
|
36
|
+
// node_modules/.pnpm/mcporter@0.13.0/node_modules/mcporter/dist/oauth-persistence.js
|
|
37
|
+
import fs2 from "node:fs/promises";
|
|
38
|
+
|
|
39
|
+
// node_modules/.pnpm/mcporter@0.13.0/node_modules/mcporter/dist/oauth-persistence-stores.js
|
|
40
|
+
import fs from "node:fs/promises";
|
|
41
|
+
import os from "node:os";
|
|
42
|
+
import path from "node:path";
|
|
43
|
+
function withStoredExpiry(tokens) {
|
|
44
|
+
const stored = tokens;
|
|
45
|
+
if (typeof stored.expires_at === "number" || typeof stored.expiresAt === "number") {
|
|
46
|
+
return tokens;
|
|
47
|
+
}
|
|
48
|
+
if (typeof tokens.expires_in === "number" && Number.isFinite(tokens.expires_in)) {
|
|
49
|
+
return {
|
|
50
|
+
...tokens,
|
|
51
|
+
expires_at: Math.floor(Date.now() / 1e3) + tokens.expires_in
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
return tokens;
|
|
55
|
+
}
|
|
56
|
+
function prepareStoredTokens(tokens) {
|
|
57
|
+
return withStoredExpiry(withOAuthTokenGeneration(tokens));
|
|
58
|
+
}
|
|
59
|
+
var DirectoryPersistence = class {
|
|
60
|
+
root;
|
|
61
|
+
logger;
|
|
62
|
+
serverUrl;
|
|
63
|
+
skipUrlMarkerWhenMissing;
|
|
64
|
+
tokenPath;
|
|
65
|
+
clientInfoPath;
|
|
66
|
+
codeVerifierPath;
|
|
67
|
+
statePath;
|
|
68
|
+
serverUrlPath;
|
|
69
|
+
discoveryStatePath;
|
|
70
|
+
authorizationServerUrlPath;
|
|
71
|
+
resourceUrlPath;
|
|
72
|
+
constructor(root, logger, serverUrl, skipUrlMarkerWhenMissing = false) {
|
|
73
|
+
this.root = root;
|
|
74
|
+
this.logger = logger;
|
|
75
|
+
this.serverUrl = serverUrl;
|
|
76
|
+
this.skipUrlMarkerWhenMissing = skipUrlMarkerWhenMissing;
|
|
77
|
+
this.tokenPath = path.join(root, "tokens.json");
|
|
78
|
+
this.clientInfoPath = path.join(root, "client.json");
|
|
79
|
+
this.codeVerifierPath = path.join(root, "code_verifier.txt");
|
|
80
|
+
this.statePath = path.join(root, "state.txt");
|
|
81
|
+
this.serverUrlPath = path.join(root, "server_url.txt");
|
|
82
|
+
this.discoveryStatePath = path.join(root, "discovery.json");
|
|
83
|
+
this.authorizationServerUrlPath = path.join(root, "authorization_server_url.txt");
|
|
84
|
+
this.resourceUrlPath = path.join(root, "resource_url.txt");
|
|
85
|
+
}
|
|
86
|
+
describe() {
|
|
87
|
+
return this.root;
|
|
88
|
+
}
|
|
89
|
+
async ensureDir() {
|
|
90
|
+
await fs.mkdir(this.root, { recursive: true });
|
|
91
|
+
}
|
|
92
|
+
async reconcileServerUrl() {
|
|
93
|
+
if (!this.serverUrl) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
const serverUrl = this.serverUrl;
|
|
97
|
+
if (this.skipUrlMarkerWhenMissing) {
|
|
98
|
+
try {
|
|
99
|
+
await fs.access(this.root);
|
|
100
|
+
} catch (error) {
|
|
101
|
+
if (error.code === "ENOENT") {
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
throw error;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
await this.ensureDir();
|
|
108
|
+
await withFileLock(this.tokenPath, async () => {
|
|
109
|
+
let previousUrl;
|
|
110
|
+
try {
|
|
111
|
+
previousUrl = (await fs.readFile(this.serverUrlPath, "utf8")).trim();
|
|
112
|
+
} catch (error) {
|
|
113
|
+
if (error.code !== "ENOENT") {
|
|
114
|
+
throw error;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
if (previousUrl === serverUrl) {
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
if (previousUrl !== void 0) {
|
|
121
|
+
await this.clearFiles("all");
|
|
122
|
+
}
|
|
123
|
+
await writeTextFileAtomic(this.serverUrlPath, serverUrl);
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
async readSnapshot() {
|
|
127
|
+
await this.reconcileServerUrl();
|
|
128
|
+
const [tokens, clientInfo, codeVerifier, state, discoveryState, authorizationServerUrl, resourceUrl] = await Promise.all([
|
|
129
|
+
this.readTokensAfterReconcile(),
|
|
130
|
+
this.readClientInfoAfterReconcile(),
|
|
131
|
+
this.readCodeVerifierAfterReconcile(),
|
|
132
|
+
this.readStateAfterReconcile(),
|
|
133
|
+
this.readDiscoveryStateAfterReconcile(),
|
|
134
|
+
this.readTextAfterReconcile(this.authorizationServerUrlPath),
|
|
135
|
+
this.readTextAfterReconcile(this.resourceUrlPath)
|
|
136
|
+
]);
|
|
137
|
+
return { tokens, clientInfo, codeVerifier, state, discoveryState, authorizationServerUrl, resourceUrl };
|
|
138
|
+
}
|
|
139
|
+
async readTokens() {
|
|
140
|
+
await this.reconcileServerUrl();
|
|
141
|
+
return await this.readTokensAfterReconcile();
|
|
142
|
+
}
|
|
143
|
+
async readTokensAfterReconcile() {
|
|
144
|
+
const tokens = await this.readJsonOrUndefined(this.tokenPath);
|
|
145
|
+
return isStoredOAuthTokens(tokens) ? withHiddenOAuthTokenGeneration(tokens) : void 0;
|
|
146
|
+
}
|
|
147
|
+
async saveTokens(tokens) {
|
|
148
|
+
await this.reconcileServerUrl();
|
|
149
|
+
await this.ensureDir();
|
|
150
|
+
await withFileLock(this.tokenPath, async () => {
|
|
151
|
+
await writeJsonFile(this.tokenPath, prepareStoredTokens(tokens));
|
|
152
|
+
});
|
|
153
|
+
this.logger?.debug?.(`Saved tokens to ${this.tokenPath}`);
|
|
154
|
+
}
|
|
155
|
+
async clearRejectedCredentials(expectedTokens, expectedClientInfo) {
|
|
156
|
+
await this.reconcileServerUrl();
|
|
157
|
+
await withFileLock(this.tokenPath, async () => {
|
|
158
|
+
if (expectedTokens) {
|
|
159
|
+
const current = await this.readJsonOrUndefined(this.tokenPath);
|
|
160
|
+
if (sameOAuthTokenGeneration(current, expectedTokens)) {
|
|
161
|
+
await this.unlinkIfPresent(this.tokenPath);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
if (expectedClientInfo) {
|
|
165
|
+
const currentClientInfo = await this.readJsonOrUndefined(this.clientInfoPath);
|
|
166
|
+
if (sameOAuthClientGeneration(currentClientInfo, expectedClientInfo)) {
|
|
167
|
+
await this.unlinkIfPresent(this.clientInfoPath);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
async unlinkIfPresent(file) {
|
|
173
|
+
try {
|
|
174
|
+
await fs.unlink(file);
|
|
175
|
+
} catch (error) {
|
|
176
|
+
if (error.code !== "ENOENT") {
|
|
177
|
+
throw error;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
async readClientInfo() {
|
|
182
|
+
await this.reconcileServerUrl();
|
|
183
|
+
return await this.readClientInfoAfterReconcile();
|
|
184
|
+
}
|
|
185
|
+
async readClientInfoAfterReconcile() {
|
|
186
|
+
const info = await this.readJsonOrUndefined(this.clientInfoPath);
|
|
187
|
+
return isStoredOAuthClientInformation(info) ? withHiddenOAuthClientGeneration(info) : void 0;
|
|
188
|
+
}
|
|
189
|
+
async saveClientInfo(info) {
|
|
190
|
+
await this.reconcileServerUrl();
|
|
191
|
+
await this.ensureDir();
|
|
192
|
+
await withFileLock(this.tokenPath, async () => {
|
|
193
|
+
await writeJsonFile(this.clientInfoPath, withOAuthClientGeneration(info));
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
async readCodeVerifier() {
|
|
197
|
+
await this.reconcileServerUrl();
|
|
198
|
+
return await this.readCodeVerifierAfterReconcile();
|
|
199
|
+
}
|
|
200
|
+
async readCodeVerifierAfterReconcile() {
|
|
201
|
+
try {
|
|
202
|
+
return (await fs.readFile(this.codeVerifierPath, "utf8")).trim();
|
|
203
|
+
} catch (error) {
|
|
204
|
+
if (error.code === "ENOENT") {
|
|
205
|
+
return void 0;
|
|
206
|
+
}
|
|
207
|
+
throw error;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
async saveCodeVerifier(value) {
|
|
211
|
+
await this.reconcileServerUrl();
|
|
212
|
+
await this.ensureDir();
|
|
213
|
+
await writeTextFileAtomic(this.codeVerifierPath, value);
|
|
214
|
+
}
|
|
215
|
+
async readState() {
|
|
216
|
+
await this.reconcileServerUrl();
|
|
217
|
+
return await this.readStateAfterReconcile();
|
|
218
|
+
}
|
|
219
|
+
async readStateAfterReconcile() {
|
|
220
|
+
return readJsonFile(this.statePath);
|
|
221
|
+
}
|
|
222
|
+
// A present-but-corrupt credential cache (tokens/client) means "no usable
|
|
223
|
+
// credentials": degrade to re-auth instead of crashing the connection,
|
|
224
|
+
// mirroring VaultPersistence and the daemon/server-proxy readers. Genuine I/O
|
|
225
|
+
// faults still propagate (readJsonFile re-throws everything except ENOENT).
|
|
226
|
+
// OAuth state is intentionally excluded so its CSRF check still fails closed.
|
|
227
|
+
async readJsonOrUndefined(filePath) {
|
|
228
|
+
try {
|
|
229
|
+
return await readJsonFile(filePath);
|
|
230
|
+
} catch (error) {
|
|
231
|
+
if (!(error instanceof SyntaxError)) {
|
|
232
|
+
throw error;
|
|
233
|
+
}
|
|
234
|
+
this.logger?.debug?.(`Ignoring corrupt OAuth cache file ${filePath}: ${error.message}`);
|
|
235
|
+
return void 0;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
async saveState(value) {
|
|
239
|
+
await this.reconcileServerUrl();
|
|
240
|
+
await this.ensureDir();
|
|
241
|
+
await writeJsonFile(this.statePath, value);
|
|
242
|
+
}
|
|
243
|
+
async readDiscoveryState() {
|
|
244
|
+
await this.reconcileServerUrl();
|
|
245
|
+
return this.readDiscoveryStateAfterReconcile();
|
|
246
|
+
}
|
|
247
|
+
async readDiscoveryStateAfterReconcile() {
|
|
248
|
+
return readJsonFile(this.discoveryStatePath);
|
|
249
|
+
}
|
|
250
|
+
async saveDiscoveryState(value) {
|
|
251
|
+
await this.reconcileServerUrl();
|
|
252
|
+
await this.ensureDir();
|
|
253
|
+
await writeJsonFile(this.discoveryStatePath, value);
|
|
254
|
+
}
|
|
255
|
+
async readAuthorizationServerUrl() {
|
|
256
|
+
await this.reconcileServerUrl();
|
|
257
|
+
return this.readTextAfterReconcile(this.authorizationServerUrlPath);
|
|
258
|
+
}
|
|
259
|
+
async saveAuthorizationServerUrl(value) {
|
|
260
|
+
await this.reconcileServerUrl();
|
|
261
|
+
await this.ensureDir();
|
|
262
|
+
await writeTextFileAtomic(this.authorizationServerUrlPath, value);
|
|
263
|
+
}
|
|
264
|
+
async readResourceUrl() {
|
|
265
|
+
await this.reconcileServerUrl();
|
|
266
|
+
return this.readTextAfterReconcile(this.resourceUrlPath);
|
|
267
|
+
}
|
|
268
|
+
async saveResourceUrl(value) {
|
|
269
|
+
await this.reconcileServerUrl();
|
|
270
|
+
await this.ensureDir();
|
|
271
|
+
await writeTextFileAtomic(this.resourceUrlPath, value);
|
|
272
|
+
}
|
|
273
|
+
async readTextAfterReconcile(filePath) {
|
|
274
|
+
try {
|
|
275
|
+
return (await fs.readFile(filePath, "utf8")).trim() || void 0;
|
|
276
|
+
} catch (error) {
|
|
277
|
+
if (error.code === "ENOENT")
|
|
278
|
+
return void 0;
|
|
279
|
+
throw error;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
async clear(scope) {
|
|
283
|
+
await this.reconcileServerUrl();
|
|
284
|
+
await this.clearFiles(scope);
|
|
285
|
+
}
|
|
286
|
+
async clearFiles(scope) {
|
|
287
|
+
const files = [];
|
|
288
|
+
if (scope === "all" || scope === "tokens") {
|
|
289
|
+
files.push(this.tokenPath);
|
|
290
|
+
}
|
|
291
|
+
if (scope === "all" || scope === "client") {
|
|
292
|
+
files.push(this.clientInfoPath);
|
|
293
|
+
}
|
|
294
|
+
if (scope === "all" || scope === "verifier") {
|
|
295
|
+
files.push(this.codeVerifierPath);
|
|
296
|
+
}
|
|
297
|
+
if (scope === "all" || scope === "state") {
|
|
298
|
+
files.push(this.statePath);
|
|
299
|
+
}
|
|
300
|
+
if (scope === "all" || scope === "discovery") {
|
|
301
|
+
files.push(this.discoveryStatePath, this.authorizationServerUrlPath, this.resourceUrlPath);
|
|
302
|
+
}
|
|
303
|
+
await Promise.all(files.map((file) => this.unlinkIfPresent(file)));
|
|
304
|
+
}
|
|
305
|
+
};
|
|
306
|
+
var VaultPersistence = class {
|
|
307
|
+
definition;
|
|
308
|
+
tokenSnapshots;
|
|
309
|
+
clientSnapshots;
|
|
310
|
+
constructor(definition) {
|
|
311
|
+
this.definition = definition;
|
|
312
|
+
}
|
|
313
|
+
describe() {
|
|
314
|
+
return `${getOAuthVaultPath()} (vault)`;
|
|
315
|
+
}
|
|
316
|
+
async reconcileServerUrl() {
|
|
317
|
+
await reconcileVaultServerUrl(this.definition);
|
|
318
|
+
}
|
|
319
|
+
async readSnapshot() {
|
|
320
|
+
await this.reconcileServerUrl();
|
|
321
|
+
const recovery = await loadVaultEntryForRecovery(this.definition);
|
|
322
|
+
this.tokenSnapshots = recovery.tokenSnapshots;
|
|
323
|
+
this.clientSnapshots = recovery.clientSnapshots;
|
|
324
|
+
return {
|
|
325
|
+
tokens: recovery.entry?.tokens,
|
|
326
|
+
clientInfo: recovery.entry?.clientInfo,
|
|
327
|
+
codeVerifier: recovery.entry?.codeVerifier,
|
|
328
|
+
state: recovery.entry?.state,
|
|
329
|
+
discoveryState: recovery.entry?.discoveryState,
|
|
330
|
+
authorizationServerUrl: recovery.entry?.authorizationServerUrl,
|
|
331
|
+
resourceUrl: recovery.entry?.resourceUrl
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
async readTokens() {
|
|
335
|
+
const snapshot = await this.readSnapshot();
|
|
336
|
+
return snapshot.tokens;
|
|
337
|
+
}
|
|
338
|
+
async saveTokens(tokens) {
|
|
339
|
+
await this.reconcileServerUrl();
|
|
340
|
+
await saveVaultEntry(this.definition, { tokens: prepareStoredTokens(tokens) });
|
|
341
|
+
}
|
|
342
|
+
async readClientInfo() {
|
|
343
|
+
const snapshot = await this.readSnapshot();
|
|
344
|
+
return snapshot.clientInfo;
|
|
345
|
+
}
|
|
346
|
+
async saveClientInfo(info) {
|
|
347
|
+
await this.reconcileServerUrl();
|
|
348
|
+
await saveVaultEntry(this.definition, { clientInfo: info });
|
|
349
|
+
}
|
|
350
|
+
async readCodeVerifier() {
|
|
351
|
+
const snapshot = await this.readSnapshot();
|
|
352
|
+
return snapshot.codeVerifier;
|
|
353
|
+
}
|
|
354
|
+
async saveCodeVerifier(value) {
|
|
355
|
+
await this.reconcileServerUrl();
|
|
356
|
+
await saveVaultEntry(this.definition, { codeVerifier: value });
|
|
357
|
+
}
|
|
358
|
+
async readState() {
|
|
359
|
+
const snapshot = await this.readSnapshot();
|
|
360
|
+
return snapshot.state;
|
|
361
|
+
}
|
|
362
|
+
async saveState(value) {
|
|
363
|
+
await this.reconcileServerUrl();
|
|
364
|
+
await saveVaultEntry(this.definition, { state: value });
|
|
365
|
+
}
|
|
366
|
+
async readDiscoveryState() {
|
|
367
|
+
return (await this.readSnapshot()).discoveryState;
|
|
368
|
+
}
|
|
369
|
+
async saveDiscoveryState(value) {
|
|
370
|
+
await this.reconcileServerUrl();
|
|
371
|
+
await saveVaultEntry(this.definition, { discoveryState: value });
|
|
372
|
+
}
|
|
373
|
+
async readAuthorizationServerUrl() {
|
|
374
|
+
return (await this.readSnapshot()).authorizationServerUrl;
|
|
375
|
+
}
|
|
376
|
+
async saveAuthorizationServerUrl(value) {
|
|
377
|
+
await this.reconcileServerUrl();
|
|
378
|
+
await saveVaultEntry(this.definition, { authorizationServerUrl: value });
|
|
379
|
+
}
|
|
380
|
+
async readResourceUrl() {
|
|
381
|
+
return (await this.readSnapshot()).resourceUrl;
|
|
382
|
+
}
|
|
383
|
+
async saveResourceUrl(value) {
|
|
384
|
+
await this.reconcileServerUrl();
|
|
385
|
+
await saveVaultEntry(this.definition, { resourceUrl: value });
|
|
386
|
+
}
|
|
387
|
+
async clear(scope) {
|
|
388
|
+
await this.reconcileServerUrl();
|
|
389
|
+
await clearVaultEntry(this.definition, scope);
|
|
390
|
+
}
|
|
391
|
+
async clearRejectedCredentials(expectedTokens, expectedClientInfo) {
|
|
392
|
+
await this.reconcileServerUrl();
|
|
393
|
+
await clearVaultTokensIfMatching(this.definition, expectedTokens, expectedClientInfo, expectedTokens ? this.tokenSnapshots : void 0, expectedClientInfo ? this.clientSnapshots : void 0);
|
|
394
|
+
}
|
|
395
|
+
};
|
|
396
|
+
var CompositePersistence = class {
|
|
397
|
+
stores;
|
|
398
|
+
recoveryTokenSnapshots = /* @__PURE__ */ new Map();
|
|
399
|
+
recoveryClientSnapshots = /* @__PURE__ */ new Map();
|
|
400
|
+
recoveryTokenSource;
|
|
401
|
+
recoveryClientSource;
|
|
402
|
+
constructor(stores) {
|
|
403
|
+
this.stores = stores;
|
|
404
|
+
}
|
|
405
|
+
describe() {
|
|
406
|
+
return this.stores.map((store) => store.describe()).join(" + ");
|
|
407
|
+
}
|
|
408
|
+
async readSnapshot() {
|
|
409
|
+
const snapshots = await Promise.all(this.stores.map((store) => store.readSnapshot()));
|
|
410
|
+
const tokens = this.firstSnapshotValue(snapshots, "tokens");
|
|
411
|
+
const clientInfo = this.firstSnapshotValue(snapshots, "clientInfo");
|
|
412
|
+
this.recoveryTokenSnapshots.clear();
|
|
413
|
+
this.recoveryClientSnapshots.clear();
|
|
414
|
+
for (const [index, snapshot] of snapshots.entries()) {
|
|
415
|
+
const store = this.stores[index];
|
|
416
|
+
this.recoveryTokenSnapshots.set(store, snapshot.tokens);
|
|
417
|
+
this.recoveryClientSnapshots.set(store, snapshot.clientInfo);
|
|
418
|
+
}
|
|
419
|
+
this.recoveryTokenSource = tokens.source;
|
|
420
|
+
this.recoveryClientSource = clientInfo.source;
|
|
421
|
+
return {
|
|
422
|
+
tokens: tokens.value,
|
|
423
|
+
clientInfo: clientInfo.value,
|
|
424
|
+
codeVerifier: this.firstSnapshotValue(snapshots, "codeVerifier").value,
|
|
425
|
+
state: this.firstSnapshotValue(snapshots, "state").value,
|
|
426
|
+
discoveryState: this.firstSnapshotValue(snapshots, "discoveryState").value,
|
|
427
|
+
authorizationServerUrl: this.firstSnapshotValue(snapshots, "authorizationServerUrl").value,
|
|
428
|
+
resourceUrl: this.firstSnapshotValue(snapshots, "resourceUrl").value
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
firstSnapshotValue(snapshots, field) {
|
|
432
|
+
for (const [index, snapshot] of snapshots.entries()) {
|
|
433
|
+
if (snapshot[field] !== void 0) {
|
|
434
|
+
return { value: snapshot[field], source: this.stores[index] };
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
return { value: void 0, source: void 0 };
|
|
438
|
+
}
|
|
439
|
+
async readTokens() {
|
|
440
|
+
const result = await this.readRecoveryValues((store) => store.readTokens(), this.recoveryTokenSnapshots);
|
|
441
|
+
this.recoveryTokenSource = result.source;
|
|
442
|
+
return result.value;
|
|
443
|
+
}
|
|
444
|
+
async saveTokens(tokens) {
|
|
445
|
+
const stored = prepareStoredTokens(tokens);
|
|
446
|
+
await Promise.all(this.stores.map((store) => store.saveTokens(stored)));
|
|
447
|
+
}
|
|
448
|
+
async clearRejectedCredentials(expectedTokens, expectedClientInfo) {
|
|
449
|
+
await Promise.all(this.stores.map((store) => {
|
|
450
|
+
const storedTokenSnapshot = expectedTokens ? this.recoveryTokenSnapshots.get(store) : void 0;
|
|
451
|
+
const tokenSnapshot = expectedTokens && storedTokenSnapshot && (store === this.recoveryTokenSource || sameOAuthTokenValue(storedTokenSnapshot, expectedTokens)) ? storedTokenSnapshot : void 0;
|
|
452
|
+
const storedClientSnapshot = expectedClientInfo ? this.recoveryClientSnapshots.get(store) : void 0;
|
|
453
|
+
const clientSnapshot = expectedClientInfo && storedClientSnapshot && (store === this.recoveryClientSource || sameOAuthClientValue(storedClientSnapshot, expectedClientInfo)) ? storedClientSnapshot : void 0;
|
|
454
|
+
return tokenSnapshot || clientSnapshot ? store.clearRejectedCredentials(tokenSnapshot, clientSnapshot) : Promise.resolve();
|
|
455
|
+
}));
|
|
456
|
+
}
|
|
457
|
+
async readClientInfo() {
|
|
458
|
+
const result = await this.readRecoveryValues((store) => store.readClientInfo(), this.recoveryClientSnapshots);
|
|
459
|
+
this.recoveryClientSource = result.source;
|
|
460
|
+
return result.value;
|
|
461
|
+
}
|
|
462
|
+
async readRecoveryValues(read, snapshots) {
|
|
463
|
+
const results = await Promise.allSettled(this.stores.map((store) => read(store)));
|
|
464
|
+
snapshots.clear();
|
|
465
|
+
let value;
|
|
466
|
+
let source;
|
|
467
|
+
for (const [index, result] of results.entries()) {
|
|
468
|
+
const store = this.stores[index];
|
|
469
|
+
if (result.status === "rejected") {
|
|
470
|
+
if (!source) {
|
|
471
|
+
throw result.reason;
|
|
472
|
+
}
|
|
473
|
+
continue;
|
|
474
|
+
}
|
|
475
|
+
snapshots.set(store, result.value);
|
|
476
|
+
if (!source && result.value !== void 0) {
|
|
477
|
+
source = store;
|
|
478
|
+
value = result.value;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
return { value, source };
|
|
482
|
+
}
|
|
483
|
+
async saveClientInfo(info) {
|
|
484
|
+
const stored = withOAuthClientGeneration(info);
|
|
485
|
+
await Promise.all(this.stores.map((store) => store.saveClientInfo(stored)));
|
|
486
|
+
}
|
|
487
|
+
async readCodeVerifier() {
|
|
488
|
+
for (const store of this.stores) {
|
|
489
|
+
const result = await store.readCodeVerifier();
|
|
490
|
+
if (result) {
|
|
491
|
+
return result;
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
return void 0;
|
|
495
|
+
}
|
|
496
|
+
async saveCodeVerifier(value) {
|
|
497
|
+
await Promise.all(this.stores.map((store) => store.saveCodeVerifier(value)));
|
|
498
|
+
}
|
|
499
|
+
async readState() {
|
|
500
|
+
for (const store of this.stores) {
|
|
501
|
+
const result = await store.readState();
|
|
502
|
+
if (result) {
|
|
503
|
+
return result;
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
return void 0;
|
|
507
|
+
}
|
|
508
|
+
async saveState(value) {
|
|
509
|
+
await Promise.all(this.stores.map((store) => store.saveState(value)));
|
|
510
|
+
}
|
|
511
|
+
async readDiscoveryState() {
|
|
512
|
+
return this.readFirst((store) => store.readDiscoveryState());
|
|
513
|
+
}
|
|
514
|
+
async saveDiscoveryState(value) {
|
|
515
|
+
await Promise.all(this.stores.map((store) => store.saveDiscoveryState(value)));
|
|
516
|
+
}
|
|
517
|
+
async readAuthorizationServerUrl() {
|
|
518
|
+
return this.readFirst((store) => store.readAuthorizationServerUrl());
|
|
519
|
+
}
|
|
520
|
+
async saveAuthorizationServerUrl(value) {
|
|
521
|
+
await Promise.all(this.stores.map((store) => store.saveAuthorizationServerUrl(value)));
|
|
522
|
+
}
|
|
523
|
+
async readResourceUrl() {
|
|
524
|
+
return this.readFirst((store) => store.readResourceUrl());
|
|
525
|
+
}
|
|
526
|
+
async saveResourceUrl(value) {
|
|
527
|
+
await Promise.all(this.stores.map((store) => store.saveResourceUrl(value)));
|
|
528
|
+
}
|
|
529
|
+
async readFirst(read) {
|
|
530
|
+
for (const store of this.stores) {
|
|
531
|
+
const value = await read(store);
|
|
532
|
+
if (value !== void 0)
|
|
533
|
+
return value;
|
|
534
|
+
}
|
|
535
|
+
return void 0;
|
|
536
|
+
}
|
|
537
|
+
async clear(scope) {
|
|
538
|
+
await Promise.all(this.stores.map((store) => store.clear(scope)));
|
|
539
|
+
}
|
|
540
|
+
};
|
|
541
|
+
async function createOAuthPersistenceStores(definition, logger) {
|
|
542
|
+
const vault = new VaultPersistence(definition);
|
|
543
|
+
const stores = [vault];
|
|
544
|
+
const serverUrl = definition.command.kind === "http" ? definition.command.url.toString() : void 0;
|
|
545
|
+
if (definition.tokenCacheDir) {
|
|
546
|
+
stores.unshift(new DirectoryPersistence(definition.tokenCacheDir, logger, serverUrl));
|
|
547
|
+
}
|
|
548
|
+
const legacyDir = path.join(legacyMcporterDir(), definition.name);
|
|
549
|
+
if (!definition.tokenCacheDir) {
|
|
550
|
+
const legacy = new DirectoryPersistence(legacyDir, logger, serverUrl, true);
|
|
551
|
+
const snapshot = await legacy.readSnapshot();
|
|
552
|
+
if (snapshot.tokens || snapshot.clientInfo || snapshot.codeVerifier || snapshot.state || snapshot.discoveryState || snapshot.authorizationServerUrl || snapshot.resourceUrl) {
|
|
553
|
+
if (snapshot.tokens) {
|
|
554
|
+
await vault.saveTokens(snapshot.tokens);
|
|
555
|
+
}
|
|
556
|
+
if (snapshot.clientInfo) {
|
|
557
|
+
await vault.saveClientInfo(snapshot.clientInfo);
|
|
558
|
+
}
|
|
559
|
+
if (snapshot.codeVerifier) {
|
|
560
|
+
await vault.saveCodeVerifier(snapshot.codeVerifier);
|
|
561
|
+
}
|
|
562
|
+
if (snapshot.state) {
|
|
563
|
+
await vault.saveState(snapshot.state);
|
|
564
|
+
}
|
|
565
|
+
if (snapshot.discoveryState) {
|
|
566
|
+
await vault.saveDiscoveryState(snapshot.discoveryState);
|
|
567
|
+
}
|
|
568
|
+
if (snapshot.authorizationServerUrl) {
|
|
569
|
+
await vault.saveAuthorizationServerUrl(snapshot.authorizationServerUrl);
|
|
570
|
+
}
|
|
571
|
+
if (snapshot.resourceUrl) {
|
|
572
|
+
await vault.saveResourceUrl(snapshot.resourceUrl);
|
|
573
|
+
}
|
|
574
|
+
logger?.info?.(`Migrated legacy OAuth cache for '${definition.name}' into vault.`);
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
return stores.length === 1 ? vault : new CompositePersistence(stores);
|
|
578
|
+
}
|
|
579
|
+
async function clearLegacyOAuthArtifacts(definition, logger, scope) {
|
|
580
|
+
const legacyDir = path.join(legacyMcporterDir(), definition.name);
|
|
581
|
+
if (!definition.tokenCacheDir || legacyDir !== definition.tokenCacheDir) {
|
|
582
|
+
const legacy = new DirectoryPersistence(legacyDir, logger);
|
|
583
|
+
await legacy.clear(scope);
|
|
584
|
+
}
|
|
585
|
+
const legacyFiles = [];
|
|
586
|
+
if (definition.name.toLowerCase() === "gmail") {
|
|
587
|
+
legacyFiles.push(path.join(os.homedir(), ".gmail-mcp", "credentials.json"));
|
|
588
|
+
}
|
|
589
|
+
await Promise.all(legacyFiles.map(async (file) => {
|
|
590
|
+
try {
|
|
591
|
+
await fs.unlink(file);
|
|
592
|
+
logger?.info?.(`Cleared legacy OAuth cache file ${file}`);
|
|
593
|
+
} catch (error) {
|
|
594
|
+
if (error.code !== "ENOENT") {
|
|
595
|
+
throw error;
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
}));
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
// node_modules/.pnpm/mcporter@0.13.0/node_modules/mcporter/dist/oauth-token-refresh.js
|
|
602
|
+
import { Buffer } from "node:buffer";
|
|
603
|
+
|
|
604
|
+
// node_modules/.pnpm/mcporter@0.13.0/node_modules/mcporter/dist/oauth-client-info.js
|
|
605
|
+
function buildStaticClientInformation(definition, options = {}) {
|
|
606
|
+
if (!definition.oauthClientId) {
|
|
607
|
+
return void 0;
|
|
608
|
+
}
|
|
609
|
+
const clientSecret = resolveOAuthClientSecret(definition);
|
|
610
|
+
return {
|
|
611
|
+
client_id: definition.oauthClientId,
|
|
612
|
+
...clientSecret ? { client_secret: clientSecret } : {},
|
|
613
|
+
...options.redirectUrl ? { redirect_uris: [options.redirectUrl.toString()] } : {},
|
|
614
|
+
grant_types: ["authorization_code", "refresh_token"],
|
|
615
|
+
response_types: ["code"],
|
|
616
|
+
...definition.oauthTokenEndpointAuthMethod ? { token_endpoint_auth_method: definition.oauthTokenEndpointAuthMethod } : {}
|
|
617
|
+
};
|
|
618
|
+
}
|
|
619
|
+
function resolveOAuthClientSecret(definition, options = {}) {
|
|
620
|
+
if (definition.oauthClientSecretEnv) {
|
|
621
|
+
const value = process.env[definition.oauthClientSecretEnv];
|
|
622
|
+
if (!value || options.rejectBlank && value.trim().length === 0) {
|
|
623
|
+
throw new Error(`Environment variable '${definition.oauthClientSecretEnv}' is required for OAuth client secret.`);
|
|
624
|
+
}
|
|
625
|
+
return value;
|
|
626
|
+
}
|
|
627
|
+
return definition.oauthClientSecret;
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
// node_modules/.pnpm/mcporter@0.13.0/node_modules/mcporter/dist/oauth-token-refresh.js
|
|
631
|
+
var TOKEN_EXPIRY_SKEW_SECONDS = 60;
|
|
632
|
+
var OAuthIssuerMismatchError = class extends Error {
|
|
633
|
+
};
|
|
634
|
+
function tokenExpirySeconds(tokens) {
|
|
635
|
+
const stored = tokens;
|
|
636
|
+
for (const candidate of [stored.expires_at, stored.expiresAt]) {
|
|
637
|
+
if (typeof candidate === "number" && Number.isFinite(candidate)) {
|
|
638
|
+
return candidate;
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
return void 0;
|
|
642
|
+
}
|
|
643
|
+
function cachedTokensChanged(original, current) {
|
|
644
|
+
if (!current || typeof current.access_token !== "string" || current.access_token.trim().length === 0) {
|
|
645
|
+
return false;
|
|
646
|
+
}
|
|
647
|
+
return !sameOAuthTokenGeneration(current, original);
|
|
648
|
+
}
|
|
649
|
+
function shouldRefreshCachedToken(tokens, skewSeconds = TOKEN_EXPIRY_SKEW_SECONDS) {
|
|
650
|
+
const expiresAt = tokenExpirySeconds(tokens);
|
|
651
|
+
if (expiresAt !== void 0) {
|
|
652
|
+
return expiresAt <= Math.floor(Date.now() / 1e3) + skewSeconds;
|
|
653
|
+
}
|
|
654
|
+
return typeof tokens.expires_in === "number" && typeof tokens.refresh_token === "string";
|
|
655
|
+
}
|
|
656
|
+
function resourceForRefresh(serverUrl, resourceMetadata) {
|
|
657
|
+
if (!resourceMetadata) {
|
|
658
|
+
return void 0;
|
|
659
|
+
}
|
|
660
|
+
const defaultResource = resourceUrlFromServerUrl(serverUrl);
|
|
661
|
+
if (!checkResourceAllowed({ requestedResource: defaultResource, configuredResource: resourceMetadata.resource })) {
|
|
662
|
+
throw new Error(`Protected resource ${resourceMetadata.resource} does not match expected ${defaultResource} (or origin)`);
|
|
663
|
+
}
|
|
664
|
+
return new URL(resourceMetadata.resource);
|
|
665
|
+
}
|
|
666
|
+
function normalizedIssuer(value) {
|
|
667
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
668
|
+
return void 0;
|
|
669
|
+
}
|
|
670
|
+
return value.endsWith("/") ? value.slice(0, -1) : value;
|
|
671
|
+
}
|
|
672
|
+
async function assertRefreshIssuerBinding(definition, persistence, tokens, clientInformation, receivedIssuer) {
|
|
673
|
+
const expectedIssuer = normalizedIssuer(tokens.issuer) ?? normalizedIssuer(clientInformation.issuer);
|
|
674
|
+
if (!expectedIssuer || expectedIssuer === normalizedIssuer(receivedIssuer)) {
|
|
675
|
+
return;
|
|
676
|
+
}
|
|
677
|
+
await Promise.all([persistence.clear("tokens"), persistence.clear("client")]);
|
|
678
|
+
throw new OAuthIssuerMismatchError(`OAuth issuer changed for '${definition.name}': expected ${expectedIssuer}, received ${receivedIssuer}. Discarded cached credentials; run 'mcporter auth ${definition.name}' to reauthorize.`);
|
|
679
|
+
}
|
|
680
|
+
function unrecoverableOAuthRefreshCode(error) {
|
|
681
|
+
const errorCode = oauthErrorCode(error);
|
|
682
|
+
if (errorCode && ["invalid_client", "invalid_grant", "unauthorized_client"].includes(errorCode)) {
|
|
683
|
+
return errorCode;
|
|
684
|
+
}
|
|
685
|
+
return void 0;
|
|
686
|
+
}
|
|
687
|
+
async function oauthErrorCodeFromResponse(response) {
|
|
688
|
+
try {
|
|
689
|
+
const payload = await response.json();
|
|
690
|
+
if (payload && typeof payload.error === "string" && payload.error.length > 0) {
|
|
691
|
+
return payload.error.toLowerCase();
|
|
692
|
+
}
|
|
693
|
+
} catch {
|
|
694
|
+
}
|
|
695
|
+
return void 0;
|
|
696
|
+
}
|
|
697
|
+
function oauthErrorCode(error) {
|
|
698
|
+
if (!error || typeof error !== "object") {
|
|
699
|
+
return void 0;
|
|
700
|
+
}
|
|
701
|
+
const { errorCode, name } = error;
|
|
702
|
+
if (typeof errorCode === "string" && errorCode.length > 0) {
|
|
703
|
+
return errorCode.toLowerCase();
|
|
704
|
+
}
|
|
705
|
+
if (typeof name === "string") {
|
|
706
|
+
const normalized = name.toLowerCase();
|
|
707
|
+
if (normalized === "invalidclienterror") {
|
|
708
|
+
return "invalid_client";
|
|
709
|
+
}
|
|
710
|
+
if (normalized === "invalidgranterror") {
|
|
711
|
+
return "invalid_grant";
|
|
712
|
+
}
|
|
713
|
+
if (normalized === "unauthorizedclienterror") {
|
|
714
|
+
return "unauthorized_client";
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
return void 0;
|
|
718
|
+
}
|
|
719
|
+
async function readCachedAccessTokenWithPersistence(definition, persistence, logger) {
|
|
720
|
+
const tokens = await persistence.readTokens();
|
|
721
|
+
if (!tokens || typeof tokens.access_token !== "string" || tokens.access_token.trim().length === 0) {
|
|
722
|
+
return void 0;
|
|
723
|
+
}
|
|
724
|
+
if (definition.auth === "refreshable_bearer") {
|
|
725
|
+
return await readExplicitRefreshableBearerToken(definition, persistence, tokens, logger);
|
|
726
|
+
}
|
|
727
|
+
if (!shouldRefreshCachedToken(tokens)) {
|
|
728
|
+
return tokens.access_token;
|
|
729
|
+
}
|
|
730
|
+
if (typeof tokens.refresh_token !== "string" || tokens.refresh_token.trim().length === 0) {
|
|
731
|
+
return tokens.access_token;
|
|
732
|
+
}
|
|
733
|
+
let persistedClientInformation;
|
|
734
|
+
try {
|
|
735
|
+
const staticClientInformation = buildStaticClientInformation(definition);
|
|
736
|
+
persistedClientInformation = staticClientInformation ? void 0 : await persistence.readClientInfo();
|
|
737
|
+
const clientInformation = staticClientInformation ?? persistedClientInformation;
|
|
738
|
+
if (!clientInformation) {
|
|
739
|
+
logger?.debug?.(`Cached OAuth token for '${definition.name}' is expired, but no client information is available.`);
|
|
740
|
+
return tokens.access_token;
|
|
741
|
+
}
|
|
742
|
+
if (definition.command.kind !== "http") {
|
|
743
|
+
return tokens.access_token;
|
|
744
|
+
}
|
|
745
|
+
const serverInfo = await discoverOAuthServerInfo(definition.command.url);
|
|
746
|
+
await assertRefreshIssuerBinding(definition, persistence, tokens, clientInformation, serverInfo.authorizationServerUrl);
|
|
747
|
+
const resource = resourceForRefresh(definition.command.url, serverInfo.resourceMetadata);
|
|
748
|
+
const refreshed = await refreshAuthorization(serverInfo.authorizationServerUrl, {
|
|
749
|
+
metadata: serverInfo.authorizationServerMetadata,
|
|
750
|
+
clientInformation,
|
|
751
|
+
refreshToken: tokens.refresh_token,
|
|
752
|
+
...resource ? { resource } : {}
|
|
753
|
+
});
|
|
754
|
+
await persistence.saveTokens({ ...refreshed, issuer: serverInfo.authorizationServerUrl });
|
|
755
|
+
logger?.debug?.(`Refreshed cached OAuth access token for '${definition.name}' (non-interactive).`);
|
|
756
|
+
return refreshed.access_token;
|
|
757
|
+
} catch (error) {
|
|
758
|
+
logger?.debug?.(`Failed to refresh cached OAuth token for '${definition.name}' non-interactively: ${error instanceof Error ? error.message : String(error)}`);
|
|
759
|
+
if (error instanceof OAuthIssuerMismatchError) {
|
|
760
|
+
throw error;
|
|
761
|
+
}
|
|
762
|
+
const unrecoverableCode = unrecoverableOAuthRefreshCode(error);
|
|
763
|
+
if (unrecoverableCode) {
|
|
764
|
+
return await recoverFromUnrecoverableRefresh(definition, persistence, tokens, unrecoverableCode, "OAuth", logger, persistedClientInformation);
|
|
765
|
+
}
|
|
766
|
+
return tokens.access_token;
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
async function recoverFromUnrecoverableRefresh(definition, persistence, tokens, unrecoverableCode, tokenKind, logger, rejectedClientInformation) {
|
|
770
|
+
const clientToClear = unrecoverableCode === "invalid_grant" ? void 0 : rejectedClientInformation;
|
|
771
|
+
await persistence.clearRejectedCredentials(tokens, clientToClear);
|
|
772
|
+
await clearLegacyOAuthArtifacts(definition, logger, unrecoverableCode === "invalid_grant" ? "tokens" : "all");
|
|
773
|
+
const latestTokens = await persistence.readTokens();
|
|
774
|
+
if (latestTokens && cachedTokensChanged(tokens, latestTokens)) {
|
|
775
|
+
logger?.debug?.(`Kept cached ${tokenKind} token for '${definition.name}' because another refresh updated it first.`);
|
|
776
|
+
return latestTokens.access_token;
|
|
777
|
+
}
|
|
778
|
+
logger?.debug?.(`Cleared cached ${tokenKind} ${unrecoverableCode === "invalid_grant" ? "token" : "credentials"} for '${definition.name}' after unrecoverable refresh failure.`);
|
|
779
|
+
return void 0;
|
|
780
|
+
}
|
|
781
|
+
async function readExplicitRefreshableBearerToken(definition, persistence, tokens, logger) {
|
|
782
|
+
const refresh = definition.refresh;
|
|
783
|
+
const skewSeconds = refresh?.refreshSkewSeconds ?? TOKEN_EXPIRY_SKEW_SECONDS;
|
|
784
|
+
if (!shouldRefreshCachedToken(tokens, skewSeconds)) {
|
|
785
|
+
return tokens.access_token;
|
|
786
|
+
}
|
|
787
|
+
if (!refresh) {
|
|
788
|
+
throw new Error(`Cached bearer token for '${definition.name}' is expired, but refresh is not configured.`);
|
|
789
|
+
}
|
|
790
|
+
if (typeof tokens.refresh_token !== "string" || tokens.refresh_token.trim().length === 0) {
|
|
791
|
+
throw new Error(`Cached bearer token for '${definition.name}' is expired, but no refresh_token is available.`);
|
|
792
|
+
}
|
|
793
|
+
try {
|
|
794
|
+
const refreshed = await refreshBearerToken(definition, tokens.refresh_token);
|
|
795
|
+
await persistence.saveTokens(refreshed);
|
|
796
|
+
logger?.debug?.(`Refreshed bearer access token for '${definition.name}' (non-interactive).`);
|
|
797
|
+
return refreshed.access_token;
|
|
798
|
+
} catch (error) {
|
|
799
|
+
logger?.debug?.(`Failed to refresh bearer token for '${definition.name}' non-interactively: ${error instanceof Error ? error.message : String(error)}`);
|
|
800
|
+
const unrecoverableCode = unrecoverableOAuthRefreshCode(error);
|
|
801
|
+
if (unrecoverableCode) {
|
|
802
|
+
const winnerToken = await recoverFromUnrecoverableRefresh(definition, persistence, tokens, unrecoverableCode, "bearer", logger);
|
|
803
|
+
if (winnerToken) {
|
|
804
|
+
return winnerToken;
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
throw new Error(`Failed to refresh cached bearer token for '${definition.name}': ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
async function refreshBearerToken(definition, refreshToken) {
|
|
811
|
+
const refresh = definition.refresh;
|
|
812
|
+
if (!refresh) {
|
|
813
|
+
throw new Error("Missing refresh configuration.");
|
|
814
|
+
}
|
|
815
|
+
const clientId = readEnvOrConfig(refresh.clientIdEnv, definition.oauthClientId);
|
|
816
|
+
const method = refresh.clientAuthMethod ?? definition.oauthTokenEndpointAuthMethod ?? "client_secret_basic";
|
|
817
|
+
const clientSecret = method === "none" ? void 0 : readClientSecret(definition, refresh.clientSecretEnv);
|
|
818
|
+
const body = new URLSearchParams({
|
|
819
|
+
grant_type: "refresh_token",
|
|
820
|
+
refresh_token: refreshToken
|
|
821
|
+
});
|
|
822
|
+
const headers = {
|
|
823
|
+
accept: "application/json",
|
|
824
|
+
"content-type": "application/x-www-form-urlencoded"
|
|
825
|
+
};
|
|
826
|
+
if (method === "client_secret_post") {
|
|
827
|
+
if (clientId) {
|
|
828
|
+
body.set("client_id", clientId);
|
|
829
|
+
}
|
|
830
|
+
if (clientSecret) {
|
|
831
|
+
body.set("client_secret", clientSecret);
|
|
832
|
+
}
|
|
833
|
+
} else if (method === "none") {
|
|
834
|
+
if (clientId) {
|
|
835
|
+
body.set("client_id", clientId);
|
|
836
|
+
}
|
|
837
|
+
} else {
|
|
838
|
+
if (!clientId || !clientSecret) {
|
|
839
|
+
throw new Error(`Refresh client credentials are required for '${method}'.`);
|
|
840
|
+
}
|
|
841
|
+
headers.authorization = `Basic ${Buffer.from(`${formEncodeCredential(clientId)}:${formEncodeCredential(clientSecret)}`).toString("base64")}`;
|
|
842
|
+
}
|
|
843
|
+
const response = await fetch(refresh.tokenEndpoint, {
|
|
844
|
+
method: "POST",
|
|
845
|
+
headers,
|
|
846
|
+
body
|
|
847
|
+
});
|
|
848
|
+
if (!response.ok) {
|
|
849
|
+
const errorCode = await oauthErrorCodeFromResponse(response);
|
|
850
|
+
throw Object.assign(new Error(`Token endpoint returned HTTP ${response.status}${errorCode ? ` (${errorCode})` : ""}.`), errorCode ? { errorCode } : {});
|
|
851
|
+
}
|
|
852
|
+
const payload = normalizeBearerTokenResponse(await response.json());
|
|
853
|
+
return {
|
|
854
|
+
...payload,
|
|
855
|
+
...payload.refresh_token ? {} : { refresh_token: refreshToken }
|
|
856
|
+
};
|
|
857
|
+
}
|
|
858
|
+
function normalizeBearerTokenResponse(value) {
|
|
859
|
+
if (!value || typeof value !== "object") {
|
|
860
|
+
throw new Error("Token endpoint did not return a JSON object.");
|
|
861
|
+
}
|
|
862
|
+
const payload = value;
|
|
863
|
+
if (typeof payload.access_token !== "string" || payload.access_token.trim().length === 0) {
|
|
864
|
+
throw new Error("Token endpoint did not return an access_token.");
|
|
865
|
+
}
|
|
866
|
+
return {
|
|
867
|
+
access_token: payload.access_token,
|
|
868
|
+
token_type: typeof payload.token_type === "string" && payload.token_type ? payload.token_type : "Bearer",
|
|
869
|
+
...typeof payload.id_token === "string" ? { id_token: payload.id_token } : {},
|
|
870
|
+
...typeof payload.scope === "string" ? { scope: payload.scope } : {},
|
|
871
|
+
...typeof payload.refresh_token === "string" && payload.refresh_token ? { refresh_token: payload.refresh_token } : {},
|
|
872
|
+
...coerceExpiresIn(payload.expires_in)
|
|
873
|
+
};
|
|
874
|
+
}
|
|
875
|
+
function coerceExpiresIn(value) {
|
|
876
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
877
|
+
return { expires_in: value };
|
|
878
|
+
}
|
|
879
|
+
if (typeof value === "string" && value.trim().length > 0) {
|
|
880
|
+
const parsed = Number(value);
|
|
881
|
+
if (Number.isFinite(parsed)) {
|
|
882
|
+
return { expires_in: parsed };
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
return {};
|
|
886
|
+
}
|
|
887
|
+
function readEnvOrConfig(envName, fallback) {
|
|
888
|
+
if (!envName) {
|
|
889
|
+
return fallback;
|
|
890
|
+
}
|
|
891
|
+
const value = process.env[envName];
|
|
892
|
+
if (value === void 0 || value.trim().length === 0) {
|
|
893
|
+
throw new Error(`Environment variable '${envName}' is required for bearer token refresh.`);
|
|
894
|
+
}
|
|
895
|
+
return value;
|
|
896
|
+
}
|
|
897
|
+
function formEncodeCredential(value) {
|
|
898
|
+
return new URLSearchParams([["", value]]).toString().slice(1);
|
|
899
|
+
}
|
|
900
|
+
function readClientSecret(definition, refreshClientSecretEnv) {
|
|
901
|
+
if (refreshClientSecretEnv) {
|
|
902
|
+
return readEnvOrConfig(refreshClientSecretEnv, void 0);
|
|
903
|
+
}
|
|
904
|
+
return resolveOAuthClientSecret(definition, { rejectBlank: true });
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
// node_modules/.pnpm/mcporter@0.13.0/node_modules/mcporter/dist/oauth-persistence.js
|
|
908
|
+
async function buildOAuthPersistence(definition, logger) {
|
|
909
|
+
return await createOAuthPersistenceStores(definition, logger);
|
|
910
|
+
}
|
|
911
|
+
async function clearOAuthCaches(definition, logger, scope = "all") {
|
|
912
|
+
const persistence = await buildOAuthPersistence(definition, logger);
|
|
913
|
+
await persistence.clear(scope);
|
|
914
|
+
if (definition.tokenCacheDir && scope === "all") {
|
|
915
|
+
await fs2.rm(definition.tokenCacheDir, { recursive: true, force: true });
|
|
916
|
+
}
|
|
917
|
+
await clearLegacyOAuthArtifacts(definition, logger, scope);
|
|
918
|
+
}
|
|
919
|
+
async function readCachedAccessToken(definition, logger) {
|
|
920
|
+
const persistence = await buildOAuthPersistence(definition, logger);
|
|
921
|
+
return await readCachedAccessTokenWithPersistence(definition, persistence, logger);
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
export {
|
|
925
|
+
buildStaticClientInformation,
|
|
926
|
+
buildOAuthPersistence,
|
|
927
|
+
clearOAuthCaches,
|
|
928
|
+
readCachedAccessToken
|
|
929
|
+
};
|