Skip to main content
Glama

maps

Search locations, save favorites, and get directions using Apple Maps. Manage guides, pin locations, and set map centers with precise coordinates for efficient navigation and location management.

Instructions

Search locations, manage guides, save favorites, and get directions using Apple Maps

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
addressNoAddress of the location (required for save, pin, addToGuide)
fromAddressNoStarting address for directions (required for directions)
guideNameNoName of the guide (required for createGuide and addToGuide)
latitudeNoLatitude for the center point (required for setCenter)
limitNoMaximum number of results to return (optional for search)
longitudeNoLongitude for the center point (required for setCenter)
nameNoName of the location (required for save and pin)
operationYesOperation to perform with Maps
queryNoSearch query for locations (required for search)
toAddressNoDestination address for directions (required for directions)
transportTypeNoType of transport to use (optional for directions)

Implementation Reference

  • Main execution handler for the 'maps' tool. Handles operations like search, save, directions, etc., by executing specific AppleScript files via runAppleScriptSafely.
    export async function handleMaps(
      args: MapsArgs,
      loadModule: LoadModuleFunction
    ): Promise<ToolResult> {
      try {
        const mapsModule = await loadModule("maps");
    
        switch (args.operation) {
          case "search": {
            const resultStr = await runAppleScriptSafely(
              "maps_search.scpt",
              [args.query]
            );
            const parsedResult: SearchResponse = JSON.parse(resultStr);
    
            let detailedText = parsedResult.message || "Search completed";
    
            if (parsedResult.locations && parsedResult.locations.length > 0) {
              detailedText += "\n\n";
              parsedResult.locations.forEach((location: any, index: number) => {
                detailedText += `Location ${index + 1}:\n`;
                detailedText += `• Name: ${location.name}\n`;
                detailedText += `• Address: ${location.address}\n`;
    
                if (location.latitude !== null && location.longitude !== null) {
                  detailedText += `• Coordinates: ${location.latitude.toFixed(
                    6
                  )}, ${location.longitude.toFixed(6)}\n`;
                }
    
                if (location.category) {
                  detailedText += `• Category: ${location.category}\n`;
                }
    
                if (index < parsedResult.locations.length - 1) {
                  detailedText += "\n";
                }
              });
            }
    
            return {
              content: [
                {
                  type: "text",
                  text: detailedText,
                },
              ],
              success: parsedResult.success,
              locations: parsedResult.locations,
              query: args.query,
              limit: args.limit,
              isError: !parsedResult.success,
            };
          }
    
          case "save": {
            const resultStr = await runAppleScriptSafely(
              "maps_save.scpt",
              [args.name, args.address]
            );
            const parsedResult: any = JSON.parse(resultStr);
            if (parsedResult.guides && Array.isArray(parsedResult.guides) && parsedResult.guides.length > 0) {
              return {
                content: [{ type: "text", text: parsedResult.message || "Save operation completed" }],
                success: parsedResult.success,
                location: parsedResult.location,
                name: args.name,
                address: args.address,
                isError: !parsedResult.success,
              };
            }
    
            return {
              content: [],
              success: parsedResult.success,
              location: parsedResult.location,
              name: args.name,
              address: args.address,
              isError: !parsedResult.success,
            };
          }
    
          case "pin": {
            const resultStr = await runAppleScriptSafely(
              "maps_pin.scpt",
              [args.name, args.address]
            );
            const parsedResult: any = JSON.parse(resultStr);
            if (parsedResult.guides && Array.isArray(parsedResult.guides) && parsedResult.guides.length > 0) {
              return {
                content: [{ type: "text", text: parsedResult.message || "Pin operation completed" }],
                success: parsedResult.success,
                location: parsedResult.location,
                name: args.name,
                address: args.address,
                isError: !parsedResult.success,
              };
            } else {
              return {
                content: [],
                success: parsedResult.success,
                location: parsedResult.location,
                name: args.name,
                address: args.address,
                isError: !parsedResult.success,
              };
            }
          }
    
          case "directions": {
            const resultStr = await runAppleScriptSafely(
              "maps_directions.scpt",
              [args.fromAddress, args.toAddress, args.transportType || "driving"]
            );
            const parsedResult: DirectionsResponse = JSON.parse(resultStr);
    
            let detailedText =
              parsedResult.message || "Directions request completed";
    
            if (parsedResult.success && parsedResult.route) {
              detailedText += "\n\n";
              detailedText += `• From: ${parsedResult.route.startAddress}\n`;
              detailedText += `• To: ${parsedResult.route.endAddress}\n`;
              detailedText += `• Distance: ${parsedResult.route.distance}\n`;
              detailedText += `• Duration: ${parsedResult.route.duration}\n`;
              detailedText += `• Transport Type: ${
                args.transportType || "driving"
              }\n`;
            }
    
            return {
              content: [{ type: "text", text: detailedText }],
              success: parsedResult.success,
              route: parsedResult.route,
              fromAddress: args.fromAddress,
              toAddress: args.toAddress,
              transportType: args.transportType || "driving",
              isError: !parsedResult.success,
            };
          }
    
          case "listGuides": {
            const resultStr = await runAppleScriptSafely(
              "maps_list_guides.scpt"
            );
            const parsedResult: ListGuidesResponse = JSON.parse(resultStr);
    
            let detailedText = parsedResult.message || "Guide listing completed";
    
            if (parsedResult.success && parsedResult.guides && Array.isArray(parsedResult.guides) && parsedResult.guides.length > 0) {
              detailedText += "\n\n";
              detailedText += "Available guides:\n";
              parsedResult.guides.forEach((guide: any, index: number) => {
                detailedText += `${index + 1}. ${guide.name} (${
                  guide.itemCount
                } items)\n`;
              });
            }
    
            return {
              content: [{ type: "text", text: detailedText }],
              success: parsedResult.success,
              guides: parsedResult.guides,
              isError: !parsedResult.success,
            };
          }
    
          case "addToGuide": {
            const resultStr = await runAppleScriptSafely(
              "maps_add_to_guide.scpt",
              [args.address, args.guideName]
            );
            const parsedResult: GuideManipulationResponse = JSON.parse(resultStr);
            return {
              content: [{ type: "text", text: parsedResult.message || "Add to guide operation completed" }],
              success: parsedResult.success,
              guideName: parsedResult.guideName || args.guideName,
              locationName: parsedResult.locationName,
              address: args.address,
              isError: !parsedResult.success,
            };
          }
    
          case "createGuide": {
            const resultStr = await runAppleScriptSafely(
              "maps_create_guide.scpt",
              [args.guideName]
            );
            const parsedResult: GuideManipulationResponse = JSON.parse(resultStr);
    
            return {
              content: [{ type: "text", text: parsedResult.message || "Create guide operation completed" }],
              success: parsedResult.success,
              guideName: parsedResult.guideName || args.guideName,
              isError: !parsedResult.success,
            };
          }
    
          case "getCenter": {
            // Use the correct script filename
            const resultStr = await runAppleScriptSafely(
              "/Users/zach/Dev/MCP/apple/src/scripts/getMapCenterCoordinates.applescript"
            );
            const parsedResult: CenterResponse = JSON.parse(resultStr);
    
            let detailedText =
              parsedResult.message || "Get center operation completed";
    
            if (
              parsedResult.success &&
              parsedResult.latitude !== undefined &&
              parsedResult.longitude !== undefined
            ) {
              detailedText += "\n\n";
              detailedText += `• Latitude: ${parsedResult.latitude.toFixed(6)}\n`;
              detailedText += `• Longitude: ${parsedResult.longitude.toFixed(6)}\n`;
              detailedText += `• Google Maps Link: https://www.google.com/maps?q=${parsedResult.latitude},${parsedResult.longitude}`;
            }
    
            return {
              content: [
                {
                  type: "text",
                  text: detailedText,
                },
              ],
              success: parsedResult.success,
              latitude: parsedResult.latitude,
              longitude: parsedResult.longitude,
              isError: !parsedResult.success,
            };
          }
    
          case "setCenter": {
            // Convert numbers to strings and make sure they're properly formatted
            const latitudeStr = String(args.latitude);
            const longitudeStr = String(args.longitude);
            
            const resultStr = await runAppleScriptSafely(
              "/Users/zach/Dev/MCP/apple/src/scripts/setMapCenterCoordinates.applescript",
              [latitudeStr, longitudeStr]
            );
            const parsedResult: CenterResponse = JSON.parse(resultStr);
    
            let detailedText =
              parsedResult.message || "Set center operation completed";
    
            if (parsedResult.success) {
              detailedText += "\n\n";
              detailedText += `• Latitude: ${args.latitude.toFixed(6)}\n`;
              detailedText += `• Longitude: ${args.longitude.toFixed(6)}\n`;
              detailedText += `• Google Maps Link: https://www.google.com/maps?q=${args.latitude},${args.longitude}`;
            }
    
            return {
              content: [
                {
                  type: "text",
                  text: detailedText,
                },
              ],
              success: parsedResult.success,
              latitude: args.latitude,
              longitude: args.longitude,
              isError: !parsedResult.success,
            };
          }
    
          default:
            throw new Error(`Unknown maps operation: ${(args as any).operation}`);
        }
      } catch (error) {
        // Provide more detailed error information
        const errorMessage = error instanceof Error ? error.message : String(error);
        
        // Check if the error is related to script path
        let additionalInfo = "";
        if (errorMessage.includes("No such file or directory") || 
            errorMessage.includes("cannot find") || 
            errorMessage.includes("unknown token")) {
          additionalInfo = "\n\nPossible issues:\n" +
                          "- The AppleScript files may not be in the expected locations\n" +
                          "- The AppleScript files may not have correct permissions\n" +
                          "- The script execution syntax may need adjustment for your environment";
        }
        
        return {
          content: [
            {
              type: "text",
              text: `Error in maps tool: ${errorMessage}${additionalInfo}`,
            },
          ],
          isError: true,
        };
      }
    }
  • Zod schema (MapsArgsSchema) for input validation of maps tool arguments, defining discriminated union based on 'operation'.
    export const MapsArgsSchema = z.discriminatedUnion("operation", [
      z.object({
        operation: z.literal("search"),
        query: z.string().min(1),
        limit: z.number().optional(),
      }),
      z.object({
        operation: z.literal("save"),
        name: z.string().min(1),
        address: z.string().min(1),
      }),
      z.object({
        operation: z.literal("pin"),
        name: z.string().min(1),
        address: z.string().min(1),
      }),
      z.object({
        operation: z.literal("directions"),
        fromAddress: z.string().min(1),
        toAddress: z.string().min(1),
        transportType: z.enum(["driving", "walking", "transit"]).optional(),
      }),
      z.object({ operation: z.literal("listGuides") }),
      z.object({
        operation: z.literal("addToGuide"),
        address: z.string().min(1),
        guideName: z.string().min(1),
      }),
      z.object({
        operation: z.literal("createGuide"),
        guideName: z.string().min(1),
      }),
      z.object({ operation: z.literal("getCenter") }),
      z.object({
        operation: z.literal("setCenter"),
        latitude: z.number(),
        longitude: z.number(),
      }),
    ]);
  • tools.ts:259-315 (registration)
    MCP protocol Tool registration for 'maps': defines name, description, and JSON inputSchema advertised to clients.
    const MAPS_TOOL: Tool = {
      name: "maps",
      description: "Search locations, manage guides, save favorites, and get directions using Apple Maps",
      inputSchema: {
        type: "object",
        properties: {
          operation: {
            type: "string",
            description: "Operation to perform with Maps",
            enum: ["search", "save", "directions", "pin", "listGuides", "addToGuide", "createGuide", "getCenter", "setCenter"] // Add setCenter
          },
          query: {
            type: "string",
            description: "Search query for locations (required for search)"
          },
          limit: {
            type: "number",
            description: "Maximum number of results to return (optional for search)"
          },
          name: {
            type: "string",
            description: "Name of the location (required for save and pin)"
          },
          address: {
            type: "string",
            description: "Address of the location (required for save, pin, addToGuide)"
          },
          fromAddress: {
            type: "string",
            description: "Starting address for directions (required for directions)"
          },
          toAddress: {
            type: "string",
            description: "Destination address for directions (required for directions)"
          },
          transportType: {
            type: "string",
            description: "Type of transport to use (optional for directions)",
            enum: ["driving", "walking", "transit"]
          },
          guideName: {
            type: "string",
            description: "Name of the guide (required for createGuide and addToGuide)"
          },
          // Add parameters for setCenter
          latitude: {
            type: "number",
            description: "Latitude for the center point (required for setCenter)"
          },
          longitude: {
            type: "number",
            description: "Longitude for the center point (required for setCenter)"
          }
        },
        required: ["operation"]
      }
    };
  • index.ts:144-147 (registration)
    Dispatch logic in main server CallToolRequest handler: parses args with MapsArgsSchema and calls handleMaps.
    case "maps": {
      const validatedArgs = MapsArgsSchema.parse(args);
      return await handleMaps(validatedArgs, loadModule);
    }
  • Helper module 'maps' with JXA functions for Maps operations (loaded by loadModule but not used in current handler implementation).
    const maps = {
        searchLocations,
        saveLocation,
        getDirections,
        dropPin,
        listGuides,
        addToGuide,
        createGuide,
        getMapCenterCoordinates, // Keep the (non-functional) getter for now
        setMapCenterCoordinates  // Add the new setter function
    };
    
    export default maps;
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries full burden for behavioral disclosure. It mentions what the tool can do but fails to describe critical behavioral traits like authentication requirements, rate limits, data persistence for saved items, whether operations are read-only or mutative, or what the return format looks like for different operations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single, efficient sentence that lists all major functions without unnecessary words. It's appropriately sized and front-loaded with the core capabilities, making every word earn its place.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a complex tool with 11 parameters, 9 distinct operations, no annotations, and no output schema, the description is insufficient. It doesn't explain how operations differ, what results to expect, error conditions, or behavioral constraints. The description fails to compensate for the lack of structured metadata.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The description mentions four functional areas (search, manage guides, save favorites, get directions) which loosely map to some of the 11 parameters, but with 100% schema description coverage, the schema already documents all parameters thoroughly. The description adds minimal value beyond what's in the schema, meeting the baseline for high coverage.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool's purpose with specific verbs (search, manage, save, get) and resources (locations, guides, favorites, directions) using Apple Maps. However, it doesn't differentiate this multi-function tool from its siblings like webSearch or calendar, which would require a 5.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides no guidance on when to use this tool versus alternatives like webSearch for general searches or other mapping tools. It lists functions but gives no context about appropriate use cases, prerequisites, or exclusions.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Related Tools

Latest Blog Posts

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/wearesage/mcp-apple'

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