Skip to main content
Glama
roleShift.js7.22 kB
/** * RoleShift Protocol * * Enables bidirectional communication where server can request client actions. * Uses MCP sampling (SEP-1577) and elicitation (SEP-1036) primitives. * * The "telepathic link" - server and client roles are negotiable per operation. */ const { Signal, SignalType } = require('./signal'); const crypto = require('crypto'); /** * RoleShift modes */ const RoleMode = { SERVER: 'server', // Normal: server responds to client requests CLIENT: 'client', // Reversed: server requests client actions PEER: 'peer' // Symmetric: either can initiate }; /** * Pending request tracker */ class RequestTracker { constructor() { this.pending = new Map(); this.timeout = 60000; // 1 minute default } /** * Create a pending request */ create(type, context = {}) { const id = `req_${crypto.randomUUID().slice(0, 8)}`; const request = { id, type, context, createdAt: Date.now(), resolved: false, result: null }; this.pending.set(id, request); // Auto-cleanup after timeout setTimeout(() => { if (this.pending.has(id) && !this.pending.get(id).resolved) { this.pending.delete(id); } }, this.timeout); return request; } /** * Resolve a pending request */ resolve(id, result) { const request = this.pending.get(id); if (!request) { throw new Error(`Request not found: ${id}`); } request.resolved = true; request.result = result; request.resolvedAt = Date.now(); return request; } /** * Get pending request */ get(id) { return this.pending.get(id); } /** * List pending requests */ list() { return Array.from(this.pending.values()).filter(r => !r.resolved); } } /** * RoleShift Protocol Handler */ class RoleShiftProtocol { constructor(options = {}) { this.mode = options.mode ?? RoleMode.SERVER; this.sampling = options.sampling ?? null; // MCP sampling capability this.elicitation = options.elicitation ?? null; // MCP elicitation capability this.tracker = new RequestTracker(); this.handlers = new Map(); } /** * Set MCP capabilities (called when server initializes) */ setCapabilities(capabilities) { if (capabilities.sampling) { this.sampling = capabilities.sampling; } if (capabilities.elicitation) { this.elicitation = capabilities.elicitation; } } /** * Check if role shift is available */ get canShift() { return this.sampling !== null || this.elicitation !== null; } /** * Request synthesis from client (server → client) * * Uses MCP sampling to ask the client LLM to process something. * Returns a Signal with the response. */ async requestSynthesis(prompt, options = {}) { if (!this.sampling) { throw new Error('Sampling capability not available'); } const request = this.tracker.create('synthesis', { prompt }); const messages = [ { role: 'user', content: prompt } ]; if (options.systemPrompt) { messages.unshift({ role: 'system', content: options.systemPrompt }); } try { const response = await this.sampling.createMessage({ messages, maxTokens: options.maxTokens ?? 4096, includeContext: options.includeContext ?? 'thisServer' }); this.tracker.resolve(request.id, response); return Signal.response( response.content?.text ?? response.content, response.model ?? 'client', 1.0, { tags: ['synthesis', 'role-shift'] } ); } catch (error) { return Signal.error( `Synthesis request failed: ${error.message}`, 'roleshift' ); } } /** * Request clarification from user (server → user via client) * * Uses MCP elicitation to ask the user for input. * Returns the user's response. */ async requestClarification(question, schema = null) { if (!this.elicitation) { throw new Error('Elicitation capability not available'); } const request = this.tracker.create('clarification', { question, schema }); try { const response = await this.elicitation.elicit({ message: question, requestedSchema: schema }); this.tracker.resolve(request.id, response); if (response.action === 'cancel') { return Signal.response( null, 'user', 0, { tags: ['cancelled', 'role-shift'] } ); } return Signal.response( response.content, 'user', 1.0, { tags: ['clarification', 'role-shift'] } ); } catch (error) { return Signal.error( `Clarification request failed: ${error.message}`, 'roleshift' ); } } /** * Request tool execution via sampling * * Asks the client to execute a tool and return results. */ async requestToolExecution(toolName, args, context = '') { if (!this.sampling) { throw new Error('Sampling capability not available'); } const prompt = `Please execute the following tool and return the results: Tool: ${toolName} Arguments: ${JSON.stringify(args, null, 2)} ${context ? `Context: ${context}` : ''} Return only the tool result, no additional commentary.`; return this.requestSynthesis(prompt, { maxTokens: 2048, includeContext: 'allServers' }); } /** * Request multi-model consensus * * Asks multiple models to respond and calculates consensus. */ async requestConsensus(prompt, models = []) { if (!this.sampling) { throw new Error('Sampling capability not available'); } // If no specific models, let client decide const request = this.tracker.create('consensus', { prompt, models }); const synthesisPrompt = `Multiple perspectives needed. Please provide your analysis: ${prompt} Respond with your assessment and confidence level (0-1).`; const response = await this.requestSynthesis(synthesisPrompt); // Single response for now - multi-model requires client support return { signals: [response], consensus: response.payload, confidence: response.confidence, method: 'single-sample' }; } /** * Handle incoming role-shift response (for async flows) */ handleResponse(requestId, response) { return this.tracker.resolve(requestId, response); } /** * Get pending requests */ getPendingRequests() { return this.tracker.list(); } /** * Register a handler for incoming shifted requests */ onRequest(type, handler) { this.handlers.set(type, handler); } /** * Process an incoming shifted request */ async processIncoming(type, payload) { const handler = this.handlers.get(type); if (!handler) { return Signal.error(`No handler for request type: ${type}`, 'roleshift'); } return handler(payload); } } /** * Create protocol instance with default configuration */ function createRoleShift(options = {}) { return new RoleShiftProtocol(options); } module.exports = { RoleMode, RoleShiftProtocol, RequestTracker, createRoleShift };

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/wheattoast11/openrouter-deep-research-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server