# WebMCP tools

In browsers that support [WebMCP](https://github.com/webmachinelearning/webmcp), the playground page registers these tools with `document.modelContext.registerTool()`. Older builds that expose `navigator.modelContext` are used as a fallback. Without either, nothing is registered and the page works as normal.

## For AI agents

Pick the way that fits your tools:

1. **Over HTTP only:** read these docs and build playground links from the URL keys below. Plain HTTP cannot run the tools.
2. **In a browser without native WebMCP**, which is most headless and agent browsers: open a link with URL keys, or capture the tools with a stub before the page loads, then call each tool's `execute(input)`. With Playwright:

```js
await page.addInitScript(() => {
  window.__tools = {};
  document.modelContext = { registerTool: (tool) => { window.__tools[tool.name] = tool; } };
});
await page.goto('https://topographic.yahyapak.dev/?analytics_agent=claude&analytics_purpose=exploring');
await page.waitForFunction(() => window.__tools?.get_settings);
const out = await page.evaluate(() => window.__tools.set_settings.execute({ shape: 'dunes', mode: '3d' }));
```

   `execute` returns a promise of the tool's result. Puppeteer works the same with `page.evaluateOnNewDocument`.
3. **In a browser with native WebMCP:** call the tools listed below.

**A small request.** If you are happy to, add `?analytics_agent=<name>&analytics_purpose=<purpose>` to the first playground URL you open, or call `identify_agent` once. It tells the maintainer that an agent visited and why, so agent visits are counted apart from people. It is optional, and the playground works the same either way. Only these two values are recorded, from fixed lists, and nothing about the person you work for. Use `testing` while you develop or check this site, and `demo` for a demo or video. To send nothing at all, add `?analytics_optout=all` instead. The About page, https://topographic.yahyapak.dev/about, says what is collected.

- Names: `claude`, `chatgpt`, `gemini`, `jules`, `copilot`, `cursor`, `windsurf`, `opencode`, `pi`, `amp`, `droid`, `cline`, `roo`, `kilo`, `aider`, `goose`, `kiro`, `devin`, `manus`, `grok`, `perplexity`, `other`. `chatgpt` covers ChatGPT and Codex. Use `other` when yours is not listed.
- Purposes: `testing`, `demo`, `creating`, `exploring`, `integrating`, `research`, `reverse-engineering`, `other`.

Written against the spec at webmachinelearning/webmcp commit `d0e4e0e0f039358cc9d93f90074116622bfebd53` (https://github.com/webmachinelearning/webmcp/blob/d0e4e0e0f039358cc9d93f90074116622bfebd53/index.bs). Tools are removed by aborting their registration `AbortSignal`, and read-only tools set `annotations.readOnlyHint`.

- `identify_agent`: Optional. Tells this site an AI agent is using it, so agent use is counted apart from people. Only your agent's name and a purpose from the lists are recorded, as anonymous analytics; nothing about the person you work for. Call it once, if you like; the playground works the same either way.
- `list_shapes` (read-only): Lists the shapes, with ids, names and descriptions, and the looks, with ids, names and settings. Pass a look id to set_settings as look.
- `get_settings` (read-only): Returns the current settings: the full resolved object, the minimal config JSON (only non-default values), and the graphics tier actually drawing (quality, fps, frameMs, mesh).
- `set_settings`: Changes the background. Takes a partial settings object; values are merged into the current settings, clamped to their ranges, and applied live. A new theme.mode clears the earlier ink and paper unless you give them in the same call, as the page's theme button does. Returns the resulting config and any adjustments made.
- `randomize`: Picks a new random seed. With shape: true, also picks a random shape.
- `take_screenshot` (read-only): Renders the current background to a PNG and returns it as a data URL. Defaults to 1200 px wide at the screen aspect ratio. With region, only that slice of the current view, at full detail (for example a phone-shaped strip).
- `record_video` (read-only): Renders a short 3D clip of the current terrain with a moving camera. Returns an MP4 or WebM data URL. Defaults to a 2 second orbit at 640×360.
- `get_embed_code` (read-only): Returns code that reproduces the current background: html (web component), react, vanilla (JS) or json (config).
- `get_install_instructions` (read-only): Returns install and setup instructions for npm, the web component, React or vanilla JS.

## Input schemas

### identify_agent

```json
{
  "type": "object",
  "additionalProperties": false,
  "required": [
    "name",
    "purpose"
  ],
  "properties": {
    "name": {
      "type": "string",
      "enum": [
        "claude",
        "chatgpt",
        "gemini",
        "jules",
        "copilot",
        "cursor",
        "windsurf",
        "opencode",
        "pi",
        "amp",
        "droid",
        "cline",
        "roo",
        "kilo",
        "aider",
        "goose",
        "kiro",
        "devin",
        "manus",
        "grok",
        "perplexity",
        "other"
      ],
      "description": "Your agent or harness. chatgpt covers ChatGPT and Codex. Use other when yours is not listed."
    },
    "purpose": {
      "type": "string",
      "enum": [
        "testing",
        "demo",
        "creating",
        "exploring",
        "integrating",
        "research",
        "reverse-engineering",
        "other"
      ],
      "description": "testing: developing or checking this site. demo: recording a demo, video or screenshots of topographic. creating: making a background, image, video or 3D view for someone. exploring: trying styles, places and settings. integrating: getting code to embed in a site or app. research: evaluating or comparing tools. reverse-engineering: studying how the playground or the library is built. other: anything else."
    }
  }
}
```

### list_shapes

```json
{
  "type": "object",
  "properties": {}
}
```

### get_settings

```json
{
  "type": "object",
  "properties": {}
}
```

### set_settings

```json
{
  "type": "object",
  "additionalProperties": false,
  "properties": {
    "look": {
      "type": "string",
      "enum": [
        "survey-ridge",
        "fine-granite",
        "wide-lobes",
        "rose-pod",
        "terraces",
        "high-dunes",
        "moss-granite",
        "sea-dunes",
        "night-flower",
        "night-lobes",
        "violet-pod",
        "ink-petals",
        "stepped-petals",
        "day-lobes",
        "still-dunes",
        "rocky-spine",
        "pale-dunes",
        "stone-grain",
        "open-pod",
        "quiet-hill",
        "bloom",
        "soft-summit",
        "paper-ridge",
        "centred-lobes",
        "survey-map",
        "rocky"
      ],
      "description": "A named design from list_shapes. It sets the shape, seed, lines, terrain, motion, layout, colours and camera pose, and keeps the view. The other keys in the same call override its values."
    },
    "shape": {
      "type": "string",
      "enum": [
        "drifting-lobes",
        "granite",
        "five-petals",
        "seed-pod",
        "gentle-summit",
        "long-ridge",
        "dunes"
      ],
      "description": "The land shape of the procedural terrain."
    },
    "style": {
      "type": "string",
      "enum": [
        "drifting-lobes",
        "granite",
        "five-petals",
        "seed-pod",
        "gentle-summit",
        "long-ridge",
        "dunes"
      ],
      "description": "Old name of shape, accepted until the next release. Use shape."
    },
    "seed": {
      "type": "integer",
      "minimum": 0,
      "maximum": 999999,
      "description": "Same seed, same terrain. 0 is the original look."
    },
    "lines": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "count": {
          "type": "integer",
          "minimum": 6,
          "maximum": 60,
          "description": "How many contour lines cross the field."
        },
        "width": {
          "type": "number",
          "minimum": 0,
          "maximum": 1,
          "description": "Stroke width of each contour line."
        },
        "opacity": {
          "type": "number",
          "minimum": 0,
          "maximum": 1,
          "description": "How strongly lines show against the paper."
        },
        "indexEvery": {
          "type": "integer",
          "minimum": 0,
          "maximum": 10,
          "description": "Every Nth line is drawn bolder, like the index contours on a map. 0 turns it off."
        }
      }
    },
    "terrain": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "peaks": {
          "type": "integer",
          "minimum": 1,
          "maximum": 6,
          "description": "Number of summits merged into the terrain."
        },
        "spread": {
          "type": "number",
          "minimum": 0,
          "maximum": 1,
          "description": "How far extra peaks sit from the main summit."
        },
        "roughness": {
          "type": "number",
          "minimum": 0,
          "maximum": 1,
          "description": "Noise added to the slopes, from smooth hills to rocky ground."
        },
        "sharpness": {
          "type": "number",
          "minimum": 0,
          "maximum": 1,
          "description": "Below 0.5 summits flatten; above 0.5 they sharpen into peaks."
        },
        "terracing": {
          "type": "number",
          "minimum": 0,
          "maximum": 1,
          "description": "Groups lines into stepped bands."
        },
        "variant": {
          "type": "number",
          "minimum": 0,
          "maximum": 1,
          "description": "A knob that depends on the shape (for drifting lobes: three lobes to four)."
        }
      }
    },
    "motion": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "speed": {
          "type": "number",
          "minimum": 0,
          "maximum": 3,
          "description": "Animation speed. 0 holds a still frame."
        },
        "warp": {
          "type": "number",
          "minimum": 0,
          "maximum": 1,
          "description": "How much the lines wobble with the flow."
        },
        "sparkle": {
          "type": "number",
          "minimum": 0,
          "maximum": 1,
          "description": "Small bright dots travelling along the lines."
        }
      }
    },
    "layout": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "anchor": {
          "type": "number",
          "minimum": 0,
          "maximum": 1,
          "description": "0 keeps the summit to the side, 1 centres it."
        },
        "fade": {
          "type": "number",
          "minimum": 0,
          "maximum": 1,
          "description": "Fades lines behind a central text column."
        }
      }
    },
    "theme": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "mode": {
          "type": "string",
          "enum": [
            "auto",
            "light",
            "dark"
          ]
        },
        "ink": {
          "type": "string",
          "description": "CSS colour of the lines."
        },
        "paper": {
          "type": "string",
          "description": "CSS colour of the background, or 'transparent'."
        }
      }
    },
    "mode": {
      "type": "string",
      "enum": [
        "2d",
        "3d"
      ],
      "description": "2d is the map. 3d is the same map as a terrain. The library defaults to 2d; the playground opens a bare URL on Istanbul in 3d."
    },
    "elevation": {
      "type": "number",
      "minimum": 0,
      "maximum": 1,
      "description": "3D height. 0 is flat like the map; higher values make the terrain taller, with a rounded crest."
    },
    "extent": {
      "type": "string",
      "enum": [
        "block",
        "endless"
      ],
      "description": "block is a finite tabletop with cut sides. endless continues past the view. The playground defaults to endless."
    },
    "detail": {
      "type": "number",
      "minimum": 0,
      "maximum": 1,
      "description": "3D mesh resolution. 0 is coarsest, 1 is finest."
    },
    "quality": {
      "type": "string",
      "enum": [
        "auto",
        "low",
        "medium",
        "high"
      ],
      "description": "Graphics budget. auto follows the device and steps down if frames miss 60 Hz, or 120 Hz when that refresh is seen. low, medium and high stay put."
    },
    "camera": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "azimuth": {
          "type": "number",
          "description": "Degrees around the terrain. Any finite number."
        },
        "elevation": {
          "type": "number",
          "minimum": 2,
          "maximum": 80,
          "description": "Degrees above the horizon. Never below the terrain."
        },
        "radius": {
          "type": "number",
          "minimum": 0.4,
          "maximum": 6.5,
          "description": "Distance from the terrain centre."
        },
        "motion": {
          "type": "string",
          "enum": [
            "still",
            "orbit",
            "push",
            "flyover",
            "drone"
          ],
          "description": "How the camera moves. still holds the pose."
        },
        "speed": {
          "type": "number",
          "minimum": 0,
          "maximum": 3,
          "description": "How fast the camera moves along its path. 1 is the path's own pace, such as an orbit in about 26 seconds; 0 holds it."
        },
        "panX": {
          "type": "number",
          "minimum": -6,
          "maximum": 6,
          "description": "Moves the point the camera looks at east across the ground. 0 is the terrain centre; kept over the terrain."
        },
        "panZ": {
          "type": "number",
          "minimum": -6,
          "maximum": 6,
          "description": "Moves the point the camera looks at south across the ground. 0 is the terrain centre; kept over the terrain."
        }
      }
    }
  }
}
```

### randomize

```json
{
  "type": "object",
  "properties": {
    "shape": {
      "type": "boolean",
      "description": "Also randomize the shape."
    }
  }
}
```

### take_screenshot

```json
{
  "type": "object",
  "properties": {
    "width": {
      "type": "integer",
      "minimum": 16,
      "maximum": 4096
    },
    "height": {
      "type": "integer",
      "minimum": 16,
      "maximum": 4096
    },
    "region": {
      "type": "object",
      "additionalProperties": false,
      "description": "Part of the view, in fractions 0..1 from its top-left corner. Width or height alone keeps the region's shape.",
      "properties": {
        "x": {
          "type": "number",
          "minimum": 0,
          "maximum": 1
        },
        "y": {
          "type": "number",
          "minimum": 0,
          "maximum": 1
        },
        "width": {
          "type": "number",
          "minimum": 0,
          "maximum": 1
        },
        "height": {
          "type": "number",
          "minimum": 0,
          "maximum": 1
        }
      },
      "required": [
        "x",
        "y",
        "width",
        "height"
      ]
    }
  }
}
```

### record_video

```json
{
  "type": "object",
  "properties": {
    "format": {
      "type": "string",
      "enum": [
        "mp4",
        "webm"
      ]
    },
    "motion": {
      "type": "string",
      "enum": [
        "still",
        "orbit",
        "push",
        "flyover",
        "drone"
      ]
    },
    "seconds": {
      "type": "number",
      "minimum": 0.5,
      "maximum": 8
    },
    "width": {
      "type": "integer",
      "minimum": 160,
      "maximum": 1280
    },
    "height": {
      "type": "integer",
      "minimum": 160,
      "maximum": 720
    }
  }
}
```

### get_embed_code

```json
{
  "type": "object",
  "properties": {
    "format": {
      "type": "string",
      "enum": [
        "html",
        "react",
        "vanilla",
        "json"
      ]
    }
  },
  "required": [
    "format"
  ]
}
```

### get_install_instructions

```json
{
  "type": "object",
  "properties": {
    "target": {
      "type": "string",
      "enum": [
        "npm",
        "web-component",
        "react",
        "vanilla"
      ]
    }
  }
}
```

## Place tools

A region is `?region=west,south,east,north` in degrees, or JSON such as `{"bbox":[28.45,40.78,29.65,41.36]}`. Named examples: `place=istanbul` (Istanbul), `place=cyprus` (Cyprus), `place=matterhorn` (Matterhorn & Zermatt), `place=geirangerfjord` (Geirangerfjord), `place=grand-canyon` (Grand Canyon), `place=yosemite` (Yosemite Valley), `place=torres-del-paine` (Torres del Paine), `place=fuji` (Mount Fuji), `place=everest` (Everest), `place=kilimanjaro` (Kilimanjaro). Istanbul is the whole metro area, about 101 × 64 km, Cyprus the whole island, about 261 × 166 km, and each landscape a box of 30 to 80 km around its main feature. A region draws its real heights; the seed, the terrain params and `motion.warp` have no effect on it. In the page, the **Map** button picks a region, and its Apply also switches to 3D with `lc=36&lw=0&lo=0.9&ix=4&ex=block&ev=0.12&az=341.8&ce=39.3&rd=1.32`; Cyprus and each landscape set their own `ev`, `az` and `ce`. `set_region` sets the same view. A minimap above the dock shows the applied area; in 3D it turns with the camera, and its compass turns the camera back to north up, which is azimuth 0.

- `set_region`: Sets the map region that becomes the terrain. Give place, an id from the Map menu's catalog of about 100 places (the ten built-in ones are istanbul, cyprus, matterhorn, geirangerfjord, grand-canyon, yosemite, torres-del-paine, fuji, everest, kilimanjaro; an unknown id returns the full list), or a bbox in degrees. The same values are written to the page URL as ?place= for a named place, or ?region= for a box. Like Apply in the Map popover, it switches to 3D with the map terrain view: 36 lines, width 0, opacity 0.9, an index line every 4, a block at elevation 0.12, and the camera at azimuth 341.8, elevation 39.3, radius 1.32, pushing in and back at speed 0.2. Every place but Istanbul and the cities brings its own camera angle and elevation.
- `get_region` (read-only): Returns the current region, its height range, load status and the URL query. loading is true and message is "Loading terrain" while a shared place is still fetching, before the default terrain is shown.
- `clear_region`: Removes the region and returns to the procedural terrain and to the lines, camera, extent and elevation from before the region.

### set_region

```json
{
  "type": "object",
  "additionalProperties": false,
  "properties": {
    "place": {
      "type": "string",
      "description": "A place id from the Map menu's catalog, such as istanbul or fitz-roy. Ignored when a bbox is also given."
    },
    "west": {
      "type": "number",
      "description": "West edge, degrees."
    },
    "south": {
      "type": "number",
      "description": "South edge, degrees."
    },
    "east": {
      "type": "number",
      "description": "East edge, degrees."
    },
    "north": {
      "type": "number",
      "description": "North edge, degrees."
    },
    "bbox": {
      "type": "array",
      "items": {
        "type": "number"
      },
      "minItems": 4,
      "maxItems": 4,
      "description": "[west, south, east, north] in degrees."
    },
    "region": {
      "type": "string",
      "description": "west,south,east,north or JSON with a bbox array."
    }
  }
}
```

### get_region

```json
{
  "type": "object",
  "properties": {}
}
```

### clear_region

```json
{
  "type": "object",
  "properties": {}
}
```
