@mcp-ts/sdk 1.3.7 → 1.3.10
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/LICENSE +21 -21
- package/README.md +397 -404
- package/dist/adapters/agui-middleware.js.map +1 -1
- package/dist/adapters/agui-middleware.mjs.map +1 -1
- package/dist/bin/mcp-ts.js +0 -0
- package/dist/bin/mcp-ts.js.map +1 -1
- package/dist/bin/mcp-ts.mjs +0 -0
- package/dist/bin/mcp-ts.mjs.map +1 -1
- package/dist/client/index.js.map +1 -1
- package/dist/client/index.mjs.map +1 -1
- package/dist/client/react.d.mts +10 -28
- package/dist/client/react.d.ts +10 -28
- package/dist/client/react.js +101 -52
- package/dist/client/react.js.map +1 -1
- package/dist/client/react.mjs +102 -53
- package/dist/client/react.mjs.map +1 -1
- package/dist/client/vue.js.map +1 -1
- package/dist/client/vue.mjs.map +1 -1
- package/dist/index.js +78 -6
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +73 -6
- package/dist/index.mjs.map +1 -1
- package/dist/server/index.js +78 -6
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +73 -6
- package/dist/server/index.mjs.map +1 -1
- package/dist/shared/index.js.map +1 -1
- package/dist/shared/index.mjs.map +1 -1
- package/package.json +185 -185
- package/src/adapters/agui-middleware.ts +382 -382
- package/src/bin/mcp-ts.ts +102 -102
- package/src/client/core/app-host.ts +417 -417
- package/src/client/core/sse-client.ts +371 -371
- package/src/client/core/types.ts +31 -31
- package/src/client/index.ts +27 -27
- package/src/client/react/index.ts +20 -16
- package/src/client/react/use-app-host.ts +74 -73
- package/src/client/react/use-mcp-apps.tsx +224 -214
- package/src/client/react/use-mcp.ts +669 -641
- package/src/client/vue/index.ts +10 -10
- package/src/client/vue/use-mcp.ts +617 -617
- package/src/index.ts +11 -11
- package/src/server/handlers/nextjs-handler.ts +204 -204
- package/src/server/handlers/sse-handler.ts +631 -631
- package/src/server/index.ts +57 -57
- package/src/server/mcp/multi-session-client.ts +228 -228
- package/src/server/mcp/oauth-client.ts +1188 -1188
- package/src/server/mcp/storage-oauth-provider.ts +272 -272
- package/src/server/storage/crypto.ts +92 -0
- package/src/server/storage/file-backend.ts +157 -157
- package/src/server/storage/index.ts +176 -176
- package/src/server/storage/memory-backend.ts +123 -123
- package/src/server/storage/redis-backend.ts +276 -276
- package/src/server/storage/redis.ts +160 -160
- package/src/server/storage/sqlite-backend.ts +182 -182
- package/src/server/storage/supabase-backend.ts +229 -228
- package/src/server/storage/types.ts +116 -116
- package/src/shared/constants.ts +29 -29
- package/src/shared/errors.ts +133 -133
- package/src/shared/event-routing.ts +28 -28
- package/src/shared/events.ts +180 -180
- package/src/shared/index.ts +75 -75
- package/src/shared/tool-utils.ts +61 -61
- package/src/shared/types.ts +282 -282
- package/src/shared/utils.ts +38 -38
- package/supabase/migrations/20260330195700_install_mcp_sessions.sql +84 -84
|
@@ -1,272 +1,272 @@
|
|
|
1
|
-
import type { OAuthClientProvider } from "@modelcontextprotocol/sdk/client/auth.js";
|
|
2
|
-
import type {
|
|
3
|
-
OAuthClientInformationFull,
|
|
4
|
-
OAuthClientInformationMixed,
|
|
5
|
-
OAuthClientMetadata,
|
|
6
|
-
OAuthTokens
|
|
7
|
-
} from "@modelcontextprotocol/sdk/shared/auth.js";
|
|
8
|
-
import { storage, SessionData } from "../storage/index.js";
|
|
9
|
-
import {
|
|
10
|
-
DEFAULT_CLIENT_NAME,
|
|
11
|
-
DEFAULT_CLIENT_URI,
|
|
12
|
-
DEFAULT_LOGO_URI,
|
|
13
|
-
DEFAULT_POLICY_URI,
|
|
14
|
-
SOFTWARE_ID,
|
|
15
|
-
SOFTWARE_VERSION,
|
|
16
|
-
TOKEN_EXPIRY_BUFFER_MS,
|
|
17
|
-
} from '../../shared/constants.js';
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Extension of OAuthClientProvider interface with additional methods
|
|
21
|
-
* Enables server-specific tracking and state management
|
|
22
|
-
*/
|
|
23
|
-
export interface AgentsOAuthProvider extends OAuthClientProvider {
|
|
24
|
-
authUrl: string | undefined;
|
|
25
|
-
clientId: string | undefined;
|
|
26
|
-
serverId: string | undefined;
|
|
27
|
-
checkState(
|
|
28
|
-
state: string
|
|
29
|
-
): Promise<{ valid: boolean; serverId?: string; error?: string }>;
|
|
30
|
-
consumeState(state: string): Promise<void>;
|
|
31
|
-
deleteCodeVerifier(): Promise<void>;
|
|
32
|
-
isTokenExpired(): boolean;
|
|
33
|
-
setTokenExpiresAt(expiresAt: number): void;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export interface StorageOAuthClientProviderOptions {
|
|
37
|
-
identity: string;
|
|
38
|
-
serverId: string;
|
|
39
|
-
sessionId: string;
|
|
40
|
-
redirectUrl: string;
|
|
41
|
-
clientName?: string;
|
|
42
|
-
clientUri?: string;
|
|
43
|
-
logoUri?: string;
|
|
44
|
-
policyUri?: string;
|
|
45
|
-
clientId?: string;
|
|
46
|
-
clientSecret?: string;
|
|
47
|
-
onRedirect?: (url: string) => void;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Storage-backed OAuth provider implementation for MCP
|
|
52
|
-
* Stores OAuth tokens, client information, and PKCE verifiers using the configured StorageBackend
|
|
53
|
-
*/
|
|
54
|
-
export class StorageOAuthClientProvider implements AgentsOAuthProvider {
|
|
55
|
-
public readonly identity: string;
|
|
56
|
-
public readonly serverId: string;
|
|
57
|
-
public readonly sessionId: string;
|
|
58
|
-
public readonly redirectUrl: string;
|
|
59
|
-
|
|
60
|
-
private readonly clientName?: string;
|
|
61
|
-
private readonly clientUri?: string;
|
|
62
|
-
private readonly logoUri?: string;
|
|
63
|
-
private readonly policyUri?: string;
|
|
64
|
-
private readonly clientSecret?: string;
|
|
65
|
-
|
|
66
|
-
private _authUrl: string | undefined;
|
|
67
|
-
private _clientId: string | undefined;
|
|
68
|
-
private onRedirectCallback?: (url: string) => void;
|
|
69
|
-
private tokenExpiresAt?: number;
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Creates a new storage-backed OAuth provider
|
|
73
|
-
* @param options - Provider configuration
|
|
74
|
-
*/
|
|
75
|
-
constructor(options: StorageOAuthClientProviderOptions) {
|
|
76
|
-
this.identity = options.identity;
|
|
77
|
-
this.serverId = options.serverId;
|
|
78
|
-
this.sessionId = options.sessionId;
|
|
79
|
-
this.redirectUrl = options.redirectUrl;
|
|
80
|
-
this.clientName = options.clientName;
|
|
81
|
-
this.clientUri = options.clientUri;
|
|
82
|
-
this.logoUri = options.logoUri;
|
|
83
|
-
this.policyUri = options.policyUri;
|
|
84
|
-
this._clientId = options.clientId;
|
|
85
|
-
this.clientSecret = options.clientSecret;
|
|
86
|
-
this.onRedirectCallback = options.onRedirect;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
get clientMetadata(): OAuthClientMetadata {
|
|
90
|
-
return {
|
|
91
|
-
client_name: this.clientName || DEFAULT_CLIENT_NAME,
|
|
92
|
-
client_uri: this.clientUri || DEFAULT_CLIENT_URI,
|
|
93
|
-
logo_uri: this.logoUri || DEFAULT_LOGO_URI,
|
|
94
|
-
policy_uri: this.policyUri || DEFAULT_POLICY_URI,
|
|
95
|
-
grant_types: ["authorization_code", "refresh_token"],
|
|
96
|
-
redirect_uris: [this.redirectUrl],
|
|
97
|
-
response_types: ["code"],
|
|
98
|
-
token_endpoint_auth_method: this.clientSecret ? "client_secret_basic" : "none",
|
|
99
|
-
software_id: SOFTWARE_ID,
|
|
100
|
-
software_version: SOFTWARE_VERSION,
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
get clientId() {
|
|
105
|
-
return this._clientId;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
set clientId(clientId_: string | undefined) {
|
|
109
|
-
this._clientId = clientId_;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* Loads OAuth data from storage session
|
|
114
|
-
* @private
|
|
115
|
-
*/
|
|
116
|
-
private async getSessionData(): Promise<SessionData> {
|
|
117
|
-
const data = await storage.getSession(this.identity, this.sessionId);
|
|
118
|
-
if (!data) {
|
|
119
|
-
return {} as SessionData;
|
|
120
|
-
}
|
|
121
|
-
return data;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* Saves OAuth data to storage
|
|
126
|
-
* @param data - Partial OAuth data to save
|
|
127
|
-
* @private
|
|
128
|
-
* @throws Error if session doesn't exist (session must be created by controller layer)
|
|
129
|
-
*/
|
|
130
|
-
private async saveSessionData(data: Partial<SessionData>): Promise<void> {
|
|
131
|
-
await storage.updateSession(this.identity, this.sessionId, data);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
/**
|
|
135
|
-
* Retrieves stored OAuth client information
|
|
136
|
-
*/
|
|
137
|
-
async clientInformation(): Promise<OAuthClientInformationMixed | undefined> {
|
|
138
|
-
const data = await this.getSessionData();
|
|
139
|
-
|
|
140
|
-
if (data.clientId && !this._clientId) {
|
|
141
|
-
this._clientId = data.clientId;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
if (data.clientInformation) {
|
|
145
|
-
return data.clientInformation;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
if (!this._clientId) {
|
|
149
|
-
return undefined;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
return {
|
|
153
|
-
client_id: this._clientId,
|
|
154
|
-
...(this.clientSecret ? { client_secret: this.clientSecret } : {}),
|
|
155
|
-
};
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
/**
|
|
159
|
-
* Stores OAuth client information
|
|
160
|
-
*/
|
|
161
|
-
async saveClientInformation(clientInformation: OAuthClientInformationFull): Promise<void> {
|
|
162
|
-
await this.saveSessionData({
|
|
163
|
-
clientInformation,
|
|
164
|
-
clientId: clientInformation.client_id
|
|
165
|
-
});
|
|
166
|
-
this.clientId = clientInformation.client_id;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
/**
|
|
170
|
-
* Stores OAuth tokens
|
|
171
|
-
*/
|
|
172
|
-
async saveTokens(tokens: OAuthTokens): Promise<void> {
|
|
173
|
-
const data: Partial<SessionData> = { tokens };
|
|
174
|
-
|
|
175
|
-
if (tokens.expires_in) {
|
|
176
|
-
this.tokenExpiresAt = Date.now() + (tokens.expires_in * 1000) - TOKEN_EXPIRY_BUFFER_MS;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
await this.saveSessionData(data);
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
get authUrl() {
|
|
183
|
-
return this._authUrl;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
async state(): Promise<string> {
|
|
187
|
-
return this.sessionId;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
async checkState(_state: string): Promise<{ valid: boolean; serverId?: string; error?: string }> {
|
|
191
|
-
const data = await storage.getSession(this.identity, this.sessionId);
|
|
192
|
-
|
|
193
|
-
if (!data) {
|
|
194
|
-
return { valid: false, error: "Session not found" };
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
return { valid: true, serverId: this.serverId };
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
async consumeState(_state: string): Promise<void> {
|
|
201
|
-
// No-op
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
async redirectToAuthorization(authUrl: URL): Promise<void> {
|
|
205
|
-
this._authUrl = authUrl.toString();
|
|
206
|
-
if (this.onRedirectCallback) {
|
|
207
|
-
this.onRedirectCallback(authUrl.toString());
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
async invalidateCredentials(
|
|
212
|
-
scope: "all" | "client" | "tokens" | "verifier"
|
|
213
|
-
): Promise<void> {
|
|
214
|
-
if (scope === "all") {
|
|
215
|
-
await storage.removeSession(this.identity, this.sessionId);
|
|
216
|
-
} else {
|
|
217
|
-
const updates: Partial<SessionData> = {};
|
|
218
|
-
|
|
219
|
-
if (scope === "client") {
|
|
220
|
-
updates.clientInformation = undefined;
|
|
221
|
-
updates.clientId = undefined;
|
|
222
|
-
} else if (scope === "tokens") {
|
|
223
|
-
updates.tokens = undefined;
|
|
224
|
-
} else if (scope === "verifier") {
|
|
225
|
-
updates.codeVerifier = undefined;
|
|
226
|
-
}
|
|
227
|
-
await this.saveSessionData(updates);
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
async saveCodeVerifier(verifier: string): Promise<void> {
|
|
232
|
-
await this.saveSessionData({ codeVerifier: verifier });
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
async codeVerifier(): Promise<string> {
|
|
236
|
-
const data = await this.getSessionData();
|
|
237
|
-
|
|
238
|
-
if (data.clientId && !this._clientId) {
|
|
239
|
-
this._clientId = data.clientId;
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
if (!data.codeVerifier) {
|
|
243
|
-
throw new Error("No code verifier found");
|
|
244
|
-
}
|
|
245
|
-
return data.codeVerifier;
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
async deleteCodeVerifier(): Promise<void> {
|
|
249
|
-
await this.saveSessionData({ codeVerifier: undefined });
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
async tokens(): Promise<OAuthTokens | undefined> {
|
|
253
|
-
const data = await this.getSessionData();
|
|
254
|
-
|
|
255
|
-
if (data.clientId && !this._clientId) {
|
|
256
|
-
this._clientId = data.clientId;
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
return data.tokens;
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
isTokenExpired(): boolean {
|
|
263
|
-
if (!this.tokenExpiresAt) {
|
|
264
|
-
return false;
|
|
265
|
-
}
|
|
266
|
-
return Date.now() >= this.tokenExpiresAt;
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
setTokenExpiresAt(expiresAt: number): void {
|
|
270
|
-
this.tokenExpiresAt = expiresAt;
|
|
271
|
-
}
|
|
272
|
-
}
|
|
1
|
+
import type { OAuthClientProvider } from "@modelcontextprotocol/sdk/client/auth.js";
|
|
2
|
+
import type {
|
|
3
|
+
OAuthClientInformationFull,
|
|
4
|
+
OAuthClientInformationMixed,
|
|
5
|
+
OAuthClientMetadata,
|
|
6
|
+
OAuthTokens
|
|
7
|
+
} from "@modelcontextprotocol/sdk/shared/auth.js";
|
|
8
|
+
import { storage, SessionData } from "../storage/index.js";
|
|
9
|
+
import {
|
|
10
|
+
DEFAULT_CLIENT_NAME,
|
|
11
|
+
DEFAULT_CLIENT_URI,
|
|
12
|
+
DEFAULT_LOGO_URI,
|
|
13
|
+
DEFAULT_POLICY_URI,
|
|
14
|
+
SOFTWARE_ID,
|
|
15
|
+
SOFTWARE_VERSION,
|
|
16
|
+
TOKEN_EXPIRY_BUFFER_MS,
|
|
17
|
+
} from '../../shared/constants.js';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Extension of OAuthClientProvider interface with additional methods
|
|
21
|
+
* Enables server-specific tracking and state management
|
|
22
|
+
*/
|
|
23
|
+
export interface AgentsOAuthProvider extends OAuthClientProvider {
|
|
24
|
+
authUrl: string | undefined;
|
|
25
|
+
clientId: string | undefined;
|
|
26
|
+
serverId: string | undefined;
|
|
27
|
+
checkState(
|
|
28
|
+
state: string
|
|
29
|
+
): Promise<{ valid: boolean; serverId?: string; error?: string }>;
|
|
30
|
+
consumeState(state: string): Promise<void>;
|
|
31
|
+
deleteCodeVerifier(): Promise<void>;
|
|
32
|
+
isTokenExpired(): boolean;
|
|
33
|
+
setTokenExpiresAt(expiresAt: number): void;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface StorageOAuthClientProviderOptions {
|
|
37
|
+
identity: string;
|
|
38
|
+
serverId: string;
|
|
39
|
+
sessionId: string;
|
|
40
|
+
redirectUrl: string;
|
|
41
|
+
clientName?: string;
|
|
42
|
+
clientUri?: string;
|
|
43
|
+
logoUri?: string;
|
|
44
|
+
policyUri?: string;
|
|
45
|
+
clientId?: string;
|
|
46
|
+
clientSecret?: string;
|
|
47
|
+
onRedirect?: (url: string) => void;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Storage-backed OAuth provider implementation for MCP
|
|
52
|
+
* Stores OAuth tokens, client information, and PKCE verifiers using the configured StorageBackend
|
|
53
|
+
*/
|
|
54
|
+
export class StorageOAuthClientProvider implements AgentsOAuthProvider {
|
|
55
|
+
public readonly identity: string;
|
|
56
|
+
public readonly serverId: string;
|
|
57
|
+
public readonly sessionId: string;
|
|
58
|
+
public readonly redirectUrl: string;
|
|
59
|
+
|
|
60
|
+
private readonly clientName?: string;
|
|
61
|
+
private readonly clientUri?: string;
|
|
62
|
+
private readonly logoUri?: string;
|
|
63
|
+
private readonly policyUri?: string;
|
|
64
|
+
private readonly clientSecret?: string;
|
|
65
|
+
|
|
66
|
+
private _authUrl: string | undefined;
|
|
67
|
+
private _clientId: string | undefined;
|
|
68
|
+
private onRedirectCallback?: (url: string) => void;
|
|
69
|
+
private tokenExpiresAt?: number;
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Creates a new storage-backed OAuth provider
|
|
73
|
+
* @param options - Provider configuration
|
|
74
|
+
*/
|
|
75
|
+
constructor(options: StorageOAuthClientProviderOptions) {
|
|
76
|
+
this.identity = options.identity;
|
|
77
|
+
this.serverId = options.serverId;
|
|
78
|
+
this.sessionId = options.sessionId;
|
|
79
|
+
this.redirectUrl = options.redirectUrl;
|
|
80
|
+
this.clientName = options.clientName;
|
|
81
|
+
this.clientUri = options.clientUri;
|
|
82
|
+
this.logoUri = options.logoUri;
|
|
83
|
+
this.policyUri = options.policyUri;
|
|
84
|
+
this._clientId = options.clientId;
|
|
85
|
+
this.clientSecret = options.clientSecret;
|
|
86
|
+
this.onRedirectCallback = options.onRedirect;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
get clientMetadata(): OAuthClientMetadata {
|
|
90
|
+
return {
|
|
91
|
+
client_name: this.clientName || DEFAULT_CLIENT_NAME,
|
|
92
|
+
client_uri: this.clientUri || DEFAULT_CLIENT_URI,
|
|
93
|
+
logo_uri: this.logoUri || DEFAULT_LOGO_URI,
|
|
94
|
+
policy_uri: this.policyUri || DEFAULT_POLICY_URI,
|
|
95
|
+
grant_types: ["authorization_code", "refresh_token"],
|
|
96
|
+
redirect_uris: [this.redirectUrl],
|
|
97
|
+
response_types: ["code"],
|
|
98
|
+
token_endpoint_auth_method: this.clientSecret ? "client_secret_basic" : "none",
|
|
99
|
+
software_id: SOFTWARE_ID,
|
|
100
|
+
software_version: SOFTWARE_VERSION,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
get clientId() {
|
|
105
|
+
return this._clientId;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
set clientId(clientId_: string | undefined) {
|
|
109
|
+
this._clientId = clientId_;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Loads OAuth data from storage session
|
|
114
|
+
* @private
|
|
115
|
+
*/
|
|
116
|
+
private async getSessionData(): Promise<SessionData> {
|
|
117
|
+
const data = await storage.getSession(this.identity, this.sessionId);
|
|
118
|
+
if (!data) {
|
|
119
|
+
return {} as SessionData;
|
|
120
|
+
}
|
|
121
|
+
return data;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Saves OAuth data to storage
|
|
126
|
+
* @param data - Partial OAuth data to save
|
|
127
|
+
* @private
|
|
128
|
+
* @throws Error if session doesn't exist (session must be created by controller layer)
|
|
129
|
+
*/
|
|
130
|
+
private async saveSessionData(data: Partial<SessionData>): Promise<void> {
|
|
131
|
+
await storage.updateSession(this.identity, this.sessionId, data);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Retrieves stored OAuth client information
|
|
136
|
+
*/
|
|
137
|
+
async clientInformation(): Promise<OAuthClientInformationMixed | undefined> {
|
|
138
|
+
const data = await this.getSessionData();
|
|
139
|
+
|
|
140
|
+
if (data.clientId && !this._clientId) {
|
|
141
|
+
this._clientId = data.clientId;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (data.clientInformation) {
|
|
145
|
+
return data.clientInformation;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (!this._clientId) {
|
|
149
|
+
return undefined;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return {
|
|
153
|
+
client_id: this._clientId,
|
|
154
|
+
...(this.clientSecret ? { client_secret: this.clientSecret } : {}),
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Stores OAuth client information
|
|
160
|
+
*/
|
|
161
|
+
async saveClientInformation(clientInformation: OAuthClientInformationFull): Promise<void> {
|
|
162
|
+
await this.saveSessionData({
|
|
163
|
+
clientInformation,
|
|
164
|
+
clientId: clientInformation.client_id
|
|
165
|
+
});
|
|
166
|
+
this.clientId = clientInformation.client_id;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Stores OAuth tokens
|
|
171
|
+
*/
|
|
172
|
+
async saveTokens(tokens: OAuthTokens): Promise<void> {
|
|
173
|
+
const data: Partial<SessionData> = { tokens };
|
|
174
|
+
|
|
175
|
+
if (tokens.expires_in) {
|
|
176
|
+
this.tokenExpiresAt = Date.now() + (tokens.expires_in * 1000) - TOKEN_EXPIRY_BUFFER_MS;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
await this.saveSessionData(data);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
get authUrl() {
|
|
183
|
+
return this._authUrl;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
async state(): Promise<string> {
|
|
187
|
+
return this.sessionId;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
async checkState(_state: string): Promise<{ valid: boolean; serverId?: string; error?: string }> {
|
|
191
|
+
const data = await storage.getSession(this.identity, this.sessionId);
|
|
192
|
+
|
|
193
|
+
if (!data) {
|
|
194
|
+
return { valid: false, error: "Session not found" };
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return { valid: true, serverId: this.serverId };
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
async consumeState(_state: string): Promise<void> {
|
|
201
|
+
// No-op
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
async redirectToAuthorization(authUrl: URL): Promise<void> {
|
|
205
|
+
this._authUrl = authUrl.toString();
|
|
206
|
+
if (this.onRedirectCallback) {
|
|
207
|
+
this.onRedirectCallback(authUrl.toString());
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
async invalidateCredentials(
|
|
212
|
+
scope: "all" | "client" | "tokens" | "verifier"
|
|
213
|
+
): Promise<void> {
|
|
214
|
+
if (scope === "all") {
|
|
215
|
+
await storage.removeSession(this.identity, this.sessionId);
|
|
216
|
+
} else {
|
|
217
|
+
const updates: Partial<SessionData> = {};
|
|
218
|
+
|
|
219
|
+
if (scope === "client") {
|
|
220
|
+
updates.clientInformation = undefined;
|
|
221
|
+
updates.clientId = undefined;
|
|
222
|
+
} else if (scope === "tokens") {
|
|
223
|
+
updates.tokens = undefined;
|
|
224
|
+
} else if (scope === "verifier") {
|
|
225
|
+
updates.codeVerifier = undefined;
|
|
226
|
+
}
|
|
227
|
+
await this.saveSessionData(updates);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
async saveCodeVerifier(verifier: string): Promise<void> {
|
|
232
|
+
await this.saveSessionData({ codeVerifier: verifier });
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
async codeVerifier(): Promise<string> {
|
|
236
|
+
const data = await this.getSessionData();
|
|
237
|
+
|
|
238
|
+
if (data.clientId && !this._clientId) {
|
|
239
|
+
this._clientId = data.clientId;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (!data.codeVerifier) {
|
|
243
|
+
throw new Error("No code verifier found");
|
|
244
|
+
}
|
|
245
|
+
return data.codeVerifier;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
async deleteCodeVerifier(): Promise<void> {
|
|
249
|
+
await this.saveSessionData({ codeVerifier: undefined });
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
async tokens(): Promise<OAuthTokens | undefined> {
|
|
253
|
+
const data = await this.getSessionData();
|
|
254
|
+
|
|
255
|
+
if (data.clientId && !this._clientId) {
|
|
256
|
+
this._clientId = data.clientId;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
return data.tokens;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
isTokenExpired(): boolean {
|
|
263
|
+
if (!this.tokenExpiresAt) {
|
|
264
|
+
return false;
|
|
265
|
+
}
|
|
266
|
+
return Date.now() >= this.tokenExpiresAt;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
setTokenExpiresAt(expiresAt: number): void {
|
|
270
|
+
this.tokenExpiresAt = expiresAt;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { randomBytes, createCipheriv, createDecipheriv } from 'node:crypto';
|
|
2
|
+
|
|
3
|
+
const ALGORITHM = 'aes-256-gcm';
|
|
4
|
+
const IV_LENGTH = 12;
|
|
5
|
+
const ENCRYPTION_PREFIX = 'enc:1:';
|
|
6
|
+
|
|
7
|
+
let warningLogged = false;
|
|
8
|
+
|
|
9
|
+
function getKey(): Buffer | null {
|
|
10
|
+
const keyString = process.env.STORAGE_ENCRYPTION_KEY;
|
|
11
|
+
if (!keyString) return null;
|
|
12
|
+
|
|
13
|
+
// Ensure key is 32 bytes (256 bits)
|
|
14
|
+
if (keyString.length === 64) {
|
|
15
|
+
return Buffer.from(keyString, 'hex');
|
|
16
|
+
} else {
|
|
17
|
+
const keyBuffer = Buffer.alloc(32);
|
|
18
|
+
keyBuffer.write(keyString, 0, 32, 'utf-8');
|
|
19
|
+
return keyBuffer;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Encrypts an object into a secure string.
|
|
25
|
+
* Falls back to returning the original object if the encryption key is missing or encryption fails.
|
|
26
|
+
*/
|
|
27
|
+
export function encryptObject(data: any): any {
|
|
28
|
+
if (data === undefined || data === null) return data;
|
|
29
|
+
|
|
30
|
+
const key = getKey();
|
|
31
|
+
if (!key) {
|
|
32
|
+
if (!warningLogged) {
|
|
33
|
+
console.warn('[mcp-ts][Storage] WARNING: STORAGE_ENCRYPTION_KEY is not set. Saving sensitive data in plain-text.');
|
|
34
|
+
warningLogged = true;
|
|
35
|
+
}
|
|
36
|
+
return data; // Fallback to plain-text
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
const text = JSON.stringify(data);
|
|
41
|
+
const iv = randomBytes(IV_LENGTH);
|
|
42
|
+
const cipher = createCipheriv(ALGORITHM, key, iv);
|
|
43
|
+
|
|
44
|
+
let encrypted = cipher.update(text, 'utf-8', 'hex');
|
|
45
|
+
encrypted += cipher.final('hex');
|
|
46
|
+
const authTag = cipher.getAuthTag().toString('hex');
|
|
47
|
+
|
|
48
|
+
return `${ENCRYPTION_PREFIX}${iv.toString('hex')}:${authTag}:${encrypted}`;
|
|
49
|
+
} catch (e) {
|
|
50
|
+
console.error('[mcp-ts][Storage] Encryption failed, falling back to plain-text.', e);
|
|
51
|
+
return data;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Decrypts a secure string back into an object.
|
|
57
|
+
* Returns the original data if it is unencrypted or if decryption fails.
|
|
58
|
+
*/
|
|
59
|
+
export function decryptObject(data: any): any {
|
|
60
|
+
if (data === undefined || data === null) return data;
|
|
61
|
+
if (typeof data !== 'string' || !data.startsWith(ENCRYPTION_PREFIX)) {
|
|
62
|
+
return data; // Already unencrypted or old plain-text data
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const key = getKey();
|
|
66
|
+
if (!key) {
|
|
67
|
+
console.warn('[mcp-ts][Storage] WARNING: Found encrypted data but STORAGE_ENCRYPTION_KEY is missing. Returning raw encrypted string.');
|
|
68
|
+
return data;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
const parts = data.split(':');
|
|
73
|
+
if (parts.length !== 5) {
|
|
74
|
+
return data;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const iv = Buffer.from(parts[2], 'hex');
|
|
78
|
+
const authTag = Buffer.from(parts[3], 'hex');
|
|
79
|
+
const encryptedText = parts[4];
|
|
80
|
+
|
|
81
|
+
const decipher = createDecipheriv(ALGORITHM, key, iv);
|
|
82
|
+
decipher.setAuthTag(authTag);
|
|
83
|
+
|
|
84
|
+
let decrypted = decipher.update(encryptedText, 'hex', 'utf-8');
|
|
85
|
+
decrypted += decipher.final('utf-8');
|
|
86
|
+
|
|
87
|
+
return JSON.parse(decrypted);
|
|
88
|
+
} catch (e) {
|
|
89
|
+
console.error('[mcp-ts][Storage] Decryption failed.', e);
|
|
90
|
+
return data;
|
|
91
|
+
}
|
|
92
|
+
}
|