diff --git a/.cursor-plugin/marketplace.json b/.cursor-plugin/marketplace.json new file mode 100644 index 0000000..0918c28 --- /dev/null +++ b/.cursor-plugin/marketplace.json @@ -0,0 +1,18 @@ +{ + "name": "agent-switchboard", + "owner": { + "name": "Agent Switchboard", + "email": "barnir@agentmail.to" + }, + "metadata": { + "description": "The Agent Switchboard plugin for Cursor: search a curated directory of AI agents, MCP servers and agentic tools from the agent.", + "version": "1.0.0" + }, + "plugins": [ + { + "name": "agent-switchboard", + "source": "./plugins/agent-switchboard", + "description": "Search a curated directory of AI agents, MCP servers and agentic tools, compare them, find alternatives, and build one MCP config for several." + } + ] +} diff --git a/CHANGELOG.md b/CHANGELOG.md index 1dc1de1..137b2ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,11 @@ Every release of [Agent Switchboard](https://agentswitchboard.dev), newest first Versions follow MAJOR.MINOR.PATCH: a minor version adds features, a patch fixes or adjusts. Versions before 2.2.0 were assigned afterwards, reconstructed from the git history. "Directory" lines summarize catalog changes since the previous release; the full log of every listing added, updated or removed is on [/changelog](https://agentswitchboard.dev/changelog), and batch by batch on [/drops](https://agentswitchboard.dev/drops). From 2.2.0 on, each minor release opens with its highlights, what you can do now with a link to try it, which [/whats-new](https://agentswitchboard.dev/whats-new) also shows. +## [2.8.2] - 2026-11-03 - Cursor plugin + +### Added +- A Cursor plugin for the Cursor Marketplace: [plugins/agent-switchboard](https://github.com/assafbar2/agentswitchboard.dev/tree/main/plugins/agent-switchboard) adds the Agent Switchboard MCP server to Cursor in one install, with no account or key. Until the marketplace lists it, add the server by URL as [/connect](https://agentswitchboard.dev/connect) shows. ([#322](https://github.com/assafbar2/agentswitchboard.dev/pull/322)) + ## [2.8.1] - 2026-11-02 - Code and data licenses ### Changed diff --git a/DISTRIBUTION.md b/DISTRIBUTION.md index c0baac3..ff6bb0a 100644 --- a/DISTRIBUTION.md +++ b/DISTRIBUTION.md @@ -25,7 +25,7 @@ or a placement-dependent order reaches either surface. | Claude Connectors Directory | ◻ Ready to submit | — | Assaf submits at claude.ai/directory/manage (paid Claude plan). Custom connector by URL works today; /connect has a prefilled install link | | ChatGPT plugin directory | ◻ Ready to submit | — | Assaf uploads a plugin ZIP at platform.openai.com (verified identity) and sets `OPENAI_APPS_CHALLENGE` in Vercel for domain verification. Custom MCP plugin by URL works today | | GitHub MCP Registry (github.com/mcp, VS Code) | ◻ Not listed | — | Curated from the official registry by manual onboarding: request in github/github-mcp-server discussions or partnerships@github.com | -| Cursor Marketplace | ◻ Not listed | — | Public repo with a Cursor plugin manifest, submitted at cursor.com/marketplace/publish (manual review) | +| Cursor Marketplace | ◻ Ready to submit | [plugins/agent-switchboard](plugins/agent-switchboard) | Assaf applies at cursor.com/marketplace/publish (Cursor sign-in) with this repo's URL; Cursor reviews it by hand | | Smithery | ◻ Not listed | — | Publish the hosted URL at smithery.ai/new (Smithery account) | | Microsoft 365 Copilot connectors | ◻ Not listed | — | Partner Center, verified publisher; every tool needs a title and `readOnlyHint` (already set) | @@ -50,5 +50,14 @@ curl -s -X POST http://localhost:3100/api/mcp \ -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"check","version":"1.0"}}}' ``` +**Cursor Marketplace**: the plugin lives in [plugins/agent-switchboard](plugins/agent-switchboard) +(Cursor Plugin format: `.cursor-plugin/plugin.json`, `mcp.json`, `assets/logo.svg`, `README.md`), +registered by [.cursor-plugin/marketplace.json](.cursor-plugin/marketplace.json) at the repo root. +Cursor reviews every update before it ships, so bump `version` in the plugin's `plugin.json` when +its files change. [tests/cursor-plugin.test.ts](tests/cursor-plugin.test.ts) checks it against +Cursor's submission checklist and keeps its README's tool list, MCP URL and logo in step with the +server. To try it in Cursor, copy the folder to `~/.cursor/plugins/local/agent-switchboard`, +run Developer: Reload Window, and check Customize for the `agent-switchboard` MCP server. + **awesome-mcp-servers**: the listing line lives in their README (Search & Data Extraction section) with a Glama score badge. Edit via PR only. diff --git a/package-lock.json b/package-lock.json index 4a2d222..7e30065 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "agentswitchboard.dev", - "version": "2.8.1", + "version": "2.8.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "agentswitchboard.dev", - "version": "2.8.1", + "version": "2.8.2", "license": "MIT", "dependencies": { "@mcp-b/webmcp-polyfill": "^5.0.1", diff --git a/package.json b/package.json index 7a1439d..02f9f6f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "agentswitchboard.dev", - "version": "2.8.1", + "version": "2.8.2", "private": true, "scripts": { "dev": "next dev", diff --git a/plugins/agent-switchboard/.cursor-plugin/plugin.json b/plugins/agent-switchboard/.cursor-plugin/plugin.json new file mode 100644 index 0000000..9357150 --- /dev/null +++ b/plugins/agent-switchboard/.cursor-plugin/plugin.json @@ -0,0 +1,15 @@ +{ + "name": "agent-switchboard", + "displayName": "Agent Switchboard", + "version": "1.0.0", + "description": "Search a curated directory of AI agents, MCP servers and agentic tools, compare them, find alternatives, and build one MCP config for several.", + "author": { + "name": "Agent Switchboard", + "email": "barnir@agentmail.to" + }, + "homepage": "https://agentswitchboard.dev", + "repository": "https://github.com/assafbar2/agentswitchboard.dev", + "license": "MIT", + "keywords": ["mcp", "mcp-servers", "ai-agents", "directory", "search", "developer-tools"], + "logo": "assets/logo.svg" +} diff --git a/plugins/agent-switchboard/README.md b/plugins/agent-switchboard/README.md new file mode 100644 index 0000000..de9f60a --- /dev/null +++ b/plugins/agent-switchboard/README.md @@ -0,0 +1,47 @@ +# Agent Switchboard for Cursor + +Search [Agent Switchboard](https://agentswitchboard.dev), a curated directory of AI agents, MCP +servers and agentic tools, from Cursor's agent. Ask which tool fits a job, read a listing's setup +steps, compare listings, find alternatives, and get one MCP config for several tools. + +The plugin adds one MCP server, `https://agentswitchboard.dev/api/mcp` (Streamable HTTP). It needs +no account, API key or configuration. In approval prompts it appears as +`plugin-agent-switchboard-agent-switchboard`. + +## Tools + +| Tool | What it does | +| --- | --- | +| `search_agents` | Find listings for a job: by query, task, category, access method (MCP, API, CLI, SDK and more), sign-in type, or official and verified status | +| `get_agent` | One listing with its description, access methods, and MCP address or install command | +| `list_categories` | The directory's categories, with how many listings each has | +| `compare_agents` | Two or three listings side by side | +| `alternatives_to` | Listings that could stand in for one, with what each has in common | +| `connect_bundle` | One MCP client config for up to 10 listings, and how to connect the rest | +| `submit_listing` | Suggests a missing product by filing a public GitHub issue for human review. It publishes nothing. It is marked destructive, so clients ask before calling it | + +Every tool except `submit_listing` is read-only. + +## Try it + +- "Find an MCP server that can query a Postgres database." +- "How do I connect the Playwright MCP server, and is it official?" +- "Compare GitHub Copilot, Cursor and Windsurf." +- "What are good alternatives to Pinecone?" +- "Give me one MCP config with Playwright MCP, GitHub MCP and Notion MCP." + +## Results and privacy + +Results are organic: they rank by relevance or A→Z, never by paid placement. The server receives +only the arguments of each tool call, never your conversation or code. See the +[privacy policy](https://agentswitchboard.dev/privacy). + +## License + +The plugin is MIT, like the rest of the repository's code. The catalog data its tools return is +CC BY-NC 4.0; see [LICENSE-DATA](https://github.com/assafbar2/agentswitchboard.dev/blob/main/LICENSE-DATA). + +## Help + +Setup for other assistants and troubleshooting: [agentswitchboard.dev/connect](https://agentswitchboard.dev/connect#troubleshooting). +Support: [agentswitchboard.dev/contact](https://agentswitchboard.dev/contact). diff --git a/plugins/agent-switchboard/assets/logo.svg b/plugins/agent-switchboard/assets/logo.svg new file mode 100644 index 0000000..8576aed --- /dev/null +++ b/plugins/agent-switchboard/assets/logo.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/plugins/agent-switchboard/mcp.json b/plugins/agent-switchboard/mcp.json new file mode 100644 index 0000000..c3295d9 --- /dev/null +++ b/plugins/agent-switchboard/mcp.json @@ -0,0 +1,7 @@ +{ + "mcpServers": { + "agent-switchboard": { + "url": "https://agentswitchboard.dev/api/mcp" + } + } +} diff --git a/tests/cursor-plugin.test.ts b/tests/cursor-plugin.test.ts new file mode 100644 index 0000000..31d28c4 --- /dev/null +++ b/tests/cursor-plugin.test.ts @@ -0,0 +1,81 @@ +/** + * The Cursor Marketplace plugin (.cursor-plugin/marketplace.json and + * plugins/agent-switchboard/) against Cursor's submission checklist + * (cursor.com/docs/reference/plugins), and in step with the live server: + * the MCP URL in server.json, the site icon, and every tool /api/mcp lists. + */ +import { describe, expect, it } from 'vitest'; +import * as fs from 'fs'; +import * as path from 'path'; +import { POST as mcpPost } from '@/app/api/[transport]/route'; + +const ROOT = path.resolve(__dirname, '..'); +const readJson = (file: string) => JSON.parse(fs.readFileSync(path.join(ROOT, file), 'utf8')); + +const PLUGIN_NAME = /^[a-z0-9](?:[a-z0-9.-]*[a-z0-9])?$/; +const marketplace = readJson('.cursor-plugin/marketplace.json'); +const entry = marketplace.plugins[0]; +const pluginDir = path.join(ROOT, entry.source); +const manifest = readJson(path.join(entry.source, '.cursor-plugin/plugin.json')); +const mcpConfig = readJson(path.join(entry.source, 'mcp.json')); +const serverJson = readJson('server.json'); + +function isSafeRelative(p: string) { + const normalized = path.posix.normalize(p); + return !path.isAbsolute(p) && normalized !== '..' && !normalized.startsWith('../'); +} + +async function listedTools(): Promise { + const res = await mcpPost( + new Request('http://localhost/api/mcp', { + method: 'POST', + headers: { 'content-type': 'application/json', accept: 'application/json, text/event-stream' }, + body: JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'tools/list', params: {} }), + }) + ); + const text = await res.text(); + const data = text.split(/\r?\n/).filter((l) => l.startsWith('data:')).pop(); + const { result } = JSON.parse(data ? data.slice(5) : text); + return result.tools.map((t: { name: string }) => t.name); +} + +describe('Cursor Marketplace plugin', () => { + it('has one marketplace entry that resolves to a plugin folder with a manifest', () => { + expect(marketplace.name).toMatch(PLUGIN_NAME); + expect(marketplace.owner.name).toBeTruthy(); + expect(marketplace.plugins).toHaveLength(1); + expect(isSafeRelative(entry.source)).toBe(true); + expect(fs.existsSync(path.join(pluginDir, '.cursor-plugin/plugin.json'))).toBe(true); + expect(entry.name).toBe(manifest.name); + }); + + it('has a kebab-case name, a description, a semver version and a committed relative logo', () => { + expect(manifest.name).toMatch(PLUGIN_NAME); + expect(manifest.description.length).toBeGreaterThan(40); + expect(manifest.version).toMatch(/^\d+\.\d+\.\d+$/); + expect(isSafeRelative(manifest.logo)).toBe(true); + expect(fs.readFileSync(path.join(pluginDir, manifest.logo), 'utf8')).toBe( + fs.readFileSync(path.join(ROOT, 'app/icon.svg'), 'utf8') + ); + }); + + it('adds only the public MCP server, with no variables or secrets', () => { + const servers = Object.values(mcpConfig.mcpServers) as Record[]; + expect(servers).toEqual([{ url: serverJson.remotes[0].url }]); + expect(manifest.variables).toBeUndefined(); + }); + + it('ships no rules, skills, agents, commands or hooks that discovery would pick up', () => { + for (const dir of ['rules', 'skills', 'agents', 'commands', 'hooks']) { + expect(fs.existsSync(path.join(pluginDir, dir)), dir).toBe(false); + } + expect(fs.existsSync(path.join(pluginDir, 'SKILL.md'))).toBe(false); + }); + + it('documents every tool the MCP server lists in its README', async () => { + const readme = fs.readFileSync(path.join(pluginDir, 'README.md'), 'utf8'); + const tools = await listedTools(); + expect(tools.length).toBeGreaterThan(0); + for (const tool of tools) expect(readme, tool).toContain(`\`${tool}\``); + }); +});