mcp-use 0.1.20 → 0.2.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/src/browser.d.ts +49 -0
- package/dist/src/browser.d.ts.map +1 -0
- package/dist/src/browser.js +75 -0
- package/dist/src/client/base.d.ts +32 -0
- package/dist/src/client/base.d.ts.map +1 -0
- package/dist/src/client/base.js +119 -0
- package/dist/src/client.d.ts +19 -16
- package/dist/src/client.d.ts.map +1 -1
- package/dist/src/client.js +24 -107
- package/dist/src/logging.d.ts +1 -1
- package/dist/src/logging.d.ts.map +1 -1
- package/dist/src/logging.js +31 -16
- package/dist/src/managers/server_manager.js +1 -1
- package/dist/src/oauth-helper.d.ts +135 -0
- package/dist/src/oauth-helper.d.ts.map +1 -0
- package/dist/src/oauth-helper.js +427 -0
- package/package.json +6 -1
- package/dist/examples/add_server_tool.d.ts +0 -8
- package/dist/examples/add_server_tool.d.ts.map +0 -1
- package/dist/examples/add_server_tool.js +0 -79
- package/dist/examples/ai_sdk_example.d.ts +0 -23
- package/dist/examples/ai_sdk_example.d.ts.map +0 -1
- package/dist/examples/ai_sdk_example.js +0 -213
- package/dist/examples/airbnb_use.d.ts +0 -10
- package/dist/examples/airbnb_use.d.ts.map +0 -1
- package/dist/examples/airbnb_use.js +0 -43
- package/dist/examples/blender_use.d.ts +0 -15
- package/dist/examples/blender_use.d.ts.map +0 -1
- package/dist/examples/blender_use.js +0 -39
- package/dist/examples/browser_use.d.ts +0 -10
- package/dist/examples/browser_use.d.ts.map +0 -1
- package/dist/examples/browser_use.js +0 -46
- package/dist/examples/chat_example.d.ts +0 -10
- package/dist/examples/chat_example.d.ts.map +0 -1
- package/dist/examples/chat_example.js +0 -86
- package/dist/examples/filesystem_use.d.ts +0 -11
- package/dist/examples/filesystem_use.d.ts.map +0 -1
- package/dist/examples/filesystem_use.js +0 -43
- package/dist/examples/http_example.d.ts +0 -18
- package/dist/examples/http_example.d.ts.map +0 -1
- package/dist/examples/http_example.js +0 -37
- package/dist/examples/mcp_everything.d.ts +0 -6
- package/dist/examples/mcp_everything.d.ts.map +0 -1
- package/dist/examples/mcp_everything.js +0 -25
- package/dist/examples/multi_server_example.d.ts +0 -10
- package/dist/examples/multi_server_example.d.ts.map +0 -1
- package/dist/examples/multi_server_example.js +0 -51
- package/dist/examples/observability.d.ts +0 -6
- package/dist/examples/observability.d.ts.map +0 -1
- package/dist/examples/observability.js +0 -50
- package/dist/examples/stream_example.d.ts +0 -12
- package/dist/examples/stream_example.d.ts.map +0 -1
- package/dist/examples/stream_example.js +0 -198
- package/dist/examples/structured_output.d.ts +0 -9
- package/dist/examples/structured_output.d.ts.map +0 -1
- package/dist/examples/structured_output.js +0 -95
@@ -0,0 +1,135 @@
|
|
1
|
+
/**
|
2
|
+
* OAuth helper for browser-based MCP authentication
|
3
|
+
*
|
4
|
+
* This helper provides OAuth 2.0 authorization code flow support for MCP servers
|
5
|
+
* that require authentication, such as Linear's MCP server.
|
6
|
+
*/
|
7
|
+
export interface OAuthConfig {
|
8
|
+
clientId?: string;
|
9
|
+
redirectUri: string;
|
10
|
+
scope?: string;
|
11
|
+
state?: string;
|
12
|
+
clientName?: string;
|
13
|
+
}
|
14
|
+
export interface OAuthDiscovery {
|
15
|
+
issuer: string;
|
16
|
+
authorization_endpoint: string;
|
17
|
+
token_endpoint: string;
|
18
|
+
registration_endpoint?: string;
|
19
|
+
response_types_supported: string[];
|
20
|
+
grant_types_supported: string[];
|
21
|
+
code_challenge_methods_supported: string[];
|
22
|
+
token_endpoint_auth_methods_supported?: string[];
|
23
|
+
}
|
24
|
+
export interface ClientRegistration {
|
25
|
+
client_id: string;
|
26
|
+
client_secret?: string;
|
27
|
+
registration_access_token?: string;
|
28
|
+
registration_client_uri?: string;
|
29
|
+
client_id_issued_at?: number;
|
30
|
+
client_secret_expires_at?: number;
|
31
|
+
}
|
32
|
+
export interface OAuthResult {
|
33
|
+
access_token: string;
|
34
|
+
token_type: string;
|
35
|
+
expires_at?: number | null;
|
36
|
+
refresh_token?: string | null;
|
37
|
+
scope?: string | null;
|
38
|
+
}
|
39
|
+
export interface OAuthState {
|
40
|
+
isRequired: boolean;
|
41
|
+
isAuthenticated: boolean;
|
42
|
+
isAuthenticating: boolean;
|
43
|
+
isCompletingOAuth: boolean;
|
44
|
+
authError: string | null;
|
45
|
+
oauthTokens: OAuthResult | null;
|
46
|
+
}
|
47
|
+
export declare class OAuthHelper {
|
48
|
+
private config;
|
49
|
+
private discovery?;
|
50
|
+
private state;
|
51
|
+
private clientRegistration?;
|
52
|
+
private serverUrl?;
|
53
|
+
private storageKey;
|
54
|
+
constructor(config: OAuthConfig);
|
55
|
+
/**
|
56
|
+
* Get current OAuth state
|
57
|
+
*/
|
58
|
+
getState(): OAuthState;
|
59
|
+
/**
|
60
|
+
* Load client registration from localStorage
|
61
|
+
*/
|
62
|
+
private loadClientRegistration;
|
63
|
+
/**
|
64
|
+
* Save client registration to localStorage
|
65
|
+
*/
|
66
|
+
private saveClientRegistration;
|
67
|
+
/**
|
68
|
+
* Check if a server requires authentication by pinging the URL
|
69
|
+
*/
|
70
|
+
checkAuthRequired(serverUrl: string): Promise<boolean>;
|
71
|
+
/**
|
72
|
+
* Fallback heuristics for determining auth requirements when direct checking fails
|
73
|
+
*/
|
74
|
+
private checkAuthByHeuristics;
|
75
|
+
/**
|
76
|
+
* Discover OAuth configuration from a server
|
77
|
+
*/
|
78
|
+
discoverOAuthConfig(serverUrl: string): Promise<OAuthDiscovery>;
|
79
|
+
/**
|
80
|
+
* Register a new OAuth client dynamically
|
81
|
+
*/
|
82
|
+
registerClient(serverUrl: string): Promise<ClientRegistration>;
|
83
|
+
/**
|
84
|
+
* Generate authorization URL for OAuth flow
|
85
|
+
*/
|
86
|
+
generateAuthUrl(serverUrl: string, additionalParams?: Record<string, string>): string;
|
87
|
+
/**
|
88
|
+
* Exchange authorization code for access token
|
89
|
+
*/
|
90
|
+
exchangeCodeForToken(serverUrl: string, code: string, codeVerifier?: string): Promise<OAuthResult>;
|
91
|
+
/**
|
92
|
+
* Handle OAuth callback and extract authorization code
|
93
|
+
*/
|
94
|
+
handleCallback(): {
|
95
|
+
code: string;
|
96
|
+
state: string;
|
97
|
+
} | null;
|
98
|
+
/**
|
99
|
+
* Start OAuth flow by opening popup window (similar to your implementation)
|
100
|
+
*/
|
101
|
+
startOAuthFlow(serverUrl: string): Promise<void>;
|
102
|
+
/**
|
103
|
+
* Complete OAuth flow by exchanging code for token
|
104
|
+
*/
|
105
|
+
completeOAuthFlow(serverUrl: string, code: string): Promise<OAuthResult>;
|
106
|
+
/**
|
107
|
+
* Reset authentication state
|
108
|
+
*/
|
109
|
+
resetAuth(): void;
|
110
|
+
/**
|
111
|
+
* Set OAuth state (internal method)
|
112
|
+
*/
|
113
|
+
private setState;
|
114
|
+
/**
|
115
|
+
* Generate a random state parameter for CSRF protection
|
116
|
+
*/
|
117
|
+
private generateState;
|
118
|
+
}
|
119
|
+
/**
|
120
|
+
* Linear-specific OAuth configuration
|
121
|
+
*/
|
122
|
+
export declare const LINEAR_OAUTH_CONFIG: OAuthConfig;
|
123
|
+
/**
|
124
|
+
* Helper function to create OAuth-enabled MCP configuration
|
125
|
+
*/
|
126
|
+
export declare function createOAuthMCPConfig(serverUrl: string, accessToken: string): {
|
127
|
+
mcpServers: {
|
128
|
+
linear: {
|
129
|
+
url: string;
|
130
|
+
authToken: string;
|
131
|
+
transport: string;
|
132
|
+
};
|
133
|
+
};
|
134
|
+
};
|
135
|
+
//# sourceMappingURL=oauth-helper.d.ts.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"oauth-helper.d.ts","sourceRoot":"","sources":["../../src/oauth-helper.ts"],"names":[],"mappings":"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"}
|
@@ -0,0 +1,427 @@
|
|
1
|
+
/**
|
2
|
+
* OAuth helper for browser-based MCP authentication
|
3
|
+
*
|
4
|
+
* This helper provides OAuth 2.0 authorization code flow support for MCP servers
|
5
|
+
* that require authentication, such as Linear's MCP server.
|
6
|
+
*/
|
7
|
+
export class OAuthHelper {
|
8
|
+
config;
|
9
|
+
discovery;
|
10
|
+
state;
|
11
|
+
clientRegistration;
|
12
|
+
serverUrl;
|
13
|
+
storageKey;
|
14
|
+
constructor(config) {
|
15
|
+
this.config = config;
|
16
|
+
this.storageKey = `mcp-oauth-${btoa(config.redirectUri)}`;
|
17
|
+
this.state = {
|
18
|
+
isRequired: false,
|
19
|
+
isAuthenticated: false,
|
20
|
+
isAuthenticating: false,
|
21
|
+
isCompletingOAuth: false,
|
22
|
+
authError: null,
|
23
|
+
oauthTokens: null,
|
24
|
+
};
|
25
|
+
// Load persisted client registration
|
26
|
+
this.loadClientRegistration();
|
27
|
+
}
|
28
|
+
/**
|
29
|
+
* Get current OAuth state
|
30
|
+
*/
|
31
|
+
getState() {
|
32
|
+
return { ...this.state };
|
33
|
+
}
|
34
|
+
/**
|
35
|
+
* Load client registration from localStorage
|
36
|
+
*/
|
37
|
+
loadClientRegistration() {
|
38
|
+
try {
|
39
|
+
const stored = localStorage.getItem(this.storageKey);
|
40
|
+
if (stored) {
|
41
|
+
const data = JSON.parse(stored);
|
42
|
+
this.clientRegistration = data.clientRegistration;
|
43
|
+
this.serverUrl = data.serverUrl;
|
44
|
+
console.log('🔄 [OAuthHelper] Loaded persisted client registration:', {
|
45
|
+
client_id: this.clientRegistration?.client_id,
|
46
|
+
server: this.serverUrl,
|
47
|
+
});
|
48
|
+
}
|
49
|
+
}
|
50
|
+
catch (error) {
|
51
|
+
console.warn('⚠️ [OAuthHelper] Failed to load client registration:', error);
|
52
|
+
}
|
53
|
+
}
|
54
|
+
/**
|
55
|
+
* Save client registration to localStorage
|
56
|
+
*/
|
57
|
+
saveClientRegistration() {
|
58
|
+
try {
|
59
|
+
const data = {
|
60
|
+
clientRegistration: this.clientRegistration,
|
61
|
+
serverUrl: this.serverUrl,
|
62
|
+
};
|
63
|
+
localStorage.setItem(this.storageKey, JSON.stringify(data));
|
64
|
+
console.log('💾 [OAuthHelper] Saved client registration to localStorage');
|
65
|
+
}
|
66
|
+
catch (error) {
|
67
|
+
console.warn('⚠️ [OAuthHelper] Failed to save client registration:', error);
|
68
|
+
}
|
69
|
+
}
|
70
|
+
/**
|
71
|
+
* Check if a server requires authentication by pinging the URL
|
72
|
+
*/
|
73
|
+
async checkAuthRequired(serverUrl) {
|
74
|
+
console.log('🔍 [OAuthHelper] Checking auth requirement for:', serverUrl);
|
75
|
+
try {
|
76
|
+
const response = await fetch(serverUrl, {
|
77
|
+
method: 'GET',
|
78
|
+
headers: {
|
79
|
+
'Accept': 'text/event-stream',
|
80
|
+
'Cache-Control': 'no-cache',
|
81
|
+
},
|
82
|
+
redirect: 'manual',
|
83
|
+
signal: AbortSignal.timeout(10000), // 10 second timeout
|
84
|
+
});
|
85
|
+
console.log('🔍 [OAuthHelper] Auth check response:', {
|
86
|
+
status: response.status,
|
87
|
+
statusText: response.statusText,
|
88
|
+
url: serverUrl,
|
89
|
+
});
|
90
|
+
// 401 Unauthorized, 403 Forbidden, or 400 Bad Request means auth is required
|
91
|
+
if (response.status === 401 || response.status === 403 || response.status === 400) {
|
92
|
+
console.log('🔐 [OAuthHelper] Authentication required for:', serverUrl);
|
93
|
+
return true;
|
94
|
+
}
|
95
|
+
// Any other response (200, 404, 500, etc.) means no auth required
|
96
|
+
console.log('✅ [OAuthHelper] No authentication required for:', serverUrl);
|
97
|
+
return false;
|
98
|
+
}
|
99
|
+
catch (error) {
|
100
|
+
console.warn('⚠️ [OAuthHelper] Could not check auth requirement for:', serverUrl, error);
|
101
|
+
// Handle specific error types
|
102
|
+
if (error.name === 'TypeError'
|
103
|
+
&& (error.message?.includes('CORS') || error.message?.includes('Failed to fetch'))) {
|
104
|
+
console.log('🔍 [OAuthHelper] CORS blocked direct check, using heuristics for:', serverUrl);
|
105
|
+
return this.checkAuthByHeuristics(serverUrl);
|
106
|
+
}
|
107
|
+
if (error.name === 'AbortError') {
|
108
|
+
console.log('⏰ [OAuthHelper] Request timeout, assuming no auth required for:', serverUrl);
|
109
|
+
return false;
|
110
|
+
}
|
111
|
+
// If we can't reach the server at all, try heuristics
|
112
|
+
return this.checkAuthByHeuristics(serverUrl);
|
113
|
+
}
|
114
|
+
}
|
115
|
+
/**
|
116
|
+
* Fallback heuristics for determining auth requirements when direct checking fails
|
117
|
+
*/
|
118
|
+
checkAuthByHeuristics(serverUrl) {
|
119
|
+
console.log('🔍 [OAuthHelper] Using heuristics to determine auth for:', serverUrl);
|
120
|
+
// Known patterns that typically require auth
|
121
|
+
const authRequiredPatterns = [
|
122
|
+
/api\.githubcopilot\.com/i, // GitHub Copilot
|
123
|
+
/api\.github\.com/i, // GitHub API
|
124
|
+
/.*\.googleapis\.com/i, // Google APIs
|
125
|
+
/api\.openai\.com/i, // OpenAI
|
126
|
+
/api\.anthropic\.com/i, // Anthropic
|
127
|
+
/.*\.atlassian\.net/i, // Atlassian (Jira, Confluence)
|
128
|
+
/.*\.slack\.com/i, // Slack
|
129
|
+
/api\.notion\.com/i, // Notion
|
130
|
+
/api\.linear\.app/i, // Linear
|
131
|
+
];
|
132
|
+
// Known patterns that typically don't require auth (public MCP servers)
|
133
|
+
const noAuthPatterns = [
|
134
|
+
/localhost/i, // Local development
|
135
|
+
/127\.0\.0\.1/, // Local development
|
136
|
+
/\.local/i, // Local development
|
137
|
+
/mcp\..*\.com/i, // Generic MCP server pattern (often public)
|
138
|
+
];
|
139
|
+
// Check no-auth patterns first
|
140
|
+
for (const pattern of noAuthPatterns) {
|
141
|
+
if (pattern.test(serverUrl)) {
|
142
|
+
console.log('✅ [OAuthHelper] Heuristic: No auth required (matches no-auth pattern):', serverUrl);
|
143
|
+
return false;
|
144
|
+
}
|
145
|
+
}
|
146
|
+
// Check auth-required patterns
|
147
|
+
for (const pattern of authRequiredPatterns) {
|
148
|
+
if (pattern.test(serverUrl)) {
|
149
|
+
console.log('🔐 [OAuthHelper] Heuristic: Auth required (matches auth pattern):', serverUrl);
|
150
|
+
return true;
|
151
|
+
}
|
152
|
+
}
|
153
|
+
// Default: assume no auth required for unknown patterns
|
154
|
+
console.log('❓ [OAuthHelper] Heuristic: Unknown pattern, assuming no auth required:', serverUrl);
|
155
|
+
return false;
|
156
|
+
}
|
157
|
+
/**
|
158
|
+
* Discover OAuth configuration from a server
|
159
|
+
*/
|
160
|
+
async discoverOAuthConfig(serverUrl) {
|
161
|
+
try {
|
162
|
+
const discoveryUrl = `${serverUrl}/.well-known/oauth-authorization-server`;
|
163
|
+
const response = await fetch(discoveryUrl);
|
164
|
+
if (!response.ok) {
|
165
|
+
throw new Error(`OAuth discovery failed: ${response.status} ${response.statusText}`);
|
166
|
+
}
|
167
|
+
this.discovery = await response.json();
|
168
|
+
return this.discovery;
|
169
|
+
}
|
170
|
+
catch (error) {
|
171
|
+
throw new Error(`Failed to discover OAuth configuration: ${error}`);
|
172
|
+
}
|
173
|
+
}
|
174
|
+
/**
|
175
|
+
* Register a new OAuth client dynamically
|
176
|
+
*/
|
177
|
+
async registerClient(serverUrl) {
|
178
|
+
if (!this.discovery) {
|
179
|
+
throw new Error('OAuth discovery not performed. Call discoverOAuthConfig first.');
|
180
|
+
}
|
181
|
+
if (!this.discovery.registration_endpoint) {
|
182
|
+
throw new Error('Server does not support dynamic client registration');
|
183
|
+
}
|
184
|
+
try {
|
185
|
+
const registrationData = {
|
186
|
+
client_name: this.config.clientName || 'MCP Use Example',
|
187
|
+
redirect_uris: [this.config.redirectUri],
|
188
|
+
grant_types: ['authorization_code'],
|
189
|
+
response_types: ['code'],
|
190
|
+
token_endpoint_auth_method: 'none', // Use public client (no secret)
|
191
|
+
scope: this.config.scope || 'read write',
|
192
|
+
};
|
193
|
+
console.log('🔐 [OAuthHelper] Registering OAuth client dynamically:', {
|
194
|
+
registration_endpoint: this.discovery.registration_endpoint,
|
195
|
+
client_name: registrationData.client_name,
|
196
|
+
redirect_uri: this.config.redirectUri,
|
197
|
+
});
|
198
|
+
const response = await fetch(this.discovery.registration_endpoint, {
|
199
|
+
method: 'POST',
|
200
|
+
headers: {
|
201
|
+
'Content-Type': 'application/json',
|
202
|
+
},
|
203
|
+
body: JSON.stringify(registrationData),
|
204
|
+
});
|
205
|
+
if (!response.ok) {
|
206
|
+
const errorText = await response.text();
|
207
|
+
throw new Error(`Client registration failed: ${response.status} ${response.statusText} - ${errorText}`);
|
208
|
+
}
|
209
|
+
this.clientRegistration = await response.json();
|
210
|
+
this.serverUrl = serverUrl;
|
211
|
+
this.saveClientRegistration();
|
212
|
+
console.log('✅ [OAuthHelper] Client registered successfully:', {
|
213
|
+
client_id: this.clientRegistration.client_id,
|
214
|
+
client_secret: this.clientRegistration.client_secret ? '***' : 'none',
|
215
|
+
});
|
216
|
+
return this.clientRegistration;
|
217
|
+
}
|
218
|
+
catch (error) {
|
219
|
+
console.error('❌ [OAuthHelper] Client registration failed:', error);
|
220
|
+
throw new Error(`Failed to register OAuth client: ${error}`);
|
221
|
+
}
|
222
|
+
}
|
223
|
+
/**
|
224
|
+
* Generate authorization URL for OAuth flow
|
225
|
+
*/
|
226
|
+
generateAuthUrl(serverUrl, additionalParams) {
|
227
|
+
if (!this.discovery) {
|
228
|
+
throw new Error('OAuth discovery not performed. Call discoverOAuthConfig first.');
|
229
|
+
}
|
230
|
+
if (!this.clientRegistration) {
|
231
|
+
throw new Error('Client not registered. Call registerClient first.');
|
232
|
+
}
|
233
|
+
const params = new URLSearchParams({
|
234
|
+
client_id: this.clientRegistration.client_id,
|
235
|
+
redirect_uri: this.config.redirectUri,
|
236
|
+
response_type: 'code',
|
237
|
+
scope: this.config.scope || 'read',
|
238
|
+
state: this.config.state || this.generateState(),
|
239
|
+
...additionalParams,
|
240
|
+
});
|
241
|
+
return `${this.discovery.authorization_endpoint}?${params.toString()}`;
|
242
|
+
}
|
243
|
+
/**
|
244
|
+
* Exchange authorization code for access token
|
245
|
+
*/
|
246
|
+
async exchangeCodeForToken(serverUrl, code, codeVerifier) {
|
247
|
+
if (!this.discovery) {
|
248
|
+
throw new Error('OAuth discovery not performed. Call discoverOAuthConfig first.');
|
249
|
+
}
|
250
|
+
if (!this.clientRegistration) {
|
251
|
+
throw new Error('Client not registered. Call registerClient first.');
|
252
|
+
}
|
253
|
+
const body = new URLSearchParams({
|
254
|
+
grant_type: 'authorization_code',
|
255
|
+
client_id: this.clientRegistration.client_id,
|
256
|
+
code,
|
257
|
+
redirect_uri: this.config.redirectUri,
|
258
|
+
});
|
259
|
+
if (codeVerifier) {
|
260
|
+
body.append('code_verifier', codeVerifier);
|
261
|
+
}
|
262
|
+
const response = await fetch(this.discovery.token_endpoint, {
|
263
|
+
method: 'POST',
|
264
|
+
headers: {
|
265
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
266
|
+
},
|
267
|
+
body: body.toString(),
|
268
|
+
});
|
269
|
+
if (!response.ok) {
|
270
|
+
const error = await response.text();
|
271
|
+
throw new Error(`Token exchange failed: ${response.status} ${response.statusText} - ${error}`);
|
272
|
+
}
|
273
|
+
return await response.json();
|
274
|
+
}
|
275
|
+
/**
|
276
|
+
* Handle OAuth callback and extract authorization code
|
277
|
+
*/
|
278
|
+
handleCallback() {
|
279
|
+
const urlParams = new URLSearchParams(window.location.search);
|
280
|
+
const code = urlParams.get('code');
|
281
|
+
const state = urlParams.get('state');
|
282
|
+
if (!code || !state) {
|
283
|
+
return null;
|
284
|
+
}
|
285
|
+
// Clean up URL
|
286
|
+
const url = new URL(window.location.href);
|
287
|
+
url.searchParams.delete('code');
|
288
|
+
url.searchParams.delete('state');
|
289
|
+
window.history.replaceState({}, '', url.toString());
|
290
|
+
return { code, state };
|
291
|
+
}
|
292
|
+
/**
|
293
|
+
* Start OAuth flow by opening popup window (similar to your implementation)
|
294
|
+
*/
|
295
|
+
async startOAuthFlow(serverUrl) {
|
296
|
+
this.setState({
|
297
|
+
isAuthenticating: true,
|
298
|
+
authError: null,
|
299
|
+
});
|
300
|
+
try {
|
301
|
+
// Step 1: Discover OAuth configuration
|
302
|
+
await this.discoverOAuthConfig(serverUrl);
|
303
|
+
// Step 2: Register client dynamically (if not already registered)
|
304
|
+
if (!this.clientRegistration) {
|
305
|
+
await this.registerClient(serverUrl);
|
306
|
+
}
|
307
|
+
// Step 3: Generate authorization URL
|
308
|
+
const authUrl = this.generateAuthUrl(serverUrl);
|
309
|
+
// Step 4: Open popup window for authentication
|
310
|
+
const authWindow = window.open(authUrl, 'mcp-oauth', 'width=500,height=600,scrollbars=yes,resizable=yes,status=yes,location=yes');
|
311
|
+
if (!authWindow) {
|
312
|
+
throw new Error('Failed to open authentication window. Please allow popups for this site and try again.');
|
313
|
+
}
|
314
|
+
console.log('✅ [OAuthHelper] OAuth popup opened successfully');
|
315
|
+
}
|
316
|
+
catch (error) {
|
317
|
+
console.error('❌ [OAuthHelper] Failed to start OAuth flow:', error);
|
318
|
+
this.setState({
|
319
|
+
isAuthenticating: false,
|
320
|
+
authError: error instanceof Error ? error.message : 'Failed to start authentication',
|
321
|
+
});
|
322
|
+
throw error;
|
323
|
+
}
|
324
|
+
}
|
325
|
+
/**
|
326
|
+
* Complete OAuth flow by exchanging code for token
|
327
|
+
*/
|
328
|
+
async completeOAuthFlow(serverUrl, code) {
|
329
|
+
this.setState({
|
330
|
+
isCompletingOAuth: true,
|
331
|
+
authError: null,
|
332
|
+
});
|
333
|
+
try {
|
334
|
+
// If we don't have discovery data, re-discover it
|
335
|
+
if (!this.discovery) {
|
336
|
+
console.log('🔍 [OAuthHelper] Re-discovering OAuth configuration for callback');
|
337
|
+
await this.discoverOAuthConfig(serverUrl);
|
338
|
+
}
|
339
|
+
// Only re-register if we don't have a client registration for this server
|
340
|
+
if (!this.clientRegistration || this.serverUrl !== serverUrl) {
|
341
|
+
console.log('🔐 [OAuthHelper] Re-registering client for callback');
|
342
|
+
await this.registerClient(serverUrl);
|
343
|
+
}
|
344
|
+
else {
|
345
|
+
console.log('🔄 [OAuthHelper] Using existing client registration for callback');
|
346
|
+
}
|
347
|
+
const tokenResponse = await this.exchangeCodeForToken(serverUrl, code);
|
348
|
+
this.setState({
|
349
|
+
isAuthenticating: false,
|
350
|
+
isAuthenticated: true,
|
351
|
+
isCompletingOAuth: false,
|
352
|
+
authError: null,
|
353
|
+
oauthTokens: tokenResponse,
|
354
|
+
});
|
355
|
+
console.log('✅ [OAuthHelper] OAuth flow completed successfully');
|
356
|
+
return tokenResponse;
|
357
|
+
}
|
358
|
+
catch (error) {
|
359
|
+
console.error('❌ [OAuthHelper] Failed to complete OAuth flow:', error);
|
360
|
+
this.setState({
|
361
|
+
isAuthenticating: false,
|
362
|
+
isCompletingOAuth: false,
|
363
|
+
authError: error instanceof Error ? error.message : 'Failed to complete authentication',
|
364
|
+
});
|
365
|
+
throw error;
|
366
|
+
}
|
367
|
+
}
|
368
|
+
/**
|
369
|
+
* Reset authentication state
|
370
|
+
*/
|
371
|
+
resetAuth() {
|
372
|
+
this.setState({
|
373
|
+
isRequired: false,
|
374
|
+
isAuthenticated: false,
|
375
|
+
isAuthenticating: false,
|
376
|
+
isCompletingOAuth: false,
|
377
|
+
authError: null,
|
378
|
+
oauthTokens: null,
|
379
|
+
});
|
380
|
+
// Clear stored client registration
|
381
|
+
this.clientRegistration = undefined;
|
382
|
+
this.serverUrl = undefined;
|
383
|
+
try {
|
384
|
+
localStorage.removeItem(this.storageKey);
|
385
|
+
console.log('🗑️ [OAuthHelper] Cleared stored client registration');
|
386
|
+
}
|
387
|
+
catch (error) {
|
388
|
+
console.warn('⚠️ [OAuthHelper] Failed to clear client registration:', error);
|
389
|
+
}
|
390
|
+
}
|
391
|
+
/**
|
392
|
+
* Set OAuth state (internal method)
|
393
|
+
*/
|
394
|
+
setState(newState) {
|
395
|
+
this.state = { ...this.state, ...newState };
|
396
|
+
}
|
397
|
+
/**
|
398
|
+
* Generate a random state parameter for CSRF protection
|
399
|
+
*/
|
400
|
+
generateState() {
|
401
|
+
return Math.random().toString(36).substring(2, 15)
|
402
|
+
+ Math.random().toString(36).substring(2, 15);
|
403
|
+
}
|
404
|
+
}
|
405
|
+
/**
|
406
|
+
* Linear-specific OAuth configuration
|
407
|
+
*/
|
408
|
+
export const LINEAR_OAUTH_CONFIG = {
|
409
|
+
// No clientId needed - will use dynamic client registration
|
410
|
+
redirectUri: typeof window !== 'undefined' ? window.location.origin + window.location.pathname : 'http://localhost:5174',
|
411
|
+
scope: 'read write',
|
412
|
+
clientName: 'MCP Use Example',
|
413
|
+
};
|
414
|
+
/**
|
415
|
+
* Helper function to create OAuth-enabled MCP configuration
|
416
|
+
*/
|
417
|
+
export function createOAuthMCPConfig(serverUrl, accessToken) {
|
418
|
+
return {
|
419
|
+
mcpServers: {
|
420
|
+
linear: {
|
421
|
+
url: serverUrl,
|
422
|
+
authToken: accessToken,
|
423
|
+
transport: 'sse',
|
424
|
+
},
|
425
|
+
},
|
426
|
+
};
|
427
|
+
}
|
package/package.json
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
{
|
2
2
|
"name": "mcp-use",
|
3
3
|
"type": "module",
|
4
|
-
"version": "0.
|
4
|
+
"version": "0.2.0",
|
5
5
|
"packageManager": "pnpm@10.6.1",
|
6
6
|
"description": "A utility library for integrating Model Context Protocol (MCP) with LangChain, Zod, and related tools. Provides helpers for schema conversion, event streaming, and SDK usage.",
|
7
7
|
"author": "mcp-use, Inc.",
|
@@ -30,6 +30,10 @@
|
|
30
30
|
".": {
|
31
31
|
"types": "./dist/index.d.ts",
|
32
32
|
"import": "./dist/index.js"
|
33
|
+
},
|
34
|
+
"./browser": {
|
35
|
+
"types": "./dist/src/browser.d.ts",
|
36
|
+
"import": "./dist/src/browser.js"
|
33
37
|
}
|
34
38
|
},
|
35
39
|
"main": "./dist/index.js",
|
@@ -103,6 +107,7 @@
|
|
103
107
|
"posthog-node": "^5.1.1",
|
104
108
|
"uuid": "^11.1.0",
|
105
109
|
"winston": "^3.17.0",
|
110
|
+
"winston-transport-browserconsole": "^1.0.5",
|
106
111
|
"ws": "^8.18.2",
|
107
112
|
"zod": "^3.25.48",
|
108
113
|
"zod-to-json-schema": "^3.24.6"
|
@@ -1 +0,0 @@
|
|
1
|
-
{"version":3,"file":"add_server_tool.d.ts","sourceRoot":"","sources":["../../examples/add_server_tool.ts"],"names":[],"mappings":"AAAA;;;;;GAKG"}
|
@@ -1,79 +0,0 @@
|
|
1
|
-
/**
|
2
|
-
* Dynamic server management example for mcp-use.
|
3
|
-
*
|
4
|
-
* This example demonstrates how to equip an MCPAgent with a tool
|
5
|
-
* to dynamically add and connect to MCP servers during a run.
|
6
|
-
*/
|
7
|
-
import { ChatOpenAI } from '@langchain/openai';
|
8
|
-
import { config } from 'dotenv';
|
9
|
-
import { MCPAgent, MCPClient } from '../index.js';
|
10
|
-
import { LangChainAdapter } from '../src/adapters/langchain_adapter.js';
|
11
|
-
import { ServerManager } from '../src/managers/server_manager.js';
|
12
|
-
import { AddMCPServerFromConfigTool } from '../src/managers/tools/add_server_from_config.js';
|
13
|
-
// Load environment variables from .env file
|
14
|
-
config();
|
15
|
-
async function main() {
|
16
|
-
// Create an empty MCPClient. It has no servers to start with.
|
17
|
-
const client = new MCPClient();
|
18
|
-
// The LLM to power the agent
|
19
|
-
const llm = new ChatOpenAI({ model: 'gpt-4o', temperature: 0 });
|
20
|
-
const serverManager = new ServerManager(client, new LangChainAdapter());
|
21
|
-
serverManager.setManagementTools([new AddMCPServerFromConfigTool(serverManager)]);
|
22
|
-
// Create the agent, enabling the ServerManager
|
23
|
-
const agent = new MCPAgent({
|
24
|
-
llm,
|
25
|
-
client,
|
26
|
-
maxSteps: 30,
|
27
|
-
autoInitialize: true,
|
28
|
-
useServerManager: true,
|
29
|
-
serverManagerFactory: () => serverManager,
|
30
|
-
});
|
31
|
-
// Define the server configuration that the agent will be asked to add.
|
32
|
-
const serverConfigA = {
|
33
|
-
command: 'npx',
|
34
|
-
args: ['@playwright/mcp@latest', '--headless'],
|
35
|
-
env: {
|
36
|
-
DISPLAY: ':1',
|
37
|
-
},
|
38
|
-
};
|
39
|
-
const serverConfigB = {
|
40
|
-
command: 'npx',
|
41
|
-
args: ['-y', '@openbnb/mcp-server-airbnb', '--ignore-robots-txt'],
|
42
|
-
};
|
43
|
-
// We'll pass the config as a JSON string in the prompt.
|
44
|
-
const serverConfigStringA = JSON.stringify(serverConfigA, null, 2);
|
45
|
-
const serverConfigStringB = JSON.stringify(serverConfigB, null, 2);
|
46
|
-
const query = `I need to browse the web. To do this, please add and connect to a new MCP server for Playwright.
|
47
|
-
The server name is 'playwright' and its configuration is:
|
48
|
-
\`\`\`json
|
49
|
-
${serverConfigStringA}
|
50
|
-
\`\`\`
|
51
|
-
Once the server is ready, navigate to https://github.com/mcp-use/mcp-use, give a star to the project, and then provide a concise summary of the project's README.
|
52
|
-
|
53
|
-
Then, please add and connect to a new MCP server for Airbnb.
|
54
|
-
The server name is 'airbnb' and its configuration is:
|
55
|
-
\`\`\`json
|
56
|
-
${serverConfigStringB}
|
57
|
-
\`\`\`
|
58
|
-
and give me a house in the location of the company mcp-use.
|
59
|
-
`;
|
60
|
-
// Run the agent. We call `stream()` to get the async generator.
|
61
|
-
const stepIterator = agent.stream(query);
|
62
|
-
let result;
|
63
|
-
while (true) {
|
64
|
-
const { done, value } = await stepIterator.next();
|
65
|
-
if (done) {
|
66
|
-
result = value;
|
67
|
-
break;
|
68
|
-
}
|
69
|
-
// You can inspect the intermediate steps here.
|
70
|
-
console.log('--- Agent Step ---');
|
71
|
-
console.dir(value, { depth: 4 });
|
72
|
-
}
|
73
|
-
console.log(`\n✅ Final Result:\n${result}`);
|
74
|
-
// Clean up the session created by the agent
|
75
|
-
await client.closeAllSessions();
|
76
|
-
}
|
77
|
-
if (import.meta.url === `file://${process.argv[1]}`) {
|
78
|
-
main().catch(console.error);
|
79
|
-
}
|