mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-24 18:19:05 +02:00
Merge branch 'master' into tests/pipeline-coverage-rescue
This commit is contained in:
commit
5fdbdb9fbd
8325 files changed
+2455125
-781354
No files matched your search
@@ -40,15 +40,20 @@
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"build": "tsc && mkdir -p dist/cliplab && cp src/cliplab/LICENSE src/cliplab/PROVENANCE.md dist/cliplab/",
|
||||
"clean": "rm -rf dist",
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"zod": "^3.24.2"
|
||||
"zod": "^4.4.3",
|
||||
"three": "0.185.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.19.21",
|
||||
"typescript": "^5.7.3"
|
||||
"@types/node": "^24.0.0",
|
||||
"typescript": "^7.0.2",
|
||||
"@types/three": "0.185.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=24.11.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import { readFile, writeFile } from "node:fs/promises";
|
||||
import { z } from "zod";
|
||||
import { createRequire } from "node:module";
|
||||
|
||||
const runnerRequire = createRequire(new URL("../../paperclip-runner/package.json", import.meta.url));
|
||||
const { Ajv } = runnerRequire("ajv");
|
||||
const standaloneCode = runnerRequire("ajv/dist/standalone").default;
|
||||
import { skillFrontmatterSchema } from "../src/frontmatter.ts";
|
||||
|
||||
// Keep the standalone mock on the production parser and schema without adding
|
||||
// a runtime dependency on a Paperclip workspace package.
|
||||
const source = await readFile(new URL("../src/frontmatter.ts", import.meta.url), "utf8");
|
||||
function section(start: string, end?: string) {
|
||||
const from = source.indexOf(start);
|
||||
const to = end ? source.indexOf(end, from + start.length) : source.length;
|
||||
if (from < 0 || to < 0) throw new Error(`Frontmatter source section missing: ${start}`);
|
||||
return source.slice(from, to).trim();
|
||||
}
|
||||
const ajv = new Ajv({ strict: false, code: { esm: true, source: true, lines: true } });
|
||||
ajv.addSchema(z.toJSONSchema(skillFrontmatterSchema, { target: "draft-7" }), "skill-frontmatter");
|
||||
const validator = standaloneCode(ajv, { validateSkillFrontmatter: "skill-frontmatter" })
|
||||
.replaceAll('require("ajv/dist/runtime/ucs2length").default', '((value: string) => [...value].length)');
|
||||
if (/\brequire\(/.test(validator)) throw new Error("Unexpected runtime dependency in the standalone skill validator");
|
||||
const generated = [
|
||||
"// @ts-nocheck -- generated parser and standalone schema validator.",
|
||||
"// Generated from packages/shared/src/frontmatter.ts. Do not edit by hand.",
|
||||
"// Regenerate: node packages/shared/scripts/generate-runner-skill-frontmatter.ts",
|
||||
section("export interface MarkdownDoc", "export interface FrontmatterBlock"),
|
||||
section("export function isPlainRecord", "export function asString"),
|
||||
section("export function parseFrontmatterMarkdown", "function assertSerializableRecord"),
|
||||
section("function parseYamlFrontmatter"),
|
||||
validator,
|
||||
].join("\n\n") + "\n";
|
||||
const target = new URL("../../paperclip-runner/src/mock-core/skill-frontmatter.generated.ts", import.meta.url);
|
||||
if (process.argv.includes("--check")) {
|
||||
if (await readFile(target, "utf8") !== generated) throw new Error("Runner skill frontmatter contract is stale. Regenerate it.");
|
||||
} else {
|
||||
await writeFile(target, generated);
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`Slack app manifest > uses identical manual and automatic configuration except the OAuth redirect 1`] = `
|
||||
[
|
||||
"app_mentions:read",
|
||||
"assistant:write",
|
||||
"channels:history",
|
||||
"channels:read",
|
||||
"chat:write",
|
||||
"commands",
|
||||
"files:read",
|
||||
"files:write",
|
||||
"groups:history",
|
||||
"groups:read",
|
||||
"im:history",
|
||||
"im:read",
|
||||
"mpim:history",
|
||||
"mpim:read",
|
||||
"reactions:read",
|
||||
"reactions:write",
|
||||
"users:read",
|
||||
"im:write",
|
||||
"emoji:read",
|
||||
"pins:read",
|
||||
"pins:write",
|
||||
"bookmarks:read",
|
||||
"bookmarks:write",
|
||||
"channels:manage",
|
||||
"channels:write.topic",
|
||||
"groups:write",
|
||||
"groups:write.topic",
|
||||
"canvases:read",
|
||||
"canvases:write",
|
||||
"lists:read",
|
||||
"lists:write",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`Slack app manifest > uses identical manual and automatic configuration except the OAuth redirect 2`] = `
|
||||
[
|
||||
"agent_session_stopped",
|
||||
"app_mention",
|
||||
"message.channels",
|
||||
"message.groups",
|
||||
"message.im",
|
||||
"message.mpim",
|
||||
"member_joined_channel",
|
||||
"member_left_channel",
|
||||
"channel_left",
|
||||
"group_left",
|
||||
"reaction_added",
|
||||
"reaction_removed",
|
||||
"channel_archive",
|
||||
"group_archive",
|
||||
"channel_unarchive",
|
||||
"group_unarchive",
|
||||
"channel_deleted",
|
||||
"channel_rename",
|
||||
"group_rename",
|
||||
"app_uninstalled",
|
||||
"tokens_revoked",
|
||||
]
|
||||
`;
|
||||
@@ -0,0 +1,73 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { ACCOUNT_HANDLE_MAX_LENGTH, toAccountHandle } from "./account-handle.js";
|
||||
import { createUserSecretDefinitionSchema } from "./validators/secret.js";
|
||||
|
||||
describe("toAccountHandle", () => {
|
||||
it("returns the value for a plain identifier", () => {
|
||||
expect(toAccountHandle("acct-42")).toBe("acct-42");
|
||||
});
|
||||
|
||||
it("returns null for a value that holds a space, such as \"a b\"", () => {
|
||||
expect(toAccountHandle("a b")).toBeNull();
|
||||
});
|
||||
|
||||
it("returns null for a value with surrounding whitespace instead of trimming it", () => {
|
||||
// Trimming a value before validation would let " acct-42" and "acct-42"
|
||||
// resolve to the same handle. Two distinct identifiers must never share
|
||||
// one handle, so surrounding whitespace is a rejection, not something to
|
||||
// strip.
|
||||
expect(toAccountHandle(" acct-42")).toBeNull();
|
||||
expect(toAccountHandle("acct-42 ")).toBeNull();
|
||||
expect(toAccountHandle(" acct-42 ")).toBeNull();
|
||||
expect(toAccountHandle("\tacct-42\n")).toBeNull();
|
||||
// The plain identifier with no surrounding whitespace still passes.
|
||||
expect(toAccountHandle("acct-42")).toBe("acct-42");
|
||||
});
|
||||
|
||||
it("returns null for an empty string", () => {
|
||||
expect(toAccountHandle("")).toBeNull();
|
||||
});
|
||||
|
||||
it("returns null for a value that holds a plus sign, such as \"a+b\"", () => {
|
||||
expect(toAccountHandle("a+b")).toBeNull();
|
||||
});
|
||||
|
||||
it("returns null for a value that holds a colon, such as \"a:b\"", () => {
|
||||
expect(toAccountHandle("a:b")).toBeNull();
|
||||
});
|
||||
|
||||
it("returns null for a value longer than 100 characters", () => {
|
||||
const tooLong = "a".repeat(ACCOUNT_HANDLE_MAX_LENGTH + 1);
|
||||
expect(toAccountHandle(tooLong)).toBeNull();
|
||||
const atLimit = "a".repeat(ACCOUNT_HANDLE_MAX_LENGTH);
|
||||
expect(toAccountHandle(atLimit)).toBe(atLimit);
|
||||
});
|
||||
|
||||
it("returns null for \".\" and for \"..\"", () => {
|
||||
expect(toAccountHandle(".")).toBeNull();
|
||||
expect(toAccountHandle("..")).toBeNull();
|
||||
});
|
||||
|
||||
it("returns null for a value that starts with a hyphen, such as \"-rf\"", () => {
|
||||
expect(toAccountHandle("-rf")).toBeNull();
|
||||
});
|
||||
|
||||
it("returns null for a value that holds a shell metacharacter, such as \"$(id)\"", () => {
|
||||
expect(toAccountHandle("$(id)")).toBeNull();
|
||||
});
|
||||
|
||||
it("returns null for a value that holds a path separator or a NUL byte", () => {
|
||||
expect(toAccountHandle("a/b")).toBeNull();
|
||||
expect(toAccountHandle("a\\b")).toBeNull();
|
||||
expect(toAccountHandle("a\0b")).toBeNull();
|
||||
});
|
||||
|
||||
it("every accepted handle also passes the secret key schema", () => {
|
||||
const candidates = ["acct-42", "acct_42", "ACCT.42", "a".repeat(ACCOUNT_HANDLE_MAX_LENGTH)];
|
||||
for (const candidate of candidates) {
|
||||
const handle = toAccountHandle(candidate);
|
||||
expect(handle).not.toBeNull();
|
||||
expect(createUserSecretDefinitionSchema.shape.key.safeParse(handle).success).toBe(true);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,44 @@
|
||||
// A canonical account handle names one login account inside one company. Two
|
||||
// downstream sinks each guard the raw account identifier with a different
|
||||
// validator: a directory-segment denylist, and the secret-key allowlist
|
||||
// (`packages/shared/src/validators/secret.ts`). `toAccountHandle` accepts a
|
||||
// value only when it satisfies both, so one handle can safely name both a
|
||||
// directory and a secret.
|
||||
|
||||
/** The longest accepted account handle. The secret key limit is 120
|
||||
* characters, and the longest name prefix a caller adds is `CODEX_HOME_` at
|
||||
* 11 characters, so 100 leaves headroom. */
|
||||
export const ACCOUNT_HANDLE_MAX_LENGTH = 100;
|
||||
|
||||
// The same character class the secret key schema uses.
|
||||
const ACCOUNT_HANDLE_PATTERN = /^[a-zA-Z0-9_.-]+$/;
|
||||
|
||||
/**
|
||||
* Converts a raw account identifier into a safe account handle. Returns
|
||||
* `null` when the value cannot become one instead of throwing, so a caller
|
||||
* decides how to fail the operation.
|
||||
*
|
||||
* The identifier is the account's identity key: it names one directory and
|
||||
* one secret, so two distinct identifiers must never resolve to the same
|
||||
* handle. A caller must never normalize the value before this check (for
|
||||
* example, by trimming it) — normalizing first can alias a distinct
|
||||
* identifier onto an already-claimed handle and let a login accept a
|
||||
* different account's credential home as its own.
|
||||
*
|
||||
* An input becomes a handle only when every rule below is true:
|
||||
*
|
||||
* - It carries no leading or trailing whitespace.
|
||||
* - It matches `/^[a-zA-Z0-9_.-]+$/`.
|
||||
* - It is {@link ACCOUNT_HANDLE_MAX_LENGTH} characters or shorter.
|
||||
* - It is neither `.` nor `..`.
|
||||
* - It does not start with `-` (a leading `-` reads as a command-line option).
|
||||
*/
|
||||
export function toAccountHandle(rawAccountId: string): string | null {
|
||||
if (typeof rawAccountId !== "string" || rawAccountId.length === 0) return null;
|
||||
if (rawAccountId.trim() !== rawAccountId) return null;
|
||||
if (!ACCOUNT_HANDLE_PATTERN.test(rawAccountId)) return null;
|
||||
if (rawAccountId.length > ACCOUNT_HANDLE_MAX_LENGTH) return null;
|
||||
if (rawAccountId === "." || rawAccountId === "..") return null;
|
||||
if (rawAccountId.startsWith("-")) return null;
|
||||
return rawAccountId;
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
import { z } from "zod";
|
||||
import { centsToUnits, normalizeCents } from "./money.js";
|
||||
|
||||
export const exactCentsSchema = z.union([z.string().max(160), z.number().finite()]).transform((value, ctx) => {
|
||||
try {
|
||||
if ((typeof value === "number" ? value < 0 : value.startsWith("-")) || centsToUnits(value) < 0n) throw new Error("Money cannot be negative");
|
||||
return normalizeCents(value);
|
||||
} catch (error) {
|
||||
ctx.addIssue({ code: "custom", message: error instanceof Error ? error.message : "Invalid money" });
|
||||
return z.NEVER;
|
||||
}
|
||||
});
|
||||
export const pricingProvenanceSchema = z.object({
|
||||
source: z.enum(["provider_reported", "provider_invoice", "operator", "rate_card", "unknown"]).default("unknown"),
|
||||
version: z.string().max(100).optional(),
|
||||
evidence: z.string().max(1000).optional(),
|
||||
inputCentsPerMillion: exactCentsSchema.optional(),
|
||||
cachedInputCentsPerMillion: exactCentsSchema.optional(),
|
||||
outputCentsPerMillion: exactCentsSchema.optional(),
|
||||
cacheWriteCentsPerMillion: exactCentsSchema.optional(),
|
||||
serviceTier: z.string().max(50).optional(),
|
||||
contextTier: z.enum(["short", "long"]).optional(),
|
||||
});
|
||||
export const importBillingInvoiceSchema = z.object({
|
||||
biller: z.string().trim().min(1).max(150),
|
||||
externalId: z.string().trim().min(1).max(200),
|
||||
currency: z.string().regex(/^[A-Za-z]{3}$/).transform(value => value.toUpperCase()),
|
||||
lines: z.array(z.object({
|
||||
externalId: z.string().trim().min(1).max(200),
|
||||
kind: z.enum(["inference", "fee", "credit"]).default("inference"),
|
||||
amountCents: exactCentsSchema,
|
||||
occurredAt: z.iso.datetime({ offset: true }),
|
||||
costEventId: z.string().uuid().optional(),
|
||||
runId: z.string().uuid().optional(),
|
||||
providerRequestId: z.string().min(1).max(250).optional(),
|
||||
model: z.string().min(1).max(250).optional(),
|
||||
pricing: pricingProvenanceSchema.optional(),
|
||||
})).min(1).max(2000),
|
||||
}).superRefine((invoice, ctx) => {
|
||||
if (new Set(invoice.lines.map(line => line.externalId)).size !== invoice.lines.length)
|
||||
ctx.addIssue({ code: "custom", path: ["lines"], message: "Invoice line identifiers must be unique" });
|
||||
});
|
||||
export const adjustCostSchema = z.object({
|
||||
idempotencyKey: z.string().trim().min(1).max(200),
|
||||
expectedCents: exactCentsSchema,
|
||||
correctedCents: exactCentsSchema,
|
||||
invoiceLineId: z.string().uuid().optional(),
|
||||
reason: z.string().trim().min(1).max(1000),
|
||||
pricing: pricingProvenanceSchema,
|
||||
});
|
||||
export const repairAccountingSchema = z.object({ fingerprint: z.string().regex(/^[a-f0-9]{64}$/), reason: z.string().trim().min(1).max(1000) });
|
||||
export const retryAccountingSchema = z.object({ runId: z.string().uuid() });
|
||||
export const importProviderCostsSchema = z.object({
|
||||
provider: z.enum(["openai", "anthropic"]),
|
||||
secretId: z.string().uuid(),
|
||||
accountId: z.string().trim().min(1).max(150).regex(/^[a-zA-Z0-9_-]+$/),
|
||||
scopeIds: z.array(z.string().trim().min(1).max(150)).min(1).max(25),
|
||||
from: z.iso.date(),
|
||||
to: z.iso.date(),
|
||||
}).superRefine((input, ctx) => {
|
||||
const duration = Date.parse(input.to) - Date.parse(input.from);
|
||||
if (duration <= 0 || duration > 31 * 86400000 || Date.parse(input.to) > Math.floor(Date.now() / 86400000) * 86400000)
|
||||
ctx.addIssue({ code: "custom", message: "Choose up to 31 completed UTC days; the end date is exclusive." });
|
||||
if (new Set(input.scopeIds).size !== input.scopeIds.length)
|
||||
ctx.addIssue({ code: "custom", message: "Project/workspace identifiers must be unique." });
|
||||
});
|
||||
export type ImportProviderCosts = z.input<typeof importProviderCostsSchema>;
|
||||
export type ImportBillingInvoice = z.input<typeof importBillingInvoiceSchema>;
|
||||
export type AdjustCost = z.input<typeof adjustCostSchema>;
|
||||
export interface AccountingFinding {
|
||||
kind: "company_projection" | "agent_projection" | "runtime_projection" | "missing_receipt" | "missing_acknowledgement" | "legacy_runtime" | "receipt_mismatch";
|
||||
entityId: string;
|
||||
repairable: boolean;
|
||||
actual: Record<string, string>;
|
||||
expected: Record<string, string>;
|
||||
}
|
||||
export interface AccountingInspection {
|
||||
companyId: string;
|
||||
fingerprint: string;
|
||||
findings: AccountingFinding[];
|
||||
checkedAt: string;
|
||||
}
|
||||
export interface AccountingHealth {
|
||||
companyId: string;
|
||||
pendingRunCount: number;
|
||||
unpricedEventCount: number;
|
||||
oldestPendingAt: string | null;
|
||||
pendingCancellationCount: number;
|
||||
heldReservationCents: string;
|
||||
items: Array<{ runId: string | null; costEventId?: string; agentId: string | null; state: "waiting_for_receipt" | "retryable" | "unpriced"; lastError: string | null; attempts: number; since: string; lastAttemptAt: string | null }>;
|
||||
}
|
||||
export interface BillingInvoice {
|
||||
id: string; companyId: string; biller: string; externalId: string; currency: string; createdAt: string;
|
||||
}
|
||||
export interface CostAdjustment {
|
||||
id: string;
|
||||
companyId: string;
|
||||
costEventId: string;
|
||||
invoiceLineId: string | null;
|
||||
idempotencyKey: string;
|
||||
receiptHash: string;
|
||||
previousCents: string;
|
||||
correctedCents: string;
|
||||
previousStatus: string;
|
||||
previousPricing: Record<string, unknown>;
|
||||
pricing: Record<string, unknown>;
|
||||
reason: string;
|
||||
actorId: string;
|
||||
createdAt: string;
|
||||
}
|
||||
export interface BillingReconciliation {
|
||||
invoice: BillingInvoice;
|
||||
lines: Array<{ id: string; externalId: string; kind: string; amountCents: string; status: string;
|
||||
matchedEventId: string | null; recordedCents: string | null; differenceCents: string | null }>;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
|
||||
import { ADAPTER_AUTH_MISSING_CHECK_CODE } from "./adapter-auth-check-code.js";
|
||||
|
||||
describe("ADAPTER_AUTH_MISSING_CHECK_CODE", () => {
|
||||
it("keeps the canonical missing-auth check code stable", () => {
|
||||
expect(ADAPTER_AUTH_MISSING_CHECK_CODE).toBe("adapter_auth_missing");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* The canonical check code an adapter Test engine emits when a sandbox target
|
||||
* has no ready authentication. The user interface reads this stable code to
|
||||
* decide login eligibility. The user interface does not read the message text
|
||||
* or the top-level status.
|
||||
*
|
||||
* The name is neutral. It carries no vendor name and no login-flow word, because
|
||||
* a Test result can sync to public packages. The adapters and the user interface
|
||||
* import this one constant, so the user interface can gate login from one code
|
||||
* across adapters.
|
||||
*/
|
||||
export const ADAPTER_AUTH_MISSING_CHECK_CODE = "adapter_auth_missing";
|
||||
@@ -0,0 +1,56 @@
|
||||
import {
|
||||
type AdapterAuthSessionInternalStatus,
|
||||
type AdapterAuthSessionStatus,
|
||||
} from "./types/agent.js";
|
||||
|
||||
// The status helpers for an adapter login session. The server and the user
|
||||
// interface import these helpers, so both sides use one source. The helpers map
|
||||
// the internal status to the public status and name the active statuses.
|
||||
|
||||
// The active internal statuses. The company credential slot allows one active
|
||||
// session at a time, so the concurrency index applies to exactly these three
|
||||
// statuses. The `promoting` state stays active because the slot still holds the
|
||||
// company credential until the promotion window ends.
|
||||
export const ADAPTER_AUTH_SESSION_ACTIVE_STATUSES = [
|
||||
"starting",
|
||||
"waiting_for_user",
|
||||
"promoting",
|
||||
] as const satisfies readonly AdapterAuthSessionInternalStatus[];
|
||||
|
||||
export type AdapterAuthSessionActiveStatus =
|
||||
(typeof ADAPTER_AUTH_SESSION_ACTIVE_STATUSES)[number];
|
||||
|
||||
const ACTIVE_STATUS_SET: ReadonlySet<AdapterAuthSessionInternalStatus> = new Set(
|
||||
ADAPTER_AUTH_SESSION_ACTIVE_STATUSES,
|
||||
);
|
||||
|
||||
/** Returns true when the status holds the company credential slot. */
|
||||
export function isActiveAdapterAuthSessionStatus(
|
||||
status: AdapterAuthSessionInternalStatus,
|
||||
): status is AdapterAuthSessionActiveStatus {
|
||||
return ACTIVE_STATUS_SET.has(status);
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps an internal status to the public status. The map hides the two internal
|
||||
* states from a public response:
|
||||
*
|
||||
* - `promoting` maps to `waiting_for_user`.
|
||||
* - `cleanup_pending` throws. It is a terminal-cleanup bookkeeping state. The
|
||||
* caller must resolve the terminal status from the row before it builds a
|
||||
* public response. The throw stops any accidental leak of the cleanup state.
|
||||
*/
|
||||
export function toPublicAdapterAuthSessionStatus(
|
||||
status: AdapterAuthSessionInternalStatus,
|
||||
): AdapterAuthSessionStatus {
|
||||
switch (status) {
|
||||
case "promoting":
|
||||
return "waiting_for_user";
|
||||
case "cleanup_pending":
|
||||
throw new Error(
|
||||
"cleanup_pending is an internal cleanup state; resolve the terminal status before you build a public response",
|
||||
);
|
||||
default:
|
||||
return status;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { AGENT_PALETTE_IDS, agentAppearanceSchema, appearanceForPalette, legacyAgentAppearance, randomAgentAppearance, resolveAgentAppearance, agentAvatarUrl } from "./agent-appearance.js";
|
||||
import { renderAgentSvg } from "./cliplab/static.js";
|
||||
|
||||
describe("agent appearance", () => {
|
||||
it("assigns only the 17 permanent cap palettes", () => {
|
||||
for (let i = 0; i < 100; i++) expect(AGENT_PALETTE_IDS).toContain(randomAgentAppearance().paletteId);
|
||||
expect(agentAppearanceSchema.safeParse({ schemaVersion: 1, characterVersion: "cap-v1", paletteId: "muted-dream" }).success).toBe(false);
|
||||
});
|
||||
it("preserves a saved appearance and resolves legacy IDs deterministically", () => {
|
||||
const appearance = appearanceForPalette("deep-tide");
|
||||
expect(resolveAgentAppearance(appearance, "different-id")).toEqual(appearance);
|
||||
expect(resolveAgentAppearance(null, "agent-1")).toEqual(legacyAgentAppearance("agent-1"));
|
||||
expect(legacyAgentAppearance("agent-1")).toEqual(legacyAgentAppearance("agent-1"));
|
||||
expect(agentAvatarUrl(appearance, 24, 2)).toBe("/api/agent-avatars/cap-v1/deep-tide/rest.png?size=24&scale=2");
|
||||
});
|
||||
it("uses a private asset URL for custom avatars while retaining the fallback palette", () => {
|
||||
const appearance = { ...appearanceForPalette("deep-tide"), customAvatarAssetId: "22222222-2222-4222-8222-222222222222" };
|
||||
expect(resolveAgentAppearance(appearance)).toEqual(appearance);
|
||||
expect(agentAvatarUrl(appearance, 128, 2)).toBe("/api/assets/22222222-2222-4222-8222-222222222222/content");
|
||||
expect(agentAppearanceSchema.safeParse({ ...appearance, customAvatarAssetId: "https://example.test/image" }).success).toBe(false);
|
||||
});
|
||||
it("renders without a browser and preserves logical-size detail at high density", () => {
|
||||
const appearance = appearanceForPalette("bubblegum-sky");
|
||||
// ClipLab v0.2.0 draws an enlarged compact face from 16px; only 12px and
|
||||
// below (not a logical size) is body-only.
|
||||
const small = renderAgentSvg(appearance, 16, 2);
|
||||
expect(small).toContain('width="32"');
|
||||
expect(small).toContain('id="agent-face-visible"');
|
||||
const eyes = renderAgentSvg(appearance, 24, 2);
|
||||
expect(eyes).toContain('width="48"');
|
||||
expect(eyes).toContain('id="agent-face-visible"');
|
||||
expect(eyes).not.toContain('id="agent-candle-light"');
|
||||
expect(renderAgentSvg(appearance, 48, 1)).toContain('id="agent-candle-light"');
|
||||
expect(renderAgentSvg(appearance, 24, 2)).toBe(eyes);
|
||||
});
|
||||
it("uses a separate URL for opaque Slack exports without changing ordinary portraits", () => {
|
||||
const appearance = appearanceForPalette("cherry-pop");
|
||||
expect(agentAvatarUrl(appearance, 512, 1, "rest", false, "paperclip-dark")).toBe("/api/agent-avatars/cap-v1/cherry-pop/rest.png?size=512&scale=1&background=paperclip-dark");
|
||||
expect(agentAvatarUrl(appearance)).not.toContain("background=");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,58 @@
|
||||
import { z } from "zod";
|
||||
|
||||
export const AGENT_PALETTE_IDS = ["bubblegum-sky", "pink-lemonade", "orchid-peach", "coral-mint", "lime-lagoon", "arctic-blue", "solar-flare", "violet-ember", "deep-tide", "coral-current", "golden-hour", "tangerine-cobalt", "electric-grove", "flamingo-jade", "cherry-pop", "turquoise-cherry", "ultraviolet-tide"] as const;
|
||||
export type AgentPaletteId = typeof AGENT_PALETTE_IDS[number];
|
||||
export type CharacterPaletteId = AgentPaletteId | "muted-dream";
|
||||
export const AGENT_AVATAR_SIZES = [16, 20, 24, 32, 40, 48, 64, 96, 128, 256, 512] as const;
|
||||
export type AgentAvatarSize = typeof AGENT_AVATAR_SIZES[number];
|
||||
export type AgentAvatarBackground = "transparent" | "paperclip-dark";
|
||||
/** sRGB PNG equivalent of index.css .dark --background: oklch(0.205 0 0). */
|
||||
export const PAPERCLIP_DARK_AVATAR_BACKGROUND = "#171717";
|
||||
export const CHARACTER_STATES = ["rest", "idle", "listening", "thinking", "working", "success", "confused", "sleepy", "loading"] as const;
|
||||
export type CharacterState = typeof CHARACTER_STATES[number];
|
||||
export const agentAppearanceSchema = z.object({
|
||||
schemaVersion: z.literal(1),
|
||||
characterVersion: z.literal("cap-v1"),
|
||||
paletteId: z.enum(AGENT_PALETTE_IDS),
|
||||
/** Company-scoped normalized image, set through the avatar upload service. */
|
||||
customAvatarAssetId: z.uuid().optional(),
|
||||
}).strict();
|
||||
export type AgentAppearance = z.infer<typeof agentAppearanceSchema>;
|
||||
|
||||
export function appearanceForPalette(paletteId: AgentPaletteId): AgentAppearance {
|
||||
return { schemaVersion: 1, characterVersion: "cap-v1", paletteId };
|
||||
}
|
||||
/** A persisted choice, never randomize while rendering. */
|
||||
export function randomAgentAppearance(): AgentAppearance {
|
||||
const bytes = new Uint32Array(1);
|
||||
// Rejection sampling avoids modulo bias.
|
||||
const limit = Math.floor(0x100000000 / AGENT_PALETTE_IDS.length) * AGENT_PALETTE_IDS.length;
|
||||
do { globalThis.crypto.getRandomValues(bytes); } while (bytes[0] >= limit);
|
||||
return appearanceForPalette(AGENT_PALETTE_IDS[bytes[0] % AGENT_PALETTE_IDS.length]);
|
||||
}
|
||||
/** Must match the migration: rolling base-31 hash, modulo 17 at every step. */
|
||||
export function legacyAgentAppearance(id: string): AgentAppearance {
|
||||
let hash = 0;
|
||||
for (const char of id) hash = (hash * 31 + char.charCodeAt(0)) % AGENT_PALETTE_IDS.length;
|
||||
return appearanceForPalette(AGENT_PALETTE_IDS[hash]);
|
||||
}
|
||||
export function resolveAgentAppearance(appearance: unknown, id = "agent"): AgentAppearance {
|
||||
const parsed = agentAppearanceSchema.safeParse(appearance);
|
||||
return parsed.success ? parsed.data : legacyAgentAppearance(id);
|
||||
}
|
||||
export function agentAvatarUrl(appearance: AgentAppearance, size: AgentAvatarSize = 512, scale: 1 | 2 = 1, pose: CharacterState = "rest", muted = false, background: AgentAvatarBackground = "transparent"): string {
|
||||
if (appearance.customAvatarAssetId) return `/api/assets/${appearance.customAvatarAssetId}/content`;
|
||||
return `/api/agent-avatars/${appearance.characterVersion}/${muted ? "muted-dream" : appearance.paletteId}/${pose}.png?size=${size}&scale=${scale}${background === "paperclip-dark" ? "&background=paperclip-dark" : ""}`;
|
||||
}
|
||||
export function characterStateForAgent(status: string): CharacterState {
|
||||
if (status === "running") return "working";
|
||||
if (status === "error") return "confused";
|
||||
if (status === "paused" || status === "terminated" || status === "pending_approval") return "rest";
|
||||
return "idle";
|
||||
}
|
||||
|
||||
/** Hydrate compact agent projections without an additional per-agent request. */
|
||||
export function withAgentAppearance<T extends { id: string; appearance?: AgentAppearance | null }>(agent: T) {
|
||||
const appearance = resolveAgentAppearance(agent.appearance, agent.id);
|
||||
return { ...agent, appearance, avatarUrl: agentAvatarUrl(appearance) };
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import { z } from "zod";
|
||||
|
||||
export const MAX_AGENT_AVATAR_BYTES = 512 * 1024;
|
||||
/** Raw PNG/JPEG/WebP bytes, not a URL or a data URL. Null restores the Paperclip character. */
|
||||
export const setAgentAvatarSchema = z.object({
|
||||
imageBase64: z.string().min(4).max(4 * Math.ceil(MAX_AGENT_AVATAR_BYTES / 3))
|
||||
.regex(/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/)
|
||||
.nullable(),
|
||||
}).strict();
|
||||
export type SetAgentAvatarInput = z.infer<typeof setAgentAvatarSchema>;
|
||||
@@ -0,0 +1,18 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { AGENT_COMMENTARY_MAX_LENGTH, submitAgentCommentarySchema } from "./validators/agent-commentary.js";
|
||||
|
||||
describe("agent commentary input", () => {
|
||||
const input = { kind: "complaint", body: "WHY 😭\n\nThis tool again.", idempotencyKey: "one" };
|
||||
it("preserves free-form text and the document-sized ceiling", () => {
|
||||
expect(submitAgentCommentarySchema.parse(input)).toEqual(input);
|
||||
const body = "🙂".repeat(AGENT_COMMENTARY_MAX_LENGTH / 2);
|
||||
expect(submitAgentCommentarySchema.parse({ ...input, kind: "suggestion", body }).body).toBe(body);
|
||||
expect(submitAgentCommentarySchema.safeParse({ ...input, body: body + "x" }).success).toBe(false);
|
||||
});
|
||||
it.each([
|
||||
{ body: " \n\t" }, { kind: "other" }, { idempotencyKey: "" }, { idempotencyKey: "x".repeat(241) },
|
||||
{ agentId: "spoof" }, { companyId: "spoof" }, { runId: "spoof" }, { issueId: "spoof" }, { category: "tooling" },
|
||||
])("rejects invalid or caller-owned fields: %j", (change) => {
|
||||
expect(submitAgentCommentarySchema.safeParse({ ...input, ...change }).success).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -156,3 +156,69 @@ describe("agent work eligibility", () => {
|
||||
expect(eligibility.invokabilityReason).toBe("invalid_org_chain");
|
||||
});
|
||||
});
|
||||
|
||||
describe("paused escalation path warning", () => {
|
||||
it("warns when an active agent's manager is paused", () => {
|
||||
const manager = agent({ id: "manager-1", name: "CTO", status: "paused", reportsTo: null });
|
||||
const coder = agent({ id: "agent-1", name: "Coder", status: "active", reportsTo: "manager-1" });
|
||||
const result = getAgentWorkEligibility({ agent: coder, agents: [coder, manager] });
|
||||
|
||||
expect(result.invokable).toBe(true);
|
||||
expect(result.orgChainHealth.status).toBe("healthy");
|
||||
expect(result.orgChainHealth.pausedAncestors).toEqual([
|
||||
{ id: "manager-1", name: "CTO", status: "paused" },
|
||||
]);
|
||||
expect(result.orgChainHealth.escalationWarning).toContain("route to paused agent CTO");
|
||||
expect(result.orgChainHealth.escalationWarning).toContain("never runs");
|
||||
});
|
||||
|
||||
it("warns about a paused grandparent through a healthy manager", () => {
|
||||
const executive = agent({ id: "exec-1", name: "CEO", status: "paused", reportsTo: null });
|
||||
const manager = agent({ id: "manager-1", name: "CTO", status: "active", reportsTo: "exec-1" });
|
||||
const coder = agent({ id: "agent-1", name: "Coder", status: "active", reportsTo: "manager-1" });
|
||||
const result = getAgentWorkEligibility({ agent: coder, agents: [coder, manager, executive] });
|
||||
|
||||
expect(result.orgChainHealth.pausedAncestors).toEqual([
|
||||
{ id: "exec-1", name: "CEO", status: "paused" },
|
||||
]);
|
||||
expect(result.orgChainHealth.escalationWarning).toContain("CEO");
|
||||
});
|
||||
|
||||
it("does not warn when the agent itself is paused", () => {
|
||||
const manager = agent({ id: "manager-1", name: "CTO", status: "paused", reportsTo: null });
|
||||
const coder = agent({ id: "agent-1", name: "Coder", status: "paused", reportsTo: "manager-1" });
|
||||
const result = getAgentWorkEligibility({ agent: coder, agents: [coder, manager] });
|
||||
|
||||
expect(result.orgChainHealth.escalationWarning).toBeNull();
|
||||
expect(result.orgChainHealth.pausedAncestors).toEqual([
|
||||
{ id: "manager-1", name: "CTO", status: "paused" },
|
||||
]);
|
||||
});
|
||||
|
||||
it("does not warn for agents with unknown statuses", () => {
|
||||
const manager = agent({ id: "manager-1", name: "CTO", status: "paused", reportsTo: null });
|
||||
const coder = agent({ id: "agent-1", name: "Coder", status: "mystery", reportsTo: "manager-1" });
|
||||
const result = getAgentWorkEligibility({ agent: coder, agents: [coder, manager] });
|
||||
|
||||
expect(result.invokable).toBe(false);
|
||||
expect(result.orgChainHealth.escalationWarning).toBeNull();
|
||||
});
|
||||
|
||||
it("does not warn on a fully active chain", () => {
|
||||
const manager = agent({ id: "manager-1", name: "CTO", status: "active", reportsTo: null });
|
||||
const coder = agent({ id: "agent-1", name: "Coder", status: "active", reportsTo: "manager-1" });
|
||||
const result = getAgentWorkEligibility({ agent: coder, agents: [coder, manager] });
|
||||
|
||||
expect(result.orgChainHealth.escalationWarning).toBeNull();
|
||||
expect(result.orgChainHealth.pausedAncestors).toEqual([]);
|
||||
});
|
||||
|
||||
it("keeps invalid-chain classification for terminated ancestors, without duplicating them as paused", () => {
|
||||
const manager = agent({ id: "manager-1", name: "CTO", status: "terminated", reportsTo: null });
|
||||
const coder = agent({ id: "agent-1", name: "Coder", status: "active", reportsTo: "manager-1" });
|
||||
const result = getAgentWorkEligibility({ agent: coder, agents: [coder, manager] });
|
||||
|
||||
expect(result.orgChainHealth.status).toBe("invalid_org_chain");
|
||||
expect(result.orgChainHealth.pausedAncestors).toEqual([]);
|
||||
});
|
||||
});
|
||||
@@ -45,6 +45,15 @@ export interface AgentOrgChainHealth {
|
||||
firstInvalidAncestor: AgentInvalidOrgChainAncestor | null;
|
||||
invalidAncestors: AgentInvalidOrgChainAncestor[];
|
||||
repairGuidance: string | null;
|
||||
/**
|
||||
* Paused ancestors of a non-paused agent. A paused manager does not make
|
||||
* the chain invalid (the agent stays invokable), but escalations routed to
|
||||
* it dead-letter: assigned work never runs and nothing surfaces it. This is
|
||||
* a warning, not a block.
|
||||
*/
|
||||
pausedAncestors?: AgentInvalidOrgChainAncestor[];
|
||||
/** Human-readable warning when the escalation path routes to a paused agent. */
|
||||
escalationWarning?: string | null;
|
||||
}
|
||||
|
||||
export interface AgentWorkEligibility {
|
||||
@@ -121,6 +130,7 @@ export function getAgentOrgChainHealth(input: {
|
||||
const byId = new Map(input.agents.map((agent) => [agent.id, agent]));
|
||||
const fullChain: AgentOrgChainEntry[] = [chainEntry(input.agent, 0, "self")];
|
||||
const invalidAncestors: AgentInvalidOrgChainAncestor[] = [];
|
||||
const pausedAncestors: AgentInvalidOrgChainAncestor[] = [];
|
||||
const seen = new Set<string>([input.agent.id]);
|
||||
|
||||
let current = input.agent;
|
||||
@@ -171,12 +181,25 @@ export function getAgentOrgChainHealth(input: {
|
||||
if (parent.status === "terminated") {
|
||||
invalidAncestors.push(invalidAncestor(parent));
|
||||
}
|
||||
if (parent.status === "paused") {
|
||||
pausedAncestors.push({ id: parent.id, name: parent.name, status: "paused" });
|
||||
}
|
||||
|
||||
current = parent;
|
||||
depth += 1;
|
||||
}
|
||||
|
||||
const firstInvalidAncestor = invalidAncestors[0] ?? null;
|
||||
// Only warn for agents that can themselves receive and run work: a paused,
|
||||
// terminated, or unknown-status agent's escalation path is moot until it is
|
||||
// invokable again. Allowlist on purpose — a denylist complement would treat
|
||||
// unrecognized statuses as workable and warn misleadingly.
|
||||
const agentCanWork = isAgentStatusInvokable(input.agent.status);
|
||||
const firstPausedAncestor = pausedAncestors[0] ?? null;
|
||||
const escalationWarning = agentCanWork && firstPausedAncestor
|
||||
? `Escalations from ${input.agent.name} route to paused agent ${firstPausedAncestor.name}. ` +
|
||||
`Work assigned to a paused agent never runs; unpause ${firstPausedAncestor.name} or change who this agent reports to.`
|
||||
: null;
|
||||
return {
|
||||
status: firstInvalidAncestor ? "invalid_org_chain" : "healthy",
|
||||
reason: firstInvalidAncestor
|
||||
@@ -192,6 +215,8 @@ export function getAgentOrgChainHealth(input: {
|
||||
repairGuidance: firstInvalidAncestor
|
||||
? buildRepairGuidance(input.agent, firstInvalidAncestor)
|
||||
: null,
|
||||
pausedAncestors,
|
||||
escalationWarning,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { AGGREGATOR_APP_CATALOG, COMPOSIO_APP_TOOLKITS, findComposioCatalogApp, findAggregatorApp } from "./aggregator-app-catalog.js";
|
||||
|
||||
describe("public aggregator app catalog", () => {
|
||||
it("indexes the full snapshots and merges provider routes for the same app", () => {
|
||||
expect(AGGREGATOR_APP_CATALOG.length).toBeGreaterThan(1500);
|
||||
const hubspot = findAggregatorApp("composio", "hubspot")!;
|
||||
expect(hubspot.routes.map((route) => route.provider)).toEqual(["composio", "arcade"]);
|
||||
expect(findAggregatorApp("arcade", "hubspot")).toBe(hubspot);
|
||||
expect(findAggregatorApp("composio", "made-up-toolkit")).toBeUndefined();
|
||||
expect(findAggregatorApp("executor", "hubspot")).toBeUndefined();
|
||||
});
|
||||
|
||||
it("retains every upstream toolkit variant for discovery and maps it to an app card", () => {
|
||||
expect(COMPOSIO_APP_TOOLKITS.length).toBeGreaterThan(1500);
|
||||
expect(COMPOSIO_APP_TOOLKITS.every(toolkit => findComposioCatalogApp(toolkit))).toBe(true);
|
||||
expect(findComposioCatalogApp("circleback_mcp")?.name).toBe("Circleback");
|
||||
expect(findComposioCatalogApp("notion")?.name).toBe("Notion");
|
||||
expect(findComposioCatalogApp("made-up-toolkit")).toBeUndefined();
|
||||
});
|
||||
|
||||
it("keeps real provider toolkit identifiers and public logo/evidence URLs", () => {
|
||||
expect(findAggregatorApp("composio", "active_campaign")?.routes[0]).toMatchObject({
|
||||
toolkit: "active_campaign", logoUrl: "https://logos.composio.dev/api/active_campaign",
|
||||
});
|
||||
for (const app of AGGREGATOR_APP_CATALOG) {
|
||||
expect(app.name).toBeTruthy();
|
||||
expect(new Set(app.routes.map((route) => route.provider)).size).toBe(app.routes.length);
|
||||
for (const route of app.routes) {
|
||||
expect(new URL(route.logoUrl).protocol).toBe("https:");
|
||||
expect(new URL(route.docsUrl).hostname).toBe(`docs.${route.provider}.dev`);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it.each([
|
||||
["close", "closeio"],
|
||||
["excel", "microsoft-excel"],
|
||||
["one_drive", "microsoft-onedrive"],
|
||||
["share_point", "microsoft-sharepoint"],
|
||||
["square", "squareup-api"],
|
||||
["twitter", "x"],
|
||||
])("merges the same service despite different provider branding (%s)", (composioToolkit, arcadeToolkit) => {
|
||||
const app = findAggregatorApp("composio", composioToolkit)!;
|
||||
expect(findAggregatorApp("arcade", arcadeToolkit)).toBe(app);
|
||||
expect(app.routes.map((route) => route.provider)).toEqual(["composio", "arcade"]);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,95 @@
|
||||
import composio from "./composio-search-catalog.json" with { type: "json" };
|
||||
import arcade from "./arcade-app-catalog.json" with { type: "json" };
|
||||
|
||||
export type AppCatalogAggregator = "composio" | "arcade" | "executor";
|
||||
|
||||
export interface AggregatorAppRoute {
|
||||
provider: AppCatalogAggregator;
|
||||
toolkit: string;
|
||||
logoUrl: string;
|
||||
docsUrl: string;
|
||||
}
|
||||
|
||||
export interface AggregatorAppCatalogEntry {
|
||||
slug: string;
|
||||
name: string;
|
||||
aliases: string[];
|
||||
routes: AggregatorAppRoute[];
|
||||
}
|
||||
|
||||
// The public catalogs use different brand names for these same services.
|
||||
const SERVICE_IDENTITY_ALIASES = new Map([
|
||||
["closeio", "close"],
|
||||
["microsoftexcel", "excel"],
|
||||
["microsoftonedrive", "onedrive"],
|
||||
["microsoftsharepoint", "sharepoint"],
|
||||
["squareup", "square"],
|
||||
["x", "twitter"],
|
||||
]);
|
||||
|
||||
/** Public discovery metadata only. A listing does not establish app authorization. */
|
||||
export function aggregatorAppIdentity(value: string): string {
|
||||
const identity = value.toLowerCase().replace(/\s+(mcp|api)$/i, "")
|
||||
.replace(/[^a-z0-9]/g, "");
|
||||
return SERVICE_IDENTITY_ALIASES.get(identity) ?? identity;
|
||||
}
|
||||
|
||||
const entries = new Map<string, AggregatorAppCatalogEntry>();
|
||||
function add(name: string, slug: string, aliases: string[], route: AggregatorAppRoute) {
|
||||
const identity = aggregatorAppIdentity(name);
|
||||
const existing = entries.get(identity);
|
||||
if (existing) {
|
||||
existing.aliases = [...new Set([...existing.aliases, name, slug, ...aliases])];
|
||||
// Prefer the ordinary toolkit over an API/MCP variant from the same provider.
|
||||
if (!existing.routes.some((candidate) => candidate.provider === route.provider)) {
|
||||
existing.routes.push(route);
|
||||
}
|
||||
return;
|
||||
}
|
||||
entries.set(identity, { slug, name: name.replace(/\s+(MCP|API)$/i, ""), aliases, routes: [route] });
|
||||
}
|
||||
|
||||
for (const [toolkit, name] of composio.toolkits) {
|
||||
add(name, toolkit.replaceAll("_", "-").replace(/^-+/, ""), [toolkit], {
|
||||
provider: "composio", toolkit,
|
||||
logoUrl: `https://logos.composio.dev/api/${toolkit}`,
|
||||
docsUrl: `https://docs.composio.dev/toolkits/${toolkit}`,
|
||||
});
|
||||
}
|
||||
for (const app of arcade.apps) {
|
||||
add(app.name, app.slug, app.aliases, {
|
||||
provider: "arcade", toolkit: app.toolkit, logoUrl: app.logoUrl, docsUrl: app.docsUrl,
|
||||
});
|
||||
}
|
||||
|
||||
export const AGGREGATOR_APP_CATALOG = [...entries.values()].sort((a, b) =>
|
||||
a.name.localeCompare(b.name, "en", { sensitivity: "base" }) || a.slug.localeCompare(b.slug));
|
||||
|
||||
/** Include provider variants and native overlaps when checking existing accounts. */
|
||||
export const COMPOSIO_APP_TOOLKITS = composio.toolkits.map(([toolkit]) => toolkit);
|
||||
|
||||
export function findComposioCatalogApp(toolkit: string) {
|
||||
return AGGREGATOR_APP_CATALOG.find(app => app.routes.some(route => route.provider === "composio")
|
||||
&& (app.aliases.includes(toolkit) || app.routes.some(route => route.provider === "composio" && route.toolkit === toolkit)));
|
||||
}
|
||||
|
||||
export function findAggregatorApp(provider: string, toolkit: string | null | undefined) {
|
||||
if (!toolkit) return undefined;
|
||||
return AGGREGATOR_APP_CATALOG.find((app) =>
|
||||
app.routes.some((route) => route.provider === provider)
|
||||
&& (app.routes.some(route => route.provider === provider && route.toolkit === toolkit) || app.aliases.includes(toolkit)));
|
||||
}
|
||||
|
||||
/** Executor supports custom integrations. Only exact known service identities share branding. */
|
||||
export function resolveAggregatorApp(provider: AppCatalogAggregator, toolkit: string, name?: string) {
|
||||
const known = provider === "composio" ? findComposioCatalogApp(toolkit) : findAggregatorApp(provider, toolkit)
|
||||
?? AGGREGATOR_APP_CATALOG.find(app => app.routes.some(route => route.provider === provider)
|
||||
&& [app.slug, ...app.aliases].some(alias => aggregatorAppIdentity(alias) === aggregatorAppIdentity(toolkit)));
|
||||
if (known) return { slug: known.slug, name: known.name };
|
||||
if (provider === "executor") {
|
||||
const identity = aggregatorAppIdentity(toolkit);
|
||||
const match = AGGREGATOR_APP_CATALOG.find(app => [app.slug, ...app.aliases].some(alias => aggregatorAppIdentity(alias) === identity));
|
||||
if (match) return { slug: match.slug, name: match.name };
|
||||
}
|
||||
return { slug: `${provider}:${toolkit}`, name: name || toolkit };
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
import { z } from "zod";
|
||||
import type { ComposioAppSnapshot, ComposioAppSyncState } from "./composio-app-setup.js";
|
||||
import type { AppCatalogAggregator } from "./aggregator-app-catalog.js";
|
||||
|
||||
export const AGGREGATOR_NAMES = { composio: "Composio", arcade: "Arcade", executor: "Executor" } as const;
|
||||
export function isAppAggregator(value: unknown): value is AppCatalogAggregator {
|
||||
return typeof value === "string" && Object.hasOwn(AGGREGATOR_NAMES, value);
|
||||
}
|
||||
|
||||
/** Cached upstream observations. These neither grant access nor create tool connections. */
|
||||
export interface AggregatorAppSnapshot extends ComposioAppSnapshot {
|
||||
provider: AppCatalogAggregator;
|
||||
appSlug: string;
|
||||
appName: string;
|
||||
freshness: "fresh" | "stale";
|
||||
}
|
||||
export interface AggregatorAppsResponse {
|
||||
provider: AppCatalogAggregator;
|
||||
apps: AggregatorAppSnapshot[];
|
||||
discovery: {
|
||||
availability: "available" | "setup_required" | "unsupported" | "disabled";
|
||||
message: string | null;
|
||||
};
|
||||
sync: Omit<ComposioAppSyncState, "coverage"> & {
|
||||
coverage: "supported_catalog" | "gateway_tools" | "visible_accounts";
|
||||
};
|
||||
}
|
||||
export const aggregatorAppsSyncSchema = z.object({ force: z.boolean().default(false) }).strict();
|
||||
export const aggregatorAppsRefreshSchema = z.object({
|
||||
toolkits: z.array(z.string().min(1).max(200)).max(64).default([]),
|
||||
}).strict();
|
||||
export const arcadeDiscoverySetupSchema = z.object({
|
||||
apiKey: z.string().trim().min(1).max(8192),
|
||||
userId: z.string().trim().min(1).max(500),
|
||||
}).strict();
|
||||
export type ArcadeDiscoverySetupInput = z.infer<typeof arcadeDiscoverySetupSchema>;
|
||||
|
||||
/** Only trustworthy browser destinations; never expose URL credentials or active schemes. */
|
||||
export function aggregatorManagementUrl(provider: AppCatalogAggregator, configured?: string | null): string | null {
|
||||
if (configured) {
|
||||
try {
|
||||
const url = new URL(configured);
|
||||
if (url.protocol !== "https:" || url.username || url.password || [...url.searchParams.keys()].some(key => /token|secret|api.?key|authorization|code/i.test(key))) return null;
|
||||
if (provider === "composio" && url.hostname !== "dashboard.composio.dev") return null;
|
||||
if (provider === "arcade" && url.hostname !== "app.arcade.dev") return null;
|
||||
return url.toString();
|
||||
} catch { return null; }
|
||||
}
|
||||
return provider === "composio" ? "https://dashboard.composio.dev/~/org/connect/apps"
|
||||
: provider === "arcade" ? "https://app.arcade.dev/" : null;
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
import { z } from "zod";
|
||||
import { aiConnectionBindingSchema, type AiConnectionBinding } from "./ai-connections.js";
|
||||
import type { AiConnectionUsage } from "./ai-connection-usage.js";
|
||||
|
||||
export const aiConnectionRouterBindingSchema = z.object({
|
||||
mode: z.literal("router"),
|
||||
connectionId: z.string().uuid(),
|
||||
}).strict();
|
||||
export const aiRuntimeConnectionBindingSchema = z.union([aiConnectionBindingSchema, aiConnectionRouterBindingSchema]);
|
||||
export type AiRuntimeConnectionBinding = z.infer<typeof aiRuntimeConnectionBindingSchema>;
|
||||
|
||||
export const aiConnectionPoolMemberSchema = z.object({
|
||||
id: z.string().uuid(),
|
||||
binding: aiConnectionBindingSchema.refine((v) => v.mode !== "responsible_user", "Select an explicit account grant"),
|
||||
profile: z.object({
|
||||
provider: z.enum(["codex", "acpx", "opencode"]),
|
||||
acpxAgent: z.enum(["claude", "grok"]).optional(),
|
||||
model: z.string().trim().min(1).max(256),
|
||||
effort: z.string().trim().min(1).max(32).optional(),
|
||||
}).strict(),
|
||||
}).strict();
|
||||
export const aiConnectionPoolConfigSchema = z.object({
|
||||
name: z.string().trim().min(1).max(160),
|
||||
enabled: z.boolean().default(false),
|
||||
mode: z.enum(["round_robin", "usage_aware"]).default("round_robin"),
|
||||
thresholdPercent: z.number().int().min(1).max(100).default(90),
|
||||
members: z.array(aiConnectionPoolMemberSchema).min(1).max(100),
|
||||
}).strict().superRefine((v, ctx) => {
|
||||
const ids = new Set<string>();
|
||||
const grants = new Set<string>();
|
||||
const connections = new Set<string>();
|
||||
for (const [index, member] of v.members.entries()) {
|
||||
const grant = `${member.binding.connectionId}:${member.binding.grantId}`;
|
||||
if (ids.has(member.id) || grants.has(grant) || connections.has(member.binding.connectionId)) ctx.addIssue({ code: "custom", path: ["members", index], message: "Duplicate pool member" });
|
||||
ids.add(member.id); grants.add(grant); connections.add(member.binding.connectionId);
|
||||
if (member.profile.provider === "acpx" && !member.profile.acpxAgent) ctx.addIssue({ code: "custom", path: ["members", index, "profile"], message: "Choose an ACP harness" });
|
||||
}
|
||||
});
|
||||
export type AiConnectionPoolMember = z.infer<typeof aiConnectionPoolMemberSchema>;
|
||||
export type AiConnectionPoolConfig = z.infer<typeof aiConnectionPoolConfigSchema>;
|
||||
export interface AiConnectionPool extends AiConnectionPoolConfig {
|
||||
id: string;
|
||||
companyId: string;
|
||||
pluginKey: string;
|
||||
revision: number;
|
||||
}
|
||||
/** Host-authorized metadata only. No credentials or raw provider responses. */
|
||||
export interface AiConnectionRouterRequest {
|
||||
companyId: string;
|
||||
pool: AiConnectionPool;
|
||||
agentId: string;
|
||||
taskKey: string;
|
||||
lastMemberId: string | null;
|
||||
cursorVersion: number;
|
||||
/** Opaque IDs preserve order without exposing unauthorized account metadata. */
|
||||
memberOrder: string[];
|
||||
pinnedMemberId?: string;
|
||||
candidates: Array<{ member: AiConnectionPoolMember; runtimeConfig: Record<string, unknown>; notes: string[]; usage?: AiConnectionUsage }>;
|
||||
now: string;
|
||||
}
|
||||
export type AiConnectionRouterResult =
|
||||
| { kind: "selected"; memberId: string; skipped?: Record<string, string> }
|
||||
| { kind: "exhausted"; retryAt: string; skipped: Record<string, string> }
|
||||
| { kind: "unavailable"; skipped: Record<string, string> };
|
||||
export interface AiConnectionPoolSaveInput {
|
||||
companyId: string;
|
||||
id?: string;
|
||||
expectedRevision?: number;
|
||||
config: AiConnectionPoolConfig;
|
||||
}
|
||||
export interface AiConnectionRouterSelection {
|
||||
poolId: string;
|
||||
memberId: string;
|
||||
binding: AiConnectionBinding;
|
||||
runtimeConfig: Record<string, unknown>;
|
||||
notes: string[];
|
||||
}
|
||||
|
||||
/** Stable, collision-free catalog identity across plugin reinstallations. */
|
||||
export function aiConnectionRouterSlug(pluginKey: string): string {
|
||||
return `ai-router-${Array.from(pluginKey, character => character.charCodeAt(0).toString(16).padStart(2, "0")).join("")}`;
|
||||
}
|
||||
|
||||
export function aiConnectionRouterAppDefinition(pluginKey: string, descriptor: { name: string; description: string }, availability = { available: true } as { available: boolean; reason?: string }): import("./types/app-definition.js").AppDefinition {
|
||||
return {
|
||||
schemaVersion: 1, slug: aiConnectionRouterSlug(pluginKey), ...descriptor,
|
||||
categories: ["ai"], branding: { logoUrl: "/brands/apps/connection-pool.svg" }, urlPatterns: [],
|
||||
aiConnectionRouter: { pluginKey }, availability,
|
||||
methods: [],
|
||||
};
|
||||
}
|
||||
|
||||
export function aiConnectionRouterPluginKey(connection: { config?: Record<string, unknown> | null }): string | null {
|
||||
const router = connection.config?.aiRouter;
|
||||
return router && typeof router === "object" && "pluginKey" in router && typeof router.pluginKey === "string" ? router.pluginKey : null;
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
import type { AiAuthMethod, AiProvider } from "./ai-connections.js";
|
||||
|
||||
/** A provider observation, never an estimate from Paperclip's run costs. */
|
||||
export interface AiConnectionUsageLimit {
|
||||
id: string;
|
||||
label: string;
|
||||
/** Null means the account-wide allowance; named model/features stay separate. */
|
||||
scope: string | null;
|
||||
windowDurationSeconds: number | null;
|
||||
/** Provider-named reset cadence when no exact duration is available. */
|
||||
resetInterval?: string | null;
|
||||
resetsAt: string | null;
|
||||
usedPercent: number | null;
|
||||
remainingPercent: number | null;
|
||||
used: number | null;
|
||||
limit: number | null;
|
||||
remaining: number | null;
|
||||
unit: string | null;
|
||||
limitReached: boolean | null;
|
||||
/** Only set when the provider reports admission explicitly. */
|
||||
allowed: boolean | null;
|
||||
}
|
||||
|
||||
export interface AiConnectionUsageOverage {
|
||||
enabled: boolean | null;
|
||||
/** Null when enabling overage does not prove a funded usable balance. */
|
||||
available: boolean | null;
|
||||
unlimited: boolean | null;
|
||||
used: number | null;
|
||||
limit: number | null;
|
||||
remaining: number | null;
|
||||
balance: number | null;
|
||||
unit: string | null;
|
||||
}
|
||||
|
||||
export interface AiConnectionUsage {
|
||||
connectionId: string;
|
||||
grantId: string;
|
||||
provider: AiProvider;
|
||||
method: AiAuthMethod;
|
||||
status: "ok" | "unsupported" | "unavailable" | "error";
|
||||
checkedAt: string;
|
||||
source: string | null;
|
||||
planType: string | null;
|
||||
limits: AiConnectionUsageLimit[];
|
||||
overage: AiConnectionUsageOverage | null;
|
||||
errorCode?: "unsupported" | "connection_unavailable" | "authentication_required" | "permission_denied" | "rate_limited" | "provider_unavailable" | "invalid_response";
|
||||
message?: string;
|
||||
}
|
||||
|
||||
export function supportsAiConnectionUsage(provider: AiProvider, method: AiAuthMethod): boolean {
|
||||
return method === "subscription"
|
||||
? provider === "openai" || provider === "anthropic" || provider === "xai"
|
||||
: provider === "openrouter";
|
||||
}
|
||||
@@ -0,0 +1,259 @@
|
||||
import { z } from "zod";
|
||||
import { aiProviderRoutingSchema, aiRoutingHarness, isAiRoutingCompatible, type AiProviderRouting } from "./ai-provider-routing.js";
|
||||
|
||||
/** Runtime authentication is a separate transport, never a tool or channel. */
|
||||
export const connectionPurposeTransportSchema = z.discriminatedUnion(
|
||||
"connectionPurpose",
|
||||
[
|
||||
z.object({
|
||||
connectionPurpose: z.literal("tool"),
|
||||
transport: z.enum(["mcp_remote", "rest_api", "local_stdio"]),
|
||||
}),
|
||||
z.object({
|
||||
connectionPurpose: z.literal("channel"),
|
||||
transport: z.enum(["chat_sdk", "rest_api", "voice"]),
|
||||
config: z.object({ provider: z.string().optional() }).passthrough().optional(),
|
||||
}).refine(
|
||||
(connection) => connection.transport === "voice" ? connection.config?.provider === "speko" : connection.transport === "chat_sdk" ? connection.config?.provider !== "speko" : connection.config?.provider === "agentmail",
|
||||
{ message: "Channel transport must match its provider: voice for Speko, REST for AgentMail, Chat SDK otherwise", path: ["config", "provider"] },
|
||||
),
|
||||
z.object({
|
||||
connectionPurpose: z.literal("ai"),
|
||||
transport: z.literal("runtime_auth"),
|
||||
}),
|
||||
],
|
||||
);
|
||||
export type ConnectionPurposeTransport = z.infer<
|
||||
typeof connectionPurposeTransportSchema
|
||||
>;
|
||||
|
||||
export const AI_PROVIDERS = [
|
||||
"anthropic",
|
||||
"openai",
|
||||
"openrouter",
|
||||
"xai",
|
||||
"google",
|
||||
] as const;
|
||||
export const aiProviderSchema = z.enum(AI_PROVIDERS);
|
||||
export const aiAuthMethodSchema = z.enum(["subscription", "api_key"]);
|
||||
export type AiProvider = z.infer<typeof aiProviderSchema>;
|
||||
export type AiAuthMethod = z.infer<typeof aiAuthMethodSchema>;
|
||||
const requirement = { provider: aiProviderSchema, method: aiAuthMethodSchema };
|
||||
export const aiConnectionBindingSchema = z.discriminatedUnion("mode", [
|
||||
z.object({
|
||||
provider: aiProviderSchema,
|
||||
// Retained on the wire for older servers during rolling upgrades. The
|
||||
// responsible user's provider default determines the actual run method.
|
||||
method: aiAuthMethodSchema,
|
||||
mode: z.literal("responsible_user"),
|
||||
}).strict(),
|
||||
z
|
||||
.object({
|
||||
...requirement,
|
||||
mode: z.literal("shared"),
|
||||
connectionId: z.string().uuid(),
|
||||
grantId: z.string().uuid(),
|
||||
})
|
||||
.strict(),
|
||||
z
|
||||
.object({
|
||||
...requirement,
|
||||
// Explicit personal selection. Human access still applies on every run.
|
||||
mode: z.literal("delegated"),
|
||||
connectionId: z.string().uuid(),
|
||||
grantId: z.string().uuid(),
|
||||
})
|
||||
.strict(),
|
||||
]);
|
||||
export type AiConnectionBinding = z.infer<typeof aiConnectionBindingSchema>;
|
||||
export const aiConnectionMetadataSchema = z.object({ ...requirement, routing: aiProviderRoutingSchema.optional() }).strict();
|
||||
export type AiConnectionMetadata = z.infer<typeof aiConnectionMetadataSchema>;
|
||||
|
||||
/** Existing integrations only. This table describes compatibility, never routing. */
|
||||
export const AI_CONNECTION_CAPABILITIES: Record<
|
||||
AiProvider,
|
||||
{
|
||||
name: string;
|
||||
methods: Partial<
|
||||
Record<AiAuthMethod, { adapters: readonly string[]; envKey: string }>
|
||||
>;
|
||||
}
|
||||
> = {
|
||||
google: { name: "Google", methods: { api_key: { adapters: ["gemini_local"], envKey: "GEMINI_API_KEY" } } },
|
||||
anthropic: {
|
||||
name: "Claude",
|
||||
methods: {
|
||||
subscription: {
|
||||
adapters: ["claude_local"],
|
||||
envKey: "CLAUDE_CODE_OAUTH_TOKEN",
|
||||
},
|
||||
api_key: { adapters: ["claude_local"], envKey: "ANTHROPIC_API_KEY" },
|
||||
},
|
||||
},
|
||||
openai: {
|
||||
name: "OpenAI",
|
||||
methods: {
|
||||
subscription: { adapters: ["codex_local"], envKey: "CODEX_HOME" },
|
||||
api_key: { adapters: ["codex_local"], envKey: "OPENAI_API_KEY" },
|
||||
},
|
||||
},
|
||||
openrouter: {
|
||||
name: "OpenRouter",
|
||||
methods: {
|
||||
api_key: { adapters: ["opencode_local"], envKey: "OPENROUTER_API_KEY" },
|
||||
},
|
||||
},
|
||||
xai: {
|
||||
name: "Grok",
|
||||
methods: {
|
||||
subscription: { adapters: ["grok_local"], envKey: "GROK_HOME" },
|
||||
api_key: { adapters: ["grok_local"], envKey: "XAI_API_KEY" },
|
||||
},
|
||||
},
|
||||
};
|
||||
export function isAiConnectionCompatible(
|
||||
requirement: AiConnectionMetadata | AiConnectionBinding,
|
||||
adapterType: string,
|
||||
model?: unknown,
|
||||
runnerProvider?: unknown,
|
||||
acpxAgent?: unknown,
|
||||
): boolean {
|
||||
adapterType = aiRoutingHarness(adapterType, runnerProvider, acpxAgent);
|
||||
if ("routing" in requirement && requirement.routing) return requirement.method === "api_key" && isAiRoutingCompatible(requirement.routing, adapterType);
|
||||
// A fixed binding contains identity only. The service checks authoritative
|
||||
// connection metadata before resolving credentials or running the harness.
|
||||
if ("mode" in requirement && requirement.mode !== "responsible_user" && requirement.method === "api_key")
|
||||
return ["claude_local", "codex_local", "opencode_local", "hermes_local", "gemini_local", "grok_local"].includes(adapterType);
|
||||
const methods = AI_CONNECTION_CAPABILITIES[requirement.provider].methods;
|
||||
const candidates = "mode" in requirement && requirement.mode === "responsible_user"
|
||||
? Object.values(methods)
|
||||
: requirement.method ? [methods[requirement.method]] : [];
|
||||
return (
|
||||
candidates.some((method) => method?.adapters.includes(adapterType)) &&
|
||||
(requirement.provider !== "openrouter" ||
|
||||
(typeof model === "string" && model.startsWith("openrouter/")))
|
||||
);
|
||||
}
|
||||
export type AiConnectionUnavailableReason =
|
||||
| "responsible_user_missing"
|
||||
| "membership_missing"
|
||||
| "default_missing"
|
||||
| "connection_missing"
|
||||
| "connection_unavailable"
|
||||
| "incompatible"
|
||||
| "access_denied"
|
||||
| "credential_missing";
|
||||
export interface AiConnectionAttribution {
|
||||
/** Billing account at execution time; never resolved again from mutable settings. */
|
||||
subscriptionId?: string;
|
||||
connectionId: string;
|
||||
grantId: string;
|
||||
provider: AiProvider;
|
||||
method: AiAuthMethod;
|
||||
mode: AiConnectionBinding["mode"];
|
||||
responsibleUserId: string | null;
|
||||
}
|
||||
export type AiConnectionResolution =
|
||||
| { ok: true; attribution: AiConnectionAttribution }
|
||||
| { ok: false; reason: AiConnectionUnavailableReason; message: string };
|
||||
|
||||
export interface AiManagedConnectionSummary {
|
||||
id: string;
|
||||
grantId: string;
|
||||
companyId: string;
|
||||
provider: AiProvider;
|
||||
method: AiAuthMethod;
|
||||
name: string;
|
||||
routing?: AiProviderRouting;
|
||||
accountLabel?: string;
|
||||
ownership: "personal" | "shared";
|
||||
ownerUserId?: string;
|
||||
ownerName?: string;
|
||||
isDefault: boolean;
|
||||
status: "connected" | "needs_attention" | "expired" | "revoked";
|
||||
unavailableReason?: string;
|
||||
usageProbeSupported?: boolean;
|
||||
}
|
||||
export interface AiConnectionList {
|
||||
currentUserId: string;
|
||||
pools?: import("./ai-connection-router.js").AiConnectionPool[];
|
||||
canManageConnections: boolean;
|
||||
connections: AiManagedConnectionSummary[];
|
||||
}
|
||||
export const createAiConnectionSchema = z
|
||||
.object({
|
||||
...requirement,
|
||||
name: z.string().trim().min(1).max(160),
|
||||
ownership: z.enum(["personal", "shared"]),
|
||||
routing: aiProviderRoutingSchema.optional(),
|
||||
apiKey: z.string().trim().min(1).max(32768).optional(),
|
||||
loginSessionId: z.string().max(128).optional(),
|
||||
connectionId: z.string().uuid().optional(),
|
||||
agentIds: z.array(z.string().uuid()).max(1000).default([]),
|
||||
allAgents: z.boolean().default(false),
|
||||
})
|
||||
.strict()
|
||||
.superRefine((v, ctx) => {
|
||||
if (!AI_CONNECTION_CAPABILITIES[v.provider].methods[v.method])
|
||||
ctx.addIssue({ code: "custom", message: "Unsupported sign-in method" });
|
||||
if (v.routing && (v.method !== "api_key" || (v.routing.kind === "openrouter" && v.provider !== "openrouter") || (v.routing.kind === "bedrock" && v.provider !== "anthropic")))
|
||||
ctx.addIssue({ code: "custom", message: "Routing requires the matching provider and API authentication." });
|
||||
if (v.routing && ["gateway", "local"].includes(v.routing.kind) && v.provider !== (v.routing.protocol === "messages" ? "anthropic" : "openai"))
|
||||
ctx.addIssue({ code: "custom", message: "The provider must match the endpoint’s API format." });
|
||||
if (v.routing?.auth === "none") {
|
||||
if (v.apiKey || v.loginSessionId) ctx.addIssue({ code: "custom", message: "Provide only the selected authentication method." });
|
||||
return;
|
||||
}
|
||||
if (
|
||||
v.method === "api_key"
|
||||
? !v.apiKey || Boolean(v.loginSessionId)
|
||||
: !v.loginSessionId || Boolean(v.apiKey)
|
||||
) {
|
||||
ctx.addIssue({
|
||||
code: "custom",
|
||||
message:
|
||||
"Provide exactly the credential for the selected sign-in method",
|
||||
});
|
||||
}
|
||||
});
|
||||
export type CreateAiConnection = z.infer<typeof createAiConnectionSchema>;
|
||||
|
||||
export const aiConnectionLoginIntentSchema = z
|
||||
.object({
|
||||
provider: z.enum(["anthropic", "openai", "xai"]),
|
||||
method: z.literal("subscription"),
|
||||
name: z.string().trim().min(1).max(160),
|
||||
ownership: z.enum(["personal", "shared"]),
|
||||
connectionId: z.string().uuid().optional(),
|
||||
agentIds: z.array(z.string().uuid()).max(1000).default([]),
|
||||
allAgents: z.boolean().default(false),
|
||||
})
|
||||
.strict();
|
||||
export type AiConnectionLoginIntent = z.infer<
|
||||
typeof aiConnectionLoginIntentSchema
|
||||
>;
|
||||
|
||||
export const localAiConnectionSchema = aiConnectionLoginIntentSchema.extend({
|
||||
localSessionId: z.string().uuid().optional(),
|
||||
});
|
||||
export const localAiLoginStartSchema = aiConnectionLoginIntentSchema.extend({ restart: z.boolean().optional() });
|
||||
export interface LocalAiLoginStatus {
|
||||
status: "ready" | "sign_in_required" | "expired";
|
||||
authorizationUrl?: string;
|
||||
code?: string;
|
||||
error?: string;
|
||||
}
|
||||
export interface LocalAiLoginAttempt {
|
||||
sessionId: string;
|
||||
/** Grok still uses terminal sign-in until it has an in-app login runner. */
|
||||
command?: string;
|
||||
expiresAt: string;
|
||||
}
|
||||
|
||||
/** Preview-era copies of rotating local credentials must be reconnected. */
|
||||
export function aiSubscriptionNeedsIsolatedLogin(config: Record<string, unknown> | undefined): boolean {
|
||||
const metadata = aiConnectionMetadataSchema.safeParse(config?.ai);
|
||||
return metadata.success && metadata.data.method === "subscription" &&
|
||||
(metadata.data.provider === "openai" || metadata.data.provider === "xai") &&
|
||||
config?.aiIsolatedSubscription !== true;
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
import { z } from "zod";
|
||||
|
||||
/** Value-free routing stored on the connection, never supplied by an agent binding. */
|
||||
export const aiProviderRoutingSchema = z
|
||||
.object({
|
||||
kind: z.enum(["openrouter", "bedrock", "gateway", "local"]),
|
||||
protocol: z.enum(["responses", "messages", "chat", "bedrock"]),
|
||||
baseUrl: z.string().trim().max(2048).optional(),
|
||||
auth: z
|
||||
.enum(["bearer", "api_key", "none"])
|
||||
.default("bearer"),
|
||||
region: z
|
||||
.string()
|
||||
.regex(/^[a-z]{2}(?:-[a-z]+)+-\d$/)
|
||||
.optional(),
|
||||
models: z
|
||||
.array(
|
||||
z
|
||||
.object({
|
||||
id: z.string().trim().min(1).max(256),
|
||||
label: z.string().trim().max(160).optional(),
|
||||
})
|
||||
.strict(),
|
||||
)
|
||||
.max(200)
|
||||
.default([]),
|
||||
})
|
||||
.strict()
|
||||
.superRefine((route, ctx) => {
|
||||
const invalid = (message: string) =>
|
||||
ctx.addIssue({ code: "custom", message });
|
||||
if (route.kind === "bedrock") {
|
||||
if (
|
||||
route.protocol !== "bedrock" ||
|
||||
!route.region ||
|
||||
route.auth !== "bearer" ||
|
||||
route.baseUrl
|
||||
)
|
||||
invalid(
|
||||
"Bedrock requires a region and a Bedrock API key.",
|
||||
);
|
||||
} else if (
|
||||
route.protocol === "bedrock" ||
|
||||
route.region
|
||||
)
|
||||
invalid("AWS settings require Bedrock.");
|
||||
if (
|
||||
route.kind === "openrouter" &&
|
||||
(route.baseUrl || route.auth !== "bearer")
|
||||
)
|
||||
invalid("OpenRouter uses its official endpoint and an API key.");
|
||||
if (route.auth === "api_key" && route.protocol !== "messages")
|
||||
invalid("API-key header authentication requires Anthropic Messages.");
|
||||
if (route.kind === "gateway" || route.kind === "local") {
|
||||
try {
|
||||
const url = new URL(route.baseUrl ?? "");
|
||||
const loopback = ["localhost", "127.0.0.1", "[::1]"].includes(
|
||||
url.hostname,
|
||||
);
|
||||
if (
|
||||
url.username ||
|
||||
url.password ||
|
||||
url.search ||
|
||||
url.hash ||
|
||||
(url.protocol !== "https:" && !(url.protocol === "http:" && loopback))
|
||||
)
|
||||
throw new Error();
|
||||
if (route.kind === "local" && !loopback)
|
||||
invalid("A local endpoint must use localhost or a loopback address.");
|
||||
} catch {
|
||||
invalid(
|
||||
"Enter an HTTPS base URL without credentials, query parameters, or a fragment. Local endpoints may use HTTP.",
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
export type AiProviderRouting = z.infer<typeof aiProviderRoutingSchema>;
|
||||
export function aiRoutingHarness(
|
||||
adapter: string,
|
||||
provider?: unknown,
|
||||
acpxAgent?: unknown,
|
||||
): string {
|
||||
if (adapter !== "paperclip_runner") return adapter;
|
||||
const runner =
|
||||
provider === "acpx" && (acpxAgent === "claude" || acpxAgent === "grok")
|
||||
? acpxAgent
|
||||
: provider;
|
||||
return (
|
||||
(
|
||||
{
|
||||
claude: "claude_local",
|
||||
codex: "codex_local",
|
||||
opencode: "opencode_local",
|
||||
grok: "grok_local",
|
||||
} as Record<string, string>
|
||||
)[String(runner)] ?? "unsupported"
|
||||
);
|
||||
}
|
||||
export function isAiRoutingCompatible(
|
||||
route: AiProviderRouting,
|
||||
harness: string,
|
||||
): boolean {
|
||||
if (route.kind === "bedrock") return harness === "claude_local";
|
||||
if (route.kind === "openrouter")
|
||||
return [
|
||||
"claude_local",
|
||||
"codex_local",
|
||||
"opencode_local",
|
||||
"hermes_local",
|
||||
].includes(harness);
|
||||
if (route.protocol === "responses") return harness === "codex_local";
|
||||
if (route.protocol === "messages") return harness === "claude_local";
|
||||
return ["opencode_local", "hermes_local"].includes(harness);
|
||||
}
|
||||
export function aiRoutingBaseUrl(
|
||||
route: AiProviderRouting,
|
||||
harness: string,
|
||||
): string {
|
||||
return route.kind === "openrouter"
|
||||
? `https://openrouter.ai/api${harness === "claude_local" ? "" : "/v1"}`
|
||||
: (route.baseUrl ?? "").replace(/\/+$/, "");
|
||||
}
|
||||
|
||||
/** OpenCode names models with a provider prefix; other harnesses use the raw ID. */
|
||||
export function aiRoutingModel(
|
||||
route: AiProviderRouting,
|
||||
harness: string,
|
||||
model: string,
|
||||
): string {
|
||||
if (!model || harness !== "opencode_local") return model;
|
||||
const prefix = route.kind === "openrouter" ? "openrouter" : "paperclip";
|
||||
return model.startsWith(`${prefix}/`) ? model : `${prefix}/${model}`;
|
||||
}
|
||||
|
||||
/** Catalog identity is derived from routing, including accounts saved before these rows existed. */
|
||||
export function aiConnectionCatalogSlug(provider: string, routing?: Pick<AiProviderRouting, "kind" | "protocol">): string {
|
||||
if (routing?.kind === "gateway") {
|
||||
return routing.protocol === "messages" ? "messages-api"
|
||||
: routing.protocol === "chat" ? "chat-completions-api" : "responses-api";
|
||||
}
|
||||
return routing?.kind ?? provider;
|
||||
}
|
||||
|
||||
/** Direct catalog setup presets; legacy gateway links still open their existing setup. */
|
||||
export function aiProviderSetupPreset(source: string | null): {
|
||||
provider: "openrouter" | "bedrock" | "gateway" | "local" | "google";
|
||||
protocol?: AiProviderRouting["protocol"];
|
||||
label?: string;
|
||||
} | undefined {
|
||||
switch (source) {
|
||||
case "responses-api": return { provider: "gateway", protocol: "responses", label: "Responses API" };
|
||||
case "messages-api": return { provider: "gateway", protocol: "messages", label: "Messages API" };
|
||||
case "chat-completions-api": return { provider: "gateway", protocol: "chat", label: "Chat Completions API" };
|
||||
case "google-ai": return { provider: "google" };
|
||||
case "google": case "openrouter": case "bedrock": case "gateway": case "local": return { provider: source };
|
||||
default: return undefined;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { announcementManifestSchema, announcementSchema, isAnnouncementEligible } from "./announcements.js";
|
||||
|
||||
const announcement = { id: "new-projects", eyebrow: "New", title: "Projects", description: "Organize your work.", primaryAction: { kind: "route", label: "Open", path: "/projects" } };
|
||||
describe("announcement contract", () => {
|
||||
it("supports an empty feed and a plain-text card", () => {
|
||||
expect(announcementManifestSchema.parse({ schemaVersion: 1, announcement: null }).announcement).toBeNull();
|
||||
expect(announcementSchema.parse(announcement).id).toBe("new-projects");
|
||||
expect(announcementSchema.parse({ ...announcement, title: "<script>alert(1)</script>" }).title).toContain("<script>");
|
||||
});
|
||||
it.each(["/api/companies", "//evil.test", "/projects/../auth", "/company/settings?danger=yes", "/unknown", "javascript:alert(1)"])("rejects unsupported route %s", (path) => {
|
||||
expect(announcementSchema.safeParse({ ...announcement, primaryAction: { kind: "route", label: "Open", path } }).success).toBe(false);
|
||||
});
|
||||
it.each(["not a URL", "http://example.com", "javascript:alert(1)", "https://user:password@example.com"])("rejects unsafe external URL %s", (url) => {
|
||||
expect(announcementSchema.safeParse({ ...announcement, primaryAction: { kind: "external", label: "Open", url } }).success).toBe(false);
|
||||
});
|
||||
it.each(["../secret.png", "https://example.com/a.png", "/assets/a.png", `assets/${"0".repeat(64)}.svg`])("rejects unsafe image path %s", (path) => {
|
||||
expect(announcementSchema.safeParse({ ...announcement, image: { path, alt: "" } }).success).toBe(false);
|
||||
});
|
||||
it("requires a content-addressed HTML animation and a static fallback", () => {
|
||||
const image = { path: `assets/${"0".repeat(64)}.png`, alt: "Poster" };
|
||||
const animation = { path: `assets/${"1".repeat(64)}.html`, alt: "Animated team" };
|
||||
expect(announcementSchema.safeParse({ ...announcement, image, animation }).success).toBe(true);
|
||||
expect(announcementSchema.safeParse({ ...announcement, animation }).success).toBe(false);
|
||||
for (const path of ["https://evil.test/a.html", "../a.html", "assets/a.html", `assets/${"1".repeat(64)}.js`]) {
|
||||
expect(announcementSchema.safeParse({ ...announcement, image, animation: { ...animation, path } }).success).toBe(false);
|
||||
}
|
||||
});
|
||||
it("rejects unknown schema versions and oversized copy", () => {
|
||||
expect(announcementManifestSchema.safeParse({ schemaVersion: 2, announcement }).success).toBe(false);
|
||||
expect(announcementSchema.safeParse({ ...announcement, description: "a".repeat(401) }).success).toBe(false);
|
||||
});
|
||||
it("rejects unknown fields throughout the remotely authored manifest", () => {
|
||||
const image = { path: `assets/${"0".repeat(64)}.png`, alt: "Preview" };
|
||||
const external = { kind: "external", label: "Learn more", url: "https://paperclip.ing" };
|
||||
for (const value of [
|
||||
{ schemaVersion: 1, announcement, extra: true },
|
||||
{ schemaVersion: 1, announcement: { ...announcement, secondaryLinks: external } },
|
||||
{ schemaVersion: 1, announcement: { ...announcement, image: { ...image, title: "Typo" } } },
|
||||
{ schemaVersion: 1, announcement: { ...announcement, primaryAction: { ...announcement.primaryAction, extra: true } } },
|
||||
{ schemaVersion: 1, announcement: { ...announcement, secondaryLink: { ...external, extra: true } } },
|
||||
]) expect(announcementManifestSchema.safeParse(value).success).toBe(false);
|
||||
});
|
||||
it("checks expiration and minimum versions numerically, including prereleases", () => {
|
||||
const item = announcementSchema.parse({ ...announcement, expiresAt: "2027-01-01T00:00:00Z", minimumPaperclipVersion: "2026.913.0" });
|
||||
for (const version of ["2026.912.0", "2026.913.0-canary.1", "unknown"]) expect(isAnnouncementEligible(item, version, 0)).toBe(false);
|
||||
for (const version of ["2026.913.0", "2026.913.0+1.git.abc", "2026.1001.0"]) expect(isAnnouncementEligible(item, version, 0)).toBe(true);
|
||||
expect(isAnnouncementEligible(item, "2026.913.0", Date.parse(item.expiresAt!))).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,74 @@
|
||||
import { z } from "zod";
|
||||
|
||||
export const DEFAULT_ANNOUNCEMENT_FEED_URL = "https://pages.paperclip.ing/announcements/v1/current.json";
|
||||
export const ANNOUNCEMENT_MANIFEST_MAX_BYTES = 64 * 1024;
|
||||
export const ANNOUNCEMENT_IMAGE_MAX_BYTES = 2 * 1024 * 1024;
|
||||
export const ANNOUNCEMENT_ANIMATION_MAX_BYTES = 128 * 1024;
|
||||
// Used both in the isolated srcdoc and on the asset endpoint. The HTTP
|
||||
// response additionally applies CSP sandbox (not supported in a meta tag).
|
||||
export const ANNOUNCEMENT_ANIMATION_CSP = "default-src 'none'; style-src 'unsafe-inline'; form-action 'none'; base-uri 'none'";
|
||||
|
||||
// Only stable, company-relative board pages. Never API paths, entity IDs, or
|
||||
// routes that depend on an experimental feature being enabled.
|
||||
export const ANNOUNCEMENT_APP_ROUTES = [
|
||||
"/dashboard", "/issues", "/projects", "/agents", "/skills", "/apps",
|
||||
"/routines", "/artifacts", "/company/settings",
|
||||
] as const;
|
||||
|
||||
export const announcementIdSchema = z.string().regex(/^[a-z0-9][a-z0-9-]{0,95}$/);
|
||||
const label = z.string().trim().min(1).max(48);
|
||||
const httpsUrl = z.string().max(2048).url().refine((value) => {
|
||||
try {
|
||||
const url = new URL(value);
|
||||
return url.protocol === "https:" && !url.username && !url.password;
|
||||
} catch { return false; }
|
||||
}, "Use an HTTPS URL without credentials");
|
||||
|
||||
export const announcementActionSchema = z.discriminatedUnion("kind", [
|
||||
z.object({ kind: z.literal("external"), label, url: httpsUrl }).strict(),
|
||||
z.object({ kind: z.literal("route"), label, path: z.enum(ANNOUNCEMENT_APP_ROUTES) }).strict(),
|
||||
]);
|
||||
|
||||
export const announcementSchema = z.object({
|
||||
id: announcementIdSchema,
|
||||
eyebrow: z.string().trim().min(1).max(48),
|
||||
title: z.string().trim().min(1).max(100),
|
||||
description: z.string().trim().min(1).max(400),
|
||||
image: z.object({
|
||||
// Immutable, content-addressed raster assets beneath the feed directory.
|
||||
path: z.string().regex(/^assets\/[a-f0-9]{64}\.(png|jpg|webp)$/),
|
||||
alt: z.string().max(200),
|
||||
}).strict().optional(),
|
||||
animation: z.object({
|
||||
path: z.string().regex(/^assets\/[a-f0-9]{64}\.html$/),
|
||||
alt: z.string().trim().min(1).max(200),
|
||||
}).strict().optional(),
|
||||
secondaryLink: announcementActionSchema.optional(),
|
||||
primaryAction: announcementActionSchema,
|
||||
expiresAt: z.string().datetime({ offset: true }).optional(),
|
||||
minimumPaperclipVersion: z.string().regex(/^\d+\.\d+\.\d+$/).optional(),
|
||||
}).strict().refine((value) => !value.animation || Boolean(value.image), {
|
||||
message: "An animation requires a static fallback image", path: ["image"],
|
||||
});
|
||||
|
||||
export const announcementManifestSchema = z.object({
|
||||
schemaVersion: z.literal(1),
|
||||
announcement: announcementSchema.nullable(),
|
||||
}).strict();
|
||||
export const dismissAnnouncementSchema = z.object({ companyId: z.string().uuid() }).strict();
|
||||
export type AnnouncementAction = z.infer<typeof announcementActionSchema>;
|
||||
export type Announcement = z.infer<typeof announcementSchema>;
|
||||
export type AnnouncementManifest = z.infer<typeof announcementManifestSchema>;
|
||||
|
||||
export function isAnnouncementEligible(announcement: Announcement, version: string, now = Date.now()): boolean {
|
||||
if (announcement.expiresAt && Date.parse(announcement.expiresAt) <= now) return false;
|
||||
if (!announcement.minimumPaperclipVersion) return true;
|
||||
const installed = /^(\d+)\.(\d+)\.(\d+)(-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/.exec(version);
|
||||
if (!installed) return false;
|
||||
const minimum = announcement.minimumPaperclipVersion.split(".").map(Number);
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const difference = Number(installed[i + 1]) - minimum[i]!;
|
||||
if (difference !== 0) return difference > 0;
|
||||
}
|
||||
return !installed[4]; // A prerelease of the minimum is not that release yet.
|
||||
}
|
||||
@@ -3,9 +3,14 @@ export const API_PREFIX = "/api";
|
||||
export const API = {
|
||||
health: `${API_PREFIX}/health`,
|
||||
companies: `${API_PREFIX}/companies`,
|
||||
companyFolders: `${API_PREFIX}/companies/:companyId/folders`,
|
||||
companyFolder: `${API_PREFIX}/companies/:companyId/folders/:folderId`,
|
||||
companyFolderMove: `${API_PREFIX}/companies/:companyId/folders/:folderId/move`,
|
||||
companyFolderItemMove: `${API_PREFIX}/companies/:companyId/folders/items/move`,
|
||||
agents: `${API_PREFIX}/agents`,
|
||||
projects: `${API_PREFIX}/projects`,
|
||||
environments: `${API_PREFIX}/environments`,
|
||||
environmentDeleteBlastRadius: `${API_PREFIX}/environments/:id/delete-blast-radius`,
|
||||
environmentCustomImageTemplate: `${API_PREFIX}/environments/:environmentId/custom-image-template`,
|
||||
environmentCustomImageTemplateDisable: `${API_PREFIX}/environments/:environmentId/custom-image-template`,
|
||||
environmentCustomImageTemplateRollback: `${API_PREFIX}/environments/:environmentId/custom-image-template/rollback`,
|
||||
@@ -16,12 +21,34 @@ export const API = {
|
||||
environmentCustomImageSetupSessionFinish: `${API_PREFIX}/environment-custom-image-setup-sessions/:sessionId/finish`,
|
||||
environmentCustomImageSetupSessionCancel: `${API_PREFIX}/environment-custom-image-setup-sessions/:sessionId/cancel`,
|
||||
issues: `${API_PREFIX}/issues`,
|
||||
stalledReviewDecision: `${API_PREFIX}/issues/:issueId/stalled-review-decision`,
|
||||
issueWatchdog: `${API_PREFIX}/issues/:issueId/watchdog`,
|
||||
issueTreeControl: `${API_PREFIX}/issues/:issueId/tree-control`,
|
||||
issueTreeHolds: `${API_PREFIX}/issues/:issueId/tree-holds`,
|
||||
summarySlot: `${API_PREFIX}/companies/:companyId/summary-slots/:scopeKind/:slotKey`,
|
||||
summarySlotRevisions: `${API_PREFIX}/companies/:companyId/summary-slots/:scopeKind/:slotKey/revisions`,
|
||||
summarySlotGenerate: `${API_PREFIX}/companies/:companyId/summary-slots/:scopeKind/:slotKey/generate`,
|
||||
goals: `${API_PREFIX}/goals`,
|
||||
approvals: `${API_PREFIX}/approvals`,
|
||||
secrets: `${API_PREFIX}/secrets`,
|
||||
tools: `${API_PREFIX}/companies/:companyId/tools`,
|
||||
toolExamples: `${API_PREFIX}/companies/:companyId/tools/examples`,
|
||||
toolApplications: `${API_PREFIX}/companies/:companyId/tools/applications`,
|
||||
toolConnections: `${API_PREFIX}/companies/:companyId/tools/connections`,
|
||||
toolCatalog: `${API_PREFIX}/companies/:companyId/tools/catalog`,
|
||||
toolProfiles: `${API_PREFIX}/companies/:companyId/tools/profiles`,
|
||||
toolPolicies: `${API_PREFIX}/companies/:companyId/tools/policies`,
|
||||
toolAudit: `${API_PREFIX}/companies/:companyId/tools/audit`,
|
||||
toolRuntimeSlots: `${API_PREFIX}/companies/:companyId/tools/runtime-slots`,
|
||||
toolRuntimeSlotStop: `${API_PREFIX}/companies/:companyId/tools/runtime-slots/:id/stop`,
|
||||
toolRuntimeSlotRestart: `${API_PREFIX}/companies/:companyId/tools/runtime-slots/:id/restart`,
|
||||
toolRuntimeHealth: `${API_PREFIX}/companies/:companyId/tools/runtime-health`,
|
||||
toolGateway: `${API_PREFIX}/tool-gateway`,
|
||||
smokeLab: `${API_PREFIX}/companies/:companyId/smoke-lab`,
|
||||
smokeLabServices: `${API_PREFIX}/companies/:companyId/smoke-lab/services`,
|
||||
smokeLabInstallFixtures: `${API_PREFIX}/companies/:companyId/smoke-lab/install-fixtures`,
|
||||
smokeLabRuns: `${API_PREFIX}/companies/:companyId/smoke-lab/runs`,
|
||||
smokeLabRunSteps: `${API_PREFIX}/companies/:companyId/smoke-lab/runs/:runId/steps`,
|
||||
userSecretDefinitions: `${API_PREFIX}/companies/:companyId/user-secret-definitions`,
|
||||
userSecretDefinition: `${API_PREFIX}/companies/:companyId/user-secret-definitions/:definitionId`,
|
||||
userSecretDefinitionCoverage: `${API_PREFIX}/companies/:companyId/user-secret-definitions/:definitionId/coverage`,
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
getAppDefinitionForUrl,
|
||||
CONNECTABLE_APP_DEFINITIONS,
|
||||
} from "./app-definitions.js";
|
||||
|
||||
describe("tool app gallery URL matching", () => {
|
||||
it("matches pasted links against gallery URL patterns", () => {
|
||||
expect(getAppDefinitionForUrl("https://mcp.zapier.com/api/mcp")?.slug).toBe("zapier");
|
||||
expect(getAppDefinitionForUrl("https://api.githubcopilot.com/mcp/")?.slug).toBe("github");
|
||||
expect(getAppDefinitionForUrl("https://github.com/paperclipai/paperclip/pull/1")?.slug).toBe("github");
|
||||
expect(getAppDefinitionForUrl("https://docs.google.com/spreadsheets/d/sheet_123/edit")?.slug).toBe("google-sheets");
|
||||
expect(getAppDefinitionForUrl("https://gmailmcp.googleapis.com/mcp/v1")?.slug).toBe("gmail");
|
||||
expect(getAppDefinitionForUrl("https://mcp.neon.tech/mcp")?.slug).toBe("neon");
|
||||
expect(getAppDefinitionForUrl("https://app.withgauge.com/mcp")?.slug).toBe("gauge");
|
||||
expect(getAppDefinitionForUrl("https://www.superagent.sh/mcp")?.slug).toBe("superagent");
|
||||
});
|
||||
|
||||
it("returns null for invalid or unknown links", () => {
|
||||
expect(getAppDefinitionForUrl("not a url")).toBeNull();
|
||||
expect(getAppDefinitionForUrl("https://example.com/mcp")).toBeNull();
|
||||
expect(getAppDefinitionForUrl("https://docs.googleapis.com/drive/v3/files")).toBeNull();
|
||||
});
|
||||
|
||||
it("lists each reviewed Google Workspace MCP endpoint independently", () => {
|
||||
expect(CONNECTABLE_APP_DEFINITIONS.map((app) => app.slug)).toEqual(expect.arrayContaining([
|
||||
"gmail",
|
||||
"google-drive",
|
||||
"google-docs",
|
||||
"google-sheets",
|
||||
"google-slides",
|
||||
"google-calendar",
|
||||
"google-chat",
|
||||
"google-people",
|
||||
"google-workspace-search",
|
||||
]));
|
||||
expect(getAppDefinitionForUrl("https://drivemcp.googleapis.com/mcp/v1")?.slug).toBe("google-drive");
|
||||
});
|
||||
|
||||
it("offers only direct MCP for Composio", () => {
|
||||
const composio = CONNECTABLE_APP_DEFINITIONS.find((app) => app.slug === "composio");
|
||||
expect(composio?.methods).toEqual([
|
||||
expect.objectContaining({ key: "mcp", transport: "mcp_remote", ownershipModes: ["dcr", "customer"] }),
|
||||
]);
|
||||
});
|
||||
|
||||
it("keeps tool gallery entries reachable through at least one pattern", () => {
|
||||
for (const app of CONNECTABLE_APP_DEFINITIONS.filter((app) => app.methods.some((method) => method.purpose !== "channel" && method.purpose !== "ai"))) {
|
||||
const example = app.urlPatterns[0]?.replace("*", "example");
|
||||
expect(example, `${app.slug} has a pattern`).toBeTruthy();
|
||||
expect(getAppDefinitionForUrl(example!)?.slug).toBe(app.slug);
|
||||
}
|
||||
});
|
||||
|
||||
it("offers region and custom model connections without claiming arbitrary URLs", () => {
|
||||
for (const slug of ["bedrock", "responses-api", "messages-api", "chat-completions-api", "local"]) {
|
||||
const app = CONNECTABLE_APP_DEFINITIONS.find(app => app.slug === slug);
|
||||
expect(app?.methods.every(method => method.purpose === "ai")).toBe(true);
|
||||
expect(app?.urlPatterns).toEqual([]);
|
||||
}
|
||||
expect(getAppDefinitionForUrl("https://customer-gateway.example/v1")).toBeNull();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,104 @@
|
||||
import a0 from "./app-definitions/agentmail.json" with { type: "json" };
|
||||
import a1 from "./app-definitions/zapier.json" with { type: "json" };
|
||||
import a2 from "./app-definitions/arcade.json" with { type: "json" };
|
||||
import a3 from "./app-definitions/executor.json" with { type: "json" };
|
||||
import a4 from "./app-definitions/railway.json" with { type: "json" };
|
||||
import a5 from "./app-definitions/github.json" with { type: "json" };
|
||||
import a6 from "./app-definitions/github-code-review-bot.json" with { type: "json" };
|
||||
import a7 from "./app-definitions/slack.json" with { type: "json" };
|
||||
import a8 from "./app-definitions/microsoft-teams.json" with { type: "json" };
|
||||
import a9 from "./app-definitions/imessage-photon.json" with { type: "json" };
|
||||
import a10 from "./app-definitions/telegram.json" with { type: "json" };
|
||||
import a11 from "./app-definitions/discord.json" with { type: "json" };
|
||||
import a12 from "./app-definitions/notion.json" with { type: "json" };
|
||||
import a13 from "./app-definitions/posthog.json" with { type: "json" };
|
||||
import a14 from "./app-definitions/linear.json" with { type: "json" };
|
||||
import a15 from "./app-definitions/context7.json" with { type: "json" };
|
||||
import a16 from "./app-definitions/shopify.json" with { type: "json" };
|
||||
import a17 from "./app-definitions/composio.json" with { type: "json" };
|
||||
import a18 from "./app-definitions/oauth-generic.json" with { type: "json" };
|
||||
import a19 from "./app-definitions/api-key-generic.json" with { type: "json" };
|
||||
import a20 from "./app-definitions/sentry.json" with { type: "json" };
|
||||
import a21 from "./app-definitions/vercel.json" with { type: "json" };
|
||||
import a22 from "./app-definitions/enterpret.json" with { type: "json" };
|
||||
import a23 from "./app-definitions/anthropic.json" with { type: "json" };
|
||||
import a24 from "./app-definitions/browser-use-cloud.json" with { type: "json" };
|
||||
import a25 from "./app-definitions/cognee.json" with { type: "json" };
|
||||
import a26 from "./app-definitions/jira.json" with { type: "json" };
|
||||
import a27 from "./app-definitions/airtable.json" with { type: "json" };
|
||||
import a28 from "./app-definitions/beehiiv.json" with { type: "json" };
|
||||
import a29 from "./app-definitions/bitly.json" with { type: "json" };
|
||||
import a30 from "./app-definitions/candid.json" with { type: "json" };
|
||||
import a31 from "./app-definitions/cloudflare.json" with { type: "json" };
|
||||
import a32 from "./app-definitions/cloudinary.json" with { type: "json" };
|
||||
import a33 from "./app-definitions/coda.json" with { type: "json" };
|
||||
import a34 from "./app-definitions/hugging-face.json" with { type: "json" };
|
||||
import a35 from "./app-definitions/kernel.json" with { type: "json" };
|
||||
import a36 from "./app-definitions/local-falcon.json" with { type: "json" };
|
||||
import a37 from "./app-definitions/make.json" with { type: "json" };
|
||||
import a38 from "./app-definitions/manufact.json" with { type: "json" };
|
||||
import a39 from "./app-definitions/miro.json" with { type: "json" };
|
||||
import a40 from "./app-definitions/netlify.json" with { type: "json" };
|
||||
import a41 from "./app-definitions/oreilly.json" with { type: "json" };
|
||||
import a42 from "./app-definitions/planetscale.json" with { type: "json" };
|
||||
import a43 from "./app-definitions/resend.json" with { type: "json" };
|
||||
import a44 from "./app-definitions/ticktick.json" with { type: "json" };
|
||||
import a45 from "./app-definitions/todoist.json" with { type: "json" };
|
||||
import a46 from "./app-definitions/webflow.json" with { type: "json" };
|
||||
import a47 from "./app-definitions/wix.json" with { type: "json" };
|
||||
import a48 from "./app-definitions/brex.json" with { type: "json" };
|
||||
import a49 from "./app-definitions/clickhouse.json" with { type: "json" };
|
||||
import a50 from "./app-definitions/egnyte.json" with { type: "json" };
|
||||
import a51 from "./app-definitions/embat.json" with { type: "json" };
|
||||
import a52 from "./app-definitions/mixpanel.json" with { type: "json" };
|
||||
import a53 from "./app-definitions/postman.json" with { type: "json" };
|
||||
import a54 from "./app-definitions/razorpay.json" with { type: "json" };
|
||||
import a55 from "./app-definitions/sanity.json" with { type: "json" };
|
||||
import a56 from "./app-definitions/stripe.json" with { type: "json" };
|
||||
import a57 from "./app-definitions/supabase.json" with { type: "json" };
|
||||
import a58 from "./app-definitions/ticket-tailor.json" with { type: "json" };
|
||||
import a59 from "./app-definitions/asana.json" with { type: "json" };
|
||||
import a60 from "./app-definitions/box.json" with { type: "json" };
|
||||
import a61 from "./app-definitions/mem0.json" with { type: "json" };
|
||||
import a62 from "./app-definitions/pagerduty.json" with { type: "json" };
|
||||
import a63 from "./app-definitions/similarweb.json" with { type: "json" };
|
||||
import a64 from "./app-definitions/xero.json" with { type: "json" };
|
||||
import a65 from "./app-definitions/youcom.json" with { type: "json" };
|
||||
import a66 from "./app-definitions/fireflies.json" with { type: "json" };
|
||||
import a67 from "./app-definitions/zep.json" with { type: "json" };
|
||||
import a68 from "./app-definitions/supermemory.json" with { type: "json" };
|
||||
import a69 from "./app-definitions/honcho.json" with { type: "json" };
|
||||
import a70 from "./app-definitions/neon.json" with { type: "json" };
|
||||
import a71 from "./app-definitions/superagent.json" with { type: "json" };
|
||||
import a72 from "./app-definitions/telem.json" with { type: "json" };
|
||||
import a73 from "./app-definitions/gauge.json" with { type: "json" };
|
||||
import a74 from "./app-definitions/tavily.json" with { type: "json" };
|
||||
import a75 from "./app-definitions/calendly.json" with { type: "json" };
|
||||
import a76 from "./app-definitions/exa.json" with { type: "json" };
|
||||
import a77 from "./app-definitions/firecrawl.json" with { type: "json" };
|
||||
import a78 from "./app-definitions/parallel-search.json" with { type: "json" };
|
||||
import a79 from "./app-definitions/windsor-ai.json" with { type: "json" };
|
||||
import a80 from "./app-definitions/gsc-wizard.json" with { type: "json" };
|
||||
import a81 from "./app-definitions/clickup.json" with { type: "json" };
|
||||
import a82 from "./app-definitions/klaviyo.json" with { type: "json" };
|
||||
import a83 from "./app-definitions/gmail.json" with { type: "json" };
|
||||
import a84 from "./app-definitions/google-drive.json" with { type: "json" };
|
||||
import a85 from "./app-definitions/google-docs.json" with { type: "json" };
|
||||
import a86 from "./app-definitions/google-sheets.json" with { type: "json" };
|
||||
import a87 from "./app-definitions/google-slides.json" with { type: "json" };
|
||||
import a88 from "./app-definitions/google-calendar.json" with { type: "json" };
|
||||
import a89 from "./app-definitions/google-chat.json" with { type: "json" };
|
||||
import a90 from "./app-definitions/google-people.json" with { type: "json" };
|
||||
import a91 from "./app-definitions/google-workspace-search.json" with { type: "json" };
|
||||
import a92 from "./app-definitions/openai.json" with { type: "json" };
|
||||
import a93 from "./app-definitions/openrouter.json" with { type: "json" };
|
||||
import a94 from "./app-definitions/xai.json" with { type: "json" };
|
||||
import a95 from "./app-definitions/google.json" with { type: "json" };
|
||||
import a96 from "./app-definitions/bedrock.json" with { type: "json" };
|
||||
import a97 from "./app-definitions/responses-api.json" with { type: "json" };
|
||||
import a98 from "./app-definitions/messages-api.json" with { type: "json" };
|
||||
import a99 from "./app-definitions/chat-completions-api.json" with { type: "json" };
|
||||
import a100 from "./app-definitions/local.json" with { type: "json" };
|
||||
import a101 from "./app-definitions/monday.json" with { type: "json" };
|
||||
import type { AppDefinition } from "./types/app-definition.js";
|
||||
export const APP_DEFINITIONS=[a0,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20,a21,a22,a23,a24,a25,a26,a27,a28,a29,a30,a31,a32,a33,a34,a35,a36,a37,a38,a39,a40,a41,a42,a43,a44,a45,a46,a47,a48,a49,a50,a51,a52,a53,a54,a55,a56,a57,a58,a59,a60,a61,a62,a63,a64,a65,a66,a67,a68,a69,a70,a71,a72,a73,a74,a75,a76,a77,a78,a79,a80,a81,a82,a83,a84,a85,a86,a87,a88,a89,a90,a91,a92,a93,a94,a95,a96,a97,a98,a99,a100,a101] as AppDefinition[];
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,279 @@
|
||||
import { APP_DEFINITIONS } from "./app-definitions.generated.js";
|
||||
import { SELF_SERVE_MCP_CANDIDATES } from "./self-serve-mcp-research.js";
|
||||
import type { AppDefinition, ConnectionMethodDef, FieldDef } from "./types/app-definition.js";
|
||||
import type { ToolConnectionOwnership } from "./types/tool-access.js";
|
||||
|
||||
export const CONNECTABLE_APP_SLUGS = new Set([
|
||||
"anthropic", "openai", "openrouter", "xai", "google", "bedrock",
|
||||
"responses-api", "messages-api", "chat-completions-api", "local",
|
||||
"agentmail",
|
||||
"browser-use-cloud",
|
||||
"cognee",
|
||||
...SELF_SERVE_MCP_CANDIDATES.map((entry) => entry.slug),
|
||||
"monday",
|
||||
"enterpret",
|
||||
"zapier",
|
||||
"arcade",
|
||||
"executor",
|
||||
"slack",
|
||||
"notion",
|
||||
"railway",
|
||||
"posthog",
|
||||
"linear",
|
||||
"google-sheets",
|
||||
"context7",
|
||||
"shopify",
|
||||
"composio",
|
||||
"gmail",
|
||||
"google-drive",
|
||||
"google-docs",
|
||||
"google-slides",
|
||||
"google-calendar",
|
||||
"google-chat",
|
||||
"google-people",
|
||||
"google-workspace-search",
|
||||
"github",
|
||||
"github-code-review-bot",
|
||||
"discord",
|
||||
"microsoft-teams",
|
||||
"telegram",
|
||||
"imessage-photon",
|
||||
]);
|
||||
|
||||
export const CONNECTABLE_APP_DEFINITIONS = APP_DEFINITIONS.filter((app) =>
|
||||
CONNECTABLE_APP_SLUGS.has(app.slug)
|
||||
);
|
||||
|
||||
/**
|
||||
* Definitions retained for existing connections and later verification, but
|
||||
* intentionally withheld from the customer-facing store. Keeping visibility
|
||||
* separate from recognition avoids breaking saved connections when a provider
|
||||
* is pulled from Browse or reserved for a future first-party experience.
|
||||
*/
|
||||
export const APP_STORE_HIDDEN_SLUGS = new Set([
|
||||
"beehiiv",
|
||||
"brex",
|
||||
"candid",
|
||||
"clickup",
|
||||
"coda",
|
||||
"egnyte",
|
||||
"embat",
|
||||
"kernel",
|
||||
"klaviyo",
|
||||
"local-falcon",
|
||||
"manufact",
|
||||
"monday",
|
||||
"oreilly",
|
||||
"planetscale",
|
||||
"razorpay",
|
||||
"sanity",
|
||||
"similarweb",
|
||||
"ticket-tailor",
|
||||
"ticktick",
|
||||
"xero",
|
||||
]);
|
||||
|
||||
export const APP_STORE_DEFINITIONS = CONNECTABLE_APP_DEFINITIONS.filter((app) =>
|
||||
!APP_STORE_HIDDEN_SLUGS.has(app.slug)
|
||||
);
|
||||
|
||||
export const DEFAULT_OWNERSHIP_AVAILABILITY: Record<ToolConnectionOwnership, boolean> = {
|
||||
platform_shared: false,
|
||||
platform_provisioned: false,
|
||||
customer: true,
|
||||
dcr: true,
|
||||
};
|
||||
|
||||
export function getConnectableAppDefinition(slug: string): AppDefinition | null {
|
||||
return CONNECTABLE_APP_DEFINITIONS.find((app) => app.slug === slug) ?? null;
|
||||
}
|
||||
|
||||
export function getAppStoreDefinition(slug: string): AppDefinition | null {
|
||||
return APP_STORE_DEFINITIONS.find((app) => app.slug === slug) ?? null;
|
||||
}
|
||||
|
||||
export function isAppStoreVisibleSlug(slug: string | null | undefined): boolean {
|
||||
return Boolean(slug && !APP_STORE_HIDDEN_SLUGS.has(slug) && CONNECTABLE_APP_SLUGS.has(slug));
|
||||
}
|
||||
|
||||
function wildcardPatternToRegExp(pattern: string): RegExp {
|
||||
const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
|
||||
return new RegExp(`^${escaped}$`, "i");
|
||||
}
|
||||
|
||||
export function getAppDefinitionForUrl(
|
||||
link: string,
|
||||
definitions: readonly AppDefinition[] = CONNECTABLE_APP_DEFINITIONS,
|
||||
): AppDefinition | null {
|
||||
let normalized: string;
|
||||
try {
|
||||
normalized = new URL(link.trim()).toString();
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
return definitions.find((app) =>
|
||||
app.urlPatterns.some((pattern) => wildcardPatternToRegExp(pattern).test(normalized))
|
||||
) ?? null;
|
||||
}
|
||||
|
||||
export function getAvailableConnectionMethods(app: AppDefinition): ConnectionMethodDef[] {
|
||||
const availability = app.ownershipAvailability ?? DEFAULT_OWNERSHIP_AVAILABILITY;
|
||||
return app.methods.filter((method) =>
|
||||
method.ownershipModes.some((ownership) => availability[ownership] !== false)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pick the method that gives a new connection the app's useful write surface.
|
||||
*
|
||||
* Google Workspace publishes separate read and write capability groups. The
|
||||
* read method is intentionally listed first for documentation, but treating
|
||||
* array order as a product default silently created read-only connections and
|
||||
* left every write action Off. Capability metadata is the durable signal; apps
|
||||
* without an explicit write/draft capability retain their declared order.
|
||||
*/
|
||||
export function getRecommendedConnectionMethod(
|
||||
methods: readonly ConnectionMethodDef[],
|
||||
): ConnectionMethodDef | null {
|
||||
const recommendedCapability = (candidates: readonly ConnectionMethodDef[]) => candidates.find((method) => {
|
||||
const capabilityKey = method.capabilityProfile?.key;
|
||||
return capabilityKey === "write" || capabilityKey === "draft";
|
||||
});
|
||||
const managedMethods = methods.filter((method) =>
|
||||
method.oauthStrategy === "paperclip_cloud_connector"
|
||||
|| method.oauthStrategy === "paperclip_id_connector"
|
||||
);
|
||||
|
||||
// Prefer the permissions needed for agent work, then the simplest sign-in.
|
||||
// Explicit read-only selections pass their own capability group here.
|
||||
return recommendedCapability(managedMethods)
|
||||
?? recommendedCapability(methods)
|
||||
?? managedMethods[0]
|
||||
?? methods[0]
|
||||
?? null;
|
||||
}
|
||||
|
||||
export function getAvailableConnectionMethod(
|
||||
app: AppDefinition,
|
||||
methodKey?: string | null,
|
||||
): ConnectionMethodDef | null {
|
||||
const methods = getAvailableConnectionMethods(app);
|
||||
return methodKey
|
||||
? methods.find((method) => method.key === methodKey) ?? null
|
||||
: getRecommendedConnectionMethod(methods);
|
||||
}
|
||||
|
||||
export function connectionMethodSupportsAutomaticOAuth(method: ConnectionMethodDef | null | undefined): boolean {
|
||||
return method?.auth === "oauth" && (
|
||||
(method.oauthStrategy === "paperclip_cloud_connector" || method.oauthStrategy === "paperclip_id_connector")
|
||||
|| method.ownershipModes.includes("dcr")
|
||||
);
|
||||
}
|
||||
|
||||
export function connectionMethodAcceptsCustomerOAuthClient(method: ConnectionMethodDef | null | undefined): boolean {
|
||||
return method?.auth === "oauth"
|
||||
&& !method.oauthStrategy
|
||||
&& method.ownershipModes.includes("customer");
|
||||
}
|
||||
|
||||
export function connectionMethodSupportsCatalogSetup(method: ConnectionMethodDef | null | undefined): boolean {
|
||||
if (!method) return false;
|
||||
if (method.transport === "runtime_auth") return Boolean(method.ai);
|
||||
if (method.auth === "none" || method.auth === "api_key") return true;
|
||||
return connectionMethodSupportsAutomaticOAuth(method)
|
||||
|| connectionMethodAcceptsCustomerOAuthClient(method);
|
||||
}
|
||||
|
||||
export function connectionMethodRequiresConfiguration(method: ConnectionMethodDef | null | undefined): boolean {
|
||||
if (!method) return false;
|
||||
const visibleTenantFields = method.tenantFields?.filter((field) => !field.hidden) ?? [];
|
||||
const visibleExtensionFields = method.extensionFields?.filter((field) => !field.hidden) ?? [];
|
||||
return Boolean(
|
||||
method.credentialFields?.length
|
||||
|| visibleTenantFields.length
|
||||
|| visibleExtensionFields.length
|
||||
|| method.configRequirements?.atLeastOneOf?.length
|
||||
// "Use your own OAuth app" is an advanced alternative when DCR/CIMD is
|
||||
// available, not a required setup field. Only customer-client-only methods
|
||||
// must stop on the configuration screen.
|
||||
|| (
|
||||
connectionMethodAcceptsCustomerOAuthClient(method)
|
||||
&& !connectionMethodSupportsAutomaticOAuth(method)
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
export function appSupportsCatalogSetup(app: AppDefinition | null | undefined): boolean {
|
||||
return Boolean(app && getAvailableConnectionMethods(app).some(connectionMethodSupportsCatalogSetup));
|
||||
}
|
||||
|
||||
export function isConnectableAppSlug(slug: string | null | undefined): boolean {
|
||||
return Boolean(slug && CONNECTABLE_APP_SLUGS.has(slug));
|
||||
}
|
||||
|
||||
export function appSupportsAutomaticOAuth(app: AppDefinition | null | undefined): boolean {
|
||||
return Boolean(app && getAvailableConnectionMethods(app).some(connectionMethodSupportsAutomaticOAuth));
|
||||
}
|
||||
|
||||
export function appAcceptsCustomerOAuthClient(app: AppDefinition | null | undefined): boolean {
|
||||
return Boolean(app && getAvailableConnectionMethods(app).some(connectionMethodAcceptsCustomerOAuthClient));
|
||||
}
|
||||
|
||||
export function credentialConfigPath(field: FieldDef, method?: ConnectionMethodDef | null): string {
|
||||
if (method?.transport === "local_stdio" && method.keyPlacement?.location === "env") return `env.${field.key}`;
|
||||
return `credentials.${field.key}`;
|
||||
}
|
||||
|
||||
/** Older credential references used bare names; current references use paths. */
|
||||
export function connectionCredentialConfigPath(ref: { name: string }): string {
|
||||
return /^(credentials|headers|oauth|remote)\./.test(ref.name)
|
||||
? ref.name
|
||||
: `credentials.${ref.name}`;
|
||||
}
|
||||
|
||||
export function resolveConnectionMethodServerUrl(
|
||||
method: ConnectionMethodDef,
|
||||
configValues: Record<string, string | boolean>,
|
||||
): string | null {
|
||||
const template = method.defaults?.serverUrlTemplate;
|
||||
if (!template) return method.defaults?.serverUrl ?? null;
|
||||
|
||||
let missingValue = false;
|
||||
const resolved = template.replace(/\{([a-zA-Z0-9_-]+)\}/g, (_placeholder, key: string) => {
|
||||
const value = configValues[key];
|
||||
if (value === undefined || String(value).trim().length === 0) {
|
||||
missingValue = true;
|
||||
return "";
|
||||
}
|
||||
return encodeURIComponent(String(value).trim());
|
||||
});
|
||||
if (missingValue) return null;
|
||||
|
||||
try {
|
||||
return new URL(resolved).toString();
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export function recommendedDefaultsForApp(app: AppDefinition, methodKey?: string | null): Record<string, unknown> {
|
||||
// Keep the parameters in the public contract: callers resolve defaults for a
|
||||
// concrete app/method. Connection finalization remains a configure-authorized,
|
||||
// audited operation, and Ask first stays available as an operator-selected
|
||||
// policy for any action after the connection is made.
|
||||
void methodKey;
|
||||
// Enterpret's run_graph_query self-reports readOnlyHint but executes Cypher
|
||||
// and has not been established as read-only. Classify it as write (server
|
||||
// classifyRisk) and require Ask first so new token connections do not ship
|
||||
// that action as Allowed.
|
||||
if (app.slug === "enterpret") {
|
||||
return {
|
||||
access: "all_agents",
|
||||
askFirstRiskLevels: ["write", "destructive"],
|
||||
};
|
||||
}
|
||||
return {
|
||||
access: "all_agents",
|
||||
askFirstRiskLevels: [],
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"slug": "agentmail",
|
||||
"name": "AgentMail",
|
||||
"description": "Give agents email inboxes and handle each conversation as a task.",
|
||||
"categories": [
|
||||
"communication"
|
||||
],
|
||||
"featured": false,
|
||||
"branding": {
|
||||
"logoUrl": "/brands/apps/agentmail.svg"
|
||||
},
|
||||
"urlPatterns": [
|
||||
"https://console.agentmail.to/*"
|
||||
],
|
||||
"methods": [
|
||||
{
|
||||
"key": "email-agent",
|
||||
"label": "Email with an agent",
|
||||
"purpose": "channel",
|
||||
"provider": "agentmail",
|
||||
"transport": "rest_api",
|
||||
"auth": "api_key",
|
||||
"ownershipModes": [
|
||||
"customer"
|
||||
],
|
||||
"whenToUse": "Assign an inbox to an agent and manage email conversations in tasks.",
|
||||
"credentialFields": [
|
||||
{
|
||||
"key": "apiKey",
|
||||
"label": "AgentMail API key",
|
||||
"type": "password",
|
||||
"placeholder": "am_…",
|
||||
"required": true,
|
||||
"secret": true
|
||||
}
|
||||
],
|
||||
"guidanceMd": "Connect an AgentMail API key, then create or select an inbox for your agent. WebSocket receiving works without a public URL.",
|
||||
"consoleLinks": {
|
||||
"keys": "https://console.agentmail.to/dashboard/api-keys",
|
||||
"docs": "https://docs.agentmail.to/inboxes"
|
||||
},
|
||||
"riskTier": "S3",
|
||||
"requiredResourceFilters": [
|
||||
"inbox"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"slug": "airtable",
|
||||
"name": "Airtable",
|
||||
"description": "Connect Airtable's provider-hosted MCP server.",
|
||||
"categories": [
|
||||
"data"
|
||||
],
|
||||
"featured": false,
|
||||
"branding": {
|
||||
"logoUrl": "/brands/apps/airtable.svg"
|
||||
},
|
||||
"urlPatterns": [
|
||||
"https://mcp.airtable.com/*"
|
||||
],
|
||||
"docsUrl": "https://support.airtable.com/articles/9897799762-using-the-airtable-mcp-server",
|
||||
"redirectConstraints": "https-or-loopback-http",
|
||||
"methods": [
|
||||
{
|
||||
"key": "mcp-oauth",
|
||||
"transport": "mcp_remote",
|
||||
"auth": "oauth",
|
||||
"oauthClientRegistration": "dcr",
|
||||
"ownershipModes": [
|
||||
"dcr"
|
||||
],
|
||||
"whenToUse": "Use browser sign-in for the provider-hosted MCP server.",
|
||||
"defaults": {
|
||||
"serverUrl": "https://mcp.airtable.com/mcp",
|
||||
"scopesHint": [
|
||||
"data.records:read",
|
||||
"data.records:write",
|
||||
"schema.bases:read",
|
||||
"schema.bases:write",
|
||||
"data.recordComments:read",
|
||||
"data.recordComments:write",
|
||||
"workspacesAndBases:read"
|
||||
]
|
||||
},
|
||||
"guidanceMd": "Connect Airtable in the browser. An Airtable account; enterprise administrators may need to allowlist the client.",
|
||||
"riskTier": "S3",
|
||||
"label": "Sign in with Airtable",
|
||||
"consoleLinks": {
|
||||
"docs": "https://support.airtable.com/articles/9897799762-using-the-airtable-mcp-server"
|
||||
},
|
||||
"warnings": [
|
||||
"An Airtable account; enterprise administrators may need to allowlist the client."
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "mcp-pat",
|
||||
"transport": "mcp_remote",
|
||||
"auth": "api_key",
|
||||
"ownershipModes": [
|
||||
"customer"
|
||||
],
|
||||
"whenToUse": "Use a personal access token when browser sign-in is not suitable.",
|
||||
"defaults": {
|
||||
"serverUrl": "https://mcp.airtable.com/mcp"
|
||||
},
|
||||
"guidanceMd": "Create an Airtable personal access token with the scopes and base resources required for the actions you intend to allow. MCP calls inherit your Airtable role and base permissions; organization allowlisting may still apply.",
|
||||
"riskTier": "S3",
|
||||
"label": "Use a personal access token",
|
||||
"credentialFields": [
|
||||
{
|
||||
"key": "authorization",
|
||||
"label": "Airtable personal access token",
|
||||
"type": "password",
|
||||
"required": true,
|
||||
"placeholder": "pat...",
|
||||
"secret": true,
|
||||
"helperMd": "Choose provider scopes and resource bases for intended actions; token and user/base role permissions both restrict access."
|
||||
}
|
||||
],
|
||||
"keyPlacement": {
|
||||
"location": "header",
|
||||
"name": "Authorization",
|
||||
"prefix": "Bearer "
|
||||
},
|
||||
"consoleLinks": {
|
||||
"docs": "https://support.airtable.com/articles/9897799762-using-the-airtable-mcp-server",
|
||||
"keys": "https://airtable.com/create/tokens"
|
||||
},
|
||||
"warnings": [
|
||||
"Use a disposable or development base for initial write qualification. Enterprise admins may need to allowlist MCP access."
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"slug": "anthropic",
|
||||
"name": "Anthropic",
|
||||
"description": "Use Anthropic APIs with a restricted key.",
|
||||
"categories": [
|
||||
"ai"
|
||||
],
|
||||
"featured": false,
|
||||
"branding": {
|
||||
"logoUrl": "/brands/apps/anthropic.svg",
|
||||
"darkLogoUrl": "/brands/apps/anthropic-dark.svg"
|
||||
},
|
||||
"urlPatterns": [
|
||||
"https://api.anthropic.com/*"
|
||||
],
|
||||
"methods": [
|
||||
{
|
||||
"key": "ai-subscription",
|
||||
"label": "Claude subscription",
|
||||
"purpose": "ai",
|
||||
"transport": "runtime_auth",
|
||||
"auth": "oauth",
|
||||
"ai": {
|
||||
"provider": "anthropic",
|
||||
"method": "subscription"
|
||||
},
|
||||
"grantKinds": [
|
||||
"user",
|
||||
"organization"
|
||||
],
|
||||
"ownershipModes": [
|
||||
"customer"
|
||||
],
|
||||
"whenToUse": "Authenticate an agent with this account.",
|
||||
"guidanceMd": "Use your personal account or an explicitly shared company account.",
|
||||
"riskTier": "S3"
|
||||
},
|
||||
{
|
||||
"key": "ai-api_key",
|
||||
"label": "Claude API key",
|
||||
"purpose": "ai",
|
||||
"transport": "runtime_auth",
|
||||
"auth": "api_key",
|
||||
"ai": {
|
||||
"provider": "anthropic",
|
||||
"method": "api_key"
|
||||
},
|
||||
"grantKinds": [
|
||||
"user",
|
||||
"organization"
|
||||
],
|
||||
"ownershipModes": [
|
||||
"customer"
|
||||
],
|
||||
"whenToUse": "Authenticate an agent with this account.",
|
||||
"guidanceMd": "Use your personal account or an explicitly shared company account.",
|
||||
"riskTier": "S3",
|
||||
"credentialFields": [
|
||||
{
|
||||
"key": "apiKey",
|
||||
"label": "API key",
|
||||
"type": "password",
|
||||
"required": true,
|
||||
"placeholder": "Enter API key",
|
||||
"secret": true
|
||||
}
|
||||
],
|
||||
"keyPlacement": {
|
||||
"location": "env",
|
||||
"name": "ANTHROPIC_API_KEY"
|
||||
}
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"model-provider"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"slug": "api-key-generic",
|
||||
"name": "API key app",
|
||||
"description": "Connect an API using a key from your provider.",
|
||||
"categories": [
|
||||
"other"
|
||||
],
|
||||
"featured": false,
|
||||
"branding": {
|
||||
"logoUrl": "/brands/apps/api-key-generic.svg"
|
||||
},
|
||||
"urlPatterns": [],
|
||||
"methods": [
|
||||
{
|
||||
"key": "api-key",
|
||||
"transport": "rest_api",
|
||||
"auth": "api_key",
|
||||
"ownershipModes": [
|
||||
"customer"
|
||||
],
|
||||
"whenToUse": "Use credentials from your provider account.",
|
||||
"defaults": {},
|
||||
"guidanceMd": "Create a restricted API key and paste it here.",
|
||||
"riskTier": "S3",
|
||||
"credentialFields": [
|
||||
{
|
||||
"key": "apiKey",
|
||||
"label": "API key",
|
||||
"type": "password",
|
||||
"required": true,
|
||||
"placeholder": "Paste the API key",
|
||||
"secret": true,
|
||||
"helperMd": "Actions depend on the operator-supplied API and key; grant read/write on the required resources at the provider. Create a key with read and write permissions for these actions; Paperclip cannot increase an existing key’s permissions."
|
||||
}
|
||||
],
|
||||
"keyPlacement": {
|
||||
"location": "header",
|
||||
"name": "Authorization",
|
||||
"prefix": "Bearer "
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"slug": "arcade",
|
||||
"name": "Arcade",
|
||||
"description": "Use the tools exposed by your Arcade MCP connection.",
|
||||
"categories": [
|
||||
"productivity"
|
||||
],
|
||||
"featured": true,
|
||||
"branding": {
|
||||
"logoUrl": "/brands/apps/arcade.png"
|
||||
},
|
||||
"urlPatterns": [
|
||||
"https://api.arcade.dev/*"
|
||||
],
|
||||
"methods": [
|
||||
{
|
||||
"key": "mcp",
|
||||
"transport": "mcp_remote",
|
||||
"auth": "none",
|
||||
"ownershipModes": [
|
||||
"dcr",
|
||||
"customer"
|
||||
],
|
||||
"whenToUse": "Use the provider-hosted connection for the quickest setup.",
|
||||
"defaults": {},
|
||||
"guidanceMd": "Paste your Arcade MCP URL. Sign in if required, or add a token or headers under Advanced authentication.",
|
||||
"riskTier": "S3",
|
||||
"label": "Connect MCP server",
|
||||
"consoleLinks": {
|
||||
"docs": "https://docs.arcade.dev/en/operate/governance/mcp-gateways"
|
||||
}
|
||||
}
|
||||
],
|
||||
"docsUrl": "https://docs.arcade.dev/en/operate/governance/mcp-gateways"
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"slug": "asana",
|
||||
"name": "Asana",
|
||||
"description": "Connect Asana's provider-hosted MCP server.",
|
||||
"categories": [
|
||||
"productivity"
|
||||
],
|
||||
"featured": false,
|
||||
"branding": {
|
||||
"logoUrl": "/brands/apps/asana.svg"
|
||||
},
|
||||
"urlPatterns": [
|
||||
"https://mcp.asana.com/*"
|
||||
],
|
||||
"docsUrl": "https://developers.asana.com/docs/integrating-with-asanas-mcp-server",
|
||||
"redirectConstraints": "https-or-loopback-http",
|
||||
"methods": [
|
||||
{
|
||||
"key": "managed",
|
||||
"transport": "mcp_remote",
|
||||
"auth": "oauth",
|
||||
"ownershipModes": [
|
||||
"platform_shared"
|
||||
],
|
||||
"whenToUse": "Connect your Asana account with Paperclip's app.",
|
||||
"defaults": {
|
||||
"serverUrl": "https://mcp.asana.com/v2/mcp",
|
||||
"scopesHint": [
|
||||
"default"
|
||||
]
|
||||
},
|
||||
"guidanceMd": "Sign in to Asana with Paperclip. Asana gives this connection access to the workspaces available to your account.",
|
||||
"riskTier": "S3",
|
||||
"label": "Sign in with Asana",
|
||||
"consoleLinks": {
|
||||
"docs": "https://developers.asana.com/docs/integrating-with-asanas-mcp-server"
|
||||
},
|
||||
"warnings": [],
|
||||
"oauthStrategy": "paperclip_cloud_connector",
|
||||
"connectorProfile": "asana.mcp",
|
||||
"grantKinds": [
|
||||
"user",
|
||||
"agent"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "mcp-own-oauth",
|
||||
"transport": "mcp_remote",
|
||||
"auth": "oauth",
|
||||
"ownershipModes": [
|
||||
"customer"
|
||||
],
|
||||
"whenToUse": "Register an OAuth app with Asana, then enter its client ID and secret.",
|
||||
"defaults": {
|
||||
"serverUrl": "https://mcp.asana.com/v2/mcp",
|
||||
"discoveryUrl": "https://mcp.asana.com/.well-known/oauth-protected-resource/v2",
|
||||
"scopesHint": [
|
||||
"default"
|
||||
]
|
||||
},
|
||||
"guidanceMd": "Create an MCP app in Asana, then add the callback URL below under OAuth. Under Manage distribution, select your workspace and save. API apps do not work with Asana MCP.",
|
||||
"riskTier": "S3",
|
||||
"label": "Use your own OAuth app",
|
||||
"consoleLinks": {
|
||||
"register": "https://app.asana.com/0/my-apps",
|
||||
"docs": "https://developers.asana.com/docs/integrating-with-asanas-mcp-server"
|
||||
},
|
||||
"warnings": [],
|
||||
"oauthClientSecretRequired": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"slug": "bedrock",
|
||||
"name": "Amazon Bedrock",
|
||||
"description": "Use Claude through Amazon Bedrock with a Bedrock API key and AWS region.",
|
||||
"categories": [
|
||||
"ai"
|
||||
],
|
||||
"branding": {
|
||||
"logoUrl": "/brands/apps/bedrock.svg"
|
||||
},
|
||||
"urlPatterns": [],
|
||||
"methods": [
|
||||
{
|
||||
"key": "ai-api_key",
|
||||
"label": "Amazon Bedrock API key",
|
||||
"purpose": "ai",
|
||||
"transport": "runtime_auth",
|
||||
"auth": "api_key",
|
||||
"ai": {
|
||||
"provider": "anthropic",
|
||||
"method": "api_key"
|
||||
},
|
||||
"grantKinds": [
|
||||
"user",
|
||||
"organization"
|
||||
],
|
||||
"ownershipModes": [
|
||||
"customer"
|
||||
],
|
||||
"whenToUse": "Use Claude through Amazon Bedrock with a Bedrock API key and AWS region.",
|
||||
"guidanceMd": "Use your personal account or an explicitly shared company account.",
|
||||
"riskTier": "S3",
|
||||
"credentialFields": [
|
||||
{
|
||||
"key": "apiKey",
|
||||
"label": "API key",
|
||||
"type": "password",
|
||||
"required": true,
|
||||
"placeholder": "Enter API key",
|
||||
"secret": true
|
||||
}
|
||||
],
|
||||
"keyPlacement": {
|
||||
"location": "env",
|
||||
"name": "AWS_BEARER_TOKEN_BEDROCK"
|
||||
}
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"model-provider"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"slug": "beehiiv",
|
||||
"name": "beehiiv",
|
||||
"description": "Connect beehiiv's provider-hosted MCP server.",
|
||||
"categories": [
|
||||
"content"
|
||||
],
|
||||
"featured": false,
|
||||
"branding": {
|
||||
"logoUrl": "/brands/apps/beehiiv.png"
|
||||
},
|
||||
"urlPatterns": [
|
||||
"https://mcp.beehiiv.com/*"
|
||||
],
|
||||
"docsUrl": "https://www.beehiiv.com/features/mcp/getting-started",
|
||||
"redirectConstraints": "https-or-loopback-http",
|
||||
"methods": [
|
||||
{
|
||||
"key": "mcp-oauth",
|
||||
"transport": "mcp_remote",
|
||||
"auth": "oauth",
|
||||
"ownershipModes": [
|
||||
"dcr"
|
||||
],
|
||||
"whenToUse": "Use browser sign-in for the provider-hosted MCP server.",
|
||||
"defaults": {
|
||||
"serverUrl": "https://mcp.beehiiv.com/mcp",
|
||||
"scopesHint": [
|
||||
"read",
|
||||
"write"
|
||||
]
|
||||
},
|
||||
"guidanceMd": "Connect beehiiv in the browser. A beehiiv account; the subscription plan controls available write capabilities.",
|
||||
"riskTier": "S3",
|
||||
"label": "Sign in with beehiiv",
|
||||
"consoleLinks": {
|
||||
"docs": "https://www.beehiiv.com/features/mcp/getting-started"
|
||||
},
|
||||
"warnings": [
|
||||
"A beehiiv account; the subscription plan controls available write capabilities."
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"slug": "bitly",
|
||||
"name": "Bitly",
|
||||
"description": "Connect Bitly's provider-hosted MCP server.",
|
||||
"categories": [
|
||||
"analytics"
|
||||
],
|
||||
"featured": false,
|
||||
"branding": {
|
||||
"logoUrl": "/brands/apps/bitly.svg"
|
||||
},
|
||||
"urlPatterns": [
|
||||
"https://api-ssl.bitly.com/*"
|
||||
],
|
||||
"docsUrl": "https://dev.bitly.com/bitly-mcp/overview/quickstart/",
|
||||
"redirectConstraints": "https-or-loopback-http",
|
||||
"methods": [
|
||||
{
|
||||
"key": "mcp-oauth",
|
||||
"transport": "mcp_remote",
|
||||
"auth": "oauth",
|
||||
"ownershipModes": [
|
||||
"dcr"
|
||||
],
|
||||
"whenToUse": "Use browser sign-in for the provider-hosted MCP server.",
|
||||
"defaults": {
|
||||
"serverUrl": "https://api-ssl.bitly.com/v4/mcp",
|
||||
"scopesHint": [
|
||||
"mcp.all"
|
||||
]
|
||||
},
|
||||
"guidanceMd": "Connect Bitly in the browser. Choose only the intended workspace. Review the discovered actions and keep writes and destructive operations disabled until explicitly needed.",
|
||||
"riskTier": "S4",
|
||||
"label": "Sign in with Bitly",
|
||||
"consoleLinks": {
|
||||
"docs": "https://dev.bitly.com/bitly-mcp/using-the-bitly-mcp-server/configuration-details/"
|
||||
},
|
||||
"warnings": [
|
||||
"The mcp.all scope covers Bitly's broad MCP catalog, including link and QR management, analytics, groups, custom domains, and Bitly Sites.",
|
||||
"The catalog includes publishing and deletion operations, including permanent short-link deletion, site deletion, and custom-domain changes. Review the full catalog and keep write and destructive actions Off during read-only qualification."
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "mcp-api-key",
|
||||
"transport": "mcp_remote",
|
||||
"auth": "api_key",
|
||||
"ownershipModes": [
|
||||
"customer"
|
||||
],
|
||||
"whenToUse": "Use a customer-created Bitly API token when browser sign-in is not suitable.",
|
||||
"defaults": {
|
||||
"serverUrl": "https://api-ssl.bitly.com/v4/mcp"
|
||||
},
|
||||
"guidanceMd": "Generate a Bitly API token under Settings → API. The token may authorize the MCP's full catalog under your Bitly account; Paperclip cannot narrow the token. Review discovered actions and keep writes and destructive operations disabled until explicitly needed.",
|
||||
"riskTier": "S4",
|
||||
"label": "Use an API key",
|
||||
"credentialFields": [
|
||||
{
|
||||
"key": "authorization",
|
||||
"label": "Bitly API key",
|
||||
"type": "password",
|
||||
"required": true,
|
||||
"placeholder": "Paste your Bitly API token",
|
||||
"secret": true,
|
||||
"helperMd": "A Bitly API token from Settings → API is documented for MCP bearer auth, but no restriction scope is documented; treat it as capable of the full MCP catalog under that account. Paperclip cannot narrow the token."
|
||||
}
|
||||
],
|
||||
"keyPlacement": {
|
||||
"location": "header",
|
||||
"name": "Authorization",
|
||||
"prefix": "Bearer "
|
||||
},
|
||||
"consoleLinks": {
|
||||
"keys": "https://bitly.com/settings/api",
|
||||
"docs": "https://dev.bitly.com/bitly-mcp/using-the-bitly-mcp-server/configuration-details/"
|
||||
},
|
||||
"warnings": [
|
||||
"This API token is not documented as a restricted MCP token. It may authorize Bitly's broad MCP catalog, including link creation/update/deletion, QR codes, Sites publishing/deletion, and custom-domain operations.",
|
||||
"Review the discovered catalog and keep write and destructive actions Off during read-only qualification."
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"slug": "box",
|
||||
"name": "Box",
|
||||
"description": "Connect Box's provider-hosted MCP server.",
|
||||
"categories": [
|
||||
"content"
|
||||
],
|
||||
"featured": false,
|
||||
"branding": {
|
||||
"logoUrl": "/brands/apps/box.svg"
|
||||
},
|
||||
"urlPatterns": [
|
||||
"https://mcp.box.com/*"
|
||||
],
|
||||
"docsUrl": "https://support.box.com/hc/en-us/articles/43847256139923-Managing-Box-MCP-Servers",
|
||||
"redirectConstraints": "https-or-loopback-http",
|
||||
"methods": [
|
||||
{
|
||||
"key": "mcp-own-oauth",
|
||||
"transport": "mcp_remote",
|
||||
"auth": "oauth",
|
||||
"ownershipModes": [
|
||||
"customer"
|
||||
],
|
||||
"whenToUse": "Register an OAuth app with Box, then enter its client ID and secret.",
|
||||
"defaults": {
|
||||
"serverUrl": "https://mcp.box.com"
|
||||
},
|
||||
"guidanceMd": "Connect Box in the browser. A Box administrator creates the OAuth integration and enables AI access.",
|
||||
"riskTier": "S3",
|
||||
"label": "Use your own OAuth app",
|
||||
"consoleLinks": {
|
||||
"register": "https://support.box.com/hc/en-us/articles/43847256139923-Managing-Box-MCP-Servers",
|
||||
"docs": "https://support.box.com/hc/en-us/articles/43847256139923-Managing-Box-MCP-Servers"
|
||||
},
|
||||
"warnings": [
|
||||
"A Box administrator creates the OAuth integration and enables AI access."
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"slug": "brex",
|
||||
"name": "Brex",
|
||||
"description": "Connect Brex's provider-hosted MCP server.",
|
||||
"categories": [
|
||||
"commerce"
|
||||
],
|
||||
"featured": false,
|
||||
"branding": {
|
||||
"logoUrl": "/brands/apps/brex.svg",
|
||||
"darkLogoUrl": "/brands/apps/brex-dark.svg"
|
||||
},
|
||||
"urlPatterns": [
|
||||
"https://api.brex.com/*"
|
||||
],
|
||||
"docsUrl": "https://www.brex.com/support/using-brex-in-ai-apps",
|
||||
"redirectConstraints": "https-or-loopback-http",
|
||||
"methods": [
|
||||
{
|
||||
"key": "mcp-oauth",
|
||||
"transport": "mcp_remote",
|
||||
"auth": "oauth",
|
||||
"ownershipModes": [
|
||||
"dcr"
|
||||
],
|
||||
"whenToUse": "Use browser sign-in for the provider-hosted MCP server.",
|
||||
"defaults": {
|
||||
"serverUrl": "https://api.brex.com/mcp",
|
||||
"scopesHint": [
|
||||
"openid",
|
||||
"offline_access",
|
||||
"email",
|
||||
"users.readonly",
|
||||
"departments.readonly",
|
||||
"locations.readonly",
|
||||
"titles.readonly",
|
||||
"legal_entities.readonly",
|
||||
"cards.readonly",
|
||||
"cards",
|
||||
"companies.readonly",
|
||||
"budgets.readonly",
|
||||
"travel.trips.readonly",
|
||||
"expenses.card.readonly",
|
||||
"expenses.card",
|
||||
"expenses.bill",
|
||||
"accounts.cash.readonly",
|
||||
"vendors.readonly",
|
||||
"accounting.integration.read",
|
||||
"accounting.record.read"
|
||||
]
|
||||
},
|
||||
"guidanceMd": "Connect Brex in the browser. Brex early access and an administrator enabling the integration; financial actions require explicit approval.",
|
||||
"riskTier": "S4",
|
||||
"label": "Sign in with Brex",
|
||||
"consoleLinks": {
|
||||
"docs": "https://www.brex.com/support/using-brex-in-ai-apps"
|
||||
},
|
||||
"warnings": [
|
||||
"Brex early access and an administrator enabling the integration; financial actions require explicit approval.",
|
||||
"Financial or destructive actions must be explicitly approved before execution."
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"slug": "browser-use-cloud",
|
||||
"name": "Browser Use Cloud",
|
||||
"description": "Delegate browser tasks and watch them live in Paperclip.",
|
||||
"categories": [
|
||||
"productivity"
|
||||
],
|
||||
"featured": false,
|
||||
"branding": {
|
||||
"logoUrl": "/brands/apps/browser-use.svg"
|
||||
},
|
||||
"urlPatterns": [
|
||||
"https://cloud.browser-use.com/*"
|
||||
],
|
||||
"methods": [
|
||||
{
|
||||
"key": "cloud-v4",
|
||||
"transport": "rest_api",
|
||||
"auth": "api_key",
|
||||
"ownershipModes": [
|
||||
"customer"
|
||||
],
|
||||
"whenToUse": "Use credentials from your provider account.",
|
||||
"defaults": {
|
||||
"serverUrl": "https://api.browser-use.com/api/v4"
|
||||
},
|
||||
"guidanceMd": "Create an API key in [Browser Use settings](https://cloud.browser-use.com/settings) and paste it below. Your agents can browse websites while you watch and interact from the task's Browser tab.",
|
||||
"riskTier": "S3",
|
||||
"label": "Browser Use Cloud",
|
||||
"credentialFields": [
|
||||
{
|
||||
"key": "apiKey",
|
||||
"label": "API key",
|
||||
"type": "password",
|
||||
"required": true,
|
||||
"placeholder": "bu_…",
|
||||
"secret": true,
|
||||
"helperMd": "Create/stop browser sessions and run browser tasks under the API key. Create a key with read and write permissions for these actions; Paperclip cannot increase an existing key’s permissions."
|
||||
}
|
||||
],
|
||||
"keyPlacement": {
|
||||
"location": "header",
|
||||
"name": "X-Browser-Use-API-Key"
|
||||
},
|
||||
"consoleLinks": {
|
||||
"keys": "https://cloud.browser-use.com/settings",
|
||||
"docs": "https://docs.browser-use.com/cloud/api-v4-overview"
|
||||
}
|
||||
}
|
||||
],
|
||||
"docsUrl": "https://docs.browser-use.com/cloud/api-v4-overview"
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"slug": "calendly",
|
||||
"name": "Calendly",
|
||||
"description": "Review Calendly event types and availability, and use supported scheduling actions.",
|
||||
"categories": [
|
||||
"productivity"
|
||||
],
|
||||
"featured": false,
|
||||
"branding": {
|
||||
"logoUrl": "/brands/apps/calendly.png"
|
||||
},
|
||||
"urlPatterns": [
|
||||
"https://mcp.calendly.com/*"
|
||||
],
|
||||
"docsUrl": "https://developer.calendly.com/docs/mcp/calendly-mcp-server",
|
||||
"redirectConstraints": "https-or-loopback-http",
|
||||
"methods": [
|
||||
{
|
||||
"key": "mcp-oauth",
|
||||
"label": "Sign in with Calendly",
|
||||
"transport": "mcp_remote",
|
||||
"auth": "oauth",
|
||||
"ownershipModes": [
|
||||
"dcr"
|
||||
],
|
||||
"whenToUse": "Connect the provider-hosted Calendly MCP server with browser sign-in.",
|
||||
"defaults": {
|
||||
"serverUrl": "https://mcp.calendly.com",
|
||||
"scopesHint": [
|
||||
"mcp:scheduling:read",
|
||||
"mcp:scheduling:write"
|
||||
]
|
||||
},
|
||||
"guidanceMd": "Check the connected account, event type, timezone, availability, and attendee details before using a scheduling write. Use a test scheduling artifact only when the discovered tool explicitly supports it.",
|
||||
"warnings": [
|
||||
"Calendly MCP requires DCR and OAuth 2.1 authorization-code with PKCE S256. It does not support personal access tokens or manually provisioned static OAuth clients.",
|
||||
"The provider advertises both scheduling read and write scopes for full access; verify the live tool catalog before enabling booking or link creation."
|
||||
],
|
||||
"consoleLinks": {
|
||||
"docs": "https://developer.calendly.com/docs/mcp/calendly-mcp-server"
|
||||
},
|
||||
"riskTier": "S3"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"slug": "candid",
|
||||
"name": "Candid",
|
||||
"description": "Connect Candid's provider-hosted MCP server.",
|
||||
"categories": [
|
||||
"data"
|
||||
],
|
||||
"featured": false,
|
||||
"branding": {
|
||||
"logoUrl": "/brands/apps/candid.png"
|
||||
},
|
||||
"urlPatterns": [
|
||||
"https://mcp.candid.org/*"
|
||||
],
|
||||
"docsUrl": "https://learning.candid.org/getting-started-with-the-candid-mcp-connector/375441",
|
||||
"redirectConstraints": "https-or-loopback-http",
|
||||
"methods": [
|
||||
{
|
||||
"key": "mcp-oauth",
|
||||
"transport": "mcp_remote",
|
||||
"auth": "oauth",
|
||||
"ownershipModes": [
|
||||
"dcr"
|
||||
],
|
||||
"whenToUse": "Use browser sign-in for the provider-hosted MCP server.",
|
||||
"defaults": {
|
||||
"serverUrl": "https://mcp.candid.org/mcp",
|
||||
"scopesHint": [
|
||||
"openid",
|
||||
"profile",
|
||||
"email"
|
||||
]
|
||||
},
|
||||
"guidanceMd": "Connect Candid in the browser. A Candid account with access to the MCP connector.",
|
||||
"riskTier": "S2",
|
||||
"label": "Sign in with Candid",
|
||||
"consoleLinks": {
|
||||
"docs": "https://learning.candid.org/getting-started-with-the-candid-mcp-connector/375441"
|
||||
},
|
||||
"warnings": [
|
||||
"A Candid account with access to the MCP connector."
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"slug": "chat-completions-api",
|
||||
"name": "Chat Completions API",
|
||||
"description": "Connect any compatible harness to a Chat Completions-compatible provider or gateway.",
|
||||
"categories": [
|
||||
"ai"
|
||||
],
|
||||
"branding": {
|
||||
"logoUrl": "/brands/apps/api-key-generic.svg"
|
||||
},
|
||||
"urlPatterns": [],
|
||||
"methods": [
|
||||
{
|
||||
"key": "ai-api_key",
|
||||
"label": "Chat Completions API API key",
|
||||
"purpose": "ai",
|
||||
"transport": "runtime_auth",
|
||||
"auth": "api_key",
|
||||
"ai": {
|
||||
"provider": "openai",
|
||||
"method": "api_key"
|
||||
},
|
||||
"grantKinds": [
|
||||
"user",
|
||||
"organization"
|
||||
],
|
||||
"ownershipModes": [
|
||||
"customer"
|
||||
],
|
||||
"whenToUse": "Connect any compatible harness to a Chat Completions-compatible provider or gateway.",
|
||||
"guidanceMd": "Use your personal account or an explicitly shared company account.",
|
||||
"riskTier": "S3",
|
||||
"credentialFields": [
|
||||
{
|
||||
"key": "apiKey",
|
||||
"label": "API key",
|
||||
"type": "password",
|
||||
"required": true,
|
||||
"placeholder": "Enter API key",
|
||||
"secret": true
|
||||
}
|
||||
],
|
||||
"keyPlacement": {
|
||||
"location": "env",
|
||||
"name": "OPENAI_API_KEY"
|
||||
}
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"model-provider"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"slug": "clickhouse",
|
||||
"name": "ClickHouse",
|
||||
"description": "Connect ClickHouse's provider-hosted MCP server.",
|
||||
"categories": [
|
||||
"data"
|
||||
],
|
||||
"featured": false,
|
||||
"branding": {
|
||||
"logoUrl": "/brands/apps/clickhouse.svg",
|
||||
"darkLogoUrl": "/brands/apps/clickhouse-dark.svg"
|
||||
},
|
||||
"urlPatterns": [
|
||||
"https://mcp.clickhouse.cloud/*"
|
||||
],
|
||||
"docsUrl": "https://clickhouse.com/blog/announcing-managed-clickstack-mcp-server",
|
||||
"redirectConstraints": "https-or-loopback-http",
|
||||
"methods": [
|
||||
{
|
||||
"key": "mcp-oauth",
|
||||
"transport": "mcp_remote",
|
||||
"auth": "oauth",
|
||||
"ownershipModes": [
|
||||
"dcr"
|
||||
],
|
||||
"whenToUse": "Use browser sign-in for the provider-hosted MCP server.",
|
||||
"defaults": {
|
||||
"serverUrl": "https://mcp.clickhouse.cloud/clickstack",
|
||||
"scopesHint": [
|
||||
"mcp:access",
|
||||
"clickstack:access",
|
||||
"openid",
|
||||
"profile",
|
||||
"email"
|
||||
]
|
||||
},
|
||||
"guidanceMd": "Connect ClickHouse in the browser. A ClickHouse Cloud ClickStack service and its service ID.",
|
||||
"riskTier": "S4",
|
||||
"label": "Sign in with ClickHouse",
|
||||
"consoleLinks": {
|
||||
"docs": "https://clickhouse.com/blog/announcing-managed-clickstack-mcp-server"
|
||||
},
|
||||
"warnings": [
|
||||
"A ClickHouse Cloud ClickStack service and its service ID."
|
||||
],
|
||||
"tenantFields": [
|
||||
{
|
||||
"key": "serviceId",
|
||||
"label": "ClickHouse Cloud service ID",
|
||||
"type": "text",
|
||||
"required": true,
|
||||
"placeholder": "11e1031f-9a13-4cac-9bc7-d4ec9286ec17",
|
||||
"helperMd": "Copy the service ID from ClickStack → Team Settings → API & Agents.",
|
||||
"transport": {
|
||||
"location": "header",
|
||||
"name": "x-service-id"
|
||||
}
|
||||
}
|
||||
],
|
||||
"requiredResourceFilters": [
|
||||
"service"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"slug": "clickup",
|
||||
"name": "ClickUp",
|
||||
"description": "Search ClickUp workspaces and manage tasks, docs, and conversations through the official MCP server.",
|
||||
"categories": [
|
||||
"productivity"
|
||||
],
|
||||
"featured": false,
|
||||
"branding": {
|
||||
"logoUrl": "/brands/apps/clickup.png"
|
||||
},
|
||||
"urlPatterns": [
|
||||
"https://mcp.clickup.com/*"
|
||||
],
|
||||
"docsUrl": "https://developer.clickup.com/docs/connect-an-ai-assistant-to-clickups-mcp-server",
|
||||
"redirectConstraints": "https-or-loopback-http",
|
||||
"methods": [
|
||||
{
|
||||
"key": "mcp-oauth",
|
||||
"label": "Sign in with ClickUp",
|
||||
"transport": "mcp_remote",
|
||||
"auth": "oauth",
|
||||
"ownershipModes": [
|
||||
"dcr"
|
||||
],
|
||||
"grantKinds": [
|
||||
"user"
|
||||
],
|
||||
"whenToUse": "Connect the provider-hosted ClickUp MCP server with browser sign-in.",
|
||||
"defaults": {
|
||||
"serverUrl": "https://mcp.clickup.com/mcp",
|
||||
"scopesHint": [
|
||||
"read",
|
||||
"write"
|
||||
]
|
||||
},
|
||||
"guidanceMd": "Choose only the ClickUp workspace needed. Confirm the target list and task before changing content, starting a timer, posting a chat message, or deleting anything.",
|
||||
"warnings": [
|
||||
"ClickUp's MCP may expose task, document, and chat writes, time tracking, and task deletion under the signed-in user's permissions. Review the catalog and keep write and destructive actions disabled until explicitly needed.",
|
||||
"Workspace usage may be subject to plan-based rolling daily MCP limits."
|
||||
],
|
||||
"consoleLinks": {
|
||||
"docs": "https://developer.clickup.com/docs/connect-an-ai-assistant-to-clickups-mcp-server"
|
||||
},
|
||||
"riskTier": "S4",
|
||||
"requiredResourceFilters": [
|
||||
"workspace",
|
||||
"list"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"slug": "cloudflare",
|
||||
"name": "Cloudflare",
|
||||
"description": "Connect Cloudflare's provider-hosted MCP server.",
|
||||
"categories": [
|
||||
"developer"
|
||||
],
|
||||
"featured": false,
|
||||
"branding": {
|
||||
"logoUrl": "/brands/apps/cloudflare.svg"
|
||||
},
|
||||
"urlPatterns": [
|
||||
"https://mcp.cloudflare.com/*"
|
||||
],
|
||||
"docsUrl": "https://developers.cloudflare.com/agents/model-context-protocol/cloudflare/servers-for-cloudflare/",
|
||||
"redirectConstraints": "https-or-loopback-http",
|
||||
"methods": [
|
||||
{
|
||||
"key": "mcp-oauth",
|
||||
"transport": "mcp_remote",
|
||||
"auth": "oauth",
|
||||
"ownershipModes": [
|
||||
"dcr"
|
||||
],
|
||||
"whenToUse": "Use browser sign-in for the provider-hosted MCP server.",
|
||||
"defaults": {
|
||||
"serverUrl": "https://mcp.cloudflare.com/mcp",
|
||||
"scopesHint": [
|
||||
"user:read",
|
||||
"account:read"
|
||||
]
|
||||
},
|
||||
"guidanceMd": "Connect Cloudflare in the browser. A Cloudflare account with access to the resources being connected.",
|
||||
"riskTier": "S3",
|
||||
"label": "Sign in with Cloudflare",
|
||||
"consoleLinks": {
|
||||
"docs": "https://developers.cloudflare.com/agents/model-context-protocol/cloudflare/servers-for-cloudflare/"
|
||||
},
|
||||
"warnings": [
|
||||
"A Cloudflare account with access to the resources being connected."
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "mcp-api-key",
|
||||
"transport": "mcp_remote",
|
||||
"auth": "api_key",
|
||||
"ownershipModes": [
|
||||
"customer"
|
||||
],
|
||||
"whenToUse": "Use a restricted customer-owned key when browser sign-in is not suitable.",
|
||||
"defaults": {
|
||||
"serverUrl": "https://mcp.cloudflare.com/mcp"
|
||||
},
|
||||
"guidanceMd": "Use a customer-created Cloudflare key. A Cloudflare account with access to the resources being connected.",
|
||||
"riskTier": "S3",
|
||||
"label": "Use an API key",
|
||||
"credentialFields": [
|
||||
{
|
||||
"key": "authorization",
|
||||
"label": "Cloudflare API key",
|
||||
"type": "password",
|
||||
"required": true,
|
||||
"placeholder": "Paste your Cloudflare API token",
|
||||
"secret": true,
|
||||
"helperMd": "Cloudflare API actions selected at provider consent or on the API token. Identity scopes alone do not grant account writes. Create a key with read and write permissions for these actions; Paperclip cannot increase an existing key’s permissions."
|
||||
}
|
||||
],
|
||||
"keyPlacement": {
|
||||
"location": "header",
|
||||
"name": "Authorization",
|
||||
"prefix": "Bearer "
|
||||
},
|
||||
"consoleLinks": {
|
||||
"keys": "https://developers.cloudflare.com/agents/model-context-protocol/cloudflare/servers-for-cloudflare/",
|
||||
"docs": "https://developers.cloudflare.com/agents/model-context-protocol/cloudflare/servers-for-cloudflare/"
|
||||
},
|
||||
"warnings": [
|
||||
"A Cloudflare account with access to the resources being connected."
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"slug": "cloudinary",
|
||||
"name": "Cloudinary",
|
||||
"description": "Connect Cloudinary's provider-hosted MCP server.",
|
||||
"categories": [
|
||||
"content"
|
||||
],
|
||||
"featured": false,
|
||||
"branding": {
|
||||
"logoUrl": "/brands/apps/cloudinary.svg",
|
||||
"darkLogoUrl": "/brands/apps/cloudinary-dark.svg"
|
||||
},
|
||||
"urlPatterns": [
|
||||
"https://asset-management.mcp.cloudinary.com/*"
|
||||
],
|
||||
"docsUrl": "https://cloudinary.com/documentation/cloudinary_llm_mcp",
|
||||
"redirectConstraints": "https-or-loopback-http",
|
||||
"methods": [
|
||||
{
|
||||
"key": "mcp-oauth",
|
||||
"transport": "mcp_remote",
|
||||
"auth": "oauth",
|
||||
"ownershipModes": [
|
||||
"dcr"
|
||||
],
|
||||
"whenToUse": "Use browser sign-in for the provider-hosted MCP server.",
|
||||
"defaults": {
|
||||
"serverUrl": "https://asset-management.mcp.cloudinary.com/mcp",
|
||||
"scopesHint": [
|
||||
"openid",
|
||||
"profile",
|
||||
"email",
|
||||
"offline_access",
|
||||
"asset_management",
|
||||
"upload",
|
||||
"media_generation"
|
||||
]
|
||||
},
|
||||
"guidanceMd": "Connect Cloudinary in the browser. A Cloudinary account; authorization is limited by the signed-in user's roles.",
|
||||
"riskTier": "S3",
|
||||
"label": "Sign in with Cloudinary",
|
||||
"consoleLinks": {
|
||||
"docs": "https://cloudinary.com/documentation/cloudinary_llm_mcp"
|
||||
},
|
||||
"warnings": [
|
||||
"A Cloudinary account; authorization is limited by the signed-in user's roles."
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"slug": "coda",
|
||||
"name": "Coda",
|
||||
"description": "Connect Coda's provider-hosted MCP server.",
|
||||
"categories": [
|
||||
"productivity"
|
||||
],
|
||||
"featured": false,
|
||||
"branding": {
|
||||
"logoUrl": "/brands/apps/coda.svg"
|
||||
},
|
||||
"urlPatterns": [
|
||||
"https://coda.io/*"
|
||||
],
|
||||
"docsUrl": "https://help.coda.io/hc/en-us/articles/44722769665549-Security-recommendations-for-the-Coda-MCP",
|
||||
"redirectConstraints": "https-or-loopback-http",
|
||||
"methods": [
|
||||
{
|
||||
"key": "mcp-oauth",
|
||||
"transport": "mcp_remote",
|
||||
"auth": "oauth",
|
||||
"ownershipModes": [
|
||||
"dcr"
|
||||
],
|
||||
"whenToUse": "Use browser sign-in for the provider-hosted MCP server.",
|
||||
"defaults": {
|
||||
"serverUrl": "https://coda.io/apis/mcp",
|
||||
"scopesHint": [
|
||||
"mcp:all"
|
||||
]
|
||||
},
|
||||
"guidanceMd": "Connect Coda in the browser. A Coda account; the hosted MCP service is currently beta.",
|
||||
"riskTier": "S3",
|
||||
"label": "Sign in with Coda",
|
||||
"consoleLinks": {
|
||||
"docs": "https://help.coda.io/hc/en-us/articles/44722769665549-Security-recommendations-for-the-Coda-MCP"
|
||||
},
|
||||
"warnings": [
|
||||
"A Coda account; the hosted MCP service is currently beta.",
|
||||
"This provider's hosted MCP server is currently beta or preview."
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "mcp-api-key",
|
||||
"transport": "mcp_remote",
|
||||
"auth": "api_key",
|
||||
"ownershipModes": [
|
||||
"customer"
|
||||
],
|
||||
"whenToUse": "Use a restricted customer-owned key when browser sign-in is not suitable.",
|
||||
"defaults": {
|
||||
"serverUrl": "https://coda.io/apis/mcp"
|
||||
},
|
||||
"guidanceMd": "Use a customer-created Coda key. A Coda account; the hosted MCP service is currently beta.",
|
||||
"riskTier": "S3",
|
||||
"label": "Use an API key",
|
||||
"credentialFields": [
|
||||
{
|
||||
"key": "authorization",
|
||||
"label": "Coda API key",
|
||||
"type": "password",
|
||||
"required": true,
|
||||
"placeholder": "Paste your Coda API token",
|
||||
"secret": true,
|
||||
"helperMd": "Modify documents/tables permitted by the account. Create a key with read and write permissions for these actions; Paperclip cannot increase an existing key’s permissions."
|
||||
}
|
||||
],
|
||||
"keyPlacement": {
|
||||
"location": "header",
|
||||
"name": "Authorization",
|
||||
"prefix": "Bearer "
|
||||
},
|
||||
"consoleLinks": {
|
||||
"keys": "https://help.coda.io/hc/en-us/articles/44722769665549-Security-recommendations-for-the-Coda-MCP",
|
||||
"docs": "https://help.coda.io/hc/en-us/articles/44722769665549-Security-recommendations-for-the-Coda-MCP"
|
||||
},
|
||||
"warnings": [
|
||||
"A Coda account; the hosted MCP service is currently beta.",
|
||||
"This provider's hosted MCP server is currently beta or preview."
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"slug": "cognee",
|
||||
"name": "Cognee",
|
||||
"description": "Build and recall shared graph memory from documents and conversations.",
|
||||
"categories": [
|
||||
"ai"
|
||||
],
|
||||
"branding": {
|
||||
"logoUrl": "/brands/apps/cognee.png"
|
||||
},
|
||||
"urlPatterns": [
|
||||
"https://*.aws.cognee.ai/*"
|
||||
],
|
||||
"docsUrl": "https://docs.cognee.ai/cognee-cloud/connections/cloud-mcp",
|
||||
"setupPrerequisite": {
|
||||
"title": "Cognee Cloud and a local runtime",
|
||||
"description": "Use your Cognee Cloud tenant API URL and key. Paperclip's runtime host needs uv installed to run the official Cognee MCP client. Public deployments require a trusted MCP runtime host.",
|
||||
"actionLabel": "Open Cognee API keys",
|
||||
"actionUrl": "https://platform.cognee.ai/api-keys"
|
||||
},
|
||||
"methods": [
|
||||
{
|
||||
"key": "cloud-local",
|
||||
"transport": "local_stdio",
|
||||
"auth": "api_key",
|
||||
"ownershipModes": [
|
||||
"customer"
|
||||
],
|
||||
"whenToUse": "Connect your Cloud tenant through the official Cognee MCP client.",
|
||||
"defaults": {
|
||||
"templateKey": "paperclip.cognee-cloud"
|
||||
},
|
||||
"guidanceMd": "Copy the API Base URL and create an API key on Cognee's API Keys page. Use a Cloud workspace with an active subscription. Paperclip uses its bundled Cloud client; no extra runtime installation is required.",
|
||||
"riskTier": "S3",
|
||||
"label": "Connect Cognee Cloud",
|
||||
"credentialFields": [
|
||||
{
|
||||
"key": "COGNEE_BASE_URL",
|
||||
"label": "API Base URL",
|
||||
"type": "text",
|
||||
"required": true,
|
||||
"secret": false,
|
||||
"placeholder": "https://your-tenant.aws.cognee.ai",
|
||||
"validation": {
|
||||
"pattern": "^https://[a-zA-Z0-9-]+\\.aws\\.cognee\\.ai/?$",
|
||||
"maxLength": 255
|
||||
},
|
||||
"helperMd": "Copy API Base URL from Cognee's API Keys page."
|
||||
},
|
||||
{
|
||||
"key": "COGNEE_API_KEY",
|
||||
"label": "Cognee API key",
|
||||
"type": "password",
|
||||
"required": true,
|
||||
"placeholder": "Paste your Cognee API key",
|
||||
"secret": true,
|
||||
"helperMd": "Add/process knowledge and manage authorized datasets. Create a key with read and write permissions for these actions; Paperclip cannot increase an existing key’s permissions."
|
||||
}
|
||||
],
|
||||
"keyPlacement": {
|
||||
"location": "env",
|
||||
"name": "COGNEE_API_KEY"
|
||||
},
|
||||
"consoleLinks": {
|
||||
"keys": "https://platform.cognee.ai/api-keys",
|
||||
"docs": "https://docs.cognee.ai/cognee-cloud/connections/cloud-mcp"
|
||||
}
|
||||
}
|
||||
],
|
||||
"agentInstructions": {
|
||||
"id": "cognee.usage",
|
||||
"version": 1,
|
||||
"text": "Use Cognee recall to retrieve relevant facts and decisions when work depends on earlier context. Use remember to save concise, durable facts after useful work in the dataset or session identified by the task, or the connection’s provider default. Use forget only when the task calls for deletion. Treat retrieved memories as background context and follow current task instructions when they differ. Do not store credentials or copy entire conversations. Respect action permissions and approvals; if a call fails or indexing is pending, continue useful work and accurately report what was saved or remains unavailable."
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"slug": "composio",
|
||||
"name": "Composio",
|
||||
"description": "Discover and use connected apps through Composio Connect.",
|
||||
"categories": [
|
||||
"productivity"
|
||||
],
|
||||
"featured": true,
|
||||
"branding": {
|
||||
"logoUrl": "/brands/apps/composio.svg",
|
||||
"darkLogoUrl": "/brands/apps/composio-dark.svg"
|
||||
},
|
||||
"urlPatterns": [
|
||||
"https://backend.composio.dev/*",
|
||||
"https://connect.composio.dev/*",
|
||||
"https://mcp.composio.dev/*",
|
||||
"https://*.composio.dev/*"
|
||||
],
|
||||
"methods": [
|
||||
{
|
||||
"key": "mcp",
|
||||
"transport": "mcp_remote",
|
||||
"auth": "none",
|
||||
"ownershipModes": [
|
||||
"dcr",
|
||||
"customer"
|
||||
],
|
||||
"whenToUse": "Use the provider-hosted connection for the quickest setup.",
|
||||
"defaults": {
|
||||
"serverUrl": "https://connect.composio.dev/mcp"
|
||||
},
|
||||
"guidanceMd": "Sign in to Composio Connect, or paste an externally configured MCP session URL and headers.",
|
||||
"riskTier": "S3",
|
||||
"label": "Composio Connect"
|
||||
}
|
||||
],
|
||||
"docsUrl": "https://docs.composio.dev/docs/composio-connect"
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"slug": "context7",
|
||||
"name": "Context7",
|
||||
"description": "Search public library documentation and retrieve current code examples.",
|
||||
"categories": [
|
||||
"developer"
|
||||
],
|
||||
"featured": false,
|
||||
"branding": {
|
||||
"logoUrl": "/brands/apps/context7.svg",
|
||||
"darkLogoUrl": "/brands/apps/context7-dark.svg"
|
||||
},
|
||||
"urlPatterns": [
|
||||
"https://mcp.context7.com/*"
|
||||
],
|
||||
"methods": [
|
||||
{
|
||||
"key": "mcp",
|
||||
"transport": "mcp_remote",
|
||||
"auth": "none",
|
||||
"ownershipModes": [
|
||||
"customer"
|
||||
],
|
||||
"whenToUse": "Use the public hosted MCP server for read-only library documentation lookups.",
|
||||
"defaults": {
|
||||
"serverUrl": "https://mcp.context7.com/mcp"
|
||||
},
|
||||
"guidanceMd": "Connect Context7 to resolve public library IDs and retrieve current documentation. Anonymous access is supported with low rate limits; this method does not configure an API key. Keep queries limited to public documentation questions and do not include credentials, personal data, or proprietary code.",
|
||||
"riskTier": "S1",
|
||||
"consoleLinks": {
|
||||
"docs": "https://context7.com/docs/api-guide"
|
||||
},
|
||||
"warnings": [
|
||||
"Context7 processes the query text. Do not include secrets, personal information, or proprietary code.",
|
||||
"Anonymous requests have low rate limits. Provider documentation says an API key raises limits and supports private repositories; this public method does not configure a key."
|
||||
]
|
||||
}
|
||||
],
|
||||
"docsUrl": "https://context7.com/docs/api-guide"
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"slug": "discord",
|
||||
"name": "Discord",
|
||||
"description": "Let people start and continue Paperclip work with an agent from Discord.",
|
||||
"categories": [
|
||||
"communication"
|
||||
],
|
||||
"featured": false,
|
||||
"branding": {
|
||||
"logoUrl": "/brands/apps/discord.svg",
|
||||
"darkLogoUrl": "/brands/apps/discord-dark.svg"
|
||||
},
|
||||
"urlPatterns": [
|
||||
"https://discord.com/*"
|
||||
],
|
||||
"methods": [
|
||||
{
|
||||
"key": "chat-agent",
|
||||
"label": "Chat with an agent",
|
||||
"purpose": "channel",
|
||||
"provider": "discord",
|
||||
"transport": "chat_sdk",
|
||||
"auth": "api_key",
|
||||
"ownershipModes": [
|
||||
"customer"
|
||||
],
|
||||
"whenToUse": "Let people in Discord start and continue work with one Paperclip agent.",
|
||||
"credentialFields": [
|
||||
{
|
||||
"key": "botToken",
|
||||
"label": "Bot token",
|
||||
"type": "password",
|
||||
"required": true,
|
||||
"placeholder": "Paste the Discord bot token",
|
||||
"secret": true
|
||||
},
|
||||
{
|
||||
"key": "applicationId",
|
||||
"label": "Application ID",
|
||||
"type": "text",
|
||||
"required": true,
|
||||
"placeholder": "123456789012345678",
|
||||
"secret": false
|
||||
},
|
||||
{
|
||||
"key": "guildId",
|
||||
"label": "Server ID",
|
||||
"type": "text",
|
||||
"required": true,
|
||||
"placeholder": "123456789012345678",
|
||||
"secret": false
|
||||
}
|
||||
],
|
||||
"guidanceMd": "Create one dedicated Discord application and bot, enable the Message Content intent, install it in one server with the documented bot permissions, then connect its bot token, Application ID, and server ID. Paperclip starts one Discord thread per root bot mention and keeps the linked Paperclip task authoritative.",
|
||||
"consoleLinks": {
|
||||
"register": "https://discord.com/developers/applications",
|
||||
"docs": "https://discord.com/developers/docs/quick-start/getting-started"
|
||||
},
|
||||
"riskTier": "S3",
|
||||
"requiredResourceFilters": [
|
||||
"channel"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"slug": "egnyte",
|
||||
"name": "Egnyte",
|
||||
"description": "Connect Egnyte's provider-hosted MCP server.",
|
||||
"categories": [
|
||||
"content"
|
||||
],
|
||||
"featured": false,
|
||||
"branding": {
|
||||
"logoUrl": "/brands/apps/egnyte.svg"
|
||||
},
|
||||
"urlPatterns": [
|
||||
"https://mcp-server.egnyte.com/*"
|
||||
],
|
||||
"docsUrl": "https://developers.egnyte.com/docs/Remote_MCP_Server",
|
||||
"redirectConstraints": "https-or-loopback-http",
|
||||
"methods": [
|
||||
{
|
||||
"key": "mcp-oauth",
|
||||
"transport": "mcp_remote",
|
||||
"auth": "oauth",
|
||||
"ownershipModes": [
|
||||
"dcr"
|
||||
],
|
||||
"whenToUse": "Use browser sign-in for the provider-hosted MCP server.",
|
||||
"defaults": {
|
||||
"serverUrl": "https://mcp-server.egnyte.com/mcp"
|
||||
},
|
||||
"guidanceMd": "Connect Egnyte in the browser. An eligible Egnyte plan and administrator approval for external LLM access.",
|
||||
"riskTier": "S3",
|
||||
"label": "Sign in with Egnyte",
|
||||
"consoleLinks": {
|
||||
"docs": "https://developers.egnyte.com/docs/Remote_MCP_Server"
|
||||
},
|
||||
"warnings": [
|
||||
"An eligible Egnyte plan and administrator approval for external LLM access."
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"slug": "embat",
|
||||
"name": "Embat",
|
||||
"description": "Connect Embat's provider-hosted MCP server.",
|
||||
"categories": [
|
||||
"commerce"
|
||||
],
|
||||
"featured": false,
|
||||
"branding": {
|
||||
"logoUrl": "/brands/apps/embat.svg"
|
||||
},
|
||||
"urlPatterns": [
|
||||
"https://tellme.embat.io/*"
|
||||
],
|
||||
"docsUrl": "https://tellme.embat.io/.well-known/oauth-protected-resource/mcp",
|
||||
"redirectConstraints": "https-or-loopback-http",
|
||||
"methods": [
|
||||
{
|
||||
"key": "mcp-oauth",
|
||||
"transport": "mcp_remote",
|
||||
"auth": "oauth",
|
||||
"ownershipModes": [
|
||||
"dcr"
|
||||
],
|
||||
"whenToUse": "Use browser sign-in for the provider-hosted MCP server.",
|
||||
"defaults": {
|
||||
"serverUrl": "https://tellme.embat.io/mcp",
|
||||
"scopesHint": [
|
||||
"email",
|
||||
"offline_access",
|
||||
"openid",
|
||||
"profile"
|
||||
]
|
||||
},
|
||||
"guidanceMd": "Connect Embat in the browser. An Embat account; pilot the connection because provider setup documentation is sparse.",
|
||||
"riskTier": "S4",
|
||||
"label": "Sign in with Embat",
|
||||
"consoleLinks": {
|
||||
"docs": "https://tellme.embat.io/.well-known/oauth-protected-resource/mcp"
|
||||
},
|
||||
"warnings": [
|
||||
"An Embat account; pilot the connection because provider setup documentation is sparse."
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"slug": "enterpret",
|
||||
"name": "Enterpret",
|
||||
"description": "Ask questions about your customer feedback and pull verbatim quotes with citations.",
|
||||
"categories": [
|
||||
"analytics"
|
||||
],
|
||||
"featured": false,
|
||||
"branding": {
|
||||
"logoUrl": "/brands/apps/enterpret.png"
|
||||
},
|
||||
"urlPatterns": [
|
||||
"https://wisdom-api.enterpret.com/*"
|
||||
],
|
||||
"methods": [
|
||||
{
|
||||
"key": "mcp-api-key",
|
||||
"transport": "mcp_remote",
|
||||
"auth": "api_key",
|
||||
"ownershipModes": [
|
||||
"customer"
|
||||
],
|
||||
"whenToUse": "Recommended. Use an organization auth token from Settings → Enterpret MCP. This is the primary, store-ready connection method.",
|
||||
"defaults": {
|
||||
"serverUrl": "https://wisdom-api.enterpret.com/server/mcp"
|
||||
},
|
||||
"guidanceMd": "Generate an auth token in Enterpret under Settings, Enterpret MCP, then paste it below. One token belongs to one Enterpret organization. This is the recommended connection method.",
|
||||
"riskTier": "S3",
|
||||
"label": "Use an auth token",
|
||||
"grantKinds": [
|
||||
"organization"
|
||||
],
|
||||
"credentialFields": [
|
||||
{
|
||||
"key": "authorization",
|
||||
"label": "Enterpret auth token",
|
||||
"type": "password",
|
||||
"required": true,
|
||||
"placeholder": "Paste the token from Settings, Enterpret MCP",
|
||||
"secret": true,
|
||||
"helperMd": "An organization auth token can read that organization's customer feedback. Use a token from Settings > Enterpret MCP and check its displayed expiry there."
|
||||
}
|
||||
],
|
||||
"keyPlacement": {
|
||||
"location": "header",
|
||||
"name": "Authorization",
|
||||
"prefix": "Bearer "
|
||||
},
|
||||
"consoleLinks": {
|
||||
"docs": "https://enterpret.support.site/article/enterpret-mcp-server"
|
||||
},
|
||||
"warnings": [
|
||||
"Check your auth token's expiry in Enterpret Settings > Enterpret MCP and replace it before it lapses.",
|
||||
"This connection reads customer feedback, including verbatim quotes with speaker attribution.",
|
||||
"run_graph_query starts as Ask first. Cypher is not established as read-only even when Enterpret advertises readOnlyHint."
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "mcp-oauth",
|
||||
"transport": "mcp_remote",
|
||||
"auth": "oauth",
|
||||
"ownershipModes": [
|
||||
"dcr"
|
||||
],
|
||||
"whenToUse": "Use browser sign-in when each person should query feedback under their own Enterpret account.",
|
||||
"defaults": {
|
||||
"serverUrl": "https://wisdom-api.enterpret.com/server/mcp",
|
||||
"scopesHint": [
|
||||
"mcp:read"
|
||||
]
|
||||
},
|
||||
"guidanceMd": "Sign in to Enterpret in the browser to query the official read-only MCP. Each person connects with their own Enterpret account, and Enterpret attributes their queries individually.",
|
||||
"riskTier": "S3",
|
||||
"label": "Sign in with Enterpret",
|
||||
"grantKinds": [
|
||||
"user"
|
||||
],
|
||||
"consoleLinks": {
|
||||
"docs": "https://enterpret.support.site/article/enterpret-mcp-server"
|
||||
},
|
||||
"warnings": [
|
||||
"The official Enterpret MCP is read-only. Enterpret previously reported broader OAuth scopes, including mcp:write and email, than Paperclip requested. Enterpret is correcting this scope reporting; it does not establish access to the separate beta Agent MCP.",
|
||||
"After revocation, Enterpret may cache token validity for up to 24 hours. Disconnect this connection to stop Paperclip access immediately. Enterpret is reducing this delay.",
|
||||
"You need an Enterpret account with access to your organization's feedback.",
|
||||
"This connection reads customer feedback, including verbatim quotes with speaker attribution."
|
||||
]
|
||||
}
|
||||
],
|
||||
"docsUrl": "https://enterpret.support.site/article/enterpret-mcp-server",
|
||||
"redirectConstraints": "https-or-loopback-http"
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"slug": "exa",
|
||||
"name": "Exa",
|
||||
"description": "Search the public web and read source pages with Exa.",
|
||||
"categories": [
|
||||
"data"
|
||||
],
|
||||
"featured": false,
|
||||
"branding": {
|
||||
"logoUrl": "/brands/apps/exa.svg"
|
||||
},
|
||||
"urlPatterns": [
|
||||
"https://mcp.exa.ai/*"
|
||||
],
|
||||
"docsUrl": "https://exa.ai/docs/get-started/exa-mcp",
|
||||
"methods": [
|
||||
{
|
||||
"key": "public-search",
|
||||
"label": "Use public Exa search",
|
||||
"transport": "mcp_remote",
|
||||
"auth": "none",
|
||||
"ownershipModes": [
|
||||
"customer"
|
||||
],
|
||||
"whenToUse": "Use Exa’s public, keyless search MCP.",
|
||||
"defaults": {
|
||||
"serverUrl": "https://mcp.exa.ai/mcp"
|
||||
},
|
||||
"guidanceMd": "The public MCP route provides web search and page fetching. Keep citations with results. Account-authenticated Agent and advanced tools are not included in this method.",
|
||||
"riskTier": "S2"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"slug": "executor",
|
||||
"name": "Executor",
|
||||
"description": "Use the tools exposed by your Executor MCP connection.",
|
||||
"categories": [
|
||||
"productivity"
|
||||
],
|
||||
"featured": true,
|
||||
"branding": {
|
||||
"logoUrl": "/brands/apps/executor.png"
|
||||
},
|
||||
"urlPatterns": [
|
||||
"https://executor.sh/*"
|
||||
],
|
||||
"methods": [
|
||||
{
|
||||
"key": "mcp",
|
||||
"transport": "mcp_remote",
|
||||
"auth": "none",
|
||||
"ownershipModes": [
|
||||
"dcr",
|
||||
"customer"
|
||||
],
|
||||
"whenToUse": "Use the provider-hosted connection for the quickest setup.",
|
||||
"defaults": {},
|
||||
"guidanceMd": "Paste your Executor MCP URL. Sign in if required, or add a token or headers under Advanced authentication.",
|
||||
"riskTier": "S3",
|
||||
"label": "Connect MCP server",
|
||||
"consoleLinks": {
|
||||
"docs": "https://executor.sh/docs/mcp-proxy"
|
||||
}
|
||||
}
|
||||
],
|
||||
"docsUrl": "https://executor.sh/docs/mcp-proxy"
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"slug": "firecrawl",
|
||||
"name": "Firecrawl",
|
||||
"description": "Search, scrape, and parse public web pages with Firecrawl.",
|
||||
"categories": [
|
||||
"data"
|
||||
],
|
||||
"featured": false,
|
||||
"branding": {
|
||||
"logoUrl": "/brands/apps/firecrawl.svg"
|
||||
},
|
||||
"urlPatterns": [
|
||||
"https://mcp.firecrawl.dev/*"
|
||||
],
|
||||
"docsUrl": "https://docs.firecrawl.dev/mcp-server",
|
||||
"redirectConstraints": "https-or-loopback-http",
|
||||
"methods": [
|
||||
{
|
||||
"key": "mcp-oauth",
|
||||
"label": "Sign in with Firecrawl",
|
||||
"transport": "mcp_remote",
|
||||
"auth": "oauth",
|
||||
"ownershipModes": [
|
||||
"dcr"
|
||||
],
|
||||
"whenToUse": "Use interactive Firecrawl account authorization for the full hosted MCP tool surface.",
|
||||
"defaults": {
|
||||
"serverUrl": "https://mcp.firecrawl.dev/v2/mcp-oauth",
|
||||
"scopesHint": [
|
||||
"firecrawl:global",
|
||||
"offline_access"
|
||||
]
|
||||
},
|
||||
"guidanceMd": "Sign in and choose the Firecrawl team whose usage should apply. This route exposes the account-authorized tool surface; review paid jobs and action policies before execution.",
|
||||
"riskTier": "S4",
|
||||
"warnings": [
|
||||
"Account authorization exposes Firecrawl’s broader hosted MCP surface, including job-based research tools that may use account credits.",
|
||||
"Crawls and research jobs can use provider credits; review their tool actions and limits before execution."
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "mcp-api-key",
|
||||
"label": "Use a Firecrawl API key",
|
||||
"transport": "mcp_remote",
|
||||
"auth": "api_key",
|
||||
"ownershipModes": [
|
||||
"customer"
|
||||
],
|
||||
"whenToUse": "Use a customer-created Firecrawl API key in a Bearer header for unattended or higher-limit access.",
|
||||
"defaults": {
|
||||
"serverUrl": "https://mcp.firecrawl.dev/v2/mcp"
|
||||
},
|
||||
"guidanceMd": "Create a Firecrawl API key for the intended team, then connect it with a Bearer header. The authenticated server may expose additional job tools and spend team credits.",
|
||||
"riskTier": "S4",
|
||||
"credentialFields": [
|
||||
{
|
||||
"key": "authorization",
|
||||
"label": "Use a Firecrawl API key",
|
||||
"type": "password",
|
||||
"required": true,
|
||||
"placeholder": "Enter provider credential",
|
||||
"secret": true,
|
||||
"helperMd": "Create an API key for the intended Firecrawl team with access to the search, scrape, parse, and any job tools you intend to use. Jobs can consume credits; Paperclip cannot increase an existing key’s permissions."
|
||||
}
|
||||
],
|
||||
"keyPlacement": {
|
||||
"location": "header",
|
||||
"name": "Authorization",
|
||||
"prefix": "Bearer "
|
||||
},
|
||||
"consoleLinks": {
|
||||
"keys": "https://docs.firecrawl.dev/mcp-server",
|
||||
"docs": "https://docs.firecrawl.dev/mcp-server"
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "keyless",
|
||||
"label": "Try keyless access",
|
||||
"transport": "mcp_remote",
|
||||
"auth": "none",
|
||||
"ownershipModes": [
|
||||
"customer"
|
||||
],
|
||||
"whenToUse": "Use the rate-limited keyless public web-search route without a Firecrawl credential.",
|
||||
"defaults": {
|
||||
"serverUrl": "https://mcp.firecrawl.dev/v2/mcp"
|
||||
},
|
||||
"guidanceMd": "The keyless route currently exposes Search, Scrape, and Parse. Keep requests bounded; these calls can consume the provider’s keyless allowance.",
|
||||
"riskTier": "S2"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"slug": "fireflies",
|
||||
"name": "Fireflies",
|
||||
"description": "Search meeting transcripts, read summaries and action items, and connect meeting-ready routines.",
|
||||
"categories": [
|
||||
"productivity"
|
||||
],
|
||||
"featured": false,
|
||||
"branding": {
|
||||
"logoUrl": "/brands/apps/fireflies.svg"
|
||||
},
|
||||
"urlPatterns": [
|
||||
"https://api.fireflies.ai/*"
|
||||
],
|
||||
"docsUrl": "https://docs.fireflies.ai/getting-started/mcp-configuration",
|
||||
"redirectConstraints": "https-or-loopback-http",
|
||||
"methods": [
|
||||
{
|
||||
"key": "mcp-oauth",
|
||||
"transport": "mcp_remote",
|
||||
"auth": "oauth",
|
||||
"ownershipModes": [
|
||||
"dcr"
|
||||
],
|
||||
"whenToUse": "Use browser sign-in for the provider-hosted MCP server.",
|
||||
"defaults": {
|
||||
"serverUrl": "https://api.fireflies.ai/mcp",
|
||||
"scopesHint": [
|
||||
"email",
|
||||
"profile"
|
||||
]
|
||||
},
|
||||
"guidanceMd": "Sign in to Fireflies to use meeting transcripts, summaries, and action items. Configure optional summary-ready webhooks separately in a routine's Triggers tab.",
|
||||
"riskTier": "S3",
|
||||
"label": "Sign in with Fireflies",
|
||||
"consoleLinks": {
|
||||
"docs": "https://docs.fireflies.ai/getting-started/mcp-configuration"
|
||||
},
|
||||
"warnings": [
|
||||
"A Fireflies account with access to the meetings you want to use. API keys are available in Settings → Developer Settings."
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "mcp-api-key",
|
||||
"transport": "mcp_remote",
|
||||
"auth": "api_key",
|
||||
"ownershipModes": [
|
||||
"customer"
|
||||
],
|
||||
"whenToUse": "Use your Fireflies API key instead of browser sign-in.",
|
||||
"defaults": {
|
||||
"serverUrl": "https://api.fireflies.ai/mcp"
|
||||
},
|
||||
"guidanceMd": "Open Fireflies Settings → Developer Settings, copy your API key, and paste it below. This key accesses your meeting data; routine webhooks use a separate signing secret.",
|
||||
"riskTier": "S3",
|
||||
"label": "Use an API key",
|
||||
"credentialFields": [
|
||||
{
|
||||
"key": "authorization",
|
||||
"label": "Fireflies API key",
|
||||
"type": "password",
|
||||
"required": true,
|
||||
"placeholder": "Paste your Fireflies API key",
|
||||
"secret": true,
|
||||
"helperMd": "Use an API key for an account with write access to the meetings your agents need to share, rename, move or turn into soundbites. Paperclip cannot increase the key’s permissions."
|
||||
}
|
||||
],
|
||||
"keyPlacement": {
|
||||
"location": "header",
|
||||
"name": "Authorization",
|
||||
"prefix": "Bearer "
|
||||
},
|
||||
"consoleLinks": {
|
||||
"keys": "https://app.fireflies.ai/settings",
|
||||
"docs": "https://docs.fireflies.ai/getting-started/mcp-configuration"
|
||||
},
|
||||
"warnings": [
|
||||
"A Fireflies account with access to the meetings you want to use. API keys are available in Settings → Developer Settings."
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"slug": "gauge",
|
||||
"name": "Gauge",
|
||||
"description": "Track how AI answers mention your brand, research keywords and traffic, and run content workflows.",
|
||||
"categories": [
|
||||
"analytics"
|
||||
],
|
||||
"featured": false,
|
||||
"branding": {
|
||||
"logoUrl": "/brands/apps/gauge.png"
|
||||
},
|
||||
"urlPatterns": [
|
||||
"https://app.withgauge.com/*"
|
||||
],
|
||||
"docsUrl": "https://docs.withgauge.com/help/mcp",
|
||||
"redirectConstraints": "https-or-loopback-http",
|
||||
"methods": [
|
||||
{
|
||||
"key": "mcp-oauth",
|
||||
"transport": "mcp_remote",
|
||||
"auth": "oauth",
|
||||
"ownershipModes": [
|
||||
"dcr"
|
||||
],
|
||||
"whenToUse": "Use browser sign-in for the provider-hosted MCP server.",
|
||||
"defaults": {
|
||||
"serverUrl": "https://app.withgauge.com/mcp",
|
||||
"scopesHint": [
|
||||
"mcp"
|
||||
]
|
||||
},
|
||||
"guidanceMd": "Connect Gauge in the browser and choose the organization this connection may use. Write tools start enabled and remain governed by Paperclip's action policies.",
|
||||
"riskTier": "S3",
|
||||
"label": "Sign in with Gauge",
|
||||
"consoleLinks": {
|
||||
"docs": "https://docs.withgauge.com/help/mcp"
|
||||
},
|
||||
"warnings": [
|
||||
"A Gauge account. Each connection reaches one Gauge organization: sign-in uses the organization chosen during consent, and an API key uses the organization that created it.",
|
||||
"Gauge content tools can publish to your connected CMS, including live. Set publish actions to Ask first before agents run unattended."
|
||||
],
|
||||
"capabilityProfile": {
|
||||
"key": "write",
|
||||
"label": "Read and write",
|
||||
"description": "Read AI visibility, SEO and traffic data, and run content workflows. Gauge content tools can publish to your connected CMS, including live. Set publish actions to Ask first before agents run unattended."
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "mcp-api-key",
|
||||
"transport": "mcp_remote",
|
||||
"auth": "api_key",
|
||||
"ownershipModes": [
|
||||
"customer"
|
||||
],
|
||||
"whenToUse": "Use a Gauge organization API key when browser sign-in is not suitable.",
|
||||
"defaults": {
|
||||
"serverUrl": "https://app.withgauge.com/mcp"
|
||||
},
|
||||
"guidanceMd": "In Gauge, open Settings → Integrations → API Keys, generate a key for Paperclip, and paste it below.",
|
||||
"riskTier": "S3",
|
||||
"label": "Use an API key",
|
||||
"credentialFields": [
|
||||
{
|
||||
"key": "authorization",
|
||||
"label": "Gauge API key",
|
||||
"type": "password",
|
||||
"required": true,
|
||||
"placeholder": "Paste your Gauge API key",
|
||||
"secret": true,
|
||||
"helperMd": "Gauge API keys are not scoped: a key reaches every MCP tool in the organization that created it, including publishing to a connected CMS, and requests are attributed to the organization rather than a user. Generate a separate key for Paperclip so it can be deleted independently, and set publish actions to Ask first before agents run unattended. Paperclip cannot narrow an existing key."
|
||||
}
|
||||
],
|
||||
"keyPlacement": {
|
||||
"location": "header",
|
||||
"name": "Authorization",
|
||||
"prefix": "Bearer "
|
||||
},
|
||||
"consoleLinks": {
|
||||
"keys": "https://docs.withgauge.com/help/mcp",
|
||||
"docs": "https://docs.withgauge.com/help/mcp"
|
||||
},
|
||||
"warnings": [
|
||||
"A Gauge account. Each connection reaches one Gauge organization: sign-in uses the organization chosen during consent, and an API key uses the organization that created it.",
|
||||
"Gauge content tools can publish to your connected CMS, including live. Set publish actions to Ask first before agents run unattended."
|
||||
],
|
||||
"capabilityProfile": {
|
||||
"key": "write",
|
||||
"label": "Read and write",
|
||||
"description": "Read AI visibility, SEO and traffic data, and run content workflows. Gauge content tools can publish to your connected CMS, including live. Set publish actions to Ask first before agents run unattended."
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"slug": "github-code-review-bot",
|
||||
"name": "GitHub Code Review Bot",
|
||||
"description": "Have an agent review pull requests and respond to GitHub mentions.",
|
||||
"categories": [
|
||||
"developer"
|
||||
],
|
||||
"featured": true,
|
||||
"branding": {
|
||||
"logoUrl": "/brands/apps/github.svg",
|
||||
"darkLogoUrl": "/brands/apps/github-dark.svg"
|
||||
},
|
||||
"urlPatterns": [],
|
||||
"methods": [
|
||||
{
|
||||
"key": "chat-agent",
|
||||
"label": "Chat with an agent",
|
||||
"purpose": "channel",
|
||||
"provider": "github",
|
||||
"transport": "chat_sdk",
|
||||
"auth": "api_key",
|
||||
"ownershipModes": [
|
||||
"customer"
|
||||
],
|
||||
"whenToUse": "Let people in GitHub start and continue work with one Paperclip agent.",
|
||||
"credentialFields": [
|
||||
{
|
||||
"key": "appId",
|
||||
"label": "GitHub App ID",
|
||||
"type": "text",
|
||||
"required": true,
|
||||
"placeholder": "123456",
|
||||
"secret": false
|
||||
},
|
||||
{
|
||||
"key": "privateKey",
|
||||
"label": "Private key (PEM)",
|
||||
"type": "textarea",
|
||||
"required": true,
|
||||
"placeholder": "-----BEGIN RSA PRIVATE KEY-----",
|
||||
"secret": true
|
||||
}
|
||||
],
|
||||
"guidanceMd": "Generate the webhook secret in Paperclip, then create one private GitHub App with active SSL-verified webhooks and Issues and Pull requests read/write permission. Subscribe to issue_comment and pull_request_review_comment for comment mentions; add issues and pull_request for description mentions, and pull_request_review for review-summary mentions. The wizard selects all five. GitHub sends installation and installation_repositories automatically. Install the App only on repositories where people may mention the agent.",
|
||||
"consoleLinks": {
|
||||
"register": "https://github.com/settings/apps/new",
|
||||
"docs": "https://docs.github.com/en/apps/creating-github-apps/registering-a-github-app/registering-a-github-app"
|
||||
},
|
||||
"riskTier": "S3",
|
||||
"requiredResourceFilters": [
|
||||
"organization",
|
||||
"repository"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"slug": "github",
|
||||
"name": "GitHub",
|
||||
"description": "Give agents access to GitHub repositories, issues, and pull requests.",
|
||||
"categories": [
|
||||
"developer"
|
||||
],
|
||||
"featured": true,
|
||||
"branding": {
|
||||
"logoUrl": "/brands/apps/github.svg",
|
||||
"darkLogoUrl": "/brands/apps/github-dark.svg"
|
||||
},
|
||||
"urlPatterns": [
|
||||
"https://api.githubcopilot.com/mcp/*",
|
||||
"https://github.com/*"
|
||||
],
|
||||
"methods": [
|
||||
{
|
||||
"key": "managed",
|
||||
"transport": "mcp_remote",
|
||||
"auth": "oauth",
|
||||
"ownershipModes": [
|
||||
"platform_shared"
|
||||
],
|
||||
"whenToUse": "Connect your GitHub account for durable MCP, shell Git, gh, and repository access.",
|
||||
"defaults": {
|
||||
"serverUrl": "https://api.githubcopilot.com/mcp/"
|
||||
},
|
||||
"guidanceMd": "Authorize Paperclip, then choose selected repositories in GitHub. You can edit repository access later from GitHub's installation settings.",
|
||||
"riskTier": "S3",
|
||||
"label": "Connect GitHub",
|
||||
"purpose": "tool",
|
||||
"oauthStrategy": "paperclip_cloud_connector",
|
||||
"connectorProfile": "github.code",
|
||||
"grantKinds": [
|
||||
"user",
|
||||
"agent"
|
||||
],
|
||||
"warnings": [
|
||||
"Shell Git and gh receive this identity for the run and are not constrained by per-tool Ask-first controls."
|
||||
],
|
||||
"requiredResourceFilters": [
|
||||
"organization",
|
||||
"repository"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "mcp-key",
|
||||
"transport": "mcp_remote",
|
||||
"auth": "api_key",
|
||||
"ownershipModes": [
|
||||
"customer"
|
||||
],
|
||||
"whenToUse": "Use the provider-hosted connection for the quickest setup.",
|
||||
"defaults": {
|
||||
"serverUrl": "https://api.githubcopilot.com/mcp/"
|
||||
},
|
||||
"guidanceMd": "Create a fine-grained token limited to the repositories agents should use.",
|
||||
"riskTier": "S3",
|
||||
"label": "Personal access token (advanced)",
|
||||
"purpose": "tool",
|
||||
"credentialFields": [
|
||||
{
|
||||
"key": "authorization",
|
||||
"label": "GitHub token",
|
||||
"type": "password",
|
||||
"required": true,
|
||||
"placeholder": "github_pat_...",
|
||||
"secret": true,
|
||||
"helperMd": "Repository contents, issues, pull requests and other granted toolsets; selected repos and installation/PAT permissions apply. Create a key with read and write permissions for these actions; Paperclip cannot increase an existing key’s permissions."
|
||||
}
|
||||
],
|
||||
"keyPlacement": {
|
||||
"location": "header",
|
||||
"name": "Authorization",
|
||||
"prefix": "Bearer "
|
||||
},
|
||||
"requiredResourceFilters": [
|
||||
"organization",
|
||||
"repository"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"slug": "gmail",
|
||||
"name": "Gmail",
|
||||
"description": "Search and read Gmail messages and create drafts without enabling mail sending.",
|
||||
"categories": [
|
||||
"communication",
|
||||
"productivity"
|
||||
],
|
||||
"featured": true,
|
||||
"branding": {
|
||||
"logoUrl": "/brands/apps/gmail.svg"
|
||||
},
|
||||
"urlPatterns": [
|
||||
"https://gmailmcp.googleapis.com/*"
|
||||
],
|
||||
"docsUrl": "https://developers.google.com/workspace/gmail/api/reference/mcp",
|
||||
"setupPrerequisite": {
|
||||
"title": "Google Developer Preview access required",
|
||||
"description": "Google must register both the Workspace email used to authorize Paperclip and the Google Cloud project that owns the OAuth client. Registration is limited to those emails and projects; it does not enable unrelated Paperclip customers.",
|
||||
"steps": [
|
||||
"Apply with the Workspace email that will sign in and the Google Cloud project that owns the OAuth client.",
|
||||
"Wait for the Google Group notification and then Google's final project-registration email, usually within a couple of days.",
|
||||
"Add every additional tester email or Cloud project through Google's member request forms before connecting."
|
||||
],
|
||||
"actionLabel": "Apply or verify Developer Preview enrollment",
|
||||
"actionUrl": "https://developers.google.com/workspace/preview"
|
||||
},
|
||||
"redirectConstraints": "https-or-loopback-http",
|
||||
"methods": [
|
||||
{
|
||||
"key": "paperclip-read",
|
||||
"label": "Connect with Paperclip",
|
||||
"transport": "mcp_remote",
|
||||
"auth": "oauth",
|
||||
"oauthStrategy": "paperclip_cloud_connector",
|
||||
"connectorProfile": "gmail.read",
|
||||
"capabilityProfile": {
|
||||
"key": "read",
|
||||
"label": "Read only",
|
||||
"description": "Search and read messages, threads, drafts, and labels."
|
||||
},
|
||||
"grantKinds": [
|
||||
"user",
|
||||
"organization"
|
||||
],
|
||||
"ownershipModes": [
|
||||
"platform_shared"
|
||||
],
|
||||
"whenToUse": "Use Paperclip-managed Google OAuth for read-only Gmail access.",
|
||||
"defaults": {
|
||||
"serverUrl": "https://gmailmcp.googleapis.com/mcp/v1",
|
||||
"scopesHint": [
|
||||
"https://www.googleapis.com/auth/gmail.readonly"
|
||||
]
|
||||
},
|
||||
"guidanceMd": "Connect Gmail with read-only access. Sending, deleting, moving, and relabeling mail are not enabled.",
|
||||
"warnings": [
|
||||
"Google Workspace MCP servers are in Developer Preview."
|
||||
],
|
||||
"riskTier": "S3"
|
||||
},
|
||||
{
|
||||
"key": "customer-read-oauth",
|
||||
"label": "Use your own Google OAuth app",
|
||||
"transport": "mcp_remote",
|
||||
"auth": "oauth",
|
||||
"capabilityProfile": {
|
||||
"key": "read",
|
||||
"label": "Read only",
|
||||
"description": "Search and read messages, threads, drafts, and labels."
|
||||
},
|
||||
"grantKinds": [
|
||||
"user",
|
||||
"organization"
|
||||
],
|
||||
"ownershipModes": [
|
||||
"customer"
|
||||
],
|
||||
"whenToUse": "Use a customer-owned Google OAuth client for read-only Gmail access.",
|
||||
"defaults": {
|
||||
"serverUrl": "https://gmailmcp.googleapis.com/mcp/v1",
|
||||
"authorizationEndpoint": "https://accounts.google.com/o/oauth2/v2/auth",
|
||||
"tokenEndpoint": "https://oauth2.googleapis.com/token",
|
||||
"metadataUrl": "https://accounts.google.com/.well-known/openid-configuration",
|
||||
"scopesHint": [
|
||||
"https://www.googleapis.com/auth/gmail.readonly"
|
||||
],
|
||||
"oauthAuthorizationParams": {
|
||||
"access_type": "offline",
|
||||
"prompt": "consent"
|
||||
}
|
||||
},
|
||||
"guidanceMd": "Enable the Gmail and Gmail MCP APIs in your Google Cloud project, then register Paperclip's callback URI.",
|
||||
"consoleLinks": {
|
||||
"register": "https://console.cloud.google.com/auth/clients",
|
||||
"docs": "https://developers.google.com/workspace/guides/configure-mcp-servers"
|
||||
},
|
||||
"warnings": [
|
||||
"Google Workspace MCP servers are in Developer Preview."
|
||||
],
|
||||
"riskTier": "S3"
|
||||
},
|
||||
{
|
||||
"key": "paperclip-draft",
|
||||
"label": "Connect with Paperclip",
|
||||
"transport": "mcp_remote",
|
||||
"auth": "oauth",
|
||||
"oauthStrategy": "paperclip_cloud_connector",
|
||||
"connectorProfile": "gmail.draft",
|
||||
"capabilityProfile": {
|
||||
"key": "draft",
|
||||
"label": "Read & create drafts",
|
||||
"description": "Read Gmail and create drafts for review in Gmail."
|
||||
},
|
||||
"grantKinds": [
|
||||
"user",
|
||||
"organization"
|
||||
],
|
||||
"ownershipModes": [
|
||||
"platform_shared"
|
||||
],
|
||||
"whenToUse": "Use Paperclip-managed Google OAuth to read Gmail and create drafts.",
|
||||
"defaults": {
|
||||
"serverUrl": "https://gmailmcp.googleapis.com/mcp/v1",
|
||||
"scopesHint": [
|
||||
"https://www.googleapis.com/auth/gmail.readonly",
|
||||
"https://www.googleapis.com/auth/gmail.compose"
|
||||
]
|
||||
},
|
||||
"guidanceMd": "Connect Gmail to search and read mail and create drafts. Sending mail is permanently disabled.",
|
||||
"warnings": [
|
||||
"Draft creation requires approval. Sending mail is not enabled.",
|
||||
"Google Workspace MCP servers are in Developer Preview."
|
||||
],
|
||||
"riskTier": "S4"
|
||||
},
|
||||
{
|
||||
"key": "customer-draft-oauth",
|
||||
"label": "Use your own Google OAuth app",
|
||||
"transport": "mcp_remote",
|
||||
"auth": "oauth",
|
||||
"capabilityProfile": {
|
||||
"key": "draft",
|
||||
"label": "Read & create drafts",
|
||||
"description": "Read Gmail and create drafts for review in Gmail."
|
||||
},
|
||||
"grantKinds": [
|
||||
"user",
|
||||
"organization"
|
||||
],
|
||||
"ownershipModes": [
|
||||
"customer"
|
||||
],
|
||||
"whenToUse": "Use a customer-owned Google OAuth client to read Gmail and create drafts.",
|
||||
"defaults": {
|
||||
"serverUrl": "https://gmailmcp.googleapis.com/mcp/v1",
|
||||
"authorizationEndpoint": "https://accounts.google.com/o/oauth2/v2/auth",
|
||||
"tokenEndpoint": "https://oauth2.googleapis.com/token",
|
||||
"metadataUrl": "https://accounts.google.com/.well-known/openid-configuration",
|
||||
"scopesHint": [
|
||||
"https://www.googleapis.com/auth/gmail.readonly",
|
||||
"https://www.googleapis.com/auth/gmail.compose"
|
||||
],
|
||||
"oauthAuthorizationParams": {
|
||||
"access_type": "offline",
|
||||
"prompt": "consent"
|
||||
}
|
||||
},
|
||||
"guidanceMd": "Enable the Gmail and Gmail MCP APIs in your Google Cloud project, then register Paperclip's callback URI.",
|
||||
"consoleLinks": {
|
||||
"register": "https://console.cloud.google.com/auth/clients",
|
||||
"docs": "https://developers.google.com/workspace/guides/configure-mcp-servers"
|
||||
},
|
||||
"warnings": [
|
||||
"Draft creation requires approval. Sending mail is not enabled.",
|
||||
"Google Workspace MCP servers are in Developer Preview."
|
||||
],
|
||||
"riskTier": "S4"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"slug": "google-calendar",
|
||||
"name": "Google Calendar",
|
||||
"description": "Read calendars and manage Google Calendar events.",
|
||||
"categories": [
|
||||
"productivity"
|
||||
],
|
||||
"featured": true,
|
||||
"branding": {
|
||||
"logoUrl": "/brands/apps/google-calendar.svg"
|
||||
},
|
||||
"urlPatterns": [
|
||||
"https://calendarmcp.googleapis.com/*",
|
||||
"https://calendar.google.com/*"
|
||||
],
|
||||
"docsUrl": "https://developers.google.com/workspace/calendar/api/reference/mcp",
|
||||
"setupPrerequisite": {
|
||||
"title": "Google Developer Preview access required",
|
||||
"description": "Google must register both the Workspace email used to authorize Paperclip and the Google Cloud project that owns the OAuth client. Registration is limited to those emails and projects; it does not enable unrelated Paperclip customers.",
|
||||
"steps": [
|
||||
"Apply with the Workspace email that will sign in and the Google Cloud project that owns the OAuth client.",
|
||||
"Wait for the Google Group notification and then Google's final project-registration email, usually within a couple of days.",
|
||||
"Add every additional tester email or Cloud project through Google's member request forms before connecting."
|
||||
],
|
||||
"actionLabel": "Apply or verify Developer Preview enrollment",
|
||||
"actionUrl": "https://developers.google.com/workspace/preview"
|
||||
},
|
||||
"redirectConstraints": "https-or-loopback-http",
|
||||
"methods": [
|
||||
{
|
||||
"key": "paperclip-read",
|
||||
"label": "Connect with Paperclip",
|
||||
"transport": "mcp_remote",
|
||||
"auth": "oauth",
|
||||
"oauthStrategy": "paperclip_cloud_connector",
|
||||
"connectorProfile": "calendar.read",
|
||||
"capabilityProfile": {
|
||||
"key": "read",
|
||||
"label": "Read only",
|
||||
"description": "Read calendars, events, and availability."
|
||||
},
|
||||
"grantKinds": [
|
||||
"user",
|
||||
"organization"
|
||||
],
|
||||
"ownershipModes": [
|
||||
"platform_shared"
|
||||
],
|
||||
"whenToUse": "Use Paperclip-managed OAuth for read-only Calendar access.",
|
||||
"defaults": {
|
||||
"serverUrl": "https://calendarmcp.googleapis.com/mcp/v1",
|
||||
"scopesHint": [
|
||||
"https://www.googleapis.com/auth/calendar.calendarlist.readonly",
|
||||
"https://www.googleapis.com/auth/calendar.events.freebusy",
|
||||
"https://www.googleapis.com/auth/calendar.events.readonly"
|
||||
]
|
||||
},
|
||||
"guidanceMd": "Connect Google Calendar to read schedules and availability.",
|
||||
"warnings": [
|
||||
"Google Workspace MCP servers are in Developer Preview."
|
||||
],
|
||||
"riskTier": "S3"
|
||||
},
|
||||
{
|
||||
"key": "customer-read-oauth",
|
||||
"label": "Use your own Google OAuth app",
|
||||
"transport": "mcp_remote",
|
||||
"auth": "oauth",
|
||||
"capabilityProfile": {
|
||||
"key": "read",
|
||||
"label": "Read only",
|
||||
"description": "Read calendars, events, and availability."
|
||||
},
|
||||
"grantKinds": [
|
||||
"user",
|
||||
"organization"
|
||||
],
|
||||
"ownershipModes": [
|
||||
"customer"
|
||||
],
|
||||
"whenToUse": "Use a customer-owned OAuth client for read-only Calendar access.",
|
||||
"defaults": {
|
||||
"serverUrl": "https://calendarmcp.googleapis.com/mcp/v1",
|
||||
"authorizationEndpoint": "https://accounts.google.com/o/oauth2/v2/auth",
|
||||
"tokenEndpoint": "https://oauth2.googleapis.com/token",
|
||||
"metadataUrl": "https://accounts.google.com/.well-known/openid-configuration",
|
||||
"scopesHint": [
|
||||
"https://www.googleapis.com/auth/calendar.calendarlist.readonly",
|
||||
"https://www.googleapis.com/auth/calendar.events.freebusy",
|
||||
"https://www.googleapis.com/auth/calendar.events.readonly"
|
||||
],
|
||||
"oauthAuthorizationParams": {
|
||||
"access_type": "offline",
|
||||
"prompt": "consent"
|
||||
}
|
||||
},
|
||||
"guidanceMd": "Enable the Calendar and Calendar MCP APIs, then register Paperclip's callback URI.",
|
||||
"consoleLinks": {
|
||||
"register": "https://console.cloud.google.com/auth/clients",
|
||||
"docs": "https://developers.google.com/workspace/guides/configure-mcp-servers"
|
||||
},
|
||||
"warnings": [
|
||||
"Google Workspace MCP servers are in Developer Preview."
|
||||
],
|
||||
"riskTier": "S3"
|
||||
},
|
||||
{
|
||||
"key": "paperclip-write",
|
||||
"label": "Connect with Paperclip",
|
||||
"transport": "mcp_remote",
|
||||
"auth": "oauth",
|
||||
"oauthStrategy": "paperclip_cloud_connector",
|
||||
"connectorProfile": "calendar.write",
|
||||
"capabilityProfile": {
|
||||
"key": "write",
|
||||
"label": "Read & manage",
|
||||
"description": "Create, update, respond to, and delete events."
|
||||
},
|
||||
"grantKinds": [
|
||||
"user",
|
||||
"organization"
|
||||
],
|
||||
"ownershipModes": [
|
||||
"platform_shared"
|
||||
],
|
||||
"whenToUse": "Use Paperclip-managed OAuth to read and manage Calendar events.",
|
||||
"defaults": {
|
||||
"serverUrl": "https://calendarmcp.googleapis.com/mcp/v1",
|
||||
"scopesHint": [
|
||||
"https://www.googleapis.com/auth/calendar.calendarlist.readonly",
|
||||
"https://www.googleapis.com/auth/calendar.events"
|
||||
]
|
||||
},
|
||||
"guidanceMd": "Connect Google Calendar to read and manage events.",
|
||||
"warnings": [
|
||||
"All event mutations require approval.",
|
||||
"Google Workspace MCP servers are in Developer Preview."
|
||||
],
|
||||
"riskTier": "S4"
|
||||
},
|
||||
{
|
||||
"key": "customer-write-oauth",
|
||||
"label": "Use your own Google OAuth app",
|
||||
"transport": "mcp_remote",
|
||||
"auth": "oauth",
|
||||
"capabilityProfile": {
|
||||
"key": "write",
|
||||
"label": "Read & manage",
|
||||
"description": "Create, update, respond to, and delete events."
|
||||
},
|
||||
"grantKinds": [
|
||||
"user",
|
||||
"organization"
|
||||
],
|
||||
"ownershipModes": [
|
||||
"customer"
|
||||
],
|
||||
"whenToUse": "Use a customer-owned OAuth client to read and manage Calendar events.",
|
||||
"defaults": {
|
||||
"serverUrl": "https://calendarmcp.googleapis.com/mcp/v1",
|
||||
"authorizationEndpoint": "https://accounts.google.com/o/oauth2/v2/auth",
|
||||
"tokenEndpoint": "https://oauth2.googleapis.com/token",
|
||||
"metadataUrl": "https://accounts.google.com/.well-known/openid-configuration",
|
||||
"scopesHint": [
|
||||
"https://www.googleapis.com/auth/calendar.calendarlist.readonly",
|
||||
"https://www.googleapis.com/auth/calendar.events"
|
||||
],
|
||||
"oauthAuthorizationParams": {
|
||||
"access_type": "offline",
|
||||
"prompt": "consent"
|
||||
}
|
||||
},
|
||||
"guidanceMd": "Enable the Calendar and Calendar MCP APIs, then register Paperclip's callback URI.",
|
||||
"consoleLinks": {
|
||||
"register": "https://console.cloud.google.com/auth/clients",
|
||||
"docs": "https://developers.google.com/workspace/guides/configure-mcp-servers"
|
||||
},
|
||||
"warnings": [
|
||||
"All event mutations require approval.",
|
||||
"Google Workspace MCP servers are in Developer Preview."
|
||||
],
|
||||
"riskTier": "S4"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"slug": "google-chat",
|
||||
"name": "Google Chat",
|
||||
"description": "Search and read Google Chat conversations and send messages.",
|
||||
"categories": [
|
||||
"communication",
|
||||
"productivity"
|
||||
],
|
||||
"featured": false,
|
||||
"branding": {
|
||||
"logoUrl": "/brands/apps/google-chat.svg"
|
||||
},
|
||||
"urlPatterns": [
|
||||
"https://chatmcp.googleapis.com/*",
|
||||
"https://chat.google.com/*"
|
||||
],
|
||||
"docsUrl": "https://developers.google.com/workspace/chat/api/reference/mcp",
|
||||
"setupPrerequisite": {
|
||||
"title": "Google Developer Preview access required",
|
||||
"description": "Google must register both the Workspace email used to authorize Paperclip and the Google Cloud project that owns the OAuth client. Registration is limited to those emails and projects; it does not enable unrelated Paperclip customers.",
|
||||
"steps": [
|
||||
"Apply with the Workspace email that will sign in and the Google Cloud project that owns the OAuth client.",
|
||||
"Wait for the Google Group notification and then Google's final project-registration email, usually within a couple of days.",
|
||||
"Add every additional tester email or Cloud project through Google's member request forms before connecting."
|
||||
],
|
||||
"actionLabel": "Apply or verify Developer Preview enrollment",
|
||||
"actionUrl": "https://developers.google.com/workspace/preview"
|
||||
},
|
||||
"redirectConstraints": "https-or-loopback-http",
|
||||
"methods": [
|
||||
{
|
||||
"key": "paperclip-read",
|
||||
"label": "Connect with Paperclip",
|
||||
"transport": "mcp_remote",
|
||||
"auth": "oauth",
|
||||
"oauthStrategy": "paperclip_cloud_connector",
|
||||
"connectorProfile": "chat.read",
|
||||
"capabilityProfile": {
|
||||
"key": "read",
|
||||
"label": "Read only",
|
||||
"description": "Search conversations and read messages."
|
||||
},
|
||||
"grantKinds": [
|
||||
"user",
|
||||
"organization"
|
||||
],
|
||||
"ownershipModes": [
|
||||
"platform_shared"
|
||||
],
|
||||
"whenToUse": "Use Paperclip-managed OAuth for read-only Chat access.",
|
||||
"defaults": {
|
||||
"serverUrl": "https://chatmcp.googleapis.com/mcp/v1",
|
||||
"scopesHint": [
|
||||
"https://www.googleapis.com/auth/chat.spaces.readonly",
|
||||
"https://www.googleapis.com/auth/chat.messages.readonly"
|
||||
]
|
||||
},
|
||||
"guidanceMd": "Connect Google Chat to search conversations and read messages. Read/unread filtering and membership listing are not supported.",
|
||||
"warnings": [
|
||||
"Google Workspace MCP servers are in Developer Preview."
|
||||
],
|
||||
"riskTier": "S3"
|
||||
},
|
||||
{
|
||||
"key": "customer-read-oauth",
|
||||
"label": "Use your own Google OAuth app",
|
||||
"transport": "mcp_remote",
|
||||
"auth": "oauth",
|
||||
"capabilityProfile": {
|
||||
"key": "read",
|
||||
"label": "Read only",
|
||||
"description": "Search conversations and read messages."
|
||||
},
|
||||
"grantKinds": [
|
||||
"user",
|
||||
"organization"
|
||||
],
|
||||
"ownershipModes": [
|
||||
"customer"
|
||||
],
|
||||
"whenToUse": "Use a customer-owned OAuth client for read-only Chat access.",
|
||||
"defaults": {
|
||||
"serverUrl": "https://chatmcp.googleapis.com/mcp/v1",
|
||||
"authorizationEndpoint": "https://accounts.google.com/o/oauth2/v2/auth",
|
||||
"tokenEndpoint": "https://oauth2.googleapis.com/token",
|
||||
"metadataUrl": "https://accounts.google.com/.well-known/openid-configuration",
|
||||
"scopesHint": [
|
||||
"https://www.googleapis.com/auth/chat.spaces.readonly",
|
||||
"https://www.googleapis.com/auth/chat.messages.readonly"
|
||||
],
|
||||
"oauthAuthorizationParams": {
|
||||
"access_type": "offline",
|
||||
"prompt": "consent"
|
||||
}
|
||||
},
|
||||
"guidanceMd": "Enable the Chat and Chat MCP APIs, configure a Chat app, then register Paperclip's callback URI. Read/unread filtering and membership listing are not supported.",
|
||||
"consoleLinks": {
|
||||
"register": "https://console.cloud.google.com/auth/clients",
|
||||
"docs": "https://developers.google.com/workspace/guides/configure-mcp-servers"
|
||||
},
|
||||
"warnings": [
|
||||
"A Google Chat app must be configured in the Cloud project.",
|
||||
"Google Workspace MCP servers are in Developer Preview."
|
||||
],
|
||||
"riskTier": "S3"
|
||||
},
|
||||
{
|
||||
"key": "paperclip-write",
|
||||
"label": "Connect with Paperclip",
|
||||
"transport": "mcp_remote",
|
||||
"auth": "oauth",
|
||||
"oauthStrategy": "paperclip_cloud_connector",
|
||||
"connectorProfile": "chat.write",
|
||||
"capabilityProfile": {
|
||||
"key": "write",
|
||||
"label": "Read & send",
|
||||
"description": "Read Chat and send messages."
|
||||
},
|
||||
"grantKinds": [
|
||||
"user",
|
||||
"organization"
|
||||
],
|
||||
"ownershipModes": [
|
||||
"platform_shared"
|
||||
],
|
||||
"whenToUse": "Use Paperclip-managed OAuth to read Chat and send messages.",
|
||||
"defaults": {
|
||||
"serverUrl": "https://chatmcp.googleapis.com/mcp/v1",
|
||||
"scopesHint": [
|
||||
"https://www.googleapis.com/auth/chat.spaces.readonly",
|
||||
"https://www.googleapis.com/auth/chat.messages.readonly",
|
||||
"https://www.googleapis.com/auth/chat.messages.create"
|
||||
]
|
||||
},
|
||||
"guidanceMd": "Connect Google Chat to read conversations and send approved messages. Read/unread filtering and membership listing are not supported.",
|
||||
"warnings": [
|
||||
"Sending messages requires approval.",
|
||||
"Google Workspace MCP servers are in Developer Preview."
|
||||
],
|
||||
"riskTier": "S4"
|
||||
},
|
||||
{
|
||||
"key": "customer-write-oauth",
|
||||
"label": "Use your own Google OAuth app",
|
||||
"transport": "mcp_remote",
|
||||
"auth": "oauth",
|
||||
"capabilityProfile": {
|
||||
"key": "write",
|
||||
"label": "Read & send",
|
||||
"description": "Read Chat and send messages."
|
||||
},
|
||||
"grantKinds": [
|
||||
"user",
|
||||
"organization"
|
||||
],
|
||||
"ownershipModes": [
|
||||
"customer"
|
||||
],
|
||||
"whenToUse": "Use a customer-owned OAuth client to read Chat and send messages.",
|
||||
"defaults": {
|
||||
"serverUrl": "https://chatmcp.googleapis.com/mcp/v1",
|
||||
"authorizationEndpoint": "https://accounts.google.com/o/oauth2/v2/auth",
|
||||
"tokenEndpoint": "https://oauth2.googleapis.com/token",
|
||||
"metadataUrl": "https://accounts.google.com/.well-known/openid-configuration",
|
||||
"scopesHint": [
|
||||
"https://www.googleapis.com/auth/chat.spaces.readonly",
|
||||
"https://www.googleapis.com/auth/chat.messages.readonly",
|
||||
"https://www.googleapis.com/auth/chat.messages.create"
|
||||
],
|
||||
"oauthAuthorizationParams": {
|
||||
"access_type": "offline",
|
||||
"prompt": "consent"
|
||||
}
|
||||
},
|
||||
"guidanceMd": "Enable the Chat and Chat MCP APIs, configure a Chat app, then register Paperclip's callback URI. Read/unread filtering and membership listing are not supported.",
|
||||
"consoleLinks": {
|
||||
"register": "https://console.cloud.google.com/auth/clients",
|
||||
"docs": "https://developers.google.com/workspace/guides/configure-mcp-servers"
|
||||
},
|
||||
"warnings": [
|
||||
"Sending messages requires approval.",
|
||||
"A Google Chat app must be configured in the Cloud project.",
|
||||
"Google Workspace MCP servers are in Developer Preview."
|
||||
],
|
||||
"riskTier": "S4"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"slug": "google-docs",
|
||||
"name": "Google Docs",
|
||||
"description": "Read and update Google Docs documents.",
|
||||
"categories": [
|
||||
"content",
|
||||
"productivity"
|
||||
],
|
||||
"featured": true,
|
||||
"branding": {
|
||||
"logoUrl": "/brands/apps/google-docs.svg"
|
||||
},
|
||||
"urlPatterns": [
|
||||
"https://docsmcp.googleapis.com/*",
|
||||
"https://docs.google.com/document/*"
|
||||
],
|
||||
"docsUrl": "https://developers.google.com/workspace/docs/api/reference/mcp",
|
||||
"setupPrerequisite": {
|
||||
"title": "Google Developer Preview access required",
|
||||
"description": "Google must register both the Workspace email used to authorize Paperclip and the Google Cloud project that owns the OAuth client. Registration is limited to those emails and projects; it does not enable unrelated Paperclip customers.",
|
||||
"steps": [
|
||||
"Apply with the Workspace email that will sign in and the Google Cloud project that owns the OAuth client.",
|
||||
"Wait for the Google Group notification and then Google's final project-registration email, usually within a couple of days.",
|
||||
"Add every additional tester email or Cloud project through Google's member request forms before connecting."
|
||||
],
|
||||
"actionLabel": "Apply or verify Developer Preview enrollment",
|
||||
"actionUrl": "https://developers.google.com/workspace/preview"
|
||||
},
|
||||
"redirectConstraints": "https-or-loopback-http",
|
||||
"methods": [
|
||||
{
|
||||
"key": "paperclip-read",
|
||||
"label": "Connect with Paperclip",
|
||||
"transport": "mcp_remote",
|
||||
"auth": "oauth",
|
||||
"oauthStrategy": "paperclip_cloud_connector",
|
||||
"connectorProfile": "docs.read",
|
||||
"capabilityProfile": {
|
||||
"key": "read",
|
||||
"label": "Read only",
|
||||
"description": "Read document text and structure."
|
||||
},
|
||||
"grantKinds": [
|
||||
"user",
|
||||
"organization"
|
||||
],
|
||||
"ownershipModes": [
|
||||
"platform_shared"
|
||||
],
|
||||
"whenToUse": "Use Paperclip-managed OAuth for read-only Docs access.",
|
||||
"defaults": {
|
||||
"serverUrl": "https://docsmcp.googleapis.com/mcp/v1",
|
||||
"scopesHint": [
|
||||
"https://www.googleapis.com/auth/documents.readonly"
|
||||
]
|
||||
},
|
||||
"guidanceMd": "Connect Google Docs to read documents.",
|
||||
"warnings": [
|
||||
"Google Workspace MCP servers are in Developer Preview."
|
||||
],
|
||||
"riskTier": "S3"
|
||||
},
|
||||
{
|
||||
"key": "customer-read-oauth",
|
||||
"label": "Use your own Google OAuth app",
|
||||
"transport": "mcp_remote",
|
||||
"auth": "oauth",
|
||||
"capabilityProfile": {
|
||||
"key": "read",
|
||||
"label": "Read only",
|
||||
"description": "Read document text and structure."
|
||||
},
|
||||
"grantKinds": [
|
||||
"user",
|
||||
"organization"
|
||||
],
|
||||
"ownershipModes": [
|
||||
"customer"
|
||||
],
|
||||
"whenToUse": "Use a customer-owned OAuth client for read-only Docs access.",
|
||||
"defaults": {
|
||||
"serverUrl": "https://docsmcp.googleapis.com/mcp/v1",
|
||||
"authorizationEndpoint": "https://accounts.google.com/o/oauth2/v2/auth",
|
||||
"tokenEndpoint": "https://oauth2.googleapis.com/token",
|
||||
"metadataUrl": "https://accounts.google.com/.well-known/openid-configuration",
|
||||
"scopesHint": [
|
||||
"https://www.googleapis.com/auth/documents.readonly"
|
||||
],
|
||||
"oauthAuthorizationParams": {
|
||||
"access_type": "offline",
|
||||
"prompt": "consent"
|
||||
}
|
||||
},
|
||||
"guidanceMd": "Enable the Drive, Docs, and Docs MCP APIs, then register Paperclip's callback URI.",
|
||||
"consoleLinks": {
|
||||
"register": "https://console.cloud.google.com/auth/clients",
|
||||
"docs": "https://developers.google.com/workspace/guides/configure-mcp-servers"
|
||||
},
|
||||
"warnings": [
|
||||
"Google Workspace MCP servers are in Developer Preview."
|
||||
],
|
||||
"riskTier": "S3"
|
||||
},
|
||||
{
|
||||
"key": "paperclip-write",
|
||||
"label": "Connect with Paperclip",
|
||||
"transport": "mcp_remote",
|
||||
"auth": "oauth",
|
||||
"oauthStrategy": "paperclip_cloud_connector",
|
||||
"connectorProfile": "docs.write",
|
||||
"capabilityProfile": {
|
||||
"key": "write",
|
||||
"label": "Read & edit",
|
||||
"description": "Read and update documents."
|
||||
},
|
||||
"grantKinds": [
|
||||
"user",
|
||||
"organization"
|
||||
],
|
||||
"ownershipModes": [
|
||||
"platform_shared"
|
||||
],
|
||||
"whenToUse": "Use Paperclip-managed OAuth to read and update Docs.",
|
||||
"defaults": {
|
||||
"serverUrl": "https://docsmcp.googleapis.com/mcp/v1",
|
||||
"scopesHint": [
|
||||
"https://www.googleapis.com/auth/documents"
|
||||
]
|
||||
},
|
||||
"guidanceMd": "Connect Google Docs to read and update documents.",
|
||||
"warnings": [
|
||||
"Document updates require approval.",
|
||||
"Google Workspace MCP servers are in Developer Preview."
|
||||
],
|
||||
"riskTier": "S4"
|
||||
},
|
||||
{
|
||||
"key": "customer-write-oauth",
|
||||
"label": "Use your own Google OAuth app",
|
||||
"transport": "mcp_remote",
|
||||
"auth": "oauth",
|
||||
"capabilityProfile": {
|
||||
"key": "write",
|
||||
"label": "Read & edit",
|
||||
"description": "Read and update documents."
|
||||
},
|
||||
"grantKinds": [
|
||||
"user",
|
||||
"organization"
|
||||
],
|
||||
"ownershipModes": [
|
||||
"customer"
|
||||
],
|
||||
"whenToUse": "Use a customer-owned OAuth client to read and update Docs.",
|
||||
"defaults": {
|
||||
"serverUrl": "https://docsmcp.googleapis.com/mcp/v1",
|
||||
"authorizationEndpoint": "https://accounts.google.com/o/oauth2/v2/auth",
|
||||
"tokenEndpoint": "https://oauth2.googleapis.com/token",
|
||||
"metadataUrl": "https://accounts.google.com/.well-known/openid-configuration",
|
||||
"scopesHint": [
|
||||
"https://www.googleapis.com/auth/documents"
|
||||
],
|
||||
"oauthAuthorizationParams": {
|
||||
"access_type": "offline",
|
||||
"prompt": "consent"
|
||||
}
|
||||
},
|
||||
"guidanceMd": "Enable the Drive, Docs, and Docs MCP APIs, then register Paperclip's callback URI.",
|
||||
"consoleLinks": {
|
||||
"register": "https://console.cloud.google.com/auth/clients",
|
||||
"docs": "https://developers.google.com/workspace/guides/configure-mcp-servers"
|
||||
},
|
||||
"warnings": [
|
||||
"Document updates require approval.",
|
||||
"Google Workspace MCP servers are in Developer Preview."
|
||||
],
|
||||
"riskTier": "S4"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"slug": "google-drive",
|
||||
"name": "Google Drive",
|
||||
"description": "Search, read, create, and copy files in Google Drive.",
|
||||
"categories": [
|
||||
"content",
|
||||
"productivity"
|
||||
],
|
||||
"featured": true,
|
||||
"branding": {
|
||||
"logoUrl": "/brands/apps/google-drive.svg"
|
||||
},
|
||||
"urlPatterns": [
|
||||
"https://drivemcp.googleapis.com/*",
|
||||
"https://drive.google.com/*"
|
||||
],
|
||||
"docsUrl": "https://developers.google.com/workspace/drive/api/reference/mcp",
|
||||
"setupPrerequisite": {
|
||||
"title": "Google Developer Preview access required",
|
||||
"description": "Google must register both the Workspace email used to authorize Paperclip and the Google Cloud project that owns the OAuth client. Registration is limited to those emails and projects; it does not enable unrelated Paperclip customers.",
|
||||
"steps": [
|
||||
"Apply with the Workspace email that will sign in and the Google Cloud project that owns the OAuth client.",
|
||||
"Wait for the Google Group notification and then Google's final project-registration email, usually within a couple of days.",
|
||||
"Add every additional tester email or Cloud project through Google's member request forms before connecting."
|
||||
],
|
||||
"actionLabel": "Apply or verify Developer Preview enrollment",
|
||||
"actionUrl": "https://developers.google.com/workspace/preview"
|
||||
},
|
||||
"redirectConstraints": "https-or-loopback-http",
|
||||
"methods": [
|
||||
{
|
||||
"key": "paperclip-read",
|
||||
"label": "Connect with Paperclip",
|
||||
"transport": "mcp_remote",
|
||||
"auth": "oauth",
|
||||
"oauthStrategy": "paperclip_cloud_connector",
|
||||
"connectorProfile": "drive.read",
|
||||
"capabilityProfile": {
|
||||
"key": "read",
|
||||
"label": "Read only",
|
||||
"description": "Search and read files and metadata."
|
||||
},
|
||||
"grantKinds": [
|
||||
"user",
|
||||
"organization"
|
||||
],
|
||||
"ownershipModes": [
|
||||
"platform_shared"
|
||||
],
|
||||
"whenToUse": "Use Paperclip-managed OAuth for read-only Drive access.",
|
||||
"defaults": {
|
||||
"serverUrl": "https://drivemcp.googleapis.com/mcp/v1",
|
||||
"scopesHint": [
|
||||
"https://www.googleapis.com/auth/drive.readonly"
|
||||
]
|
||||
},
|
||||
"guidanceMd": "Connect Google Drive to search and read files. Google's Developer Preview Program must register the signed-in Workspace account and Google Cloud project before tools can run.",
|
||||
"warnings": [
|
||||
"Before connecting, enroll the signed-in Workspace account and Google Cloud project in Google's Developer Preview Program and wait for the registration confirmation."
|
||||
],
|
||||
"riskTier": "S3"
|
||||
},
|
||||
{
|
||||
"key": "customer-read-oauth",
|
||||
"label": "Use your own Google OAuth app",
|
||||
"transport": "mcp_remote",
|
||||
"auth": "oauth",
|
||||
"capabilityProfile": {
|
||||
"key": "read",
|
||||
"label": "Read only",
|
||||
"description": "Search and read files and metadata."
|
||||
},
|
||||
"grantKinds": [
|
||||
"user",
|
||||
"organization"
|
||||
],
|
||||
"ownershipModes": [
|
||||
"customer"
|
||||
],
|
||||
"whenToUse": "Use a customer-owned OAuth client for read-only Drive access.",
|
||||
"defaults": {
|
||||
"serverUrl": "https://drivemcp.googleapis.com/mcp/v1",
|
||||
"authorizationEndpoint": "https://accounts.google.com/o/oauth2/v2/auth",
|
||||
"tokenEndpoint": "https://oauth2.googleapis.com/token",
|
||||
"metadataUrl": "https://accounts.google.com/.well-known/openid-configuration",
|
||||
"scopesHint": [
|
||||
"https://www.googleapis.com/auth/drive.readonly"
|
||||
],
|
||||
"oauthAuthorizationParams": {
|
||||
"access_type": "offline",
|
||||
"prompt": "consent"
|
||||
}
|
||||
},
|
||||
"guidanceMd": "Enroll the Workspace account and Google Cloud project in Google's Developer Preview Program, enable the Drive and Drive MCP APIs, then register Paperclip's callback URI.",
|
||||
"consoleLinks": {
|
||||
"register": "https://console.cloud.google.com/auth/clients",
|
||||
"docs": "https://developers.google.com/workspace/guides/configure-mcp-servers"
|
||||
},
|
||||
"warnings": [
|
||||
"Before connecting, enroll the signed-in Workspace account and Google Cloud project in Google's Developer Preview Program and wait for the registration confirmation."
|
||||
],
|
||||
"riskTier": "S3"
|
||||
},
|
||||
{
|
||||
"key": "paperclip-write",
|
||||
"label": "Connect with Paperclip",
|
||||
"transport": "mcp_remote",
|
||||
"auth": "oauth",
|
||||
"oauthStrategy": "paperclip_cloud_connector",
|
||||
"connectorProfile": "drive.write",
|
||||
"capabilityProfile": {
|
||||
"key": "write",
|
||||
"label": "Read & create",
|
||||
"description": "Read files and create or copy files."
|
||||
},
|
||||
"grantKinds": [
|
||||
"user",
|
||||
"organization"
|
||||
],
|
||||
"ownershipModes": [
|
||||
"platform_shared"
|
||||
],
|
||||
"whenToUse": "Use Paperclip-managed OAuth for Drive read and create access.",
|
||||
"defaults": {
|
||||
"serverUrl": "https://drivemcp.googleapis.com/mcp/v1",
|
||||
"scopesHint": [
|
||||
"https://www.googleapis.com/auth/drive.readonly",
|
||||
"https://www.googleapis.com/auth/drive.file"
|
||||
]
|
||||
},
|
||||
"guidanceMd": "Connect Google Drive to read files and create or copy app-accessible files. Google's Developer Preview Program must register the signed-in Workspace account and Google Cloud project before tools can run.",
|
||||
"warnings": [
|
||||
"File creation and copying require approval.",
|
||||
"Before connecting, enroll the signed-in Workspace account and Google Cloud project in Google's Developer Preview Program and wait for the registration confirmation."
|
||||
],
|
||||
"riskTier": "S4"
|
||||
},
|
||||
{
|
||||
"key": "customer-write-oauth",
|
||||
"label": "Use your own Google OAuth app",
|
||||
"transport": "mcp_remote",
|
||||
"auth": "oauth",
|
||||
"capabilityProfile": {
|
||||
"key": "write",
|
||||
"label": "Read & create",
|
||||
"description": "Read files and create or copy files."
|
||||
},
|
||||
"grantKinds": [
|
||||
"user",
|
||||
"organization"
|
||||
],
|
||||
"ownershipModes": [
|
||||
"customer"
|
||||
],
|
||||
"whenToUse": "Use a customer-owned OAuth client for Drive read and create access.",
|
||||
"defaults": {
|
||||
"serverUrl": "https://drivemcp.googleapis.com/mcp/v1",
|
||||
"authorizationEndpoint": "https://accounts.google.com/o/oauth2/v2/auth",
|
||||
"tokenEndpoint": "https://oauth2.googleapis.com/token",
|
||||
"metadataUrl": "https://accounts.google.com/.well-known/openid-configuration",
|
||||
"scopesHint": [
|
||||
"https://www.googleapis.com/auth/drive.readonly",
|
||||
"https://www.googleapis.com/auth/drive.file"
|
||||
],
|
||||
"oauthAuthorizationParams": {
|
||||
"access_type": "offline",
|
||||
"prompt": "consent"
|
||||
}
|
||||
},
|
||||
"guidanceMd": "Enroll the Workspace account and Google Cloud project in Google's Developer Preview Program, enable the Drive and Drive MCP APIs, then register Paperclip's callback URI.",
|
||||
"consoleLinks": {
|
||||
"register": "https://console.cloud.google.com/auth/clients",
|
||||
"docs": "https://developers.google.com/workspace/guides/configure-mcp-servers"
|
||||
},
|
||||
"warnings": [
|
||||
"File creation and copying require approval.",
|
||||
"Before connecting, enroll the signed-in Workspace account and Google Cloud project in Google's Developer Preview Program and wait for the registration confirmation."
|
||||
],
|
||||
"riskTier": "S4"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"slug": "google-people",
|
||||
"name": "Google People",
|
||||
"description": "Search contacts and directory profiles with the Google People API.",
|
||||
"categories": [
|
||||
"communication",
|
||||
"productivity"
|
||||
],
|
||||
"featured": false,
|
||||
"branding": {
|
||||
"logoUrl": "/brands/apps/google-people.svg"
|
||||
},
|
||||
"urlPatterns": [
|
||||
"https://people.googleapis.com/*"
|
||||
],
|
||||
"docsUrl": "https://developers.google.com/people/api/mcp",
|
||||
"setupPrerequisite": {
|
||||
"title": "Google Developer Preview access required",
|
||||
"description": "Google must register both the Workspace email used to authorize Paperclip and the Google Cloud project that owns the OAuth client. Registration is limited to those emails and projects; it does not enable unrelated Paperclip customers.",
|
||||
"steps": [
|
||||
"Apply with the Workspace email that will sign in and the Google Cloud project that owns the OAuth client.",
|
||||
"Wait for the Google Group notification and then Google's final project-registration email, usually within a couple of days.",
|
||||
"Add every additional tester email or Cloud project through Google's member request forms before connecting."
|
||||
],
|
||||
"actionLabel": "Apply or verify Developer Preview enrollment",
|
||||
"actionUrl": "https://developers.google.com/workspace/preview"
|
||||
},
|
||||
"redirectConstraints": "https-or-loopback-http",
|
||||
"methods": [
|
||||
{
|
||||
"key": "paperclip-read",
|
||||
"label": "Connect with Paperclip",
|
||||
"transport": "mcp_remote",
|
||||
"auth": "oauth",
|
||||
"oauthStrategy": "paperclip_cloud_connector",
|
||||
"connectorProfile": "people.read",
|
||||
"capabilityProfile": {
|
||||
"key": "read",
|
||||
"label": "Read contacts",
|
||||
"description": "Search contacts, directory people, and your profile."
|
||||
},
|
||||
"grantKinds": [
|
||||
"user",
|
||||
"organization"
|
||||
],
|
||||
"ownershipModes": [
|
||||
"platform_shared"
|
||||
],
|
||||
"whenToUse": "Use Paperclip-managed OAuth for Google People access.",
|
||||
"defaults": {
|
||||
"serverUrl": "https://people.googleapis.com/mcp/v1",
|
||||
"scopesHint": [
|
||||
"https://www.googleapis.com/auth/directory.readonly",
|
||||
"https://www.googleapis.com/auth/userinfo.profile",
|
||||
"https://www.googleapis.com/auth/contacts.readonly"
|
||||
]
|
||||
},
|
||||
"guidanceMd": "Connect Google People to search contacts and directory profiles.",
|
||||
"warnings": [
|
||||
"Google Workspace MCP servers are in Developer Preview."
|
||||
],
|
||||
"riskTier": "S3"
|
||||
},
|
||||
{
|
||||
"key": "customer-read-oauth",
|
||||
"label": "Use your own Google OAuth app",
|
||||
"transport": "mcp_remote",
|
||||
"auth": "oauth",
|
||||
"capabilityProfile": {
|
||||
"key": "read",
|
||||
"label": "Read contacts",
|
||||
"description": "Search contacts, directory people, and your profile."
|
||||
},
|
||||
"grantKinds": [
|
||||
"user",
|
||||
"organization"
|
||||
],
|
||||
"ownershipModes": [
|
||||
"customer"
|
||||
],
|
||||
"whenToUse": "Use a customer-owned OAuth client for Google People access.",
|
||||
"defaults": {
|
||||
"serverUrl": "https://people.googleapis.com/mcp/v1",
|
||||
"authorizationEndpoint": "https://accounts.google.com/o/oauth2/v2/auth",
|
||||
"tokenEndpoint": "https://oauth2.googleapis.com/token",
|
||||
"metadataUrl": "https://accounts.google.com/.well-known/openid-configuration",
|
||||
"scopesHint": [
|
||||
"https://www.googleapis.com/auth/directory.readonly",
|
||||
"https://www.googleapis.com/auth/userinfo.profile",
|
||||
"https://www.googleapis.com/auth/contacts.readonly"
|
||||
],
|
||||
"oauthAuthorizationParams": {
|
||||
"access_type": "offline",
|
||||
"prompt": "consent"
|
||||
}
|
||||
},
|
||||
"guidanceMd": "Enable the People and People MCP APIs, then register Paperclip's callback URI.",
|
||||
"consoleLinks": {
|
||||
"register": "https://console.cloud.google.com/auth/clients",
|
||||
"docs": "https://developers.google.com/workspace/guides/configure-mcp-servers"
|
||||
},
|
||||
"warnings": [
|
||||
"Directory search availability depends on your Workspace account.",
|
||||
"Google Workspace MCP servers are in Developer Preview."
|
||||
],
|
||||
"riskTier": "S3"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"slug": "google-sheets",
|
||||
"name": "Google Sheets",
|
||||
"description": "Read and update Google Sheets spreadsheets.",
|
||||
"categories": [
|
||||
"data",
|
||||
"productivity"
|
||||
],
|
||||
"featured": true,
|
||||
"branding": {
|
||||
"logoUrl": "/brands/apps/google-sheets.svg"
|
||||
},
|
||||
"urlPatterns": [
|
||||
"https://sheetsmcp.googleapis.com/*",
|
||||
"https://docs.google.com/spreadsheets/*",
|
||||
"https://sheets.google.com/*"
|
||||
],
|
||||
"docsUrl": "https://developers.google.com/workspace/sheets/api/reference/mcp",
|
||||
"setupPrerequisite": {
|
||||
"title": "Google Developer Preview access required",
|
||||
"description": "Google must register both the Workspace email used to authorize Paperclip and the Google Cloud project that owns the OAuth client. Registration is limited to those emails and projects; it does not enable unrelated Paperclip customers.",
|
||||
"steps": [
|
||||
"Apply with the Workspace email that will sign in and the Google Cloud project that owns the OAuth client.",
|
||||
"Wait for the Google Group notification and then Google's final project-registration email, usually within a couple of days.",
|
||||
"Add every additional tester email or Cloud project through Google's member request forms before connecting."
|
||||
],
|
||||
"actionLabel": "Apply or verify Developer Preview enrollment",
|
||||
"actionUrl": "https://developers.google.com/workspace/preview"
|
||||
},
|
||||
"redirectConstraints": "https-or-loopback-http",
|
||||
"methods": [
|
||||
{
|
||||
"key": "paperclip-read",
|
||||
"label": "Connect with Paperclip",
|
||||
"transport": "mcp_remote",
|
||||
"auth": "oauth",
|
||||
"oauthStrategy": "paperclip_cloud_connector",
|
||||
"connectorProfile": "sheets.read",
|
||||
"capabilityProfile": {
|
||||
"key": "read",
|
||||
"label": "Read only",
|
||||
"description": "Read spreadsheet values and structure."
|
||||
},
|
||||
"grantKinds": [
|
||||
"user",
|
||||
"organization"
|
||||
],
|
||||
"ownershipModes": [
|
||||
"platform_shared"
|
||||
],
|
||||
"whenToUse": "Use Paperclip-managed OAuth for read-only Sheets access.",
|
||||
"defaults": {
|
||||
"serverUrl": "https://sheetsmcp.googleapis.com/mcp/v1",
|
||||
"scopesHint": [
|
||||
"https://www.googleapis.com/auth/spreadsheets.readonly"
|
||||
]
|
||||
},
|
||||
"guidanceMd": "Connect Google Sheets to read spreadsheets.",
|
||||
"warnings": [
|
||||
"Google Workspace MCP servers are in Developer Preview."
|
||||
],
|
||||
"riskTier": "S3"
|
||||
},
|
||||
{
|
||||
"key": "customer-read-oauth",
|
||||
"label": "Use your own Google OAuth app",
|
||||
"transport": "mcp_remote",
|
||||
"auth": "oauth",
|
||||
"capabilityProfile": {
|
||||
"key": "read",
|
||||
"label": "Read only",
|
||||
"description": "Read spreadsheet values and structure."
|
||||
},
|
||||
"grantKinds": [
|
||||
"user",
|
||||
"organization"
|
||||
],
|
||||
"ownershipModes": [
|
||||
"customer"
|
||||
],
|
||||
"whenToUse": "Use a customer-owned OAuth client for read-only Sheets access.",
|
||||
"defaults": {
|
||||
"serverUrl": "https://sheetsmcp.googleapis.com/mcp/v1",
|
||||
"authorizationEndpoint": "https://accounts.google.com/o/oauth2/v2/auth",
|
||||
"tokenEndpoint": "https://oauth2.googleapis.com/token",
|
||||
"metadataUrl": "https://accounts.google.com/.well-known/openid-configuration",
|
||||
"scopesHint": [
|
||||
"https://www.googleapis.com/auth/spreadsheets.readonly"
|
||||
],
|
||||
"oauthAuthorizationParams": {
|
||||
"access_type": "offline",
|
||||
"prompt": "consent"
|
||||
}
|
||||
},
|
||||
"guidanceMd": "Enable the Drive, Sheets, and Sheets MCP APIs, then register Paperclip's callback URI.",
|
||||
"consoleLinks": {
|
||||
"register": "https://console.cloud.google.com/auth/clients",
|
||||
"docs": "https://developers.google.com/workspace/guides/configure-mcp-servers"
|
||||
},
|
||||
"warnings": [
|
||||
"Google Workspace MCP servers are in Developer Preview."
|
||||
],
|
||||
"riskTier": "S3"
|
||||
},
|
||||
{
|
||||
"key": "paperclip-write",
|
||||
"label": "Connect with Paperclip",
|
||||
"transport": "mcp_remote",
|
||||
"auth": "oauth",
|
||||
"oauthStrategy": "paperclip_cloud_connector",
|
||||
"connectorProfile": "sheets.write",
|
||||
"capabilityProfile": {
|
||||
"key": "write",
|
||||
"label": "Read & edit",
|
||||
"description": "Read and update spreadsheet values, formulas, and dimensions."
|
||||
},
|
||||
"grantKinds": [
|
||||
"user",
|
||||
"organization"
|
||||
],
|
||||
"ownershipModes": [
|
||||
"platform_shared"
|
||||
],
|
||||
"whenToUse": "Use Paperclip-managed OAuth to read and update Sheets.",
|
||||
"defaults": {
|
||||
"serverUrl": "https://sheetsmcp.googleapis.com/mcp/v1",
|
||||
"scopesHint": [
|
||||
"https://www.googleapis.com/auth/spreadsheets"
|
||||
]
|
||||
},
|
||||
"guidanceMd": "Connect Google Sheets to read and update spreadsheets.",
|
||||
"warnings": [
|
||||
"Spreadsheet updates require approval.",
|
||||
"Google Workspace MCP servers are in Developer Preview."
|
||||
],
|
||||
"riskTier": "S4"
|
||||
},
|
||||
{
|
||||
"key": "customer-write-oauth",
|
||||
"label": "Use your own Google OAuth app",
|
||||
"transport": "mcp_remote",
|
||||
"auth": "oauth",
|
||||
"capabilityProfile": {
|
||||
"key": "write",
|
||||
"label": "Read & edit",
|
||||
"description": "Read and update spreadsheet values, formulas, and dimensions."
|
||||
},
|
||||
"grantKinds": [
|
||||
"user",
|
||||
"organization"
|
||||
],
|
||||
"ownershipModes": [
|
||||
"customer"
|
||||
],
|
||||
"whenToUse": "Use a customer-owned OAuth client to read and update Sheets.",
|
||||
"defaults": {
|
||||
"serverUrl": "https://sheetsmcp.googleapis.com/mcp/v1",
|
||||
"authorizationEndpoint": "https://accounts.google.com/o/oauth2/v2/auth",
|
||||
"tokenEndpoint": "https://oauth2.googleapis.com/token",
|
||||
"metadataUrl": "https://accounts.google.com/.well-known/openid-configuration",
|
||||
"scopesHint": [
|
||||
"https://www.googleapis.com/auth/spreadsheets"
|
||||
],
|
||||
"oauthAuthorizationParams": {
|
||||
"access_type": "offline",
|
||||
"prompt": "consent"
|
||||
}
|
||||
},
|
||||
"guidanceMd": "Enable the Drive, Sheets, and Sheets MCP APIs, then register Paperclip's callback URI.",
|
||||
"consoleLinks": {
|
||||
"register": "https://console.cloud.google.com/auth/clients",
|
||||
"docs": "https://developers.google.com/workspace/guides/configure-mcp-servers"
|
||||
},
|
||||
"warnings": [
|
||||
"Spreadsheet updates require approval.",
|
||||
"Google Workspace MCP servers are in Developer Preview."
|
||||
],
|
||||
"riskTier": "S4"
|
||||
},
|
||||
{
|
||||
"key": "local",
|
||||
"label": "Use the Paperclip robot account",
|
||||
"transport": "local_stdio",
|
||||
"auth": "none",
|
||||
"capabilityProfile": {
|
||||
"key": "robot",
|
||||
"label": "Share selected sheets",
|
||||
"description": "Share only named spreadsheets with the Paperclip robot account."
|
||||
},
|
||||
"ownershipModes": [
|
||||
"customer"
|
||||
],
|
||||
"whenToUse": "Share selected spreadsheets with the Paperclip robot account instead of connecting a Google identity.",
|
||||
"defaults": {
|
||||
"templateKey": "paperclip.google-sheets"
|
||||
},
|
||||
"guidanceMd": "Share each spreadsheet with the Paperclip robot email, then paste the sheet links.",
|
||||
"riskTier": "S3",
|
||||
"requiredResourceFilters": [
|
||||
"spreadsheet"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"slug": "google-slides",
|
||||
"name": "Google Slides",
|
||||
"description": "Read and update Google Slides presentations.",
|
||||
"categories": [
|
||||
"content",
|
||||
"productivity"
|
||||
],
|
||||
"featured": true,
|
||||
"branding": {
|
||||
"logoUrl": "/brands/apps/google-slides.svg"
|
||||
},
|
||||
"urlPatterns": [
|
||||
"https://slidesmcp.googleapis.com/*",
|
||||
"https://docs.google.com/presentation/*"
|
||||
],
|
||||
"docsUrl": "https://developers.google.com/workspace/slides/api/reference/mcp",
|
||||
"setupPrerequisite": {
|
||||
"title": "Google Developer Preview access required",
|
||||
"description": "Google must register both the Workspace email used to authorize Paperclip and the Google Cloud project that owns the OAuth client. Registration is limited to those emails and projects; it does not enable unrelated Paperclip customers.",
|
||||
"steps": [
|
||||
"Apply with the Workspace email that will sign in and the Google Cloud project that owns the OAuth client.",
|
||||
"Wait for the Google Group notification and then Google's final project-registration email, usually within a couple of days.",
|
||||
"Add every additional tester email or Cloud project through Google's member request forms before connecting."
|
||||
],
|
||||
"actionLabel": "Apply or verify Developer Preview enrollment",
|
||||
"actionUrl": "https://developers.google.com/workspace/preview"
|
||||
},
|
||||
"redirectConstraints": "https-or-loopback-http",
|
||||
"methods": [
|
||||
{
|
||||
"key": "paperclip-read",
|
||||
"label": "Connect with Paperclip",
|
||||
"transport": "mcp_remote",
|
||||
"auth": "oauth",
|
||||
"oauthStrategy": "paperclip_cloud_connector",
|
||||
"connectorProfile": "slides.read",
|
||||
"capabilityProfile": {
|
||||
"key": "read",
|
||||
"label": "Read only",
|
||||
"description": "Read presentation slides and content."
|
||||
},
|
||||
"grantKinds": [
|
||||
"user",
|
||||
"organization"
|
||||
],
|
||||
"ownershipModes": [
|
||||
"platform_shared"
|
||||
],
|
||||
"whenToUse": "Use Paperclip-managed OAuth for read-only Slides access.",
|
||||
"defaults": {
|
||||
"serverUrl": "https://slidesmcp.googleapis.com/mcp/v1",
|
||||
"scopesHint": [
|
||||
"https://www.googleapis.com/auth/presentations.readonly"
|
||||
]
|
||||
},
|
||||
"guidanceMd": "Connect Google Slides to read presentations.",
|
||||
"warnings": [
|
||||
"Google Workspace MCP servers are in Developer Preview."
|
||||
],
|
||||
"riskTier": "S3"
|
||||
},
|
||||
{
|
||||
"key": "customer-read-oauth",
|
||||
"label": "Use your own Google OAuth app",
|
||||
"transport": "mcp_remote",
|
||||
"auth": "oauth",
|
||||
"capabilityProfile": {
|
||||
"key": "read",
|
||||
"label": "Read only",
|
||||
"description": "Read presentation slides and content."
|
||||
},
|
||||
"grantKinds": [
|
||||
"user",
|
||||
"organization"
|
||||
],
|
||||
"ownershipModes": [
|
||||
"customer"
|
||||
],
|
||||
"whenToUse": "Use a customer-owned OAuth client for read-only Slides access.",
|
||||
"defaults": {
|
||||
"serverUrl": "https://slidesmcp.googleapis.com/mcp/v1",
|
||||
"authorizationEndpoint": "https://accounts.google.com/o/oauth2/v2/auth",
|
||||
"tokenEndpoint": "https://oauth2.googleapis.com/token",
|
||||
"metadataUrl": "https://accounts.google.com/.well-known/openid-configuration",
|
||||
"scopesHint": [
|
||||
"https://www.googleapis.com/auth/presentations.readonly"
|
||||
],
|
||||
"oauthAuthorizationParams": {
|
||||
"access_type": "offline",
|
||||
"prompt": "consent"
|
||||
}
|
||||
},
|
||||
"guidanceMd": "Enable the Drive, Slides, and Slides MCP APIs, then register Paperclip's callback URI.",
|
||||
"consoleLinks": {
|
||||
"register": "https://console.cloud.google.com/auth/clients",
|
||||
"docs": "https://developers.google.com/workspace/guides/configure-mcp-servers"
|
||||
},
|
||||
"warnings": [
|
||||
"Google Workspace MCP servers are in Developer Preview."
|
||||
],
|
||||
"riskTier": "S3"
|
||||
},
|
||||
{
|
||||
"key": "paperclip-write",
|
||||
"label": "Connect with Paperclip",
|
||||
"transport": "mcp_remote",
|
||||
"auth": "oauth",
|
||||
"oauthStrategy": "paperclip_cloud_connector",
|
||||
"connectorProfile": "slides.write",
|
||||
"capabilityProfile": {
|
||||
"key": "write",
|
||||
"label": "Read & edit",
|
||||
"description": "Read and update presentations."
|
||||
},
|
||||
"grantKinds": [
|
||||
"user",
|
||||
"organization"
|
||||
],
|
||||
"ownershipModes": [
|
||||
"platform_shared"
|
||||
],
|
||||
"whenToUse": "Use Paperclip-managed OAuth to read and update Slides.",
|
||||
"defaults": {
|
||||
"serverUrl": "https://slidesmcp.googleapis.com/mcp/v1",
|
||||
"scopesHint": [
|
||||
"https://www.googleapis.com/auth/presentations"
|
||||
]
|
||||
},
|
||||
"guidanceMd": "Connect Google Slides to read and update presentations.",
|
||||
"warnings": [
|
||||
"Presentation updates require approval.",
|
||||
"Google Workspace MCP servers are in Developer Preview."
|
||||
],
|
||||
"riskTier": "S4"
|
||||
},
|
||||
{
|
||||
"key": "customer-write-oauth",
|
||||
"label": "Use your own Google OAuth app",
|
||||
"transport": "mcp_remote",
|
||||
"auth": "oauth",
|
||||
"capabilityProfile": {
|
||||
"key": "write",
|
||||
"label": "Read & edit",
|
||||
"description": "Read and update presentations."
|
||||
},
|
||||
"grantKinds": [
|
||||
"user",
|
||||
"organization"
|
||||
],
|
||||
"ownershipModes": [
|
||||
"customer"
|
||||
],
|
||||
"whenToUse": "Use a customer-owned OAuth client to read and update Slides.",
|
||||
"defaults": {
|
||||
"serverUrl": "https://slidesmcp.googleapis.com/mcp/v1",
|
||||
"authorizationEndpoint": "https://accounts.google.com/o/oauth2/v2/auth",
|
||||
"tokenEndpoint": "https://oauth2.googleapis.com/token",
|
||||
"metadataUrl": "https://accounts.google.com/.well-known/openid-configuration",
|
||||
"scopesHint": [
|
||||
"https://www.googleapis.com/auth/presentations"
|
||||
],
|
||||
"oauthAuthorizationParams": {
|
||||
"access_type": "offline",
|
||||
"prompt": "consent"
|
||||
}
|
||||
},
|
||||
"guidanceMd": "Enable the Drive, Slides, and Slides MCP APIs, then register Paperclip's callback URI.",
|
||||
"consoleLinks": {
|
||||
"register": "https://console.cloud.google.com/auth/clients",
|
||||
"docs": "https://developers.google.com/workspace/guides/configure-mcp-servers"
|
||||
},
|
||||
"warnings": [
|
||||
"Presentation updates require approval.",
|
||||
"Google Workspace MCP servers are in Developer Preview."
|
||||
],
|
||||
"riskTier": "S4"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"slug": "google-workspace-search",
|
||||
"name": "Google Workspace Search",
|
||||
"description": "Search Gmail, Drive, Calendar, and Chat through one read-only Google Workspace search tool.",
|
||||
"categories": [
|
||||
"data",
|
||||
"productivity"
|
||||
],
|
||||
"featured": false,
|
||||
"branding": {
|
||||
"logoUrl": "/brands/apps/google-workspace-search.svg"
|
||||
},
|
||||
"urlPatterns": [
|
||||
"https://workspacemcp.googleapis.com/*"
|
||||
],
|
||||
"docsUrl": "https://developers.google.com/workspace/guides/universal-search-mcp",
|
||||
"setupPrerequisite": {
|
||||
"title": "Google Developer Preview access required",
|
||||
"description": "Google must register both the Workspace email used to authorize Paperclip and the Google Cloud project that owns the OAuth client. Registration is limited to those emails and projects; it does not enable unrelated Paperclip customers.",
|
||||
"steps": [
|
||||
"Apply with the Workspace email that will sign in and the Google Cloud project that owns the OAuth client.",
|
||||
"Wait for the Google Group notification and then Google's final project-registration email, usually within a couple of days.",
|
||||
"Add every additional tester email or Cloud project through Google's member request forms before connecting."
|
||||
],
|
||||
"actionLabel": "Apply or verify Developer Preview enrollment",
|
||||
"actionUrl": "https://developers.google.com/workspace/preview"
|
||||
},
|
||||
"redirectConstraints": "https-or-loopback-http",
|
||||
"methods": [
|
||||
{
|
||||
"key": "paperclip-read",
|
||||
"label": "Connect with Paperclip",
|
||||
"transport": "mcp_remote",
|
||||
"auth": "oauth",
|
||||
"oauthStrategy": "paperclip_cloud_connector",
|
||||
"connectorProfile": "workspace-search.read",
|
||||
"capabilityProfile": {
|
||||
"key": "read",
|
||||
"label": "Search Workspace",
|
||||
"description": "Search Gmail, Drive, Calendar, and Chat without write access."
|
||||
},
|
||||
"grantKinds": [
|
||||
"user",
|
||||
"organization"
|
||||
],
|
||||
"ownershipModes": [
|
||||
"platform_shared"
|
||||
],
|
||||
"whenToUse": "Use Paperclip-managed OAuth for cross-product Workspace search.",
|
||||
"defaults": {
|
||||
"serverUrl": "https://workspacemcp.googleapis.com/mcp/v1",
|
||||
"scopesHint": [
|
||||
"https://www.googleapis.com/auth/gmail.readonly",
|
||||
"https://www.googleapis.com/auth/drive.readonly",
|
||||
"https://www.googleapis.com/auth/calendar.readonly",
|
||||
"https://www.googleapis.com/auth/chat.messages.readonly"
|
||||
]
|
||||
},
|
||||
"guidanceMd": "Connect Google Workspace Search for one read-only search tool spanning Gmail, Drive, Calendar, and Chat.",
|
||||
"warnings": [
|
||||
"This requests read access to all four supported search corpora.",
|
||||
"Google Workspace MCP servers are in Developer Preview."
|
||||
],
|
||||
"riskTier": "S3"
|
||||
},
|
||||
{
|
||||
"key": "customer-read-oauth",
|
||||
"label": "Use your own Google OAuth app",
|
||||
"transport": "mcp_remote",
|
||||
"auth": "oauth",
|
||||
"capabilityProfile": {
|
||||
"key": "read",
|
||||
"label": "Search Workspace",
|
||||
"description": "Search Gmail, Drive, Calendar, and Chat without write access."
|
||||
},
|
||||
"grantKinds": [
|
||||
"user",
|
||||
"organization"
|
||||
],
|
||||
"ownershipModes": [
|
||||
"customer"
|
||||
],
|
||||
"whenToUse": "Use a customer-owned OAuth client for cross-product Workspace search.",
|
||||
"defaults": {
|
||||
"serverUrl": "https://workspacemcp.googleapis.com/mcp/v1",
|
||||
"authorizationEndpoint": "https://accounts.google.com/o/oauth2/v2/auth",
|
||||
"tokenEndpoint": "https://oauth2.googleapis.com/token",
|
||||
"metadataUrl": "https://accounts.google.com/.well-known/openid-configuration",
|
||||
"scopesHint": [
|
||||
"https://www.googleapis.com/auth/gmail.readonly",
|
||||
"https://www.googleapis.com/auth/drive.readonly",
|
||||
"https://www.googleapis.com/auth/calendar.readonly",
|
||||
"https://www.googleapis.com/auth/chat.messages.readonly"
|
||||
],
|
||||
"oauthAuthorizationParams": {
|
||||
"access_type": "offline",
|
||||
"prompt": "consent"
|
||||
}
|
||||
},
|
||||
"guidanceMd": "Enable the Gmail, Drive, Calendar, Chat, and Workspace MCP APIs, then register Paperclip's callback URI.",
|
||||
"consoleLinks": {
|
||||
"register": "https://console.cloud.google.com/auth/clients",
|
||||
"docs": "https://developers.google.com/workspace/guides/universal-search-mcp"
|
||||
},
|
||||
"warnings": [
|
||||
"This requests read access to all four supported search corpora.",
|
||||
"Google Workspace MCP servers are in Developer Preview."
|
||||
],
|
||||
"riskTier": "S3"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"slug": "google",
|
||||
"name": "Google Gemini",
|
||||
"description": "Connect Google Gemini accounts for your agents.",
|
||||
"categories": [
|
||||
"ai"
|
||||
],
|
||||
"branding": {
|
||||
"logoUrl": "/brands/apps/google.svg"
|
||||
},
|
||||
"urlPatterns": [
|
||||
"https://generativelanguage.googleapis.com/*"
|
||||
],
|
||||
"methods": [
|
||||
{
|
||||
"key": "ai-api_key",
|
||||
"label": "Google Gemini API key",
|
||||
"purpose": "ai",
|
||||
"transport": "runtime_auth",
|
||||
"auth": "api_key",
|
||||
"ai": {
|
||||
"provider": "google",
|
||||
"method": "api_key"
|
||||
},
|
||||
"grantKinds": [
|
||||
"user",
|
||||
"organization"
|
||||
],
|
||||
"ownershipModes": [
|
||||
"customer"
|
||||
],
|
||||
"whenToUse": "Authenticate an agent with this account.",
|
||||
"guidanceMd": "Use your personal account or an explicitly shared company account.",
|
||||
"riskTier": "S3",
|
||||
"credentialFields": [
|
||||
{
|
||||
"key": "apiKey",
|
||||
"label": "API key",
|
||||
"type": "password",
|
||||
"required": true,
|
||||
"placeholder": "Enter API key",
|
||||
"secret": true
|
||||
}
|
||||
],
|
||||
"keyPlacement": {
|
||||
"location": "env",
|
||||
"name": "GEMINI_API_KEY"
|
||||
}
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"model-provider"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"slug": "gsc-wizard",
|
||||
"name": "GSC Wizard",
|
||||
"description": "Read Google Search Console, Bing Webmaster, and analytics data for connected properties.",
|
||||
"categories": [
|
||||
"analytics"
|
||||
],
|
||||
"featured": false,
|
||||
"branding": {
|
||||
"logoUrl": "/brands/apps/gsc-wizard.png"
|
||||
},
|
||||
"urlPatterns": [
|
||||
"https://mcp.gscwizard.com/*"
|
||||
],
|
||||
"docsUrl": "https://www.gscwizard.com/claude-search-console-mcp.html",
|
||||
"redirectConstraints": "https-or-loopback-http",
|
||||
"methods": [
|
||||
{
|
||||
"key": "mcp-oauth",
|
||||
"label": "Sign in with GSC Wizard",
|
||||
"transport": "mcp_remote",
|
||||
"auth": "oauth",
|
||||
"ownershipModes": [
|
||||
"dcr"
|
||||
],
|
||||
"whenToUse": "Use remote OAuth authorization for the GSC Wizard MCP.",
|
||||
"defaults": {
|
||||
"serverUrl": "https://mcp.gscwizard.com/mcp",
|
||||
"scopesHint": [
|
||||
"mcp:read",
|
||||
"mcp:write"
|
||||
]
|
||||
},
|
||||
"guidanceMd": "Connect the GSC Wizard account and its authorized properties. Check site identity and date bounds before querying; review any URL submission or property-changing action separately.",
|
||||
"riskTier": "S3",
|
||||
"warnings": [
|
||||
"Some tools may submit URLs or otherwise change external indexing state; review action details before use.",
|
||||
"GSC Wizard connects multiple search and analytics providers; confirm the authorized property and action policy for each tool."
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "mcp-api-key",
|
||||
"label": "Use a GSC Wizard MCP key",
|
||||
"transport": "mcp_remote",
|
||||
"auth": "api_key",
|
||||
"ownershipModes": [
|
||||
"customer"
|
||||
],
|
||||
"whenToUse": "Use a customer-created GSC Wizard MCP key in a Bearer header.",
|
||||
"defaults": {
|
||||
"serverUrl": "https://mcp.gscwizard.com/mcp"
|
||||
},
|
||||
"guidanceMd": "Create a key for the intended account and select the appropriate read or write permission during key creation. Verify each connected property before querying; review URL submission actions separately.",
|
||||
"riskTier": "S3",
|
||||
"credentialFields": [
|
||||
{
|
||||
"key": "authorization",
|
||||
"label": "Use a GSC Wizard MCP key",
|
||||
"type": "password",
|
||||
"required": true,
|
||||
"placeholder": "Enter provider credential",
|
||||
"secret": true,
|
||||
"helperMd": "At key creation, choose read-only or read/write according to the approved task. Read/write keys can reach URL-submission and related provider actions; Paperclip cannot increase an existing key’s permissions."
|
||||
}
|
||||
],
|
||||
"keyPlacement": {
|
||||
"location": "header",
|
||||
"name": "Authorization",
|
||||
"prefix": "Bearer "
|
||||
},
|
||||
"consoleLinks": {
|
||||
"keys": "https://www.gscwizard.com/claude-search-console-mcp.html",
|
||||
"docs": "https://www.gscwizard.com/claude-search-console-mcp.html"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"slug": "honcho",
|
||||
"name": "Honcho",
|
||||
"description": "Remember conversations and retrieve context about peers.",
|
||||
"categories": [
|
||||
"ai"
|
||||
],
|
||||
"featured": false,
|
||||
"branding": {
|
||||
"logoUrl": "/brands/apps/honcho.svg"
|
||||
},
|
||||
"urlPatterns": [
|
||||
"https://mcp.honcho.dev/*"
|
||||
],
|
||||
"docsUrl": "https://honcho.dev/docs/v3/guides/integrations/mcp",
|
||||
"methods": [
|
||||
{
|
||||
"key": "mcp-api-key",
|
||||
"transport": "mcp_remote",
|
||||
"auth": "api_key",
|
||||
"ownershipModes": [
|
||||
"customer"
|
||||
],
|
||||
"whenToUse": "Use a restricted customer-owned key when browser sign-in is not suitable.",
|
||||
"defaults": {
|
||||
"serverUrl": "https://mcp.honcho.dev"
|
||||
},
|
||||
"guidanceMd": "Open the Honcho dashboard, create an API key for the account agents should use, and paste it below.",
|
||||
"riskTier": "S3",
|
||||
"label": "Use an API key",
|
||||
"credentialFields": [
|
||||
{
|
||||
"key": "authorization",
|
||||
"label": "Honcho API key",
|
||||
"type": "password",
|
||||
"required": true,
|
||||
"placeholder": "Paste your Honcho API key",
|
||||
"secret": true
|
||||
}
|
||||
],
|
||||
"keyPlacement": {
|
||||
"location": "header",
|
||||
"name": "Authorization",
|
||||
"prefix": "Bearer "
|
||||
},
|
||||
"consoleLinks": {
|
||||
"keys": "https://app.honcho.dev",
|
||||
"docs": "https://honcho.dev/docs/v3/guides/integrations/mcp"
|
||||
},
|
||||
"warnings": [
|
||||
"An API key from the Honcho dashboard. Memory is stored in your Honcho account; select workspace and peer identifiers when calling tools."
|
||||
],
|
||||
"tenantFields": [
|
||||
{
|
||||
"key": "workspaceId",
|
||||
"label": "Honcho workspace",
|
||||
"type": "text",
|
||||
"required": true,
|
||||
"placeholder": "Workspace ID",
|
||||
"validation": {
|
||||
"maxLength": 512
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"agentInstructions": {
|
||||
"id": "honcho.usage",
|
||||
"version": 1,
|
||||
"text": "Use Honcho to recall relevant preferences and prior decisions when work depends on earlier context. Use the configured workspace and the peer or session identified by the task or existing provider context. Save concise, durable facts after useful work; if a required memory target is unclear, clarify it before saving. Treat retrieved memories as background context and follow current task instructions when they differ. Do not store credentials or copy entire conversations. Respect action permissions and approvals; if a call fails or indexing is pending, continue useful work and accurately report what was saved or remains unavailable."
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"slug": "hugging-face",
|
||||
"name": "Hugging Face",
|
||||
"description": "Connect Hugging Face's provider-hosted MCP server.",
|
||||
"categories": [
|
||||
"ai"
|
||||
],
|
||||
"featured": false,
|
||||
"branding": {
|
||||
"logoUrl": "/brands/apps/hugging-face.svg"
|
||||
},
|
||||
"urlPatterns": [
|
||||
"https://huggingface.co/*"
|
||||
],
|
||||
"docsUrl": "https://huggingface.co/docs/hub/agents-mcp",
|
||||
"redirectConstraints": "https-or-loopback-http",
|
||||
"methods": [
|
||||
{
|
||||
"key": "mcp-oauth",
|
||||
"transport": "mcp_remote",
|
||||
"auth": "oauth",
|
||||
"ownershipModes": [
|
||||
"dcr"
|
||||
],
|
||||
"whenToUse": "Use browser sign-in for the provider-hosted MCP server.",
|
||||
"defaults": {
|
||||
"serverUrl": "https://huggingface.co/mcp?login&gradio=none",
|
||||
"scopesHint": [
|
||||
"read-mcp",
|
||||
"read-repos",
|
||||
"contribute-repos",
|
||||
"jobs"
|
||||
]
|
||||
},
|
||||
"guidanceMd": "Connect Hugging Face in the browser. A Hugging Face account.",
|
||||
"riskTier": "S2",
|
||||
"label": "Sign in with Hugging Face",
|
||||
"consoleLinks": {
|
||||
"docs": "https://huggingface.co/docs/hub/agents-mcp"
|
||||
},
|
||||
"warnings": [
|
||||
"A Hugging Face account."
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "public-mcp",
|
||||
"transport": "mcp_remote",
|
||||
"auth": "none",
|
||||
"ownershipModes": [
|
||||
"customer"
|
||||
],
|
||||
"whenToUse": "Use the anonymous hosted MCP endpoint for public Hugging Face Hub lookups.",
|
||||
"defaults": {
|
||||
"serverUrl": "https://huggingface.co/mcp"
|
||||
},
|
||||
"guidanceMd": "This anonymous method exposes the public Hugging Face Hub read tools for repository search, public repository details, datasets, and documentation. It does not require an account or API key and is rate-limited. Keep queries to public information; do not include secrets, personal data, private repository names, or proprietary content.",
|
||||
"riskTier": "S1",
|
||||
"label": "Use public Hugging Face Hub",
|
||||
"consoleLinks": {
|
||||
"docs": "https://huggingface.co/docs/hub/agents-mcp"
|
||||
},
|
||||
"warnings": [
|
||||
"Anonymous requests are rate-limited by Hugging Face.",
|
||||
"This method only advertises read tools for the public Hub; do not include secrets, personal data, private repository names, or proprietary content in queries."
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"slug": "imessage-photon",
|
||||
"name": "iMessage Photon",
|
||||
"description": "Message a Paperclip agent from Apple Messages using Photon Cloud. Pro supports DMs; dedicated lines also support groups.",
|
||||
"categories": [
|
||||
"communication"
|
||||
],
|
||||
"featured": false,
|
||||
"branding": {
|
||||
"logoUrl": "/brands/apps/imessage-photon.png"
|
||||
},
|
||||
"urlPatterns": [
|
||||
"https://photon.codes/*"
|
||||
],
|
||||
"methods": [
|
||||
{
|
||||
"key": "chat-agent",
|
||||
"label": "Chat with an agent",
|
||||
"purpose": "channel",
|
||||
"provider": "imessage-photon",
|
||||
"transport": "chat_sdk",
|
||||
"auth": "api_key",
|
||||
"ownershipModes": [
|
||||
"customer"
|
||||
],
|
||||
"whenToUse": "Let people in iMessage Photon start and continue work with one Paperclip agent.",
|
||||
"credentialFields": [
|
||||
{
|
||||
"key": "projectSecret",
|
||||
"label": "Project secret",
|
||||
"type": "password",
|
||||
"required": true,
|
||||
"placeholder": "Photon project secret",
|
||||
"secret": true
|
||||
}
|
||||
],
|
||||
"guidanceMd": "Connect a Photon Cloud project. Pro shared lines support DMs after sender enrollment in Photon and identity linking in Paperclip. Dedicated lines also support individually enabled groups.",
|
||||
"consoleLinks": {
|
||||
"register": "https://photon.codes/",
|
||||
"docs": "https://photon.codes/docs/spectrum-ts/providers/imessage/connection-and-routing"
|
||||
},
|
||||
"riskTier": "S3",
|
||||
"requiredResourceFilters": [
|
||||
"direct_message",
|
||||
"group_chat"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"slug": "jira",
|
||||
"name": "Jira",
|
||||
"description": "Connect Jira's provider-hosted MCP server.",
|
||||
"categories": [
|
||||
"productivity"
|
||||
],
|
||||
"featured": true,
|
||||
"branding": {
|
||||
"logoUrl": "/brands/apps/jira.svg"
|
||||
},
|
||||
"urlPatterns": [
|
||||
"https://mcp.atlassian.com/*"
|
||||
],
|
||||
"docsUrl": "https://support.atlassian.com/atlassian-rovo-mcp-server/docs/getting-started-with-the-atlassian-remote-mcp-server/",
|
||||
"redirectConstraints": "https-or-loopback-http",
|
||||
"methods": [
|
||||
{
|
||||
"key": "mcp-oauth",
|
||||
"transport": "mcp_remote",
|
||||
"auth": "oauth",
|
||||
"ownershipModes": [
|
||||
"dcr"
|
||||
],
|
||||
"whenToUse": "Use browser sign-in for the provider-hosted MCP server.",
|
||||
"defaults": {
|
||||
"serverUrl": "https://mcp.atlassian.com/v1/mcp/authv2",
|
||||
"scopesHint": [
|
||||
"read:me",
|
||||
"read:account",
|
||||
"offline_access",
|
||||
"email",
|
||||
"read:jira-work",
|
||||
"write:jira-work",
|
||||
"search:confluence",
|
||||
"read:confluence-user",
|
||||
"read:page:confluence",
|
||||
"write:page:confluence",
|
||||
"read:comment:confluence",
|
||||
"write:comment:confluence",
|
||||
"read:space:confluence",
|
||||
"read:hierarchical-content:confluence",
|
||||
"write:component:compass",
|
||||
"read:component:compass",
|
||||
"read:scorecard:compass",
|
||||
"write:scorecard:compass",
|
||||
"read:event:compass",
|
||||
"read:metric:compass",
|
||||
"read:all:twg",
|
||||
"write:all:twg"
|
||||
]
|
||||
},
|
||||
"guidanceMd": "Connect Jira in the browser. An active Jira or Confluence site; tenant policy may require an administrator to approve the client.",
|
||||
"riskTier": "S3",
|
||||
"label": "Sign in with Jira",
|
||||
"consoleLinks": {
|
||||
"docs": "https://support.atlassian.com/atlassian-rovo-mcp-server/docs/getting-started-with-the-atlassian-remote-mcp-server/"
|
||||
},
|
||||
"warnings": [
|
||||
"An active Jira or Confluence site; tenant policy may require an administrator to approve the client."
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"slug": "kernel",
|
||||
"name": "Kernel",
|
||||
"description": "Connect Kernel's provider-hosted MCP server.",
|
||||
"categories": [
|
||||
"developer"
|
||||
],
|
||||
"featured": false,
|
||||
"branding": {
|
||||
"logoUrl": "/brands/apps/kernel.svg"
|
||||
},
|
||||
"urlPatterns": [
|
||||
"https://mcp.onkernel.com/*"
|
||||
],
|
||||
"docsUrl": "https://www.kernel.sh/docs/reference/mcp-server/",
|
||||
"redirectConstraints": "https-or-loopback-http",
|
||||
"methods": [
|
||||
{
|
||||
"key": "mcp-oauth",
|
||||
"transport": "mcp_remote",
|
||||
"auth": "oauth",
|
||||
"ownershipModes": [
|
||||
"dcr"
|
||||
],
|
||||
"whenToUse": "Use browser sign-in for the provider-hosted MCP server.",
|
||||
"defaults": {
|
||||
"serverUrl": "https://mcp.onkernel.com/mcp",
|
||||
"scopesHint": [
|
||||
"openid"
|
||||
]
|
||||
},
|
||||
"guidanceMd": "Connect Kernel in the browser. A Kernel account with either browser authorization or an API key.",
|
||||
"riskTier": "S3",
|
||||
"label": "Sign in with Kernel",
|
||||
"consoleLinks": {
|
||||
"docs": "https://www.kernel.sh/docs/reference/mcp-server/"
|
||||
},
|
||||
"warnings": [
|
||||
"A Kernel account with either browser authorization or an API key."
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "mcp-api-key",
|
||||
"transport": "mcp_remote",
|
||||
"auth": "api_key",
|
||||
"ownershipModes": [
|
||||
"customer"
|
||||
],
|
||||
"whenToUse": "Use a restricted customer-owned key when browser sign-in is not suitable.",
|
||||
"defaults": {
|
||||
"serverUrl": "https://mcp.onkernel.com/mcp"
|
||||
},
|
||||
"guidanceMd": "Use a customer-created Kernel key. A Kernel account with either browser authorization or an API key.",
|
||||
"riskTier": "S3",
|
||||
"label": "Use an API key",
|
||||
"credentialFields": [
|
||||
{
|
||||
"key": "authorization",
|
||||
"label": "Kernel API key",
|
||||
"type": "password",
|
||||
"required": true,
|
||||
"placeholder": "Paste your Kernel API key",
|
||||
"secret": true,
|
||||
"helperMd": "Launch/manage browsers, profiles, apps and browser automation. Create a key with read and write permissions for these actions; Paperclip cannot increase an existing key’s permissions."
|
||||
}
|
||||
],
|
||||
"keyPlacement": {
|
||||
"location": "header",
|
||||
"name": "X-API-Key",
|
||||
"prefix": null
|
||||
},
|
||||
"consoleLinks": {
|
||||
"keys": "https://www.kernel.sh/docs/reference/mcp-server/",
|
||||
"docs": "https://www.kernel.sh/docs/reference/mcp-server/"
|
||||
},
|
||||
"warnings": [
|
||||
"A Kernel account with either browser authorization or an API key."
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"slug": "klaviyo",
|
||||
"name": "Klaviyo",
|
||||
"description": "Review Klaviyo campaign performance and prepare campaign changes for review.",
|
||||
"categories": [
|
||||
"commerce"
|
||||
],
|
||||
"featured": false,
|
||||
"branding": {
|
||||
"logoUrl": "/brands/apps/klaviyo.png"
|
||||
},
|
||||
"urlPatterns": [
|
||||
"https://mcp.klaviyo.com/*"
|
||||
],
|
||||
"docsUrl": "https://developers.klaviyo.com/en/docs/connect_to_the_klaviyo_mcp_server",
|
||||
"redirectConstraints": "https-or-loopback-http",
|
||||
"methods": [
|
||||
{
|
||||
"key": "mcp-oauth",
|
||||
"label": "Sign in with Klaviyo",
|
||||
"transport": "mcp_remote",
|
||||
"auth": "oauth",
|
||||
"oauthClientRegistration": "dcr",
|
||||
"ownershipModes": [
|
||||
"dcr"
|
||||
],
|
||||
"whenToUse": "Connect to the provider-hosted Klaviyo MCP server with browser sign-in.",
|
||||
"defaults": {
|
||||
"serverUrl": "https://mcp.klaviyo.com/mcp?toolsets=campaigns:read,campaigns:write,metrics:read,segments:read"
|
||||
},
|
||||
"guidanceMd": "Review campaign and metric data, then prepare campaign changes for human review. Campaign write access may expose sends and other state changes; never send or schedule a campaign without explicit authorization and an allowed Paperclip action.",
|
||||
"riskTier": "S4",
|
||||
"tenantFields": [
|
||||
{
|
||||
"key": "readOnly",
|
||||
"label": "Read-only mode",
|
||||
"type": "checkbox",
|
||||
"advanced": true,
|
||||
"defaultValue": false,
|
||||
"helperMd": "Turn on to remove Klaviyo MCP tools that can modify account data. This provider-side restriction is separate from Paperclip action permissions.",
|
||||
"transport": {
|
||||
"location": "query",
|
||||
"name": "read-only",
|
||||
"format": "boolean",
|
||||
"omitFalse": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"consoleLinks": {
|
||||
"docs": "https://developers.klaviyo.com/en/docs/connect_to_the_klaviyo_mcp_server"
|
||||
},
|
||||
"warnings": [
|
||||
"Klaviyo's remote MCP requires an Owner, Admin, or Manager account role.",
|
||||
"The campaigns:write toolset may expose campaign sending and other writes. Keep write actions Off until reviewed; the provider's Advanced read-only mode is optional and does not replace Paperclip action permissions."
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"slug": "linear",
|
||||
"name": "Linear",
|
||||
"description": "Create, update, and read Linear issues.",
|
||||
"categories": [
|
||||
"productivity"
|
||||
],
|
||||
"featured": true,
|
||||
"branding": {
|
||||
"logoUrl": "/brands/apps/linear.svg",
|
||||
"darkLogoUrl": "/brands/apps/linear-dark.svg"
|
||||
},
|
||||
"urlPatterns": [
|
||||
"https://mcp.linear.app/*"
|
||||
],
|
||||
"methods": [
|
||||
{
|
||||
"key": "mcp-oauth",
|
||||
"transport": "mcp_remote",
|
||||
"auth": "oauth",
|
||||
"ownershipModes": [
|
||||
"dcr",
|
||||
"customer"
|
||||
],
|
||||
"whenToUse": "Use the provider-hosted connection for the quickest setup.",
|
||||
"defaults": {
|
||||
"serverUrl": "https://mcp.linear.app/mcp",
|
||||
"authorizationEndpoint": "https://linear.app/oauth/authorize",
|
||||
"tokenEndpoint": "https://api.linear.app/oauth/token",
|
||||
"scopesHint": [
|
||||
"read",
|
||||
"write"
|
||||
]
|
||||
},
|
||||
"guidanceMd": "Connect Linear for issues and projects. Paperclip registers its own OAuth client with Linear MCP; no developer-console setup is needed.",
|
||||
"riskTier": "S2",
|
||||
"requiredResourceFilters": [
|
||||
"workspace",
|
||||
"team",
|
||||
"project"
|
||||
],
|
||||
"credentialSources": {
|
||||
"vercelConnect": {
|
||||
"services": [
|
||||
"linear"
|
||||
],
|
||||
"principalModes": [
|
||||
"user"
|
||||
],
|
||||
"scopes": [
|
||||
"read",
|
||||
"write"
|
||||
],
|
||||
"header": {
|
||||
"name": "Authorization",
|
||||
"prefix": "Bearer "
|
||||
}
|
||||
}
|
||||
},
|
||||
"label": "Sign in with Linear",
|
||||
"consoleLinks": {
|
||||
"docs": "https://linear.app/docs/mcp"
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "mcp-api-key",
|
||||
"transport": "mcp_remote",
|
||||
"auth": "api_key",
|
||||
"ownershipModes": [
|
||||
"customer"
|
||||
],
|
||||
"whenToUse": "Use a Linear API key when browser sign-in is not suitable.",
|
||||
"defaults": {
|
||||
"serverUrl": "https://mcp.linear.app/mcp"
|
||||
},
|
||||
"guidanceMd": "Create a Linear API key with the permissions needed for the actions you intend to allow. A key restricted to Read is supported for read-only use; workspace and role access still apply.",
|
||||
"riskTier": "S2",
|
||||
"label": "Use an API key",
|
||||
"credentialFields": [
|
||||
{
|
||||
"key": "authorization",
|
||||
"label": "Linear API key",
|
||||
"type": "password",
|
||||
"required": true,
|
||||
"secret": true,
|
||||
"helperMd": "Choose intended permissions and teams when creating the personal API key. Read-only is supported with Read permission only.",
|
||||
"placeholder": "Paste your Linear API key"
|
||||
}
|
||||
],
|
||||
"keyPlacement": {
|
||||
"location": "header",
|
||||
"name": "Authorization",
|
||||
"prefix": "Bearer "
|
||||
},
|
||||
"consoleLinks": {
|
||||
"docs": "https://linear.app/docs/api-and-webhooks"
|
||||
},
|
||||
"warnings": [
|
||||
"Linear’s standard MCP endpoint exposes read and write tools; review the discovered catalog and Paperclip action policies."
|
||||
]
|
||||
}
|
||||
],
|
||||
"docsUrl": "https://linear.app/docs/mcp",
|
||||
"redirectConstraints": "https-or-loopback-http"
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"slug": "local-falcon",
|
||||
"name": "Local Falcon",
|
||||
"description": "Connect Local Falcon's provider-hosted MCP server.",
|
||||
"categories": [
|
||||
"analytics"
|
||||
],
|
||||
"featured": false,
|
||||
"branding": {
|
||||
"logoUrl": "/brands/apps/local-falcon.png"
|
||||
},
|
||||
"urlPatterns": [
|
||||
"https://mcp.localfalcon.com/*"
|
||||
],
|
||||
"docsUrl": "https://docs.localfalcon.com/",
|
||||
"redirectConstraints": "https-or-loopback-http",
|
||||
"methods": [
|
||||
{
|
||||
"key": "mcp-oauth",
|
||||
"transport": "mcp_remote",
|
||||
"auth": "oauth",
|
||||
"ownershipModes": [
|
||||
"dcr"
|
||||
],
|
||||
"whenToUse": "Use browser sign-in for the provider-hosted MCP server.",
|
||||
"defaults": {
|
||||
"serverUrl": "https://mcp.localfalcon.com",
|
||||
"scopesHint": [
|
||||
"api",
|
||||
"offline_access"
|
||||
]
|
||||
},
|
||||
"guidanceMd": "Connect Local Falcon in the browser. A Local Falcon account with MCP access.",
|
||||
"riskTier": "S2",
|
||||
"label": "Sign in with Local Falcon",
|
||||
"consoleLinks": {
|
||||
"docs": "https://docs.localfalcon.com/"
|
||||
},
|
||||
"warnings": [
|
||||
"A Local Falcon account with MCP access."
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"slug": "local",
|
||||
"name": "Local endpoint",
|
||||
"description": "Use a local model server in the agent’s execution environment.",
|
||||
"categories": [
|
||||
"ai"
|
||||
],
|
||||
"branding": {
|
||||
"logoUrl": "/brands/apps/api-key-generic.svg"
|
||||
},
|
||||
"urlPatterns": [],
|
||||
"methods": [
|
||||
{
|
||||
"key": "ai-api_key",
|
||||
"label": "Local endpoint API key",
|
||||
"purpose": "ai",
|
||||
"transport": "runtime_auth",
|
||||
"auth": "api_key",
|
||||
"ai": {
|
||||
"provider": "openai",
|
||||
"method": "api_key"
|
||||
},
|
||||
"grantKinds": [
|
||||
"user",
|
||||
"organization"
|
||||
],
|
||||
"ownershipModes": [
|
||||
"customer"
|
||||
],
|
||||
"whenToUse": "Use a local model server in the agent’s execution environment.",
|
||||
"guidanceMd": "Use your personal account or an explicitly shared company account.",
|
||||
"riskTier": "S3",
|
||||
"credentialFields": [
|
||||
{
|
||||
"key": "apiKey",
|
||||
"label": "API key",
|
||||
"type": "password",
|
||||
"required": true,
|
||||
"placeholder": "Enter API key",
|
||||
"secret": true
|
||||
}
|
||||
],
|
||||
"keyPlacement": {
|
||||
"location": "env",
|
||||
"name": "OPENAI_API_KEY"
|
||||
}
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"model-provider"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"slug": "make",
|
||||
"name": "Make",
|
||||
"description": "Connect Make's provider-hosted MCP server.",
|
||||
"categories": [
|
||||
"productivity"
|
||||
],
|
||||
"featured": false,
|
||||
"branding": {
|
||||
"logoUrl": "/brands/apps/make.svg",
|
||||
"darkLogoUrl": "/brands/apps/make-dark.svg"
|
||||
},
|
||||
"urlPatterns": [
|
||||
"https://mcp.make.com/*",
|
||||
"https://*.make.com/mcp"
|
||||
],
|
||||
"docsUrl": "https://developers.make.com/mcp-server",
|
||||
"redirectConstraints": "https-or-loopback-http",
|
||||
"methods": [
|
||||
{
|
||||
"key": "mcp-oauth",
|
||||
"transport": "mcp_remote",
|
||||
"auth": "oauth",
|
||||
"ownershipModes": [
|
||||
"dcr"
|
||||
],
|
||||
"whenToUse": "Use browser sign-in for the provider-hosted MCP server.",
|
||||
"defaults": {
|
||||
"serverUrl": "https://mcp.make.com"
|
||||
},
|
||||
"guidanceMd": "Connect Make in the browser. A Make account and access to the scenarios exposed to MCP.",
|
||||
"riskTier": "S3",
|
||||
"label": "Sign in with Make",
|
||||
"consoleLinks": {
|
||||
"docs": "https://developers.make.com/mcp-server"
|
||||
},
|
||||
"warnings": [
|
||||
"A Make account and access to the scenarios exposed to MCP."
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "mcp-api-token",
|
||||
"transport": "mcp_remote",
|
||||
"auth": "api_key",
|
||||
"ownershipModes": [
|
||||
"customer"
|
||||
],
|
||||
"whenToUse": "Use a Make MCP token when browser sign-in is not suitable.",
|
||||
"defaults": {
|
||||
"serverUrlTemplate": "https://{zone}/mcp"
|
||||
},
|
||||
"guidanceMd": "Create a Make API token with the mcp:use scope and any additional scopes required by the selected scenarios. Enter the zone host from your Make organization. The token is sent as a Bearer header and remains a secret.",
|
||||
"riskTier": "S3",
|
||||
"label": "Use an MCP token",
|
||||
"tenantFields": [
|
||||
{
|
||||
"key": "zone",
|
||||
"label": "Make zone host",
|
||||
"type": "text",
|
||||
"required": true,
|
||||
"placeholder": "eu2.make.com",
|
||||
"helperMd": "Enter the zone host for your Make organization exactly as shown in the Make MCP client setup instructions. The server URL is https://<zone>/mcp.",
|
||||
"validation": {
|
||||
"pattern": "^[A-Za-z0-9-]+\\.make\\.com$",
|
||||
"maxLength": 253
|
||||
}
|
||||
}
|
||||
],
|
||||
"credentialFields": [
|
||||
{
|
||||
"key": "authorization",
|
||||
"label": "Make MCP token",
|
||||
"type": "password",
|
||||
"required": true,
|
||||
"placeholder": "Paste your MCP token",
|
||||
"secret": true,
|
||||
"helperMd": "Provider-issued Make token. Include documented mcp:use and only additional scopes required by selected scenarios; exact scopes are user selected."
|
||||
}
|
||||
],
|
||||
"keyPlacement": {
|
||||
"location": "header",
|
||||
"name": "Authorization",
|
||||
"prefix": "Bearer "
|
||||
},
|
||||
"consoleLinks": {
|
||||
"docs": "https://developers.make.com/mcp-server/connect-using-mcp-token"
|
||||
},
|
||||
"warnings": [
|
||||
"A token is a secret; do not place it in the server URL. Scenario availability depends on token scopes and scenario access controls."
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"slug": "manufact",
|
||||
"name": "Manufact",
|
||||
"description": "Connect Manufact's provider-hosted MCP server.",
|
||||
"categories": [
|
||||
"productivity"
|
||||
],
|
||||
"featured": false,
|
||||
"branding": {
|
||||
"logoUrl": "/brands/apps/manufact.svg",
|
||||
"darkLogoUrl": "/brands/apps/manufact-dark.svg"
|
||||
},
|
||||
"urlPatterns": [
|
||||
"https://mcp.manufact.com/*"
|
||||
],
|
||||
"docsUrl": "https://docs.manufact.com/mcp",
|
||||
"redirectConstraints": "https-or-loopback-http",
|
||||
"methods": [
|
||||
{
|
||||
"key": "mcp-oauth",
|
||||
"transport": "mcp_remote",
|
||||
"auth": "oauth",
|
||||
"ownershipModes": [
|
||||
"dcr"
|
||||
],
|
||||
"whenToUse": "Use browser sign-in for the provider-hosted MCP server.",
|
||||
"defaults": {
|
||||
"serverUrl": "https://mcp.manufact.com/mcp",
|
||||
"scopesHint": [
|
||||
"openid",
|
||||
"profile",
|
||||
"email",
|
||||
"offline_access"
|
||||
]
|
||||
},
|
||||
"guidanceMd": "Connect Manufact in the browser. A Manufact account with MCP access.",
|
||||
"riskTier": "S3",
|
||||
"label": "Sign in with Manufact",
|
||||
"consoleLinks": {
|
||||
"docs": "https://docs.manufact.com/mcp"
|
||||
},
|
||||
"warnings": [
|
||||
"A Manufact account with MCP access."
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"slug": "mem0",
|
||||
"name": "Mem0",
|
||||
"description": "Remember preferences, conversations, events, and agent state.",
|
||||
"categories": [
|
||||
"ai"
|
||||
],
|
||||
"featured": false,
|
||||
"branding": {
|
||||
"logoUrl": "/brands/apps/mem0.svg"
|
||||
},
|
||||
"urlPatterns": [
|
||||
"https://mcp.mem0.ai/*"
|
||||
],
|
||||
"docsUrl": "https://docs.mem0.ai/platform/mem0-mcp",
|
||||
"methods": [
|
||||
{
|
||||
"key": "mcp-api-key",
|
||||
"transport": "mcp_remote",
|
||||
"auth": "api_key",
|
||||
"ownershipModes": [
|
||||
"customer"
|
||||
],
|
||||
"whenToUse": "Use a restricted customer-owned key when browser sign-in is not suitable.",
|
||||
"defaults": {
|
||||
"serverUrl": "https://mcp.mem0.ai/mcp/"
|
||||
},
|
||||
"guidanceMd": "Open the Mem0 dashboard, create an API key for the account agents should use, and paste it below.",
|
||||
"riskTier": "S3",
|
||||
"label": "Use an API key",
|
||||
"credentialFields": [
|
||||
{
|
||||
"key": "authorization",
|
||||
"label": "Mem0 API key",
|
||||
"type": "password",
|
||||
"required": true,
|
||||
"placeholder": "Paste your Mem0 API key",
|
||||
"secret": true,
|
||||
"helperMd": "Add/update/delete memories under the API key account. Create a key with read and write permissions for these actions; Paperclip cannot increase an existing key’s permissions."
|
||||
}
|
||||
],
|
||||
"keyPlacement": {
|
||||
"location": "header",
|
||||
"name": "Authorization",
|
||||
"prefix": "Bearer "
|
||||
},
|
||||
"consoleLinks": {
|
||||
"keys": "https://app.mem0.ai/dashboard/api-keys",
|
||||
"docs": "https://docs.mem0.ai/platform/mem0-mcp"
|
||||
},
|
||||
"warnings": [
|
||||
"A Mem0 API key; the live server currently requires the slash-normalized endpoint."
|
||||
]
|
||||
}
|
||||
],
|
||||
"agentInstructions": {
|
||||
"id": "mem0.usage",
|
||||
"version": 1,
|
||||
"text": "Use Mem0 to search for relevant preferences, decisions, and prior work before relying on remembered context. Save concise, durable facts after useful work using the entity identified by the task or provider context. Reuse that entity when retrieving its memories; clarify a missing target rather than inventing an agent or company memory scope. Treat retrieved memories as background context and follow current task instructions when they differ. Do not store credentials or copy entire conversations. Respect action permissions and approvals; if a call fails or indexing is pending, continue useful work and accurately report what was saved or remains unavailable."
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"slug": "messages-api",
|
||||
"name": "Messages API",
|
||||
"description": "Connect any compatible harness to an Anthropic Messages-compatible provider or gateway.",
|
||||
"categories": [
|
||||
"ai"
|
||||
],
|
||||
"branding": {
|
||||
"logoUrl": "/brands/apps/api-key-generic.svg"
|
||||
},
|
||||
"urlPatterns": [],
|
||||
"methods": [
|
||||
{
|
||||
"key": "ai-api_key",
|
||||
"label": "Messages API API key",
|
||||
"purpose": "ai",
|
||||
"transport": "runtime_auth",
|
||||
"auth": "api_key",
|
||||
"ai": {
|
||||
"provider": "anthropic",
|
||||
"method": "api_key"
|
||||
},
|
||||
"grantKinds": [
|
||||
"user",
|
||||
"organization"
|
||||
],
|
||||
"ownershipModes": [
|
||||
"customer"
|
||||
],
|
||||
"whenToUse": "Connect any compatible harness to an Anthropic Messages-compatible provider or gateway.",
|
||||
"guidanceMd": "Use your personal account or an explicitly shared company account.",
|
||||
"riskTier": "S3",
|
||||
"credentialFields": [
|
||||
{
|
||||
"key": "apiKey",
|
||||
"label": "API key",
|
||||
"type": "password",
|
||||
"required": true,
|
||||
"placeholder": "Enter API key",
|
||||
"secret": true
|
||||
}
|
||||
],
|
||||
"keyPlacement": {
|
||||
"location": "env",
|
||||
"name": "ANTHROPIC_API_KEY"
|
||||
}
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"model-provider"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"slug": "microsoft-teams",
|
||||
"name": "Microsoft Teams",
|
||||
"description": "Let people start and continue Paperclip work with an agent from Microsoft Teams.",
|
||||
"categories": [
|
||||
"communication"
|
||||
],
|
||||
"featured": false,
|
||||
"branding": {
|
||||
"logoUrl": "/brands/apps/microsoft-teams.svg"
|
||||
},
|
||||
"urlPatterns": [
|
||||
"https://teams.microsoft.com/*"
|
||||
],
|
||||
"methods": [
|
||||
{
|
||||
"key": "chat-agent",
|
||||
"label": "Chat with an agent",
|
||||
"purpose": "channel",
|
||||
"provider": "microsoft-teams",
|
||||
"transport": "chat_sdk",
|
||||
"auth": "api_key",
|
||||
"ownershipModes": [
|
||||
"customer"
|
||||
],
|
||||
"whenToUse": "Let people in Microsoft Teams start and continue work with one Paperclip agent.",
|
||||
"credentialFields": [
|
||||
{
|
||||
"key": "clientId",
|
||||
"label": "Application / Client ID",
|
||||
"type": "text",
|
||||
"required": true,
|
||||
"placeholder": "00000000-0000-0000-0000-000000000000",
|
||||
"secret": false
|
||||
},
|
||||
{
|
||||
"key": "tenantId",
|
||||
"label": "Directory / Tenant ID",
|
||||
"type": "text",
|
||||
"required": true,
|
||||
"placeholder": "00000000-0000-0000-0000-000000000000",
|
||||
"secret": false
|
||||
},
|
||||
{
|
||||
"key": "clientSecret",
|
||||
"label": "Client secret",
|
||||
"type": "password",
|
||||
"required": true,
|
||||
"placeholder": "Paste the client-secret value",
|
||||
"secret": true
|
||||
}
|
||||
],
|
||||
"guidanceMd": "Use a Microsoft 365 work or school organization where you can register an Entra app, create a single-tenant Azure Bot, and upload or install a Teams app. Personal or free Teams accounts at teams.live.com cannot complete this setup. Enable personal, team, and groupChat bot scopes and the ChannelMessage.Read.Group and ChatMessage.Read.Chat resource-specific application permissions. Those RSC grants let an installed app receive every message in a team or group chat without an @mention, so explain that access to installers. One team install covers its standard channels; private and shared channels require a separate installation and are not supported by this release.",
|
||||
"consoleLinks": {
|
||||
"register": "https://dev.teams.microsoft.com/apps",
|
||||
"docs": "https://learn.microsoft.com/en-us/microsoftteams/platform/bots/how-to/create-a-bot-for-teams"
|
||||
},
|
||||
"riskTier": "S3",
|
||||
"requiredResourceFilters": [
|
||||
"team",
|
||||
"channel",
|
||||
"chat"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"slug": "miro",
|
||||
"name": "Miro",
|
||||
"description": "Connect Miro's provider-hosted MCP server.",
|
||||
"categories": [
|
||||
"productivity"
|
||||
],
|
||||
"featured": false,
|
||||
"branding": {
|
||||
"logoUrl": "/brands/apps/miro.svg"
|
||||
},
|
||||
"urlPatterns": [
|
||||
"https://mcp.miro.com/*"
|
||||
],
|
||||
"docsUrl": "https://help.miro.com/hc/en-us/articles/31625301583890-How-to-enable-Miro-s-MCP-Server-user-guide",
|
||||
"redirectConstraints": "https-or-loopback-http",
|
||||
"methods": [
|
||||
{
|
||||
"key": "mcp-oauth",
|
||||
"transport": "mcp_remote",
|
||||
"auth": "oauth",
|
||||
"ownershipModes": [
|
||||
"dcr"
|
||||
],
|
||||
"whenToUse": "Use browser sign-in for the provider-hosted MCP server.",
|
||||
"defaults": {
|
||||
"serverUrl": "https://mcp.miro.com/",
|
||||
"scopesHint": [
|
||||
"boards:read",
|
||||
"boards:write",
|
||||
"openid",
|
||||
"email"
|
||||
]
|
||||
},
|
||||
"guidanceMd": "Connect Miro in the browser. A Miro account; enterprise administrators may restrict third-party MCP clients.",
|
||||
"riskTier": "S3",
|
||||
"label": "Sign in with Miro",
|
||||
"consoleLinks": {
|
||||
"docs": "https://help.miro.com/hc/en-us/articles/31625301583890-How-to-enable-Miro-s-MCP-Server-user-guide"
|
||||
},
|
||||
"warnings": [
|
||||
"A Miro account; enterprise administrators may restrict third-party MCP clients."
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"slug": "mixpanel",
|
||||
"name": "Mixpanel",
|
||||
"description": "Connect Mixpanel's provider-hosted MCP server.",
|
||||
"categories": [
|
||||
"analytics"
|
||||
],
|
||||
"featured": false,
|
||||
"branding": {
|
||||
"logoUrl": "/brands/apps/mixpanel.svg",
|
||||
"darkLogoUrl": "/brands/apps/mixpanel-dark.svg"
|
||||
},
|
||||
"urlPatterns": [
|
||||
"https://mcp.mixpanel.com/*"
|
||||
],
|
||||
"docsUrl": "https://mixpanel.com/blog/mixpanel-mcp-server/",
|
||||
"redirectConstraints": "https-or-loopback-http",
|
||||
"methods": [
|
||||
{
|
||||
"key": "mcp-oauth",
|
||||
"transport": "mcp_remote",
|
||||
"auth": "oauth",
|
||||
"ownershipModes": [
|
||||
"dcr"
|
||||
],
|
||||
"whenToUse": "Use browser sign-in for the provider-hosted MCP server.",
|
||||
"defaults": {
|
||||
"serverUrl": "https://mcp.mixpanel.com/mcp",
|
||||
"scopesHint": [
|
||||
"projects",
|
||||
"analysis",
|
||||
"events",
|
||||
"insights",
|
||||
"segmentation",
|
||||
"retention",
|
||||
"data:read",
|
||||
"funnels",
|
||||
"flows",
|
||||
"data_definitions",
|
||||
"alerts",
|
||||
"annotations",
|
||||
"bookmarks",
|
||||
"business_context",
|
||||
"cohorts",
|
||||
"context",
|
||||
"custom_alerts",
|
||||
"custom_events",
|
||||
"custom_properties",
|
||||
"dashboard_reports",
|
||||
"experiments",
|
||||
"feature_flags",
|
||||
"metrics",
|
||||
"user_details"
|
||||
]
|
||||
},
|
||||
"guidanceMd": "Connect Mixpanel in the browser. A Mixpanel account; the hosted MCP server is currently beta.",
|
||||
"riskTier": "S3",
|
||||
"label": "Sign in with Mixpanel",
|
||||
"consoleLinks": {
|
||||
"docs": "https://mixpanel.com/blog/mixpanel-mcp-server/"
|
||||
},
|
||||
"warnings": [
|
||||
"A Mixpanel account; the hosted MCP server is currently beta.",
|
||||
"This provider's hosted MCP server is currently beta or preview."
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"slug": "monday",
|
||||
"name": "monday.com",
|
||||
"description": "Read and manage boards and work items in monday.com.",
|
||||
"categories": [
|
||||
"productivity"
|
||||
],
|
||||
"featured": false,
|
||||
"branding": {
|
||||
"logoUrl": "/brands/apps/monday.png",
|
||||
"darkLogoUrl": "/brands/apps/monday-dark.png"
|
||||
},
|
||||
"urlPatterns": [
|
||||
"https://mcp.monday.com/*"
|
||||
],
|
||||
"docsUrl": "https://developer.monday.com/api-reference/docs/integrate-with-monday-mcp",
|
||||
"redirectConstraints": "https-or-loopback-http",
|
||||
"methods": [
|
||||
{
|
||||
"key": "mcp-oauth",
|
||||
"label": "Sign in with monday.com",
|
||||
"transport": "mcp_remote",
|
||||
"auth": "oauth",
|
||||
"ownershipModes": [
|
||||
"dcr"
|
||||
],
|
||||
"whenToUse": "Connect the hosted monday.com MCP server using OAuth and dynamic client registration.",
|
||||
"defaults": {
|
||||
"serverUrl": "https://mcp.monday.com/mcp"
|
||||
},
|
||||
"guidanceMd": "Use the selected monday.com account only. Read the target board and its columns before acting, and verify the exact board and item before any change. Keep write and destructive actions Off unless the user explicitly asks for a specific change. Never send messages, publish, or trigger automations through a board action without separate authorization.",
|
||||
"warnings": [
|
||||
"The hosted MCP server acts as the authorizing monday.com user and inherits that user's monday.com permissions. An account admin may also restrict MCP access to selected workspaces.",
|
||||
"An additional OAuth/DCR connection can require account-admin approval for Paperclip and its exact callback URL before user authorization completes. If approval is pending, reconnect after the account admin approves it.",
|
||||
"monday.com personal API tokens are documented for personal use, prototyping, and testing, and the provider says they are not intended for integrations used by other people. This connector uses the OAuth/DCR route for additional MCP clients; account-level approval may be required.",
|
||||
"The tool catalog includes actions that can send notifications (optionally email), delete or activate automations, publish workflows, and create or run monday.com platform agents; a low-level tool can execute GraphQL mutations. Keep these actions Off unless separately authorized, and never run agents or change automations during qualification.",
|
||||
"MCP calls count toward the account's monday.com API call limits. Keep tests bounded and avoid repeated discovery or broad queries."
|
||||
],
|
||||
"consoleLinks": {
|
||||
"docs": "https://developer.monday.com/api-reference/docs/integrate-with-monday-mcp"
|
||||
},
|
||||
"riskTier": "S4"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"slug": "neon",
|
||||
"name": "Neon",
|
||||
"description": "Manage Postgres projects and branches, run SQL, and inspect schemas in Neon.",
|
||||
"categories": [
|
||||
"data"
|
||||
],
|
||||
"featured": false,
|
||||
"branding": {
|
||||
"logoUrl": "/brands/apps/neon.png"
|
||||
},
|
||||
"urlPatterns": [
|
||||
"https://mcp.neon.tech/*"
|
||||
],
|
||||
"docsUrl": "https://neon.com/docs/ai/neon-mcp-server",
|
||||
"redirectConstraints": "https-or-loopback-http",
|
||||
"methods": [
|
||||
{
|
||||
"key": "mcp-oauth",
|
||||
"transport": "mcp_remote",
|
||||
"auth": "oauth",
|
||||
"ownershipModes": [
|
||||
"dcr"
|
||||
],
|
||||
"whenToUse": "Use browser sign-in for the provider-hosted MCP server.",
|
||||
"defaults": {
|
||||
"serverUrl": "https://mcp.neon.tech/mcp",
|
||||
"scopesHint": [
|
||||
"read",
|
||||
"write"
|
||||
]
|
||||
},
|
||||
"guidanceMd": "Connect Neon in the browser. Open Advanced to pin one project or enable read-only mode. Write tools start enabled and remain governed by Paperclip's action policies.",
|
||||
"riskTier": "S4",
|
||||
"label": "Sign in with Neon",
|
||||
"consoleLinks": {
|
||||
"docs": "https://neon.com/docs/ai/neon-mcp-server"
|
||||
},
|
||||
"warnings": [
|
||||
"A Neon account. The hosted server grants broad project and database management, so use a development project and review write actions before connecting production data.",
|
||||
"Neon recommends its hosted server for development and testing. Review write and destructive actions before execution."
|
||||
],
|
||||
"tenantFields": [
|
||||
{
|
||||
"key": "projectId",
|
||||
"label": "Pin to project ID",
|
||||
"type": "text",
|
||||
"advanced": true,
|
||||
"placeholder": "Optional Neon project ID",
|
||||
"helperMd": "Optional. Restrict this connection to one project. Copy the project ID from Neon Console → Project settings → General.",
|
||||
"validation": {
|
||||
"pattern": "^[a-z0-9-]+$",
|
||||
"maxLength": 64
|
||||
},
|
||||
"transport": {
|
||||
"location": "query",
|
||||
"name": "projectId"
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "readOnly",
|
||||
"label": "Read-only mode",
|
||||
"type": "checkbox",
|
||||
"defaultValue": false,
|
||||
"helperMd": "Enable this to limit SQL to SELECT queries and schema inspection.",
|
||||
"transport": {
|
||||
"location": "query",
|
||||
"name": "readonly",
|
||||
"format": "boolean",
|
||||
"omitFalse": true
|
||||
},
|
||||
"advanced": true
|
||||
}
|
||||
],
|
||||
"requiredResourceFilters": [
|
||||
"project"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "mcp-api-key",
|
||||
"transport": "mcp_remote",
|
||||
"auth": "api_key",
|
||||
"ownershipModes": [
|
||||
"customer"
|
||||
],
|
||||
"whenToUse": "Use a restricted customer-owned key when browser sign-in is not suitable.",
|
||||
"defaults": {
|
||||
"serverUrl": "https://mcp.neon.tech/mcp"
|
||||
},
|
||||
"guidanceMd": "Use a customer-created Neon API key. Prefer a project-scoped key for one development project; personal and organization keys reach every project they can access. Write tools start enabled and remain governed by Paperclip's action policies.",
|
||||
"riskTier": "S4",
|
||||
"label": "Use an API key",
|
||||
"credentialFields": [
|
||||
{
|
||||
"key": "authorization",
|
||||
"label": "Neon API key",
|
||||
"type": "password",
|
||||
"required": true,
|
||||
"placeholder": "napi_... or neon_project_key_...",
|
||||
"secret": true,
|
||||
"helperMd": "Project, branch, compute, snapshot, SQL and schema changes within the key’s reach. A project-scoped key limits access to one project with Editor rights; personal and organization keys reach every project they can access. Paperclip cannot increase an existing key’s permissions."
|
||||
}
|
||||
],
|
||||
"keyPlacement": {
|
||||
"location": "header",
|
||||
"name": "Authorization",
|
||||
"prefix": "Bearer "
|
||||
},
|
||||
"consoleLinks": {
|
||||
"keys": "https://console.neon.tech/app/settings/api-keys",
|
||||
"docs": "https://neon.com/docs/ai/neon-mcp-server"
|
||||
},
|
||||
"warnings": [
|
||||
"A Neon account. The hosted server grants broad project and database management, so use a development project and review write actions before connecting production data.",
|
||||
"Neon recommends its hosted server for development and testing. Review write and destructive actions before execution."
|
||||
],
|
||||
"tenantFields": [
|
||||
{
|
||||
"key": "projectId",
|
||||
"label": "Pin to project ID",
|
||||
"type": "text",
|
||||
"advanced": true,
|
||||
"placeholder": "Optional Neon project ID",
|
||||
"helperMd": "Optional. Restrict this connection to one project. Copy the project ID from Neon Console → Project settings → General.",
|
||||
"validation": {
|
||||
"pattern": "^[a-z0-9-]+$",
|
||||
"maxLength": 64
|
||||
},
|
||||
"transport": {
|
||||
"location": "query",
|
||||
"name": "projectId"
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "readOnly",
|
||||
"label": "Read-only mode",
|
||||
"type": "checkbox",
|
||||
"defaultValue": false,
|
||||
"helperMd": "Enable this to limit SQL to SELECT queries and schema inspection.",
|
||||
"transport": {
|
||||
"location": "query",
|
||||
"name": "readonly",
|
||||
"format": "boolean",
|
||||
"omitFalse": true
|
||||
},
|
||||
"advanced": true
|
||||
}
|
||||
],
|
||||
"requiredResourceFilters": [
|
||||
"project"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"slug": "netlify",
|
||||
"name": "Netlify",
|
||||
"description": "Connect Netlify's provider-hosted MCP server.",
|
||||
"categories": [
|
||||
"developer"
|
||||
],
|
||||
"featured": false,
|
||||
"branding": {
|
||||
"logoUrl": "/brands/apps/netlify.svg",
|
||||
"darkLogoUrl": "/brands/apps/netlify-dark.svg"
|
||||
},
|
||||
"urlPatterns": [
|
||||
"https://netlify-mcp.netlify.app/*"
|
||||
],
|
||||
"docsUrl": "https://docs.netlify.com/build/build-with-ai/agent-setup-guides/agent-setup-overview/",
|
||||
"redirectConstraints": "https-or-loopback-http",
|
||||
"methods": [
|
||||
{
|
||||
"key": "mcp-oauth",
|
||||
"transport": "mcp_remote",
|
||||
"auth": "oauth",
|
||||
"ownershipModes": [
|
||||
"dcr"
|
||||
],
|
||||
"whenToUse": "Use browser sign-in for the provider-hosted MCP server.",
|
||||
"defaults": {
|
||||
"serverUrl": "https://netlify-mcp.netlify.app/mcp",
|
||||
"scopesHint": [
|
||||
"offline_access",
|
||||
"read",
|
||||
"write"
|
||||
]
|
||||
},
|
||||
"guidanceMd": "Connect Netlify in the browser. A Netlify account with access to the relevant team and sites.",
|
||||
"riskTier": "S3",
|
||||
"label": "Sign in with Netlify",
|
||||
"consoleLinks": {
|
||||
"docs": "https://docs.netlify.com/build/build-with-ai/agent-setup-guides/agent-setup-overview/"
|
||||
},
|
||||
"warnings": [
|
||||
"A Netlify account with access to the relevant team and sites."
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"slug": "notion",
|
||||
"name": "Notion",
|
||||
"description": "Read and update pages in your Notion workspace.",
|
||||
"categories": [
|
||||
"content"
|
||||
],
|
||||
"featured": true,
|
||||
"branding": {
|
||||
"logoUrl": "/brands/apps/notion.svg"
|
||||
},
|
||||
"urlPatterns": [
|
||||
"https://mcp.notion.com/*"
|
||||
],
|
||||
"methods": [
|
||||
{
|
||||
"key": "mcp-oauth",
|
||||
"transport": "mcp_remote",
|
||||
"auth": "oauth",
|
||||
"ownershipModes": [
|
||||
"customer",
|
||||
"dcr"
|
||||
],
|
||||
"whenToUse": "Use the provider-hosted connection for the quickest setup.",
|
||||
"defaults": {
|
||||
"serverUrl": "https://mcp.notion.com/mcp",
|
||||
"scopesHint": [
|
||||
"default"
|
||||
]
|
||||
},
|
||||
"guidanceMd": "Connect Notion for workspace content. Share only the pages and databases agents should use.",
|
||||
"riskTier": "S3",
|
||||
"requiredResourceFilters": [
|
||||
"workspace",
|
||||
"page",
|
||||
"database"
|
||||
],
|
||||
"credentialSources": {
|
||||
"vercelConnect": {
|
||||
"services": [
|
||||
"notion"
|
||||
],
|
||||
"principalModes": [
|
||||
"user"
|
||||
],
|
||||
"scopes": [
|
||||
"*"
|
||||
],
|
||||
"header": {
|
||||
"name": "Authorization",
|
||||
"prefix": "Bearer "
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"redirectConstraints": "https-or-loopback-http",
|
||||
"docsUrl": "https://developers.notion.com/guides/mcp/build-mcp-client"
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"slug": "oauth-generic",
|
||||
"name": "OAuth app",
|
||||
"description": "Connect a provider using your own OAuth client.",
|
||||
"categories": [
|
||||
"other"
|
||||
],
|
||||
"featured": false,
|
||||
"branding": {
|
||||
"logoUrl": "/brands/apps/oauth-generic.svg"
|
||||
},
|
||||
"urlPatterns": [],
|
||||
"methods": [
|
||||
{
|
||||
"key": "oauth",
|
||||
"transport": "rest_api",
|
||||
"auth": "oauth",
|
||||
"ownershipModes": [
|
||||
"customer",
|
||||
"dcr"
|
||||
],
|
||||
"whenToUse": "Use credentials from your provider account.",
|
||||
"defaults": {},
|
||||
"guidanceMd": "Register an OAuth client with the provider and add Paperclip's redirect URI.",
|
||||
"riskTier": "S3",
|
||||
"credentialFields": [
|
||||
{
|
||||
"key": "clientId",
|
||||
"label": "Client ID",
|
||||
"type": "text",
|
||||
"required": true,
|
||||
"placeholder": "Paste the client ID",
|
||||
"secret": false
|
||||
},
|
||||
{
|
||||
"key": "clientSecret",
|
||||
"label": "Client secret",
|
||||
"type": "password",
|
||||
"required": true,
|
||||
"placeholder": "Paste the client secret",
|
||||
"secret": true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"slug": "openai",
|
||||
"name": "OpenAI",
|
||||
"description": "Connect OpenAI accounts for your agents.",
|
||||
"categories": [
|
||||
"ai"
|
||||
],
|
||||
"branding": {
|
||||
"logoUrl": "/brands/apps/openai.svg",
|
||||
"darkLogoUrl": "/brands/apps/openai-dark.svg"
|
||||
},
|
||||
"urlPatterns": [
|
||||
"https://api.openai.com/*"
|
||||
],
|
||||
"methods": [
|
||||
{
|
||||
"key": "ai-subscription",
|
||||
"label": "OpenAI subscription",
|
||||
"purpose": "ai",
|
||||
"transport": "runtime_auth",
|
||||
"auth": "oauth",
|
||||
"ai": {
|
||||
"provider": "openai",
|
||||
"method": "subscription"
|
||||
},
|
||||
"grantKinds": [
|
||||
"user",
|
||||
"organization"
|
||||
],
|
||||
"ownershipModes": [
|
||||
"customer"
|
||||
],
|
||||
"whenToUse": "Authenticate an agent with this account.",
|
||||
"guidanceMd": "Use your personal account or an explicitly shared company account.",
|
||||
"riskTier": "S3"
|
||||
},
|
||||
{
|
||||
"key": "ai-api_key",
|
||||
"label": "OpenAI API key",
|
||||
"purpose": "ai",
|
||||
"transport": "runtime_auth",
|
||||
"auth": "api_key",
|
||||
"ai": {
|
||||
"provider": "openai",
|
||||
"method": "api_key"
|
||||
},
|
||||
"grantKinds": [
|
||||
"user",
|
||||
"organization"
|
||||
],
|
||||
"ownershipModes": [
|
||||
"customer"
|
||||
],
|
||||
"whenToUse": "Authenticate an agent with this account.",
|
||||
"guidanceMd": "Use your personal account or an explicitly shared company account.",
|
||||
"riskTier": "S3",
|
||||
"credentialFields": [
|
||||
{
|
||||
"key": "apiKey",
|
||||
"label": "API key",
|
||||
"type": "password",
|
||||
"required": true,
|
||||
"placeholder": "Enter API key",
|
||||
"secret": true
|
||||
}
|
||||
],
|
||||
"keyPlacement": {
|
||||
"location": "env",
|
||||
"name": "OPENAI_API_KEY"
|
||||
}
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"model-provider"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"slug": "openrouter",
|
||||
"name": "OpenRouter",
|
||||
"description": "Connect OpenRouter accounts for your agents.",
|
||||
"categories": [
|
||||
"ai"
|
||||
],
|
||||
"branding": {
|
||||
"logoUrl": "/brands/apps/openrouter.svg",
|
||||
"darkLogoUrl": "/brands/apps/openrouter-dark.svg"
|
||||
},
|
||||
"urlPatterns": [
|
||||
"https://openrouter.ai/api/*"
|
||||
],
|
||||
"methods": [
|
||||
{
|
||||
"key": "ai-api_key",
|
||||
"label": "OpenRouter API key",
|
||||
"purpose": "ai",
|
||||
"transport": "runtime_auth",
|
||||
"auth": "api_key",
|
||||
"ai": {
|
||||
"provider": "openrouter",
|
||||
"method": "api_key"
|
||||
},
|
||||
"grantKinds": [
|
||||
"user",
|
||||
"organization"
|
||||
],
|
||||
"ownershipModes": [
|
||||
"customer"
|
||||
],
|
||||
"whenToUse": "Authenticate an agent with this account.",
|
||||
"guidanceMd": "Use your personal account or an explicitly shared company account.",
|
||||
"riskTier": "S3",
|
||||
"credentialFields": [
|
||||
{
|
||||
"key": "apiKey",
|
||||
"label": "API key",
|
||||
"type": "password",
|
||||
"required": true,
|
||||
"placeholder": "Enter API key",
|
||||
"secret": true
|
||||
}
|
||||
],
|
||||
"keyPlacement": {
|
||||
"location": "env",
|
||||
"name": "OPENROUTER_API_KEY"
|
||||
}
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"model-provider"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"slug": "oreilly",
|
||||
"name": "O'Reilly",
|
||||
"description": "Connect O'Reilly's provider-hosted MCP server.",
|
||||
"categories": [
|
||||
"content"
|
||||
],
|
||||
"featured": false,
|
||||
"branding": {
|
||||
"logoUrl": "/brands/apps/oreilly.svg"
|
||||
},
|
||||
"urlPatterns": [
|
||||
"https://api.oreilly.com/*"
|
||||
],
|
||||
"docsUrl": "https://learning.oreilly.com/apidocs/mcp/content/",
|
||||
"redirectConstraints": "https-or-loopback-http",
|
||||
"methods": [
|
||||
{
|
||||
"key": "mcp-oauth",
|
||||
"transport": "mcp_remote",
|
||||
"auth": "oauth",
|
||||
"ownershipModes": [
|
||||
"dcr"
|
||||
],
|
||||
"whenToUse": "Use browser sign-in for the provider-hosted MCP server.",
|
||||
"defaults": {
|
||||
"serverUrl": "https://api.oreilly.com/api/content-discovery/v1/mcp/",
|
||||
"scopesHint": [
|
||||
"openid",
|
||||
"offline_access"
|
||||
]
|
||||
},
|
||||
"guidanceMd": "Connect O'Reilly in the browser. An O'Reilly Learning subscription with MCP or API access.",
|
||||
"riskTier": "S2",
|
||||
"label": "Sign in with O'Reilly",
|
||||
"consoleLinks": {
|
||||
"docs": "https://learning.oreilly.com/apidocs/mcp/content/"
|
||||
},
|
||||
"warnings": [
|
||||
"An O'Reilly Learning subscription with MCP or API access."
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "mcp-api-key",
|
||||
"transport": "mcp_remote",
|
||||
"auth": "api_key",
|
||||
"ownershipModes": [
|
||||
"customer"
|
||||
],
|
||||
"whenToUse": "Use a restricted customer-owned key when browser sign-in is not suitable.",
|
||||
"defaults": {
|
||||
"serverUrl": "https://api.oreilly.com/api/content-discovery/v1/mcp/"
|
||||
},
|
||||
"guidanceMd": "Use a customer-created O'Reilly key. An O'Reilly Learning subscription with MCP or API access.",
|
||||
"riskTier": "S2",
|
||||
"label": "Use an API key",
|
||||
"credentialFields": [
|
||||
{
|
||||
"key": "authorization",
|
||||
"label": "O'Reilly API key",
|
||||
"type": "password",
|
||||
"required": true,
|
||||
"placeholder": "Paste your O'Reilly API token",
|
||||
"secret": true,
|
||||
"helperMd": "None: content discovery and retrieval. A read-only key is sufficient."
|
||||
}
|
||||
],
|
||||
"keyPlacement": {
|
||||
"location": "header",
|
||||
"name": "Authorization",
|
||||
"prefix": "Bearer "
|
||||
},
|
||||
"consoleLinks": {
|
||||
"keys": "https://learning.oreilly.com/apidocs/mcp/content/",
|
||||
"docs": "https://learning.oreilly.com/apidocs/mcp/content/"
|
||||
},
|
||||
"warnings": [
|
||||
"An O'Reilly Learning subscription with MCP or API access."
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"slug": "pagerduty",
|
||||
"name": "PagerDuty",
|
||||
"description": "Connect PagerDuty's provider-hosted MCP server.",
|
||||
"categories": [
|
||||
"developer"
|
||||
],
|
||||
"featured": false,
|
||||
"branding": {
|
||||
"logoUrl": "/brands/apps/pagerduty.svg"
|
||||
},
|
||||
"urlPatterns": [
|
||||
"https://mcp.pagerduty.com/*"
|
||||
],
|
||||
"docsUrl": "https://support.pagerduty.com/main/docs/pagerduty-mcp-server",
|
||||
"methods": [
|
||||
{
|
||||
"key": "mcp-api-key-us",
|
||||
"transport": "mcp_remote",
|
||||
"auth": "api_key",
|
||||
"ownershipModes": [
|
||||
"customer"
|
||||
],
|
||||
"whenToUse": "Use a restricted customer-owned key when browser sign-in is not suitable.",
|
||||
"defaults": {
|
||||
"serverUrl": "https://mcp.pagerduty.com/mcp"
|
||||
},
|
||||
"guidanceMd": "Use a customer-created PagerDuty key. A PagerDuty API token; choose the regional endpoint that hosts the account.",
|
||||
"riskTier": "S4",
|
||||
"label": "US service region",
|
||||
"credentialFields": [
|
||||
{
|
||||
"key": "authorization",
|
||||
"label": "PagerDuty API key",
|
||||
"type": "password",
|
||||
"required": true,
|
||||
"placeholder": "Paste your PagerDuty user API token",
|
||||
"secret": true,
|
||||
"helperMd": "Incident and on-call changes require a full-access API key and role access. Create a key with read and write permissions for these actions; Paperclip cannot increase an existing key’s permissions."
|
||||
}
|
||||
],
|
||||
"keyPlacement": {
|
||||
"location": "header",
|
||||
"name": "Authorization",
|
||||
"prefix": "Token token="
|
||||
},
|
||||
"consoleLinks": {
|
||||
"keys": "https://support.pagerduty.com/main/docs/pagerduty-mcp-server",
|
||||
"docs": "https://support.pagerduty.com/main/docs/pagerduty-mcp-server"
|
||||
},
|
||||
"warnings": [
|
||||
"A PagerDuty API token; choose the regional endpoint that hosts the account."
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "mcp-api-key-eu",
|
||||
"transport": "mcp_remote",
|
||||
"auth": "api_key",
|
||||
"ownershipModes": [
|
||||
"customer"
|
||||
],
|
||||
"whenToUse": "Use a restricted customer-owned key when browser sign-in is not suitable.",
|
||||
"defaults": {
|
||||
"serverUrl": "https://mcp.eu.pagerduty.com/mcp"
|
||||
},
|
||||
"guidanceMd": "Use a customer-created PagerDuty key. A PagerDuty API token; choose the regional endpoint that hosts the account.",
|
||||
"riskTier": "S4",
|
||||
"label": "EU service region",
|
||||
"credentialFields": [
|
||||
{
|
||||
"key": "authorization",
|
||||
"label": "PagerDuty API key",
|
||||
"type": "password",
|
||||
"required": true,
|
||||
"placeholder": "Paste your PagerDuty user API token",
|
||||
"secret": true,
|
||||
"helperMd": "Incident and on-call changes require a full-access API key and role access. Create a key with read and write permissions for these actions; Paperclip cannot increase an existing key’s permissions."
|
||||
}
|
||||
],
|
||||
"keyPlacement": {
|
||||
"location": "header",
|
||||
"name": "Authorization",
|
||||
"prefix": "Token token="
|
||||
},
|
||||
"consoleLinks": {
|
||||
"keys": "https://support.pagerduty.com/main/docs/pagerduty-mcp-server",
|
||||
"docs": "https://support.pagerduty.com/main/docs/pagerduty-mcp-server"
|
||||
},
|
||||
"warnings": [
|
||||
"A PagerDuty API token; choose the regional endpoint that hosts the account."
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
Loaded 100 of 8325 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user