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
new file mode 100644
index 0000000..a8926e8
--- /dev/null
+++ b/CLAUDE_COMMUNITY_SUBMISSION.md
@@ -0,0 +1,141 @@
+# 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` |
+| Privacy policy | `https://syntermedia.ai/privacy` |
+| Terms | `https://syntermedia.ai/terms` |
+
+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.
+
+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 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.
+- [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.
+- [ ] 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`, 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 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`
+
+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..2d6f775 100644
--- a/README.md
+++ b/README.md
@@ -2,9 +2,9 @@
-# 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
@@ -47,18 +58,20 @@ 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.
-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 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 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)).
---
@@ -116,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
@@ -136,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.
---
@@ -163,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
{
@@ -207,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.