mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-24 10:13:11 +02:00
feat(apps): add Ditto connection (#16472)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - Agents reach external services through the Apps catalog. Each catalog entry is a reviewed `AppDefinition` that connects a provider's hosted MCP server to Paperclip's shared vault, grants, policies, gateway, and audit trail. > - Ditto (`dittowords.com`) is a single source of truth for product copy. Its hosted MCP server lets agents read style guide rules, search and reuse approved text, and create, edit, translate and delete text, components, variants and style guides. > - Ditto is not in the catalog. Operators must use the generic "Connect your own MCP server" flow. That flow has no branding, no guidance about Ditto's `token` API-key scheme, and no warning about permanent deletes. > - The generic risk classifier also reads several Ditto mutations as reads: `translate_*` replaces variant text, and `link_*` and `unlink_*` replace developer IDs. > - This pull request adds the Ditto definition, its artwork, the research and permission-review ledger rows, documentation, deterministic tests, and one reviewed risk-classification rule. > - The benefit is a branded, governed Ditto connection with browser sign-in, an API-key option that uses the correct header, and correct risk levels for every documented tool. ## Linked Issues or Issue Description **Problem or motivation** Product and engineering teams use Ditto to keep UI copy consistent. They want their Paperclip agents to follow the Ditto style guide and reuse approved strings when they write code. Ditto is not in the Apps catalog. Operators must paste the MCP URL into the generic remote-MCP flow. That flow gives no branding and no method guidance. It also does not tell the operator that Ditto API keys use `Authorization: token <key>`, not `Bearer`, and it does not warn that delete tools are permanent. The generic classifier also marks five Ditto mutations as reads, so the Permissions screen shows them with the wrong risk level. **Proposed solution** Add a catalog-only Ditto connection built from the connector playbook. Browser sign-in uses dynamic client registration on Ditto's Auth0 authorization server and requests the reviewed scopes `openid`, `profile`, `email` and `offline_access`. An optional method sends a customer-created workspace API key with the `token` scheme. Both methods share one `write` capability profile, so the delete and link warning shows before sign-in. Add a reviewed `ditto` rule to `classifyRisk`: `list_*`, `get_*` and `search_*` tools, `suggest_edit`, and tools that Ditto marks read-only are reads; `delete_*` tools are destructive; all other tools are writes. **Alternatives considered** A plugin was not needed because no custom UI, tables, workers, or webhooks are involved. Requesting no scopes was not chosen, because Auth0 issues a refresh token only for `offline_access`, so the connection would expire with its first access token. Ditto's separate documentation MCP server (`developer.dittowords.com/mcp`) was not added, because it only searches Ditto's public docs. **Roadmap alignment** This extends the existing self-serve remote-MCP connection catalog and does not overlap planned core work. ## What Changed - Added the `ditto` provider to `scripts/ingest-app-definitions.mjs` (category `content`, `token` API-key placement, guidance, warnings, shared capability profile, description) and regenerated `packages/shared/src/app-definitions/ditto.json` and the generated registry. - Added the Ditto row to the self-serve MCP research ledger with `dcr_or_api_key` auth and risk tier S4, because the normal tool catalog includes permanent deletes. Widened `SelfServeMcpResearchEntry.wave` to include `4`, which existing entries already use. - Added permission reviews for `ditto/mcp-oauth` (explicit scopes `openid`, `profile`, `email`, `offline_access`) and `ditto/mcp-api-key` (provider key), with evidence links. - Added a reviewed `ditto` rule to `classifyRisk` in `server/src/services/tool-access.ts`. - Added Ditto's mark (`ui/public/brands/apps/ditto.png`, the avatar of Ditto's official GitHub organization) and the brand manifest entry. - Added gallery copy for the Ditto card. - Added `doc/connections/DITTO.md` (sign-in sequence diagram, provider and Paperclip endpoints including `/api/tools/oauth/callback`, scopes, administrator setup, capabilities and policy, manifest, brand provenance, validation hook) and linked it from the connections README and the permission audit. - Tests: Ditto definition shape, store visibility and artwork, URL recognition, reviewed scopes, `token` header projection with the key kept out of connection config, risk classification of every read-looking Ditto mutation, the connect form's sign-in default, the warning before sign-in, API-key gating, and the pinned catalog counts. ## Verification - `pnpm exec vitest run packages/shared/src/app-definitions.test.ts packages/shared/src/app-definitions-url.test.ts ui/src/pages/apps/AppsConnect.test.tsx ui/src/pages/apps/Browse.test.tsx ui/src/lib/app-brand-assets.test.ts ui/src/pages/apps/AppLogo.brand-assets.test.tsx server/src/__tests__/tool-access-service.test.ts` — 765 passed. - `node scripts/check-app-brand-assets.mjs` and `node --test scripts/app-brand-validation.test.mjs` — passed. - `pnpm --filter @paperclipai/shared typecheck`, `pnpm --filter @paperclipai/server typecheck`, `pnpm --filter @paperclipai/ui typecheck` — clean. - Manual: on a local instance, open Apps → Browse and confirm the Ditto card and icon. Open `/apps/connect?source=ditto` and confirm that sign-in is the default, that the warning shows before sign-in, and that the API-key method is under Advanced. - Live metadata probe: an unauthenticated `initialize` on `https://api.dittowords.com/v2/mcp` returns 401 with `resource_metadata`. The protected-resource and authorization-server documents return the recorded endpoints. Dynamic client registration succeeds, and the authorize endpoint redirects to Ditto's sign-in page with the reviewed scopes and the `resource` parameter. - Each of the 41 documented tool names was run through `classifyRisk` with and without the new rule. Without the rule, `translate_text_items`, `translate_library_components`, `link_figma_nodes_to_text_item`, `link_text_items_to_component` and `unlink_text_items_from_component` were reads. ## Risks - Low risk to existing providers: the catalog change is additive, and the new `classifyRisk` branch runs only for the `ditto` source template. - Ditto delete tools are permanent, and linking a text item to a component replaces its developer ID. Both methods warn the operator before connecting, and the API-key helper text tells operators to set delete and link actions to Ask first. - Ditto API keys are not scoped, reach the whole workspace, and act as the user who created them. Paperclip cannot narrow an issued key. - The permission-review ledger records live proof for both methods as not run. The lifecycle checklist in `doc/connections/DITTO.md` still needs a documented pass with a Ditto workspace, including confirmation that Auth0 issues a refresh token that `/v2/mcp` accepts. > For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and discuss it in `#dev` before opening the PR. Feature PRs that overlap with planned core work may need to be redirected — check the roadmap first. See `CONTRIBUTING.md`. ## Model Used - Claude Opus 5.5 (`claude-opus-5-5`, 1M context) in Claude Code, with tool use (shell, file editing, web research). ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [ ] All Paperclip CI gates are green - [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge
This commit is contained in:
1 parent
6dda8cc2b7
commit
a1e2173ceb
17 files changed
+637
-34
No files matched your search
@@ -1014,6 +1014,7 @@ const categoryBySlug = {
|
||||
cloudflare: "developer",
|
||||
cloudinary: "content",
|
||||
coda: "productivity",
|
||||
ditto: "content",
|
||||
egnyte: "content",
|
||||
embat: "commerce",
|
||||
fireflies: "productivity",
|
||||
@@ -1096,6 +1097,11 @@ const apiKeySpec = {
|
||||
prefix: "Bearer ",
|
||||
placeholder: "Paste your Cloudflare API token",
|
||||
},
|
||||
ditto: {
|
||||
name: "Authorization",
|
||||
prefix: "token ",
|
||||
placeholder: "Paste your Ditto API key",
|
||||
},
|
||||
coda: {
|
||||
name: "Authorization",
|
||||
prefix: "Bearer ",
|
||||
@@ -1233,6 +1239,34 @@ const specialMethodsFor = (entry) => {
|
||||
];
|
||||
// Gauge's sign-in picks one organization during consent; an API key is bound
|
||||
// to the organization that created it and carries no user identity.
|
||||
// Ditto's sign-in runs on Auth0, which issues refresh tokens only for
|
||||
// offline_access. Its API keys use the `token` scheme, not `Bearer`, and act
|
||||
// as the user who created them.
|
||||
if (entry.slug === "ditto") {
|
||||
const warning =
|
||||
"Ditto delete tools cannot be undone, and linking text to a library component replaces each text item's developer ID. Set those actions to Ask first before agents run unattended.";
|
||||
const capabilityProfile = {
|
||||
key: "write",
|
||||
label: "Read and write",
|
||||
description: `Read style guide rules and search product text, and create, edit and translate text, components, variants and style guides. ${warning}`,
|
||||
};
|
||||
return [
|
||||
oauthMethodFor(entry, "mcp-oauth", entry.serverUrl, {
|
||||
guidanceMd:
|
||||
"Connect Ditto in the browser. The connection acts as the Ditto user who signs in. Write tools start enabled and remain governed by Paperclip's action policies.",
|
||||
warnings: [entry.prerequisite, warning],
|
||||
capabilityProfile,
|
||||
}),
|
||||
apiKeyMethodFor(entry, "mcp-api-key", entry.serverUrl, {
|
||||
whenToUse: "Use a Ditto workspace API key when browser sign-in is not suitable.",
|
||||
guidanceMd:
|
||||
"In Ditto, open Developers → API keys, create a key for Paperclip, and paste it below.",
|
||||
consoleLinks: { keys: "https://app.dittowords.com/developers", docs: entry.docsUrl },
|
||||
warnings: [entry.prerequisite, warning],
|
||||
capabilityProfile,
|
||||
}),
|
||||
];
|
||||
}
|
||||
if (entry.slug === "gauge") {
|
||||
const warning =
|
||||
"Gauge content tools can publish to your connected CMS, including live. Set publish actions to Ask first before agents run unattended.";
|
||||
@@ -1783,7 +1817,7 @@ for (const entry of researchManifest.entries) {
|
||||
schemaVersion: 1,
|
||||
slug: entry.slug,
|
||||
name: entry.name,
|
||||
description: ({ neon: "Manage Postgres projects and branches, run SQL, and inspect schemas in Neon.", gauge: "Track how AI answers mention your brand, research keywords and traffic, and run content workflows.", superagent: "Review security findings, start red-team reports, and score content and packages before agents trust them.", mem0: "Remember preferences, conversations, events, and agent state.", zep: "Retrieve temporal graph memory and authorized business context.", supermemory: "Search and save shared memories, documents, and profiles.", honcho: "Remember conversations and retrieve context about peers.", telem: "Search the web and read pages across many search providers with one API key." })[entry.slug] ?? (entry.slug === "fireflies"
|
||||
description: ({ neon: "Manage Postgres projects and branches, run SQL, and inspect schemas in Neon.", ditto: "Search and reuse approved product copy, follow your style guide, and edit and translate text.", gauge: "Track how AI answers mention your brand, research keywords and traffic, and run content workflows.", superagent: "Review security findings, start red-team reports, and score content and packages before agents trust them.", mem0: "Remember preferences, conversations, events, and agent state.", zep: "Retrieve temporal graph memory and authorized business context.", supermemory: "Search and save shared memories, documents, and profiles.", honcho: "Remember conversations and retrieve context about peers.", telem: "Search the web and read pages across many search providers with one API key." })[entry.slug] ?? (entry.slug === "fireflies"
|
||||
? "Search meeting transcripts, read summaries and action items, and connect meeting-ready routines."
|
||||
: `Connect ${entry.name}'s provider-hosted MCP server.`),
|
||||
categories: [categoryBySlug[entry.slug] ?? "other"],
|
||||
|
||||
Reference in new issue
Block a user