avancement planning

This commit is contained in:
2026-05-26 11:58:39 +02:00
parent 619a2b240a
commit 150b97cd2e
4892 changed files with 99214 additions and 429382 deletions
@@ -7,71 +7,30 @@ import { Client } from '../../client/index.js';
import { StreamableHTTPClientTransport } from '../../client/streamableHttp.js';
import { CallToolResultSchema, ListToolsResultSchema } from '../../types.js';
import { UnauthorizedError } from '../../client/auth.js';
import { InMemoryOAuthClientProvider } from './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 = 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);
});
}
@@ -81,7 +40,7 @@ class InteractiveOAuthClient {
async openBrowser(url) {
console.log(`🌐 Opening browser for authorization: ${url}`);
const command = `open "${url}"`;
exec(command, (error) => {
exec(command, error => {
if (error) {
console.error(`Failed to open browser: ${error.message}`);
console.log(`Please manually open: ${url}`);
@@ -109,7 +68,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>
@@ -186,20 +145,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) => {
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 Client({
name: 'simple-oauth-client',
version: '1.0.0',
version: '1.0.0'
}, { capabilities: {} });
console.log('👤 Client created');
console.log('🔐 Starting OAuth flow...');
@@ -215,6 +173,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) {
@@ -234,8 +193,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) {
@@ -255,7 +217,7 @@ class InteractiveOAuthClient {
try {
const request = {
method: 'tools/list',
params: {},
params: {}
};
const result = await this.client.request(request, ListToolsResultSchema);
if (result.tools && result.tools.length > 0) {
@@ -290,7 +252,7 @@ class InteractiveOAuthClient {
try {
toolArgs = JSON.parse(argsString);
}
catch (_a) {
catch {
console.log('❌ Invalid arguments format (expected JSON)');
return;
}
@@ -307,13 +269,13 @@ class InteractiveOAuthClient {
method: 'tools/call',
params: {
name: toolName,
arguments: toolArgs,
},
arguments: toolArgs
}
};
const result = await this.client.request(request, 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);
}
@@ -330,6 +292,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
}, 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) {
@@ -342,11 +376,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!');
@@ -365,7 +404,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);
});