Corsair AMcards 插件接入指南:用 10 个类型安全的 API 调用打通自动化实体贺卡邮寄
【免费下载链接】corsairConnect your users to their apps项目地址: https://gitcode.com/GitHub_Trending/corsa/corsair
Corsair 是一个面向多租户场景的第三方应用连接框架,而@corsair-dev/amcards是它官方提供的 AMcards 集成插件。AMcards 是自动化贺卡平台,支持向真实地址邮寄个性化实体贺卡。本文将围绕该插件的完整接入流程展开:从安装、租户授权,到 10 个类型安全的amcards.api.*操作、5 个本地同步实体与检索过滤器,再到底层的请求封装、限流与错误处理实现,帮助你在自己的 Agent 或后端服务中快速落地"替用户发送实体贺卡"的能力。
插件概览:一个插件,两套能力
@corsair-dev/amcards插件围绕 AMcards 官方 API v1(Django REST / Tastypie 风格)封装出两层能力:
- 10 个类型安全的 API 操作:
cards.list、categories.get/list、contacts.list、gifts.get/list、schema.getApi/getCategory、templates.get/list,全部标注为read风险级别; - 5 个本地同步实体:
cards、categories、contacts、gifts、templates,数据同步到本地数据库后可通过.search()/.list()快速检索。
在插件文档(overview.mdx)中,它被定义为"Automated greeting card platform for sending personalized physical mail campaigns"(自动化实体贺卡邮寄平台)。
安装与初始化
安装依赖
使用pnpm安装插件(Corsair 插件采用 workspace 级 peerDependencies 设计,需要corsair >= 0.1.0与zod ^4.1.13,见 package.json):
pnpm add corsair @corsair-dev/amcards仓库示例与插件本身均使用pnpm工作区(pnpm-workspace.yaml),若使用 npm/yarn/bun 同样支持:
npm install corsair @corsair-dev/amcards # 或 yarn add corsair @corsair-dev/amcards # 或 bun add corsair @corsair-dev/amcards注册插件
在创建 Corsair 实例时传入amcards()工厂函数:
import Database from 'better-sqlite3'; import { createCorsair } from 'corsair'; import { amcards } from '@corsair-dev/amcards'; export const corsair = createCorsair({ plugins: [ amcards(), ], database: new Database('corsair.db'), kek: process.env.CORSAIR_KEK!, hub: { projectApiKey: process.env.CORSAIR_API_KEY!, signingSecret: process.env.CORSAIR_SIGNING_SECRET!, }, });要点说明:
database为本地数据库实例,AMcards 同步数据会持久化到其中;kek(Key Encryption Key)与hub配置用于凭证加密与连接流程,可参考 quick-start.mdx 与 multi-tenancy.mdx;- 多租户是默认行为,后续所有调用需通过
corsair.withTenant(id)划定租户作用域。
从源码看,amcards()工厂(index.ts)默认将authType收敛为'api_key',并返回一个满足CorsairPlugin契约的插件对象:包含id: 'amcards'、authConfig、schema、10 个 endpoint、Zod 输入输出 schema、endpointMeta(每个操作的风险级别与描述)以及自定义的keyBuilder。
认证方式:API Key(推荐)
AMcards 插件只支持API Key(即 AMcards 的 API Access Token)这一种认证方式,不支持 OAuth。
- 首次以某个租户身份发起请求时,Corsair 会提示为该租户录入 API Key(见 api-key.mdx);
- 凭证由租户维度的密钥管理器托管,调用时无需在业务代码中显式携带 Token。
底层认证实现
keyBuilder(index.ts)的逻辑是:
- 仅接受
source === 'endpoint'的调用场景,否则抛出AuthMissingError('amcards', 'api_key'); - 若插件选项里显式传入了
key,直接使用; - 否则从
ctx.keys.get_api_key()解析租户的 API Key,取不到同样抛出AuthMissingError。
认证配置amcardsAuthConfig(index.ts)将api_key的账户作用域声明为['tenant_external_id'],即密钥按租户外部 ID 隔离存储。
Token 的发送方式
底层 HTTP 层(client.ts)使用 Django REST 风格的 TokenAuthentication:
Authorization: Token <api_access_token>源码注释明确说明:OpenAPIConfig.TOKEN有意保持未设置,以避免共享传输层自动发出Authorization: Bearer头。每次请求还会固定携带Accept: application/json与Content-Type: application/json。
全部端点一览
插件 README(README.md)给出了 10 个端点的官方总览,全部为read风险级别:
| 操作 | 操作 ID | 风险 | 描述 |
|---|---|---|---|
cards.list | amcards.api.cards.list | read | 列出当前账户的贺卡 |
categories.get | amcards.api.categories.get | read | 按 id 获取卡片模板分类 |
categories.list | amcards.api.categories.list | read | 按优先级顺序列出卡片模板分类 |
contacts.list | amcards.api.contacts.list | read | 列出联系人,可按姓名或邮箱过滤 |
gifts.get | amcards.api.gifts.get | read | 按 id 获取礼物 |
gifts.list | amcards.api.gifts.list | read | 列出可用礼物 |
schema.getApi | amcards.api.schema.getApi | read | 获取 AMcards API v1 schema(资源映射) |
schema.getCategory | amcards.api.schema.getCategory | read | 获取只读的 Category 资源 schema |
templates.get | amcards.api.templates.get | read | 按 id 获取公开卡片模板 |
templates.list | amcards.api.templates.list | read | 列出公开卡片模板 |
这 10 个端点与 index.ts 中的amcardsEndpointsNested一一对应,在运行时以嵌套命名空间暴露为tenant.amcards.api.<resource>.<action>()。
端点详解与调用示例
以下所有调用均基于const tenant = corsair.withTenant('acme')获取的租户作用域对象。每个操作的输入输出 Schema 定义在 endpoints/types.ts,底层 HTTP 映射在 endpoints/handlers.ts。
cards.list — 列出账户贺卡
await tenant.amcards.api.cards.list({});输入参数(均可选):
| 名称 | 类型 | 描述 |
|---|---|---|
skip | number | 跳过的行数,对应 Tastypie/DRF 的offset |
limit | number | 每页大小 |
输出:{ id: number | string }数组,或带count/next/previous/results/meta/objects的分页包装结构。
categories.get / categories.list — 模板分类
按 id 获取单个分类:
await tenant.amcards.api.categories.get({ category_id: 9 });category_id为必填的正整数。分类字段包括id、title、priority(1 为最高优先级)、parent、hierarchy。
列出分类(支持多级筛选):
await tenant.amcards.api.categories.list({ parent__id: 3, // 按父分类 id 筛子分类 title__icontains: 'birthday', // 标题大小写不敏感搜索 parent__title__icontains: 'holiday', // 父标题大小写不敏感搜索 });contacts.list — 联系人列表
await tenant.amcards.api.contacts.list({ email: 'ada@example.com', first_name: 'Ada', last_name: 'Lovelace', skip: 0, limit: 50, });联系人字段:id、first_name、last_name、email、created_at、updated_at。handler 会把这些参数原样映射为 AMcards 的email、first_name、last_name查询参数(见 handlers.ts)。
gifts.get / gifts.list — 礼物
无需认证即可访问的公开资源。handler 通过auth: false省略 Token 头(handlers.ts)。
await tenant.amcards.api.gifts.get({ id: 3 }); await tenant.amcards.api.gifts.list({});礼物字段:id、name、description、price、shipping_cost、available、availability,其中price/shipping_cost兼容字符串或数字两种返回形式。
templates.get / templates.list — 公开卡片模板
同为公开资源(auth: false),可按分类或名称模糊搜索:
await tenant.amcards.api.templates.get({ id: 4 }); await tenant.amcards.api.templates.list({ category__id: 9, // 按分类 id 过滤 name__icontains: 'thanks', // 模板名大小写不敏感搜索 });模板字段:id、name、category、configuration、panels、metadata(后四者为任意结构)。
schema.getApi / schema.getCategory — 获取 API schema
两个 schema 端点返回的是描述性元数据,常用于动态发现 API 结构:
await tenant.amcards.api.schema.getApi({}); // API v1 资源映射 await tenant.amcards.api.schema.getCategory({}); // Category 资源 schemagetApi对应GET /api/v1/(DRF/Tastypie API root),getCategory对应GET /api/v1/categories/schema/。
租户连接流程
插件 README 明确标注"Auth: API key. Corsair prompts your tenant for credentials on first use",具体落地为:
- 业务后端调用
corsair.manage.connect.createLink生成连接链接(参考 connect.mdx):
const { connectUrl } = await corsair.manage.connect.createLink({ plugin: 'amcards', tenantId: 'acme', }); // 将用户浏览器重定向到 connectUrl- 用户在 Corsair Hub 托管的页面上录入 AMcards API Access Token;
- Hub 将结果投递回你的应用,此后该租户的所有
amcards.*调用自动携带凭证,无需再次录入。
Webhooks:当前无支持
插件 README 明确说明"No webhooks"。源码层面也印证了这一点:index.ts 中webhooks: {}为空对象、webhookHooks: undefined、pluginWebhookMatcher: undefined。因此该插件目前是纯拉取模型(API 调用 + 本地同步),不提供任何入站事件推送。
本地同步数据与检索
插件将cards、categories、contacts、gifts、templates五个实体同步到本地数据库,实体字段定义在 schema/database.ts,字段命名遵循 AMcards API v1 的 snake_case 约定,并使用.loose()保留响应中的额外键。
每个实体都支持tenant.amcards.db.<entity>.search()与.list()(完整过滤器见 database.mdx):
const rows = await tenant.amcards.db.contacts.search({ data: { first_name: { equals: 'Ada' } }, limit: 100, offset: 0, });各实体的可检索字段与操作符汇总:
| 实体 | 可检索字段 | 支持操作符 |
|---|---|---|
cards | entity_id | equals, contains, startsWith, endsWith, in |
categories | entity_id,title,priority | 字符串类:equals, contains, startsWith, endsWith, in;priority(number):equals, gt, gte, lt, lte, in |
contacts | entity_id,first_name,last_name,email,created_at,updated_at | equals, contains, startsWith, endsWith, in |
gifts | entity_id,name,description,available | 字符串类同上;available(boolean):equals |
templates | entity_id,name | equals, contains, startsWith, endsWith, in |
所有.search()均支持limit与offset分页;.list()与.search()位于同一路径(省略.search后缀),用于直接列举。这些本地检索能力让 Agent 可以在不频繁打 AMcards 远程 API 的情况下快速完成"查找联系人 / 匹配模板 / 选礼物"等决策逻辑。
底层实现:请求封装、限流与错误处理
请求封装与分页兼容
makeAmcardsRequest(client.ts)统一处理:
- GET/POST/PUT/PATCH/DELETE 方法分发,写请求自动携带 JSON mediaType;
compactQuery剔除值为undefined的查询参数,避免发出?foo=undefined;- 路径 ID 通过
encodeAmcardsPathId做 URI 编码(client.ts)。
值得注意的分页兼容设计:AMcards 的列表响应可能是 Django REST 的results结构、Tastypie 的objects结构,甚至裸数组。listResponse(endpoints/types.ts)用z.union同时接受这三种形态,保证任何历史版本的响应都不会导致解析失败。
内置限流
client.ts 为共享传输层配置了限流策略:
const AMCARDS_RATE_LIMIT_CONFIG: RateLimitConfig = { enabled: true, maxRetries: 3, initialRetryDelay: 1000, backoffMultiplier: 2, headerNames: { retryAfter: 'Retry-After' }, };即最多重试 3 次、初始退避 1 秒、退避倍数 2,并读取Retry-After响应头。
错误类型与错误处理器
所有请求失败都会被包装为AmcardsAPIError(client.ts),并透传status、statusText、body、retryAfter、rateLimitReset、rateLimitRemaining、rateLimitLimit等字段。
error-handlers.ts 定义了六类错误匹配与重试策略:
| 分类 | 匹配依据(状态码优先) | 重试策略 |
|---|---|---|
RATE_LIMIT_ERROR | 429 | 不重试 |
AUTH_ERROR | 401/403 | 不重试 |
NOT_FOUND_ERROR | 404 | 不重试 |
VALIDATION_ERROR | 400/422 | 不重试 |
SERVER_ERROR | >= 500 | 最多重试 2 次,指数退避 |
DEFAULT | 兜底 | 不重试 |
注释明确指出:AMcards 是 Django REST API,HTTP 状态码本身就是契约,消息启发式匹配(如 message 中包含 "rate limit"、"invalid token" 等)只在拿不到状态码时兜底使用。
测试覆盖
插件自带完整测试:handlers.test.ts 通过 mockmakeAmcardsRequest验证 10 个 handler 的请求参数映射与输出 Schema 解析,例如联系人(first_name/last_name/email/created_at/updated_at)、分类(title/priority)、礼物(price/shipping_cost/available)、模板(name/panels)等典型负载均被覆盖;另有 client.test.ts 与 schema.test.ts 分别覆盖请求封装与 Schema 校验。
与 Agent / MCP 集成
插件能力可以通过 Corsair 的 MCP 适配层暴露为 MCP 工具,使 Claude、Cursor 等编码 Agent 直接调用 AMcards 的 10 个操作(参考 mcp-adapters.mdx)。由于全部操作均为read风险级别,将其暴露给 Agent 时权限模型相对简单清晰;同时借助本地同步实体的.search(),Agent 可以先在本地完成联系人匹配与模板选择,再按需调用远程 API,降低延迟与限流压力。
版本与许可
- 插件包名为
@corsair-dev/amcards,peerDependencies 要求corsair >= 0.1.0、zod ^4.1.13(见 package.json); - 许可协议为Apache-2.0(README 与 package.json 一致);
- 插件类型定义:
amcards、AmcardsPluginOptions、AmcardsContext、AmcardsEndpoints等均从 index.ts 导出;输入输出类型与 Schema 从 endpoints/types.ts 导出。
小结
@corsair-dev/amcards插件把 AMcards 的 Django REST / Tastypie 风格 API 收敛为 10 个类型安全的只读操作,配合本地 5 实体同步、内置限流与按状态码分类的错误处理,为"替用户发送个性化实体贺卡"的 Agent 场景提供了开箱即用的连接能力。核心使用路径可概括为:安装插件 → 注册到 Corsair → 通过 connect link 引导租户录入 API Key →withTenant(id)作用域内调用amcards.api.*或检索amcards.db.*。更完整的输入输出类型可继续查阅 api.mdx 与 database.mdx。
【免费下载链接】corsairConnect your users to their apps项目地址: https://gitcode.com/GitHub_Trending/corsa/corsair
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考