This file is the canonical context for any AI coding agent (Claude Code, Cursor, Codex, Zed AI, Gemini, etc.) working in this repository. CLAUDE.md is a symlink to this file — edit AGENTS.md only.
A coordinating monorepo for marketplace plugins / extensions that install the Latitude MCP server into the major AI coding agents and IDEs.
- Latitude is an open-source AI agent monitoring platform. Full observability into what's failing in production: discover underlying issues, get alerts when something breaks, verify your fix worked.
- The MCP server lives at
https://api.latitude.so/v1/mcp— a remote, OAuth-authenticated (OAuth 2.1 + DCR), streamable HTTP MCP server. - Tools exposed by the MCP are dynamically generated from the Latitude API, so the catalog stays in sync automatically.
- MCP user docs: https://docs.latitude.so/getting-started/mcp
- API reference: https://api.latitude.so/docs
- This monorepo holds shared assets (
assets/), agent-facing docs (docs/,AGENTS.md), and a working tree for all platforms. - Each
<platform>/directory is intended to live in its own public Git repository (e.g.latitude-cursor,latitude-claude,latitude-codex,latitude-zed,latitude-antigravity) so marketplace submissions can point at a single-purpose repo. - The per-platform repos are wired back into this monorepo as git submodules so all five can be edited together from one working copy.
- Therefore: every
<platform>/folder must remain self-contained — its ownLICENSE,.gitignore,README.md, andassets/— with zero references that escape upward (no../LICENSE, no../assets/icon-light.png).
- Only one plugin per platform, named "Latitude". No additional plugins.
- Ship only the MCP server registration. Do not add skills, rules, agents, commands, hooks, LSPs, monitors, settings, or any other plugin component. The goal is a clean one-click MCP installer — nothing else.
- Don't bundle or vendor the MCP server. Latitude is a remote HTTPS service. Each plugin only registers the URL with the host.
- Don't hardcode any credentials. Auth is OAuth 2.1 with Dynamic Client Registration (DCR) — the client registers itself with the MCP server on first connect. The plugin only ever ships the URL. No client ID, no client secret, no API key.
- URL-only configs wherever the host supports remote HTTP MCP. Cursor, Claude Code, Codex, and Antigravity all do — use their native HTTP transport, not the
mcp-remotebridge. Zed's extension API only supports spawn-based context servers, so the Zed extension is the one exception and must usemcp-remote. - Each
<platform>/folder must stay self-contained. Each one will eventually live in its own public repo, wired back into this monorepo as a submodule. So: no upward references from inside<platform>/— no../LICENSE, no../assets/..., no../docs/.... Every per-platform repo carries its ownLICENSE,.gitignore,README.md, andassets/. Agent-facing docs (AGENTS.md,docs/) live only in the monorepo and do not belong inside any<platform>/. - The
CLAUDE.mdat the monorepo root is a symlink toAGENTS.md. EditAGENTS.mdonly — do not delete the symlink or create a separateCLAUDE.md. Do not createCLAUDE.mdorAGENTS.mdinside any<platform>/folder.
mcps/ # ← this monorepo (the coordinating repo)
├── cursor/ # → submodule: latitude-cursor
│ ├── .cursor-plugin/plugin.json
│ ├── mcp.json # Latitude MCP server config
│ ├── assets/ # Logos (own copy)
│ ├── .gitignore
│ ├── LICENSE
│ └── README.md
├── claude/ # → submodule: latitude-claude
│ ├── .claude-plugin/plugin.json
│ ├── .mcp.json # Latitude MCP server config (note leading dot)
│ ├── assets/
│ ├── .gitignore
│ ├── LICENSE
│ └── README.md
├── codex/ # → submodule: latitude-codex
│ ├── .codex-plugin/plugin.json
│ ├── .mcp.json # Latitude MCP server config
│ ├── assets/
│ ├── .gitignore
│ ├── LICENSE
│ └── README.md
├── zed/ # → submodule: latitude-zed
│ ├── extension.toml
│ ├── Cargo.toml
│ ├── src/lib.rs
│ ├── assets/
│ ├── .gitignore
│ ├── LICENSE
│ └── README.md
├── antigravity/ # → submodule: latitude-antigravity
│ ├── plugin.json
│ ├── mcp_config.json
│ ├── assets/
│ ├── .gitignore
│ ├── LICENSE
│ └── README.md
├── assets/ # Canonical light/dark icons (source of truth)
│ ├── icon-light.png
│ └── icon-dark.png
├── docs/ # Plugin-system references + plan
│ ├── plan.md
│ ├── cursor.md
│ ├── claude.md
│ ├── codex.md
│ ├── zed.md
│ └── antigravity.md
├── .gitignore
├── LICENSE
├── README.md
├── AGENTS.md # ← this file
└── CLAUDE.md → AGENTS.md # symlink (monorepo-only)
Icons are duplicated into each <platform>/assets/ directory because each plugin must be self-contained — marketplaces clone or zip each plugin in isolation, and once the platform folder is its own repo there's no parent assets/ to reach into. The canonical copies live at /assets/; the duplicates in each platform folder are kept in sync with those.
- name:
Latitude - title:
Latitude - description:
Open-source AI agent monitoring platform. Full observability into what's failing in production. Discover underlying issues, get alerts when something breaks and verify your fix worked. - websiteUrl:
https://latitude.so - icons:
- light:
https://framerusercontent.com/images/fPQsqC1Gx3CiQElnbBSmbQVYcA.png - dark:
https://framerusercontent.com/images/l5c1DNVxQ3iAvTDihvg9pFw2l2k.png
- light:
- instructions:
All Latitude MCP methods have descriptions and input/output schemas. For any doubt visit the Latitude documentation: https://docs.latitude.so/llms.txt
Local copies of the light/dark icons sit at the repo root and inside each plugin's assets/ directory. Plugins should reference the local copy (relative path) rather than the framerusercontent URL, because most marketplaces require committed assets.
Deeper references for each host's plugin system live under docs/: docs/cursor.md, docs/claude.md, docs/codex.md, docs/zed.md, docs/antigravity.md. The implementation + submission roadmap is in docs/plan.md.
- Layout: single-plugin (plugin at folder root, no
.cursor-plugin/marketplace.json). - MCP transport: native remote URL — Cursor supports
{ "url": "..." }directly inmcp.json. No bridge needed. - Logo field:
logoinplugin.json, relative path resolves against the plugin root. - Local test:
ln -s "$(pwd)/cursor" ~/.cursor/plugins/local/latitude, then restart Cursor and check Cursor Settings → Features → Model Context Protocol. - Submit: https://cursor.com/marketplace/publish with the public repo URL. Manual review.
- Layout: plugin at folder root, manifest at
.claude-plugin/plugin.json. - MCP transport: native HTTP —
{ "type": "http", "url": "https://api.latitude.so/v1/mcp" }in.mcp.json. Claude Code's MCP layer supports remote streamable HTTP servers with OAuth + DCR, so nomcp-remotebridge is needed and there's no Node.js prerequisite.- Caveat: Latitude's manual-install docs still recommend
mcp-remotefor the Claude Desktop chat app (a separate product from Claude Code) because that app doesn't speak remote OAuth MCP yet. That doesn't affect this plugin — Claude Code plugins target Claude Code, not Claude Desktop.
- Caveat: Latitude's manual-install docs still recommend
- Don't put
skills/,agents/,commands/,hooks/,monitors/,bin/,settings.json,.lsp.jsoninside the plugin. None are needed. - Local test:
claude --plugin-dir ./claude, then/mcp, selectlatitude, authenticate. - Submit: https://claude.ai/settings/plugins/submit or https://platform.claude.com/plugins/submit. CI runs
claude plugin validate— run it locally first.
- Layout: plugin at folder root, manifest at
.codex-plugin/plugin.json. - MCP transport: native HTTP. The Codex CLI manual install uses
transport = "http"+url = "..."in TOML; the same key names work in JSON.mcp.json. Usemcp_servers(snake_case) as the top-level key. - Local test: copy or symlink into
~/.codex/plugins/latitude/, add an entry to~/.agents/plugins/marketplace.jsonpointing at the folder, restart Codex, then enable the plugin. The$plugin-creatorskill inside Codex can scaffold a local marketplace entry for you. - Submit: self-serve publishing is "coming soon" per Codex docs. Track https://developers.openai.com/codex/plugins/build. For now, the plugin is publish-ready locally; submission has to wait.
- Layout: a Git repository containing
extension.toml+ a Rust crate compiled to Wasm. Submitted as a Git submodule underextensions/latitude/in https://github.com/zed-industries/extensions. - MCP transport: Zed's extension API only supports spawn-based context servers (
zed_extension_api::Command). Zed's settings file accepts aurl-only context server, but the extension API does not — there's no way to register a remote URL from extension code. So this is the one plugin in the monorepo that has to bridge viamcp-remote.- Implementation:
context_server_commandreturnsnpx -y mcp-remote https://api.latitude.so/v1/mcp. - Document the Node.js prerequisite in
zed/README.md. - DCR still works through the bridge —
mcp-remotedoes the OAuth/DCR dance with the remote MCP and exposes a stdio transport locally.
- Implementation:
- License: extension repo must contain one of the accepted licenses (MIT works). The license file goes at the extension root.
- Naming: extension
idmust not contain "zed" or "extension". Uselatitude— the "popular tooling" exception in the Zed docs covers brand-name IDs. - Local test: open Extensions panel → "Install Dev Extension" → pick
zed/. Usezed --foregroundto seedbg!/println!output. - Submit: PR to https://github.com/zed-industries/extensions adding the extension as a submodule pointing at this repo's eventual
zed/mirror, plus an entry inextensions.toml. The submodule must use HTTPS, be public, and point at a branch (not a detached commit).
-
Layout: plugin folder containing
plugin.json(marker file) +mcp_config.jsonat the root. No.<platform>-plugin/subdir — Antigravity discovers plugins by the presence of theplugin.jsonmarker file directly at the plugin root. -
Manifest: minimal —
{ "name": "latitude" }. Thenamefield is optional and defaults to the directory name; we set it explicitly. -
MCP config field name:
serverUrl, noturl. Antigravity's schema is its own — don't copy Cursor/Claude/Codex shapes. -
MCP transport: native streamable HTTP —
{ "mcpServers": { "latitude": { "serverUrl": "https://api.latitude.so/v1/mcp" } } }. Antigravity handles OAuth + DCR automatically; no client ID/secret, no bridge. -
Don't put
skills/,rules/, orhooks.jsonin the plugin. None are needed. -
Local test: copy
antigravity/into one of:~/.gemini/config/plugins/latitude/(editor, global)~/.gemini/antigravity-cli/plugins/latitude/(CLI, global)<workspace>/.agents/plugins/latitude/(workspace)
Then restart Antigravity → Customizations tab → Authenticate next to
latitude. -
Submit: no public third-party submission flow yet. The bundled marketplace ("Build with Google") is curated by Google. For now the distribution path is users copying the folder into one of the install locations above. Track https://antigravity.google/docs/plugins for changes.
- Folder structure inside
.claude-plugin/: onlyplugin.jsonbelongs there.skills/,agents/,hooks/, etc. live at the plugin root — never inside.claude-plugin/. Same pattern in.cursor-plugin/and.codex-plugin/. Antigravity is different — itsplugin.jsonsits directly at the plugin root, no hidden subdir. - MCP config field naming is not portable across hosts:
- Cursor / Antigravity raw config —
url(Cursor) vsserverUrl(Antigravity). - Claude Code —
type: "http"+url. - Codex —
transport: "http"+url, withmcp_servers(snake_case) at the top level. - Zed extensions — Rust
Commandonly; no URL field via the extension API. Don't copy one host's MCP config into another's file.
- Cursor / Antigravity raw config —
- Cursor single-plugin layout: no
marketplace.jsonfor a single plugin. The plugin folder is the repo root for Cursor's purposes (in this monorepo, that's thecursor/subdir). - Zed extension shipping a server binary: not allowed. Always spawn from the user environment.
- Zed extension IDs: must avoid
zedandextensionsubstrings; descriptive suffixes (-theme,-snippets) are recommended except for "popular tooling" brands, where the bare brand id is acceptable. We uselatitude. - Icon paths: must be relative to the plugin folder and committed to the repo. Don't reference the framerusercontent URLs from manifests — marketplaces won't accept that.
- Symlinks for assets: avoid them. Marketplaces clone/zip plugins and symlinks travel poorly. Copy icons into each plugin's
assets/.
- Start each plugin at
0.1.0. Bump per plugin independently as each marketplace ships updates. - Latitude API version doesn't need to match — the MCP server URL is stable, so plugins don't need to be re-released when the API ticks.
- For Claude Code, omitting
versioninplugin.jsonfalls back to commit SHA = every commit ships as a new version. Setting an explicitversionis preferred for controlled releases.
scripts/validate-all.sh at the monorepo root runs every platform's validator in turn and prints a pass/fail summary. Run it from anywhere; it works out the repo root from its own location.
./scripts/validate-all.shWhat each platform's validator does, and how to run it standalone:
| Platform | Validator | Run standalone |
|---|---|---|
| Cursor | JSON parse + cursor-agent --plugin-dir <dir> smoke-load. Cursor ships no validate subcommand. |
cursor-agent --plugin-dir cursor --trust -p ok --mode ask |
| Claude Code | claude plugin validate — ships with the claude CLI. Strict; rejects unknown manifest keys. |
claude plugin validate claude |
| Codex | Python script validate_plugin.py from openai/codex (vendored on first run into .cache/). Enforces required interface.* fields, hex brandColor, mcpServers keying, etc. |
python3 .cache/codex-validate_plugin.py codex |
| Zed | cargo build --target wasm32-wasip2 --release (compile = validation) + ID/name rules ported from zed-industries/extensions/src/lib/validation.js + LICENSE-present check. |
cargo build --manifest-path zed/Cargo.toml --target wasm32-wasip2 --release |
| Antigravity | JSON parse + structural check that mcp_config.json has a mcpServers map with serverUrl or command per server. Antigravity has no published CLI validator. |
(run validate-all.sh) |
- Claude
displayNamerejection:claude plugin validatein v2.1.141 reportsUnrecognized key: "displayName"even though the docs say it was added in v2.1.143. Until 2.1.143 ships, leavedisplayNameout ofclaude/.claude-plugin/plugin.json. - Codex first-run cost: the first invocation downloads
validate_plugin.pyand provisions apyyamlvenv under.cache/. Subsequent runs are instant. The.cache/directory is gitignored. - Zed build target:
wasm32-wasip2is the current Zed extension target. Older docs referencewasm32-wasip1which also works;rustup target add wasm32-wasip2may be needed. - Cursor local installs don't follow symlinks: Cursor's plugin docs suggest
ln -s … ~/.cursor/plugins/local/<name>, but stable Cursor (≥ 3.4.20 tested) silently skips symlinks. Usecp -Rand re-copy when iterating. - Cursor local installs don't render
logo/description: Cursor resolves the manifest's relativelogopath to araw.githubusercontent.comURL using the marketplace's repo+SHA record, which doesn't exist for local installs. Description and icon both surface only after marketplace publishing. Don't chase this during local testing — it's expected.
Once each <platform>/ is its own public repo wired back in as a submodule, normal git operations inside a platform folder act on the submodule's repo, not the monorepo. Workflow:
# Initial bring-up
git clone --recurse-submodules <monorepo>
# Or, if cloned already:
git submodule update --init --recursive
# Pick up latest from every platform repo
git submodule update --remote --merge
# Edits inside <platform>/:
cd cursor
git checkout main # avoid detached HEAD
# … make changes …
git add . && git commit -m "..." && git push
cd ..
git add cursor && git commit -m "bump cursor submodule" && git pushCommon pitfalls when working as an AI agent:
- After editing files in a
<platform>/folder, rungit statusboth from the platform folder (which sees the submodule's working tree) and from the monorepo root (which sees the gitlink change). Both need their own commit. - A submodule sitting on a detached HEAD will accept commits but won't push them anywhere useful — always check the branch first.
- Don't commit a monorepo gitlink that points at an unpushed submodule commit. Push the submodule first.
- Logos / branding: update
assets/icon-light.png/assets/icon-dark.pngat the monorepo root (source of truth), then re-copy into each<platform>/assets/. Don't change one without the others. - MCP URL or auth model: this is set by the Latitude backend. If it ever changes, every plugin's MCP config has to be updated in lockstep.
- Adding a new host platform: create a sibling folder. Add a
docs/<platform>.mdreference and updatedocs/plan.mdandREADME.md. Mirror the constraint set above (single plugin, MCP-only).