No tools loaded. Click "Load Tools" to get started.
{tool.name}
{tool.server && (
-
- from {tool.server}
+ borderRadius: '3px',
+ }}
+ >
+ from
+ {' '}
+ {tool.server}
)}
-
+
{tool.description && (
{tool.description}
)}
-
+
{tool.inputSchema && (
Input Schema
-
+ fontSize: '0.9em',
+ }}
+ >
{JSON.stringify(tool.inputSchema, null, 2)}
@@ -340,26 +376,42 @@ const ReactExample: React.FC = () => {
return (
-
-
+ borderRadius: '4px',
+ }}
+ >
🔐 OAuth Authentication Required
- This example demonstrates OAuth authentication with Linear's MCP server.
+ This example demonstrates OAuth authentication with Linear's MCP server.
Click "Authenticate with Linear" to start the OAuth flow.
- Note: You'll need to register this application with Linear to get a proper client ID.
+ Note:
+ {' '}
+ You'll need to register this application with Linear to get a proper client ID.
For this demo, we're using a placeholder client ID.
- Browser limitations: Stdio connections (using command and args)
- are not supported in the browser. Use ws_url for WebSocket or url for HTTP/SSE connections.
+ Browser limitations:
+ {' '}
+ Stdio connections (using
+ command
+ {' '}
+ and
+ args
+ )
+ are not supported in the browser. Use
+ ws_url
+ {' '}
+ for WebSocket or
+ url
+ {' '}
+ for HTTP/SSE connections.
diff --git a/examples/react/serve.js b/examples/react/serve.js
index aec09416..9d723118 100644
--- a/examples/react/serve.js
+++ b/examples/react/serve.js
@@ -1,10 +1,9 @@
#!/usr/bin/env node
-import { createServer } from 'http'
-import { readFileSync, existsSync } from 'fs'
-import { join, extname } from 'path'
-import { fileURLToPath } from 'url'
-import { dirname } from 'path'
+import { existsSync, readFileSync } from 'node:fs'
+import { createServer } from 'node:http'
+import { dirname, extname, join } from 'node:path'
+import { fileURLToPath } from 'node:url'
const __filename = fileURLToPath(import.meta.url)
const __dirname = dirname(__filename)
@@ -21,12 +20,12 @@ const mimeTypes = {
'.jpg': 'image/jpeg',
'.gif': 'image/gif',
'.svg': 'image/svg+xml',
- '.ico': 'image/x-icon'
+ '.ico': 'image/x-icon',
}
const server = createServer((req, res) => {
- let filePath = join(distDir, req.url === '/' ? 'react_example.html' : req.url)
-
+ const filePath = join(distDir, req.url === '/' ? 'react_example.html' : req.url)
+
// Security check - prevent directory traversal
if (!filePath.startsWith(distDir)) {
res.writeHead(403)
@@ -44,10 +43,11 @@ const server = createServer((req, res) => {
const content = readFileSync(filePath)
const ext = extname(filePath)
const contentType = mimeTypes[ext] || 'application/octet-stream'
-
+
res.writeHead(200, { 'Content-Type': contentType })
res.end(content)
- } catch (error) {
+ }
+ catch {
res.writeHead(500)
res.end('Internal Server Error')
}
diff --git a/examples/react/tsconfig.json b/examples/react/tsconfig.json
index 2c731fae..17ac59b0 100644
--- a/examples/react/tsconfig.json
+++ b/examples/react/tsconfig.json
@@ -1,25 +1,25 @@
{
"compilerOptions": {
"target": "ES2020",
- "useDefineForClassFields": true,
+ "jsx": "react-jsx",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
+ "useDefineForClassFields": true,
"module": "ESNext",
- "skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
- "allowImportingTsExtensions": true,
"resolveJsonModule": true,
- "isolatedModules": true,
- "noEmit": true,
- "jsx": "react-jsx",
+ "allowImportingTsExtensions": true,
/* Linting */
"strict": true,
+ "noFallthroughCasesInSwitch": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
- "noFallthroughCasesInSwitch": true
+ "noEmit": true,
+ "isolatedModules": true,
+ "skipLibCheck": true
},
- "include": ["**/*.ts", "**/*.tsx"],
- "references": [{ "path": "./tsconfig.node.json" }]
+ "references": [{ "path": "./tsconfig.node.json" }],
+ "include": ["**/*.ts", "**/*.tsx"]
}
diff --git a/examples/react/tsconfig.node.json b/examples/react/tsconfig.node.json
index 42872c59..dde08948 100644
--- a/examples/react/tsconfig.node.json
+++ b/examples/react/tsconfig.node.json
@@ -1,10 +1,10 @@
{
"compilerOptions": {
"composite": true,
- "skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "bundler",
- "allowSyntheticDefaultImports": true
+ "allowSyntheticDefaultImports": true,
+ "skipLibCheck": true
},
"include": ["vite.config.ts"]
}
diff --git a/examples/react/vite.config.ts b/examples/react/vite.config.ts
index eed0d196..7de8b540 100644
--- a/examples/react/vite.config.ts
+++ b/examples/react/vite.config.ts
@@ -1,25 +1,25 @@
-import { defineConfig } from 'vite'
-import react from '@vitejs/plugin-react'
-import { resolve } from 'path'
+import { resolve } from 'node:path'
import { NodeGlobalsPolyfillPlugin } from '@esbuild-plugins/node-globals-polyfill'
import { NodeModulesPolyfillPlugin } from '@esbuild-plugins/node-modules-polyfill'
+import react from '@vitejs/plugin-react'
+import { defineConfig } from 'vite'
export default defineConfig({
plugins: [react()],
build: {
outDir: 'dist',
commonjsOptions: {
- transformMixedEsModules: true
- }
+ transformMixedEsModules: true,
+ },
},
resolve: {
alias: {
'mcp-use/browser': resolve(__dirname, '../../dist/src/browser.js'),
'mcp-use': resolve(__dirname, '../../dist/src'),
- }
+ },
},
define: {
- global: 'globalThis',
+ 'global': 'globalThis',
'process.env.DEBUG': 'undefined',
'process.env.MCP_USE_ANONYMIZED_TELEMETRY': 'undefined',
'process.env.MCP_USE_TELEMETRY_SOURCE': 'undefined',
@@ -32,15 +32,15 @@ export default defineConfig({
include: ['react', 'react-dom'],
esbuildOptions: {
define: {
- global: 'globalThis'
+ global: 'globalThis',
},
plugins: [
NodeGlobalsPolyfillPlugin({
process: true,
- buffer: true
+ buffer: true,
}),
- NodeModulesPolyfillPlugin()
- ]
- }
- }
+ NodeModulesPolyfillPlugin(),
+ ],
+ },
+ },
})
diff --git a/package-lock.json b/package-lock.json
index 4fdadb1c..14e059a4 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -22,6 +22,7 @@
"posthog-node": "^5.1.1",
"uuid": "^11.1.0",
"winston": "^3.17.0",
+ "winston-transport-browserconsole": "^1.0.5",
"ws": "^8.18.2",
"zod": "^3.25.48",
"zod-to-json-schema": "^3.24.6"
@@ -8842,6 +8843,16 @@
"node": ">= 12.0.0"
}
},
+ "node_modules/winston-transport-browserconsole": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/winston-transport-browserconsole/-/winston-transport-browserconsole-1.0.5.tgz",
+ "integrity": "sha512-BFZDvknATAmsqaRY3WatB2S0eEb0ziwqBYIv+H0Fnu9oe7GnPUVQzEJC1frmLdNsfEkfnyR1PCNDBAFtZE3SuQ==",
+ "license": "ISC",
+ "dependencies": {
+ "winston": "^3.2.1",
+ "winston-transport": "^4.3.0"
+ }
+ },
"node_modules/winston/node_modules/is-stream": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
diff --git a/src/browser.ts b/src/browser.ts
index 91cb6a5e..ce11949a 100644
--- a/src/browser.ts
+++ b/src/browser.ts
@@ -1,27 +1,27 @@
/**
* Browser-compatible exports for mcp-use
- *
+ *
* This module provides browser-safe versions of mcp-use components
* that avoid Node.js-specific dependencies (like fs, path for file operations).
- *
+ *
* The actual MCP protocol connectors (WebSocket, HTTP/SSE) work fine in browsers.
*/
import type { BaseConnector } from './connectors/base.js'
+import { BaseMCPClient } from './client/base.js'
import { HttpConnector } from './connectors/http.js'
import { WebSocketConnector } from './connectors/websocket.js'
-import { BaseMCPClient } from './client/base.js'
import { logger } from './logging.js'
import { MCPSession } from './session.js'
/**
* Browser-compatible MCP Client
- *
+ *
* Unlike the Node.js version, this doesn't support:
* - Loading config from files (loadConfigFile)
* - Saving config to files (saveConfig)
* - StdioConnector (requires child_process)
- *
+ *
* Supported connectors:
* - WebSocketConnector: Connect to MCP servers via WebSocket
* - HttpConnector: Connect to MCP servers via HTTP/SSE
@@ -37,11 +37,11 @@ export class MCPClient extends BaseMCPClient {
/**
* Create a connector from server configuration (browser-safe version)
- *
+ *
* Supports:
* - WebSocket connections: { ws_url: "ws://..." }
* - HTTP connections: { url: "http://..." }
- *
+ *
* Does NOT support:
* - Stdio connections: { command: "...", args: [...] }
*/
@@ -87,9 +87,9 @@ export { MCPSession }
export { logger }
// Re-export OAuth helper for browser authentication
-export { OAuthHelper, LINEAR_OAUTH_CONFIG, createOAuthMCPConfig } from './oauth-helper.js'
-export type { OAuthConfig, OAuthDiscovery, OAuthResult, OAuthState, ClientRegistration } from './oauth-helper.js'
+export { createOAuthMCPConfig, LINEAR_OAUTH_CONFIG, OAuthHelper } from './oauth-helper.js'
+export type { ClientRegistration, OAuthConfig, OAuthDiscovery, OAuthResult, OAuthState } from './oauth-helper.js'
// Re-export types that are safe for browser
-export type { BaseMessage, HumanMessage, SystemMessage, AIMessage, ToolMessage } from '@langchain/core/messages'
-export type { StreamEvent } from '@langchain/core/tracers/log_stream'
\ No newline at end of file
+export type { AIMessage, BaseMessage, HumanMessage, SystemMessage, ToolMessage } from '@langchain/core/messages'
+export type { StreamEvent } from '@langchain/core/tracers/log_stream'
diff --git a/src/client.ts b/src/client.ts
index 3c7c1539..17eafa85 100644
--- a/src/client.ts
+++ b/src/client.ts
@@ -1,12 +1,12 @@
+import type { BaseConnector } from './connectors/base.js'
import fs from 'node:fs'
import path from 'node:path'
-import type { BaseConnector } from './connectors/base.js'
-import { createConnectorFromConfig, loadConfigFile } from './config.js'
import { BaseMCPClient } from './client/base.js'
+import { createConnectorFromConfig, loadConfigFile } from './config.js'
/**
* Node.js-specific MCPClient implementation
- *
+ *
* Extends the base client with Node.js-specific features like:
* - File system operations (saveConfig)
* - Config file loading (fromConfigFile)
diff --git a/src/client/base.ts b/src/client/base.ts
index 2ca697ac..c8706867 100644
--- a/src/client/base.ts
+++ b/src/client/base.ts
@@ -4,7 +4,7 @@ import { MCPSession } from '../session.js'
/**
* Base MCPClient class with shared functionality
- *
+ *
* This class contains all the common logic that works in both Node.js and browser environments.
* Platform-specific implementations should extend this class and override methods as needed.
*/
@@ -70,7 +70,7 @@ export abstract class BaseMCPClient {
const connector = this.createConnectorFromConfig(servers[serverName])
const session = new MCPSession(connector)
-
+
if (autoInitialize) {
await session.initialize()
}
diff --git a/src/logging.ts b/src/logging.ts
index 7116fcd6..c858864e 100644
--- a/src/logging.ts
+++ b/src/logging.ts
@@ -9,7 +9,8 @@ async function getNodeModules() {
const fs = await import('node:fs')
const path = await import('node:path')
return { fs: fs.default, path: path.default }
- } catch {
+ }
+ catch {
return { fs: null, path: null }
}
}
@@ -97,31 +98,31 @@ class SimpleConsoleLogger {
info(message: string): void {
if (this.shouldLog('info')) {
- console.info(this.formatMessage('info', message)) // eslint-disable-line no-console
+ console.info(this.formatMessage('info', message))
}
}
debug(message: string): void {
if (this.shouldLog('debug')) {
- console.debug(this.formatMessage('debug', message)) // eslint-disable-line no-console
+ console.debug(this.formatMessage('debug', message))
}
}
http(message: string): void {
if (this.shouldLog('http')) {
- console.log(this.formatMessage('http', message)) // eslint-disable-line no-console
+ console.log(this.formatMessage('http', message))
}
}
verbose(message: string): void {
if (this.shouldLog('verbose')) {
- console.log(this.formatMessage('verbose', message)) // eslint-disable-line no-console
+ console.log(this.formatMessage('verbose', message))
}
}
silly(message: string): void {
if (this.shouldLog('silly')) {
- console.log(this.formatMessage('silly', message)) // eslint-disable-line no-console
+ console.log(this.formatMessage('silly', message))
}
}
@@ -225,7 +226,7 @@ export class Logger {
Object.values(this.simpleInstances).forEach((logger) => {
logger.level = resolvedLevel
})
-
+
return
}
winstonRoot.clear()
diff --git a/src/managers/server_manager.ts b/src/managers/server_manager.ts
index 84cf9be7..49878592 100644
--- a/src/managers/server_manager.ts
+++ b/src/managers/server_manager.ts
@@ -55,7 +55,7 @@ export class ServerManager {
}))
logger.info(`Server Manager State: [${context}]`)
- console.table(tableData) // eslint-disable-line no-console
+ console.table(tableData)
}
initialize(): void {
diff --git a/src/oauth-helper.ts b/src/oauth-helper.ts
index daca5c4c..0ced866b 100644
--- a/src/oauth-helper.ts
+++ b/src/oauth-helper.ts
@@ -1,6 +1,6 @@
/**
* OAuth helper for browser-based MCP authentication
- *
+ *
* This helper provides OAuth 2.0 authorization code flow support for MCP servers
* that require authentication, such as Linear's MCP server.
*/
@@ -69,7 +69,7 @@ export class OAuthHelper {
authError: null,
oauthTokens: null,
}
-
+
// Load persisted client registration
this.loadClientRegistration()
}
@@ -93,10 +93,11 @@ export class OAuthHelper {
this.serverUrl = data.serverUrl
console.log('🔄 [OAuthHelper] Loaded persisted client registration:', {
client_id: this.clientRegistration?.client_id,
- server: this.serverUrl
+ server: this.serverUrl,
})
}
- } catch (error) {
+ }
+ catch (error) {
console.warn('⚠️ [OAuthHelper] Failed to load client registration:', error)
}
}
@@ -108,11 +109,12 @@ export class OAuthHelper {
try {
const data = {
clientRegistration: this.clientRegistration,
- serverUrl: this.serverUrl
+ serverUrl: this.serverUrl,
}
localStorage.setItem(this.storageKey, JSON.stringify(data))
console.log('💾 [OAuthHelper] Saved client registration to localStorage')
- } catch (error) {
+ }
+ catch (error) {
console.warn('⚠️ [OAuthHelper] Failed to save client registration:', error)
}
}
@@ -149,12 +151,13 @@ export class OAuthHelper {
// Any other response (200, 404, 500, etc.) means no auth required
console.log('✅ [OAuthHelper] No authentication required for:', serverUrl)
return false
- } catch (error: any) {
+ }
+ catch (error: any) {
console.warn('⚠️ [OAuthHelper] Could not check auth requirement for:', serverUrl, error)
// Handle specific error types
- if (error.name === 'TypeError' &&
- (error.message?.includes('CORS') || error.message?.includes('Failed to fetch'))) {
+ if (error.name === 'TypeError'
+ && (error.message?.includes('CORS') || error.message?.includes('Failed to fetch'))) {
console.log('🔍 [OAuthHelper] CORS blocked direct check, using heuristics for:', serverUrl)
return this.checkAuthByHeuristics(serverUrl)
}
@@ -191,7 +194,7 @@ export class OAuthHelper {
// Known patterns that typically don't require auth (public MCP servers)
const noAuthPatterns = [
/localhost/i, // Local development
- /127\.0\.0\.1/i, // Local development
+ /127\.0\.0\.1/, // Local development
/\.local/i, // Local development
/mcp\..*\.com/i, // Generic MCP server pattern (often public)
]
@@ -224,14 +227,15 @@ export class OAuthHelper {
try {
const discoveryUrl = `${serverUrl}/.well-known/oauth-authorization-server`
const response = await fetch(discoveryUrl)
-
+
if (!response.ok) {
throw new Error(`OAuth discovery failed: ${response.status} ${response.statusText}`)
}
-
+
this.discovery = await response.json()
return this.discovery!
- } catch (error) {
+ }
+ catch (error) {
throw new Error(`Failed to discover OAuth configuration: ${error}`)
}
}
@@ -269,7 +273,7 @@ export class OAuthHelper {
headers: {
'Content-Type': 'application/json',
},
- body: JSON.stringify(registrationData)
+ body: JSON.stringify(registrationData),
})
if (!response.ok) {
@@ -280,14 +284,15 @@ export class OAuthHelper {
this.clientRegistration = await response.json()
this.serverUrl = serverUrl
this.saveClientRegistration()
-
+
console.log('✅ [OAuthHelper] Client registered successfully:', {
client_id: this.clientRegistration!.client_id,
client_secret: this.clientRegistration!.client_secret ? '***' : 'none',
})
return this.clientRegistration!
- } catch (error) {
+ }
+ catch (error) {
console.error('❌ [OAuthHelper] Client registration failed:', error)
throw new Error(`Failed to register OAuth client: ${error}`)
}
@@ -311,7 +316,7 @@ export class OAuthHelper {
response_type: 'code',
scope: this.config.scope || 'read',
state: this.config.state || this.generateState(),
- ...additionalParams
+ ...additionalParams,
})
return `${this.discovery.authorization_endpoint}?${params.toString()}`
@@ -321,9 +326,9 @@ export class OAuthHelper {
* Exchange authorization code for access token
*/
async exchangeCodeForToken(
- serverUrl: string,
- code: string,
- codeVerifier?: string
+ serverUrl: string,
+ code: string,
+ codeVerifier?: string,
): Promise
{
if (!this.discovery) {
throw new Error('OAuth discovery not performed. Call discoverOAuthConfig first.')
@@ -349,7 +354,7 @@ export class OAuthHelper {
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
- body: body.toString()
+ body: body.toString(),
})
if (!response.ok) {
@@ -363,7 +368,7 @@ export class OAuthHelper {
/**
* Handle OAuth callback and extract authorization code
*/
- handleCallback(): { code: string; state: string } | null {
+ handleCallback(): { code: string, state: string } | null {
const urlParams = new URLSearchParams(window.location.search)
const code = urlParams.get('code')
const state = urlParams.get('state')
@@ -393,20 +398,20 @@ export class OAuthHelper {
try {
// Step 1: Discover OAuth configuration
await this.discoverOAuthConfig(serverUrl)
-
+
// Step 2: Register client dynamically (if not already registered)
if (!this.clientRegistration) {
await this.registerClient(serverUrl)
}
-
+
// Step 3: Generate authorization URL
const authUrl = this.generateAuthUrl(serverUrl)
-
+
// Step 4: Open popup window for authentication
const authWindow = window.open(
authUrl,
'mcp-oauth',
- 'width=500,height=600,scrollbars=yes,resizable=yes,status=yes,location=yes'
+ 'width=500,height=600,scrollbars=yes,resizable=yes,status=yes,location=yes',
)
if (!authWindow) {
@@ -414,7 +419,8 @@ export class OAuthHelper {
}
console.log('✅ [OAuthHelper] OAuth popup opened successfully')
- } catch (error) {
+ }
+ catch (error) {
console.error('❌ [OAuthHelper] Failed to start OAuth flow:', error)
this.setState({
isAuthenticating: false,
@@ -444,12 +450,13 @@ export class OAuthHelper {
if (!this.clientRegistration || this.serverUrl !== serverUrl) {
console.log('🔐 [OAuthHelper] Re-registering client for callback')
await this.registerClient(serverUrl)
- } else {
+ }
+ else {
console.log('🔄 [OAuthHelper] Using existing client registration for callback')
}
const tokenResponse = await this.exchangeCodeForToken(serverUrl, code)
-
+
this.setState({
isAuthenticating: false,
isAuthenticated: true,
@@ -460,7 +467,8 @@ export class OAuthHelper {
console.log('✅ [OAuthHelper] OAuth flow completed successfully')
return tokenResponse
- } catch (error) {
+ }
+ catch (error) {
console.error('❌ [OAuthHelper] Failed to complete OAuth flow:', error)
this.setState({
isAuthenticating: false,
@@ -483,14 +491,15 @@ export class OAuthHelper {
authError: null,
oauthTokens: null,
})
-
+
// Clear stored client registration
this.clientRegistration = undefined
this.serverUrl = undefined
try {
localStorage.removeItem(this.storageKey)
console.log('🗑️ [OAuthHelper] Cleared stored client registration')
- } catch (error) {
+ }
+ catch (error) {
console.warn('⚠️ [OAuthHelper] Failed to clear client registration:', error)
}
}
@@ -506,8 +515,8 @@ export class OAuthHelper {
* Generate a random state parameter for CSRF protection
*/
private generateState(): string {
- return Math.random().toString(36).substring(2, 15) +
- Math.random().toString(36).substring(2, 15)
+ return Math.random().toString(36).substring(2, 15)
+ + Math.random().toString(36).substring(2, 15)
}
}
@@ -530,8 +539,8 @@ export function createOAuthMCPConfig(serverUrl: string, accessToken: string) {
linear: {
url: serverUrl,
authToken: accessToken,
- transport: 'sse'
- }
- }
+ transport: 'sse',
+ },
+ },
}
}