@exposify/next 0.0.8 → 0.0.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.
package/README.md CHANGED
@@ -1,14 +1,25 @@
1
1
  # @exposify/next
2
2
 
3
- Next.js integration for [Exposify](../../README.md): your route handlers,
4
- pages and Server Actions become MCP tools that AI agents can call, at `/mcp` on
5
- your own app.
3
+ Next.js integration for
4
+ [Exposify](https://github.com/ayadagain/agentready#readme): your route
5
+ handlers, pages and Server Actions become MCP tools that AI agents can call,
6
+ at `/mcp` on your own app. Tool calls go over HTTP to your own app, so your
7
+ `proxy.ts`/middleware auth applies — there's no separate service and no
8
+ privilege escalation.
6
9
 
7
10
  ```sh
8
11
  npm install @exposify/next
9
12
  npx @exposify/next setup # pick the endpoints, get the route file to create
10
13
  ```
11
14
 
15
+ > **Which package?** Use **`@exposify/next`** for a Next.js app, including one
16
+ > whose users sign in with Supabase Auth (`@supabase/ssr`) — tool calls run
17
+ > through your Next server, so your own auth and checks apply. Use
18
+ > [**`@exposify/supabase`**](https://github.com/ayadagain/agentready/tree/main/packages/supabase)
19
+ > instead when you want an MCP endpoint served straight from a Supabase **edge
20
+ > function** over PostgREST, with RLS as the authorization layer and no Next
21
+ > app in front of it.
22
+
12
23
  Or by hand, in two files:
13
24
 
14
25
  ```ts
@@ -182,8 +193,8 @@ reads, and Exposify refreshes the session before supabase-js would. The
182
193
  route reads `NEXT_PUBLIC_SUPABASE_URL` and `NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY`
183
194
  (or `_ANON_KEY`); pass `aiReady(exposify, { supabase: { url, key } })` to
184
195
  name them, or `supabase: false` to turn it off. The dashboard shows what was
185
- found and has the switch for passwords. `inspect` says which way it went and
186
- why; `npx @exposify/next test-sign-in` signs in on your running app and checks
196
+ found and has the switch for passwords. `inspect` says which way it went
197
+ (`--why` for the reasoning); `npx @exposify/next test-sign-in` signs in on your running app and checks
187
198
  a protected page accepts the cookie. If your `proxy.ts` sends signed-out
188
199
  visitors to the login page (Supabase's starter does), it would send AI
189
200
  assistants there too, before they can sign in: `inspect` warns, and
@@ -197,13 +208,31 @@ alone keep their session in the browser, where the server never sees it.
197
208
  **No login found, and no sign-in method in the dashboard?** Then agents use
198
209
  your app signed out: `/mcp` doesn't ask them to sign in, and your own checks
199
210
  decide what they can do. The startup log says which it is
200
- (`[agentready] How agents sign in: ...`), and so does the dashboard's "How
211
+ (`[exposify] How agents sign in: ...`), and so does the dashboard's "How
201
212
  agents sign in" page. Adding a sign-in method there takes effect within a
202
213
  minute.
203
214
 
204
215
  Not supported yet: logins in more than one step (SMS codes, email links),
205
216
  and logins that need a CSRF token from your own form page.
206
217
 
218
+ ## Connect your AI assistant
219
+
220
+ With `EXPOSIFY_KEY` set and the broker running, anyone opening your MCP URL in a
221
+ browser gets a page on your domain, in your project's name and sign-in colors,
222
+ with steps for Claude, ChatGPT, Cursor and VS Code, and Claude Code under "For
223
+ developers". There's nothing to add to your code. `GET` requests that accept
224
+ `text/html` get the page; MCP clients never send that, so they're unaffected.
225
+
226
+ Turn it off or change its intro on the project's dashboard page, under "Connect
227
+ page"; the same card shows page opens over 7 days and live connections. Changes
228
+ show within about a minute. Free-tier projects also show "Powered by Exposify".
229
+ The page has no analytics or external scripts; your server tells the broker about
230
+ each open.
231
+
232
+ Your MCP URL answers as it did before when the page is off, when the broker has
233
+ never answered, and when the project has no running broker. That last case
234
+ includes an app with no sign-in method.
235
+
207
236
  ## Pages
208
237
 
209
238
  Every App Router page is a read tool too, so an app whose server components
@@ -350,9 +379,10 @@ instead of booting the app.
350
379
 
351
380
  Server Actions get their own rows:
352
381
 
353
- - `inspect` lists each action with whether it's a tool and why, its status
354
- (input known, input partly unknown, or flagged as sign-in/out or taking a
355
- file), the forms that submit to it, and what the scan couldn't tell. Where
382
+ - `inspect` lists each action with whether it's a tool; add `--why` for the
383
+ reason on each, its status (input known, input partly unknown, or flagged as
384
+ sign-in/out or taking a file), the forms that submit to it, and what the scan
385
+ couldn't tell. Where
356
386
  an input you wrote no longer matches the code (a field the action now
357
387
  reads, an option the form dropped), it says so, and `inspect --check` fails
358
388
  on it in CI. The `inspect --snapshot` file includes action tools, by their
package/dist/index.cjs CHANGED
@@ -129,7 +129,7 @@ async function runAction(fn, action, args) {
129
129
  const flow = nextControlFlow(err);
130
130
  if (flow && "redirect" in flow) return json(JSON.stringify({ ok: true, redirectedTo: flow.redirect }));
131
131
  if (flow) return json(JSON.stringify({ error: ERRORS[flow.status] ?? `HTTP ${flow.status}` }), flow.status);
132
- console.error(`[agentready] server action ${action.export} threw:`, err);
132
+ console.error(`[exposify] server action ${action.export} threw:`, err);
133
133
  return json(JSON.stringify({ error: "the action threw an error" }), 500);
134
134
  }
135
135
  }
@@ -304,7 +304,7 @@ async function readActionResponse(res, exportName) {
304
304
  const flow = nextControlFlow({ digest: err.digest });
305
305
  if (flow && "redirect" in flow) return json(JSON.stringify({ ok: true, redirectedTo: flow.redirect }));
306
306
  if (flow) return json(JSON.stringify({ error: ERRORS[flow.status] ?? `HTTP ${flow.status}` }), flow.status);
307
- console.error(`[agentready] server action ${exportName} threw: ${err.message}`);
307
+ console.error(`[exposify] server action ${exportName} threw: ${err.message}`);
308
308
  return json(JSON.stringify({ error: "the action threw an error" }), 500);
309
309
  }
310
310
  }
@@ -735,7 +735,7 @@ async function agentIp(request, options = {}) {
735
735
  var import_core2 = require("@exposify/core");
736
736
  var DISPATCH_HEADER = "x-exposify-dispatch";
737
737
  var PRM_SUFFIX = "/.well-known/oauth-protected-resource";
738
- var LOG_PREFIX = "[agentready]";
738
+ var LOG_PREFIX = "[exposify]";
739
739
  var ACTION_NOTE = "It returns the action's result, or { ok: true, redirectedTo } when the action redirects; a redirect to a sign-in page means the call wasn't authorised.";
740
740
  var LEFT_BLANK = "A field you leave out is as if left blank on the form.";
741
741
  var PAGE_NOTE = "Returns the page's main content as text, with links, and the data it was rendered with when the page passes any to its components. An error naming a sign-in page means signing in first.";
@@ -1320,6 +1320,13 @@ function aiReady(manifest, options = {}) {
1320
1320
  }
1321
1321
  const actionKey = actionUrl?.exec(pathname)?.[1];
1322
1322
  if (actionKey !== void 0) return serveAction(req, decodeURIComponent(actionKey));
1323
+ const hosted = (0, import_core.hostedResolverInfo)(cfg.auth);
1324
+ if (hosted && (0, import_core.wantsConnectPage)(req) && !pathname.endsWith(PRM_SUFFIX)) {
1325
+ const pub = new URL((0, import_core.publicRequestUrl)(req.url, req.headers));
1326
+ const derived = `${pub.origin}${pub.pathname}`;
1327
+ const page = await (0, import_core.serveConnectPage)(req, hosted, hosted.resource ?? brokerGate?.resourceFor(derived) ?? derived);
1328
+ if (page) return page;
1329
+ }
1323
1330
  if (req.headers.has(DISPATCH_HEADER)) {
1324
1331
  return Response.json({ error: "a tool call can't call the MCP endpoint itself" }, { status: 508 });
1325
1332
  }
package/dist/index.js CHANGED
@@ -19,6 +19,8 @@ import {
19
19
  fieldKind,
20
20
  handleLoginPage,
21
21
  hostedResolverInfo,
22
+ serveConnectPage,
23
+ wantsConnectPage,
22
24
  loginResultFromResponse,
23
25
  keepFailedCookies,
24
26
  readStep,
@@ -139,7 +141,7 @@ async function runAction(fn, action, args) {
139
141
  const flow = nextControlFlow(err);
140
142
  if (flow && "redirect" in flow) return json(JSON.stringify({ ok: true, redirectedTo: flow.redirect }));
141
143
  if (flow) return json(JSON.stringify({ error: ERRORS[flow.status] ?? `HTTP ${flow.status}` }), flow.status);
142
- console.error(`[agentready] server action ${action.export} threw:`, err);
144
+ console.error(`[exposify] server action ${action.export} threw:`, err);
143
145
  return json(JSON.stringify({ error: "the action threw an error" }), 500);
144
146
  }
145
147
  }
@@ -314,7 +316,7 @@ async function readActionResponse(res, exportName) {
314
316
  const flow = nextControlFlow({ digest: err.digest });
315
317
  if (flow && "redirect" in flow) return json(JSON.stringify({ ok: true, redirectedTo: flow.redirect }));
316
318
  if (flow) return json(JSON.stringify({ error: ERRORS[flow.status] ?? `HTTP ${flow.status}` }), flow.status);
317
- console.error(`[agentready] server action ${exportName} threw: ${err.message}`);
319
+ console.error(`[exposify] server action ${exportName} threw: ${err.message}`);
318
320
  return json(JSON.stringify({ error: "the action threw an error" }), 500);
319
321
  }
320
322
  }
@@ -749,7 +751,7 @@ import {
749
751
  } from "@exposify/core";
750
752
  var DISPATCH_HEADER = "x-exposify-dispatch";
751
753
  var PRM_SUFFIX = "/.well-known/oauth-protected-resource";
752
- var LOG_PREFIX = "[agentready]";
754
+ var LOG_PREFIX = "[exposify]";
753
755
  var ACTION_NOTE = "It returns the action's result, or { ok: true, redirectedTo } when the action redirects; a redirect to a sign-in page means the call wasn't authorised.";
754
756
  var LEFT_BLANK = "A field you leave out is as if left blank on the form.";
755
757
  var PAGE_NOTE = "Returns the page's main content as text, with links, and the data it was rendered with when the page passes any to its components. An error naming a sign-in page means signing in first.";
@@ -1334,6 +1336,13 @@ function aiReady(manifest, options = {}) {
1334
1336
  }
1335
1337
  const actionKey = actionUrl?.exec(pathname)?.[1];
1336
1338
  if (actionKey !== void 0) return serveAction(req, decodeURIComponent(actionKey));
1339
+ const hosted = hostedResolverInfo(cfg.auth);
1340
+ if (hosted && wantsConnectPage(req) && !pathname.endsWith(PRM_SUFFIX)) {
1341
+ const pub = new URL(publicRequestUrl(req.url, req.headers));
1342
+ const derived = `${pub.origin}${pub.pathname}`;
1343
+ const page = await serveConnectPage(req, hosted, hosted.resource ?? brokerGate?.resourceFor(derived) ?? derived);
1344
+ if (page) return page;
1345
+ }
1337
1346
  if (req.headers.has(DISPATCH_HEADER)) {
1338
1347
  return Response.json({ error: "a tool call can't call the MCP endpoint itself" }, { status: 508 });
1339
1348
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exposify/next",
3
- "version": "0.0.8",
3
+ "version": "0.0.10",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -32,8 +32,8 @@
32
32
  },
33
33
  "dependencies": {
34
34
  "@babel/parser": "^8.0.0",
35
- "@exposify/cli": "0.0.7",
36
- "@exposify/core": "0.3.17",
35
+ "@exposify/cli": "0.0.9",
36
+ "@exposify/core": "0.3.19",
37
37
  "tinyglobby": "^0.2.10",
38
38
  "zod": "^3.24.0"
39
39
  },