@lanes-sh/link 0.6.10 → 0.7.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.
Files changed (202) hide show
  1. package/.gcloudignore +25 -15
  2. package/README.md +62 -45
  3. package/instructions/skills/lanes-link/SKILL.md +25 -0
  4. package/package.json +3 -2
  5. package/src/audit/conformance.ts +1 -1
  6. package/src/cli/argv.ts +14 -1
  7. package/src/cli/commands/connect/acquire.ts +100 -0
  8. package/src/cli/commands/connect/custom/index.ts +1 -1
  9. package/src/cli/commands/connect/declare.ts +19 -0
  10. package/src/cli/commands/connect/index.ts +54 -56
  11. package/src/cli/commands/connect/requirements.ts +23 -1
  12. package/src/cli/commands/connect/setup.ts +1 -1
  13. package/src/cli/commands/connect/variables.ts +220 -0
  14. package/src/cli/commands/knowledge/index.ts +22 -4
  15. package/src/cli/commands/knowledge/migrate.ts +61 -43
  16. package/src/cli/commands/operate/tools.ts +1 -1
  17. package/src/cli/commands/owner/entities.ts +244 -0
  18. package/src/cli/commands/owner/shared.ts +14 -0
  19. package/src/cli/commands/owner/vault.ts +2 -2
  20. package/src/cli/commands/owner.ts +13 -3
  21. package/src/cli/commands/setup.ts +12 -0
  22. package/src/cli/config-edit.ts +16 -6
  23. package/src/cli/config-repair.ts +10 -0
  24. package/src/cli/dispatch-owner.ts +30 -1
  25. package/src/cli/main.ts +2 -1
  26. package/src/cli/runtime/open.ts +14 -7
  27. package/src/cli/runtime/registry.ts +6 -1
  28. package/src/cli/selection.ts +11 -0
  29. package/src/cli/usage.ts +13 -2
  30. package/src/connectivity/auth/strategy/index.ts +1 -1
  31. package/src/connectivity/capability.ts +1 -1
  32. package/src/connectivity/context.ts +1 -1
  33. package/src/connectivity/index.ts +1 -1
  34. package/src/connectivity/mail/attachments.ts +42 -73
  35. package/src/connectivity/mail/content-type.ts +71 -0
  36. package/src/connectivity/mail/index.ts +1 -1
  37. package/src/connectivity/manifest/provider.ts +50 -0
  38. package/src/connectivity/manifest/variables.ts +173 -0
  39. package/src/connectivity/transports/factory.ts +74 -1
  40. package/src/connectivity/transports/http/index.ts +8 -2
  41. package/src/connectivity/transports/http/keys.ts +83 -0
  42. package/src/connectivity/transports/imap/attachment.ts +53 -10
  43. package/src/connectivity/transports/imap/capabilities.ts +28 -0
  44. package/src/connectivity/transports/imap/download.ts +111 -0
  45. package/src/connectivity/transports/imap/index.ts +13 -0
  46. package/src/connectivity/transports/imap/operations.ts +1 -0
  47. package/src/deployments/adapters/s3.ts +1 -1
  48. package/src/deployments/gcp/provision.ts +1 -1
  49. package/src/deployments/knowledge.ts +44 -18
  50. package/src/deployments/prepare.ts +1 -1
  51. package/src/deployments/serving.ts +1 -1
  52. package/src/dispatch/dispatch.ts +17 -10
  53. package/src/dispatch/staging.ts +76 -1
  54. package/src/profile/knowledge.ts +19 -11
  55. package/src/profile/schema.ts +2 -2
  56. package/src/providers/README.md +189 -0
  57. package/src/providers/airtable/index.ts +10 -0
  58. package/src/providers/algolia/index.ts +10 -0
  59. package/src/providers/amplitude/index.ts +10 -0
  60. package/src/providers/apify/index.ts +10 -0
  61. package/src/providers/asana/index.ts +10 -0
  62. package/src/providers/assets/provider.ts +11 -4
  63. package/src/providers/atlassian/index.ts +19 -0
  64. package/src/providers/attio/index.ts +10 -0
  65. package/src/providers/betterstack/index.ts +10 -0
  66. package/src/providers/box/index.ts +64 -0
  67. package/src/providers/brightdata/index.ts +10 -0
  68. package/src/providers/buildkite/index.ts +10 -0
  69. package/src/providers/bunq/index.ts +2 -2
  70. package/src/providers/calendly/index.ts +10 -0
  71. package/src/providers/canva/index.ts +10 -0
  72. package/src/providers/circleci/index.ts +10 -0
  73. package/src/providers/clickup/index.ts +10 -0
  74. package/src/providers/close/index.ts +10 -0
  75. package/src/providers/cloudflare_bindings/index.ts +14 -0
  76. package/src/providers/cloudflare_observability/index.ts +10 -0
  77. package/src/providers/contentful/index.ts +10 -0
  78. package/src/providers/custom/load.ts +1 -1
  79. package/src/providers/datadog/index.ts +10 -0
  80. package/src/providers/discord/index.ts +1 -1
  81. package/src/providers/discord/specs/discord.v10.json +0 -88
  82. package/src/providers/discord/specs/vendor.ts +1 -1
  83. package/src/providers/dropbox/index.ts +10 -0
  84. package/src/providers/entities/catalogue.ts +366 -0
  85. package/src/providers/entities/find.ts +279 -0
  86. package/src/providers/entities/provider.ts +334 -0
  87. package/src/providers/entities/render.ts +142 -0
  88. package/src/providers/entities/store.ts +276 -0
  89. package/src/providers/entities/writes.ts +279 -0
  90. package/src/providers/example/provider.ts +1 -1
  91. package/src/providers/expensify/index.ts +10 -0
  92. package/src/providers/fastmail/calendar/index.ts +21 -0
  93. package/src/providers/fastmail/contacts/index.ts +17 -0
  94. package/src/providers/fastmail/index.ts +4 -0
  95. package/src/providers/fastmail/mail/index.ts +38 -0
  96. package/src/providers/fastmail/shared/setup.ts +52 -0
  97. package/src/providers/figma/index.ts +10 -0
  98. package/src/providers/fireflies/index.ts +10 -0
  99. package/src/providers/flagsmith/index.ts +10 -0
  100. package/src/providers/gamma/index.ts +10 -0
  101. package/src/providers/github/index.ts +2 -2
  102. package/src/providers/google/gmail/attachment.ts +16 -1
  103. package/src/providers/google/gmail-imap/index.ts +7 -2
  104. package/src/providers/google/shared/oauth.ts +1 -1
  105. package/src/providers/google/shared/service-account.ts +1 -1
  106. package/src/providers/google/shared/setup.ts +1 -1
  107. package/src/providers/google/specs/calendar.v3.json +0 -32
  108. package/src/providers/google/specs/docs.v1.json +0 -76
  109. package/src/providers/google/specs/drive.v3.json +0 -32
  110. package/src/providers/google/specs/gmail.v1.json +0 -76
  111. package/src/providers/google/specs/people.v1.json +0 -77
  112. package/src/providers/google/specs/sheets.v4.json +0 -76
  113. package/src/providers/google/specs/tasks.v1.json +0 -76
  114. package/src/providers/grafana/index.ts +10 -0
  115. package/src/providers/heroku/index.ts +10 -0
  116. package/src/providers/hubspot/index.ts +75 -0
  117. package/src/providers/hygraph/index.ts +10 -0
  118. package/src/providers/icloud/mail/index.ts +5 -0
  119. package/src/providers/icloud/shared/setup.ts +1 -1
  120. package/src/providers/index.ts +255 -0
  121. package/src/providers/insightly/index.ts +10 -0
  122. package/src/providers/jam/index.ts +10 -0
  123. package/src/providers/klaviyo/index.ts +10 -0
  124. package/src/providers/mailbox/index.ts +87 -0
  125. package/src/providers/memory/provider.ts +3 -8
  126. package/src/providers/mercury/index.ts +10 -0
  127. package/src/providers/microsoft/calendar/index.ts +49 -0
  128. package/src/providers/microsoft/contacts/index.ts +50 -0
  129. package/src/providers/microsoft/drive/index.ts +62 -0
  130. package/src/providers/microsoft/index.ts +12 -0
  131. package/src/providers/microsoft/mail/index.ts +76 -0
  132. package/src/providers/microsoft/shared/oauth.ts +58 -0
  133. package/src/providers/microsoft/shared/scopes.ts +56 -0
  134. package/src/providers/microsoft/shared/setup.ts +45 -0
  135. package/src/providers/microsoft/specs/microsoft-todo.v1.json +755 -0
  136. package/src/providers/microsoft/specs/onedrive.v1.json +993 -0
  137. package/src/providers/microsoft/specs/outlook-calendar.v1.json +1743 -0
  138. package/src/providers/microsoft/specs/outlook-contacts.v1.json +430 -0
  139. package/src/providers/microsoft/specs/outlook-mail.v1.json +901 -0
  140. package/src/providers/microsoft/specs/vendor.ts +232 -0
  141. package/src/providers/microsoft/todo/index.ts +54 -0
  142. package/src/providers/miro/index.ts +10 -0
  143. package/src/providers/mixpanel/index.ts +10 -0
  144. package/src/providers/monday/index.ts +10 -0
  145. package/src/providers/mux/index.ts +10 -0
  146. package/src/providers/navan/index.ts +10 -0
  147. package/src/providers/neon/index.ts +10 -0
  148. package/src/providers/netlify/index.ts +10 -0
  149. package/src/providers/nextcloud/calendar/index.ts +28 -0
  150. package/src/providers/nextcloud/contacts/index.ts +18 -0
  151. package/src/providers/nextcloud/index.ts +3 -0
  152. package/src/providers/nextcloud/shared/setup.ts +59 -0
  153. package/src/providers/owner.ts +15 -3
  154. package/src/providers/paddle/index.ts +10 -0
  155. package/src/providers/paypal/index.ts +10 -0
  156. package/src/providers/posthog/index.ts +10 -0
  157. package/src/providers/prisma/index.ts +10 -0
  158. package/src/providers/ramp/index.ts +10 -0
  159. package/src/providers/recurly/index.ts +10 -0
  160. package/src/providers/reddit/index.ts +1 -1
  161. package/src/providers/remote/index.ts +10 -0
  162. package/src/providers/render/index.ts +35 -0
  163. package/src/providers/replicate/index.ts +10 -0
  164. package/src/providers/resend/index.ts +10 -0
  165. package/src/providers/riverside/index.ts +10 -0
  166. package/src/providers/rootly/index.ts +10 -0
  167. package/src/providers/rudderstack/index.ts +10 -0
  168. package/src/providers/salesloft/index.ts +10 -0
  169. package/src/providers/sanity/index.ts +10 -0
  170. package/src/providers/scopes.ts +2 -0
  171. package/src/providers/sentry/index.ts +10 -0
  172. package/src/providers/setup/plan.ts +11 -0
  173. package/src/providers/setup/provider.ts +12 -0
  174. package/src/providers/shared/openapi.ts +70 -0
  175. package/src/providers/shared/slug.ts +28 -0
  176. package/src/providers/shared/vendor-operations.ts +43 -1
  177. package/src/providers/shared/vendor-spec.ts +98 -17
  178. package/src/providers/shortcut/index.ts +10 -0
  179. package/src/providers/slack/index.ts +2 -2
  180. package/src/providers/square/index.ts +10 -0
  181. package/src/providers/storyblok/index.ts +10 -0
  182. package/src/providers/stripe/index.ts +10 -0
  183. package/src/providers/supabase/index.ts +10 -0
  184. package/src/providers/tasks/store.ts +2 -7
  185. package/src/providers/tavily/index.ts +10 -0
  186. package/src/providers/todoist/index.ts +10 -0
  187. package/src/providers/untested.ts +105 -0
  188. package/src/providers/vault/provider.ts +1 -1
  189. package/src/providers/vercel/index.ts +10 -0
  190. package/src/providers/vimeo/index.ts +10 -0
  191. package/src/providers/webflow/index.ts +10 -0
  192. package/src/providers/whimsical/index.ts +10 -0
  193. package/src/providers/wix/index.ts +10 -0
  194. package/src/providers/workable/index.ts +10 -0
  195. package/src/providers/yahoo_mail/index.ts +73 -0
  196. package/src/providers/zapier/index.ts +10 -0
  197. package/src/providers/zoho_mail/index.ts +79 -0
  198. package/src/secrets/document.ts +1 -1
  199. package/src/server/attachments.ts +129 -5
  200. package/src/server/index.ts +3 -3
  201. package/src/server/mcp/instructions.ts +88 -18
  202. package/src/server/stdio.ts +1 -1
@@ -1,7 +1,14 @@
1
1
  import { createHash } from 'node:crypto';
2
2
  import type { AuditSink } from '#audit';
3
3
  import type { Principal } from '#auth';
4
- import { newHandle, putStaged, sweepStaged, STAGED_TTL_MS } from '#connectivity/mail';
4
+ import {
5
+ getStaged,
6
+ newHandle,
7
+ putStaged,
8
+ sweepStaged,
9
+ STAGED_TTL_MS,
10
+ type StagedFile,
11
+ } from '#connectivity/mail';
5
12
  import type { BlobStore } from '#stores/blobs';
6
13
  import { scopeBlobStore } from '#stores/blobs';
7
14
  import { scopeNamespace } from './context.ts';
@@ -100,3 +107,71 @@ export async function stageAttachment(
100
107
 
101
108
  return { handle, sha256, expiresAt };
102
109
  }
110
+
111
+ export interface FetchStagedRequest {
112
+ readonly principal: Principal;
113
+ readonly providerId: string;
114
+ readonly connectionId: string;
115
+ readonly handle: string;
116
+ readonly clientLabel?: string | undefined;
117
+ }
118
+
119
+ /**
120
+ * Handing bytes back out, which is the half staging was missing.
121
+ *
122
+ * Upload always worked: bytes in over HTTP, a handle through the model, a send
123
+ * that names it. The other direction had no route at all — an attachment that
124
+ * arrived by mail could be *described* by `get_message` and attached to an
125
+ * outgoing message by `send_message`, and there was no way to simply have it.
126
+ * The only paths out were to mail it somewhere else or to base64 it through the
127
+ * conversation, which is the cost this whole design exists to avoid.
128
+ *
129
+ * So it is the same shape run backwards, and deliberately the same scoping: the
130
+ * store is namespaced by provider and connection here exactly as it is above, so
131
+ * a handle minted for `icloud_mail.main` is fetchable from that connection and
132
+ * from no other. Nothing is bridged and no provider gains reach it did not have
133
+ * — the bytes were already inside this account's namespace, and this is a door
134
+ * onto it rather than a route between two.
135
+ *
136
+ * Null for "no such handle", "expired", and "wrong connection" alike. A caller
137
+ * cannot act on the difference, and distinguishing them would let someone probe
138
+ * for handles staged against an account they cannot reach.
139
+ */
140
+ export async function fetchStaged(
141
+ deps: {
142
+ readonly storage: BlobStore;
143
+ readonly audit: AuditSink;
144
+ readonly profile: string;
145
+ },
146
+ request: FetchStagedRequest,
147
+ ): Promise<StagedFile | null> {
148
+ const storage = scopeBlobStore(
149
+ deps.storage,
150
+ scopeNamespace(request.providerId, request.connectionId),
151
+ );
152
+
153
+ const found = await getStaged(storage, request.handle);
154
+
155
+ // Logged either way. "Did anything leave the endpoint" is the question this
156
+ // answers, and a miss is as worth having as a hit — a run of them against
157
+ // handles that do not exist is what probing looks like.
158
+ await deps.audit.append({
159
+ profile: deps.profile,
160
+ principal: request.principal.id,
161
+ ...(request.clientLabel ? { clientLabel: request.clientLabel } : {}),
162
+ provider: request.providerId,
163
+ connection: `${request.providerId}.${request.connectionId}`,
164
+ capability: 'attachments.fetch',
165
+ arguments: {
166
+ handle: request.handle,
167
+ ...(found
168
+ ? { filename: found.filename, bytes: found.bytes.byteLength, content_type: found.contentType }
169
+ : {}),
170
+ },
171
+ authorization: 'allowed',
172
+ status: found ? 'ok' : 'error',
173
+ durationMs: 0,
174
+ });
175
+
176
+ return found;
177
+ }
@@ -2,21 +2,28 @@ import { z } from 'zod';
2
2
  import { credentialRef } from './primitives.ts';
3
3
 
4
4
  /**
5
- * Where a profile's memory and skills live, when that is not where everything
6
- * else lives.
5
+ * Where a profile's memory, skills and entities live, when that is not where
6
+ * everything else lives.
7
7
  *
8
8
  * A target's `storage:` block names one backend for everything a profile holds
9
9
  * — runtime state, the audit log, memory entries, skills, cached attachments.
10
- * That is right for most of it and wrong for two: state and the log are
11
- * artefacts of one installation, and memory entries and skills are documents
12
- * the owner wrote. Documents want history, review, and to be readable from more
13
- * than one machine, and the backend that suits an object nobody reads is not
14
- * the backend that suits those.
10
+ * That is right for most of it and wrong for three: state and the log are
11
+ * artefacts of one installation, and memory entries, skills and entity files
12
+ * are documents the owner wrote. Documents want history, review, and to be
13
+ * readable from more than one machine, and the backend that suits an object
14
+ * nobody reads is not the backend that suits those.
15
15
  *
16
- * So this block moves exactly those two, and it is the whole of what it can
16
+ * ADR-041 said "memory and skills, and nothing else", and ADR-056 amends the
17
+ * count without touching the rule. That exclusion was never arithmetic: its
18
+ * argument is a discriminator — an artefact of one installation stays, a
19
+ * document the owner wrote may move — and an entity file is a Markdown document
20
+ * with frontmatter, hand-editable, wanting history and review. It is on the
21
+ * same side of that line as a memory entry, by the same test.
22
+ *
23
+ * So this block moves exactly those three, and it is the whole of what it can
17
24
  * move. There is no `credentials` or `vault` value here and there will not be:
18
25
  * a repository is a place to publish, and those two hold the material whose
19
- * entire value is that it is not published (`docs/detailed/security.md`). The
26
+ * entire value is that it is not published (`https://lanes.sh/docs/link/security`). The
20
27
  * exclusion is structural — a field that does not exist cannot be set by
21
28
  * accident, by a flag, or by an operator following an example.
22
29
  *
@@ -89,8 +96,8 @@ export type KnowledgeConfig = z.infer<typeof knowledgeTargetSchema>;
89
96
  /**
90
97
  * Where each area sits inside the repository.
91
98
  *
92
- * Two directories, named after the two things that move. `memory` is also the
93
- * memory provider's own blob namespace — the prefix core scopes it into under
99
+ * Three directories, named after the three things that move. `memory` is also
100
+ * the memory provider's own blob namespace — the prefix core scopes it into under
94
101
  * the profile's blob root — which is why the same word does both jobs: the
95
102
  * route that redirects it and the directory it lands in are the same fact, and
96
103
  * spelling them separately is how they would come to disagree.
@@ -101,6 +108,7 @@ export type KnowledgeConfig = z.infer<typeof knowledgeTargetSchema>;
101
108
  export const KNOWLEDGE_LAYOUT = {
102
109
  memory: 'memory',
103
110
  skills: 'skills',
111
+ entities: 'entities',
104
112
  } as const;
105
113
 
106
114
  export type KnowledgeArea = keyof typeof KNOWLEDGE_LAYOUT;
@@ -116,7 +116,7 @@ export const auditTargetSchema = z.object({
116
116
  *
117
117
  * Its own block rather than a reuse of `credentials`, because the vault must
118
118
  * never be the credential store — the two hold different things and collapsing
119
- * them is the single most damaging mistake available here (`docs/detailed/security.md`).
119
+ * them is the single most damaging mistake available here (`https://lanes.sh/docs/link/security`).
120
120
  *
121
121
  * Optional, defaulting to `file`: every profile written before ADR-014 keeps
122
122
  * working unchanged, and a local run needs no vault configuration at all.
@@ -242,7 +242,7 @@ export const deployTargetSchema = z.object({
242
242
  *
243
243
  * It is also what makes `limits.requests_per_minute` mean something in
244
244
  * aggregate. Those limits are per instance and always were; with no ceiling
245
- * the aggregate had no value at all, and `docs/detailed/deployment-cloudrun.md`
245
+ * the aggregate had no value at all, and `https://lanes.sh/docs/link/deployment-cloudrun`
246
246
  * told the reader to cap this themselves because nothing here did.
247
247
  */
248
248
  max_instances: z.number().int().min(1).max(100).default(DEPLOY_DEFAULTS.max_instances),
@@ -0,0 +1,189 @@
1
+ # Providers
2
+
3
+ A hundred and five of them, and almost all are a folder and a line. This page is the inventory and the
4
+ ins and outs; [Connect your accounts](https://lanes.sh/docs/link/connect) is the same list written for someone
5
+ deciding what to connect.
6
+
7
+ ## What a provider is
8
+
9
+ Two choices and nothing else: **how a service is reached** (`connector.kind`) and **how we prove who
10
+ we are** (`auth.kind`). Both are closed unions, both are orthogonal, and everything else is data
11
+ hung off the pair. That is why iCloud can speak IMAP with a password while Gmail speaks HTTP with
12
+ OAuth and neither costs the other any code.
13
+
14
+ - [`../connectivity/transports/README.md`](../connectivity/transports/README.md) — the six ways to reach a service
15
+ - [`../connectivity/auth/README.md`](../connectivity/auth/README.md) — the credential types, and the ones not built yet
16
+ - [Connectivity coverage](https://lanes.sh/docs/link/connectivity-coverage) — which pairs work, which are refused, and why
17
+
18
+ Where they come from:
19
+
20
+ | | |
21
+ |---|---|
22
+ | **connector** | `mcp` 77 · `http` 15 · `imap` 6 · `dav` 6 · `fs` 1 |
23
+ | **auth** | `oauth (dynamic)` 70 · `oauth (manual)` 18 · `basic` 12 · `bearer` 2 · `header` 1 · `none` 1 · `strategy` 1 |
24
+
25
+ Two of the hundred and five are code rather than data — `gmail`, which assembles an RFC 2822 message to
26
+ send one, and `bunq`, which runs a signing handshake no declarative form describes. Everything else
27
+ is a manifest.
28
+
29
+ ## Tested means somebody connected it
30
+
31
+ **Twenty-one are tested. Eighty-four are not**, and the tables say which.
32
+
33
+ A manifest can be right in every way this repository can check and still not work. What the checks
34
+ prove: the schema validates, the vendored spec generates registrable tools inside the 64 KB budget,
35
+ every scope has a described meaning, the redaction keys name arguments that exist, and — for the
36
+ remote MCP servers — the endpoint answered a live protocol probe with an OAuth challenge.
37
+
38
+ What only a real account proves: that the grant is the right one, that the identity probe reads a
39
+ field the vendor actually returns, that a refresh token comes back, and that the tool list is worth
40
+ having. Until somebody has done that, the honest label is untested — not broken, and not a warning,
41
+ just a fact about what has been established.
42
+
43
+ The list is [`untested.ts`](untested.ts), it is the single source of truth, and
44
+ `../readme.test.ts` asserts the documentation marks exactly those and no others. A provider leaves
45
+ the list when somebody connects it and it does what it says.
46
+
47
+ ## Adding one
48
+
49
+ A folder here and a line in [`index.ts`](index.ts). That file says it too, and means it: the folder
50
+ holds *all* of the provider — the manifest, the scopes it asks for, what it redacts from the audit
51
+ log, the setup walkthrough, and any vendored specification.
52
+
53
+ The full walkthrough is [Creating a provider](https://lanes.sh/docs/link/creating-a-provider),
54
+ which opens by pointing out that most providers should not be built-ins at all — a YAML manifest in
55
+ a profile's own `providers.d/` is validated by the same schema and loaded by [`custom/`](custom/).
56
+
57
+ Four things bite, and all four are quiet:
58
+
59
+ 1. **A redaction key that misses is invisible.** It withholds every argument and reads exactly like
60
+ working redaction. Key it on the capability name *as served* — Microsoft Graph's operations are
61
+ `me.ListMessages`, dots and all, because `shortenName` strips the provider id and the provider is
62
+ not called "me".
63
+ 2. **Schema size is not spec size.** `mcp-from-openapi` inlines `$ref`s, so a request body that
64
+ references a wide entity explodes: Graph's `PATCH /me/messages` measured 346 KB against a 64 KB
65
+ budget. `projectRequestBody` is the remedy where the body is a schema reference, and it is
66
+ checked — a field the vendor renames fails the refresh instead of becoming an argument the API
67
+ ignores.
68
+ 3. **A placeholder in the address needs a variable, and the reverse.** A provider whose host belongs
69
+ to the account rather than the vendor declares `variables`; `defineProvider` refuses a
70
+ `{placeholder}` nothing fills and a variable nothing uses, because the first fails as DNS and the
71
+ second asks the operator a question nothing reads. See ADR-055.
72
+ 4. **A property key the Anthropic API refuses takes down every provider at once**, not just its own.
73
+ `^[a-zA-Z0-9_.-]{1,64}$`, and one bad key rejects the whole `tools` array. The `http` transport
74
+ renames them and leaves the wire name alone, so OData's `$top` reaches an agent as `top`.
75
+ 5. **`base_url` must equal the spec's own `servers[0].url`**, and the version lives in different
76
+ places per vendor — Drive puts it in the host, Sheets in the path. Copying the wrong one 404s
77
+ every call.
78
+
79
+ ## The inventory
80
+
81
+ `tested` is blank where nobody has connected it yet.
82
+
83
+ | id | name | connector | auth | tested |
84
+ |---|---|---|---|---|
85
+ | `fastmail_calendar` | Fastmail Calendar | `dav` | `basic` | |
86
+ | `fastmail_contacts` | Fastmail Contacts | `dav` | `basic` | |
87
+ | `icloud_calendar` | iCloud Calendar | `dav` | `basic` | yes |
88
+ | `icloud_contacts` | iCloud Contacts | `dav` | `basic` | yes |
89
+ | `nextcloud_calendar` | Nextcloud Calendar | `dav` | `basic` | |
90
+ | `nextcloud_contacts` | Nextcloud Contacts | `dav` | `basic` | |
91
+ | `icloud_drive` | iCloud Drive | `fs` | `none` | yes |
92
+ | `bunq` | bunq | `http` | `strategy` | yes |
93
+ | `calendar` | Google Calendar | `http` | `oauth (manual)` | yes |
94
+ | `contacts` | Google Contacts | `http` | `oauth (manual)` | yes |
95
+ | `discord` | Discord | `http` | `header` | yes |
96
+ | `docs` | Google Docs | `http` | `oauth (manual)` | yes |
97
+ | `drive` | Google Drive | `http` | `oauth (manual)` | yes |
98
+ | `gmail` | Gmail | `http` | `oauth (manual)` | yes |
99
+ | `google_tasks` | Google Tasks | `http` | `oauth (manual)` | yes |
100
+ | `microsoft_todo` | Microsoft To Do | `http` | `oauth (manual)` | |
101
+ | `onedrive` | OneDrive | `http` | `oauth (manual)` | |
102
+ | `outlook_calendar` | Outlook Calendar | `http` | `oauth (manual)` | |
103
+ | `outlook_contacts` | Outlook Contacts | `http` | `oauth (manual)` | |
104
+ | `outlook_mail` | Outlook Mail | `http` | `oauth (manual)` | |
105
+ | `reddit` | Reddit | `http` | `oauth (manual)` | yes |
106
+ | `sheets` | Google Sheets | `http` | `oauth (manual)` | yes |
107
+ | `fastmail_mail` | Fastmail Mail | `imap` | `basic` | |
108
+ | `gmail_imap` | Gmail (IMAP) | `imap` | `basic` | yes |
109
+ | `icloud_mail` | iCloud Mail | `imap` | `basic` | yes |
110
+ | `mailbox` | Mailbox (any IMAP server) | `imap` | `basic` | |
111
+ | `yahoo_mail` | Yahoo Mail | `imap` | `basic` | |
112
+ | `zoho_mail` | Zoho Mail | `imap` | `basic` | |
113
+ | `airtable` | Airtable | `mcp` | `oauth (dynamic)` | |
114
+ | `algolia` | Algolia | `mcp` | `oauth (dynamic)` | |
115
+ | `amplitude` | Amplitude | `mcp` | `oauth (dynamic)` | |
116
+ | `apify` | Apify | `mcp` | `oauth (dynamic)` | |
117
+ | `asana` | Asana | `mcp` | `oauth (dynamic)` | |
118
+ | `atlassian` | Atlassian | `mcp` | `oauth (dynamic)` | |
119
+ | `attio` | Attio | `mcp` | `oauth (dynamic)` | |
120
+ | `betterstack` | Better Stack | `mcp` | `oauth (dynamic)` | |
121
+ | `box` | Box | `mcp` | `oauth (manual)` | |
122
+ | `brightdata` | Bright Data | `mcp` | `oauth (dynamic)` | |
123
+ | `buildkite` | Buildkite | `mcp` | `oauth (dynamic)` | |
124
+ | `calendly` | Calendly | `mcp` | `oauth (dynamic)` | |
125
+ | `canva` | Canva | `mcp` | `oauth (dynamic)` | |
126
+ | `circleci` | CircleCI | `mcp` | `oauth (dynamic)` | |
127
+ | `clickup` | ClickUp | `mcp` | `oauth (dynamic)` | |
128
+ | `close` | Close | `mcp` | `oauth (dynamic)` | |
129
+ | `cloudflare_bindings` | Cloudflare Bindings | `mcp` | `oauth (dynamic)` | |
130
+ | `cloudflare_observability` | Cloudflare Observability | `mcp` | `oauth (dynamic)` | |
131
+ | `contentful` | Contentful | `mcp` | `oauth (dynamic)` | |
132
+ | `datadog` | Datadog | `mcp` | `oauth (dynamic)` | |
133
+ | `drive_mcp` | Google Drive (Google MCP) | `mcp` | `oauth (manual)` | yes |
134
+ | `dropbox` | Dropbox | `mcp` | `oauth (dynamic)` | |
135
+ | `expensify` | Expensify | `mcp` | `oauth (dynamic)` | |
136
+ | `figma` | Figma | `mcp` | `oauth (dynamic)` | |
137
+ | `fireflies` | Fireflies | `mcp` | `oauth (dynamic)` | |
138
+ | `flagsmith` | Flagsmith | `mcp` | `oauth (dynamic)` | |
139
+ | `gamma` | Gamma | `mcp` | `oauth (dynamic)` | |
140
+ | `github` | GitHub | `mcp` | `bearer` | yes |
141
+ | `gmail_mcp` | Gmail (Google MCP) | `mcp` | `oauth (manual)` | yes |
142
+ | `grafana` | Grafana | `mcp` | `oauth (dynamic)` | |
143
+ | `heroku` | Heroku | `mcp` | `oauth (dynamic)` | |
144
+ | `hubspot` | HubSpot | `mcp` | `oauth (manual)` | |
145
+ | `hygraph` | Hygraph | `mcp` | `oauth (dynamic)` | |
146
+ | `insightly` | Insightly | `mcp` | `oauth (dynamic)` | |
147
+ | `jam` | Jam | `mcp` | `oauth (dynamic)` | |
148
+ | `klaviyo` | Klaviyo | `mcp` | `oauth (dynamic)` | |
149
+ | `linear` | Linear | `mcp` | `oauth (dynamic)` | yes |
150
+ | `mercury` | Mercury | `mcp` | `oauth (dynamic)` | |
151
+ | `miro` | Miro | `mcp` | `oauth (dynamic)` | |
152
+ | `mixpanel` | Mixpanel | `mcp` | `oauth (dynamic)` | |
153
+ | `monday` | monday.com | `mcp` | `oauth (dynamic)` | |
154
+ | `mux` | Mux | `mcp` | `oauth (dynamic)` | |
155
+ | `navan` | Navan | `mcp` | `oauth (dynamic)` | |
156
+ | `neon` | Neon | `mcp` | `oauth (dynamic)` | |
157
+ | `netlify` | Netlify | `mcp` | `oauth (dynamic)` | |
158
+ | `notion` | Notion | `mcp` | `oauth (dynamic)` | yes |
159
+ | `paddle` | Paddle | `mcp` | `oauth (dynamic)` | |
160
+ | `paypal` | PayPal | `mcp` | `oauth (dynamic)` | |
161
+ | `posthog` | PostHog | `mcp` | `oauth (dynamic)` | |
162
+ | `prisma` | Prisma | `mcp` | `oauth (dynamic)` | |
163
+ | `ramp` | Ramp | `mcp` | `oauth (dynamic)` | |
164
+ | `recurly` | Recurly | `mcp` | `oauth (dynamic)` | |
165
+ | `remote` | Remote | `mcp` | `oauth (dynamic)` | |
166
+ | `render` | Render | `mcp` | `bearer` | |
167
+ | `replicate` | Replicate | `mcp` | `oauth (dynamic)` | |
168
+ | `resend` | Resend | `mcp` | `oauth (dynamic)` | |
169
+ | `riverside` | Riverside | `mcp` | `oauth (dynamic)` | |
170
+ | `rootly` | Rootly | `mcp` | `oauth (dynamic)` | |
171
+ | `rudderstack` | RudderStack | `mcp` | `oauth (dynamic)` | |
172
+ | `salesloft` | Salesloft | `mcp` | `oauth (dynamic)` | |
173
+ | `sanity` | Sanity | `mcp` | `oauth (dynamic)` | |
174
+ | `sentry` | Sentry | `mcp` | `oauth (dynamic)` | |
175
+ | `shortcut` | Shortcut | `mcp` | `oauth (dynamic)` | |
176
+ | `slack` | Slack | `mcp` | `oauth (manual)` | yes |
177
+ | `square` | Square | `mcp` | `oauth (dynamic)` | |
178
+ | `storyblok` | Storyblok | `mcp` | `oauth (dynamic)` | |
179
+ | `stripe` | Stripe | `mcp` | `oauth (dynamic)` | |
180
+ | `supabase` | Supabase | `mcp` | `oauth (dynamic)` | |
181
+ | `tavily` | Tavily | `mcp` | `oauth (dynamic)` | |
182
+ | `todoist` | Todoist | `mcp` | `oauth (dynamic)` | |
183
+ | `vercel` | Vercel | `mcp` | `oauth (dynamic)` | |
184
+ | `vimeo` | Vimeo | `mcp` | `oauth (dynamic)` | |
185
+ | `webflow` | Webflow | `mcp` | `oauth (dynamic)` | |
186
+ | `whimsical` | Whimsical | `mcp` | `oauth (dynamic)` | |
187
+ | `wix` | Wix | `mcp` | `oauth (dynamic)` | |
188
+ | `workable` | Workable | `mcp` | `oauth (dynamic)` | |
189
+ | `zapier` | Zapier | `mcp` | `oauth (dynamic)` | |
@@ -0,0 +1,10 @@
1
+ import { defineProvider } from '#connectivity';
2
+
3
+ /** Airtable registers us at connect time — nothing for an operator to set up. */
4
+ export const airtable = defineProvider({
5
+ id: 'airtable',
6
+ name: 'Airtable',
7
+ description: 'Bases, tables, records, fields, and schema, via Airtable\'s official MCP server.',
8
+ connector: { kind: 'mcp', endpoint: 'https://mcp.airtable.com/mcp' },
9
+ auth: { kind: 'oauth', registration: 'dynamic' },
10
+ });
@@ -0,0 +1,10 @@
1
+ import { defineProvider } from '#connectivity';
2
+
3
+ /** Algolia registers us at connect time — nothing for an operator to set up. */
4
+ export const algolia = defineProvider({
5
+ id: 'algolia',
6
+ name: 'Algolia',
7
+ description: 'Search indices, records, queries, and synonyms, via Algolia\'s official MCP server.',
8
+ connector: { kind: 'mcp', endpoint: 'https://mcp.algolia.com/mcp' },
9
+ auth: { kind: 'oauth', registration: 'dynamic' },
10
+ });
@@ -0,0 +1,10 @@
1
+ import { defineProvider } from '#connectivity';
2
+
3
+ /** Amplitude registers us at connect time — nothing for an operator to set up. */
4
+ export const amplitude = defineProvider({
5
+ id: 'amplitude',
6
+ name: 'Amplitude',
7
+ description: 'Events, charts, cohorts, and user activity, via Amplitude\'s official MCP server.',
8
+ connector: { kind: 'mcp', endpoint: 'https://mcp.amplitude.com/mcp' },
9
+ auth: { kind: 'oauth', registration: 'dynamic' },
10
+ });
@@ -0,0 +1,10 @@
1
+ import { defineProvider } from '#connectivity';
2
+
3
+ /** Apify registers us at connect time — nothing for an operator to set up. */
4
+ export const apify = defineProvider({
5
+ id: 'apify',
6
+ name: 'Apify',
7
+ description: 'Actors, runs, datasets, and scraped results, via Apify\'s official MCP server.',
8
+ connector: { kind: 'mcp', endpoint: 'https://mcp.apify.com' },
9
+ auth: { kind: 'oauth', registration: 'dynamic' },
10
+ });
@@ -0,0 +1,10 @@
1
+ import { defineProvider } from '#connectivity';
2
+
3
+ /** Asana registers us at connect time — nothing for an operator to set up. */
4
+ export const asana = defineProvider({
5
+ id: 'asana',
6
+ name: 'Asana',
7
+ description: 'Tasks, projects, portfolios, and workspaces, via Asana\'s official MCP server.',
8
+ connector: { kind: 'mcp', endpoint: 'https://mcp.asana.com/mcp' },
9
+ auth: { kind: 'oauth', registration: 'dynamic' },
10
+ });
@@ -40,8 +40,15 @@ import {
40
40
  * model is the cost the five sources exist to avoid; there is no reason to pay
41
41
  * it on the way in that would not also apply on the way out.
42
42
  *
43
- * **Getting an asset into a mail is not done here**, and that is a boundary
44
- * rather than a gap. A staged handle is scoped to `<provider>/<connection>`
43
+ * **Neither direction between a mailbox and this store is done here**, and that
44
+ * is a boundary rather than a gap. It cost somebody a real attempt: `store` takes
45
+ * the shared attachment shape, which advertises `message_id`, so an agent asked to
46
+ * keep a PDF that had arrived by mail read the description, named the message, and
47
+ * was refused — twice, having tried the obvious workaround in between. The schema
48
+ * offered a route the handler never had. The description above now says so, and
49
+ * the refusal in `#connectivity/mail` names what does work instead.
50
+ *
51
+ * The reason it cannot is real rather than incidental. A staged handle is scoped to `<provider>/<connection>`
45
52
  * (`#dispatch`'s `stageAttachment`), so a handle minted under `assets/main` is
46
53
  * deliberately unresolvable from `gmail/main` — bridging the two means crossing
47
54
  * the isolation every provider relies on, and that belongs in dispatch if it
@@ -211,10 +218,10 @@ export const assetsProvider: ProviderDefinition = defineLocalProvider({
211
218
  name: 'store',
212
219
  title: 'Store a file',
213
220
  description:
214
- 'Keep a file in this profile. Name exactly one source — path, url, handle, message_id, or data — and the endpoint reads the bytes itself; never base64 a file into this call when any other source will do. Storing under a name that exists replaces it.',
221
+ 'Keep a file in this profile. Name exactly one source — path, url, handle, or data — and the endpoint reads the bytes itself; never base64 a file into this call when any other source will do. Storing under a name that exists replaces it. An attachment sitting in a mailbox cannot be named here: this connection holds no mailbox, so ask the mail connection to send it somewhere it can be reached from.',
215
222
  inputSchema: z.object({
216
223
  source: attachmentRefSchema.describe(
217
- 'Where the bytes come from. Exactly one of path, url, handle, message_id, or data.',
224
+ 'Where the bytes come from. Exactly one of path, url, handle, or data. The mailbox sources — message_id and uid — are part of the shared shape but cannot resolve here.',
218
225
  ),
219
226
  name: z
220
227
  .string()
@@ -0,0 +1,19 @@
1
+ import { defineProvider } from '#connectivity';
2
+
3
+ /**
4
+ * Jira, Confluence and Compass, through Atlassian's own MCP server.
5
+ *
6
+ * The endpoint is `/v1/mcp/authv2` and the path matters: `/v1/mcp` and
7
+ * `/v1/sse` both answer and neither publishes the protected-resource metadata
8
+ * the SDK discovers from, so a client pointed at either gets a 401 it cannot act
9
+ * on. This one advertises dynamic client registration, which is why there is
10
+ * nothing here for an operator to set up.
11
+ */
12
+ export const atlassian = defineProvider({
13
+ id: 'atlassian',
14
+ name: 'Atlassian',
15
+ description:
16
+ 'Jira issues, Confluence pages, and Compass components, via Atlassian\'s official MCP server.',
17
+ connector: { kind: 'mcp', endpoint: 'https://mcp.atlassian.com/v1/mcp/authv2' },
18
+ auth: { kind: 'oauth', registration: 'dynamic' },
19
+ });
@@ -0,0 +1,10 @@
1
+ import { defineProvider } from '#connectivity';
2
+
3
+ /** Attio registers us at connect time — nothing for an operator to set up. */
4
+ export const attio = defineProvider({
5
+ id: 'attio',
6
+ name: 'Attio',
7
+ description: 'Records, lists, notes, and tasks in the CRM, via Attio\'s official MCP server.',
8
+ connector: { kind: 'mcp', endpoint: 'https://mcp.attio.com/mcp' },
9
+ auth: { kind: 'oauth', registration: 'dynamic' },
10
+ });
@@ -0,0 +1,10 @@
1
+ import { defineProvider } from '#connectivity';
2
+
3
+ /** Better Stack registers us at connect time — nothing for an operator to set up. */
4
+ export const betterstack = defineProvider({
5
+ id: 'betterstack',
6
+ name: 'Better Stack',
7
+ description: 'Incidents, monitors, heartbeats, and logs, via Better Stack\'s official MCP server.',
8
+ connector: { kind: 'mcp', endpoint: 'https://mcp.betterstack.com' },
9
+ auth: { kind: 'oauth', registration: 'dynamic' },
10
+ });
@@ -0,0 +1,64 @@
1
+ import { defineProvider } from '#connectivity';
2
+
3
+ export const BOX_APP = 'box';
4
+
5
+ /** Matched literally by Box, so it is declared rather than picked at run time. */
6
+ const BOX_REDIRECT_URI = 'http://127.0.0.1:8772/callback';
7
+
8
+ /**
9
+ * Box, through the server Box runs.
10
+ *
11
+ * Box publishes protected-resource metadata and an ordinary authorization-code
12
+ * flow, and no dynamic client registration — so the client is the operator's,
13
+ * and there are two consoles it can come from. The admin route is the one Box
14
+ * prefers and the one that works on a managed enterprise account; the developer
15
+ * route is what an individual has. The steps below take the second, and say so.
16
+ */
17
+ export const box = defineProvider({
18
+ id: 'box',
19
+ name: 'Box',
20
+ description: 'Files, folders, and metadata in Box, via Box\'s official MCP server.',
21
+ connector: { kind: 'mcp', endpoint: 'https://mcp.box.com/' },
22
+ auth: {
23
+ kind: 'oauth',
24
+ registration: 'manual',
25
+ app: BOX_APP,
26
+ authorize_url: 'https://account.box.com/api/oauth2/authorize',
27
+ token_url: 'https://api.box.com/oauth2/token',
28
+ redirect_uri: BOX_REDIRECT_URI,
29
+ },
30
+ setup: {
31
+ summary:
32
+ 'Box needs an OAuth app of your own. On a managed account an administrator can instead add ' +
33
+ 'credentials to the Custom Box MCP Server integration in the Admin Console, which is the route ' +
34
+ 'Box prefers; these steps take the developer one, which is what an individual account has.',
35
+ docs_url: 'https://developer.box.com/guides/box-mcp/remote/',
36
+ steps: [
37
+ 'Sign in at https://app.box.com/developers/console and choose "Create Platform App" → "Custom App".',
38
+ 'Pick "User Authentication (OAuth 2.0)" as the authentication method. Not Server Authentication — that one issues a service identity with no access to your own files.',
39
+ `Under Configuration → OAuth 2.0 Redirect URIs, add exactly: ${BOX_REDIRECT_URI} — the whole URL, including the port. Box matches it literally.`,
40
+ 'Under Application Scopes, tick at least "Read all files and folders stored in Box"; add write access only if you want the agent to be able to change files.',
41
+ 'Save, then copy the Client ID and Client Secret from the Configuration page.',
42
+ 'If your Box account is managed by an administrator, the app also has to be authorised in the Admin Console under Apps → Custom Apps Manager before it will work.',
43
+ ],
44
+ troubleshooting:
45
+ 'redirect_uri_mismatch means the URI on the app is not the one above — check the port, and 127.0.0.1 rather than localhost. ' +
46
+ 'A sign-in that succeeds and then reaches nothing is usually an app awaiting administrator authorisation, or one created with Server Authentication rather than User Authentication.',
47
+ prompts: [
48
+ {
49
+ key: 'client_id',
50
+ label: 'Box app Client ID',
51
+ secret: false,
52
+ scope: 'shared' as const,
53
+ credential_ref: `${BOX_APP}/client_id`,
54
+ },
55
+ {
56
+ key: 'client_secret',
57
+ label: 'Box app Client Secret',
58
+ secret: true,
59
+ scope: 'shared' as const,
60
+ credential_ref: `${BOX_APP}/client_secret`,
61
+ },
62
+ ],
63
+ },
64
+ });
@@ -0,0 +1,10 @@
1
+ import { defineProvider } from '#connectivity';
2
+
3
+ /** Bright Data registers us at connect time — nothing for an operator to set up. */
4
+ export const brightdata = defineProvider({
5
+ id: 'brightdata',
6
+ name: 'Bright Data',
7
+ description: 'Web scraping, search results, and datasets, via Bright Data\'s official MCP server.',
8
+ connector: { kind: 'mcp', endpoint: 'https://mcp.brightdata.com/mcp' },
9
+ auth: { kind: 'oauth', registration: 'dynamic' },
10
+ });
@@ -0,0 +1,10 @@
1
+ import { defineProvider } from '#connectivity';
2
+
3
+ /** Buildkite registers us at connect time — nothing for an operator to set up. */
4
+ export const buildkite = defineProvider({
5
+ id: 'buildkite',
6
+ name: 'Buildkite',
7
+ description: 'Pipelines, builds, jobs, and artifacts, via Buildkite\'s official MCP server.',
8
+ connector: { kind: 'mcp', endpoint: 'https://mcp.buildkite.com/mcp' },
9
+ auth: { kind: 'oauth', registration: 'dynamic' },
10
+ });
@@ -54,7 +54,7 @@ const manifest = defineProvider({
54
54
  'bunq issues an API key from inside the app, not from a web console. The key is bound to the IP addresses ' +
55
55
  'it is used from unless you mark it as a wildcard key — which you must do in the app if this endpoint will ' +
56
56
  'ever run anywhere but this machine.',
57
- docs: 'docs/detailed/setup/bunq.md',
57
+ docs: 'https://lanes.sh/docs/link/bunq',
58
58
  docs_url: 'https://doc.bunq.com/basics/authentication/api-keys',
59
59
  steps: [
60
60
  'In the bunq app: Profile → Security & Settings → Developers → API keys → Add API key.',
@@ -63,7 +63,7 @@ const manifest = defineProvider({
63
63
  'If this endpoint will run deployed rather than on this machine, mark the key as a wildcard key in the same screen. bunq refuses to set that over the API, deliberately, so it cannot be done for you.',
64
64
  'Copy the key and paste it below. Nothing is sent anywhere until you do — the handshake that registers this device runs immediately afterwards.',
65
65
  'You will be asked what to call this connection. bunq has no endpoint that reports whose account a key belongs to, so the label is yours to choose.',
66
- 'To try this without a real account first, use bunq\'s sandbox: https://public-api.sandbox.bunq.com issues a test key and needs no bank account at all. Put a manifest of your own in providers.d/ naming that base_url — see docs/detailed/setup/bunq.md.',
66
+ 'To try this without a real account first, use bunq\'s sandbox: https://public-api.sandbox.bunq.com issues a test key and needs no bank account at all. Put a manifest of your own in providers.d/ naming that base_url — see https://lanes.sh/docs/link/bunq.',
67
67
  ],
68
68
  troubleshooting:
69
69
  'bunq refused the key. The usual causes are a key that was revoked in the app, a request from an address the key ' +
@@ -0,0 +1,10 @@
1
+ import { defineProvider } from '#connectivity';
2
+
3
+ /** Calendly registers us at connect time — nothing for an operator to set up. */
4
+ export const calendly = defineProvider({
5
+ id: 'calendly',
6
+ name: 'Calendly',
7
+ description: 'Scheduled events, invitees, event types, and availability, via Calendly\'s official MCP server.',
8
+ connector: { kind: 'mcp', endpoint: 'https://mcp.calendly.com' },
9
+ auth: { kind: 'oauth', registration: 'dynamic' },
10
+ });
@@ -0,0 +1,10 @@
1
+ import { defineProvider } from '#connectivity';
2
+
3
+ /** Canva registers us at connect time — nothing for an operator to set up. */
4
+ export const canva = defineProvider({
5
+ id: 'canva',
6
+ name: 'Canva',
7
+ description: 'Designs, folders, brand templates, assets, and exports, via Canva\'s official MCP server.',
8
+ connector: { kind: 'mcp', endpoint: 'https://mcp.canva.com/mcp' },
9
+ auth: { kind: 'oauth', registration: 'dynamic' },
10
+ });
@@ -0,0 +1,10 @@
1
+ import { defineProvider } from '#connectivity';
2
+
3
+ /** CircleCI registers us at connect time — nothing for an operator to set up. */
4
+ export const circleci = defineProvider({
5
+ id: 'circleci',
6
+ name: 'CircleCI',
7
+ description: 'Pipelines, workflows, jobs, and test results, via CircleCI\'s official MCP server.',
8
+ connector: { kind: 'mcp', endpoint: 'https://mcp.circleci.com/v1/mcp' },
9
+ auth: { kind: 'oauth', registration: 'dynamic' },
10
+ });
@@ -0,0 +1,10 @@
1
+ import { defineProvider } from '#connectivity';
2
+
3
+ /** ClickUp registers us at connect time — nothing for an operator to set up. */
4
+ export const clickup = defineProvider({
5
+ id: 'clickup',
6
+ name: 'ClickUp',
7
+ description: 'Tasks, lists, spaces, docs, and time entries, via ClickUp\'s official MCP server.',
8
+ connector: { kind: 'mcp', endpoint: 'https://mcp.clickup.com/mcp' },
9
+ auth: { kind: 'oauth', registration: 'dynamic' },
10
+ });