@every-app/sdk 0.2.0 → 0.3.0-rc.10

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 (189) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +609 -3
  3. package/dist/cloudflare/catalogSettings.generated.d.ts +81 -0
  4. package/dist/cloudflare/catalogSettings.generated.d.ts.map +1 -0
  5. package/dist/cloudflare/catalogSettings.generated.js +174 -0
  6. package/dist/cloudflare/index.d.ts +23 -0
  7. package/dist/cloudflare/index.d.ts.map +1 -0
  8. package/dist/cloudflare/index.js +24 -0
  9. package/dist/cloudflare/sentinel.d.ts +127 -0
  10. package/dist/cloudflare/sentinel.d.ts.map +1 -0
  11. package/dist/cloudflare/sentinel.js +85 -0
  12. package/dist/cloudflare/worker.d.ts +805 -0
  13. package/dist/cloudflare/worker.d.ts.map +1 -0
  14. package/dist/cloudflare/worker.js +997 -0
  15. package/dist/dev/index.d.ts +8 -0
  16. package/dist/dev/index.d.ts.map +1 -0
  17. package/dist/dev/index.js +7 -0
  18. package/dist/dev/keypair.d.ts +26 -0
  19. package/dist/dev/keypair.d.ts.map +1 -0
  20. package/dist/dev/keypair.js +62 -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 +204 -0
  25. package/dist/internal/aiDefault.d.ts.map +1 -0
  26. package/dist/internal/aiDefault.js +334 -0
  27. package/dist/internal/aiProviders.d.ts +72 -0
  28. package/dist/internal/aiProviders.d.ts.map +1 -0
  29. package/dist/internal/aiProviders.js +233 -0
  30. package/dist/internal/appSettings.d.ts +315 -0
  31. package/dist/internal/appSettings.d.ts.map +1 -0
  32. package/dist/internal/appSettings.js +549 -0
  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 +3174 -0
  37. package/dist/internal/capabilities.d.ts.map +1 -0
  38. package/dist/internal/capabilities.js +1110 -0
  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 +1973 -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 +423 -0
  52. package/dist/internal/catalog/infra.d.ts.map +1 -0
  53. package/dist/internal/catalog/infra.js +543 -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 +49 -0
  58. package/dist/internal/devProviders.d.ts.map +1 -0
  59. package/dist/internal/devProviders.js +80 -0
  60. package/dist/internal/email.d.ts +280 -0
  61. package/dist/internal/email.d.ts.map +1 -0
  62. package/dist/internal/email.js +613 -0
  63. package/dist/internal/events.d.ts +193 -0
  64. package/dist/internal/events.d.ts.map +1 -0
  65. package/dist/internal/events.js +367 -0
  66. package/dist/internal/gatewaySettings.d.ts +83 -0
  67. package/dist/internal/gatewaySettings.d.ts.map +1 -0
  68. package/dist/internal/gatewaySettings.js +215 -0
  69. package/dist/internal/identity.d.ts +57 -5
  70. package/dist/internal/identity.d.ts.map +1 -1
  71. package/dist/internal/identity.js +99 -22
  72. package/dist/internal/index.d.ts +22 -1
  73. package/dist/internal/index.d.ts.map +1 -1
  74. package/dist/internal/index.js +22 -1
  75. package/dist/internal/members.d.ts +36 -0
  76. package/dist/internal/members.d.ts.map +1 -0
  77. package/dist/internal/members.js +7 -0
  78. package/dist/internal/oauthClient.d.ts +61 -0
  79. package/dist/internal/oauthClient.d.ts.map +1 -0
  80. package/dist/internal/oauthClient.js +133 -0
  81. package/dist/internal/oauthPresets.d.ts +33 -0
  82. package/dist/internal/oauthPresets.d.ts.map +1 -0
  83. package/dist/internal/oauthPresets.js +1064 -0
  84. package/dist/internal/oauthScopes.d.ts +172 -0
  85. package/dist/internal/oauthScopes.d.ts.map +1 -0
  86. package/dist/internal/oauthScopes.js +510 -0
  87. package/dist/internal/oauthSpec.d.ts +102 -0
  88. package/dist/internal/oauthSpec.d.ts.map +1 -0
  89. package/dist/internal/oauthSpec.js +789 -0
  90. package/dist/internal/organizationName.d.ts +18 -0
  91. package/dist/internal/organizationName.d.ts.map +1 -0
  92. package/dist/internal/organizationName.js +24 -0
  93. package/dist/internal/protocol.d.ts +150 -3
  94. package/dist/internal/protocol.d.ts.map +1 -1
  95. package/dist/internal/protocol.js +163 -2
  96. package/dist/internal/providerApps.d.ts +75 -0
  97. package/dist/internal/providerApps.d.ts.map +1 -0
  98. package/dist/internal/providerApps.js +59 -0
  99. package/dist/internal/providerRegistry.d.ts +218 -0
  100. package/dist/internal/providerRegistry.d.ts.map +1 -0
  101. package/dist/internal/providerRegistry.js +432 -0
  102. package/dist/internal/providerSpec.d.ts +66 -0
  103. package/dist/internal/providerSpec.d.ts.map +1 -0
  104. package/dist/internal/providerSpec.js +1154 -0
  105. package/dist/internal/remoteMcp.d.ts +188 -0
  106. package/dist/internal/remoteMcp.d.ts.map +1 -0
  107. package/dist/internal/remoteMcp.js +543 -0
  108. package/dist/internal/signInProviders.d.ts +104 -0
  109. package/dist/internal/signInProviders.d.ts.map +1 -0
  110. package/dist/internal/signInProviders.js +245 -0
  111. package/dist/internal/tools.d.ts +115 -0
  112. package/dist/internal/tools.d.ts.map +1 -0
  113. package/dist/internal/tools.js +80 -0
  114. package/dist/manifest.d.ts +238 -0
  115. package/dist/manifest.d.ts.map +1 -0
  116. package/dist/manifest.js +30 -0
  117. package/dist/server/ai.d.ts +111 -0
  118. package/dist/server/ai.d.ts.map +1 -0
  119. package/dist/server/ai.js +145 -0
  120. package/dist/server/aiBinding.d.ts +68 -0
  121. package/dist/server/aiBinding.d.ts.map +1 -0
  122. package/dist/server/aiBinding.js +294 -0
  123. package/dist/server/attribution.d.ts +43 -0
  124. package/dist/server/attribution.d.ts.map +1 -0
  125. package/dist/server/attribution.js +75 -0
  126. package/dist/server/authorizeUrl.d.ts +43 -0
  127. package/dist/server/authorizeUrl.d.ts.map +1 -0
  128. package/dist/server/authorizeUrl.js +106 -0
  129. package/dist/server/connectUrl.d.ts +21 -0
  130. package/dist/server/connectUrl.d.ts.map +1 -0
  131. package/dist/server/connectUrl.js +37 -0
  132. package/dist/server/context.d.ts +68 -0
  133. package/dist/server/context.d.ts.map +1 -0
  134. package/dist/server/context.js +117 -0
  135. package/dist/server/emailBinding.d.ts +72 -0
  136. package/dist/server/emailBinding.d.ts.map +1 -0
  137. package/dist/server/emailBinding.js +124 -0
  138. package/dist/server/emit.d.ts +63 -0
  139. package/dist/server/emit.d.ts.map +1 -0
  140. package/dist/server/emit.js +101 -0
  141. package/dist/server/env.d.ts +44 -0
  142. package/dist/server/env.d.ts.map +1 -0
  143. package/dist/server/env.js +1 -0
  144. package/dist/server/everyApp.d.ts +65 -19
  145. package/dist/server/everyApp.d.ts.map +1 -1
  146. package/dist/server/everyApp.js +195 -30
  147. package/dist/server/fromMcpTools.d.ts +89 -0
  148. package/dist/server/fromMcpTools.d.ts.map +1 -0
  149. package/dist/server/fromMcpTools.js +192 -0
  150. package/dist/server/gatewayFetch.d.ts +54 -14
  151. package/dist/server/gatewayFetch.d.ts.map +1 -1
  152. package/dist/server/gatewayFetch.js +201 -37
  153. package/dist/server/gatewayUrl.d.ts +13 -0
  154. package/dist/server/gatewayUrl.d.ts.map +1 -0
  155. package/dist/server/gatewayUrl.js +34 -0
  156. package/dist/server/index.d.ts +23 -3
  157. package/dist/server/index.d.ts.map +1 -1
  158. package/dist/server/index.js +19 -2
  159. package/dist/server/integrationFetch.d.ts +162 -0
  160. package/dist/server/integrationFetch.d.ts.map +1 -0
  161. package/dist/server/integrationFetch.js +688 -0
  162. package/dist/server/listMembers.d.ts +32 -0
  163. package/dist/server/listMembers.d.ts.map +1 -0
  164. package/dist/server/listMembers.js +71 -0
  165. package/dist/server/nodeServer.d.ts +48 -0
  166. package/dist/server/nodeServer.d.ts.map +1 -0
  167. package/dist/server/nodeServer.js +45 -0
  168. package/dist/server/refusal.d.ts +55 -0
  169. package/dist/server/refusal.d.ts.map +1 -0
  170. package/dist/server/refusal.js +193 -0
  171. package/dist/server/sendEmail.d.ts +39 -0
  172. package/dist/server/sendEmail.d.ts.map +1 -0
  173. package/dist/server/sendEmail.js +114 -0
  174. package/dist/server/tools.d.ts +184 -0
  175. package/dist/server/tools.d.ts.map +1 -0
  176. package/dist/server/tools.js +347 -0
  177. package/dist/server/toolsEndpoint.d.ts +32 -0
  178. package/dist/server/toolsEndpoint.d.ts.map +1 -0
  179. package/dist/server/toolsEndpoint.js +237 -0
  180. package/dist/tanstack-start/index.d.ts +9 -0
  181. package/dist/tanstack-start/index.d.ts.map +1 -0
  182. package/dist/tanstack-start/index.js +29 -0
  183. package/dist/testing/index.d.ts +93 -0
  184. package/dist/testing/index.d.ts.map +1 -0
  185. package/dist/testing/index.js +156 -0
  186. package/package.json +42 -8
  187. package/dist/server/mcp.d.ts +0 -66
  188. package/dist/server/mcp.d.ts.map +0 -1
  189. package/dist/server/mcp.js +0 -147
@@ -0,0 +1,237 @@
1
+ /**
2
+ * The app side of the internal tools contract (../internal/tools.ts).
3
+ *
4
+ * `everyApp()` answers the two reserved paths here, before any app code runs
5
+ * and whether or not the app defines tools:
6
+ *
7
+ * - `/__everyapp/tools/list` needs the gateway's system token for this app
8
+ * (purpose "tools.list"). It describes tools; it never runs them.
9
+ * - `/__everyapp/tools/call` needs an identity token minted for this exact
10
+ * call: `chan: "mcp"` plus a `tc` claim naming the tool and call id. The
11
+ * token's scopes must include the scope the tool's risk requires; `*` does
12
+ * not count, because the ToolBus only ever mints explicit scopes.
13
+ *
14
+ * The perimeter also refuses these paths from outside and strips inbound
15
+ * `x-everyapp-*` headers, so only the gateway's ToolBus ever reaches them.
16
+ */
17
+ import { ConfigurationError, IDENTITY_HEADER, IdentityError, SYSTEM_HEADER, TOOLS_CALL_PATH, TOOLS_LIST_PATH, TOOLS_PROTOCOL_VERSION, assertOrganizationPin, catalogHash, requiredScopeForRisk, resolveVerifyOptions, verifyIdentity, verifySystemJwt, } from "../internal/index.js";
18
+ import { serveRequest } from "./context.js";
19
+ import { SDK_VERSION, ToolError, toToolResult, toolSetInternals, validateWithSchema, } from "./tools.js";
20
+ function toolsJson(status, body, headers = {}) {
21
+ return Response.json(body, {
22
+ status,
23
+ headers: { "cache-control": "no-store", ...headers },
24
+ });
25
+ }
26
+ function toolsError(status, code, message, extra = {}) {
27
+ return toolsJson(status, {
28
+ v: TOOLS_PROTOCOL_VERSION,
29
+ error: {
30
+ code,
31
+ message,
32
+ ...(extra.issues ? { issues: extra.issues } : {}),
33
+ },
34
+ }, extra.headers);
35
+ }
36
+ async function readJsonBody(request) {
37
+ try {
38
+ return await request.json();
39
+ }
40
+ catch {
41
+ return undefined;
42
+ }
43
+ }
44
+ function isPlainObject(value) {
45
+ return (typeof value === "object" &&
46
+ value !== null &&
47
+ !Array.isArray(value) &&
48
+ Object.getPrototypeOf(value) === Object.prototype);
49
+ }
50
+ let emptyCatalog;
51
+ /**
52
+ * The app's catalog, or the contract's "misconfigured" answer when it can't
53
+ * be built at all, so the deploy shows the developer what to fix. (A single
54
+ * tool that can't be described is left out of the catalog with a warning.)
55
+ */
56
+ async function catalogOrError(toolSet) {
57
+ try {
58
+ return await catalogFor(toolSet);
59
+ }
60
+ catch (error) {
61
+ console.error(error);
62
+ return toolsError(500, "misconfigured", error instanceof Error ? error.message : String(error));
63
+ }
64
+ }
65
+ function catalogFor(toolSet) {
66
+ if (toolSet)
67
+ return toolSetInternals(toolSet).catalog();
68
+ // An app without tools still answers the contract, with an empty catalog,
69
+ // so the gateway can tell "no tools" from "no contract".
70
+ emptyCatalog ??= catalogHash({ tools: [] }).then((hash) => ({
71
+ tools: [],
72
+ hash,
73
+ warnings: [],
74
+ }));
75
+ return emptyCatalog;
76
+ }
77
+ /**
78
+ * Answer a request to the reserved tools paths, or return null for any other
79
+ * path so the app's own handler runs.
80
+ */
81
+ export async function handleToolsRequest(request, env, ctx, options) {
82
+ const { pathname } = new URL(request.url);
83
+ if (pathname !== TOOLS_LIST_PATH && pathname !== TOOLS_CALL_PATH) {
84
+ return null;
85
+ }
86
+ if (request.method !== "POST") {
87
+ return toolsError(405, "method_not_allowed", "Use POST.", {
88
+ headers: { allow: "POST" },
89
+ });
90
+ }
91
+ let verify;
92
+ try {
93
+ verify = resolveVerifyOptions(options.identity);
94
+ }
95
+ catch (error) {
96
+ if (error instanceof ConfigurationError) {
97
+ console.error(error);
98
+ return toolsError(500, "misconfigured", error.message);
99
+ }
100
+ throw error;
101
+ }
102
+ return pathname === TOOLS_LIST_PATH
103
+ ? listTools(request, verify, options)
104
+ : callTool(request, env, ctx, verify, options);
105
+ }
106
+ async function listTools(request, verify, options) {
107
+ try {
108
+ await verifySystemJwt(request.headers.get(SYSTEM_HEADER) ?? "", {
109
+ ...verify,
110
+ purpose: "tools.list",
111
+ });
112
+ }
113
+ catch (error) {
114
+ if (error instanceof IdentityError) {
115
+ return toolsError(401, "unauthenticated", error.message);
116
+ }
117
+ throw error;
118
+ }
119
+ const body = await readJsonBody(request);
120
+ if (!isPlainObject(body) || body.v !== TOOLS_PROTOCOL_VERSION) {
121
+ return toolsError(400, "invalid_request", `Expected {"v":${TOOLS_PROTOCOL_VERSION}}.`);
122
+ }
123
+ const catalog = await catalogOrError(options.toolSet);
124
+ if (catalog instanceof Response)
125
+ return catalog;
126
+ const response = {
127
+ v: TOOLS_PROTOCOL_VERSION,
128
+ sdk: SDK_VERSION,
129
+ hash: catalog.hash,
130
+ ...(catalog.instructions !== undefined
131
+ ? { instructions: catalog.instructions }
132
+ : {}),
133
+ tools: catalog.tools,
134
+ ...(catalog.warnings.length > 0 ? { warnings: catalog.warnings } : {}),
135
+ };
136
+ return toolsJson(200, response);
137
+ }
138
+ function parseCallRequest(body) {
139
+ if (!isPlainObject(body) || body.v !== TOOLS_PROTOCOL_VERSION)
140
+ return null;
141
+ const { name, callId } = body;
142
+ const args = body.arguments ?? {};
143
+ if (typeof name !== "string" || !name)
144
+ return null;
145
+ if (typeof callId !== "string" || !callId)
146
+ return null;
147
+ if (!isPlainObject(args))
148
+ return null;
149
+ return { v: TOOLS_PROTOCOL_VERSION, name, arguments: args, callId };
150
+ }
151
+ async function callTool(request, env, ctx, verify, options) {
152
+ let user, expiresAt;
153
+ try {
154
+ ({ user, expiresAt } = await verifyIdentity(request.headers.get(IDENTITY_HEADER) ?? "", verify));
155
+ // The same pin as the handler path: a call for another organization's
156
+ // install never runs a tool here.
157
+ assertOrganizationPin(user, options.identity.organizationId);
158
+ }
159
+ catch (error) {
160
+ if (error instanceof IdentityError) {
161
+ return toolsError(401, "unauthenticated", error.message);
162
+ }
163
+ throw error;
164
+ }
165
+ if (user.channel !== "mcp" || !user.toolCall) {
166
+ return toolsError(403, "forbidden", "Tool calls need an identity the gateway minted for this call.");
167
+ }
168
+ const call = parseCallRequest(await readJsonBody(request));
169
+ if (!call) {
170
+ return toolsError(400, "invalid_request", `Expected {"v":${TOOLS_PROTOCOL_VERSION},"name","arguments","callId"}.`);
171
+ }
172
+ if (call.name !== user.toolCall.name || call.callId !== user.toolCall.id) {
173
+ return toolsError(403, "forbidden", "The identity was minted for a different tool call.");
174
+ }
175
+ const tool = options.toolSet
176
+ ? toolSetInternals(options.toolSet).tool(call.name)
177
+ : undefined;
178
+ if (!tool) {
179
+ return toolsError(404, "unknown_tool", `Unknown tool: ${call.name}`);
180
+ }
181
+ const scope = requiredScopeForRisk(tool.risk);
182
+ if (!user.scopes.includes(scope)) {
183
+ return toolsError(403, "forbidden", `Calling ${call.name} needs the ${scope} scope.`);
184
+ }
185
+ const input = await validateWithSchema(tool.input, call.arguments);
186
+ if (!input.ok) {
187
+ return toolsError(400, "invalid_arguments", `Invalid arguments for ${call.name}: ${input.issues
188
+ .map((issue) => issue.path?.length
189
+ ? `${issue.path.join(".")}: ${issue.message}`
190
+ : issue.message)
191
+ .join("; ")}`, { issues: input.issues });
192
+ }
193
+ // The hash only lets the gateway notice a newer catalog: a catalog that
194
+ // can't be built is logged here, and doesn't stop this tool from running.
195
+ const hash = await catalogFor(options.toolSet).then((catalog) => catalog.hash, (error) => {
196
+ console.error(error);
197
+ return "";
198
+ });
199
+ let result;
200
+ try {
201
+ // The call runs in the request context like a page would: currentUser()
202
+ // is the caller, and its Gateway calls are attributed to them.
203
+ const value = await serveRequest({ request, user, expiresAt }, () => tool.handler(input.value, {
204
+ user,
205
+ env: env,
206
+ ctx,
207
+ request,
208
+ callId: call.callId,
209
+ }));
210
+ result = await toToolResult(tool, value);
211
+ }
212
+ catch (error) {
213
+ if (error instanceof ToolError) {
214
+ result = {
215
+ content: [{ type: "text", text: error.message }],
216
+ isError: true,
217
+ };
218
+ }
219
+ else {
220
+ console.error(`[everyapp] tool "${call.name}" failed (ref ${call.callId}):`, error);
221
+ result = {
222
+ content: [
223
+ {
224
+ type: "text",
225
+ text: `Internal error in ${options.appId} (ref ${call.callId})`,
226
+ },
227
+ ],
228
+ isError: true,
229
+ };
230
+ }
231
+ }
232
+ return toolsJson(200, {
233
+ v: TOOLS_PROTOCOL_VERSION,
234
+ catalogHash: hash,
235
+ result,
236
+ });
237
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Puts the person the Gateway signed in on `context.user` (an
3
+ * `EveryAppUser`), verified from the identity the Gateway sent; throws the
4
+ * 401 Response when there is none (a public route, anonymous).
5
+ */
6
+ export declare const ensureUserMiddleware: import("@tanstack/react-start").FunctionMiddlewareAfterServer<{}, unknown, undefined, {
7
+ user: import("../internal/protocol.js").EveryAppUser;
8
+ }, undefined, undefined, undefined>;
9
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/tanstack-start/index.ts"],"names":[],"mappings":"AAmBA;;;;GAIG;AACH,eAAO,MAAM,oBAAoB;;mCAK/B,CAAC"}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * @every-app/sdk/tanstack-start — identity for a TanStack Start app's server
3
+ * functions:
4
+ *
5
+ * import { ensureUserMiddleware } from "@every-app/sdk/tanstack-start";
6
+ *
7
+ * export const getTodos = createServerFn()
8
+ * .middleware([ensureUserMiddleware])
9
+ * .handler(({ context: { user } }) => listTodos(user.id));
10
+ *
11
+ * The SDK lists `@tanstack/react-start` as a peer dependency, so the Start
12
+ * plugin compiles this module like the app's own: the `.server()` part never
13
+ * reaches the browser.
14
+ */
15
+ import { createMiddleware } from "@tanstack/react-start";
16
+ import { getRequest } from "@tanstack/react-start/server";
17
+ import { env } from "cloudflare:workers";
18
+ import { requireEveryAppUser } from "../server/everyApp.js";
19
+ /**
20
+ * Puts the person the Gateway signed in on `context.user` (an
21
+ * `EveryAppUser`), verified from the identity the Gateway sent; throws the
22
+ * 401 Response when there is none (a public route, anonymous).
23
+ */
24
+ export const ensureUserMiddleware = createMiddleware({
25
+ type: "function",
26
+ }).server(async ({ next }) => {
27
+ const user = await requireEveryAppUser(getRequest(), env);
28
+ return next({ context: { user } });
29
+ });
@@ -0,0 +1,93 @@
1
+ import { IDENTITY_HEADER, type IdentityChannel, type ToolCallResultV1, type ToolsCallResponseV1, type ToolsListResponseV1 } from "../internal/index.js";
2
+ import type { ToolSet } from "../server/tools.js";
3
+ export { IDENTITY_HEADER };
4
+ export type { ToolCallResultV1 as ToolCallResult, ToolsCallResponseV1 as ToolCallResponse, ToolsListResponseV1 as ToolsListResponse, };
5
+ /** The person a test signs in; each field left out takes {@link TEST_USER}'s. */
6
+ export interface TestUser {
7
+ id?: string;
8
+ email?: string;
9
+ orgId?: string;
10
+ /** `owner`, `admin` or `member`, as the Gateway signs it. */
11
+ orgRole?: string;
12
+ }
13
+ /** The person a test signs in by default: the owner of `org_test`. */
14
+ export declare const TEST_USER: Readonly<Required<TestUser>>;
15
+ /** The app id {@link testEnv} uses when none is given. */
16
+ export declare const TEST_APP_ID = "test-app";
17
+ /** What an identity is minted for, beside the person. */
18
+ export interface TestIdentityOptions {
19
+ user?: TestUser;
20
+ /** The app the identity is for (the token's audience). Default: {@link TEST_APP_ID}. */
21
+ appId?: string;
22
+ /** Default `["*"]`: a browser session, which holds every scope. */
23
+ scopes?: string[];
24
+ /** Default `"web"`. */
25
+ channel?: IdentityChannel;
26
+ }
27
+ /**
28
+ * The Worker env `everyApp()` needs to trust test identities: the app id,
29
+ * the dev issuer and key, `EVERYAPP_DEV`, and a `GATEWAY_URL`. `overrides`
30
+ * adds the app's own bindings (a fake `DB`, a secret) or replaces any of
31
+ * these.
32
+ */
33
+ export declare function testEnv<T extends object = Record<never, never>>(overrides?: {
34
+ appId?: string;
35
+ } & T): {
36
+ EVERYAPP_APP_ID: string;
37
+ EVERYAPP_DEV: string;
38
+ EVERYAPP_IDENTITY_ISSUER: string;
39
+ EVERYAPP_IDENTITY_PUBLIC_KEYS: string;
40
+ GATEWAY_URL: string;
41
+ } & Omit<T, "appId">;
42
+ /**
43
+ * An `ExecutionContext` for calling a Worker's `fetch` in a test:
44
+ * `waitUntil` keeps each promise in `waited`, so a test can await them.
45
+ */
46
+ export declare function testContext(): ExecutionContext & {
47
+ waited: Promise<unknown>[];
48
+ };
49
+ /** An identity token for `user`, as the Gateway sends in the identity header. */
50
+ export declare function mintTestIdentity(options?: TestIdentityOptions & {
51
+ toolCall?: {
52
+ id: string;
53
+ name: string;
54
+ };
55
+ }): Promise<string>;
56
+ /**
57
+ * A request as the Gateway forwards it: `user` signed in (the default
58
+ * person when left out), or nobody with `user: null`, as on a public route.
59
+ */
60
+ export declare function testRequest(input: string | URL, init?: RequestInit & Omit<TestIdentityOptions, "user"> & {
61
+ user?: TestUser | null;
62
+ }): Promise<Request>;
63
+ /** A Worker as `everyApp()` returns it. */
64
+ export interface TestWorker {
65
+ fetch(request: Request, env: never, ctx: ExecutionContext): Response | Promise<Response>;
66
+ }
67
+ export interface CallToolOptions {
68
+ user?: TestUser;
69
+ /**
70
+ * The caller's scopes. Default: `mcp:read`, `mcp:write` and
71
+ * `mcp:destructive`, so every tool may be called; pass fewer to test a
72
+ * refusal.
73
+ */
74
+ scopes?: string[];
75
+ /** Default: {@link testEnv}() for the worker's app. */
76
+ env?: object;
77
+ ctx?: ExecutionContext;
78
+ }
79
+ /**
80
+ * Call one tool as the Gateway does for an AI client: a `chan: "mcp"`
81
+ * identity minted for this call, through the app's tools endpoint, so the
82
+ * SDK checks the scope, validates input and output and maps errors as it
83
+ * does deployed. `app` is the Worker `everyApp()` returned, or the
84
+ * `defineTools()` set alone. The answer is `{ result }`, or `{ error }`
85
+ * when the SDK refused the call.
86
+ */
87
+ export declare function callTool(app: TestWorker | ToolSet, name: string, args?: Record<string, unknown>, options?: CallToolOptions): Promise<ToolsCallResponseV1>;
88
+ /** The tool catalog, read as the Gateway reads it when the app deploys. */
89
+ export declare function listTools(app: TestWorker | ToolSet, options?: {
90
+ env?: object;
91
+ ctx?: ExecutionContext;
92
+ }): Promise<ToolsListResponseV1>;
93
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/testing/index.ts"],"names":[],"mappings":"AAuBA,OAAO,EAIL,eAAe,EASf,KAAK,eAAe,EACpB,KAAK,gBAAgB,EACrB,KAAK,mBAAmB,EACxB,KAAK,mBAAmB,EACzB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAElD,OAAO,EAAE,eAAe,EAAE,CAAC;AAC3B,YAAY,EACV,gBAAgB,IAAI,cAAc,EAClC,mBAAmB,IAAI,gBAAgB,EACvC,mBAAmB,IAAI,iBAAiB,GACzC,CAAC;AAEF,iFAAiF;AACjF,MAAM,WAAW,QAAQ;IACvB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,6DAA6D;IAC7D,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,sEAAsE;AACtE,eAAO,MAAM,SAAS,EAAE,QAAQ,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAKjD,CAAC;AAEH,0DAA0D;AAC1D,eAAO,MAAM,WAAW,aAAa,CAAC;AAEtC,yDAAyD;AACzD,MAAM,WAAW,mBAAmB;IAClC,IAAI,CAAC,EAAE,QAAQ,CAAC;IAChB,wFAAwF;IACxF,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,mEAAmE;IACnE,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,uBAAuB;IACvB,OAAO,CAAC,EAAE,eAAe,CAAC;CAC3B;AAED;;;;;GAKG;AACH,wBAAgB,OAAO,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,EAC7D,SAAS,GAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,CAAgC,GAC/D;IACD,eAAe,EAAE,MAAM,CAAC;IACxB,YAAY,EAAE,MAAM,CAAC;IACrB,wBAAwB,EAAE,MAAM,CAAC;IACjC,6BAA6B,EAAE,MAAM,CAAC;IACtC,WAAW,EAAE,MAAM,CAAC;CACrB,GAAG,IAAI,CAAC,CAAC,EAAE,OAAO,CAAC,CAUnB;AAED;;;GAGG;AACH,wBAAgB,WAAW,IAAI,gBAAgB,GAAG;IAChD,MAAM,EAAE,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;CAC5B,CAQA;AAED,iFAAiF;AACjF,wBAAsB,gBAAgB,CACpC,OAAO,GAAE,mBAAmB,GAAG;IAC7B,QAAQ,CAAC,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;CACpC,GACL,OAAO,CAAC,MAAM,CAAC,CAgBjB;AAED;;;GAGG;AACH,wBAAsB,WAAW,CAC/B,KAAK,EAAE,MAAM,GAAG,GAAG,EACnB,IAAI,GAAE,WAAW,GACf,IAAI,CAAC,mBAAmB,EAAE,MAAM,CAAC,GAAG;IAAE,IAAI,CAAC,EAAE,QAAQ,GAAG,IAAI,CAAA;CAAO,GACpE,OAAO,CAAC,OAAO,CAAC,CAUlB;AAED,2CAA2C;AAC3C,MAAM,WAAW,UAAU;IACzB,KAAK,CACH,OAAO,EAAE,OAAO,EAChB,GAAG,EAAE,KAAK,EACV,GAAG,EAAE,gBAAgB,GACpB,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;CACjC;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,CAAC,EAAE,QAAQ,CAAC;IAChB;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,uDAAuD;IACvD,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,gBAAgB,CAAC;CACxB;AAED;;;;;;;GAOG;AACH,wBAAsB,QAAQ,CAC5B,GAAG,EAAE,UAAU,GAAG,OAAO,EACzB,IAAI,EAAE,MAAM,EACZ,IAAI,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,EAClC,OAAO,GAAE,eAAoB,GAC5B,OAAO,CAAC,mBAAmB,CAAC,CAwB9B;AAED,2EAA2E;AAC3E,wBAAsB,SAAS,CAC7B,GAAG,EAAE,UAAU,GAAG,OAAO,EACzB,OAAO,GAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,gBAAgB,CAAA;CAAO,GACrD,OAAO,CAAC,mBAAmB,CAAC,CAuB9B"}
@@ -0,0 +1,156 @@
1
+ /**
2
+ * @every-app/sdk/testing — an app's tests, without a Gateway: a signed-in
3
+ * person for a request, and the tools called the way the Gateway calls them.
4
+ *
5
+ * import { callTool, testContext, testEnv, testRequest } from "@every-app/sdk/testing";
6
+ *
7
+ * const worker = everyApp(handler, { tools });
8
+ * const response = await worker.fetch(
9
+ * await testRequest("https://todo.test/api/todos", { user: { id: "ana" } }),
10
+ * testEnv(),
11
+ * testContext(),
12
+ * );
13
+ * const result = await callTool(worker, "list_todos", {}, { user: { id: "ana" } });
14
+ *
15
+ * Identities are signed with the well-known dev keypair, which `everyApp()`
16
+ * trusts only with `EVERYAPP_DEV` set, as {@link testEnv} sets it: never in a
17
+ * deployed Worker. For tests only; a Worker never imports this module.
18
+ */
19
+ import { DEV_ISSUER, DEV_PRIVATE_KEY_PEM, DEV_PUBLIC_KEY_PEM, } from "../dev/keypair.js";
20
+ import { APP_ID_ENV, AUDIENCE_ENV, IDENTITY_DEV_KEY_ID, IDENTITY_HEADER, MCP_DESTRUCTIVE_SCOPE, MCP_READ_SCOPE, MCP_WRITE_SCOPE, SYSTEM_HEADER, TOOLS_CALL_PATH, TOOLS_LIST_PATH, mintIdentityJwt, mintSystemJwt, } from "../internal/index.js";
21
+ import { everyApp } from "../server/everyApp.js";
22
+ export { IDENTITY_HEADER };
23
+ /** The person a test signs in by default: the owner of `org_test`. */
24
+ export const TEST_USER = Object.freeze({
25
+ id: "user_test",
26
+ email: "test@example.com",
27
+ orgId: "org_test",
28
+ orgRole: "owner",
29
+ });
30
+ /** The app id {@link testEnv} uses when none is given. */
31
+ export const TEST_APP_ID = "test-app";
32
+ /**
33
+ * The Worker env `everyApp()` needs to trust test identities: the app id,
34
+ * the dev issuer and key, `EVERYAPP_DEV`, and a `GATEWAY_URL`. `overrides`
35
+ * adds the app's own bindings (a fake `DB`, a secret) or replaces any of
36
+ * these.
37
+ */
38
+ export function testEnv(overrides = {}) {
39
+ const { appId = TEST_APP_ID, ...rest } = overrides;
40
+ return {
41
+ EVERYAPP_APP_ID: appId,
42
+ EVERYAPP_DEV: "1",
43
+ EVERYAPP_IDENTITY_ISSUER: DEV_ISSUER,
44
+ EVERYAPP_IDENTITY_PUBLIC_KEYS: JSON.stringify([DEV_PUBLIC_KEY_PEM]),
45
+ GATEWAY_URL: "https://gateway.test",
46
+ ...rest,
47
+ };
48
+ }
49
+ /**
50
+ * An `ExecutionContext` for calling a Worker's `fetch` in a test:
51
+ * `waitUntil` keeps each promise in `waited`, so a test can await them.
52
+ */
53
+ export function testContext() {
54
+ const waited = [];
55
+ return {
56
+ waited,
57
+ waitUntil: (promise) => void waited.push(promise),
58
+ passThroughOnException: () => { },
59
+ props: {},
60
+ };
61
+ }
62
+ /** An identity token for `user`, as the Gateway sends in the identity header. */
63
+ export async function mintTestIdentity(options = {}) {
64
+ const user = { ...TEST_USER, ...options.user };
65
+ return mintIdentityJwt(DEV_PRIVATE_KEY_PEM, {
66
+ subject: {
67
+ sub: user.id,
68
+ email: user.email,
69
+ orgId: user.orgId,
70
+ orgRole: user.orgRole,
71
+ },
72
+ audience: options.appId ?? TEST_APP_ID,
73
+ issuer: DEV_ISSUER,
74
+ keyId: IDENTITY_DEV_KEY_ID,
75
+ channel: options.channel,
76
+ scopes: options.scopes,
77
+ toolCall: options.toolCall,
78
+ });
79
+ }
80
+ /**
81
+ * A request as the Gateway forwards it: `user` signed in (the default
82
+ * person when left out), or nobody with `user: null`, as on a public route.
83
+ */
84
+ export async function testRequest(input, init = {}) {
85
+ const { user, appId, scopes, channel, ...requestInit } = init;
86
+ const headers = new Headers(requestInit.headers);
87
+ if (user !== null) {
88
+ headers.set(IDENTITY_HEADER, await mintTestIdentity({ user, appId, scopes, channel }));
89
+ }
90
+ return new Request(input, { ...requestInit, headers });
91
+ }
92
+ /**
93
+ * Call one tool as the Gateway does for an AI client: a `chan: "mcp"`
94
+ * identity minted for this call, through the app's tools endpoint, so the
95
+ * SDK checks the scope, validates input and output and maps errors as it
96
+ * does deployed. `app` is the Worker `everyApp()` returned, or the
97
+ * `defineTools()` set alone. The answer is `{ result }`, or `{ error }`
98
+ * when the SDK refused the call.
99
+ */
100
+ export async function callTool(app, name, args = {}, options = {}) {
101
+ const env = options.env ?? testEnv();
102
+ const callId = crypto.randomUUID();
103
+ const identity = await mintTestIdentity({
104
+ user: options.user,
105
+ appId: audienceOf(env),
106
+ channel: "mcp",
107
+ scopes: options.scopes ?? [
108
+ MCP_READ_SCOPE,
109
+ MCP_WRITE_SCOPE,
110
+ MCP_DESTRUCTIVE_SCOPE,
111
+ ],
112
+ toolCall: { id: callId, name },
113
+ });
114
+ const response = await workerOf(app).fetch(new Request(`https://app.test${TOOLS_CALL_PATH}`, {
115
+ method: "POST",
116
+ headers: { [IDENTITY_HEADER]: identity },
117
+ body: JSON.stringify({ v: 1, name, arguments: args, callId }),
118
+ }), env, options.ctx ?? testContext());
119
+ return (await response.json());
120
+ }
121
+ /** The tool catalog, read as the Gateway reads it when the app deploys. */
122
+ export async function listTools(app, options = {}) {
123
+ const env = options.env ?? testEnv();
124
+ const token = await mintSystemJwt(DEV_PRIVATE_KEY_PEM, {
125
+ audience: audienceOf(env),
126
+ issuer: DEV_ISSUER,
127
+ purpose: "tools.list",
128
+ keyId: IDENTITY_DEV_KEY_ID,
129
+ });
130
+ const response = await workerOf(app).fetch(new Request(`https://app.test${TOOLS_LIST_PATH}`, {
131
+ method: "POST",
132
+ headers: { [SYSTEM_HEADER]: token },
133
+ body: JSON.stringify({ v: 1 }),
134
+ }), env, options.ctx ?? testContext());
135
+ if (!response.ok) {
136
+ throw new Error(`the tools list answered ${response.status}: ${await response.text()}`);
137
+ }
138
+ return (await response.json());
139
+ }
140
+ /** Who the app's identities are minted for: the deployed audience, else the app id. */
141
+ function audienceOf(env) {
142
+ const values = env;
143
+ for (const key of [AUDIENCE_ENV, APP_ID_ENV]) {
144
+ const value = values[key];
145
+ if (typeof value === "string" && value)
146
+ return value;
147
+ }
148
+ return TEST_APP_ID;
149
+ }
150
+ function workerOf(app) {
151
+ if ("fetch" in app)
152
+ return app;
153
+ return everyApp(() => new Response("Not found", { status: 404 }), {
154
+ tools: app,
155
+ });
156
+ }
package/package.json CHANGED
@@ -1,9 +1,13 @@
1
1
  {
2
2
  "name": "@every-app/sdk",
3
- "version": "0.2.0",
3
+ "version": "0.3.0-rc.10",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "exports": {
7
+ ".": {
8
+ "types": "./dist/index.d.ts",
9
+ "default": "./dist/index.js"
10
+ },
7
11
  "./server": {
8
12
  "types": "./dist/server/index.d.ts",
9
13
  "default": "./dist/server/index.js"
@@ -11,34 +15,64 @@
11
15
  "./internal": {
12
16
  "types": "./dist/internal/index.d.ts",
13
17
  "default": "./dist/internal/index.js"
14
- }
18
+ },
19
+ "./cloudflare": {
20
+ "types": "./dist/cloudflare/index.d.ts",
21
+ "default": "./dist/cloudflare/index.js"
22
+ },
23
+ "./cloudflare/sentinel": {
24
+ "types": "./dist/cloudflare/sentinel.d.ts",
25
+ "default": "./dist/cloudflare/sentinel.js"
26
+ },
27
+ "./dev": {
28
+ "types": "./dist/dev/index.d.ts",
29
+ "default": "./dist/dev/index.js"
30
+ },
31
+ "./tanstack-start": {
32
+ "types": "./dist/tanstack-start/index.d.ts",
33
+ "default": "./dist/tanstack-start/index.js"
34
+ },
35
+ "./testing": {
36
+ "types": "./dist/testing/index.d.ts",
37
+ "default": "./dist/testing/index.js"
38
+ },
39
+ "./package.json": "./package.json"
15
40
  },
16
41
  "files": [
17
42
  "dist",
18
43
  "README.md"
19
44
  ],
45
+ "dependencies": {
46
+ "jose": "^6.2.5"
47
+ },
20
48
  "peerDependencies": {
21
- "jose": "^6.0.0"
49
+ "@tanstack/react-start": "^1.168.61"
22
50
  },
23
51
  "devDependencies": {
24
52
  "@cloudflare/workers-types": "^4.20251014.0",
53
+ "@tanstack/react-start": "1.168.61",
25
54
  "@types/node": "^22.18.13",
26
- "jose": "^6.0.12",
27
55
  "prettier": "^3.6.2",
28
56
  "typescript": "^5.9.3",
29
- "vite": "^7.1.2",
57
+ "vite": "^7.3.7",
30
58
  "vite-tsconfig-paths": "^5.1.4",
31
- "vitest": "^3.2.4"
59
+ "vitest": "^3.2.6",
60
+ "zod": "^4.2.0"
61
+ },
62
+ "peerDependenciesMeta": {
63
+ "@tanstack/react-start": {
64
+ "optional": true
65
+ }
32
66
  },
33
67
  "scripts": {
34
68
  "prebuild": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\"",
35
69
  "build": "tsc -p tsconfig.build.json",
36
- "build:prepublish": "pnpm install --ignore-scripts && npm run types:check && npm run build",
37
70
  "types:check": "tsc --noEmit",
38
71
  "format:check": "prettier --check .",
39
72
  "format:write": "prettier . --write",
40
73
  "test": "vitest run --silent",
41
74
  "test:watch": "vitest",
42
- "check:esm-relative-imports": "node ./scripts/check-esm-relative-imports.mjs"
75
+ "check:esm-relative-imports": "node ./scripts/check-esm-relative-imports.mjs",
76
+ "catalog:update": "UPDATE_CATALOG=1 vitest run src/cloudflare/catalogSettings.test.ts --silent"
43
77
  }
44
78
  }