avancement planning
This commit is contained in:
Generated
Vendored
+101
-62
@@ -9,71 +9,30 @@ const index_js_1 = require("../../client/index.js");
|
||||
const streamableHttp_js_1 = require("../../client/streamableHttp.js");
|
||||
const types_js_1 = require("../../types.js");
|
||||
const auth_js_1 = require("../../client/auth.js");
|
||||
const simpleOAuthClientProvider_js_1 = require("./simpleOAuthClientProvider.js");
|
||||
// Configuration
|
||||
const DEFAULT_SERVER_URL = 'http://localhost:3000/mcp';
|
||||
const CALLBACK_PORT = 8090; // Use different port than auth server (3001)
|
||||
const CALLBACK_URL = `http://localhost:${CALLBACK_PORT}/callback`;
|
||||
/**
|
||||
* In-memory OAuth client provider for demonstration purposes
|
||||
* In production, you should persist tokens securely
|
||||
*/
|
||||
class InMemoryOAuthClientProvider {
|
||||
constructor(_redirectUrl, _clientMetadata, onRedirect) {
|
||||
this._redirectUrl = _redirectUrl;
|
||||
this._clientMetadata = _clientMetadata;
|
||||
this._onRedirect = onRedirect || ((url) => {
|
||||
console.log(`Redirect to: ${url.toString()}`);
|
||||
});
|
||||
}
|
||||
get redirectUrl() {
|
||||
return this._redirectUrl;
|
||||
}
|
||||
get clientMetadata() {
|
||||
return this._clientMetadata;
|
||||
}
|
||||
clientInformation() {
|
||||
return this._clientInformation;
|
||||
}
|
||||
saveClientInformation(clientInformation) {
|
||||
this._clientInformation = clientInformation;
|
||||
}
|
||||
tokens() {
|
||||
return this._tokens;
|
||||
}
|
||||
saveTokens(tokens) {
|
||||
this._tokens = tokens;
|
||||
}
|
||||
redirectToAuthorization(authorizationUrl) {
|
||||
this._onRedirect(authorizationUrl);
|
||||
}
|
||||
saveCodeVerifier(codeVerifier) {
|
||||
this._codeVerifier = codeVerifier;
|
||||
}
|
||||
codeVerifier() {
|
||||
if (!this._codeVerifier) {
|
||||
throw new Error('No code verifier saved');
|
||||
}
|
||||
return this._codeVerifier;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Interactive MCP client with OAuth authentication
|
||||
* Demonstrates the complete OAuth flow with browser-based authorization
|
||||
*/
|
||||
class InteractiveOAuthClient {
|
||||
constructor(serverUrl) {
|
||||
constructor(serverUrl, clientMetadataUrl) {
|
||||
this.serverUrl = serverUrl;
|
||||
this.clientMetadataUrl = clientMetadataUrl;
|
||||
this.client = null;
|
||||
this.rl = (0, node_readline_1.createInterface)({
|
||||
input: process.stdin,
|
||||
output: process.stdout,
|
||||
output: process.stdout
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Prompts user for input via readline
|
||||
*/
|
||||
async question(query) {
|
||||
return new Promise((resolve) => {
|
||||
return new Promise(resolve => {
|
||||
this.rl.question(query, resolve);
|
||||
});
|
||||
}
|
||||
@@ -83,7 +42,7 @@ class InteractiveOAuthClient {
|
||||
async openBrowser(url) {
|
||||
console.log(`🌐 Opening browser for authorization: ${url}`);
|
||||
const command = `open "${url}"`;
|
||||
(0, node_child_process_1.exec)(command, (error) => {
|
||||
(0, node_child_process_1.exec)(command, error => {
|
||||
if (error) {
|
||||
console.error(`Failed to open browser: ${error.message}`);
|
||||
console.log(`Please manually open: ${url}`);
|
||||
@@ -111,7 +70,7 @@ class InteractiveOAuthClient {
|
||||
const code = parsedUrl.searchParams.get('code');
|
||||
const error = parsedUrl.searchParams.get('error');
|
||||
if (code) {
|
||||
console.log(`✅ Authorization code received: ${code === null || code === void 0 ? void 0 : code.substring(0, 10)}...`);
|
||||
console.log(`✅ Authorization code received: ${code?.substring(0, 10)}...`);
|
||||
res.writeHead(200, { 'Content-Type': 'text/html' });
|
||||
res.end(`
|
||||
<html>
|
||||
@@ -188,20 +147,19 @@ class InteractiveOAuthClient {
|
||||
redirect_uris: [CALLBACK_URL],
|
||||
grant_types: ['authorization_code', 'refresh_token'],
|
||||
response_types: ['code'],
|
||||
token_endpoint_auth_method: 'client_secret_post',
|
||||
scope: 'mcp:tools'
|
||||
token_endpoint_auth_method: 'client_secret_post'
|
||||
};
|
||||
console.log('🔐 Creating OAuth provider...');
|
||||
const oauthProvider = new InMemoryOAuthClientProvider(CALLBACK_URL, clientMetadata, (redirectUrl) => {
|
||||
const oauthProvider = new simpleOAuthClientProvider_js_1.InMemoryOAuthClientProvider(CALLBACK_URL, clientMetadata, (redirectUrl) => {
|
||||
console.log(`📌 OAuth redirect handler called - opening browser`);
|
||||
console.log(`Opening browser to: ${redirectUrl.toString()}`);
|
||||
this.openBrowser(redirectUrl.toString());
|
||||
});
|
||||
}, this.clientMetadataUrl);
|
||||
console.log('🔐 OAuth provider created');
|
||||
console.log('👤 Creating MCP client...');
|
||||
this.client = new index_js_1.Client({
|
||||
name: 'simple-oauth-client',
|
||||
version: '1.0.0',
|
||||
version: '1.0.0'
|
||||
}, { capabilities: {} });
|
||||
console.log('👤 Client created');
|
||||
console.log('🔐 Starting OAuth flow...');
|
||||
@@ -217,6 +175,7 @@ class InteractiveOAuthClient {
|
||||
console.log('Commands:');
|
||||
console.log(' list - List available tools');
|
||||
console.log(' call <tool_name> [args] - Call a tool');
|
||||
console.log(' stream <tool_name> [args] - Call a tool with streaming (shows task status)');
|
||||
console.log(' quit - Exit the client');
|
||||
console.log();
|
||||
while (true) {
|
||||
@@ -236,8 +195,11 @@ class InteractiveOAuthClient {
|
||||
else if (command.startsWith('call ')) {
|
||||
await this.handleCallTool(command);
|
||||
}
|
||||
else if (command.startsWith('stream ')) {
|
||||
await this.handleStreamTool(command);
|
||||
}
|
||||
else {
|
||||
console.log('❌ Unknown command. Try \'list\', \'call <tool_name>\', or \'quit\'');
|
||||
console.log("❌ Unknown command. Try 'list', 'call <tool_name>', 'stream <tool_name>', or 'quit'");
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
@@ -257,7 +219,7 @@ class InteractiveOAuthClient {
|
||||
try {
|
||||
const request = {
|
||||
method: 'tools/list',
|
||||
params: {},
|
||||
params: {}
|
||||
};
|
||||
const result = await this.client.request(request, types_js_1.ListToolsResultSchema);
|
||||
if (result.tools && result.tools.length > 0) {
|
||||
@@ -292,7 +254,7 @@ class InteractiveOAuthClient {
|
||||
try {
|
||||
toolArgs = JSON.parse(argsString);
|
||||
}
|
||||
catch (_a) {
|
||||
catch {
|
||||
console.log('❌ Invalid arguments format (expected JSON)');
|
||||
return;
|
||||
}
|
||||
@@ -309,13 +271,13 @@ class InteractiveOAuthClient {
|
||||
method: 'tools/call',
|
||||
params: {
|
||||
name: toolName,
|
||||
arguments: toolArgs,
|
||||
},
|
||||
arguments: toolArgs
|
||||
}
|
||||
};
|
||||
const result = await this.client.request(request, types_js_1.CallToolResultSchema);
|
||||
console.log(`\n🔧 Tool '${toolName}' result:`);
|
||||
if (result.content) {
|
||||
result.content.forEach((content) => {
|
||||
result.content.forEach(content => {
|
||||
if (content.type === 'text') {
|
||||
console.log(content.text);
|
||||
}
|
||||
@@ -332,6 +294,78 @@ class InteractiveOAuthClient {
|
||||
console.error(`❌ Failed to call tool '${toolName}':`, error);
|
||||
}
|
||||
}
|
||||
async handleStreamTool(command) {
|
||||
const parts = command.split(/\s+/);
|
||||
const toolName = parts[1];
|
||||
if (!toolName) {
|
||||
console.log('❌ Please specify a tool name');
|
||||
return;
|
||||
}
|
||||
// Parse arguments (simple JSON-like format)
|
||||
let toolArgs = {};
|
||||
if (parts.length > 2) {
|
||||
const argsString = parts.slice(2).join(' ');
|
||||
try {
|
||||
toolArgs = JSON.parse(argsString);
|
||||
}
|
||||
catch {
|
||||
console.log('❌ Invalid arguments format (expected JSON)');
|
||||
return;
|
||||
}
|
||||
}
|
||||
await this.streamTool(toolName, toolArgs);
|
||||
}
|
||||
async streamTool(toolName, toolArgs) {
|
||||
if (!this.client) {
|
||||
console.log('❌ Not connected to server');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
// Using the experimental tasks API - WARNING: may change without notice
|
||||
console.log(`\n🔧 Streaming tool '${toolName}'...`);
|
||||
const stream = this.client.experimental.tasks.callToolStream({
|
||||
name: toolName,
|
||||
arguments: toolArgs
|
||||
}, types_js_1.CallToolResultSchema, {
|
||||
task: {
|
||||
taskId: `task-${Date.now()}`,
|
||||
ttl: 60000
|
||||
}
|
||||
});
|
||||
// Iterate through all messages yielded by the generator
|
||||
for await (const message of stream) {
|
||||
switch (message.type) {
|
||||
case 'taskCreated':
|
||||
console.log(`✓ Task created: ${message.task.taskId}`);
|
||||
break;
|
||||
case 'taskStatus':
|
||||
console.log(`⟳ Status: ${message.task.status}`);
|
||||
if (message.task.statusMessage) {
|
||||
console.log(` ${message.task.statusMessage}`);
|
||||
}
|
||||
break;
|
||||
case 'result':
|
||||
console.log('✓ Completed!');
|
||||
message.result.content.forEach(content => {
|
||||
if (content.type === 'text') {
|
||||
console.log(content.text);
|
||||
}
|
||||
else {
|
||||
console.log(content);
|
||||
}
|
||||
});
|
||||
break;
|
||||
case 'error':
|
||||
console.log('✗ Error:');
|
||||
console.log(` ${message.error.message}`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.error(`❌ Failed to stream tool '${toolName}':`, error);
|
||||
}
|
||||
}
|
||||
close() {
|
||||
this.rl.close();
|
||||
if (this.client) {
|
||||
@@ -344,11 +378,16 @@ class InteractiveOAuthClient {
|
||||
* Main entry point
|
||||
*/
|
||||
async function main() {
|
||||
const serverUrl = process.env.MCP_SERVER_URL || DEFAULT_SERVER_URL;
|
||||
const args = process.argv.slice(2);
|
||||
const serverUrl = args[0] || DEFAULT_SERVER_URL;
|
||||
const clientMetadataUrl = args[1];
|
||||
console.log('🚀 Simple MCP OAuth Client');
|
||||
console.log(`Connecting to: ${serverUrl}`);
|
||||
if (clientMetadataUrl) {
|
||||
console.log(`Client Metadata URL: ${clientMetadataUrl}`);
|
||||
}
|
||||
console.log();
|
||||
const client = new InteractiveOAuthClient(serverUrl);
|
||||
const client = new InteractiveOAuthClient(serverUrl, clientMetadataUrl);
|
||||
// Handle graceful shutdown
|
||||
process.on('SIGINT', () => {
|
||||
console.log('\n\n👋 Goodbye!');
|
||||
@@ -367,7 +406,7 @@ async function main() {
|
||||
}
|
||||
}
|
||||
// Run if this file is executed directly
|
||||
main().catch((error) => {
|
||||
main().catch(error => {
|
||||
console.error('Unhandled error:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user