Skip to main content
Glama

Claude TypeScript MCP Servers

by ukkz
frame.ts8.53 kB
/** * Frame操作系ハンドラー */ import { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { Frame } from "puppeteer"; import { createErrorResponse, createSuccessResponse, ensureBrowser, findFrame } from "../utils.js"; import { getCurrentFrame, setCurrentFrame } from "../state.js"; import { GetFramesArgs, SwitchToFrameArgs, EvaluateInFrameArgs, SearchAcrossFramesArgs, } from "../types.js"; /** * フレーム情報を取得する処理 */ export async function handleGetFrames( args: GetFramesArgs, server: Server, ): Promise<CallToolResult> { try { const page = await ensureBrowser(server); // フレームツリーを再帰的に取得 const getFrameInfo = async (frame: Frame, level: number = 0): Promise<any> => { const url = frame.url(); const name = frame.name(); const parentFrame = frame.parentFrame(); const info: any = { level, url, name: name || "(unnamed)", isMainFrame: frame === page.mainFrame(), parentUrl: parentFrame ? parentFrame.url() : null, }; if (args.detailed) { try { // クロスオリジンフレームの場合はアクセスできない可能性 const title = await frame.title().catch(() => "(inaccessible)"); info.title = title; } catch (e) { info.title = "(error)"; } } // 子フレームを収集 const childFrames = frame.childFrames(); if (childFrames.length > 0) { info.children = []; for (const child of childFrames) { info.children.push(await getFrameInfo(child, level + 1)); } } return info; }; const frameTree = await getFrameInfo(page.mainFrame()); return createSuccessResponse(`Frame tree:\n${JSON.stringify(frameTree, null, 2)}`); } catch (error) { return createErrorResponse(`Failed to get frames: ${(error as Error).message}`); } } /** * 特定のフレームに切り替える処理 */ export async function handleSwitchToFrame( args: SwitchToFrameArgs, server: Server, ): Promise<CallToolResult> { try { const page = await ensureBrowser(server); const targetFrame = await findFrame(page, args); if (!targetFrame) { if (args.frameSelector) { return createErrorResponse(`Frame element not found: ${args.frameSelector}`); } else if (args.frameName) { return createErrorResponse(`Frame not found with name: ${args.frameName}`); } else if (args.frameIndex !== undefined) { const frames = page.frames(); return createErrorResponse( `Frame index out of range: ${args.frameIndex} (available: 0-${frames.length - 1})`, ); } return createErrorResponse("Must specify either frameSelector, frameName, or frameIndex"); } // フレームコンテキストを切り替え setCurrentFrame(targetFrame); return createSuccessResponse( `Switched to frame:\n` + `URL: ${targetFrame.url()}\n` + `Name: ${targetFrame.name() || "(unnamed)"}`, ); } catch (error) { return createErrorResponse(`Failed to switch to frame: ${(error as Error).message}`); } } /** * メインフレームに戻る処理 */ export async function handleSwitchToMainFrame(server: Server): Promise<CallToolResult> { try { const page = await ensureBrowser(server); // メインフレームに切り替え setCurrentFrame(page.mainFrame()); return createSuccessResponse(`Switched back to main frame\n` + `URL: ${page.url()}`); } catch (error) { return createErrorResponse(`Failed to switch to main frame: ${(error as Error).message}`); } } /** * 特定のフレーム内でJavaScriptを実行する処理 */ export async function handleEvaluateInFrame( args: EvaluateInFrameArgs, server: Server, ): Promise<CallToolResult> { try { const page = await ensureBrowser(server); let targetFrame: Frame | undefined; // フレームを特定 if (args.frameSelector || args.frameName) { targetFrame = await findFrame(page, args); if (!targetFrame) { return createErrorResponse( args.frameSelector ? `Frame element not found: ${args.frameSelector}` : `Frame not found with name: ${args.frameName}`, ); } } else { // 現在のフレームコンテキストを使用 const current = getCurrentFrame(); targetFrame = (current as Frame) || page.mainFrame(); } // スクリプトを実行 const result = await targetFrame.evaluate(args.script); return createSuccessResponse( `Executed in frame: ${targetFrame.url()}\n` + `Result: ${JSON.stringify(result, null, 2)}`, ); } catch (error) { return createErrorResponse(`Failed to evaluate in frame: ${(error as Error).message}`); } } /** * 全フレームから要素を検索する処理 */ export async function handleSearchAcrossFrames( args: SearchAcrossFramesArgs, server: Server, ): Promise<CallToolResult> { try { const page = await ensureBrowser(server); const results: any[] = []; // フレームを再帰的に検索 const searchInFrame = async (frame: Frame, framePath: string[]) => { try { const frameResults = await frame.evaluate( (selector, text, attributes) => { const matches: any[] = []; if (selector) { // CSSセレクタで検索 const elements = document.querySelectorAll(selector); elements.forEach((el, index) => { matches.push({ type: "selector", selector, index, tagName: el.tagName, text: (el as HTMLElement).innerText || "", }); }); } if (text) { // テキストで検索 const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, null); let node; while ((node = walker.nextNode())) { if (node.textContent && node.textContent.includes(text)) { const parent = node.parentElement; if (parent) { matches.push({ type: "text", text: node.textContent, parentTag: parent.tagName, parentId: parent.id, parentClass: parent.className, }); } } } } if (attributes) { // 属性で検索 const allElements = document.querySelectorAll("*"); allElements.forEach((el) => { let matchesAll = true; for (const [key, value] of Object.entries(attributes)) { if (el.getAttribute(key) !== value) { matchesAll = false; break; } } if (matchesAll) { matches.push({ type: "attributes", tagName: el.tagName, attributes: attributes, text: (el as HTMLElement).innerText || "", }); } }); } return matches; }, args.selector, args.text, args.attributes, ); // 結果にフレームパスを追加 frameResults.forEach((result: any) => { result.framePath = framePath; result.frameUrl = frame.url(); results.push(result); }); } catch (e) { // クロスオリジンフレームの場合はスキップ } // 子フレームを検索 const childFrames = frame.childFrames(); for (let i = 0; i < childFrames.length; i++) { const childFrame = childFrames[i]; if (childFrame) { await searchInFrame(childFrame, [...framePath, `frame[${i}]`]); } } }; await searchInFrame(page.mainFrame(), ["main"]); return createSuccessResponse( `Found ${results.length} match(es):\n${JSON.stringify(results, null, 2)}`, ); } catch (error) { return createErrorResponse(`Failed to search across frames: ${(error as Error).message}`); } }

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/ukkz/claude-ts-mcps'

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