Skip to main content
Glama

Frida MCP Server

by nonsleepr
session-tools.ts18.3 kB
/** * Session management MCP tools for Frida */ import * as frida from 'frida'; import { z } from 'zod'; import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { getDevice, generateSessionId, sleep, getSessionMessagesAsync } from '../helpers.js'; import { logger } from '../logger.js'; import { sessions, scripts, scriptMessages } from '../state.js'; import type { ScriptMessage, ExecutionReceipt } from '../types.js'; import { SCRIPT_EXECUTE_WRAPPER } from '../scripts.js'; /** * Register session management tools with the MCP server */ export function registerSessionTools(server: McpServer): void { // Create Interactive Session server.registerTool( 'create_interactive_session', { title: 'Create Interactive Session', description: 'Create an interactive session for dynamic instrumentation. Establishes a Frida session for injecting JavaScript, hooking functions, and monitoring the target process. The session persists until explicitly closed or the process terminates.', inputSchema: { process_id: z.number().describe('PID of target process'), device_id: z.string().optional().describe('Optional device ID or connection string (hostname:port or hostname)') }, outputSchema: { status: z.string(), process_id: z.number().optional(), session_id: z.string().optional(), message: z.string().optional(), error: z.string().optional() } }, async ({ process_id, device_id }: { process_id: number; device_id?: string }) => { try { logger.info(`Creating session for PID ${process_id}`); const device = await getDevice(device_id); const session = await device.attach(process_id); // Generate a unique session ID const sessionId = generateSessionId(process_id); logger.debug(`Generated session_id: ${sessionId}`); // Store the session sessions.set(sessionId, session); scripts.set(sessionId, []); scriptMessages.set(sessionId, []); logger.info(`Session ${sessionId} created successfully`); const result = { status: 'success', process_id, session_id: sessionId, message: `Interactive session created for process ${process_id}. Use execute_in_session to run JavaScript commands.` }; return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }], structuredContent: result }; } catch (error) { const result = { status: 'error', error: error instanceof Error ? error.message : String(error) }; return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }], structuredContent: result }; } } ); // Execute in Session server.registerTool( 'execute_in_session', { title: 'Execute in Session', description: 'Execute JavaScript code within an existing Frida session. keep_alive=false (default): Script runs once, results in initial_logs. keep_alive=true: Script persists for hooks, retrieve messages via frida://sessions/{session_id}/messages resource.', inputSchema: { session_id: z.string().describe('Session ID from create_interactive_session'), javascript_code: z.string().describe('JavaScript code to execute'), keep_alive: z.boolean().optional().default(false).describe('If true, script persists; if false, runs once') }, outputSchema: { status: z.string(), result: z.string().optional(), initial_logs: z.array(z.string()).optional(), error: z.string().optional(), stack: z.string().optional(), script_unloaded: z.boolean().optional(), message: z.string().optional(), info: z.string().optional() } }, async ({ session_id, javascript_code, keep_alive = false }: { session_id: string; javascript_code: string; keep_alive?: boolean; }) => { const session = sessions.get(session_id); if (!session) { const result = { status: 'error', error: `Session with ID ${session_id} not found` }; return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }], structuredContent: result }; } try { // Wrap the code using the template const wrappedCode = SCRIPT_EXECUTE_WRAPPER.replace('{code}', javascript_code); const script = await session.createScript(wrappedCode); // Capture messages from the initial execution const initialExecutionResults: any[] = []; const handleMessage = (message: frida.Message, _data: Buffer | null) => { if (message.type === 'send') { const payload = message.payload as ExecutionReceipt; if (payload.type === 'execution_receipt') { initialExecutionResults.push(payload); } } else if (message.type === 'error') { initialExecutionResults.push({ script_error: message.description, details: message }); } }; const handlePersistentMessage = (message: frida.Message, data: Buffer | null) => { // Handle binary data serialization const messageData: ScriptMessage = { type: message.type, payload: message.type === 'send' ? message.payload : undefined, data: null }; // If there's binary data, base64 encode it if (data !== null) { try { messageData.data = data.toString('base64'); } catch (error) { logger.error(`Failed to encode binary data: ${error}`); } } // Add message to queue const messageQueue = scriptMessages.get(session_id); if (messageQueue) { messageQueue.push(messageData); logger.debug(`Queued message type=${message.type}, queue_size=${messageQueue.length}`); } }; if (keep_alive) { script.message.connect(handlePersistentMessage); const sessionScripts = scripts.get(session_id); if (sessionScripts) { sessionScripts.push(script); } } else { script.message.connect(handleMessage); } await script.load(); // Give a short time for initial results if (!keep_alive) { await sleep(200); } // Process results let finalResult: any = {}; if (initialExecutionResults.length > 0) { const receipt = initialExecutionResults[0]; if ('script_error' in receipt) { finalResult = { status: 'error', error: 'Script execution error', details: receipt.script_error }; } else if (receipt.error) { finalResult = { status: 'error', error: receipt.error.message, stack: receipt.error.stack, initial_logs: receipt.initial_logs || [] }; } else { finalResult = { status: 'success', result: receipt.result, initial_logs: receipt.initial_logs || [] }; } } else if (keep_alive) { finalResult = { status: 'success', message: 'Script loaded persistently. Use get_session_messages to retrieve async messages.', initial_logs: [] }; } else { finalResult = { status: 'nodata', message: 'Script loaded but sent no initial messages.', initial_logs: [] }; } if (!keep_alive) { await script.unload(); finalResult.script_unloaded = true; } else { finalResult.script_unloaded = false; finalResult.info = 'Script is persistent. Manage lifecycle if necessary.'; } return { content: [{ type: 'text', text: JSON.stringify(finalResult, null, 2) }], structuredContent: finalResult }; } catch (error) { const result = { status: 'error', error: error instanceof Error ? error.message : String(error) }; return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }], structuredContent: result }; } } ); // Load Script File server.registerTool( 'load_script_file', { title: 'Load Script File', description: 'Load and execute a Frida JavaScript file into an existing session.', inputSchema: { session_id: z.string().describe('Session ID from create_interactive_session'), script_path: z.string().describe('Path to JavaScript file'), keep_alive: z.boolean().optional().default(true).describe('If true, script persists; if false, runs once') }, outputSchema: { status: z.string(), script_file: z.string().optional(), error: z.string().optional() } }, async ({ session_id, script_path, keep_alive = true }: { session_id: string; script_path: string; keep_alive?: boolean; }) => { const session = sessions.get(session_id); if (!session) { const result = { status: 'error', error: `Session with ID ${session_id} not found` }; return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }], structuredContent: result }; } try { // Read the script file from filesystem const fs = await import('fs/promises'); const javascriptCode = await fs.readFile(script_path, 'utf-8'); // Wrap the code using the template (same as execute_in_session) const wrappedCode = SCRIPT_EXECUTE_WRAPPER.replace('{code}', javascriptCode); const script = await session.createScript(wrappedCode); // Capture messages from the initial execution const initialExecutionResults: any[] = []; const handleMessage = (message: frida.Message, _data: Buffer | null) => { if (message.type === 'send') { const payload = message.payload as ExecutionReceipt; if (payload.type === 'execution_receipt') { initialExecutionResults.push(payload); } } else if (message.type === 'error') { initialExecutionResults.push({ script_error: message.description, details: message }); } }; const handlePersistentMessage = (message: frida.Message, data: Buffer | null) => { // Handle binary data serialization const messageData: ScriptMessage = { type: message.type, payload: message.type === 'send' ? message.payload : undefined, data: null }; // If there's binary data, base64 encode it if (data !== null) { try { messageData.data = data.toString('base64'); } catch (error) { logger.error(`Failed to encode binary data: ${error}`); } } // Add message to queue const messageQueue = scriptMessages.get(session_id); if (messageQueue) { messageQueue.push(messageData); logger.debug(`Queued message type=${message.type}, queue_size=${messageQueue.length}`); } }; if (keep_alive) { script.message.connect(handlePersistentMessage); const sessionScripts = scripts.get(session_id); if (sessionScripts) { sessionScripts.push(script); } } else { script.message.connect(handleMessage); } await script.load(); // Give a short time for initial results if (!keep_alive) { await sleep(200); } // Process results let finalResult: any = {}; if (initialExecutionResults.length > 0) { const receipt = initialExecutionResults[0]; if ('script_error' in receipt) { finalResult = { status: 'error', error: 'Script execution error', details: receipt.script_error, script_file: script_path }; } else if (receipt.error) { finalResult = { status: 'error', error: receipt.error.message, stack: receipt.error.stack, initial_logs: receipt.initial_logs || [], script_file: script_path }; } else { finalResult = { status: 'success', result: receipt.result, initial_logs: receipt.initial_logs || [], script_file: script_path }; } } else if (keep_alive) { finalResult = { status: 'success', message: 'Script file loaded persistently. Use get_session_messages to retrieve async messages.', script_file: script_path, initial_logs: [] }; } else { finalResult = { status: 'nodata', message: 'Script loaded but sent no initial messages.', script_file: script_path, initial_logs: [] }; } if (!keep_alive) { await script.unload(); finalResult.script_unloaded = true; } else { finalResult.script_unloaded = false; finalResult.info = 'Script is persistent. Manage lifecycle if necessary.'; } return { content: [{ type: 'text', text: JSON.stringify(finalResult, null, 2) }], structuredContent: finalResult }; } catch (error) { const result = { status: 'error', error: error instanceof Error ? error.message : String(error), script_file: script_path }; return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }], structuredContent: result }; } } ); }

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/nonsleepr/frida-mcp.ts'

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