@sonordev/site-kit 4.2.4 → 4.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/agent-manifest.json +11 -1
- package/dist/AnalyticsProvider-WRAJE62B.js +31 -0
- package/dist/{AnalyticsProvider-LFSSF7MQ.js.map → AnalyticsProvider-WRAJE62B.js.map} +1 -1
- package/dist/AnalyticsProvider-YZAOPVAY.mjs +10 -0
- package/dist/{AnalyticsProvider-HVPZPHZI.mjs.map → AnalyticsProvider-YZAOPVAY.mjs.map} +1 -1
- package/dist/ChatWidget-NMZGPP7A.js +20 -0
- package/dist/{ChatWidget-RFVIYV5C.js.map → ChatWidget-NMZGPP7A.js.map} +1 -1
- package/dist/ChatWidget-ZI34F7JU.mjs +11 -0
- package/dist/{ChatWidget-7E4PLIYQ.mjs.map → ChatWidget-ZI34F7JU.mjs.map} +1 -1
- package/dist/EngageWidget-2U4EN7OP.js +18 -0
- package/dist/{EngageWidget-LPMSXVT6.js.map → EngageWidget-2U4EN7OP.js.map} +1 -1
- package/dist/EngageWidget-NI2BN6GO.mjs +9 -0
- package/dist/{EngageWidget-N7EQGSSL.mjs.map → EngageWidget-NI2BN6GO.mjs.map} +1 -1
- package/dist/{FileField-AS6TUTXA.mjs → FileField-I4UICUJF.mjs} +5 -5
- package/dist/{FileField-AS6TUTXA.mjs.map → FileField-I4UICUJF.mjs.map} +1 -1
- package/dist/{FileField-Y7NOLT27.js → FileField-UI6DAB3K.js} +6 -6
- package/dist/{FileField-Y7NOLT27.js.map → FileField-UI6DAB3K.js.map} +1 -1
- package/dist/{FormSpotlight-RMYOXT2T.js → FormSpotlight-LO4BU6FY.js} +15 -15
- package/dist/{FormSpotlight-RMYOXT2T.js.map → FormSpotlight-LO4BU6FY.js.map} +1 -1
- package/dist/{FormSpotlight-I6FEUYAA.mjs → FormSpotlight-RGFZMYPT.mjs} +3 -3
- package/dist/{FormSpotlight-I6FEUYAA.mjs.map → FormSpotlight-RGFZMYPT.mjs.map} +1 -1
- package/dist/{FormStage-44OWEWZM.js → FormStage-NT5ZAH5Y.js} +13 -13
- package/dist/{FormStage-44OWEWZM.js.map → FormStage-NT5ZAH5Y.js.map} +1 -1
- package/dist/{FormStage-G3MUJMTC.mjs → FormStage-RBXMNWM3.mjs} +3 -3
- package/dist/{FormStage-G3MUJMTC.mjs.map → FormStage-RBXMNWM3.mjs.map} +1 -1
- package/dist/ManagedForm-CJOFGQK6.mjs +14 -0
- package/dist/{ManagedForm-LVBNVUZE.mjs.map → ManagedForm-CJOFGQK6.mjs.map} +1 -1
- package/dist/ManagedForm-MKV5RM54.js +23 -0
- package/dist/{ManagedForm-YC6WLYVJ.js.map → ManagedForm-MKV5RM54.js.map} +1 -1
- package/dist/{SignalCore-DYBIG4JP.mjs → SignalCore-B2ZSLEXI.mjs} +5 -5
- package/dist/{SignalCore-DYBIG4JP.mjs.map → SignalCore-B2ZSLEXI.mjs.map} +1 -1
- package/dist/{SignalCore-AW5VDA4Y.js → SignalCore-IX4L2TYS.js} +11 -11
- package/dist/{SignalCore-AW5VDA4Y.js.map → SignalCore-IX4L2TYS.js.map} +1 -1
- package/dist/SitemapSync-2LLNO24P.mjs +8 -0
- package/dist/{SitemapSync-2JJOGL7D.mjs.map → SitemapSync-2LLNO24P.mjs.map} +1 -1
- package/dist/SitemapSync-B2CY2JYM.js +17 -0
- package/dist/{SitemapSync-S54TSZLM.js.map → SitemapSync-B2CY2JYM.js.map} +1 -1
- package/dist/analytics/index.js +9 -9
- package/dist/analytics/index.mjs +4 -4
- package/dist/{chunk-CLD3IE6D.js → chunk-2EA4DC2G.js} +5 -5
- package/dist/{chunk-CLD3IE6D.js.map → chunk-2EA4DC2G.js.map} +1 -1
- package/dist/chunk-2X5ZB5D7.mjs +6 -0
- package/dist/{chunk-EFMPOC6L.mjs.map → chunk-2X5ZB5D7.mjs.map} +1 -1
- package/dist/{chunk-KPSESCYA.js → chunk-3AAFSXFC.js} +15 -15
- package/dist/{chunk-KPSESCYA.js.map → chunk-3AAFSXFC.js.map} +1 -1
- package/dist/{chunk-C6CNCDIN.mjs → chunk-3DFYFDGC.mjs} +3 -3
- package/dist/{chunk-C6CNCDIN.mjs.map → chunk-3DFYFDGC.mjs.map} +1 -1
- package/dist/{chunk-ICJ7EU4Z.js → chunk-3KVG2PSJ.js} +7 -7
- package/dist/{chunk-ICJ7EU4Z.js.map → chunk-3KVG2PSJ.js.map} +1 -1
- package/dist/{chunk-XLD6K3BV.mjs → chunk-3ZD2XCLE.mjs} +4 -4
- package/dist/{chunk-XLD6K3BV.mjs.map → chunk-3ZD2XCLE.mjs.map} +1 -1
- package/dist/{chunk-XWBQLPTZ.mjs → chunk-5PBJ42NC.mjs} +4 -4
- package/dist/{chunk-XWBQLPTZ.mjs.map → chunk-5PBJ42NC.mjs.map} +1 -1
- package/dist/{chunk-2MLIU6OJ.mjs → chunk-5S735BSH.mjs} +4 -4
- package/dist/{chunk-2MLIU6OJ.mjs.map → chunk-5S735BSH.mjs.map} +1 -1
- package/dist/{chunk-6ZQFCZPW.mjs → chunk-6SN3ABID.mjs} +3 -3
- package/dist/{chunk-6ZQFCZPW.mjs.map → chunk-6SN3ABID.mjs.map} +1 -1
- package/dist/{chunk-C6HDI464.js → chunk-7X4LKAYJ.js} +14 -14
- package/dist/{chunk-C6HDI464.js.map → chunk-7X4LKAYJ.js.map} +1 -1
- package/dist/{chunk-UHVII7W7.mjs → chunk-ACL5FDJI.mjs} +3 -3
- package/dist/{chunk-UHVII7W7.mjs.map → chunk-ACL5FDJI.mjs.map} +1 -1
- package/dist/{chunk-YDOWCCE2.mjs → chunk-ANJBOE46.mjs} +4 -4
- package/dist/{chunk-YDOWCCE2.mjs.map → chunk-ANJBOE46.mjs.map} +1 -1
- package/dist/{chunk-U7L7FW3K.js → chunk-BGIT5M27.js} +4 -4
- package/dist/{chunk-U7L7FW3K.js.map → chunk-BGIT5M27.js.map} +1 -1
- package/dist/{chunk-E3F6OOVQ.mjs → chunk-DYDJ6NCM.mjs} +3 -3
- package/dist/{chunk-E3F6OOVQ.mjs.map → chunk-DYDJ6NCM.mjs.map} +1 -1
- package/dist/{chunk-RH5WIBIA.js → chunk-F3MOVBEE.js} +7 -7
- package/dist/{chunk-RH5WIBIA.js.map → chunk-F3MOVBEE.js.map} +1 -1
- package/dist/{chunk-NK6K4VHD.js → chunk-FIVPS4Z6.js} +4 -4
- package/dist/{chunk-NK6K4VHD.js.map → chunk-FIVPS4Z6.js.map} +1 -1
- package/dist/{chunk-CPJA5XTO.mjs → chunk-G5EETWKH.mjs} +4 -4
- package/dist/{chunk-CPJA5XTO.mjs.map → chunk-G5EETWKH.mjs.map} +1 -1
- package/dist/{chunk-BX6FFL2Y.js → chunk-HXHDOIV6.js} +5 -5
- package/dist/{chunk-BX6FFL2Y.js.map → chunk-HXHDOIV6.js.map} +1 -1
- package/dist/{chunk-ULV3DZ27.mjs → chunk-IDSTSLMM.mjs} +3 -3
- package/dist/{chunk-ULV3DZ27.mjs.map → chunk-IDSTSLMM.mjs.map} +1 -1
- package/dist/{chunk-BJW6YR6W.mjs → chunk-IT76CWZ2.mjs} +3 -3
- package/dist/{chunk-BJW6YR6W.mjs.map → chunk-IT76CWZ2.mjs.map} +1 -1
- package/dist/{chunk-OIY5O3SE.js → chunk-IW6S62NR.js} +8 -8
- package/dist/{chunk-OIY5O3SE.js.map → chunk-IW6S62NR.js.map} +1 -1
- package/dist/{chunk-L5GUV4M7.mjs → chunk-IWFQPGA2.mjs} +7 -7
- package/dist/{chunk-L5GUV4M7.mjs.map → chunk-IWFQPGA2.mjs.map} +1 -1
- package/dist/{chunk-YHGMM2XF.js → chunk-IWV72YOE.js} +14 -14
- package/dist/{chunk-YHGMM2XF.js.map → chunk-IWV72YOE.js.map} +1 -1
- package/dist/{chunk-ARFWE2EL.js → chunk-IZVV36RT.js} +14 -14
- package/dist/{chunk-ARFWE2EL.js.map → chunk-IZVV36RT.js.map} +1 -1
- package/dist/{chunk-WX6VKFHW.mjs → chunk-JBX6KV46.mjs} +4 -4
- package/dist/{chunk-WX6VKFHW.mjs.map → chunk-JBX6KV46.mjs.map} +1 -1
- package/dist/{chunk-V3HJLXAI.mjs → chunk-JJTCVH4Q.mjs} +3 -3
- package/dist/{chunk-V3HJLXAI.mjs.map → chunk-JJTCVH4Q.mjs.map} +1 -1
- package/dist/{chunk-LSNIDSLQ.js → chunk-K7QBRZLN.js} +18 -18
- package/dist/{chunk-LSNIDSLQ.js.map → chunk-K7QBRZLN.js.map} +1 -1
- package/dist/{chunk-UBSZ466T.mjs → chunk-L4FHGCQ2.mjs} +3 -3
- package/dist/{chunk-UBSZ466T.mjs.map → chunk-L4FHGCQ2.mjs.map} +1 -1
- package/dist/{chunk-5K2CX5LK.js → chunk-LTALFZX7.js} +6 -6
- package/dist/{chunk-5K2CX5LK.js.map → chunk-LTALFZX7.js.map} +1 -1
- package/dist/{chunk-33OSUHPZ.mjs → chunk-LVMPYAOQ.mjs} +4 -4
- package/dist/{chunk-33OSUHPZ.mjs.map → chunk-LVMPYAOQ.mjs.map} +1 -1
- package/dist/{chunk-2MFLOK2I.js → chunk-NGAKS4WG.js} +3 -3
- package/dist/{chunk-2MFLOK2I.js.map → chunk-NGAKS4WG.js.map} +1 -1
- package/dist/{chunk-QQCOIGLN.js → chunk-O4TMBV54.js} +8 -8
- package/dist/{chunk-QQCOIGLN.js.map → chunk-O4TMBV54.js.map} +1 -1
- package/dist/{chunk-FSSVIYQ7.mjs → chunk-OBRGUSGB.mjs} +3 -3
- package/dist/{chunk-FSSVIYQ7.mjs.map → chunk-OBRGUSGB.mjs.map} +1 -1
- package/dist/{chunk-A7O7IWL7.js → chunk-OOBEJZJ5.js} +3 -3
- package/dist/{chunk-A7O7IWL7.js.map → chunk-OOBEJZJ5.js.map} +1 -1
- package/dist/{chunk-YZ6UYIXD.js → chunk-PJZHIISA.js} +3 -3
- package/dist/{chunk-YZ6UYIXD.js.map → chunk-PJZHIISA.js.map} +1 -1
- package/dist/{chunk-XKBADVXN.mjs → chunk-QEHPIQYN.mjs} +3 -3
- package/dist/{chunk-XKBADVXN.mjs.map → chunk-QEHPIQYN.mjs.map} +1 -1
- package/dist/{chunk-2XLWCYB3.js → chunk-R2O5VDNJ.js} +12 -12
- package/dist/{chunk-2XLWCYB3.js.map → chunk-R2O5VDNJ.js.map} +1 -1
- package/dist/{chunk-UVRB4SQU.mjs → chunk-RW76CJBS.mjs} +3 -3
- package/dist/{chunk-UVRB4SQU.mjs.map → chunk-RW76CJBS.mjs.map} +1 -1
- package/dist/{chunk-KDWKSL7U.js → chunk-SABYEM57.js} +9 -9
- package/dist/{chunk-KDWKSL7U.js.map → chunk-SABYEM57.js.map} +1 -1
- package/dist/{chunk-JHOMOLZA.js → chunk-SM27DPML.js} +12 -12
- package/dist/{chunk-JHOMOLZA.js.map → chunk-SM27DPML.js.map} +1 -1
- package/dist/{chunk-NB4MK2Q5.js → chunk-SWIKKSKL.js} +3 -2
- package/dist/chunk-SWIKKSKL.js.map +1 -0
- package/dist/{chunk-BDDTZJKC.js → chunk-T6YDAJ5F.js} +4 -4
- package/dist/{chunk-BDDTZJKC.js.map → chunk-T6YDAJ5F.js.map} +1 -1
- package/dist/{chunk-ZJNIM4GQ.js → chunk-TBIWQQVF.js} +15 -15
- package/dist/{chunk-ZJNIM4GQ.js.map → chunk-TBIWQQVF.js.map} +1 -1
- package/dist/chunk-U3P4WF2A.js +8 -0
- package/dist/{chunk-4JVFWTER.js.map → chunk-U3P4WF2A.js.map} +1 -1
- package/dist/{chunk-LUXCT2NU.js → chunk-UDXWBLBH.js} +4 -4
- package/dist/{chunk-LUXCT2NU.js.map → chunk-UDXWBLBH.js.map} +1 -1
- package/dist/{chunk-HP7V2DI3.mjs → chunk-UFT7IKDN.mjs} +4 -4
- package/dist/{chunk-HP7V2DI3.mjs.map → chunk-UFT7IKDN.mjs.map} +1 -1
- package/dist/{chunk-GU5OTUQS.mjs → chunk-VS67QHNX.mjs} +3 -3
- package/dist/{chunk-GU5OTUQS.mjs.map → chunk-VS67QHNX.mjs.map} +1 -1
- package/dist/{chunk-SYR7YSB2.mjs → chunk-XPNOM353.mjs} +3 -3
- package/dist/{chunk-SYR7YSB2.mjs.map → chunk-XPNOM353.mjs.map} +1 -1
- package/dist/{chunk-SSRQHRZB.mjs → chunk-YSLGMCQH.mjs} +8 -8
- package/dist/{chunk-SSRQHRZB.mjs.map → chunk-YSLGMCQH.mjs.map} +1 -1
- package/dist/{chunk-NI7OY4FW.mjs → chunk-ZN4HBK5C.mjs} +3 -2
- package/dist/chunk-ZN4HBK5C.mjs.map +1 -0
- package/dist/cli/index.js +3 -3
- package/dist/cli/index.mjs +1 -1
- package/dist/client/index.js +5 -5
- package/dist/client/index.mjs +4 -4
- package/dist/commerce/index.js +56 -56
- package/dist/commerce/index.mjs +5 -5
- package/dist/engage/index.js +13 -13
- package/dist/engage/index.mjs +7 -7
- package/dist/fleet/index.js +8 -8
- package/dist/fleet/index.mjs +4 -4
- package/dist/forms/index.js +21 -21
- package/dist/forms/index.mjs +10 -10
- package/dist/forms/server.js +10 -10
- package/dist/forms/server.mjs +8 -8
- package/dist/forms/static.js +7 -7
- package/dist/forms/static.mjs +6 -6
- package/dist/images/index.js +11 -11
- package/dist/images/index.mjs +4 -4
- package/dist/index.js +63 -63
- package/dist/index.mjs +9 -9
- package/dist/layout/client.js +9 -9
- package/dist/layout/client.mjs +8 -8
- package/dist/layout/index.js +11 -11
- package/dist/layout/index.mjs +9 -9
- package/dist/maps/index.js +5 -5
- package/dist/maps/index.mjs +3 -3
- package/dist/mcp/WebMcpTools.d.ts +60 -0
- package/dist/mcp/WebMcpTools.d.ts.map +1 -0
- package/dist/mcp/client.d.ts +9 -0
- package/dist/mcp/client.d.ts.map +1 -0
- package/dist/mcp/client.js +132 -0
- package/dist/mcp/client.js.map +1 -0
- package/dist/mcp/client.mjs +129 -0
- package/dist/mcp/client.mjs.map +1 -0
- package/dist/mcp/declarative.d.ts +77 -0
- package/dist/mcp/declarative.d.ts.map +1 -0
- package/dist/mcp/handlers.d.ts +92 -0
- package/dist/mcp/handlers.d.ts.map +1 -0
- package/dist/mcp/index.d.ts +29 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/index.js +537 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/index.mjs +509 -0
- package/dist/mcp/index.mjs.map +1 -0
- package/dist/mcp/protocol.d.ts +140 -0
- package/dist/mcp/protocol.d.ts.map +1 -0
- package/dist/mcp/protocol.test.d.ts +2 -0
- package/dist/mcp/protocol.test.d.ts.map +1 -0
- package/dist/mcp/serverCard.d.ts +77 -0
- package/dist/mcp/serverCard.d.ts.map +1 -0
- package/dist/mcp/types.d.ts +154 -0
- package/dist/mcp/types.d.ts.map +1 -0
- package/dist/search/index.js +4 -4
- package/dist/search/index.mjs +3 -3
- package/dist/seo/client.js +5 -5
- package/dist/seo/client.mjs +4 -4
- package/dist/seo/index.js +5 -5
- package/dist/seo/index.mjs +4 -4
- package/dist/seo-pages/contract.js +6 -6
- package/dist/seo-pages/contract.mjs +2 -2
- package/dist/server/index.js +3 -3
- package/dist/server/index.mjs +2 -2
- package/dist/shared/build-entries.d.ts +4 -1
- package/dist/shared/build-entries.d.ts.map +1 -1
- package/dist/shared/version.d.ts +1 -1
- package/dist/signal/index.js +11 -11
- package/dist/signal/index.mjs +2 -2
- package/dist/sites/contract.js +9 -9
- package/dist/sites/contract.mjs +2 -2
- package/dist/sites/normalize.d.ts +2 -1
- package/dist/sites/normalize.d.ts.map +1 -1
- package/dist/sync/index.js +16 -16
- package/dist/sync/index.mjs +4 -4
- package/package.json +11 -1
- package/dist/AnalyticsProvider-HVPZPHZI.mjs +0 -10
- package/dist/AnalyticsProvider-LFSSF7MQ.js +0 -31
- package/dist/ChatWidget-7E4PLIYQ.mjs +0 -11
- package/dist/ChatWidget-RFVIYV5C.js +0 -20
- package/dist/EngageWidget-LPMSXVT6.js +0 -18
- package/dist/EngageWidget-N7EQGSSL.mjs +0 -9
- package/dist/ManagedForm-LVBNVUZE.mjs +0 -14
- package/dist/ManagedForm-YC6WLYVJ.js +0 -23
- package/dist/SitemapSync-2JJOGL7D.mjs +0 -8
- package/dist/SitemapSync-S54TSZLM.js +0 -17
- package/dist/chunk-4JVFWTER.js +0 -8
- package/dist/chunk-EFMPOC6L.mjs +0 -6
- package/dist/chunk-NB4MK2Q5.js.map +0 -1
- package/dist/chunk-NI7OY4FW.mjs.map +0 -1
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { JsonSchemaObject } from './types';
|
|
2
|
+
/** True when the visiting browser exposes WebMCP at all. */
|
|
3
|
+
export declare function isWebMcpAvailable(): boolean;
|
|
4
|
+
/** A tool as it crosses the server→client boundary: JSON, no functions. */
|
|
5
|
+
export interface WebMcpToolDescriptor {
|
|
6
|
+
name: string;
|
|
7
|
+
title?: string;
|
|
8
|
+
description: string;
|
|
9
|
+
inputSchema: JsonSchemaObject;
|
|
10
|
+
}
|
|
11
|
+
export interface WebMcpLocalTool extends WebMcpToolDescriptor {
|
|
12
|
+
/** Runs in the browser. Use for DOM-aware tools only. */
|
|
13
|
+
execute: (args: Record<string, unknown>) => Promise<unknown> | unknown;
|
|
14
|
+
}
|
|
15
|
+
export interface WebMcpToolsProps {
|
|
16
|
+
/**
|
|
17
|
+
* Proxied tools — executed server-side via the MCP endpoint.
|
|
18
|
+
*
|
|
19
|
+
* Usually omit this. Passing descriptors from a server component sends the
|
|
20
|
+
* whole tool catalog through the RSC payload of EVERY page, costing every
|
|
21
|
+
* human visitor several KB for a capability only an agent uses. Leave it
|
|
22
|
+
* empty and the component fetches `tools/list` instead — but only after
|
|
23
|
+
* confirming the browser actually exposes WebMCP, so the fetch never
|
|
24
|
+
* happens for ordinary visitors. Pass descriptors only to pin a subset.
|
|
25
|
+
*/
|
|
26
|
+
tools?: WebMcpToolDescriptor[];
|
|
27
|
+
/** Tools whose handler runs in the browser. */
|
|
28
|
+
localTools?: WebMcpLocalTool[];
|
|
29
|
+
/** MCP endpoint on this origin (default `/api/mcp`). */
|
|
30
|
+
endpoint?: string;
|
|
31
|
+
/**
|
|
32
|
+
* Wait for the page to go quiet before registering (default true).
|
|
33
|
+
*
|
|
34
|
+
* Registration is not user-visible and must never compete with hydration.
|
|
35
|
+
* An agent arriving at the page polls or listens for `toolchange`, so a
|
|
36
|
+
* couple of hundred milliseconds costs nothing; a blocked LCP costs a lot.
|
|
37
|
+
*/
|
|
38
|
+
defer?: boolean;
|
|
39
|
+
/**
|
|
40
|
+
* Fetch the catalog from the endpoint's `tools/list` when no `tools` are
|
|
41
|
+
* passed (default true). The fetch is gated on WebMCP being present.
|
|
42
|
+
*/
|
|
43
|
+
discover?: boolean;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Register this site's MCP tools with the visiting browser's agent.
|
|
47
|
+
*
|
|
48
|
+
* Renders nothing. Mount it as a CHILDLESS sibling in the layout — never as a
|
|
49
|
+
* wrapper around `{children}`, which would de-opt the whole route to client
|
|
50
|
+
* rendering (the cardinal rule in the root CLAUDE.md).
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* ```tsx
|
|
54
|
+
* // app/layout.tsx
|
|
55
|
+
* <SiteKitLayout analytics={false}>{children}</SiteKitLayout>
|
|
56
|
+
* <WebMcpTools tools={MCP_TOOL_DESCRIPTORS} />
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
59
|
+
export declare function WebMcpTools({ tools, localTools, endpoint, defer, discover, }: WebMcpToolsProps): null;
|
|
60
|
+
//# sourceMappingURL=WebMcpTools.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"WebMcpTools.d.ts","sourceRoot":"","sources":["../../src/mcp/WebMcpTools.tsx"],"names":[],"mappings":"AAiCA,OAAO,KAAK,EAAE,gBAAgB,EAAiB,MAAM,SAAS,CAAA;AA6B9D,4DAA4D;AAC5D,wBAAgB,iBAAiB,IAAI,OAAO,CAE3C;AAMD,2EAA2E;AAC3E,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,WAAW,EAAE,MAAM,CAAA;IACnB,WAAW,EAAE,gBAAgB,CAAA;CAC9B;AAED,MAAM,WAAW,eAAgB,SAAQ,oBAAoB;IAC3D,yDAAyD;IACzD,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAA;CACvE;AAED,MAAM,WAAW,gBAAgB;IAC/B;;;;;;;;;OASG;IACH,KAAK,CAAC,EAAE,oBAAoB,EAAE,CAAA;IAC9B,+CAA+C;IAC/C,UAAU,CAAC,EAAE,eAAe,EAAE,CAAA;IAC9B,wDAAwD;IACxD,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;;;;;;OAMG;IACH,KAAK,CAAC,EAAE,OAAO,CAAA;IACf;;;OAGG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB;AAMD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,WAAW,CAAC,EAC1B,KAAU,EACV,UAAe,EACf,QAAqB,EACrB,KAAY,EACZ,QAAe,GAChB,EAAE,gBAAgB,QAqIlB"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @sonordev/site-kit/mcp/client — browser-side WebMCP
|
|
3
|
+
*
|
|
4
|
+
* Split from `@sonordev/site-kit/mcp` so a route handler importing the server
|
|
5
|
+
* pieces never drags a `'use client'` component into its graph.
|
|
6
|
+
*/
|
|
7
|
+
export { WebMcpTools, isWebMcpAvailable } from './WebMcpTools';
|
|
8
|
+
export type { WebMcpToolsProps, WebMcpToolDescriptor, WebMcpLocalTool, } from './WebMcpTools';
|
|
9
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/mcp/client.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAA;AAC9D,YAAY,EACV,gBAAgB,EAChB,oBAAoB,EACpB,eAAe,GAChB,MAAM,eAAe,CAAA"}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
var chunkHGNSJXWT_js = require('../chunk-HGNSJXWT.js');
|
|
5
|
+
require('../chunk-SNAQBZPT.js');
|
|
6
|
+
var react = require('react');
|
|
7
|
+
|
|
8
|
+
function getModelContext() {
|
|
9
|
+
if (typeof window === "undefined") return null;
|
|
10
|
+
const doc = document;
|
|
11
|
+
if (doc.modelContext) return doc.modelContext;
|
|
12
|
+
const nav = navigator;
|
|
13
|
+
return nav.modelContext ?? null;
|
|
14
|
+
}
|
|
15
|
+
function isWebMcpAvailable() {
|
|
16
|
+
return getModelContext() !== null;
|
|
17
|
+
}
|
|
18
|
+
function WebMcpTools({
|
|
19
|
+
tools = [],
|
|
20
|
+
localTools = [],
|
|
21
|
+
endpoint = "/api/mcp",
|
|
22
|
+
defer = true,
|
|
23
|
+
discover = true
|
|
24
|
+
}) {
|
|
25
|
+
const ready = chunkHGNSJXWT_js.useDeferredActivation(defer);
|
|
26
|
+
react.useEffect(() => {
|
|
27
|
+
if (!ready) return;
|
|
28
|
+
const modelContext = getModelContext();
|
|
29
|
+
if (!modelContext) return;
|
|
30
|
+
const controller = new AbortController();
|
|
31
|
+
const callRemote = async (name, args) => {
|
|
32
|
+
try {
|
|
33
|
+
const response = await fetch(endpoint, {
|
|
34
|
+
method: "POST",
|
|
35
|
+
headers: {
|
|
36
|
+
"Content-Type": "application/json",
|
|
37
|
+
Accept: "application/json, text/event-stream",
|
|
38
|
+
"Mcp-Method": "tools/call",
|
|
39
|
+
"Mcp-Name": name
|
|
40
|
+
},
|
|
41
|
+
body: JSON.stringify({
|
|
42
|
+
jsonrpc: "2.0",
|
|
43
|
+
id: `webmcp-${name}`,
|
|
44
|
+
method: "tools/call",
|
|
45
|
+
params: { name, arguments: args }
|
|
46
|
+
}),
|
|
47
|
+
signal: controller.signal
|
|
48
|
+
});
|
|
49
|
+
const payload = await response.json();
|
|
50
|
+
if (payload.error) {
|
|
51
|
+
return {
|
|
52
|
+
content: [{ type: "text", text: payload.error.message ?? "Tool call failed." }],
|
|
53
|
+
isError: true
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
return payload.result ?? { content: [{ type: "text", text: "(no result)" }] };
|
|
57
|
+
} catch (error) {
|
|
58
|
+
if (controller.signal.aborted) throw error;
|
|
59
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
60
|
+
return { content: [{ type: "text", text: `Tool call failed: ${detail}` }], isError: true };
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
const normalize = (value) => {
|
|
64
|
+
if (value && typeof value === "object" && Array.isArray(value.content)) {
|
|
65
|
+
return value;
|
|
66
|
+
}
|
|
67
|
+
return {
|
|
68
|
+
content: [
|
|
69
|
+
{ type: "text", text: typeof value === "string" ? value : JSON.stringify(value, null, 2) }
|
|
70
|
+
],
|
|
71
|
+
structuredContent: value
|
|
72
|
+
};
|
|
73
|
+
};
|
|
74
|
+
const proxied = (list) => list.map((tool) => ({
|
|
75
|
+
name: tool.name,
|
|
76
|
+
description: tool.description,
|
|
77
|
+
inputSchema: tool.inputSchema,
|
|
78
|
+
execute: (args) => callRemote(tool.name, args ?? {})
|
|
79
|
+
}));
|
|
80
|
+
const local = localTools.map((tool) => ({
|
|
81
|
+
name: tool.name,
|
|
82
|
+
description: tool.description,
|
|
83
|
+
inputSchema: tool.inputSchema,
|
|
84
|
+
execute: async (args) => normalize(await tool.execute(args ?? {}))
|
|
85
|
+
}));
|
|
86
|
+
const listRemoteTools = async () => {
|
|
87
|
+
const response = await fetch(endpoint, {
|
|
88
|
+
method: "POST",
|
|
89
|
+
headers: {
|
|
90
|
+
"Content-Type": "application/json",
|
|
91
|
+
Accept: "application/json, text/event-stream",
|
|
92
|
+
"Mcp-Method": "tools/list"
|
|
93
|
+
},
|
|
94
|
+
body: JSON.stringify({ jsonrpc: "2.0", id: "webmcp-list", method: "tools/list" }),
|
|
95
|
+
signal: controller.signal
|
|
96
|
+
});
|
|
97
|
+
const payload = await response.json();
|
|
98
|
+
return payload.result?.tools ?? [];
|
|
99
|
+
};
|
|
100
|
+
let cancelled = false;
|
|
101
|
+
void (async () => {
|
|
102
|
+
try {
|
|
103
|
+
const discovered = tools.length === 0 && discover ? await listRemoteTools() : tools;
|
|
104
|
+
if (cancelled) return;
|
|
105
|
+
const definitions = [...proxied(discovered), ...local];
|
|
106
|
+
if (definitions.length === 0) return;
|
|
107
|
+
if (typeof modelContext.registerTool === "function") {
|
|
108
|
+
for (const definition of definitions) {
|
|
109
|
+
if (cancelled) return;
|
|
110
|
+
await modelContext.registerTool(definition, { signal: controller.signal });
|
|
111
|
+
}
|
|
112
|
+
} else if (typeof modelContext.provideContext === "function") {
|
|
113
|
+
await modelContext.provideContext({ tools: definitions });
|
|
114
|
+
}
|
|
115
|
+
} catch (error) {
|
|
116
|
+
if (process.env.NODE_ENV !== "production") {
|
|
117
|
+
console.warn("[site-kit/mcp] WebMCP registration failed:", error);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
})();
|
|
121
|
+
return () => {
|
|
122
|
+
cancelled = true;
|
|
123
|
+
controller.abort();
|
|
124
|
+
};
|
|
125
|
+
}, [ready, tools, localTools, endpoint, discover]);
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
exports.WebMcpTools = WebMcpTools;
|
|
130
|
+
exports.isWebMcpAvailable = isWebMcpAvailable;
|
|
131
|
+
//# sourceMappingURL=client.js.map
|
|
132
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/mcp/WebMcpTools.tsx"],"names":["useDeferredActivation","useEffect"],"mappings":"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","file":"client.js","sourcesContent":["'use client'\n\n/**\n * @sonordev/site-kit/mcp — WebMCP in-page tool registration\n *\n * The browser half of WebMCP (W3C Web Machine Learning CG). When an agent is\n * driving the page — a browser-integrated assistant, an extension, a\n * headless-Chrome operator — it does not fetch our `.well-known` card and open\n * a network connection. It looks for tools registered on the *document*.\n *\n * `document.modelContext.registerTool()` is the current shape in the explainer;\n * earlier drafts and some flagged builds put the same object on `navigator`,\n * and an even earlier one took a whole tool array via `provideContext()`. All\n * three are probed, because the spec is a live draft and a site that only\n * knows one shape silently exposes nothing in the others.\n *\n * ── Why the handlers are proxied ──────────────────────────────────────────\n * Tools are registered here as thin wrappers that POST `tools/call` to this\n * site's own MCP endpoint. That is deliberate:\n *\n * - one implementation. The tool an agent calls in-page and the tool it\n * calls over HTTP run the SAME server-side handler, so they cannot drift.\n * - no secrets in the bundle. Handlers reach Sonor with `SONOR_API_KEY`.\n * Registering real handlers client-side would drag that key into a\n * `'use client'` graph, which is exactly how a raw `sonor_` key once ended\n * up in a public chunk of an upforge build (see build-entries.ts).\n *\n * Tools that genuinely need live DOM state (reading what's on screen, filling\n * a form the user is looking at) are passed as `localTools` and run in-page.\n */\n\nimport { useEffect } from 'react'\nimport { useDeferredActivation } from '../shared/defer'\nimport type { JsonSchemaObject, McpToolResult } from './types'\n\n// ============================================\n// The WebMCP surface, as far as we rely on it\n// ============================================\n\ninterface WebMcpToolDefinition {\n name: string\n description: string\n inputSchema: JsonSchemaObject\n execute: (args: Record<string, unknown>) => Promise<McpToolResult>\n}\n\ninterface ModelContextLike {\n registerTool?: (\n tool: WebMcpToolDefinition,\n options?: { signal?: AbortSignal },\n ) => Promise<unknown> | unknown\n provideContext?: (context: { tools: WebMcpToolDefinition[] }) => Promise<unknown> | unknown\n}\n\nfunction getModelContext(): ModelContextLike | null {\n if (typeof window === 'undefined') return null\n const doc = document as unknown as { modelContext?: ModelContextLike }\n if (doc.modelContext) return doc.modelContext\n const nav = navigator as unknown as { modelContext?: ModelContextLike }\n return nav.modelContext ?? null\n}\n\n/** True when the visiting browser exposes WebMCP at all. */\nexport function isWebMcpAvailable(): boolean {\n return getModelContext() !== null\n}\n\n// ============================================\n// Props\n// ============================================\n\n/** A tool as it crosses the server→client boundary: JSON, no functions. */\nexport interface WebMcpToolDescriptor {\n name: string\n title?: string\n description: string\n inputSchema: JsonSchemaObject\n}\n\nexport interface WebMcpLocalTool extends WebMcpToolDescriptor {\n /** Runs in the browser. Use for DOM-aware tools only. */\n execute: (args: Record<string, unknown>) => Promise<unknown> | unknown\n}\n\nexport interface WebMcpToolsProps {\n /**\n * Proxied tools — executed server-side via the MCP endpoint.\n *\n * Usually omit this. Passing descriptors from a server component sends the\n * whole tool catalog through the RSC payload of EVERY page, costing every\n * human visitor several KB for a capability only an agent uses. Leave it\n * empty and the component fetches `tools/list` instead — but only after\n * confirming the browser actually exposes WebMCP, so the fetch never\n * happens for ordinary visitors. Pass descriptors only to pin a subset.\n */\n tools?: WebMcpToolDescriptor[]\n /** Tools whose handler runs in the browser. */\n localTools?: WebMcpLocalTool[]\n /** MCP endpoint on this origin (default `/api/mcp`). */\n endpoint?: string\n /**\n * Wait for the page to go quiet before registering (default true).\n *\n * Registration is not user-visible and must never compete with hydration.\n * An agent arriving at the page polls or listens for `toolchange`, so a\n * couple of hundred milliseconds costs nothing; a blocked LCP costs a lot.\n */\n defer?: boolean\n /**\n * Fetch the catalog from the endpoint's `tools/list` when no `tools` are\n * passed (default true). The fetch is gated on WebMCP being present.\n */\n discover?: boolean\n}\n\n// ============================================\n// Component\n// ============================================\n\n/**\n * Register this site's MCP tools with the visiting browser's agent.\n *\n * Renders nothing. Mount it as a CHILDLESS sibling in the layout — never as a\n * wrapper around `{children}`, which would de-opt the whole route to client\n * rendering (the cardinal rule in the root CLAUDE.md).\n *\n * @example\n * ```tsx\n * // app/layout.tsx\n * <SiteKitLayout analytics={false}>{children}</SiteKitLayout>\n * <WebMcpTools tools={MCP_TOOL_DESCRIPTORS} />\n * ```\n */\nexport function WebMcpTools({\n tools = [],\n localTools = [],\n endpoint = '/api/mcp',\n defer = true,\n discover = true,\n}: WebMcpToolsProps) {\n const ready = useDeferredActivation(defer)\n\n useEffect(() => {\n if (!ready) return\n // Gate EVERYTHING on WebMCP being present. A browser without it does no\n // work and issues no request — which is every human visitor today.\n const modelContext = getModelContext()\n if (!modelContext) return\n\n const controller = new AbortController()\n\n const callRemote = async (\n name: string,\n args: Record<string, unknown>,\n ): Promise<McpToolResult> => {\n try {\n const response = await fetch(endpoint, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Accept: 'application/json, text/event-stream',\n 'Mcp-Method': 'tools/call',\n 'Mcp-Name': name,\n },\n body: JSON.stringify({\n jsonrpc: '2.0',\n id: `webmcp-${name}`,\n method: 'tools/call',\n params: { name, arguments: args },\n }),\n signal: controller.signal,\n })\n const payload = (await response.json()) as {\n result?: McpToolResult\n error?: { message?: string }\n }\n if (payload.error) {\n return {\n content: [{ type: 'text', text: payload.error.message ?? 'Tool call failed.' }],\n isError: true,\n }\n }\n return payload.result ?? { content: [{ type: 'text', text: '(no result)' }] }\n } catch (error) {\n if (controller.signal.aborted) throw error\n const detail = error instanceof Error ? error.message : String(error)\n return { content: [{ type: 'text', text: `Tool call failed: ${detail}` }], isError: true }\n }\n }\n\n const normalize = (value: unknown): McpToolResult => {\n if (value && typeof value === 'object' && Array.isArray((value as McpToolResult).content)) {\n return value as McpToolResult\n }\n return {\n content: [\n { type: 'text', text: typeof value === 'string' ? value : JSON.stringify(value, null, 2) },\n ],\n structuredContent: value,\n }\n }\n\n const proxied = (list: WebMcpToolDescriptor[]): WebMcpToolDefinition[] =>\n list.map((tool) => ({\n name: tool.name,\n description: tool.description,\n inputSchema: tool.inputSchema,\n execute: (args: Record<string, unknown>) => callRemote(tool.name, args ?? {}),\n }))\n\n const local = localTools.map((tool) => ({\n name: tool.name,\n description: tool.description,\n inputSchema: tool.inputSchema,\n execute: async (args: Record<string, unknown>) => normalize(await tool.execute(args ?? {})),\n }))\n\n /** Ask our own endpoint what it serves, so the two can't disagree. */\n const listRemoteTools = async (): Promise<WebMcpToolDescriptor[]> => {\n const response = await fetch(endpoint, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Accept: 'application/json, text/event-stream',\n 'Mcp-Method': 'tools/list',\n },\n body: JSON.stringify({ jsonrpc: '2.0', id: 'webmcp-list', method: 'tools/list' }),\n signal: controller.signal,\n })\n const payload = (await response.json()) as {\n result?: { tools?: WebMcpToolDescriptor[] }\n }\n return payload.result?.tools ?? []\n }\n\n let cancelled = false\n void (async () => {\n try {\n const discovered =\n tools.length === 0 && discover ? await listRemoteTools() : tools\n if (cancelled) return\n\n const definitions = [...proxied(discovered), ...local]\n if (definitions.length === 0) return\n\n if (typeof modelContext.registerTool === 'function') {\n for (const definition of definitions) {\n if (cancelled) return\n await modelContext.registerTool(definition, { signal: controller.signal })\n }\n } else if (typeof modelContext.provideContext === 'function') {\n await modelContext.provideContext({ tools: definitions })\n }\n } catch (error) {\n // A draft API that rejects is not a site error. Stay silent in\n // production so a spec change never shows up as console noise on a\n // marketing page.\n if (process.env.NODE_ENV !== 'production') {\n console.warn('[site-kit/mcp] WebMCP registration failed:', error)\n }\n }\n })()\n\n return () => {\n cancelled = true\n // Aborting the signal is how WebMCP unregisters — important on client\n // navigation, or the previous page's tools linger on the document.\n controller.abort()\n }\n }, [ready, tools, localTools, endpoint, discover])\n\n return null\n}\n"]}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { useDeferredActivation } from '../chunk-ZVBLXXNJ.mjs';
|
|
3
|
+
import '../chunk-BEXTDHO4.mjs';
|
|
4
|
+
import { useEffect } from 'react';
|
|
5
|
+
|
|
6
|
+
function getModelContext() {
|
|
7
|
+
if (typeof window === "undefined") return null;
|
|
8
|
+
const doc = document;
|
|
9
|
+
if (doc.modelContext) return doc.modelContext;
|
|
10
|
+
const nav = navigator;
|
|
11
|
+
return nav.modelContext ?? null;
|
|
12
|
+
}
|
|
13
|
+
function isWebMcpAvailable() {
|
|
14
|
+
return getModelContext() !== null;
|
|
15
|
+
}
|
|
16
|
+
function WebMcpTools({
|
|
17
|
+
tools = [],
|
|
18
|
+
localTools = [],
|
|
19
|
+
endpoint = "/api/mcp",
|
|
20
|
+
defer = true,
|
|
21
|
+
discover = true
|
|
22
|
+
}) {
|
|
23
|
+
const ready = useDeferredActivation(defer);
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
if (!ready) return;
|
|
26
|
+
const modelContext = getModelContext();
|
|
27
|
+
if (!modelContext) return;
|
|
28
|
+
const controller = new AbortController();
|
|
29
|
+
const callRemote = async (name, args) => {
|
|
30
|
+
try {
|
|
31
|
+
const response = await fetch(endpoint, {
|
|
32
|
+
method: "POST",
|
|
33
|
+
headers: {
|
|
34
|
+
"Content-Type": "application/json",
|
|
35
|
+
Accept: "application/json, text/event-stream",
|
|
36
|
+
"Mcp-Method": "tools/call",
|
|
37
|
+
"Mcp-Name": name
|
|
38
|
+
},
|
|
39
|
+
body: JSON.stringify({
|
|
40
|
+
jsonrpc: "2.0",
|
|
41
|
+
id: `webmcp-${name}`,
|
|
42
|
+
method: "tools/call",
|
|
43
|
+
params: { name, arguments: args }
|
|
44
|
+
}),
|
|
45
|
+
signal: controller.signal
|
|
46
|
+
});
|
|
47
|
+
const payload = await response.json();
|
|
48
|
+
if (payload.error) {
|
|
49
|
+
return {
|
|
50
|
+
content: [{ type: "text", text: payload.error.message ?? "Tool call failed." }],
|
|
51
|
+
isError: true
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
return payload.result ?? { content: [{ type: "text", text: "(no result)" }] };
|
|
55
|
+
} catch (error) {
|
|
56
|
+
if (controller.signal.aborted) throw error;
|
|
57
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
58
|
+
return { content: [{ type: "text", text: `Tool call failed: ${detail}` }], isError: true };
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
const normalize = (value) => {
|
|
62
|
+
if (value && typeof value === "object" && Array.isArray(value.content)) {
|
|
63
|
+
return value;
|
|
64
|
+
}
|
|
65
|
+
return {
|
|
66
|
+
content: [
|
|
67
|
+
{ type: "text", text: typeof value === "string" ? value : JSON.stringify(value, null, 2) }
|
|
68
|
+
],
|
|
69
|
+
structuredContent: value
|
|
70
|
+
};
|
|
71
|
+
};
|
|
72
|
+
const proxied = (list) => list.map((tool) => ({
|
|
73
|
+
name: tool.name,
|
|
74
|
+
description: tool.description,
|
|
75
|
+
inputSchema: tool.inputSchema,
|
|
76
|
+
execute: (args) => callRemote(tool.name, args ?? {})
|
|
77
|
+
}));
|
|
78
|
+
const local = localTools.map((tool) => ({
|
|
79
|
+
name: tool.name,
|
|
80
|
+
description: tool.description,
|
|
81
|
+
inputSchema: tool.inputSchema,
|
|
82
|
+
execute: async (args) => normalize(await tool.execute(args ?? {}))
|
|
83
|
+
}));
|
|
84
|
+
const listRemoteTools = async () => {
|
|
85
|
+
const response = await fetch(endpoint, {
|
|
86
|
+
method: "POST",
|
|
87
|
+
headers: {
|
|
88
|
+
"Content-Type": "application/json",
|
|
89
|
+
Accept: "application/json, text/event-stream",
|
|
90
|
+
"Mcp-Method": "tools/list"
|
|
91
|
+
},
|
|
92
|
+
body: JSON.stringify({ jsonrpc: "2.0", id: "webmcp-list", method: "tools/list" }),
|
|
93
|
+
signal: controller.signal
|
|
94
|
+
});
|
|
95
|
+
const payload = await response.json();
|
|
96
|
+
return payload.result?.tools ?? [];
|
|
97
|
+
};
|
|
98
|
+
let cancelled = false;
|
|
99
|
+
void (async () => {
|
|
100
|
+
try {
|
|
101
|
+
const discovered = tools.length === 0 && discover ? await listRemoteTools() : tools;
|
|
102
|
+
if (cancelled) return;
|
|
103
|
+
const definitions = [...proxied(discovered), ...local];
|
|
104
|
+
if (definitions.length === 0) return;
|
|
105
|
+
if (typeof modelContext.registerTool === "function") {
|
|
106
|
+
for (const definition of definitions) {
|
|
107
|
+
if (cancelled) return;
|
|
108
|
+
await modelContext.registerTool(definition, { signal: controller.signal });
|
|
109
|
+
}
|
|
110
|
+
} else if (typeof modelContext.provideContext === "function") {
|
|
111
|
+
await modelContext.provideContext({ tools: definitions });
|
|
112
|
+
}
|
|
113
|
+
} catch (error) {
|
|
114
|
+
if (process.env.NODE_ENV !== "production") {
|
|
115
|
+
console.warn("[site-kit/mcp] WebMCP registration failed:", error);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
})();
|
|
119
|
+
return () => {
|
|
120
|
+
cancelled = true;
|
|
121
|
+
controller.abort();
|
|
122
|
+
};
|
|
123
|
+
}, [ready, tools, localTools, endpoint, discover]);
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export { WebMcpTools, isWebMcpAvailable };
|
|
128
|
+
//# sourceMappingURL=client.mjs.map
|
|
129
|
+
//# sourceMappingURL=client.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/mcp/WebMcpTools.tsx"],"names":[],"mappings":"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","file":"client.mjs","sourcesContent":["'use client'\n\n/**\n * @sonordev/site-kit/mcp — WebMCP in-page tool registration\n *\n * The browser half of WebMCP (W3C Web Machine Learning CG). When an agent is\n * driving the page — a browser-integrated assistant, an extension, a\n * headless-Chrome operator — it does not fetch our `.well-known` card and open\n * a network connection. It looks for tools registered on the *document*.\n *\n * `document.modelContext.registerTool()` is the current shape in the explainer;\n * earlier drafts and some flagged builds put the same object on `navigator`,\n * and an even earlier one took a whole tool array via `provideContext()`. All\n * three are probed, because the spec is a live draft and a site that only\n * knows one shape silently exposes nothing in the others.\n *\n * ── Why the handlers are proxied ──────────────────────────────────────────\n * Tools are registered here as thin wrappers that POST `tools/call` to this\n * site's own MCP endpoint. That is deliberate:\n *\n * - one implementation. The tool an agent calls in-page and the tool it\n * calls over HTTP run the SAME server-side handler, so they cannot drift.\n * - no secrets in the bundle. Handlers reach Sonor with `SONOR_API_KEY`.\n * Registering real handlers client-side would drag that key into a\n * `'use client'` graph, which is exactly how a raw `sonor_` key once ended\n * up in a public chunk of an upforge build (see build-entries.ts).\n *\n * Tools that genuinely need live DOM state (reading what's on screen, filling\n * a form the user is looking at) are passed as `localTools` and run in-page.\n */\n\nimport { useEffect } from 'react'\nimport { useDeferredActivation } from '../shared/defer'\nimport type { JsonSchemaObject, McpToolResult } from './types'\n\n// ============================================\n// The WebMCP surface, as far as we rely on it\n// ============================================\n\ninterface WebMcpToolDefinition {\n name: string\n description: string\n inputSchema: JsonSchemaObject\n execute: (args: Record<string, unknown>) => Promise<McpToolResult>\n}\n\ninterface ModelContextLike {\n registerTool?: (\n tool: WebMcpToolDefinition,\n options?: { signal?: AbortSignal },\n ) => Promise<unknown> | unknown\n provideContext?: (context: { tools: WebMcpToolDefinition[] }) => Promise<unknown> | unknown\n}\n\nfunction getModelContext(): ModelContextLike | null {\n if (typeof window === 'undefined') return null\n const doc = document as unknown as { modelContext?: ModelContextLike }\n if (doc.modelContext) return doc.modelContext\n const nav = navigator as unknown as { modelContext?: ModelContextLike }\n return nav.modelContext ?? null\n}\n\n/** True when the visiting browser exposes WebMCP at all. */\nexport function isWebMcpAvailable(): boolean {\n return getModelContext() !== null\n}\n\n// ============================================\n// Props\n// ============================================\n\n/** A tool as it crosses the server→client boundary: JSON, no functions. */\nexport interface WebMcpToolDescriptor {\n name: string\n title?: string\n description: string\n inputSchema: JsonSchemaObject\n}\n\nexport interface WebMcpLocalTool extends WebMcpToolDescriptor {\n /** Runs in the browser. Use for DOM-aware tools only. */\n execute: (args: Record<string, unknown>) => Promise<unknown> | unknown\n}\n\nexport interface WebMcpToolsProps {\n /**\n * Proxied tools — executed server-side via the MCP endpoint.\n *\n * Usually omit this. Passing descriptors from a server component sends the\n * whole tool catalog through the RSC payload of EVERY page, costing every\n * human visitor several KB for a capability only an agent uses. Leave it\n * empty and the component fetches `tools/list` instead — but only after\n * confirming the browser actually exposes WebMCP, so the fetch never\n * happens for ordinary visitors. Pass descriptors only to pin a subset.\n */\n tools?: WebMcpToolDescriptor[]\n /** Tools whose handler runs in the browser. */\n localTools?: WebMcpLocalTool[]\n /** MCP endpoint on this origin (default `/api/mcp`). */\n endpoint?: string\n /**\n * Wait for the page to go quiet before registering (default true).\n *\n * Registration is not user-visible and must never compete with hydration.\n * An agent arriving at the page polls or listens for `toolchange`, so a\n * couple of hundred milliseconds costs nothing; a blocked LCP costs a lot.\n */\n defer?: boolean\n /**\n * Fetch the catalog from the endpoint's `tools/list` when no `tools` are\n * passed (default true). The fetch is gated on WebMCP being present.\n */\n discover?: boolean\n}\n\n// ============================================\n// Component\n// ============================================\n\n/**\n * Register this site's MCP tools with the visiting browser's agent.\n *\n * Renders nothing. Mount it as a CHILDLESS sibling in the layout — never as a\n * wrapper around `{children}`, which would de-opt the whole route to client\n * rendering (the cardinal rule in the root CLAUDE.md).\n *\n * @example\n * ```tsx\n * // app/layout.tsx\n * <SiteKitLayout analytics={false}>{children}</SiteKitLayout>\n * <WebMcpTools tools={MCP_TOOL_DESCRIPTORS} />\n * ```\n */\nexport function WebMcpTools({\n tools = [],\n localTools = [],\n endpoint = '/api/mcp',\n defer = true,\n discover = true,\n}: WebMcpToolsProps) {\n const ready = useDeferredActivation(defer)\n\n useEffect(() => {\n if (!ready) return\n // Gate EVERYTHING on WebMCP being present. A browser without it does no\n // work and issues no request — which is every human visitor today.\n const modelContext = getModelContext()\n if (!modelContext) return\n\n const controller = new AbortController()\n\n const callRemote = async (\n name: string,\n args: Record<string, unknown>,\n ): Promise<McpToolResult> => {\n try {\n const response = await fetch(endpoint, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Accept: 'application/json, text/event-stream',\n 'Mcp-Method': 'tools/call',\n 'Mcp-Name': name,\n },\n body: JSON.stringify({\n jsonrpc: '2.0',\n id: `webmcp-${name}`,\n method: 'tools/call',\n params: { name, arguments: args },\n }),\n signal: controller.signal,\n })\n const payload = (await response.json()) as {\n result?: McpToolResult\n error?: { message?: string }\n }\n if (payload.error) {\n return {\n content: [{ type: 'text', text: payload.error.message ?? 'Tool call failed.' }],\n isError: true,\n }\n }\n return payload.result ?? { content: [{ type: 'text', text: '(no result)' }] }\n } catch (error) {\n if (controller.signal.aborted) throw error\n const detail = error instanceof Error ? error.message : String(error)\n return { content: [{ type: 'text', text: `Tool call failed: ${detail}` }], isError: true }\n }\n }\n\n const normalize = (value: unknown): McpToolResult => {\n if (value && typeof value === 'object' && Array.isArray((value as McpToolResult).content)) {\n return value as McpToolResult\n }\n return {\n content: [\n { type: 'text', text: typeof value === 'string' ? value : JSON.stringify(value, null, 2) },\n ],\n structuredContent: value,\n }\n }\n\n const proxied = (list: WebMcpToolDescriptor[]): WebMcpToolDefinition[] =>\n list.map((tool) => ({\n name: tool.name,\n description: tool.description,\n inputSchema: tool.inputSchema,\n execute: (args: Record<string, unknown>) => callRemote(tool.name, args ?? {}),\n }))\n\n const local = localTools.map((tool) => ({\n name: tool.name,\n description: tool.description,\n inputSchema: tool.inputSchema,\n execute: async (args: Record<string, unknown>) => normalize(await tool.execute(args ?? {})),\n }))\n\n /** Ask our own endpoint what it serves, so the two can't disagree. */\n const listRemoteTools = async (): Promise<WebMcpToolDescriptor[]> => {\n const response = await fetch(endpoint, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Accept: 'application/json, text/event-stream',\n 'Mcp-Method': 'tools/list',\n },\n body: JSON.stringify({ jsonrpc: '2.0', id: 'webmcp-list', method: 'tools/list' }),\n signal: controller.signal,\n })\n const payload = (await response.json()) as {\n result?: { tools?: WebMcpToolDescriptor[] }\n }\n return payload.result?.tools ?? []\n }\n\n let cancelled = false\n void (async () => {\n try {\n const discovered =\n tools.length === 0 && discover ? await listRemoteTools() : tools\n if (cancelled) return\n\n const definitions = [...proxied(discovered), ...local]\n if (definitions.length === 0) return\n\n if (typeof modelContext.registerTool === 'function') {\n for (const definition of definitions) {\n if (cancelled) return\n await modelContext.registerTool(definition, { signal: controller.signal })\n }\n } else if (typeof modelContext.provideContext === 'function') {\n await modelContext.provideContext({ tools: definitions })\n }\n } catch (error) {\n // A draft API that rejects is not a site error. Stay silent in\n // production so a spec change never shows up as console noise on a\n // marketing page.\n if (process.env.NODE_ENV !== 'production') {\n console.warn('[site-kit/mcp] WebMCP registration failed:', error)\n }\n }\n })()\n\n return () => {\n cancelled = true\n // Aborting the signal is how WebMCP unregisters — important on client\n // navigation, or the previous page's tools linger on the document.\n controller.abort()\n }\n }, [ready, tools, localTools, endpoint, discover])\n\n return null\n}\n"]}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @sonordev/site-kit/mcp — declarative WebMCP (forms as tools)
|
|
3
|
+
*
|
|
4
|
+
* The half of WebMCP that needs no JavaScript. A `<form>` carrying `toolname`
|
|
5
|
+
* and `tooldescription` IS an agent tool: the browser synthesizes the input
|
|
6
|
+
* schema from the form controls, and the agent fills and submits the real
|
|
7
|
+
* form the human would have used.
|
|
8
|
+
*
|
|
9
|
+
* This matters more than the imperative API for one reason — there is exactly
|
|
10
|
+
* one code path. The agent submits the same form, hits the same validation,
|
|
11
|
+
* files the same CRM record. Nothing can drift, because there is nothing to
|
|
12
|
+
* drift from.
|
|
13
|
+
*
|
|
14
|
+
* Attributes (per the W3C declarative-api explainer):
|
|
15
|
+
* toolname on <form> — the tool's machine name
|
|
16
|
+
* tooldescription on <form> — what it does
|
|
17
|
+
* toolautosubmit on <form> — submit without waiting for the human
|
|
18
|
+
* toolparamdescription on a control — describes that field in the schema
|
|
19
|
+
*
|
|
20
|
+
* These are plain HTML attributes; React 19 passes unknown lowercase
|
|
21
|
+
* attributes straight through to the DOM, so the returned objects spread
|
|
22
|
+
* directly onto JSX.
|
|
23
|
+
*/
|
|
24
|
+
export interface DeclarativeToolFormOptions {
|
|
25
|
+
/** Machine name, `snake_case`. Match the remote tool it mirrors. */
|
|
26
|
+
name: string;
|
|
27
|
+
/** What submitting this form accomplishes, in one sentence. */
|
|
28
|
+
description: string;
|
|
29
|
+
/**
|
|
30
|
+
* Let an agent submit without a human confirming.
|
|
31
|
+
*
|
|
32
|
+
* Default false, and leave it false for anything that creates a record,
|
|
33
|
+
* sends a message, or spends money. A booking request or an audit request
|
|
34
|
+
* should be something the person watching agrees to — `toolautosubmit` on a
|
|
35
|
+
* lead form is how an agent files fifty of them by accident.
|
|
36
|
+
*/
|
|
37
|
+
autoSubmit?: boolean;
|
|
38
|
+
}
|
|
39
|
+
export interface DeclarativeToolFormAttributes {
|
|
40
|
+
toolname: string;
|
|
41
|
+
tooldescription: string;
|
|
42
|
+
toolautosubmit?: string;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Attributes that turn a `<form>` into an agent-callable tool.
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* ```tsx
|
|
49
|
+
* <form {...declarativeToolForm({
|
|
50
|
+
* name: 'request_site_audit',
|
|
51
|
+
* description: 'Request a free technical SEO and performance audit of a website.',
|
|
52
|
+
* })} action="/api/audit" method="post">
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
export declare function declarativeToolForm(options: DeclarativeToolFormOptions): DeclarativeToolFormAttributes;
|
|
56
|
+
export interface DeclarativeToolFieldAttributes {
|
|
57
|
+
toolparamdescription: string;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Describe one form control for the synthesized input schema.
|
|
61
|
+
*
|
|
62
|
+
* The control's `name` becomes the schema property and `required` marks it
|
|
63
|
+
* required, so this only supplies the prose — but the prose is what the model
|
|
64
|
+
* reads to decide what to put in the field.
|
|
65
|
+
*
|
|
66
|
+
* @example
|
|
67
|
+
* ```tsx
|
|
68
|
+
* <input
|
|
69
|
+
* type="url"
|
|
70
|
+
* name="url"
|
|
71
|
+
* required
|
|
72
|
+
* {...declarativeToolField('The full URL of the website to audit, including https://')}
|
|
73
|
+
* />
|
|
74
|
+
* ```
|
|
75
|
+
*/
|
|
76
|
+
export declare function declarativeToolField(description: string): DeclarativeToolFieldAttributes;
|
|
77
|
+
//# sourceMappingURL=declarative.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"declarative.d.ts","sourceRoot":"","sources":["../../src/mcp/declarative.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,MAAM,WAAW,0BAA0B;IACzC,oEAAoE;IACpE,IAAI,EAAE,MAAM,CAAA;IACZ,+DAA+D;IAC/D,WAAW,EAAE,MAAM,CAAA;IACnB;;;;;;;OAOG;IACH,UAAU,CAAC,EAAE,OAAO,CAAA;CACrB;AAED,MAAM,WAAW,6BAA6B;IAC5C,QAAQ,EAAE,MAAM,CAAA;IAChB,eAAe,EAAE,MAAM,CAAA;IACvB,cAAc,CAAC,EAAE,MAAM,CAAA;CACxB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,0BAA0B,GAClC,6BAA6B,CAS/B;AAED,MAAM,WAAW,8BAA8B;IAC7C,oBAAoB,EAAE,MAAM,CAAA;CAC7B;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,MAAM,GAAG,8BAA8B,CAExF"}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @sonordev/site-kit/mcp — Next.js route handlers (Streamable HTTP + card)
|
|
3
|
+
*
|
|
4
|
+
* Two things get mounted on a site:
|
|
5
|
+
*
|
|
6
|
+
* app/api/mcp/route.ts → createMcpHandler({ server })
|
|
7
|
+
* app/.well-known/mcp-server-card/... → createMcpServerCardHandler({ ... })
|
|
8
|
+
* app/.well-known/mcp.json/route.ts → the same handler (legacy path)
|
|
9
|
+
*
|
|
10
|
+
* The endpoint is stateless. Every request is self-describing, nothing is
|
|
11
|
+
* cached between calls, and no session is minted — which is exactly what the
|
|
12
|
+
* 2026-07-28 revision asks for and also the only thing that works on a
|
|
13
|
+
* serverless host where consecutive requests hit different instances.
|
|
14
|
+
*/
|
|
15
|
+
import { MCP_SUPPORTED_PROTOCOL_VERSIONS, type McpServerDefinition } from './protocol';
|
|
16
|
+
import { type BuildMcpServerCardOptions } from './serverCard';
|
|
17
|
+
/**
|
|
18
|
+
* Undo the spec's Base64 sentinel wrapper: `=?base64?{b64}?=`.
|
|
19
|
+
* Values that aren't wrapped come back untouched.
|
|
20
|
+
*/
|
|
21
|
+
export declare function decodeHeaderValue(value: string | null): string | null;
|
|
22
|
+
export interface McpHandlerOptions {
|
|
23
|
+
server: McpServerDefinition;
|
|
24
|
+
/** Absolute site origin, passed to every tool handler as context. */
|
|
25
|
+
baseUrl?: string;
|
|
26
|
+
/**
|
|
27
|
+
* Origins allowed to call the endpoint from a browser. Defaults to `*`.
|
|
28
|
+
*
|
|
29
|
+
* A public marketing MCP server is meant to be called by anyone's agent and
|
|
30
|
+
* serves only data that is already on the public site, so the wildcard is
|
|
31
|
+
* the correct setting, not a lapse. The DNS-rebinding attack the spec's
|
|
32
|
+
* Origin rule defends against is about *localhost* servers, where the
|
|
33
|
+
* browser can reach a private origin the attacker otherwise couldn't.
|
|
34
|
+
* Set this explicitly for any endpoint that gains auth or writes.
|
|
35
|
+
*/
|
|
36
|
+
allowedOrigins?: string[] | '*';
|
|
37
|
+
/** Reject modern requests missing the mirror headers. See DispatchOptions. */
|
|
38
|
+
strictHeaders?: boolean;
|
|
39
|
+
}
|
|
40
|
+
export interface McpRouteHandlers {
|
|
41
|
+
POST: (request: Request) => Promise<Response>;
|
|
42
|
+
GET: (request: Request) => Promise<Response>;
|
|
43
|
+
DELETE: (request: Request) => Promise<Response>;
|
|
44
|
+
OPTIONS: (request: Request) => Promise<Response>;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Create the Streamable HTTP MCP endpoint.
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* ```ts
|
|
51
|
+
* // app/api/mcp/route.ts
|
|
52
|
+
* import { createMcpHandler } from '@sonordev/site-kit/mcp'
|
|
53
|
+
* import { mcpServer } from '@/lib/mcp/server'
|
|
54
|
+
*
|
|
55
|
+
* export const { POST, GET, DELETE, OPTIONS } = createMcpHandler({ server: mcpServer })
|
|
56
|
+
* export const dynamic = 'force-dynamic'
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
59
|
+
export declare function createMcpHandler(options: McpHandlerOptions): McpRouteHandlers;
|
|
60
|
+
export interface McpServerCardHandlerOptions extends BuildMcpServerCardOptions {
|
|
61
|
+
/** Seconds for `Cache-Control: public, max-age=…` (default 3600). */
|
|
62
|
+
maxAge?: number;
|
|
63
|
+
}
|
|
64
|
+
export interface McpServerCardRouteHandlers {
|
|
65
|
+
GET: () => Promise<Response>;
|
|
66
|
+
OPTIONS: () => Promise<Response>;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Serve the SEP-2127 server card. Mount at BOTH well-known paths.
|
|
70
|
+
*
|
|
71
|
+
* The handler never reads the incoming Request, so the route stays statically
|
|
72
|
+
* prerenderable — the same constraint the llms.txt handlers document, and for
|
|
73
|
+
* the same reason: touching `request.headers` opts the route into dynamic
|
|
74
|
+
* rendering and it stops being served from the CDN.
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* ```ts
|
|
78
|
+
* // app/.well-known/mcp-server-card/route.ts
|
|
79
|
+
* import { createMcpServerCardHandler } from '@sonordev/site-kit/mcp'
|
|
80
|
+
* import { mcpServer, SITE_URL } from '@/lib/mcp/server'
|
|
81
|
+
*
|
|
82
|
+
* export const { GET, OPTIONS } = createMcpServerCardHandler({
|
|
83
|
+
* info: mcpServer.info,
|
|
84
|
+
* tools: mcpServer.tools,
|
|
85
|
+
* baseUrl: SITE_URL,
|
|
86
|
+
* })
|
|
87
|
+
* export const revalidate = 3600
|
|
88
|
+
* ```
|
|
89
|
+
*/
|
|
90
|
+
export declare function createMcpServerCardHandler(options: McpServerCardHandlerOptions): McpServerCardRouteHandlers;
|
|
91
|
+
export { MCP_SUPPORTED_PROTOCOL_VERSIONS };
|
|
92
|
+
//# sourceMappingURL=handlers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"handlers.d.ts","sourceRoot":"","sources":["../../src/mcp/handlers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAKL,+BAA+B,EAG/B,KAAK,mBAAmB,EACzB,MAAM,YAAY,CAAA;AACnB,OAAO,EAEL,KAAK,yBAAyB,EAE/B,MAAM,cAAc,CAAA;AAoCrB;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,GAAG,IAAI,CAcrE;AAMD,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,mBAAmB,CAAA;IAC3B,qEAAqE;IACrE,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB;;;;;;;;;OASG;IACH,cAAc,CAAC,EAAE,MAAM,EAAE,GAAG,GAAG,CAAA;IAC/B,8EAA8E;IAC9E,aAAa,CAAC,EAAE,OAAO,CAAA;CACxB;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAA;IAC7C,GAAG,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAA;IAC5C,MAAM,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAA;IAC/C,OAAO,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAA;CACjD;AAoBD;;;;;;;;;;;;GAYG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,iBAAiB,GAAG,gBAAgB,CA8G7E;AAMD,MAAM,WAAW,2BAA4B,SAAQ,yBAAyB;IAC5E,qEAAqE;IACrE,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED,MAAM,WAAW,0BAA0B;IACzC,GAAG,EAAE,MAAM,OAAO,CAAC,QAAQ,CAAC,CAAA;IAC5B,OAAO,EAAE,MAAM,OAAO,CAAC,QAAQ,CAAC,CAAA;CACjC;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,0BAA0B,CACxC,OAAO,EAAE,2BAA2B,GACnC,0BAA0B,CAkB5B;AAED,OAAO,EAAE,+BAA+B,EAAE,CAAA"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @sonordev/site-kit/mcp — Main entry point
|
|
3
|
+
*
|
|
4
|
+
* WebMCP + Model Context Protocol for marketing sites: make a site something
|
|
5
|
+
* an AI agent can *use*, not just read.
|
|
6
|
+
*
|
|
7
|
+
* Three surfaces, one tool definition:
|
|
8
|
+
*
|
|
9
|
+
* 1. Remote MCP endpoint — `createMcpHandler`. Streamable HTTP, dual-era
|
|
10
|
+
* (2026-07-28 modern + the `initialize` handshake older clients speak).
|
|
11
|
+
* 2. Discovery — `createMcpServerCardHandler`, the SEP-2127 card served at
|
|
12
|
+
* `/.well-known/mcp-server-card` and `/.well-known/mcp.json`.
|
|
13
|
+
* 3. In-page WebMCP — `<WebMcpTools>` (imperative) and
|
|
14
|
+
* `declarativeToolForm` (declarative), for browser-driving agents.
|
|
15
|
+
*
|
|
16
|
+
* @see https://modelcontextprotocol.io/specification/2026-07-28
|
|
17
|
+
* @see https://github.com/webmachinelearning/webmcp
|
|
18
|
+
*/
|
|
19
|
+
export type { JsonSchemaNode, JsonSchemaObject, McpContent, McpTextContent, McpResourceLinkContent, McpToolResult, McpToolHandlerResult, McpToolAnnotations, McpToolContext, McpToolHandler, SiteKitMcpTool, McpServerInfo, McpServerIcon, } from './types';
|
|
20
|
+
export { defineMcpTool } from './types';
|
|
21
|
+
export { MCP_PROTOCOL_VERSION_MODERN, MCP_PROTOCOL_VERSIONS_LEGACY, MCP_SUPPORTED_PROTOCOL_VERSIONS, META_PROTOCOL_VERSION, META_CLIENT_INFO, META_SERVER_INFO, MCP_ERROR_HEADER_MISMATCH, MCP_ERROR_UNSUPPORTED_PROTOCOL_VERSION, JSONRPC_METHOD_NOT_FOUND, dispatch, toToolResult, toolErrorResult, toToolDescriptor, validateArgs, isSupportedVersion, } from './protocol';
|
|
22
|
+
export type { JsonRpcRequest, JsonRpcResponse, JsonRpcError, DispatchOutcome, DispatchOptions, McpServerDefinition, McpToolDescriptor, } from './protocol';
|
|
23
|
+
export { buildMcpServerCard, validateMcpServerCard, MCP_SERVER_CARD_SCHEMA, MCP_SERVER_CARD_PATH, MCP_SERVER_CARD_LEGACY_PATH, SITE_KIT_CARD_META_KEY, } from './serverCard';
|
|
24
|
+
export type { McpServerCard, McpRemote, BuildMcpServerCardOptions, } from './serverCard';
|
|
25
|
+
export { createMcpHandler, createMcpServerCardHandler, decodeHeaderValue, } from './handlers';
|
|
26
|
+
export type { McpHandlerOptions, McpRouteHandlers, McpServerCardHandlerOptions, McpServerCardRouteHandlers, } from './handlers';
|
|
27
|
+
export { declarativeToolForm, declarativeToolField } from './declarative';
|
|
28
|
+
export type { DeclarativeToolFormOptions, DeclarativeToolFormAttributes, DeclarativeToolFieldAttributes, } from './declarative';
|
|
29
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/mcp/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAKH,YAAY,EACV,cAAc,EACd,gBAAgB,EAChB,UAAU,EACV,cAAc,EACd,sBAAsB,EACtB,aAAa,EACb,oBAAoB,EACpB,kBAAkB,EAClB,cAAc,EACd,cAAc,EACd,cAAc,EACd,aAAa,EACb,aAAa,GACd,MAAM,SAAS,CAAA;AAEhB,OAAO,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AAKvC,OAAO,EACL,2BAA2B,EAC3B,4BAA4B,EAC5B,+BAA+B,EAC/B,qBAAqB,EACrB,gBAAgB,EAChB,gBAAgB,EAChB,yBAAyB,EACzB,sCAAsC,EACtC,wBAAwB,EACxB,QAAQ,EACR,YAAY,EACZ,eAAe,EACf,gBAAgB,EAChB,YAAY,EACZ,kBAAkB,GACnB,MAAM,YAAY,CAAA;AACnB,YAAY,EACV,cAAc,EACd,eAAe,EACf,YAAY,EACZ,eAAe,EACf,eAAe,EACf,mBAAmB,EACnB,iBAAiB,GAClB,MAAM,YAAY,CAAA;AAKnB,OAAO,EACL,kBAAkB,EAClB,qBAAqB,EACrB,sBAAsB,EACtB,oBAAoB,EACpB,2BAA2B,EAC3B,sBAAsB,GACvB,MAAM,cAAc,CAAA;AACrB,YAAY,EACV,aAAa,EACb,SAAS,EACT,yBAAyB,GAC1B,MAAM,cAAc,CAAA;AAKrB,OAAO,EACL,gBAAgB,EAChB,0BAA0B,EAC1B,iBAAiB,GAClB,MAAM,YAAY,CAAA;AACnB,YAAY,EACV,iBAAiB,EACjB,gBAAgB,EAChB,2BAA2B,EAC3B,0BAA0B,GAC3B,MAAM,YAAY,CAAA;AAKnB,OAAO,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAA;AACzE,YAAY,EACV,0BAA0B,EAC1B,6BAA6B,EAC7B,8BAA8B,GAC/B,MAAM,eAAe,CAAA"}
|