From a343e64f11752f617c11325958132ce95fb962fb Mon Sep 17 00:00:00 2001 From: Amp Date: Sun, 6 Sep 2026 19:01:54 +0000 Subject: [PATCH 1/2] docs: prepare Claude community submission Amp-Thread-ID: https://ampcode.com/threads/T-01a077fc-5d89-7517-8628-e1f0282f1064 Co-authored-by: Joel Horwitz --- CLAUDE_COMMUNITY_SUBMISSION.md | 90 ++++++++++++++++++++++++++++++++++ README.md | 11 +++-- 2 files changed, 96 insertions(+), 5 deletions(-) create mode 100644 CLAUDE_COMMUNITY_SUBMISSION.md diff --git a/CLAUDE_COMMUNITY_SUBMISSION.md b/CLAUDE_COMMUNITY_SUBMISSION.md new file mode 100644 index 0000000..148c753 --- /dev/null +++ b/CLAUDE_COMMUNITY_SUBMISSION.md @@ -0,0 +1,90 @@ +# Claude plugin directory submission + +Prepared: 2026-09-06 + +This is the reviewer-ready package for the public Synter plugin repository. It +does not record or imply that the provider-side submission form was sent. + +## Submission fields + +| Field | Value | +| --- | --- | +| Public repository | `https://github.com/Synter-Media-AI/plugin` | +| Plugin name | `synter` | +| Publisher | Synter | +| Website | `https://syntermedia.ai` | +| License | MIT | +| Category | Marketing and advertising | +| Short description | Operate cross-channel advertising with approval-gated Synter agents, skills, and MCP tools. | +| Support | `https://syntermedia.ai/contact` | + +Suggested long description: + +> Synter is an AI agent operator for cross-channel advertising. The plugin +> combines campaign planning, audience, creative, measurement, reporting, and +> optimization skills with Synter's hosted MCP tool surface. Users connect +> their own advertising accounts and explicitly approve spend-changing actions. + +## Canonical installation + +Direct installation from Synter's marketplace: + +```text +/plugin marketplace add Synter-Media-AI/plugin +/plugin install synter@synter +``` + +After Anthropic approves and publishes the community listing, its separate +installation path will be: + +```text +/plugin marketplace add anthropics/claude-plugins-community +/plugin install synter@claude-community +``` + +Do not advertise the community command until the listing appears in Anthropic's +catalog. + +## Validation and reviewer checklist + +- [x] Repository is public and contains an MIT license. +- [x] `.claude-plugin/plugin.json` names the plugin `synter` and points to the + canonical public repository. +- [x] `.claude-plugin/marketplace.json` references the repository root. +- [x] `.mcp.json` uses the production HTTPS MCP endpoint and a sensitive, + required user-config API key. +- [x] README installation commands match the authoritative marketplace + manifest and production Synter documentation. +- [x] `node scripts/validate-manifests.js` passes. +- [x] `npx -y @anthropic-ai/claude-code plugin validate . --strict` prints + `✔ Validation passed` on 2026-09-06. +- [ ] Provider reviewer confirms the plugin does not collect credentials in + prompts; the configured key is sent only as `X-Synter-Key` to + `https://mcp.syntermedia.ai`. +- [ ] Provider reviewer exercises `/synter:quickstart`, one read-only report, + and one write preview without approving the write. +- [ ] An authorized Synter representative submits the public repository URL + through the Claude.ai or Console plugin submission form. + +## Security and data-use evidence + +- The plugin declares no local executable dependency or install script. +- The only remote MCP destination is `https://mcp.syntermedia.ai`. +- The session hook prints a static operating and approval-safety primer into + session context; it does not read or transmit credentials. +- Advertising writes are approval-gated. The bundled guidance does not turn a + model response into approval and does not bypass provider permissions. +- Source, manifests, skills, agents, hooks, and output styles are inspectable in + the public repository. + +## Provider-side action still required + +Submission is an external Anthropic action. A Synter representative with the +required Claude.ai directory-management access or Console Developer/Admin/Owner +role must submit this repository at one of: + +- `https://claude.ai/admin-settings/directory/submissions/plugins/new` +- `https://platform.claude.com/plugins/submit` + +No pull request to Anthropic's read-only community catalog is required or +accepted; approved entries are synchronized from Anthropic's review pipeline. diff --git a/README.md b/README.md index 70202cd..cde559a 100644 --- a/README.md +++ b/README.md @@ -47,18 +47,19 @@ Free GA4 and onboarding tools work with no key and no credits. ### ChatGPT (Developer Mode / Connectors) -ChatGPT's custom-connector setup (**Settings → Connectors → Advanced → Developer mode**, or the Workspace admin equivalent under **Apps**) accepts a remote MCP server URL plus an authentication mechanism — but per OpenAI's own docs, that mechanism is limited to **no authentication** or **OAuth 2.1**. There's no field for a static API key or a custom request header. Verified against OpenAI's current documentation: +ChatGPT's custom-connector setup (**Settings → Connectors → Advanced → Developer mode**, or the Workspace admin equivalent under **Apps**) accepts Synter's production remote MCP server through OAuth 2.1: - [Developer mode and MCP apps in ChatGPT](https://help.openai.com/en/articles/12584461-developer-mode-and-full-mcp-connectors-in-chatgpt-beta) (OpenAI Help Center) — app setup lets you "pick the authentication mechanism, if applicable," then walks through the OAuth authorization prompt; no custom-header option is described. - [Authentication – Apps SDK](https://developers.openai.com/apps-sdk/build/auth) (OpenAI Developers) — the only documented way to add custom auth to an MCP server ChatGPT connects to is a full OAuth 2.1 flow (protected-resource metadata, CIMD/DCR, PKCE). Static API keys or bearer headers aren't part of the spec ChatGPT implements. -Synter's hosted MCP server authenticates every request with a static `X-Synter-Key` header — the same credential the Claude Code, Claude Desktop, and Cursor configs above send — and doesn't implement OAuth. **That means pointing ChatGPT's connector picker straight at `https://mcp.syntermedia.ai` won't authenticate today: ChatGPT has no field to carry the required key.** This is a real gap, not a documentation omission — closing it means adding an OAuth 2.1 front end to the MCP server, which is out of scope for this release. +Add `https://mcp.syntermedia.ai` as the remote MCP URL and complete the Synter authorization flow in the browser. The hosted service publishes OAuth authorization-server and protected-resource metadata, supports dynamic client registration and PKCE, and returns the required resource challenge to unauthenticated MCP clients. The API-key header in this plugin remains the authentication path for Claude Code, Claude Desktop, Cursor, and other clients that support custom headers. -Until then, the working paths to Synter's tools are the ones already documented on this page: +Supported connection paths are: -- **Claude Code, Claude Desktop, or Cursor** — the plugin sends the header correctly out of the box (see Install above). +- **ChatGPT** — connect the production MCP URL and authorize with OAuth 2.1. +- **Claude Code, Claude Desktop, or Cursor** — the plugin sends the API-key header correctly out of the box (see Install above). - **Any other MCP client that supports custom headers** — wire the HTTP endpoint directly with `X-Synter-Key: syn_your_api_key_here` (see "Wire the MCP straight into any client" below). -- **A client with no remote-MCP support at all** — run the published stdio server locally: `SYNTER_API_KEY=syn_... npx -y @synterai/mcp-server` (get a key at [syntermedia.ai/developer](https://syntermedia.ai/developer)). Note this doesn't help ChatGPT specifically — ChatGPT's connector product only reaches remote HTTPS servers, it cannot spawn a local stdio process. +- **A client with no remote-MCP support** — run the published stdio server locally: `SYNTER_API_KEY=syn_... npx -y @synterai/mcp-server` (get a key at [syntermedia.ai/developer](https://syntermedia.ai/developer)). --- From 086628121ea558a93655db29d77a63533ac9fd5e Mon Sep 17 00:00:00 2001 From: Amp Date: Sun, 6 Sep 2026 19:50:28 +0000 Subject: [PATCH 2/2] fix: make Claude plugin directory-ready Amp-Thread-ID: https://ampcode.com/threads/T-01a077fc-5d89-7517-8628-e1f0282f1064 Co-authored-by: Joel Horwitz --- .claude-plugin/plugin.json | 16 ++------ .cursor-plugin/marketplace.json | 2 +- .cursor-plugin/plugin.json | 2 +- .github/workflows/release.yml | 3 ++ .github/workflows/validate.yml | 5 ++- .mcp.json | 5 +-- CHANGELOG.md | 11 +++++ CLAUDE_COMMUNITY_SUBMISSION.md | 73 ++++++++++++++++++++++++++++----- README.md | 50 ++++++++++++++-------- context/brand-and-safety.md | 2 +- scripts/build-zip.sh | 11 ++--- scripts/session-context.sh | 2 +- scripts/validate-manifests.js | 36 ++++++++++++---- skills/help/SKILL.md | 10 +++-- skills/quickstart/SKILL.md | 22 ++++++---- 15 files changed, 177 insertions(+), 73 deletions(-) diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index 46f4d01..df09701 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -3,10 +3,11 @@ "name": "synter", "displayName": "Synter", "description": "The AI Agent Operator for Ads. Connect every ad platform, build audiences from signals and first-party data, generate on-brand creative, launch cross-platform campaigns, and reallocate spend by ROAS — all in one conversation. You direct, the agents execute. Nothing ships without your approval.", - "version": "1.0.0", + "version": "1.1.0", "license": "MIT", "author": { "name": "Synter", + "email": "joel@synterai.com", "url": "https://syntermedia.ai" }, "homepage": "https://syntermedia.ai", @@ -24,14 +25,5 @@ "linkedin-ads", "mcp", "synter" - ], - "userConfig": { - "synter_api_key": { - "type": "string", - "title": "Synter API key", - "description": "Required. Your Synter API key (syn_...). Create one at syntermedia.ai/developer — takes 30 seconds. The Synter MCP server rejects connections without a valid key, so the plugin cannot start without it.", - "sensitive": true, - "required": true - } - } -} \ No newline at end of file + ] +} diff --git a/.cursor-plugin/marketplace.json b/.cursor-plugin/marketplace.json index 2b492ee..126c407 100644 --- a/.cursor-plugin/marketplace.json +++ b/.cursor-plugin/marketplace.json @@ -6,7 +6,7 @@ }, "metadata": { "description": "Synter's official plugin marketplace. The AI Agent Operator for Ads.", - "version": "1.0.0" + "version": "1.1.0" }, "plugins": [ { diff --git a/.cursor-plugin/plugin.json b/.cursor-plugin/plugin.json index 5214c84..374beb8 100644 --- a/.cursor-plugin/plugin.json +++ b/.cursor-plugin/plugin.json @@ -2,7 +2,7 @@ "$schema": "https://cursor.com/schemas/cursor-plugin/plugin.json", "name": "synter", "displayName": "Synter", - "version": "1.0.0", + "version": "1.1.0", "description": "The AI Agent Operator for Ads. Connect 21 ad platforms, build audiences, generate on-brand creative, launch cross-platform campaigns, and reallocate spend by ROAS — one conversation, every platform. You direct, the agents execute. Nothing that spends money ships without your approval.", "author": { "name": "Synter", diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 63a3daf..313dacf 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -24,6 +24,9 @@ jobs: - name: Run manifest validation run: node scripts/validate-manifests.js + - name: Run strict Claude plugin validation + run: npx -y @anthropic-ai/claude-code@2.1.263 plugin validate . --strict + - name: Ensure zip/rsync are available run: | command -v zip >/dev/null || sudo apt-get update -qq && sudo apt-get install -y -qq zip diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 3d1a10c..7bf6ff4 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -7,7 +7,7 @@ on: jobs: validate-manifests: - name: Validate manifests, skills, and interpolation guards + name: Validate manifests, skills, and authentication guards runs-on: ubuntu-latest steps: - name: Checkout @@ -20,3 +20,6 @@ jobs: - name: Run manifest validation run: node scripts/validate-manifests.js + + - name: Run strict Claude plugin validation + run: npx -y @anthropic-ai/claude-code@2.1.263 plugin validate . --strict diff --git a/.mcp.json b/.mcp.json index 2f36265..91f0211 100644 --- a/.mcp.json +++ b/.mcp.json @@ -3,10 +3,7 @@ "synter": { "type": "http", "url": "https://mcp.syntermedia.ai", - "headers": { - "X-Synter-Key": "${user_config.synter_api_key}" - }, - "description": "Synter Advertising Platform MCP \u2014 cross-channel ad management for AI agents. Requires a Synter API key (syn_..., from syntermedia.ai/developer); the server rejects unauthenticated connections." + "description": "Synter Advertising Platform MCP \u2014 cross-channel ad management for AI agents. Sign in through the browser OAuth prompt; never paste credentials into chat." } } } diff --git a/CHANGELOG.md b/CHANGELOG.md index d884747..6c30c00 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,17 @@ All notable changes to the Synter plugin are documented here. This project follows [semantic versioning](https://semver.org). +## [1.1.0] — 2026-09-06 + +### Changed +- Claude and Cowork now authenticate the hosted Synter MCP through secure browser OAuth 2.1 instead of collecting a static API key in plugin configuration. The MCP endpoint publishes protected-resource and authorization-server metadata, supports dynamic client registration and PKCE, and returns a standards-compliant `401` challenge for protected tool calls. +- Installation and troubleshooting guidance now distinguishes direct GitHub marketplace installation from Anthropic directory discovery and tells users never to paste credentials into chat. +- Claude and Cursor package versions remain aligned so release archives and marketplace caches receive the updated skills and documentation. + +### Added +- Anthropic community directory submission fields, policy evidence, test prompts, and reviewer checklist. +- Strict Claude plugin validation in pull-request and release CI. + ## [1.0.0] — 2026-08-27 ### Changed diff --git a/CLAUDE_COMMUNITY_SUBMISSION.md b/CLAUDE_COMMUNITY_SUBMISSION.md index 148c753..a8926e8 100644 --- a/CLAUDE_COMMUNITY_SUBMISSION.md +++ b/CLAUDE_COMMUNITY_SUBMISSION.md @@ -17,6 +17,8 @@ does not record or imply that the provider-side submission form was sent. | Category | Marketing and advertising | | Short description | Operate cross-channel advertising with approval-gated Synter agents, skills, and MCP tools. | | Support | `https://syntermedia.ai/contact` | +| Privacy policy | `https://syntermedia.ai/privacy` | +| Terms | `https://syntermedia.ai/terms` | Suggested long description: @@ -45,43 +47,92 @@ installation path will be: Do not advertise the community command until the listing appears in Anthropic's catalog. +The public repository is also directly installable in Claude Desktop and +Cowork: **Customize → Plugins → Personal plugins → + → Add marketplace → Add +from a repository**, then enter `Synter-Media-AI/plugin` and install `synter`. +Direct installation is not public-directory discovery. + ## Validation and reviewer checklist - [x] Repository is public and contains an MIT license. - [x] `.claude-plugin/plugin.json` names the plugin `synter` and points to the canonical public repository. - [x] `.claude-plugin/marketplace.json` references the repository root. -- [x] `.mcp.json` uses the production HTTPS MCP endpoint and a sensitive, - required user-config API key. +- [x] `.mcp.json` uses the production HTTPS MCP endpoint and secure browser + OAuth; it contains no static key, token, custom auth header, or credential + prompt. - [x] README installation commands match the authoritative marketplace manifest and production Synter documentation. - [x] `node scripts/validate-manifests.js` passes. - [x] `npx -y @anthropic-ai/claude-code plugin validate . --strict` prints `✔ Validation passed` on 2026-09-06. -- [ ] Provider reviewer confirms the plugin does not collect credentials in - prompts; the configured key is sent only as `X-Synter-Key` to - `https://mcp.syntermedia.ai`. +- [x] Production OAuth protected-resource and authorization-server metadata + return `200`; a protected tool call returns `401` with a + `WWW-Authenticate` resource-metadata challenge. +- [x] The production MCP tool surface has 195 tools; every tool supplies a + title plus boolean `readOnlyHint` and `destructiveHint` annotations. +- [x] Clean Claude Code configuration can add `Synter-Media-AI/plugin`, install + `synter@synter`, and inventory all 57 skills, 7 agents, 1 hook, and 1 MCP + server. - [ ] Provider reviewer exercises `/synter:quickstart`, one read-only report, and one write preview without approving the write. -- [ ] An authorized Synter representative submits the public repository URL - through the Claude.ai or Console plugin submission form. +- [ ] Provider reviewer uses a Synter-owned test workspace with sample data; + reviewer credentials must be delivered through Anthropic's private form, + never committed to this repository. +- [ ] An authorized Synter representative accepts the Software Directory Terms + and submits through the authenticated Claude.ai or Console form. ## Security and data-use evidence - The plugin declares no local executable dependency or install script. -- The only remote MCP destination is `https://mcp.syntermedia.ai`. +- The only remote MCP destination is `https://mcp.syntermedia.ai`, which uses + OAuth 2.1 with PKCE for Claude clients. - The session hook prints a static operating and approval-safety primer into session context; it does not read or transmit credentials. - Advertising writes are approval-gated. The bundled guidance does not turn a model response into approval and does not bypass provider permissions. - Source, manifests, skills, agents, hooks, and output styles are inspectable in the public repository. +- Synter's privacy policy, terms, and support channel are public and linked from + the README. + +## Reviewer prompts + +Use a Synter-owned test workspace with synthetic/sample campaign data. Do not +approve or execute production spend. + +1. `Use Synter to list my connected ad accounts and identify the workspace.` +2. `Report sample campaign spend and conversions for the last seven days. Read only; do not change anything.` +3. `Draft a paused cross-platform campaign and show the exact account, budget, and changes that would require my approval. Do not execute.` + +Expected safety behavior: the first two prompts perform only reads. The third +produces a reviewable plan or paused draft and stops at the explicit approval +boundary before any external spend-changing action. + +## Directory policy evidence + +| Requirement | Evidence | +| --- | --- | +| Public source and license | Public GitHub repository; MIT `LICENSE` | +| OAuth for authenticated remote MCP | OAuth metadata, DCR, PKCE, and protected-tool `401` challenge at `https://mcp.syntermedia.ai` | +| Tool annotations | All 195 discovered tools include `title`, boolean `readOnlyHint`, and boolean `destructiveHint` | +| Privacy and support | `https://syntermedia.ai/privacy`; `https://syntermedia.ai/contact` | +| Narrow, visible instructions | Human-readable skills and agents in this repository; no encoded or remotely loaded behavioral instructions | +| Approval boundary | Spend-changing operations require explicit approval; a model response is not approval | +| Test account | Must be supplied privately by Synter in the submission form | + +The plugin manages advertising workflows and can invoke creative tools, so the +submission must describe those capabilities accurately. Anthropic retains +discretion under the Software Directory Policy's unsupported-use restrictions; +do not claim acceptance or an Anthropic Verified status before it appears in +the directory. ## Provider-side action still required -Submission is an external Anthropic action. A Synter representative with the -required Claude.ai directory-management access or Console Developer/Admin/Owner -role must submit this repository at one of: +Submission requires an authenticated Synter representative with the required +Claude.ai directory-management access or Console Developer/Admin/Owner role, +acceptance of Anthropic's Software Directory Terms, and a private reviewer test +account. Use one of: - `https://claude.ai/admin-settings/directory/submissions/plugins/new` - `https://platform.claude.com/plugins/submit` diff --git a/README.md b/README.md index cde559a..2d6f775 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,9 @@ Synter

-# Synter — Claude Code & Cursor plugin +# Synter — Claude and Cursor plugin -**The AI Agent Operator for Ads.** Claude Code and Cursor for advertising. You direct, the agents execute. +**The AI Agent Operator for Ads.** Claude, Cowork, Claude Code, and Cursor for advertising. You direct, the agents execute. Connect every ad platform, build audiences from signals and first-party data, generate on-brand creative, plan and launch cross-platform campaigns, and reallocate spend by ROAS — all in one conversation. Nothing that spends money ships without your approval. @@ -21,17 +21,28 @@ One interface. Every ad platform. Ship faster. /plugin install synter@synter ``` -Create a key at [syntermedia.ai/developer](https://syntermedia.ai/developer) and paste it when enabling the plugin — the Synter MCP server rejects unauthenticated connections, so the plugin cannot start without one. Once enabled, `/synter:quickstart` handles first-run onboarding. +Start a new conversation and run `/synter:quickstart`. Claude opens Synter's +secure browser OAuth flow when the first protected tool is used. Never paste an +API key, access token, or authorization code into chat. -### Claude Desktop +### Claude Desktop and Cowork -Claude Desktop's **Settings → Plugins** UI installs this plugin either of two ways: +Open **Customize → Plugins**. Under **Personal plugins**, click **+ → Add +marketplace → Add from a repository**, enter `Synter-Media-AI/plugin`, and +install `synter` from that marketplace. The full GitHub URL also works. -**Add marketplace** — click **Add marketplace**, point it at `Synter-Media-AI/plugin` (the GitHub repo, same source Claude Code's `/plugin marketplace add` reads), then install `synter` from the listing. This tracks `main`, so you always get the latest tagged manifests. +Alternatively, download the package from the [latest GitHub +release](https://github.com/Synter-Media-AI/plugin/releases/latest) and choose +**+ → Upload plugin**. A tagged release (`synter--v*`) rebuilds the zip through +the repository's release workflow. -**Upload plugin** — download the packaged zip from the [v1.0.0 release](https://github.com/Synter-Media-AI/plugin/releases/latest/download/synter-plugin-1.0.0.zip) (browse all releases at [github.com/Synter-Media-AI/plugin/releases](https://github.com/Synter-Media-AI/plugin/releases)) and drag it into **Upload plugin**. Every tagged release (`synter--v*`) rebuilds this zip via `.github/workflows/release.yml`, with the plugin manifest at the archive root — the layout Desktop's upload flow expects. +Start a new conversation after installation and complete the Synter browser +sign-in prompt. Skills work in Claude chat and Cowork; hooks and sub-agents run +in Cowork. The plugin can be installed directly today, but it will not appear +in Anthropic's public directory until Anthropic approves the separate community +submission. -Both routes need the same API key as Claude Code — create one at [syntermedia.ai/developer](https://syntermedia.ai/developer) and paste it when enabling the plugin. +Official install documentation: [Use plugins in Claude](https://support.claude.com/en/articles/13837440-use-plugins-in-claude) and [Install plugins in Cowork](https://claude.com/docs/cowork/guide/plugins). ### Cursor @@ -52,12 +63,13 @@ ChatGPT's custom-connector setup (**Settings → Connectors → Advanced → Dev - [Developer mode and MCP apps in ChatGPT](https://help.openai.com/en/articles/12584461-developer-mode-and-full-mcp-connectors-in-chatgpt-beta) (OpenAI Help Center) — app setup lets you "pick the authentication mechanism, if applicable," then walks through the OAuth authorization prompt; no custom-header option is described. - [Authentication – Apps SDK](https://developers.openai.com/apps-sdk/build/auth) (OpenAI Developers) — the only documented way to add custom auth to an MCP server ChatGPT connects to is a full OAuth 2.1 flow (protected-resource metadata, CIMD/DCR, PKCE). Static API keys or bearer headers aren't part of the spec ChatGPT implements. -Add `https://mcp.syntermedia.ai` as the remote MCP URL and complete the Synter authorization flow in the browser. The hosted service publishes OAuth authorization-server and protected-resource metadata, supports dynamic client registration and PKCE, and returns the required resource challenge to unauthenticated MCP clients. The API-key header in this plugin remains the authentication path for Claude Code, Claude Desktop, Cursor, and other clients that support custom headers. +Add `https://mcp.syntermedia.ai` as the remote MCP URL and complete the Synter authorization flow in the browser. The hosted service publishes OAuth authorization-server and protected-resource metadata, supports dynamic client registration and PKCE, and returns the required resource challenge to unauthenticated MCP clients. Supported connection paths are: - **ChatGPT** — connect the production MCP URL and authorize with OAuth 2.1. -- **Claude Code, Claude Desktop, or Cursor** — the plugin sends the API-key header correctly out of the box (see Install above). +- **Claude Code, Claude Desktop, or Cowork** — install this plugin and authorize with OAuth 2.1 in the browser. +- **Cursor** — install the Cursor plugin and set `SYNTER_API_KEY` in **Plugins → Configure**. - **Any other MCP client that supports custom headers** — wire the HTTP endpoint directly with `X-Synter-Key: syn_your_api_key_here` (see "Wire the MCP straight into any client" below). - **A client with no remote-MCP support** — run the published stdio server locally: `SYNTER_API_KEY=syn_... npx -y @synterai/mcp-server` (get a key at [syntermedia.ai/developer](https://syntermedia.ai/developer)). @@ -117,12 +129,12 @@ The Synter Advertising Platform MCP (`https://mcp.syntermedia.ai`) — cross-pla ```text plugin/ ├── .claude-plugin/ -│ ├── plugin.json # Claude Code manifest + userConfig (API key) +│ ├── plugin.json # Claude/Cowork plugin manifest │ └── marketplace.json # Claude Code marketplace ├── .cursor-plugin/ │ ├── plugin.json # Cursor Plugin manifest + SYNTER_API_KEY variable │ └── marketplace.json # Cursor Team Marketplace index -├── .mcp.json # Synter MCP for Claude (header: ${user_config.synter_api_key}) +├── .mcp.json # Synter remote MCP for Claude (browser OAuth) ├── mcp.json # Synter MCP for Cursor (header: ${SYNTER_API_KEY}) ├── skills/ # /synter:* slash commands (SKILL.md each) ├── agents/ # subagent definitions @@ -137,7 +149,7 @@ plugin/ └── LICENSE # MIT ``` -> **Don't cross-wire the two MCP configs.** `.mcp.json` is Claude-only and must interpolate `${user_config.synter_api_key}` (sourced from `.claude-plugin/plugin.json`'s `userConfig`); `mcp.json` is Cursor-only and must interpolate `${SYNTER_API_KEY}` (sourced from `.cursor-plugin/plugin.json`'s `variables`). Copying one file's header value into the other silently breaks that client's authentication. +> **Don't cross-wire the two MCP configs.** `.mcp.json` is Claude-only and uses browser OAuth with no static credentials; `mcp.json` is Cursor-only and interpolates `${SYNTER_API_KEY}` from the Cursor plugin's variables. Copying the Cursor header into Claude bypasses the supported secure sign-in flow and violates public-directory requirements. --- @@ -164,19 +176,23 @@ for await (const m of query({ ### Or wire the MCP straight into any client -**HTTP (recommended)** +**HTTP with browser OAuth (recommended)** ```json { "mcpServers": { "synter": { "type": "http", - "url": "https://mcp.syntermedia.ai", - "headers": { "X-Synter-Key": "syn_your_api_key_here" } + "url": "https://mcp.syntermedia.ai" } } } ``` +Clients that do not implement remote MCP OAuth can instead use an +`X-Synter-Key` header if they support custom headers, or the stdio package +below. Create a key at [syntermedia.ai/developer](https://syntermedia.ai/developer) +and store it in the client's secret configuration, never in chat. + **stdio (Claude Desktop)** — via the public SDK [`@synterai/mcp-server`](https://www.npmjs.com/package/@synterai/mcp-server): ```json { @@ -208,4 +224,4 @@ claude --plugin-dir . # load locally without a marketplace After editing `hooks/`, `.mcp.json`, or `agents/`, run `/reload-plugins`. -MIT licensed. © 2026 Synter. +[Privacy](https://syntermedia.ai/privacy) · [Terms](https://syntermedia.ai/terms) · [Support](https://syntermedia.ai/contact) · MIT licensed. © 2026 Synter. diff --git a/context/brand-and-safety.md b/context/brand-and-safety.md index db35559..f8b6867 100644 --- a/context/brand-and-safety.md +++ b/context/brand-and-safety.md @@ -35,7 +35,7 @@ Never fabricate a price, competitor, statistic, or claim. If you don't have the - Nothing that spends money ships without explicit user approval. No exceptions, no tier carve-outs. - Default to recommend-then-execute: propose the plan (platform, budget, audience, creative), show it, and wait for an explicit yes before any create/enable/budget/launch/pause call. - Reads are free: `list_*`, `get_*`, `pull_*`, `verify_*`, and reports run without asking. -- Confirm the org/account before any write (`list_connected_accounts`). Never run one org's campaigns through another's key. +- Confirm the org/account before any write (`list_connected_accounts`). Never run one org's campaigns through another's credentials or session. - Use real account/campaign IDs the tools return — never invent them. - Guard budgets against fat-finger amounts (a daily budget 10–100x intended is the classic incident); default to sane ceilings. - "Created" ≠ "live and spending." Verify before claiming a campaign is running. diff --git a/scripts/build-zip.sh b/scripts/build-zip.sh index 3bfde49..47afeda 100755 --- a/scripts/build-zip.sh +++ b/scripts/build-zip.sh @@ -23,9 +23,10 @@ rm -f "$ZIP_PATH" echo "Built $ZIP_PATH" -# Hard root-check: Claude Desktop's zip upload requires the manifest at -# /.claude-plugin/plugin.json, not nested under a repo folder. -unzip -l "$ZIP_PATH" | grep -q 'synter/.claude-plugin/plugin.json' \ - || { echo "FATAL: plugin.json not at archive root" >&2; exit 1; } +# Hard layout check: Claude accepts the plugin directly at the archive root or +# inside one top-level folder. The release artifact uses the latter. +unzip -Z1 "$ZIP_PATH" > "$STAGE/archive-entries.txt" +grep -Fxq 'synter/.claude-plugin/plugin.json' "$STAGE/archive-entries.txt" \ + || { echo "FATAL: plugin.json not inside the archive's top-level plugin folder" >&2; exit 1; } -echo "OK: synter/.claude-plugin/plugin.json present at archive root" +echo "OK: synter/.claude-plugin/plugin.json present" diff --git a/scripts/session-context.sh b/scripts/session-context.sh index 3595f61..d2faf12 100755 --- a/scripts/session-context.sh +++ b/scripts/session-context.sh @@ -10,7 +10,7 @@ Operating rules: - MONEY SAFETY RULES: Nothing that spends money ships without explicit user approval. No exceptions, no tier carve-outs. - Default to recommend-then-execute: propose the plan (platform, budget, audience, creative), show it, and wait for an explicit yes before any create/enable/budget/launch/pause call. - Reads are free: list_*, get_*, pull_*, verify_*, and reports run without asking. -- Confirm the org/account before any write (list_connected_accounts). Never run one org's campaigns through another's key. Use real IDs the tools return — never invent them. +- Confirm the org/account before any write (list_connected_accounts). Never run one org's campaigns through another's credentials or session. Use real IDs the tools return — never invent them. - "Created" != "live and spending" — verify before claiming. - Brand voice: terse, certain, doing the work. Show, don't sell. No banned language (no "AI-Powered", "seamless", "beta", hype CTAs). Never fabricate prices, competitors, or stats. - Get started or get help: /synter:quickstart and /synter:help. diff --git a/scripts/validate-manifests.js b/scripts/validate-manifests.js index 8424abb..ec87308 100644 --- a/scripts/validate-manifests.js +++ b/scripts/validate-manifests.js @@ -50,6 +50,22 @@ if (pluginJson && marketplaceJson) { `.claude-plugin/plugin.json name ("${pluginJson.name}")` ); } + + const cursorPluginJson = manifests['.cursor-plugin/plugin.json']; + const cursorMarketplaceJson = manifests['.cursor-plugin/marketplace.json']; + const cursorMarketplaceVersion = cursorMarketplaceJson && cursorMarketplaceJson.metadata && cursorMarketplaceJson.metadata.version; + if (cursorPluginJson && cursorPluginJson.version !== pluginJson.version) { + errors.push( + `.cursor-plugin/plugin.json version ("${cursorPluginJson.version}") does not match ` + + `.claude-plugin/plugin.json version ("${pluginJson.version}")` + ); + } + if (cursorMarketplaceVersion !== pluginJson.version) { + errors.push( + `.cursor-plugin/marketplace.json metadata.version ("${cursorMarketplaceVersion}") does not match ` + + `.claude-plugin/plugin.json version ("${pluginJson.version}")` + ); + } } // (c) Skill frontmatter check: name matches directory, description present. @@ -95,12 +111,18 @@ if (fs.existsSync(SKILLS_DIR)) { errors.push('skills/: directory not found'); } -// (d) Interpolation guards — the two mcp manifests must reference their -// respective client's variable substitution syntax, or a client's install -// UI silently ships a dead API key. -const mcpDotJson = readFile('.mcp.json'); -if (!mcpDotJson.includes('user_config.synter_api_key')) { - errors.push('.mcp.json: missing required literal "user_config.synter_api_key"'); +// (d) Authentication guards — Claude's public directory requires OAuth for +// authenticated remote MCP services. Cursor keeps its supported API-key path. +const claudeMcp = manifests['.mcp.json']; +const claudeServer = claudeMcp && claudeMcp.mcpServers && claudeMcp.mcpServers.synter; +if (!claudeServer || claudeServer.type !== 'http' || claudeServer.url !== 'https://mcp.syntermedia.ai') { + errors.push('.mcp.json: Synter must use the production HTTPS remote MCP endpoint'); +} +if (claudeServer && claudeServer.headers) { + errors.push('.mcp.json: Claude plugin must use browser OAuth, not static request headers'); +} +if (pluginJson && pluginJson.userConfig && pluginJson.userConfig.synter_api_key) { + errors.push('.claude-plugin/plugin.json: Claude plugin must not collect a static Synter API key'); } const mcpJson = readFile('mcp.json'); @@ -117,4 +139,4 @@ if (errors.length > 0) { process.exit(1); } -console.log('All manifests, skill frontmatter, and interpolation guards passed.'); +console.log('All manifests, skill frontmatter, versions, and authentication guards passed.'); diff --git a/skills/help/SKILL.md b/skills/help/SKILL.md index 656d969..a47ee49 100644 --- a/skills/help/SKILL.md +++ b/skills/help/SKILL.md @@ -27,14 +27,18 @@ Direct API connections to Google, Meta, LinkedIn, Microsoft, Reddit, TikTok, X, ## Getting set up -- No key yet? Run **/synter:quickstart** or `synter_onboarding_start(email="...")`. -- Create an API key at **syntermedia.ai/developer**, then set it as the plugin's `synter_api_key` and `/reload-plugins`. +- Run **/synter:quickstart** and complete the secure Synter browser sign-in when + Claude prompts. Never paste an API key, access token, or authorization code + into chat. +- If the OAuth prompt does not appear, reconnect the Synter MCP server from the + client's connector/plugin settings and retry in a new conversation. - Free GA4 tools work with no key and no credits. ## Docs & support - Docs: **syntermedia.ai** and the developer quickstart at **syntermedia.ai/docs/quickstart**. - Product: **syntermedia.ai**. -- Support: reach the team through the in-app support channel. +- Privacy: **syntermedia.ai/privacy**. +- Support: **syntermedia.ai/contact** or the in-app support channel. Anything that spends money asks for your approval first. Reads are free to run. diff --git a/skills/quickstart/SKILL.md b/skills/quickstart/SKILL.md index b8ccf8e..1ecc2bd 100644 --- a/skills/quickstart/SKILL.md +++ b/skills/quickstart/SKILL.md @@ -1,6 +1,6 @@ --- name: quickstart -description: Guided introduction to Synter — get onboarded, connect your first ad platform, and run your first cross-platform action. Use when a user is new to Synter, asks how to get started, has no API key yet, or wants a tour of what the agents can do. +description: Guided introduction to Synter — sign in securely, connect your first ad platform, and run your first cross-platform action. Use when a user is new to Synter, asks how to get started, needs to authenticate, or wants a tour of what the agents can do. --- # Synter Quickstart @@ -11,17 +11,21 @@ You are the Synter operator. The user directs; you execute. Get them from zero t Call `get_connection_status` (or `get_credit_balance`). -- **Works** → they have a valid key. Skip to step 3. -- **Auth error / no key** → onboard them (step 2). +- **Works** → they are signed in. Skip to step 3. +- **Browser sign-in prompt** → ask them to complete Synter OAuth, then retry. +- **Auth error without a prompt** → reconnect the Synter MCP server from the + client's connector/plugin settings. Never ask the user to paste an API key, + access token, or authorization code into chat. ## 2. Onboard (no account needed) -The free GA4 and onboarding tools work without a key. +The Claude plugin uses secure browser OAuth. If the user does not have a +Synter account, the authorization page guides them through creating or signing +in to one. When the browser returns to Claude, retry `get_connection_status`. -1. `synter_onboarding_start(email="")` — sends a magic link. -2. Tell them to click the link in their email. -3. `synter_onboarding_status(session_token="...")` — poll until ready. -4. Once ready, they create an API key at **syntermedia.ai/developer** and set it as the plugin's `synter_api_key` (re-enable the plugin in `/plugin`, or add it to MCP config). Then `/reload-plugins`. +For MCP clients that cannot complete browser OAuth, use Synter's separate +client-specific setup guide at **syntermedia.ai/docs/integrate/install**. Do not +collect credentials in the conversation. Free GA4 tools to demo value before they connect anything: `ga4_list_properties()`, `ga4_run_report(metrics="sessions,totalUsers,conversions", dimensions="date", days=28)`. @@ -42,6 +46,6 @@ Offer one concrete next step based on what they have: ## House rules - Show, don't sell. Real numbers, real platform names, real outcomes. -- Confirm the org/account before any write: `list_connected_accounts`. Never run one org's campaigns through another's key. +- Confirm the org/account before any write: `list_connected_accounts`. Never run one org's campaigns through another's session. - Reads are free to run. Anything that creates, enables, or changes budget needs explicit user approval first. - More help: run the **help** skill.