CTDS Chrome Extensions
7 tools over the Model Context Protocol.
The domain logic of the Chimney Top Chrome extensions (Window Organizer, Tab Link Copier, Pixel Ruler) and their store listings, without a browser: the tools take the windows, tabs, text or rectangles the user describes and return what the extension would show or do. Nothing here reads the user's real browser, storage or license; evaluate_entitlement is the policy on a record the caller supplies and never contacts the license service. Tab and window ids are the caller's; sizes are CSS pixels; times are ISO 8601.
Connect
The server is at https://chimneytopdigital.com/api/mcp/extensions. It is free, asks for no sign-in and keeps nothing between calls. Rate limits apply per address.
| Client | How |
|---|---|
| Claude Code | One command in a terminal: |
| Claude (web and desktop apps) | Settings → Connectors → Add custom connector, with the URL https://chimneytopdigital.com/api/mcp/extensions. No sign-in is asked for. |
| Codex CLI | One command in a terminal: |
| Any other MCP client | Use the Streamable HTTP transport with the URL https://chimneytopdigital.com/api/mcp/extensions: one JSON-RPC message per POST, JSON answers, no session and no authentication. |
To see the raw protocol, list the tools and call one from a terminal:
curl -s https://chimneytopdigital.com/api/mcp/extensions -H 'content-type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
curl -s https://chimneytopdigital.com/api/mcp/extensions -H 'content-type: application/json' \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"list_extensions","arguments":{}}}'Tools
Every tool is read-only and idempotent, so a client may run it without asking you first. Arguments are checked before a tool runs; a wrong one comes back as a message naming the field.
list_extensionsList extensionsThe Chrome extensions this app builds, from each extension.config.ts: name, version, description, launch status, store listing (category, tagline, overview, features, what it replaces), permissions with their justifications, pricing and changelog. Use it to answer what an extension does, needs or costs.
set(all | launch | retired | demo, default "launch"): Which extensions: the launch set (default), the retired ones, the reference demo, or all
window_overviewWindow and tab overviewWindow Organizer's view of a set of browser windows and tabs: windows focused first, tabs matching a search (title or address, case-insensitive), totals, the tabs it would close as duplicates (same address, fragment ignored; the active or pinned copy is kept) and, when asked, the tab order a sort by site or title would give. Pass the windows and tabs as the user describes them.
windows(array, required)query(string, default ""): Search text; empty shows every tabsort_by(domain | title): Also return each window's tab ids in this order (pinned tabs stay first)
parse_window_importParse a saved-windows import fileReads a JSON or CSV export from another window or tab manager the way Window Organizer's import does: the windows and tabs it would import (http and https pages only), or the errors that stop the import. Give the file text and, when known, its name (the extension decides JSON or CSV; otherwise the first character does).
text(string, required): The file contentsfilename(string): The file name, for format detection
format_tab_linksFormat tab linksTab Link Copier's output for a list of tabs: plain URLs, title and URL, Markdown links or HTML links, or a custom one-line template with $url, $title, $host and $index (a Pro feature in the extension). The copy options skip browser pages, repeated addresses and pinned tabs as the extension does.
tabs(array, required)format(urls | titled | markdown | html, default "urls"): Built-in format: urls (Plain URLs), titled (Title and URL), markdown (Markdown links), html (HTML links); ignored when template is giventemplate(string): Custom format, one line per tab, with $url, $title, $host or $index; \t and \n insert a tab or a line breakskip_non_web(boolean, default true): Leave out chrome://, file: and other non-web addressesdedupe(boolean, default true): Copy a repeated address onceinclude_pinned(boolean, default true)
extract_linksExtract web addresses from textEvery http and https address in a piece of pasted text (a list, an email, Markdown, HTML or JSON), in order and de-duplicated, as Tab Link Copier's "open pasted links" does: trailing punctuation trimmed, at most 100 addresses, and never a javascript:, data: or file: address.
text(string, required)
evaluate_entitlementEvaluate a cached license recordThe shared license policy of the extensions and the license-check service: given the last stored license answer and the time, whether the user has Pro right now, from which source (fresh, cache, offline grace, grace expired), until when the grace period runs, and whether the record is due for a re-check. Pure arithmetic on the record; it contacts no license service.
record(object): The last stored answer; omit for a user who never activated a keynow(string): The time to evaluate at (ISO 8601); defaults to the current timepolicy(client | service, default "client"): client: the extension (refresh after 12 h, 7 days of grace); service: the license-check Lambda (6 h, 3 days)just_verified(boolean, default false): True when the record came from the upstream in this same call
measure_rectMeasure a ruler rectanglePixel Ruler's readout for a rectangle on a page: the rectangle kept whole-numbered, at least 8 px across and inside the viewport, and its width, height and position in px or rem (rem uses the page's root font size, default 16 px).
rect(object, required): The rectangle in viewport pixelsviewport(object, required): The viewport size in pixelsunit(px | rem, default "px")root_font_px(number): The page's root font size in px, for rem
Limits
- The tools compute from the values the assistant passes in. They reach no stored data.
- Results carry the same notices the app shows. They are estimates and working aids, not professional advice; ask your assistant to show the notice with the figure.
- Rate limits apply per address. A request over the limit gets a plain error the assistant can read.
- The tool list may grow. This page is generated from the running server, so it shows the tools as they are today.
If something fails
- "Couldn't reach the MCP server", or a 404
- Check the address character by character: the server answers only at
https://chimneytopdigital.com/api/mcp/extensions, over HTTPS. Nothing else on this host speaks MCP. - 405 Method Not Allowed
- The client sent a GET. The server speaks the Streamable HTTP transport without an event stream: one JSON-RPC message per POST, a JSON answer, no session. A notification gets an empty 202.
- 429 Too Many Requests
- Your address passed the fair-use limit. The
Retry-Afterheader says how many seconds to wait; the limit resets on its own. - 413 Payload Too Large
- One request may carry 256 KiB. Send fewer rows per call, or summarize before you send.
- A tool answers with an error naming a field
- The argument did not match the tool's schema above: money is integer cents (or plain dollars where the tool says so), dates are
YYYY-MM-DD, and lists are JSON arrays. Fix the named field and call again; nothing was stored. - Still stuck
- Write to support@chimneytopdigital.com with the tool name, the arguments you sent (with nothing personal in them), the time in UTC and the exact message. We keep no record of calls, so what you send us is all we can look at.
How we built the servers, and the rules every one follows: our guide. Questions: hello@chimneytopdigital.com.