diff --git a/src/index.ts b/src/index.ts index f111926..8fd5c02 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,6 +5,9 @@ import { registerDownloadDemoStepPrettyHTMLTool } from './tools/download-demo-st import { VERSION } from './constants'; import { registerCapturePagePrompt } from './tools/capture-page-prompt'; import { registerImportPagePrompt } from './tools/import-page-prompt'; +import { registerQueryUimap } from './tools/query-uimap'; +import { registerFindUimapPath } from './tools/find-uimap-path'; +import { registerReportUimapFeedback } from './tools/report-uimap-feedback'; async function main() { const server = new McpServer({ @@ -16,6 +19,11 @@ async function main() { registerDownloadDemoStepPrettyHTMLTool(server); registerCapturePagePrompt(server); registerImportPagePrompt(server); + + // Uimap tools + registerQueryUimap(server); + registerFindUimapPath(server); + registerReportUimapFeedback(server); await server.connect(new StdioServerTransport()); } diff --git a/src/tools/find-uimap-path.ts b/src/tools/find-uimap-path.ts new file mode 100644 index 0000000..cfa5772 --- /dev/null +++ b/src/tools/find-uimap-path.ts @@ -0,0 +1,46 @@ +import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; +import { z } from 'zod'; +import { getApiFetch } from '../utils/request'; +import { createToolExecuter } from './base'; + +const Name = 'find_uimap_path'; + +const Description = ` +Find the shortest navigation path between two URLs using an existing Uimap. + +Use this when you: +- Know your current page and target page +- Need step-by-step navigation instructions +- Want to verify a navigation path exists +`; + +const Schema = z.object({ + url: z.string().describe('DemoWay app URL (e.g., "https://app.demoway.com")'), + uimapId: z.string().describe('The Uimap ID to use for path finding'), + from: z.string().describe('Source URL (current page)'), + to: z.string().describe('Target URL (destination page)'), +}); + +const executer = createToolExecuter(async ({ url, uimapId, from, to }, extra) => { + const $apiFetch = getApiFetch(url); + const result = await $apiFetch(`/api/uimap/${uimapId}/path`, { + query: { + from, + to, + }, + signal: extra.signal, + }); + + return { + content: [ + { + type: 'text', + text: JSON.stringify(result), + }, + ], + }; +}); + +export function registerFindUimapPath(server: McpServer) { + server.tool(Name, Description, Schema.shape, executer); +} diff --git a/src/tools/query-uimap.ts b/src/tools/query-uimap.ts new file mode 100644 index 0000000..5a213b6 --- /dev/null +++ b/src/tools/query-uimap.ts @@ -0,0 +1,51 @@ +import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; +import { z } from 'zod'; +import { getApiFetch } from '../utils/request'; +import { createToolExecuter } from './base'; + +const Name = 'query_uimap'; + +const Description = ` +Query Uimap to get the best action plan for completing a task on a specific domain. +This tool returns a structured action plan including: +1. Primary approach: Direct navigation to target page with actions +2. Fallback approach: Step-by-step navigation from entry point + +Use this when you need to: +- Navigate to a specific page on a website +- Perform a task like "add a DNS record" or "create a new project" +- Understand the navigation structure of a website +`; + +const Schema = z.object({ + url: z.string().describe('DemoWay app URL (e.g., "https://app.demoway.com")'), + domain: z.string().describe('The target domain to query (e.g., "dash.cloudflare.com")'), + task: z.string().describe('The task description (e.g., "add a DNS A record")'), + uimapId: z.string().optional().describe('Optional: specific Uimap ID to use (if not provided, best available map will be selected)'), +}); + +const executer = createToolExecuter(async ({ url, domain, task, uimapId }, extra) => { + const $apiFetch = getApiFetch(url); + const result = await $apiFetch('/api/uimap/query', { + method: 'POST', + body: { + domain, + task, + uimapId, + }, + signal: extra.signal, + }); + + return { + content: [ + { + type: 'text', + text: JSON.stringify(result), + }, + ], + }; +}); + +export function registerQueryUimap(server: McpServer) { + server.tool(Name, Description, Schema.shape, executer); +} diff --git a/src/tools/report-uimap-feedback.ts b/src/tools/report-uimap-feedback.ts new file mode 100644 index 0000000..0fcf5ac --- /dev/null +++ b/src/tools/report-uimap-feedback.ts @@ -0,0 +1,57 @@ +import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; +import { z } from 'zod'; +import { getApiFetch } from '../utils/request'; +import { createToolExecuter } from './base'; + +const Name = 'report_uimap_feedback'; + +const Description = ` +Report feedback about Uimap path execution results. +This helps improve Uimap quality by tracking which paths work and which don't. + +Use this tool after executing a path to: +- Report successful execution (improves map score) +- Report failed execution with details (helps identify issues) + +The feedback will be used to: +1. Calculate Uimap scores +2. Rank maps by reliability +3. Identify outdated or broken paths +`; + +const Schema = z.object({ + url: z.string().describe('DemoWay app URL (e.g., "https://app.demoway.com")'), + uimapId: z.string().describe('The Uimap ID that was used'), + task: z.string().describe('The task that was attempted'), + success: z.boolean().describe('Whether the execution was successful'), + failedAtStep: z.number().optional().describe('If failed, which step number failed (1-based)'), + failReason: z.string().optional().describe('If failed, description of what went wrong'), +}); + +const executer = createToolExecuter(async ({ url, uimapId, task, success, failedAtStep, failReason }, extra) => { + const $apiFetch = getApiFetch(url); + const result = await $apiFetch('/api/uimap/feedback', { + method: 'POST', + body: { + uimapId, + task, + success, + failedAtStep, + failReason, + }, + signal: extra.signal, + }); + + return { + content: [ + { + type: 'text', + text: JSON.stringify(result), + }, + ], + }; +}); + +export function registerReportUimapFeedback(server: McpServer) { + server.tool(Name, Description, Schema.shape, executer); +}