diff --git a/README.md b/README.md index 6447e54..e86bf01 100644 --- a/README.md +++ b/README.md @@ -76,6 +76,8 @@ Try these prompts with Claude Desktop or other MCP clients after setup: - "Show me areas reachable within 30 minutes of downtown Portland by car" - "Calculate a travel time matrix between these 3 hotel locations (Marriott, Sheraton and Hilton) and the convention center in Denver" - "Find the optimal route visiting these 3 tourist attractions (Golden Gate, Musical Stairs and Fisherman's Wharf) in San Francisco" +- "Clean up this GPS trace from my bike ride and snap it to actual roads" +- "Optimize my delivery route for these 10 addresses: [list of addresses]" ### Tips for Better Results diff --git a/src/prompts/CleanGpsTracePrompt.ts b/src/prompts/CleanGpsTracePrompt.ts new file mode 100644 index 0000000..2c2368c --- /dev/null +++ b/src/prompts/CleanGpsTracePrompt.ts @@ -0,0 +1,87 @@ +// Copyright (c) Mapbox, Inc. +// Licensed under the MIT License. + +import { BasePrompt } from './BasePrompt.js'; +import type { + PromptArgument, + PromptMessage +} from '@modelcontextprotocol/sdk/types.js'; + +/** + * Prompt for cleaning and snapping noisy GPS traces to actual roads. + * + * This prompt guides the agent through: + * 1. Parsing and validating GPS coordinates + * 2. Using map matching to snap the trace to roads + * 3. Visualizing before/after comparison + * 4. Analyzing confidence scores and improvements + * + * Example queries: + * - "Clean up this GPS trace from my bike ride" + * - "Fix GPS drift in my recorded delivery route" + * - "Snap this tracking data to actual roads" + */ +export class CleanGpsTracePrompt extends BasePrompt { + readonly name = 'clean-gps-trace'; + readonly description = + 'Clean and snap noisy GPS traces to actual roads using map matching'; + + readonly arguments: PromptArgument[] = [ + { + name: 'coordinates', + description: + 'GPS coordinates as array of [longitude, latitude] pairs or JSON string', + required: true + }, + { + name: 'mode', + description: + 'Travel mode: driving, walking, or cycling (default: driving)', + required: false + }, + { + name: 'timestamps', + description: + 'Optional Unix timestamps for each coordinate (as JSON array)', + required: false + } + ]; + + getMessages(args: Record): PromptMessage[] { + this.validateArguments(args); + + const { coordinates, mode = 'driving', timestamps } = args; + + return [ + { + role: 'user', + content: { + type: 'text', + text: `Clean and snap this GPS trace to actual roads using map matching. + +GPS Coordinates: ${coordinates} +Travel Mode: ${mode}${timestamps ? `\nTimestamps: ${timestamps}` : ''} + +Please follow these steps: +1. Parse and validate the GPS coordinates (ensure they're in [longitude, latitude] format) +2. Use map_matching_tool with: + - coordinates: parsed coordinate array + - profile: mapbox/${mode} + ${timestamps ? '- timestamps: parsed timestamp array\n ' : ''}- geometries: geojson (to get the matched route) + - annotations: true (to get confidence scores) +3. Create a visual comparison showing: + - Original noisy GPS trace (in red/orange) + - Cleaned matched route (in blue/green) + - Confidence scores for the matching +4. Provide a summary including: + - Number of points processed + - Average confidence score + - Distance before and after matching + - Any segments with low confidence that may need review + +Format the output to clearly show the improvements from map matching.` + } + } + ]; + } +} diff --git a/src/prompts/OptimizeDeliveriesPrompt.ts b/src/prompts/OptimizeDeliveriesPrompt.ts new file mode 100644 index 0000000..e64d08f --- /dev/null +++ b/src/prompts/OptimizeDeliveriesPrompt.ts @@ -0,0 +1,100 @@ +// Copyright (c) Mapbox, Inc. +// Licensed under the MIT License. + +import { BasePrompt } from './BasePrompt.js'; +import type { + PromptArgument, + PromptMessage +} from '@modelcontextprotocol/sdk/types.js'; + +/** + * Prompt for optimizing multi-stop routes with time windows and constraints. + * + * This prompt guides the agent through: + * 1. Geocoding addresses (if needed) + * 2. Setting up optimization constraints + * 3. Running the optimization API + * 4. Visualizing the optimized route + * 5. Showing time and distance savings + * + * Example queries: + * - "Optimize my delivery route for these 10 addresses" + * - "What's the best order to visit these locations?" + * - "Plan a multi-stop trip with time constraints" + */ +export class OptimizeDeliveriesPrompt extends BasePrompt { + readonly name = 'optimize-deliveries'; + readonly description = + 'Find the optimal route for multiple stops with optional time windows and capacity constraints'; + + readonly arguments: PromptArgument[] = [ + { + name: 'stops', + description: + 'List of stops as addresses or coordinates (comma-separated or JSON array)', + required: true + }, + { + name: 'profile', + description: + 'Routing profile: driving, driving-traffic, cycling, or walking (default: driving-traffic)', + required: false + }, + { + name: 'start_location', + description: 'Optional starting location (if different from first stop)', + required: false + }, + { + name: 'end_location', + description: 'Optional ending location (if different from last stop)', + required: false + } + ]; + + getMessages(args: Record): PromptMessage[] { + this.validateArguments(args); + + const { + stops, + profile = 'driving-traffic', + start_location, + end_location + } = args; + + return [ + { + role: 'user', + content: { + type: 'text', + text: `Optimize the route for multiple stops to find the most efficient order. + +Stops: ${stops} +Profile: ${profile}${start_location ? `\nStart Location: ${start_location}` : ''}${end_location ? `\nEnd Location: ${end_location}` : ''} + +Please follow these steps: +1. Parse the stops list (handle both address strings and coordinates) +2. Geocode any addresses to get coordinates (use search_and_geocode_tool) +3. Prepare the optimization request: + - Convert stops to shipments format (each stop is a delivery) + - Set source=first and destination=last (or use specified start/end locations) + - Use profile: mapbox/${profile} +4. Call optimization_tool with the shipment configuration +5. Display the results: + - Show the optimized route on a map with numbered waypoints + - Provide the optimized order of stops + - Show total distance and estimated time + - Compare against the original order (if applicable): + * Time saved + * Distance saved + * Efficiency gain percentage +6. Provide turn-by-turn summary for the optimized route + +Format the output to clearly show the optimization benefits and the recommended stop order. + +Note: The Optimization API is asynchronous, so the tool will poll for results. This may take a few seconds.` + } + } + ]; + } +} diff --git a/src/prompts/promptRegistry.ts b/src/prompts/promptRegistry.ts index 2371b2c..9696b54 100644 --- a/src/prompts/promptRegistry.ts +++ b/src/prompts/promptRegistry.ts @@ -1,8 +1,10 @@ // Copyright (c) Mapbox, Inc. // Licensed under the MIT License. +import { CleanGpsTracePrompt } from './CleanGpsTracePrompt.js'; import { FindPlacesNearbyPrompt } from './FindPlacesNearbyPrompt.js'; import { GetDirectionsPrompt } from './GetDirectionsPrompt.js'; +import { OptimizeDeliveriesPrompt } from './OptimizeDeliveriesPrompt.js'; import { ShowReachableAreasPrompt } from './ShowReachableAreasPrompt.js'; /** @@ -14,8 +16,10 @@ import { ShowReachableAreasPrompt } from './ShowReachableAreasPrompt.js'; // Instantiate all prompts const ALL_PROMPTS = [ + new CleanGpsTracePrompt(), new FindPlacesNearbyPrompt(), new GetDirectionsPrompt(), + new OptimizeDeliveriesPrompt(), new ShowReachableAreasPrompt() ] as const; diff --git a/test/prompts/promptRegistry.test.ts b/test/prompts/promptRegistry.test.ts index b119588..512a90b 100644 --- a/test/prompts/promptRegistry.test.ts +++ b/test/prompts/promptRegistry.test.ts @@ -12,13 +12,15 @@ describe('Prompt Registry', () => { test('returns all registered prompts', () => { const prompts = getAllPrompts(); - // Should have at least the 3 initial prompts - expect(prompts.length).toBeGreaterThanOrEqual(3); + // Should have at least the 5 prompts + expect(prompts.length).toBeGreaterThanOrEqual(5); // Verify expected prompts are present const promptNames = prompts.map((p) => p.name); + expect(promptNames).toContain('clean-gps-trace'); expect(promptNames).toContain('find-places-nearby'); expect(promptNames).toContain('get-directions'); + expect(promptNames).toContain('optimize-deliveries'); expect(promptNames).toContain('show-reachable-areas'); }); @@ -83,8 +85,10 @@ describe('Prompt Registry', () => { test('returns correct prompt instances', () => { const promptNames = [ + 'clean-gps-trace', 'find-places-nearby', 'get-directions', + 'optimize-deliveries', 'show-reachable-areas' ]; @@ -185,5 +189,69 @@ describe('Prompt Registry', () => { const modeArg = metadata.arguments?.find((a) => a.name === 'mode'); expect(modeArg?.required).toBe(false); }); + + test('clean-gps-trace has correct metadata', () => { + const prompt = getPromptByName('clean-gps-trace'); + expect(prompt).toBeDefined(); + + const metadata = prompt!.getMetadata(); + + expect(metadata.name).toBe('clean-gps-trace'); + expect(metadata.description).toContain('GPS'); + + // Should have coordinates, mode, timestamps arguments + const argNames = metadata.arguments?.map((a) => a.name) || []; + expect(argNames).toContain('coordinates'); + expect(argNames).toContain('mode'); + expect(argNames).toContain('timestamps'); + + // coordinates should be required + const coordinatesArg = metadata.arguments?.find( + (a) => a.name === 'coordinates' + ); + expect(coordinatesArg?.required).toBe(true); + + // mode and timestamps should be optional + const modeArg = metadata.arguments?.find((a) => a.name === 'mode'); + expect(modeArg?.required).toBe(false); + + const timestampsArg = metadata.arguments?.find( + (a) => a.name === 'timestamps' + ); + expect(timestampsArg?.required).toBe(false); + }); + + test('optimize-deliveries has correct metadata', () => { + const prompt = getPromptByName('optimize-deliveries'); + expect(prompt).toBeDefined(); + + const metadata = prompt!.getMetadata(); + + expect(metadata.name).toBe('optimize-deliveries'); + expect(metadata.description).toContain('optimal'); + + // Should have stops, profile, start_location, end_location arguments + const argNames = metadata.arguments?.map((a) => a.name) || []; + expect(argNames).toContain('stops'); + expect(argNames).toContain('profile'); + expect(argNames).toContain('start_location'); + expect(argNames).toContain('end_location'); + + // stops should be required + const stopsArg = metadata.arguments?.find((a) => a.name === 'stops'); + expect(stopsArg?.required).toBe(true); + + // profile, start_location, end_location should be optional + const profileArg = metadata.arguments?.find((a) => a.name === 'profile'); + expect(profileArg?.required).toBe(false); + + const startArg = metadata.arguments?.find( + (a) => a.name === 'start_location' + ); + expect(startArg?.required).toBe(false); + + const endArg = metadata.arguments?.find((a) => a.name === 'end_location'); + expect(endArg?.required).toBe(false); + }); }); });