@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 +39 -9
- package/dist/index.cjs +10 -3
- package/dist/index.js +12 -3
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,14 +1,25 @@
|
|
|
1
1
|
# @exposify/next
|
|
2
2
|
|
|
3
|
-
Next.js integration for
|
|
4
|
-
|
|
5
|
-
|
|
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
|
|
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
|
-
(`[
|
|
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
|
|
354
|
-
(input known, input partly unknown, or flagged as
|
|
355
|
-
file), the forms that submit to it, and what the scan
|
|
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(`[
|
|
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(`[
|
|
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 = "[
|
|
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(`[
|
|
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(`[
|
|
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 = "[
|
|
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.
|
|
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.
|
|
36
|
-
"@exposify/core": "0.3.
|
|
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
|
},
|