@keemakr/operator-sdk 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@keemakr/operator-sdk",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "The keemakr connector contract: defineProvider + ConnectionProvider/ConnectionOperation types + OperationContext (governed fetch) + the connector manifest schema, shared by keemakr-core's built-in adapters and external connector repos. One shape, two consumers.",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/src/index.ts CHANGED
@@ -22,6 +22,23 @@ export type AuthKind = 'oauth2' | 'api_key' | 'app_password';
22
22
  // to 'ready'), so legacy adapters stay connectable until told otherwise.
23
23
  export type Maturity = 'ready' | 'coming_soon';
24
24
 
25
+ // Who a connector is FOR — the audience axis the end-user client uses to decide
26
+ // whether to surface it, and how. Classify EVERY connector (defineProvider will
27
+ // enforce this) so the client catalog never conflates the three kinds:
28
+ // 'user-account' → the tenant's OWN account; connect/disconnect is the point.
29
+ // Shown in the end-user connections hub (Gmail, Slack, Stripe,
30
+ // HubSpot, social channels).
31
+ // 'agent-tool' → infrastructure an AGENT uses to do a task, not a personal
32
+ // account. The platform may hold a shared key; a tenant may
33
+ // override with their own. NEVER a standalone hub card — it
34
+ // surfaces only via the install card of an agent that declares
35
+ // it (Apollo, Hunter, Clearbit — enrichment/analytics/scraping).
36
+ // 'internal' → platform plumbing, or a dormant connector with no operations
37
+ // and no consumer. Never shown to end users (e.g. agent-sessions;
38
+ // op-less stubs — also keep those at maturity:'coming_soon').
39
+ // See docs/architecture/05 + 15 and the AGENTS.md "Connectors" convention.
40
+ export type ConnectorSurface = 'user-account' | 'agent-tool' | 'internal';
41
+
25
42
  export interface TokenSet {
26
43
  accessToken: string;
27
44
  refreshToken?: string | null;
@@ -95,6 +112,12 @@ export interface ConnectionProvider {
95
112
  // Connect-path maturity. Omitted ⇒ 'ready'. See Maturity above.
96
113
  maturity?: Maturity;
97
114
 
115
+ // Audience classification — see ConnectorSurface. Drives whether the end-user
116
+ // client surfaces this connector and how. Optional in the type for now so
117
+ // existing/external adapters compile through the SDK bump; defineProvider warns
118
+ // when omitted and this tightens to required once every built-in declares it.
119
+ surface?: ConnectorSurface;
120
+
98
121
  // Egress allowlist: the ONLY hosts this connector's operations/test may reach
99
122
  // through ctx.fetch. Bare hostname for an exact match ('api.acme.com') or a
100
123
  // wildcard for subdomains ('*.acme.com' — does NOT match the apex; list it
@@ -267,6 +290,16 @@ export function defineProvider(p: ConnectionProvider): ConnectionProvider {
267
290
  if (typeof p.test !== 'function') {
268
291
  throw new Error(`connections: provider '${p.provider}' must implement test()`);
269
292
  }
293
+ if (!p.surface) {
294
+ // Nudge, not throw (yet): the built-ins all declare `surface`, but external/marketplace
295
+ // connectors (separate repos) haven't adopted it. Tightening to a hard throw + a required
296
+ // field is gated on adding `surface` to the connector manifest schema.
297
+ console.warn(
298
+ `connections: provider '${p.provider}' does not declare surface ` +
299
+ `('user-account' | 'agent-tool' | 'internal') — classify it so the client catalog knows ` +
300
+ `whether and how to show it. See the AGENTS.md "Connectors" convention.`,
301
+ );
302
+ }
270
303
  if (p.operations && Object.keys(p.operations).length > 0 && !p.allowedDomains?.length) {
271
304
  // Warn, don't throw: built-ins are grandfathered on ambient fetch. The
272
305
  // marketplace publish gate REQUIRES allowed_domains in the manifest, so
package/src/manifest.ts CHANGED
@@ -59,9 +59,24 @@ export const connectorManifestSchema = z
59
59
  // tenant's credential — which is why remote connectors require explicit
60
60
  // per-connection consent before any call.
61
61
  runtime_url: z.string().url().max(500).optional(),
62
+ // Lane 'mcp': the author points at a remote Model Context Protocol server
63
+ // instead of writing operation bodies. Each declared operation forwards to
64
+ // that server's `tools/call` over Streamable HTTP, driven through the same
65
+ // governed fetch as Lane B. `url` is https-only and author-declared (never
66
+ // tenant-supplied); `auth` is 'injected' only in v1 — core vaults the
67
+ // tenant's credential and rides it on a transport header, never in the tool
68
+ // args. ('server_managed', where the MCP server holds its own upstream
69
+ // OAuth, is reserved for a later, higher-tier release.)
70
+ mcp: z
71
+ .object({
72
+ url: z.string().url().max(500),
73
+ auth: z.enum(['injected']),
74
+ })
75
+ .optional(),
62
76
  // Remote connectors have no adapter code in core, so their connect form is
63
77
  // declared HERE (vendored connectors keep connectFields in code — the
64
- // adapter stays the single source of truth for Lane A).
78
+ // adapter stays the single source of truth for Lane A). MCP connectors are
79
+ // the same: no adapter code, so the form lives here too.
65
80
  connect_fields: z
66
81
  .array(
67
82
  z.object({
@@ -91,10 +106,19 @@ export const connectorManifestSchema = z
91
106
  changelog: z.string().max(5000).optional(),
92
107
  })
93
108
  .superRefine((m, ctx) => {
94
- if (!m.entrypoint && !m.runtime_url) {
109
+ const lanes = [m.entrypoint, m.runtime_url, m.mcp].filter((v) => v != null).length;
110
+ if (lanes === 0) {
111
+ ctx.addIssue({
112
+ code: 'custom',
113
+ message:
114
+ 'declare exactly one lane: an entrypoint (vendored, Lane A), a runtime_url (remote, Lane B), or an mcp endpoint (remote MCP)',
115
+ });
116
+ }
117
+ if (lanes > 1) {
95
118
  ctx.addIssue({
96
119
  code: 'custom',
97
- message: 'declare an entrypoint (vendored, Lane A) or a runtime_url (remote, Lane B)',
120
+ message:
121
+ 'declare exactly one lane — entrypoint, runtime_url, and mcp are mutually exclusive',
98
122
  });
99
123
  }
100
124
  if (m.runtime_url && !m.runtime_url.startsWith('https://')) {
@@ -116,6 +140,25 @@ export const connectorManifestSchema = z
116
140
  'remote connectors must declare connect_fields (there is no adapter code to own the form)',
117
141
  });
118
142
  }
143
+ if (m.mcp && !m.mcp.url.startsWith('https://')) {
144
+ ctx.addIssue({ code: 'custom', path: ['mcp', 'url'], message: 'mcp.url must be https' });
145
+ }
146
+ if (m.mcp && m.auth_kind === 'oauth2') {
147
+ ctx.addIssue({
148
+ code: 'custom',
149
+ path: ['auth_kind'],
150
+ message:
151
+ 'mcp connectors are api_key/app_password only — core injects the vaulted credential on a transport header, it does not broker an author-owned OAuth dance',
152
+ });
153
+ }
154
+ if (m.mcp && (!m.connect_fields || m.connect_fields.length === 0)) {
155
+ ctx.addIssue({
156
+ code: 'custom',
157
+ path: ['connect_fields'],
158
+ message:
159
+ 'mcp connectors must declare connect_fields (there is no adapter code to own the form)',
160
+ });
161
+ }
119
162
  });
120
163
 
121
164
  export type ConnectorOperationDeclaration = z.infer<typeof connectorOperationDeclaration>;