From 1d39ad7dd567c64b7070095dd93764a35ee80235 Mon Sep 17 00:00:00 2001 From: delai Date: Sun, 15 Feb 2026 12:24:42 +0800 Subject: [PATCH 1/2] feat: add UIMap MCP tools - Add query_uimap tool for AI-driven task planning - Add find_uimap_path tool for navigation path finding - Add report_uimap_feedback tool for execution feedback --- src/index.ts | 8 +++++ src/tools/find-uimap-path.ts | 46 ++++++++++++++++++++++++ src/tools/query-uimap.ts | 51 ++++++++++++++++++++++++++ src/tools/report-uimap-feedback.ts | 57 ++++++++++++++++++++++++++++++ 4 files changed, 162 insertions(+) create mode 100644 src/tools/find-uimap-path.ts create mode 100644 src/tools/query-uimap.ts create mode 100644 src/tools/report-uimap-feedback.ts diff --git a/src/index.ts b/src/index.ts index f111926..e0f6391 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..53971b5 --- /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 navigation path between two URLs using an existing UIMap. +This tool uses BFS to find the shortest path from a source URL to a target URL. + +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({ + 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 ({ uiMapId, from, to }, extra) => { + const $apiFetch = getApiFetch(from); + const result = await $apiFetch(`/api/uimap/${uiMapId}/path`, { + query: { + from, + to, + }, + signal: extra.signal, + }); + + return { + content: [ + { + type: 'text', + text: JSON.stringify(result, null, 2), + }, + ], + }; +}); + +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..09448f8 --- /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({ + domain: z.string().describe('The 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 ({ domain, task, uiMapId }, extra) => { + // Use admin API endpoint + const $apiFetch = getApiFetch(`https://${domain}`); + const result = await $apiFetch('/api/uimap/query', { + method: 'POST', + body: { + domain, + task, + uiMapId, + }, + signal: extra.signal, + }); + + return { + content: [ + { + type: 'text', + text: JSON.stringify(result, null, 2), + }, + ], + }; +}); + +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..2556810 --- /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({ + 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'), + domain: z.string().describe('The domain for API endpoint'), +}); + +const executer = createToolExecuter(async ({ uiMapId, task, success, failedAtStep, failReason, domain }, extra) => { + const $apiFetch = getApiFetch(`https://${domain}`); + 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({ success: true, message: 'Feedback recorded' }, null, 2), + }, + ], + }; +}); + +export function registerReportUIMapFeedback(server: McpServer) { + server.tool(Name, Description, Schema.shape, executer); +} From 42ea65c28b6b19ffd08baa4ce584055c70ae79cb Mon Sep 17 00:00:00 2001 From: delai Date: Tue, 24 Feb 2026 10:52:14 +0800 Subject: [PATCH 2/2] refactor: standardize naming for UIMap tools and update descriptions - Rename functions and variables to use 'Uimap' consistently across tools - Update descriptions in tool schemas for clarity and consistency - Adjust API fetch parameters to include 'url' and 'uimapId' instead of 'domain' and 'uiMapId' --- src/index.ts | 14 +++++++------- src/tools/find-uimap-path.ts | 16 ++++++++-------- src/tools/query-uimap.ts | 18 +++++++++--------- src/tools/report-uimap-feedback.ts | 20 ++++++++++---------- 4 files changed, 34 insertions(+), 34 deletions(-) diff --git a/src/index.ts b/src/index.ts index e0f6391..8fd5c02 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,9 +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'; +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({ @@ -20,10 +20,10 @@ async function main() { registerCapturePagePrompt(server); registerImportPagePrompt(server); - // UIMap tools - registerQueryUIMap(server); - registerFindUIMapPath(server); - registerReportUIMapFeedback(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 index 53971b5..cfa5772 100644 --- a/src/tools/find-uimap-path.ts +++ b/src/tools/find-uimap-path.ts @@ -6,8 +6,7 @@ import { createToolExecuter } from './base'; const Name = 'find_uimap_path'; const Description = ` -Find the navigation path between two URLs using an existing UIMap. -This tool uses BFS to find the shortest path from a source URL to a target URL. +Find the shortest navigation path between two URLs using an existing Uimap. Use this when you: - Know your current page and target page @@ -16,14 +15,15 @@ Use this when you: `; const Schema = z.object({ - uiMapId: z.string().describe('The UIMap ID to use for path finding'), + 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 ({ uiMapId, from, to }, extra) => { - const $apiFetch = getApiFetch(from); - const result = await $apiFetch(`/api/uimap/${uiMapId}/path`, { +const executer = createToolExecuter(async ({ url, uimapId, from, to }, extra) => { + const $apiFetch = getApiFetch(url); + const result = await $apiFetch(`/api/uimap/${uimapId}/path`, { query: { from, to, @@ -35,12 +35,12 @@ const executer = createToolExecuter(async ({ uiMapId, from, content: [ { type: 'text', - text: JSON.stringify(result, null, 2), + text: JSON.stringify(result), }, ], }; }); -export function registerFindUIMapPath(server: McpServer) { +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 index 09448f8..5a213b6 100644 --- a/src/tools/query-uimap.ts +++ b/src/tools/query-uimap.ts @@ -6,7 +6,7 @@ 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. +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 @@ -18,20 +18,20 @@ Use this when you need to: `; const Schema = z.object({ - domain: z.string().describe('The domain to query (e.g., "dash.cloudflare.com")'), + 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)'), + uimapId: z.string().optional().describe('Optional: specific Uimap ID to use (if not provided, best available map will be selected)'), }); -const executer = createToolExecuter(async ({ domain, task, uiMapId }, extra) => { - // Use admin API endpoint - const $apiFetch = getApiFetch(`https://${domain}`); +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, + uimapId, }, signal: extra.signal, }); @@ -40,12 +40,12 @@ const executer = createToolExecuter(async ({ domain, task, content: [ { type: 'text', - text: JSON.stringify(result, null, 2), + text: JSON.stringify(result), }, ], }; }); -export function registerQueryUIMap(server: McpServer) { +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 index 2556810..0fcf5ac 100644 --- a/src/tools/report-uimap-feedback.ts +++ b/src/tools/report-uimap-feedback.ts @@ -6,34 +6,34 @@ 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. +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 +1. Calculate Uimap scores 2. Rank maps by reliability 3. Identify outdated or broken paths `; const Schema = z.object({ - uiMapId: z.string().describe('The UIMap ID that was used'), + 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'), - domain: z.string().describe('The domain for API endpoint'), }); -const executer = createToolExecuter(async ({ uiMapId, task, success, failedAtStep, failReason, domain }, extra) => { - const $apiFetch = getApiFetch(`https://${domain}`); +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, + uimapId, task, success, failedAtStep, @@ -46,12 +46,12 @@ const executer = createToolExecuter(async ({ uiMapId, task, content: [ { type: 'text', - text: JSON.stringify({ success: true, message: 'Feedback recorded' }, null, 2), + text: JSON.stringify(result), }, ], }; }); -export function registerReportUIMapFeedback(server: McpServer) { +export function registerReportUimapFeedback(server: McpServer) { server.tool(Name, Description, Schema.shape, executer); }