@every-app/sdk 0.3.0-rc.1 → 0.3.0-rc.11

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 (181) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +62 -64
  3. package/dist/cloudflare/catalogSettings.generated.d.ts +84 -0
  4. package/dist/cloudflare/catalogSettings.generated.d.ts.map +1 -0
  5. package/dist/cloudflare/catalogSettings.generated.js +175 -0
  6. package/dist/cloudflare/index.d.ts +3 -2
  7. package/dist/cloudflare/index.d.ts.map +1 -1
  8. package/dist/cloudflare/index.js +4 -1
  9. package/dist/cloudflare/sentinel.d.ts +60 -2
  10. package/dist/cloudflare/sentinel.d.ts.map +1 -1
  11. package/dist/cloudflare/sentinel.js +37 -2
  12. package/dist/cloudflare/worker.d.ts +391 -63
  13. package/dist/cloudflare/worker.d.ts.map +1 -1
  14. package/dist/cloudflare/worker.js +394 -108
  15. package/dist/dev/index.d.ts +1 -1
  16. package/dist/dev/index.d.ts.map +1 -1
  17. package/dist/dev/index.js +1 -1
  18. package/dist/dev/keypair.d.ts +2 -0
  19. package/dist/dev/keypair.d.ts.map +1 -1
  20. package/dist/dev/keypair.js +2 -0
  21. package/dist/index.d.ts +6 -0
  22. package/dist/index.d.ts.map +1 -0
  23. package/dist/index.js +5 -0
  24. package/dist/internal/aiDefault.d.ts +205 -0
  25. package/dist/internal/aiDefault.d.ts.map +1 -0
  26. package/dist/internal/aiDefault.js +335 -0
  27. package/dist/internal/aiProviders.d.ts +7 -2
  28. package/dist/internal/aiProviders.d.ts.map +1 -1
  29. package/dist/internal/aiProviders.js +19 -6
  30. package/dist/internal/appSettings.d.ts +82 -4
  31. package/dist/internal/appSettings.d.ts.map +1 -1
  32. package/dist/internal/appSettings.js +204 -29
  33. package/dist/internal/audit.d.ts +108 -0
  34. package/dist/internal/audit.d.ts.map +1 -0
  35. package/dist/internal/audit.js +235 -0
  36. package/dist/internal/capabilities.d.ts +2261 -815
  37. package/dist/internal/capabilities.d.ts.map +1 -1
  38. package/dist/internal/capabilities.js +122 -1034
  39. package/dist/internal/catalog/commerce.d.ts +394 -0
  40. package/dist/internal/catalog/commerce.d.ts.map +1 -0
  41. package/dist/internal/catalog/commerce.js +477 -0
  42. package/dist/internal/catalog/comms.d.ts +375 -0
  43. package/dist/internal/catalog/comms.d.ts.map +1 -0
  44. package/dist/internal/catalog/comms.js +520 -0
  45. package/dist/internal/catalog/index.d.ts +2008 -0
  46. package/dist/internal/catalog/index.d.ts.map +1 -0
  47. package/dist/internal/catalog/index.js +19 -0
  48. package/dist/internal/catalog/inference.d.ts +393 -0
  49. package/dist/internal/catalog/inference.d.ts.map +1 -0
  50. package/dist/internal/catalog/inference.js +460 -0
  51. package/dist/internal/catalog/infra.d.ts +458 -0
  52. package/dist/internal/catalog/infra.d.ts.map +1 -0
  53. package/dist/internal/catalog/infra.js +608 -0
  54. package/dist/internal/catalog/search.d.ts +400 -0
  55. package/dist/internal/catalog/search.d.ts.map +1 -0
  56. package/dist/internal/catalog/search.js +463 -0
  57. package/dist/internal/devProviders.d.ts +2 -2
  58. package/dist/internal/devProviders.d.ts.map +1 -1
  59. package/dist/internal/devProviders.js +19 -1
  60. package/dist/internal/events.d.ts +65 -3
  61. package/dist/internal/events.d.ts.map +1 -1
  62. package/dist/internal/events.js +110 -5
  63. package/dist/internal/gatewaySettings.d.ts +3 -2
  64. package/dist/internal/gatewaySettings.d.ts.map +1 -1
  65. package/dist/internal/gatewaySettings.js +12 -6
  66. package/dist/internal/identity.d.ts +41 -4
  67. package/dist/internal/identity.d.ts.map +1 -1
  68. package/dist/internal/identity.js +48 -10
  69. package/dist/internal/index.d.ts +6 -1
  70. package/dist/internal/index.d.ts.map +1 -1
  71. package/dist/internal/index.js +6 -1
  72. package/dist/internal/members.d.ts +3 -2
  73. package/dist/internal/members.d.ts.map +1 -1
  74. package/dist/internal/oauthBuiltIns.d.ts +101 -0
  75. package/dist/internal/oauthBuiltIns.d.ts.map +1 -0
  76. package/dist/internal/oauthBuiltIns.js +122 -0
  77. package/dist/internal/oauthClient.d.ts +8 -50
  78. package/dist/internal/oauthClient.d.ts.map +1 -1
  79. package/dist/internal/oauthClient.js +2 -177
  80. package/dist/internal/oauthPresets.d.ts +32 -0
  81. package/dist/internal/oauthPresets.d.ts.map +1 -0
  82. package/dist/internal/oauthPresets.js +253 -0
  83. package/dist/internal/oauthScopes.d.ts +43 -100
  84. package/dist/internal/oauthScopes.d.ts.map +1 -1
  85. package/dist/internal/oauthScopes.js +83 -382
  86. package/dist/internal/oauthSpec.d.ts +103 -0
  87. package/dist/internal/oauthSpec.d.ts.map +1 -0
  88. package/dist/internal/oauthSpec.js +520 -0
  89. package/dist/internal/protocol.d.ts +97 -5
  90. package/dist/internal/protocol.d.ts.map +1 -1
  91. package/dist/internal/protocol.js +89 -1
  92. package/dist/internal/providerRegistry.d.ts +17 -6
  93. package/dist/internal/providerRegistry.d.ts.map +1 -1
  94. package/dist/internal/providerRegistry.js +71 -8
  95. package/dist/internal/providerSpec.d.ts +18 -7
  96. package/dist/internal/providerSpec.d.ts.map +1 -1
  97. package/dist/internal/providerSpec.js +420 -34
  98. package/dist/internal/remoteMcp.d.ts.map +1 -1
  99. package/dist/internal/remoteMcp.js +1 -5
  100. package/dist/internal/signInProviders.d.ts +104 -0
  101. package/dist/internal/signInProviders.d.ts.map +1 -0
  102. package/dist/internal/signInProviders.js +245 -0
  103. package/dist/internal/tools.d.ts +5 -4
  104. package/dist/internal/tools.d.ts.map +1 -1
  105. package/dist/internal/tools.js +4 -3
  106. package/dist/manifest.d.ts +267 -0
  107. package/dist/manifest.d.ts.map +1 -0
  108. package/dist/manifest.js +30 -0
  109. package/dist/server/ai.d.ts +111 -0
  110. package/dist/server/ai.d.ts.map +1 -0
  111. package/dist/server/ai.js +145 -0
  112. package/dist/server/aiBinding.d.ts +68 -0
  113. package/dist/server/aiBinding.d.ts.map +1 -0
  114. package/dist/server/aiBinding.js +294 -0
  115. package/dist/server/attribution.d.ts +18 -5
  116. package/dist/server/attribution.d.ts.map +1 -1
  117. package/dist/server/attribution.js +19 -5
  118. package/dist/server/connectUrl.d.ts +16 -0
  119. package/dist/server/connectUrl.d.ts.map +1 -0
  120. package/dist/server/connectUrl.js +40 -0
  121. package/dist/server/context.d.ts +68 -0
  122. package/dist/server/context.d.ts.map +1 -0
  123. package/dist/server/context.js +117 -0
  124. package/dist/server/emailBinding.d.ts +72 -0
  125. package/dist/server/emailBinding.d.ts.map +1 -0
  126. package/dist/server/emailBinding.js +124 -0
  127. package/dist/server/emit.d.ts +1 -1
  128. package/dist/server/emit.d.ts.map +1 -1
  129. package/dist/server/emit.js +7 -7
  130. package/dist/server/env.d.ts +44 -0
  131. package/dist/server/env.d.ts.map +1 -0
  132. package/dist/server/env.js +1 -0
  133. package/dist/server/eventsEndpoint.d.ts +48 -0
  134. package/dist/server/eventsEndpoint.d.ts.map +1 -0
  135. package/dist/server/eventsEndpoint.js +145 -0
  136. package/dist/server/everyApp.d.ts +40 -11
  137. package/dist/server/everyApp.d.ts.map +1 -1
  138. package/dist/server/everyApp.js +116 -6
  139. package/dist/server/fromMcpTools.d.ts +89 -0
  140. package/dist/server/fromMcpTools.d.ts.map +1 -0
  141. package/dist/server/fromMcpTools.js +192 -0
  142. package/dist/server/gatewayFetch.d.ts +42 -18
  143. package/dist/server/gatewayFetch.d.ts.map +1 -1
  144. package/dist/server/gatewayFetch.js +138 -19
  145. package/dist/server/gatewayUrl.d.ts +13 -0
  146. package/dist/server/gatewayUrl.d.ts.map +1 -0
  147. package/dist/server/gatewayUrl.js +34 -0
  148. package/dist/server/index.d.ts +16 -4
  149. package/dist/server/index.d.ts.map +1 -1
  150. package/dist/server/index.js +13 -3
  151. package/dist/server/integrationFetch.d.ts +92 -73
  152. package/dist/server/integrationFetch.d.ts.map +1 -1
  153. package/dist/server/integrationFetch.js +206 -319
  154. package/dist/server/listMembers.d.ts +1 -1
  155. package/dist/server/listMembers.d.ts.map +1 -1
  156. package/dist/server/listMembers.js +8 -11
  157. package/dist/server/refusal.d.ts +55 -0
  158. package/dist/server/refusal.d.ts.map +1 -0
  159. package/dist/server/refusal.js +193 -0
  160. package/dist/server/sendEmail.d.ts +1 -1
  161. package/dist/server/sendEmail.d.ts.map +1 -1
  162. package/dist/server/sendEmail.js +7 -7
  163. package/dist/server/tools.d.ts +7 -2
  164. package/dist/server/tools.d.ts.map +1 -1
  165. package/dist/server/tools.js +2 -1
  166. package/dist/server/toolsEndpoint.d.ts +3 -1
  167. package/dist/server/toolsEndpoint.d.ts.map +1 -1
  168. package/dist/server/toolsEndpoint.js +19 -7
  169. package/dist/server/webhooks.d.ts +56 -0
  170. package/dist/server/webhooks.d.ts.map +1 -0
  171. package/dist/server/webhooks.js +205 -0
  172. package/dist/tanstack-start/index.d.ts +9 -0
  173. package/dist/tanstack-start/index.d.ts.map +1 -0
  174. package/dist/tanstack-start/index.js +29 -0
  175. package/dist/testing/index.d.ts +93 -0
  176. package/dist/testing/index.d.ts.map +1 -0
  177. package/dist/testing/index.js +156 -0
  178. package/package.json +35 -14
  179. package/dist/server/authorizeUrl.d.ts +0 -43
  180. package/dist/server/authorizeUrl.d.ts.map +0 -1
  181. package/dist/server/authorizeUrl.js +0 -110
@@ -13,11 +13,24 @@
13
13
  * `everyapp.config.ts` (the Cloudflare Vite plugin hides every file
14
14
  * `cloudflare.config.ts` imports from the Worker in dev).
15
15
  * It also answers the gateway's internal tools contract
16
- * (`/__everyapp/tools/*`) for the app's `defineTools()` catalog.
16
+ * (`/__everyapp/tools/*`) for the app's `defineTools()` catalog, and the
17
+ * event sink's deliveries (`/__everyapp/events`) for `events: { on }`.
18
+ *
19
+ * Around the handler and around each tool call it opens the request
20
+ * context (context.ts): `currentUser()` reads the user anywhere below, and
21
+ * every call back to the Gateway is attributed to the request without a
22
+ * `for`. A deployed Worker is pinned to its organization
23
+ * (`EVERY_APP_ORG_ID`), and an identity for any other is refused on both
24
+ * paths. The response leaves without `Set-Cookie`: the perimeter strips
25
+ * every `Cookie` before a request reaches the app, so a cookie the app's
26
+ * framework sets could never come back, and one named like the Gateway's
27
+ * session cookie would lock the person out of the app host.
17
28
  */
18
- import { APP_ID_ENV, AUDIENCE_ENV, ConfigurationError, DEV_ENV, IdentityError, ISSUER_ENV, PUBLIC_KEYS_ENV, getIdentityFromRequest, } from "../internal/index.js";
29
+ import { APP_ID_ENV, AUDIENCE_ENV, ConfigurationError, DEV_ENV, IdentityError, IDENTITY_HEADER, ISSUER_ENV, ORG_ID_ENV, PUBLIC_KEYS_ENV, getIdentityFromRequest, isAdmin, isAgentCall, } from "../internal/index.js";
30
+ import { currentUser, serveRequest } from "./context.js";
19
31
  import { handleToolsRequest } from "./toolsEndpoint.js";
20
- export { ConfigurationError, IdentityError };
32
+ import { handleEventsRequest, } from "./eventsEndpoint.js";
33
+ export { ConfigurationError, IdentityError, isAdmin, isAgentCall };
21
34
  const requestIdentities = new WeakMap();
22
35
  function envValue(env, key) {
23
36
  if (!env || typeof env !== "object")
@@ -68,6 +81,17 @@ function audienceFor(env, config) {
68
81
  return appId;
69
82
  throw new ConfigurationError(`${AUDIENCE_ENV} is not set — the app's cloudflare.config.ts must declare it (everyAppWorker() does at deploy).`);
70
83
  }
84
+ /**
85
+ * The organization a deployed Worker serves ({@link ORG_ID_ENV}), which an
86
+ * identity must name. Under the dev opt-in there is none to enforce: the
87
+ * stub gateway's one organization is the only one, and `everyapp dev
88
+ * --mode mirror` signs real organizations in.
89
+ */
90
+ function pinnedOrganization(env, config) {
91
+ if (devIdentitiesAllowed(env, config))
92
+ return undefined;
93
+ return envValue(env, ORG_ID_ENV);
94
+ }
71
95
  function unauthorized(message) {
72
96
  return Response.json({ error: "unauthenticated", message }, { status: 401 });
73
97
  }
@@ -77,6 +101,7 @@ function misconfigured(message) {
77
101
  function identityOptions(env, config, audience) {
78
102
  return {
79
103
  audience,
104
+ organizationId: pinnedOrganization(env, config),
80
105
  issuer: config.issuer ?? envValue(env, ISSUER_ENV),
81
106
  publicKeys: config.publicKeys,
82
107
  env: env,
@@ -91,15 +116,54 @@ async function resolveIdentity(request, env, config) {
91
116
  function rememberIdentity(request, identity) {
92
117
  requestIdentities.set(request, identity);
93
118
  }
119
+ /**
120
+ * The user the request context already verified, when `request` carries
121
+ * the same identity token: a framework that rebuilds the request (TanStack
122
+ * Start's `getRequest()`, a Hono context) still gets the one verification.
123
+ */
124
+ function contextUserFor(request) {
125
+ const headers = "headers" in request ? request.headers : request;
126
+ const token = headers.get(IDENTITY_HEADER);
127
+ if (!token)
128
+ return null;
129
+ const scoped = servedRequestToken();
130
+ return scoped && scoped.token === token ? scoped.user : null;
131
+ }
132
+ /**
133
+ * The served request's token and user, or null outside a request. Reads
134
+ * the context without throwing (currentUser() throws outside one).
135
+ */
136
+ function servedRequestToken() {
137
+ let user;
138
+ try {
139
+ user = currentUser();
140
+ }
141
+ catch {
142
+ return null;
143
+ }
144
+ if (!user)
145
+ return null;
146
+ const served = servedRequests.get(user);
147
+ return served ? { token: served, user } : null;
148
+ }
149
+ // The identity token of each request everyApp() serves, by its user, so a
150
+ // second read of the same token answers from the context.
151
+ const servedRequests = new WeakMap();
94
152
  /**
95
153
  * Return the verified Every App user for this request, or null for a declared
96
- * public request. Uses the request cache first, then verifies the identity
97
- * headers from the app id and public keys configured in env.
154
+ * public request. `request` is the inbound `Request` or just its `Headers`
155
+ * (a framework that hands out headers alone needs no made-up Request).
156
+ * Answers from the request context or the per-request cache first, then
157
+ * verifies the identity headers from the audience and public keys in env.
158
+ * Inside a handler `everyApp()` serves, `currentUser()` is the shorter way.
98
159
  */
99
160
  export async function getEveryAppUser(request, env) {
100
161
  const cached = requestIdentities.get(request);
101
162
  if (cached)
102
163
  return cached.user;
164
+ const fromContext = contextUserFor(request);
165
+ if (fromContext)
166
+ return fromContext;
103
167
  const identity = await resolveIdentity(request, env, {
104
168
  keysEnvVar: PUBLIC_KEYS_ENV,
105
169
  });
@@ -125,6 +189,40 @@ export async function requireEveryAppUser(request, env) {
125
189
  throw error;
126
190
  }
127
191
  }
192
+ /**
193
+ * The user of the request being served (`currentUser()`), or throw the
194
+ * same 401 JSON Response as `requireEveryAppUser()` on a public route.
195
+ * Outside a request (`withOrganization()` work, a handler not under
196
+ * `everyApp()`) it throws `RequestContextError`, as `currentUser()` does.
197
+ */
198
+ export function requireCurrentUser() {
199
+ const user = currentUser();
200
+ if (!user) {
201
+ throw unauthorized("this request does not have an authenticated user");
202
+ }
203
+ return user;
204
+ }
205
+ /**
206
+ * `response` without `Set-Cookie`: the perimeter strips every `Cookie`
207
+ * before a request reaches the app, so no cookie the app sets ever comes
208
+ * back, and one named like the Gateway's session cookie locks the person
209
+ * out of the app host (the perimeter refuses a session cookie sent twice).
210
+ * A WebSocket handshake (101) passes as it is: it can't be rebuilt from
211
+ * its body, and the perimeter drops its cookies' `Domain`.
212
+ */
213
+ function withoutCookies(response) {
214
+ if (response.status === 101 || !response.headers.has("set-cookie")) {
215
+ return response;
216
+ }
217
+ console.warn("[everyapp] Dropped a Set-Cookie header: the Gateway never sends an app's cookies back to it.");
218
+ const headers = new Headers(response.headers);
219
+ headers.delete("set-cookie");
220
+ return new Response(response.body, {
221
+ status: response.status,
222
+ statusText: response.statusText,
223
+ headers,
224
+ });
225
+ }
128
226
  export function hasScope(user, scope) {
129
227
  return user.scopes.includes("*") || user.scopes.includes(scope);
130
228
  }
@@ -165,6 +263,13 @@ export function everyApp(handler, manifestOrOptions, legacyOptions = {}) {
165
263
  });
166
264
  if (toolsResponse)
167
265
  return toolsResponse;
266
+ // So does the event sink's delivery path (`/__everyapp/events`).
267
+ const eventsResponse = await handleEventsRequest(request, env, ctx, {
268
+ events: options.events,
269
+ identity: identityOptions(env, config, audience),
270
+ });
271
+ if (eventsResponse)
272
+ return eventsResponse;
168
273
  let identity;
169
274
  try {
170
275
  identity = await getIdentityFromRequest(request, identityOptions(env, config, audience));
@@ -179,7 +284,12 @@ export function everyApp(handler, manifestOrOptions, legacyOptions = {}) {
179
284
  return unauthorized(err.message);
180
285
  throw err;
181
286
  }
182
- return handler(request, env, ctx, identity.user);
287
+ const { user, expiresAt } = identity;
288
+ if (user) {
289
+ servedRequests.set(user, request.headers.get(IDENTITY_HEADER) ?? "");
290
+ }
291
+ const response = await serveRequest({ request, user, expiresAt }, () => handler(request, env, ctx, user));
292
+ return withoutCookies(response);
183
293
  },
184
294
  };
185
295
  }
@@ -0,0 +1,89 @@
1
+ /**
2
+ * `fromMcpTools()` — an app's existing MCP tools as `defineTools()` entries.
3
+ *
4
+ * const tools: McpToolLike[] = [];
5
+ * registerMyTools((name, config, handler) =>
6
+ * tools.push({ name, ...config, handler }),
7
+ * );
8
+ * export default everyApp(handler, {
9
+ * tools: defineTools(fromMcpTools(tools), { instructions }),
10
+ * });
11
+ *
12
+ * Takes the shape `McpServer.registerTool(name, config, handler)` is called
13
+ * with (or what a low-level server's `tools/list` returns, plus a handler),
14
+ * structurally: the SDK takes no dependency on `@modelcontextprotocol/sdk`,
15
+ * and a live `McpServer` keeps its registry private, so the app hands over
16
+ * the list it registers from.
17
+ */
18
+ import { type ToolRisk } from "../internal/index.js";
19
+ import { type StandardSchemaWithJSON, type ToolContext, type ToolDefinition, type ToolSchema } from "./tools.js";
20
+ /** MCP tool annotations (all hints); only these are read. */
21
+ export interface McpToolAnnotationsLike {
22
+ title?: string;
23
+ readOnlyHint?: boolean;
24
+ destructiveHint?: boolean;
25
+ idempotentHint?: boolean;
26
+ openWorldHint?: boolean;
27
+ }
28
+ /** An MCP `CallToolResult`, structurally. */
29
+ export interface McpToolResultLike {
30
+ content?: ReadonlyArray<{
31
+ readonly type: string;
32
+ }>;
33
+ structuredContent?: unknown;
34
+ isError?: boolean;
35
+ }
36
+ /**
37
+ * A tool schema as MCP servers write one: a {@link ToolSchema} (a full zod
38
+ * object, `{ jsonSchema, validate }`), a raw shape of Standard Schemas
39
+ * (`{ query: z.string() }`, what `McpServer.registerTool` takes), or a plain
40
+ * JSON Schema object (a low-level server's `tools/list`), which is described
41
+ * but not validated: the handler checks its own arguments, as it did before.
42
+ */
43
+ export type McpToolSchemaLike = ToolSchema | Readonly<Record<string, StandardSchemaWithJSON>> | Readonly<Record<string, unknown>>;
44
+ /** One MCP tool: `registerTool`'s name, config and handler, flattened. */
45
+ export interface McpToolLike<TExtra = ToolContext> {
46
+ name: string;
47
+ title?: string;
48
+ description?: string;
49
+ /** Omitted: the tool takes no arguments. */
50
+ inputSchema?: McpToolSchemaLike;
51
+ outputSchema?: McpToolSchemaLike;
52
+ annotations?: McpToolAnnotationsLike;
53
+ /**
54
+ * Called with the validated arguments and the value of
55
+ * {@link FromMcpToolsOptions.context} (the call's {@link ToolContext} by
56
+ * default). Return a `CallToolResult`; throw {@link ToolError} for a message
57
+ * the model may read.
58
+ */
59
+ handler: (args: never, extra: TExtra) => McpToolResultLike | Promise<McpToolResultLike>;
60
+ }
61
+ export interface FromMcpToolsOptions<TExtra = ToolContext> {
62
+ /**
63
+ * What each handler gets as its second argument, built per call from the
64
+ * verified caller (`user`, `env`, `request`, `callId`). Default: the
65
+ * {@link ToolContext} itself.
66
+ */
67
+ context?: (context: ToolContext) => TExtra | Promise<TExtra>;
68
+ /**
69
+ * A tool's risk, overriding the default for the tools it returns a risk
70
+ * for. Default: `readOnlyHint: true` is `read`, `destructiveHint: true`
71
+ * is `destructive`, anything else `write`.
72
+ */
73
+ risk?: (tool: McpToolLike<TExtra>) => ToolRisk | undefined;
74
+ }
75
+ /**
76
+ * Adapt MCP tools into `defineTools()` entries, keyed by tool name, to spread
77
+ * beside the app's own: `defineTools({ ...fromMcpTools(list), my_tool })`.
78
+ *
79
+ * - `title`, `description` (the name when absent), `idempotentHint` and
80
+ * `openWorldHint` carry over; risk as {@link FromMcpToolsOptions.risk}.
81
+ * - A result's text blocks, joined by a blank line, are what the model
82
+ * reads; `structuredContent` is sent as structured content (checked
83
+ * against `outputSchema` when there is one). Other blocks are dropped.
84
+ * - `isError: true` becomes a {@link ToolError} with the result's text.
85
+ *
86
+ * Names must already be lowercase snake_case; `defineTools()` checks them.
87
+ */
88
+ export declare function fromMcpTools<TExtra = ToolContext>(tools: Iterable<McpToolLike<TExtra>>, options?: FromMcpToolsOptions<TExtra>): Record<string, ToolDefinition>;
89
+ //# sourceMappingURL=fromMcpTools.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fromMcpTools.d.ts","sourceRoot":"","sources":["../../src/server/fromMcpTools.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,EAAqB,KAAK,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AACxE,OAAO,EAGL,KAAK,sBAAsB,EAC3B,KAAK,WAAW,EAChB,KAAK,cAAc,EACnB,KAAK,UAAU,EAChB,MAAM,YAAY,CAAC;AAIpB,6DAA6D;AAC7D,MAAM,WAAW,sBAAsB;IACrC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,6CAA6C;AAC7C,MAAM,WAAW,iBAAiB;IAChC,OAAO,CAAC,EAAE,aAAa,CAAC;QAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACnD,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED;;;;;;GAMG;AACH,MAAM,MAAM,iBAAiB,GACzB,UAAU,GACV,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,sBAAsB,CAAC,CAAC,GAChD,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;AAEtC,0EAA0E;AAC1E,MAAM,WAAW,WAAW,CAAC,MAAM,GAAG,WAAW;IAC/C,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,4CAA4C;IAC5C,WAAW,CAAC,EAAE,iBAAiB,CAAC;IAChC,YAAY,CAAC,EAAE,iBAAiB,CAAC;IACjC,WAAW,CAAC,EAAE,sBAAsB,CAAC;IACrC;;;;;OAKG;IACH,OAAO,EAAE,CACP,IAAI,EAAE,KAAK,EACX,KAAK,EAAE,MAAM,KACV,iBAAiB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAAC;CACrD;AAED,MAAM,WAAW,mBAAmB,CAAC,MAAM,GAAG,WAAW;IACvD;;;;OAIG;IACH,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC7D;;;;OAIG;IACH,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,WAAW,CAAC,MAAM,CAAC,KAAK,QAAQ,GAAG,SAAS,CAAC;CAC5D;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,YAAY,CAAC,MAAM,GAAG,WAAW,EAC/C,KAAK,EAAE,QAAQ,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,EACpC,OAAO,GAAE,mBAAmB,CAAC,MAAM,CAAM,GACxC,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CA+ChC"}
@@ -0,0 +1,192 @@
1
+ /**
2
+ * `fromMcpTools()` — an app's existing MCP tools as `defineTools()` entries.
3
+ *
4
+ * const tools: McpToolLike[] = [];
5
+ * registerMyTools((name, config, handler) =>
6
+ * tools.push({ name, ...config, handler }),
7
+ * );
8
+ * export default everyApp(handler, {
9
+ * tools: defineTools(fromMcpTools(tools), { instructions }),
10
+ * });
11
+ *
12
+ * Takes the shape `McpServer.registerTool(name, config, handler)` is called
13
+ * with (or what a low-level server's `tools/list` returns, plus a handler),
14
+ * structurally: the SDK takes no dependency on `@modelcontextprotocol/sdk`,
15
+ * and a live `McpServer` keeps its registry private, so the app hands over
16
+ * the list it registers from.
17
+ */
18
+ import { TOOL_NAME_PATTERN } from "../internal/index.js";
19
+ import { ToolError, toolResult, } from "./tools.js";
20
+ const JSON_SCHEMA_TARGET = "draft-2020-12";
21
+ /**
22
+ * Adapt MCP tools into `defineTools()` entries, keyed by tool name, to spread
23
+ * beside the app's own: `defineTools({ ...fromMcpTools(list), my_tool })`.
24
+ *
25
+ * - `title`, `description` (the name when absent), `idempotentHint` and
26
+ * `openWorldHint` carry over; risk as {@link FromMcpToolsOptions.risk}.
27
+ * - A result's text blocks, joined by a blank line, are what the model
28
+ * reads; `structuredContent` is sent as structured content (checked
29
+ * against `outputSchema` when there is one). Other blocks are dropped.
30
+ * - `isError: true` becomes a {@link ToolError} with the result's text.
31
+ *
32
+ * Names must already be lowercase snake_case; `defineTools()` checks them.
33
+ */
34
+ export function fromMcpTools(tools, options = {}) {
35
+ const out = {};
36
+ for (const tool of tools) {
37
+ const problem = (message) => new TypeError(`fromMcpTools: tool "${tool?.name}" ${message}`);
38
+ if (!tool || typeof tool.name !== "string") {
39
+ throw new TypeError("fromMcpTools: every tool needs a name");
40
+ }
41
+ if (!TOOL_NAME_PATTERN.test(tool.name)) {
42
+ throw problem("must be named in lowercase snake_case: a letter, then letters, digits or _ (at most 48 characters); rename it in the list");
43
+ }
44
+ if (Object.prototype.hasOwnProperty.call(out, tool.name)) {
45
+ throw problem("is listed twice");
46
+ }
47
+ if (typeof tool.handler !== "function") {
48
+ throw problem("needs a handler function");
49
+ }
50
+ const output = tool.outputSchema === undefined
51
+ ? undefined
52
+ : toToolSchema(tool.outputSchema, problem);
53
+ const annotations = tool.annotations ?? {};
54
+ const title = tool.title ?? annotations.title;
55
+ out[tool.name] = {
56
+ ...(title !== undefined ? { title } : {}),
57
+ description: tool.description?.trim() ? tool.description : tool.name,
58
+ risk: options.risk?.(tool) ?? defaultRisk(annotations),
59
+ input: toToolSchema(tool.inputSchema ?? {}, problem),
60
+ ...(output ? { output } : {}),
61
+ ...(annotations.idempotentHint !== undefined
62
+ ? { idempotent: annotations.idempotentHint }
63
+ : {}),
64
+ ...(annotations.openWorldHint !== undefined
65
+ ? { openWorld: annotations.openWorldHint }
66
+ : {}),
67
+ handler: async (args, context) => {
68
+ const extra = options.context
69
+ ? await options.context(context)
70
+ : context;
71
+ const result = await tool.handler(args, extra);
72
+ return fromCallToolResult(result);
73
+ },
74
+ };
75
+ }
76
+ return out;
77
+ }
78
+ function defaultRisk(annotations) {
79
+ if (annotations.readOnlyHint === true)
80
+ return "read";
81
+ return annotations.destructiveHint === true ? "destructive" : "write";
82
+ }
83
+ function fromCallToolResult(result) {
84
+ if (!isRecord(result)) {
85
+ throw new Error("the MCP tool did not return a CallToolResult");
86
+ }
87
+ const text = (Array.isArray(result.content) ? result.content : [])
88
+ .flatMap((part) => isRecord(part) && part.type === "text" && typeof part.text === "string"
89
+ ? [part.text]
90
+ : [])
91
+ .join("\n\n");
92
+ if (result.isError === true)
93
+ throw new ToolError(text || "The tool failed.");
94
+ const structured = result.structuredContent;
95
+ if (isRecord(structured) && !Array.isArray(structured)) {
96
+ return toolResult({ text, structured });
97
+ }
98
+ return text;
99
+ }
100
+ function isRecord(value) {
101
+ return typeof value === "object" && value !== null;
102
+ }
103
+ function isStandardWithJson(value) {
104
+ if (!isRecord(value))
105
+ return false;
106
+ const standard = value["~standard"];
107
+ return (isRecord(standard) &&
108
+ typeof standard.validate === "function" &&
109
+ isRecord(standard.jsonSchema));
110
+ }
111
+ function isPlainToolSchema(value) {
112
+ return isRecord(value.jsonSchema) && typeof value.validate === "function";
113
+ }
114
+ function toToolSchema(schema, problem) {
115
+ if (!isRecord(schema) || Array.isArray(schema)) {
116
+ throw problem("has a schema that is not an object");
117
+ }
118
+ if (isStandardWithJson(schema) || isPlainToolSchema(schema)) {
119
+ return schema;
120
+ }
121
+ if ("~standard" in schema) {
122
+ throw problem("has a schema that doesn't convert to JSON Schema (zod >= 4.2 does)");
123
+ }
124
+ const values = Object.values(schema);
125
+ if (values.every((value) => isRecord(value) && "~standard" in value)) {
126
+ if (!values.every(isStandardWithJson)) {
127
+ throw problem("has a raw shape whose fields don't convert to JSON Schema (zod >= 4.2 does)");
128
+ }
129
+ return rawShapeSchema(schema);
130
+ }
131
+ return jsonSchemaOnly(schema);
132
+ }
133
+ /** A plain JSON Schema: described as is; any object passes. */
134
+ function jsonSchemaOnly(jsonSchema) {
135
+ return {
136
+ jsonSchema,
137
+ validate: (value) => isRecord(value) && !Array.isArray(value)
138
+ ? { value }
139
+ : { issues: [{ message: "Expected an object" }] },
140
+ };
141
+ }
142
+ /**
143
+ * `{ key: schema }` as an object schema, the way `McpServer` wraps one in
144
+ * `z.object()`: unknown keys are dropped, and a field is optional when its
145
+ * schema accepts `undefined`.
146
+ */
147
+ function rawShapeSchema(shape) {
148
+ const properties = {};
149
+ const required = [];
150
+ for (const [key, field] of Object.entries(shape)) {
151
+ const { $schema: _, ...property } = field["~standard"].jsonSchema.input({
152
+ target: JSON_SCHEMA_TARGET,
153
+ });
154
+ properties[key] = property;
155
+ const absent = field["~standard"].validate(undefined);
156
+ // An async validator can't be asked here; call the field required.
157
+ if (absent instanceof Promise || absent.issues)
158
+ required.push(key);
159
+ }
160
+ return {
161
+ jsonSchema: {
162
+ type: "object",
163
+ properties,
164
+ ...(required.length > 0 ? { required } : {}),
165
+ },
166
+ validate: async (value) => {
167
+ if (!isRecord(value) || Array.isArray(value)) {
168
+ return { issues: [{ message: "Expected an object" }] };
169
+ }
170
+ const parsed = {};
171
+ const issues = [];
172
+ for (const [key, field] of Object.entries(shape)) {
173
+ const result = await field["~standard"].validate(value[key]);
174
+ if (result.issues) {
175
+ for (const issue of result.issues) {
176
+ issues.push({
177
+ message: issue.message,
178
+ path: [
179
+ key,
180
+ ...(issue.path ?? []).map((segment) => typeof segment === "object" ? segment.key : segment),
181
+ ],
182
+ });
183
+ }
184
+ }
185
+ else if (key in value || result.value !== undefined) {
186
+ parsed[key] = result.value;
187
+ }
188
+ }
189
+ return issues.length > 0 ? { issues } : { value: parsed };
190
+ },
191
+ };
192
+ }
@@ -3,27 +3,48 @@ export declare const BINDING_ORIGIN = "http://every-app-gateway.internal";
3
3
  export interface GatewayServiceBinding {
4
4
  fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response>;
5
5
  }
6
- export interface GatewayFetchEnv {
7
- EVERY_APP_GATEWAY?: GatewayServiceBinding;
8
- EVERYAPP_DEV?: string | boolean;
9
- /** `1` on a shared app's Workers (`everyAppWorker()` sets it). */
10
- EVERYAPP_SHARED?: string;
11
- OPENAI_API_KEY?: string;
12
- OPENROUTER_API_KEY?: string;
13
- /** Workers AI: an API token (Workers AI Read and Edit) and the account it bills. */
14
- WORKERS_AI_API_TOKEN?: string;
15
- CLOUDFLARE_ACCOUNT_ID?: string;
16
- }
6
+ /**
7
+ * The Gateway the SDK's services call: the `EVERY_APP_GATEWAY` service
8
+ * binding when deployed; under `everyapp dev --gateway` (linked dev,
9
+ * docs/design/linked-dev.md) a `{ fetch }` that sends the same requests to
10
+ * the gateway-lite's loopback forwarder, which adds the person's token and
11
+ * forwards them to the real Gateway's `/linked-dev/<app>` route. Undefined
12
+ * with neither (stub and mirror modes: the `.dev.vars` fallbacks). A value
13
+ * under the binding's name that isn't one is answered as before by each
14
+ * caller (`ConfigurationError`).
15
+ */
16
+ export declare function gatewayBinding(env: object): GatewayServiceBinding | undefined;
17
+ /**
18
+ * The linked dev shim: a request to the binding's origin goes to the
19
+ * forwarder's URL with the same path and query, the run's nonce on it. The
20
+ * forwarder answers as the Gateway did, refusals included; nothing here
21
+ * falls back to a local key.
22
+ */
23
+ export declare function linkedDevBinding(forwarderUrl: string, nonce: string): GatewayServiceBinding;
17
24
  export interface CreateGatewayFetchOptions {
18
- env: GatewayFetchEnv;
19
25
  /**
20
- * Whom the call is for: the inbound request (its user), or
21
- * `{ background: true, organizationId }` for work no person started
22
- * (crons, Workflows, queues). An organization's own app may omit it for
23
- * background work, which is then the app's; a shared app's Worker must
24
- * pass one or the other ({@link AttributionError}).
26
+ * The Worker env, as it is: the EVERY_APP_GATEWAY binding when deployed,
27
+ * or in local dev EVERYAPP_DEV and the provider's settings in
28
+ * `.dev.vars` (`OPENROUTER_API_KEY`, …). No cast needed.
29
+ */
30
+ env: object;
31
+ /**
32
+ * Whom the call is for. Left out, the request `everyApp()` is serving
33
+ * (its user, while the identity is good) or the organization
34
+ * `withOrganization()` named; outside both, `RequestContextError`.
35
+ * Explicitly: the inbound request, or `{ background: true,
36
+ * organizationId }` for work no person started (crons, Workflows,
37
+ * queues). A shared app's Worker must end up with one or the other
38
+ * ({@link AttributionError}).
25
39
  */
26
40
  for?: GatewayCallFor;
41
+ /**
42
+ * `true`: answer null instead of throwing `ConfigurationError` when AI
43
+ * is unavailable (no binding, and in local dev no provider key), so a
44
+ * feature that can do without a model checks for null. A binding that
45
+ * isn't one, and an attribution mistake, still throw.
46
+ */
47
+ optional?: boolean;
27
48
  }
28
49
  /**
29
50
  * Create a fetch for the gateway's AI providers (`/api/ai/<provider>/…`),
@@ -31,7 +52,10 @@ export interface CreateGatewayFetchOptions {
31
52
  * developer's own provider key from `.dev.vars` is a direct-provider
32
53
  * fallback when no binding exists.
33
54
  */
34
- export declare function createGatewayFetch({ env, for: target, }: CreateGatewayFetchOptions): typeof fetch;
55
+ export declare function createGatewayFetch(options: CreateGatewayFetchOptions & {
56
+ optional: true;
57
+ }): typeof fetch | null;
58
+ export declare function createGatewayFetch(options: CreateGatewayFetchOptions): typeof fetch;
35
59
  /** A copy of `source` aimed at `url`, streaming its body rather than buffering it. */
36
60
  export declare function streamingRequest(url: string, source: Request, headers: Headers): Request;
37
61
  //# sourceMappingURL=gatewayFetch.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"gatewayFetch.d.ts","sourceRoot":"","sources":["../../src/server/gatewayFetch.ts"],"names":[],"mappings":"AASA,OAAO,EAGL,KAAK,cAAc,EACpB,MAAM,kBAAkB,CAAC;AAE1B,eAAO,MAAM,cAAc,sCAAsC,CAAC;AAGlE,MAAM,WAAW,qBAAqB;IACpC,KAAK,CAAC,KAAK,EAAE,WAAW,GAAG,GAAG,EAAE,IAAI,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;CACxE;AAED,MAAM,WAAW,eAAe;IAC9B,iBAAiB,CAAC,EAAE,qBAAqB,CAAC;IAC1C,YAAY,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IAChC,kEAAkE;IAClE,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,oFAAoF;IACpF,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,qBAAqB,CAAC,EAAE,MAAM,CAAC;CAChC;AAED,MAAM,WAAW,yBAAyB;IACxC,GAAG,EAAE,eAAe,CAAC;IACrB;;;;;;OAMG;IACH,GAAG,CAAC,EAAE,cAAc,CAAC;CACtB;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,EACjC,GAAG,EACH,GAAG,EAAE,MAAM,GACZ,EAAE,yBAAyB,GAAG,OAAO,KAAK,CA8D1C;AAED,sFAAsF;AACtF,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,OAAO,EACf,OAAO,EAAE,OAAO,GACf,OAAO,CAUT"}
1
+ {"version":3,"file":"gatewayFetch.d.ts","sourceRoot":"","sources":["../../src/server/gatewayFetch.ts"],"names":[],"mappings":"AAiBA,OAAO,EAGL,KAAK,cAAc,EACpB,MAAM,kBAAkB,CAAC;AAE1B,eAAO,MAAM,cAAc,sCAAsC,CAAC;AAGlE,MAAM,WAAW,qBAAqB;IACpC,KAAK,CAAC,KAAK,EAAE,WAAW,GAAG,GAAG,EAAE,IAAI,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;CACxE;AAED;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,qBAAqB,GAAG,SAAS,CAW7E;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAC9B,YAAY,EAAE,MAAM,EACpB,KAAK,EAAE,MAAM,GACZ,qBAAqB,CAiBvB;AAeD,MAAM,WAAW,yBAAyB;IACxC;;;;OAIG;IACH,GAAG,EAAE,MAAM,CAAC;IACZ;;;;;;;;OAQG;IACH,GAAG,CAAC,EAAE,cAAc,CAAC;IACrB;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,yBAAyB,GAAG;IAAE,QAAQ,EAAE,IAAI,CAAA;CAAE,GACtD,OAAO,KAAK,GAAG,IAAI,CAAC;AACvB,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,yBAAyB,GACjC,OAAO,KAAK,CAAC;AA2HhB,sFAAsF;AACtF,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,OAAO,EACf,OAAO,EAAE,OAAO,GACf,OAAO,CAUT"}