@ductape/mcp 0.1.48 → 0.1.50

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/dist/index.js CHANGED
@@ -1192,9 +1192,63 @@ const docsInputSchema = z.object({
1192
1192
  topic: z.string().describe('Feature topic to look up. Supported: ' +
1193
1193
  'transactions, presave, triggers, aggregations, migrations, indexes, performance, actions, ' +
1194
1194
  'graphs, storage, cloud, vector, warehouse, secrets, apps, products, sessions, caches, ' +
1195
- 'notifications, resilience, features, events, logs'),
1195
+ 'notifications, resilience, features, events, logs, frontend, frontend-analytics, client, react, vue'),
1196
1196
  });
1197
1197
  const DOCS = {
1198
+ frontend: `
1199
+ DUCTAPE FRONTEND SDK GUIDE
1200
+
1201
+ Choose one integration package:
1202
+ React 17+ — npm install @ductape/react
1203
+ Provider and hooks built on @ductape/client.
1204
+ Continue with ductape_docs({ topic: "react" }).
1205
+ Vue 3+ — npm install @ductape/vue
1206
+ Plugin and composables built on @ductape/client.
1207
+ Continue with ductape_docs({ topic: "vue" }).
1208
+ Other UI — npm install @ductape/client
1209
+ frameworks Use directly with Svelte, Angular, vanilla JavaScript, or a custom adapter.
1210
+ Continue with ductape_docs({ topic: "client" }).
1211
+
1212
+ Do not install @ductape/client separately when using @ductape/react or @ductape/vue unless the
1213
+ application also needs direct access to a client API not exposed by the framework package. Both
1214
+ framework packages wrap @ductape/client and expose the underlying client through useDuctape().
1215
+
1216
+ SHARED APPLICATION LIFECYCLE
1217
+ 1. Create one client at the application root using a browser-safe publishableKey, proxy baseUrl,
1218
+ default product tag, and environment slug.
1219
+ 2. Connect the real-time client only in the browser. In React use DuctapeProvider autoConnect or
1220
+ connect() after mount; in Vue use createDuctape({ autoConnect: true }) or connect() in onMounted.
1221
+ 3. Authenticate the player with the sessions API and retain the returned session token according
1222
+ to the application's security policy. Refresh it before expiry (the React/Vue packages provide
1223
+ useSessionAutoRefresh) and revoke it on logout.
1224
+ 4. Pass the session value when connecting a broker that requires player-scoped authorization.
1225
+ 5. Subscribe through the framework hook/composable or the client service. Always unsubscribe on
1226
+ component teardown; the framework integrations do this automatically for declarative hooks.
1227
+ 6. The underlying client reconnects its WebSocket and restores active subscriptions. Observe
1228
+ connectionState to render disconnected/reconnecting UI; do not create duplicate subscriptions.
1229
+ 7. Disconnect resource sessions and the root client when the owning application scope is torn down.
1230
+
1231
+ PRODUCT ANALYTICS
1232
+ Frontend product analytics complements backend session propagation; neither replaces the other.
1233
+ Continue with ductape_docs({ topic: "frontend-analytics" }) for identity lifecycle, pageviews,
1234
+ custom events, privacy masking, hidden-state safety, trace correlation, and package-version checks.
1235
+
1236
+ AUTHENTICATION AND SECURITY
1237
+ - Use publishableKey in browser applications. Never ship workspace private keys or privileged
1238
+ access keys in frontend bundles.
1239
+ - A Ductape player session and the real-time transport connection are separate: authenticate or
1240
+ refresh the session, then use that session when opening player-scoped broker resources.
1241
+ - For SSR, create/connect the real-time client only on the browser side.
1242
+
1243
+ MIGRATING A CUSTOM WRAPPER
1244
+ - Keep @ductape/client when the wrapper implements application-specific projection or state logic.
1245
+ - For React, move root connection ownership to DuctapeProvider, replace imperative subscriptions
1246
+ with useBroker/useBrokerSubscription, and keep projection reduction in an application hook.
1247
+ - For Vue, move root ownership to createDuctape(), replace imperative subscriptions with
1248
+ useBroker/useBrokerSubscription, and keep projection reduction in an application composable.
1249
+ - Verify session handoff, initial loading state, error state, reconnect UI, subscription cleanup,
1250
+ and duplicate-event behavior with a live end-to-end run before removing the old wrapper.
1251
+ `.trim(),
1198
1252
  transactions: `
1199
1253
  DUCTAPE DATABASE TRANSACTIONS
1200
1254
 
@@ -1924,6 +1978,7 @@ Resource registration (all via ductape_cli or ductape_execute — see per-topic
1924
1978
  caches → ductape_docs({ topic: "caches" })
1925
1979
  notifications → ductape_docs({ topic: "notifications" })
1926
1980
  sessions → ductape_docs({ topic: "sessions" })
1981
+ frontend analytics → ductape_docs({ topic: "frontend-analytics" })
1927
1982
  resilience → ductape_docs({ topic: "resilience" })
1928
1983
  features → ductape_docs({ topic: "features" })
1929
1984
 
@@ -2011,6 +2066,16 @@ Analytics:
2011
2066
 
2012
2067
  Token format: "session_tag:jwt_token" — pass this format verbatim wherever session is expected.
2013
2068
  JWT is signed with the product private key (not a symmetric shared secret).
2069
+
2070
+ SESSION PROPAGATION
2071
+ Preserve the original full token on every user-context database, Event, Feature, notification,
2072
+ storage, graph, vector, action, and other runtime operation that accepts session. Do not replace
2073
+ it with the session tag, user ID, decoded claims, or refresh token. Durable user-initiated work
2074
+ should retain the initiating token; intentional system/background work should be sessionless.
2075
+
2076
+ Backend propagation attributes component activity to an actor, but it does not record frontend
2077
+ pageviews, navigation, UI intent, funnels, or client failures. Continue immediately with
2078
+ ductape_docs({ topic: "frontend-analytics" }); both layers are required for a complete picture.
2014
2079
  `.trim(),
2015
2080
  caches: `
2016
2081
  DUCTAPE CACHES
@@ -2173,6 +2238,222 @@ Failure actions: notification channels, HTTP webhooks, and/or message broker emi
2173
2238
  be configured simultaneously on the same healthcheck.
2174
2239
  Input template references: $Input{field} → maps declared input to the probe's action input.
2175
2240
  Provider status: available | unavailable
2241
+ `.trim(),
2242
+ 'frontend-analytics': `
2243
+ DUCTAPE FRONTEND PRODUCT ANALYTICS AND SESSION ACTIVITY
2244
+
2245
+ Frontend analytics and backend session attribution are complementary; neither replaces the other.
2246
+
2247
+ BACKEND OPERATION ATTRIBUTION
2248
+ Pass the original full Ductape session token to every user-initiated database, Event, Feature,
2249
+ notification, storage, graph, vector, action, and other runtime operation that accepts session.
2250
+ This attributes server work to the authenticated actor. Durable work should retain the initiating
2251
+ session; intentional system/background work should remain sessionless.
2252
+
2253
+ FRONTEND PRODUCT ANALYTICS
2254
+ Use the browser Analytics service for anonymous visits, authenticated page views, navigation,
2255
+ UI interactions, funnels, client errors, realtime state, and product-surface engagement.
2256
+
2257
+ IMPORTANT:
2258
+ Passing session to backend component calls does not replace frontend analytics.
2259
+ Calling analytics.identify(sessionToken) and tracking frontend events does not replace backend
2260
+ session propagation. A complete activity picture requires both.
2261
+
2262
+ VERSION AND DOCUMENTATION PRECEDENCE
2263
+ 1. Installed package types and exports determine what can be called now.
2264
+ 2. Version-matched package documentation explains intended usage.
2265
+ 3. Current docs/docs/Frontend describes the latest supported design.
2266
+ 4. If they disagree, report the mismatch; never fabricate compatibility.
2267
+
2268
+ Inspect package.json plus package exports/type declarations before recommending framework hooks.
2269
+ The current repository exports useAnalytics from @ductape/react and @ductape/vue. Their installed
2270
+ hook/composable exposes track, pageview, identify, visitorId, enableAutoCapture, and flush.
2271
+ clearSession and disableAutoCapture are available on client.analytics but are not currently
2272
+ returned by those framework wrappers. Use useDuctape().client.analytics for those calls, or
2273
+ recommend a package version that exports them; do not generate a hook method that does not exist.
2274
+
2275
+ CLIENT API — IDENTIFY AFTER AUTHENTICATION
2276
+ ductape.analytics.identify(sessionToken);
2277
+
2278
+ sessionToken is the complete value returned by Ductape, in "player-session:jwt" format.
2279
+ It is NOT a player ID, session tag, session ID, decoded claims, or refresh token.
2280
+ identify links subsequent frontend analytics to the authenticated Ductape session and lets
2281
+ supported analytics correlate anonymous pre-login activity with authenticated activity.
2282
+
2283
+ LOGOUT / ACCOUNT SWITCHING
2284
+ await ductape.analytics.flush();
2285
+ ductape.analytics.clearSession();
2286
+
2287
+ Clear analytics identity when logout or revocation succeeds, refresh fails irrecoverably, local
2288
+ authentication is removed, or a different user is about to authenticate in the same browser.
2289
+ Removing only the application token can leave later anonymous/next-user events associated with
2290
+ the previous analytics identity. Flush is best-effort; browser shutdown does not guarantee it.
2291
+
2292
+ CUSTOM EVENTS
2293
+ await ductape.analytics.track({
2294
+ event: 'order_submitted',
2295
+ traceId,
2296
+ properties: { matchId, orderType },
2297
+ });
2298
+
2299
+ IAnalyticsTrackOptions:
2300
+ event: string
2301
+ properties?: Record<string, unknown>
2302
+ session?: string
2303
+ product?: string
2304
+ env?: string
2305
+ traceId?: string
2306
+ context?: { url?, path?, referrer?, locale?, screen?: { width, height } }
2307
+
2308
+ identify establishes the default analytics session. An individual event may explicitly provide
2309
+ session. Normally use product/env from client configuration. traceId correlates frontend intent
2310
+ with backend logs, Events, Features, and order processing.
2311
+
2312
+ PAGE VIEWS
2313
+ await ductape.analytics.pageview({
2314
+ path: location.pathname,
2315
+ title: document.title,
2316
+ properties: { matchId, screen: 'governance' },
2317
+ });
2318
+
2319
+ IAnalyticsPageviewOptions:
2320
+ path?: string
2321
+ title?: string
2322
+ session?: string
2323
+ product?: string
2324
+ env?: string
2325
+ properties?: Record<string, unknown>
2326
+
2327
+ AUTO-CAPTURE — OPT IN ONLY AFTER A PRIVACY AUDIT
2328
+ const stopAutoCapture = ductape.analytics.enableAutoCapture({
2329
+ session: () => authSession?.token,
2330
+ clicks: false,
2331
+ pageviews: true,
2332
+ maskTextSelectors: [
2333
+ '[data-private]',
2334
+ '[data-secret]',
2335
+ '[data-player-message]',
2336
+ '[data-intelligence-report]',
2337
+ ],
2338
+ });
2339
+ stopAutoCapture(); // or ductape.analytics.disableAutoCapture()
2340
+
2341
+ Mark sensitive UI with data-private/data-secret attributes. Text masking may not mask attributes,
2342
+ IDs, URLs, element names, custom properties, console errors, or network errors. Inspect actual
2343
+ payloads before production. For hidden-information products, begin with reviewed automatic
2344
+ pageviews, clicks disabled, and preferred custom named events.
2345
+
2346
+ VISITOR ID
2347
+ const visitorId = ductape.analytics.getVisitorId();
2348
+ A visitor ID is anonymous analytics identity, not authentication or authorization.
2349
+
2350
+ FRONTEND SESSION LIFECYCLE
2351
+ Before login:
2352
+ - Track anonymous pageviews/onboarding; do not invent a session.
2353
+ After login/registration:
2354
+ - Store the session securely, identify(fullSessionToken), track success, start authenticated
2355
+ pageviews, and pass the same token to user-context realtime/component operations.
2356
+ After refresh:
2357
+ - Replace the old token, identify(newSessionToken), update realtime connections/subscriptions,
2358
+ and use the refreshed token for future backend operations.
2359
+ Logout:
2360
+ - Optionally track logout_initiated, flush, revoke, disconnect realtime, clearSession, then
2361
+ remove local authentication.
2362
+ Refresh failure/revocation:
2363
+ - Disconnect user-context clients, clearSession, clear local auth, navigate to authentication,
2364
+ and track only anonymous events afterward.
2365
+
2366
+ REACT ROUTE TRACKING (verify installed exports first)
2367
+ import { useAnalytics, useDuctape } from '@ductape/react';
2368
+ import { useEffect } from 'react';
2369
+ import { useLocation } from 'react-router-dom';
2370
+
2371
+ function ProductAnalytics({ sessionToken }: { sessionToken?: string }) {
2372
+ const analytics = useAnalytics();
2373
+ const { client } = useDuctape();
2374
+ const location = useLocation();
2375
+ useEffect(() => {
2376
+ if (sessionToken) analytics.identify(sessionToken);
2377
+ else client.analytics.clearSession();
2378
+ }, [analytics, client, sessionToken]);
2379
+ useEffect(() => {
2380
+ void analytics.pageview({ path: location.pathname, title: document.title });
2381
+ }, [analytics, location.pathname]);
2382
+ return null;
2383
+ }
2384
+
2385
+ SAFE EVENT TAXONOMY
2386
+ Define stable names centrally; do not invent variants throughout components.
2387
+ Authentication:
2388
+ registration_started, registration_completed, registration_failed,
2389
+ login_started, login_completed, login_failed, session_refreshed,
2390
+ session_refresh_failed, logout_completed
2391
+ Match:
2392
+ match_list_viewed, match_creation_started, match_created, match_joined, lobby_viewed,
2393
+ player_marked_ready, match_preparation_started, world_loaded, match_reconnected,
2394
+ match_completed, endgame_viewed
2395
+ Orders:
2396
+ order_form_opened, order_previewed, order_submission_started, order_submitted,
2397
+ order_submission_failed, order_cancelled, boundary_result_viewed
2398
+ Realtime:
2399
+ realtime_connect_started, realtime_connected, realtime_disconnected,
2400
+ realtime_reconnect_attempted, realtime_subscription_failed, projection_refresh_failed,
2401
+ client_error
2402
+ Funnels:
2403
+ tutorial_started, tutorial_step_completed, tutorial_abandoned, first_match_created,
2404
+ first_order_submitted, first_boundary_viewed, first_match_completed
2405
+
2406
+ Safe properties include matchId, orderType, screen, tick, result, and errorCategory.
2407
+
2408
+ HIDDEN AND SENSITIVE DATA — NEVER SEND TO ANALYTICS
2409
+ Do not track exact hidden formations, operative/handler identities, secret operation payloads,
2410
+ false-report truth markers, undiscovered evasion details, invisible treaties, canonical hidden
2411
+ map state, private messages, passwords, authorization headers, session/refresh tokens in event
2412
+ properties, or full errors/records that may contain secrets. Analytics must observe usage, not
2413
+ become a hidden-state side channel.
2414
+
2415
+ CORRELATION
2416
+ Frontend: create traceId = crypto.randomUUID(), track intent with traceId, and send traceId with
2417
+ the actual request. Backend logs the same traceId and propagates session to Ductape operations.
2418
+ Track completion with the same traceId.
2419
+
2420
+ traceId = correlation
2421
+ session = actor attribution
2422
+ idempotencyKey = duplicate prevention
2423
+ matchId/orderId = domain identity
2424
+ These values are not interchangeable.
2425
+
2426
+ OWNERSHIP BOUNDARIES
2427
+ Analytics is never an authoritative order, authorization proof, or gameplay source of truth.
2428
+ Analytics failure must not block or alter gameplay. The server verifies sessions independently;
2429
+ authoritative database and Event streams remain the source of truth. Prefer non-blocking
2430
+ analytics except explicit best-effort flushes at safe lifecycle transitions.
2431
+
2432
+ FRONTEND PROJECT AUDIT
2433
+ Inspect installed @ductape/client/react/vue versions and actual exports; client/provider setup;
2434
+ identify after login and refresh; clearSession on logout/account switch; SPA pageviews; auto-
2435
+ capture and masking; taxonomy consistency; sensitive custom properties; and shared trace IDs.
2436
+ Report capability as Present/Missing/Partial/Not applicable with concrete findings.
2437
+
2438
+ WHEN “SESSION ACTIVITY IS MISSING FROM THE DASHBOARD”
2439
+ Investigate both tracks before blaming the SDK.
2440
+ Backend: start/verify format, correct env, session on database/Event/Feature/notification/etc.,
2441
+ and intentional background sessionlessness.
2442
+ Frontend: identify after login/refresh, clearSession on logout, pageviews/custom events, flush,
2443
+ correct publishable key/product/env, installed API compatibility, browser failures, and privacy
2444
+ controls that may suppress events.
2445
+
2446
+ PAYLOAD RECIPES
2447
+ Anonymous:
2448
+ await ductape.analytics.pageview({ path: window.location.pathname, title: document.title });
2449
+ Authenticated:
2450
+ ductape.analytics.identify(playerSessionToken);
2451
+ await ductape.analytics.track({
2452
+ event: 'match_created', session: playerSessionToken, traceId, properties: { matchId },
2453
+ });
2454
+ Logout:
2455
+ await ductape.analytics.flush();
2456
+ ductape.analytics.clearSession();
2176
2457
  `.trim(),
2177
2458
  features: `
2178
2459
  DUCTAPE FEATURES
@@ -2858,9 +3139,15 @@ SESSIONS SERVICE
2858
3139
  const { token } = await ductape.sessions.refresh({ refreshToken: '...' });
2859
3140
 
2860
3141
  ANALYTICS SERVICE
2861
- ductape.analytics.pageview({ page: '/dashboard' });
2862
- ductape.analytics.track('button_click', { button: 'sign-up' });
2863
- ductape.analytics.identify({ userId: 'u_123', traits: { plan: 'pro' } });
3142
+ ductape.analytics.identify('player-session:eyJ...'); // full Ductape session token
3143
+ await ductape.analytics.pageview({ path: '/dashboard', title: document.title });
3144
+ await ductape.analytics.track({
3145
+ event: 'button_clicked',
3146
+ properties: { button: 'sign-up' },
3147
+ });
3148
+ await ductape.analytics.flush();
3149
+ ductape.analytics.clearSession(); // logout/account switch
3150
+ See ductape_docs({ topic: "frontend-analytics" }) before enabling auto-capture.
2864
3151
 
2865
3152
  FRAMEWORK-SPECIFIC PACKAGES
2866
3153
  For React and Vue projects, use the dedicated packages instead of managing the client manually:
@@ -2961,13 +3248,15 @@ AGENT HOOKS
2961
3248
  useAgentSignal(hookOptions?) → { mutate, isLoading, error }
2962
3249
 
2963
3250
  BROKER HOOKS
2964
- useBroker(broker, options?)
3251
+ useBroker()
2965
3252
  → { isConnected, isConnecting, error, connect, disconnect }
3253
+ connect({ broker, session?, product?, env? }) forwards the complete options object to
3254
+ @ductape/client. Pass session for player-scoped authorization.
2966
3255
  useBrokerPublish(hookOptions?)
2967
3256
  → { mutate({ topic, message, headers?, key? }), isLoading, error, data }
2968
3257
  useBrokerSubscription(subscribeOptions, hookOptions?)
2969
3258
  → { data: BrokerMessage[], isSubscribed, error, unsubscribe, resubscribe }
2970
- subscribeOptions: { topic, group? }
3259
+ subscribeOptions: { broker?, topic, group?, session?, product?, env? }
2971
3260
 
2972
3261
  GRAPH HOOKS
2973
3262
  useGraph(graphTag) → { isConnected, isConnecting, error, connect, disconnect }
@@ -3037,7 +3326,10 @@ ACTIONS HOOKS
3037
3326
  useActionRun(hookOptions?) → { mutate, isLoading, error, data }
3038
3327
 
3039
3328
  ANALYTICS HOOK
3040
- useAnalytics() → { pageview, track, identify }
3329
+ useAnalytics() → { pageview, track, identify, visitorId, enableAutoCapture, flush }
3330
+ The currently installed hook does NOT return clearSession or disableAutoCapture.
3331
+ Use useDuctape().client.analytics.clearSession()/disableAutoCapture(), or verify a newer installed
3332
+ package export before generating those hook calls. See ductape_docs({ topic: "frontend-analytics" }).
3041
3333
 
3042
3334
  hookOptions pattern (applies to all hooks):
3043
3335
  enabled? boolean — false skips auto-fetch/subscribe
@@ -3206,7 +3498,10 @@ ACTIONS COMPOSABLES
3206
3498
  useActionRun(composableOptions?) → { mutate, isLoading, error, data }
3207
3499
 
3208
3500
  ANALYTICS COMPOSABLE
3209
- useAnalytics() → { pageview, track, identify }
3501
+ useAnalytics() → { pageview, track, identify, visitorId, enableAutoCapture, flush }
3502
+ The currently installed composable does NOT return clearSession or disableAutoCapture.
3503
+ Use useDuctape().client.analytics.clearSession()/disableAutoCapture(), or verify a newer installed
3504
+ package export before generating those composable calls. See ductape_docs({ topic: "frontend-analytics" }).
3210
3505
 
3211
3506
  composableOptions pattern (applies to all composables):
3212
3507
  enabled? boolean — false skips auto-fetch/subscribe
@@ -3549,7 +3844,7 @@ async function main() {
3549
3844
  'index strategy, operation types) that should be confirmed with the user first.\n\n' +
3550
3845
  'Available topics: transactions, presave, triggers, aggregations, migrations, indexes, performance, actions, ' +
3551
3846
  'graphs, storage, cloud, vector, warehouse, secrets, apps, products, sessions, caches, ' +
3552
- 'notifications, resilience, features, events, logs, client, react, vue',
3847
+ 'notifications, resilience, features, events, logs, frontend, frontend-analytics, client, react, vue',
3553
3848
  inputSchema: docsInputSchema,
3554
3849
  }, docsHandler);
3555
3850
  server.registerTool('ductape_cli', {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ductape/mcp",
3
- "version": "0.1.48",
3
+ "version": "0.1.50",
4
4
  "description": "MCP server that exposes Ductape SDK operations via the backend proxy",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -9,6 +9,7 @@
9
9
  },
10
10
  "scripts": {
11
11
  "build": "tsc",
12
+ "test": "node scripts/check-frontend-analytics-guidance.mjs",
12
13
  "start": "node dist/index.js",
13
14
  "dev": "tsx src/index.ts"
14
15
  },
@@ -0,0 +1,37 @@
1
+ import assert from 'node:assert/strict';
2
+ import { readFileSync } from 'node:fs';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { dirname, resolve } from 'node:path';
5
+
6
+ const here = dirname(fileURLToPath(import.meta.url));
7
+ const source = readFileSync(resolve(here, '../src/index.ts'), 'utf8');
8
+
9
+ const checks = [
10
+ ['dedicated docs topic', /'frontend-analytics': `/],
11
+ ['anonymous analytics without an invented session', /Before login:[\s\S]*do not invent a session/i],
12
+ ['identify after authentication', /After login\/registration:[\s\S]*identify\(fullSessionToken\)/i],
13
+ ['identify after refresh', /After refresh:[\s\S]*identify\(newSessionToken\)/i],
14
+ ['clear identity on logout and account switching', /LOGOUT \/ ACCOUNT SWITCHING[\s\S]*clearSession\(\)/],
15
+ ['SPA route pageviews', /location\.pathname[\s\S]*analytics\.pageview/],
16
+ ['pageviews and custom events', /CUSTOM EVENTS[\s\S]*PAGE VIEWS/],
17
+ ['analytics is non-authoritative', /Analytics is never an authoritative order/],
18
+ ['tokens excluded from properties', /session\/refresh tokens in event[\s\S]*properties/],
19
+ ['hidden multiplayer state warning', /HIDDEN AND SENSITIVE DATA/],
20
+ ['masking review before auto-capture', /AUTO-CAPTURE — OPT IN ONLY AFTER A PRIVACY AUDIT/],
21
+ ['visitor ID is not authentication', /visitor ID is anonymous analytics identity, not authentication/i],
22
+ ['trace correlation', /traceId\s*= correlation/],
23
+ ['dashboard troubleshooting covers both tracks', /Investigate both tracks before blaming the SDK/],
24
+ ['installed exports take precedence', /Installed package types and exports determine what can be called now/],
25
+ ];
26
+
27
+ for (const [name, pattern] of checks) {
28
+ assert.match(source, pattern, `Missing frontend analytics guidance: ${name}`);
29
+ }
30
+
31
+ assert.doesNotMatch(
32
+ source,
33
+ /analytics\.identify\(\{\s*userId/,
34
+ 'MCP guidance must not use the obsolete identify({ userId, traits }) signature',
35
+ );
36
+
37
+ console.log(`frontend analytics guidance: ${checks.length + 1} acceptance checks passed`);
package/src/index.ts CHANGED
@@ -1242,11 +1242,66 @@ const docsInputSchema = z.object({
1242
1242
  'Feature topic to look up. Supported: ' +
1243
1243
  'transactions, presave, triggers, aggregations, migrations, indexes, performance, actions, ' +
1244
1244
  'graphs, storage, cloud, vector, warehouse, secrets, apps, products, sessions, caches, ' +
1245
- 'notifications, resilience, features, events, logs',
1245
+ 'notifications, resilience, features, events, logs, frontend, frontend-analytics, client, react, vue',
1246
1246
  ),
1247
1247
  });
1248
1248
 
1249
1249
  const DOCS: Record<string, string> = {
1250
+ frontend: `
1251
+ DUCTAPE FRONTEND SDK GUIDE
1252
+
1253
+ Choose one integration package:
1254
+ React 17+ — npm install @ductape/react
1255
+ Provider and hooks built on @ductape/client.
1256
+ Continue with ductape_docs({ topic: "react" }).
1257
+ Vue 3+ — npm install @ductape/vue
1258
+ Plugin and composables built on @ductape/client.
1259
+ Continue with ductape_docs({ topic: "vue" }).
1260
+ Other UI — npm install @ductape/client
1261
+ frameworks Use directly with Svelte, Angular, vanilla JavaScript, or a custom adapter.
1262
+ Continue with ductape_docs({ topic: "client" }).
1263
+
1264
+ Do not install @ductape/client separately when using @ductape/react or @ductape/vue unless the
1265
+ application also needs direct access to a client API not exposed by the framework package. Both
1266
+ framework packages wrap @ductape/client and expose the underlying client through useDuctape().
1267
+
1268
+ SHARED APPLICATION LIFECYCLE
1269
+ 1. Create one client at the application root using a browser-safe publishableKey, proxy baseUrl,
1270
+ default product tag, and environment slug.
1271
+ 2. Connect the real-time client only in the browser. In React use DuctapeProvider autoConnect or
1272
+ connect() after mount; in Vue use createDuctape({ autoConnect: true }) or connect() in onMounted.
1273
+ 3. Authenticate the player with the sessions API and retain the returned session token according
1274
+ to the application's security policy. Refresh it before expiry (the React/Vue packages provide
1275
+ useSessionAutoRefresh) and revoke it on logout.
1276
+ 4. Pass the session value when connecting a broker that requires player-scoped authorization.
1277
+ 5. Subscribe through the framework hook/composable or the client service. Always unsubscribe on
1278
+ component teardown; the framework integrations do this automatically for declarative hooks.
1279
+ 6. The underlying client reconnects its WebSocket and restores active subscriptions. Observe
1280
+ connectionState to render disconnected/reconnecting UI; do not create duplicate subscriptions.
1281
+ 7. Disconnect resource sessions and the root client when the owning application scope is torn down.
1282
+
1283
+ PRODUCT ANALYTICS
1284
+ Frontend product analytics complements backend session propagation; neither replaces the other.
1285
+ Continue with ductape_docs({ topic: "frontend-analytics" }) for identity lifecycle, pageviews,
1286
+ custom events, privacy masking, hidden-state safety, trace correlation, and package-version checks.
1287
+
1288
+ AUTHENTICATION AND SECURITY
1289
+ - Use publishableKey in browser applications. Never ship workspace private keys or privileged
1290
+ access keys in frontend bundles.
1291
+ - A Ductape player session and the real-time transport connection are separate: authenticate or
1292
+ refresh the session, then use that session when opening player-scoped broker resources.
1293
+ - For SSR, create/connect the real-time client only on the browser side.
1294
+
1295
+ MIGRATING A CUSTOM WRAPPER
1296
+ - Keep @ductape/client when the wrapper implements application-specific projection or state logic.
1297
+ - For React, move root connection ownership to DuctapeProvider, replace imperative subscriptions
1298
+ with useBroker/useBrokerSubscription, and keep projection reduction in an application hook.
1299
+ - For Vue, move root ownership to createDuctape(), replace imperative subscriptions with
1300
+ useBroker/useBrokerSubscription, and keep projection reduction in an application composable.
1301
+ - Verify session handoff, initial loading state, error state, reconnect UI, subscription cleanup,
1302
+ and duplicate-event behavior with a live end-to-end run before removing the old wrapper.
1303
+ `.trim(),
1304
+
1250
1305
  transactions: `
1251
1306
  DUCTAPE DATABASE TRANSACTIONS
1252
1307
 
@@ -1991,6 +2046,7 @@ Resource registration (all via ductape_cli or ductape_execute — see per-topic
1991
2046
  caches → ductape_docs({ topic: "caches" })
1992
2047
  notifications → ductape_docs({ topic: "notifications" })
1993
2048
  sessions → ductape_docs({ topic: "sessions" })
2049
+ frontend analytics → ductape_docs({ topic: "frontend-analytics" })
1994
2050
  resilience → ductape_docs({ topic: "resilience" })
1995
2051
  features → ductape_docs({ topic: "features" })
1996
2052
 
@@ -2079,6 +2135,16 @@ Analytics:
2079
2135
 
2080
2136
  Token format: "session_tag:jwt_token" — pass this format verbatim wherever session is expected.
2081
2137
  JWT is signed with the product private key (not a symmetric shared secret).
2138
+
2139
+ SESSION PROPAGATION
2140
+ Preserve the original full token on every user-context database, Event, Feature, notification,
2141
+ storage, graph, vector, action, and other runtime operation that accepts session. Do not replace
2142
+ it with the session tag, user ID, decoded claims, or refresh token. Durable user-initiated work
2143
+ should retain the initiating token; intentional system/background work should be sessionless.
2144
+
2145
+ Backend propagation attributes component activity to an actor, but it does not record frontend
2146
+ pageviews, navigation, UI intent, funnels, or client failures. Continue immediately with
2147
+ ductape_docs({ topic: "frontend-analytics" }); both layers are required for a complete picture.
2082
2148
  `.trim(),
2083
2149
 
2084
2150
  caches: `
@@ -2246,6 +2312,223 @@ Input template references: $Input{field} → maps declared input to the probe's
2246
2312
  Provider status: available | unavailable
2247
2313
  `.trim(),
2248
2314
 
2315
+ 'frontend-analytics': `
2316
+ DUCTAPE FRONTEND PRODUCT ANALYTICS AND SESSION ACTIVITY
2317
+
2318
+ Frontend analytics and backend session attribution are complementary; neither replaces the other.
2319
+
2320
+ BACKEND OPERATION ATTRIBUTION
2321
+ Pass the original full Ductape session token to every user-initiated database, Event, Feature,
2322
+ notification, storage, graph, vector, action, and other runtime operation that accepts session.
2323
+ This attributes server work to the authenticated actor. Durable work should retain the initiating
2324
+ session; intentional system/background work should remain sessionless.
2325
+
2326
+ FRONTEND PRODUCT ANALYTICS
2327
+ Use the browser Analytics service for anonymous visits, authenticated page views, navigation,
2328
+ UI interactions, funnels, client errors, realtime state, and product-surface engagement.
2329
+
2330
+ IMPORTANT:
2331
+ Passing session to backend component calls does not replace frontend analytics.
2332
+ Calling analytics.identify(sessionToken) and tracking frontend events does not replace backend
2333
+ session propagation. A complete activity picture requires both.
2334
+
2335
+ VERSION AND DOCUMENTATION PRECEDENCE
2336
+ 1. Installed package types and exports determine what can be called now.
2337
+ 2. Version-matched package documentation explains intended usage.
2338
+ 3. Current docs/docs/Frontend describes the latest supported design.
2339
+ 4. If they disagree, report the mismatch; never fabricate compatibility.
2340
+
2341
+ Inspect package.json plus package exports/type declarations before recommending framework hooks.
2342
+ The current repository exports useAnalytics from @ductape/react and @ductape/vue. Their installed
2343
+ hook/composable exposes track, pageview, identify, visitorId, enableAutoCapture, and flush.
2344
+ clearSession and disableAutoCapture are available on client.analytics but are not currently
2345
+ returned by those framework wrappers. Use useDuctape().client.analytics for those calls, or
2346
+ recommend a package version that exports them; do not generate a hook method that does not exist.
2347
+
2348
+ CLIENT API — IDENTIFY AFTER AUTHENTICATION
2349
+ ductape.analytics.identify(sessionToken);
2350
+
2351
+ sessionToken is the complete value returned by Ductape, in "player-session:jwt" format.
2352
+ It is NOT a player ID, session tag, session ID, decoded claims, or refresh token.
2353
+ identify links subsequent frontend analytics to the authenticated Ductape session and lets
2354
+ supported analytics correlate anonymous pre-login activity with authenticated activity.
2355
+
2356
+ LOGOUT / ACCOUNT SWITCHING
2357
+ await ductape.analytics.flush();
2358
+ ductape.analytics.clearSession();
2359
+
2360
+ Clear analytics identity when logout or revocation succeeds, refresh fails irrecoverably, local
2361
+ authentication is removed, or a different user is about to authenticate in the same browser.
2362
+ Removing only the application token can leave later anonymous/next-user events associated with
2363
+ the previous analytics identity. Flush is best-effort; browser shutdown does not guarantee it.
2364
+
2365
+ CUSTOM EVENTS
2366
+ await ductape.analytics.track({
2367
+ event: 'order_submitted',
2368
+ traceId,
2369
+ properties: { matchId, orderType },
2370
+ });
2371
+
2372
+ IAnalyticsTrackOptions:
2373
+ event: string
2374
+ properties?: Record<string, unknown>
2375
+ session?: string
2376
+ product?: string
2377
+ env?: string
2378
+ traceId?: string
2379
+ context?: { url?, path?, referrer?, locale?, screen?: { width, height } }
2380
+
2381
+ identify establishes the default analytics session. An individual event may explicitly provide
2382
+ session. Normally use product/env from client configuration. traceId correlates frontend intent
2383
+ with backend logs, Events, Features, and order processing.
2384
+
2385
+ PAGE VIEWS
2386
+ await ductape.analytics.pageview({
2387
+ path: location.pathname,
2388
+ title: document.title,
2389
+ properties: { matchId, screen: 'governance' },
2390
+ });
2391
+
2392
+ IAnalyticsPageviewOptions:
2393
+ path?: string
2394
+ title?: string
2395
+ session?: string
2396
+ product?: string
2397
+ env?: string
2398
+ properties?: Record<string, unknown>
2399
+
2400
+ AUTO-CAPTURE — OPT IN ONLY AFTER A PRIVACY AUDIT
2401
+ const stopAutoCapture = ductape.analytics.enableAutoCapture({
2402
+ session: () => authSession?.token,
2403
+ clicks: false,
2404
+ pageviews: true,
2405
+ maskTextSelectors: [
2406
+ '[data-private]',
2407
+ '[data-secret]',
2408
+ '[data-player-message]',
2409
+ '[data-intelligence-report]',
2410
+ ],
2411
+ });
2412
+ stopAutoCapture(); // or ductape.analytics.disableAutoCapture()
2413
+
2414
+ Mark sensitive UI with data-private/data-secret attributes. Text masking may not mask attributes,
2415
+ IDs, URLs, element names, custom properties, console errors, or network errors. Inspect actual
2416
+ payloads before production. For hidden-information products, begin with reviewed automatic
2417
+ pageviews, clicks disabled, and preferred custom named events.
2418
+
2419
+ VISITOR ID
2420
+ const visitorId = ductape.analytics.getVisitorId();
2421
+ A visitor ID is anonymous analytics identity, not authentication or authorization.
2422
+
2423
+ FRONTEND SESSION LIFECYCLE
2424
+ Before login:
2425
+ - Track anonymous pageviews/onboarding; do not invent a session.
2426
+ After login/registration:
2427
+ - Store the session securely, identify(fullSessionToken), track success, start authenticated
2428
+ pageviews, and pass the same token to user-context realtime/component operations.
2429
+ After refresh:
2430
+ - Replace the old token, identify(newSessionToken), update realtime connections/subscriptions,
2431
+ and use the refreshed token for future backend operations.
2432
+ Logout:
2433
+ - Optionally track logout_initiated, flush, revoke, disconnect realtime, clearSession, then
2434
+ remove local authentication.
2435
+ Refresh failure/revocation:
2436
+ - Disconnect user-context clients, clearSession, clear local auth, navigate to authentication,
2437
+ and track only anonymous events afterward.
2438
+
2439
+ REACT ROUTE TRACKING (verify installed exports first)
2440
+ import { useAnalytics, useDuctape } from '@ductape/react';
2441
+ import { useEffect } from 'react';
2442
+ import { useLocation } from 'react-router-dom';
2443
+
2444
+ function ProductAnalytics({ sessionToken }: { sessionToken?: string }) {
2445
+ const analytics = useAnalytics();
2446
+ const { client } = useDuctape();
2447
+ const location = useLocation();
2448
+ useEffect(() => {
2449
+ if (sessionToken) analytics.identify(sessionToken);
2450
+ else client.analytics.clearSession();
2451
+ }, [analytics, client, sessionToken]);
2452
+ useEffect(() => {
2453
+ void analytics.pageview({ path: location.pathname, title: document.title });
2454
+ }, [analytics, location.pathname]);
2455
+ return null;
2456
+ }
2457
+
2458
+ SAFE EVENT TAXONOMY
2459
+ Define stable names centrally; do not invent variants throughout components.
2460
+ Authentication:
2461
+ registration_started, registration_completed, registration_failed,
2462
+ login_started, login_completed, login_failed, session_refreshed,
2463
+ session_refresh_failed, logout_completed
2464
+ Match:
2465
+ match_list_viewed, match_creation_started, match_created, match_joined, lobby_viewed,
2466
+ player_marked_ready, match_preparation_started, world_loaded, match_reconnected,
2467
+ match_completed, endgame_viewed
2468
+ Orders:
2469
+ order_form_opened, order_previewed, order_submission_started, order_submitted,
2470
+ order_submission_failed, order_cancelled, boundary_result_viewed
2471
+ Realtime:
2472
+ realtime_connect_started, realtime_connected, realtime_disconnected,
2473
+ realtime_reconnect_attempted, realtime_subscription_failed, projection_refresh_failed,
2474
+ client_error
2475
+ Funnels:
2476
+ tutorial_started, tutorial_step_completed, tutorial_abandoned, first_match_created,
2477
+ first_order_submitted, first_boundary_viewed, first_match_completed
2478
+
2479
+ Safe properties include matchId, orderType, screen, tick, result, and errorCategory.
2480
+
2481
+ HIDDEN AND SENSITIVE DATA — NEVER SEND TO ANALYTICS
2482
+ Do not track exact hidden formations, operative/handler identities, secret operation payloads,
2483
+ false-report truth markers, undiscovered evasion details, invisible treaties, canonical hidden
2484
+ map state, private messages, passwords, authorization headers, session/refresh tokens in event
2485
+ properties, or full errors/records that may contain secrets. Analytics must observe usage, not
2486
+ become a hidden-state side channel.
2487
+
2488
+ CORRELATION
2489
+ Frontend: create traceId = crypto.randomUUID(), track intent with traceId, and send traceId with
2490
+ the actual request. Backend logs the same traceId and propagates session to Ductape operations.
2491
+ Track completion with the same traceId.
2492
+
2493
+ traceId = correlation
2494
+ session = actor attribution
2495
+ idempotencyKey = duplicate prevention
2496
+ matchId/orderId = domain identity
2497
+ These values are not interchangeable.
2498
+
2499
+ OWNERSHIP BOUNDARIES
2500
+ Analytics is never an authoritative order, authorization proof, or gameplay source of truth.
2501
+ Analytics failure must not block or alter gameplay. The server verifies sessions independently;
2502
+ authoritative database and Event streams remain the source of truth. Prefer non-blocking
2503
+ analytics except explicit best-effort flushes at safe lifecycle transitions.
2504
+
2505
+ FRONTEND PROJECT AUDIT
2506
+ Inspect installed @ductape/client/react/vue versions and actual exports; client/provider setup;
2507
+ identify after login and refresh; clearSession on logout/account switch; SPA pageviews; auto-
2508
+ capture and masking; taxonomy consistency; sensitive custom properties; and shared trace IDs.
2509
+ Report capability as Present/Missing/Partial/Not applicable with concrete findings.
2510
+
2511
+ WHEN “SESSION ACTIVITY IS MISSING FROM THE DASHBOARD”
2512
+ Investigate both tracks before blaming the SDK.
2513
+ Backend: start/verify format, correct env, session on database/Event/Feature/notification/etc.,
2514
+ and intentional background sessionlessness.
2515
+ Frontend: identify after login/refresh, clearSession on logout, pageviews/custom events, flush,
2516
+ correct publishable key/product/env, installed API compatibility, browser failures, and privacy
2517
+ controls that may suppress events.
2518
+
2519
+ PAYLOAD RECIPES
2520
+ Anonymous:
2521
+ await ductape.analytics.pageview({ path: window.location.pathname, title: document.title });
2522
+ Authenticated:
2523
+ ductape.analytics.identify(playerSessionToken);
2524
+ await ductape.analytics.track({
2525
+ event: 'match_created', session: playerSessionToken, traceId, properties: { matchId },
2526
+ });
2527
+ Logout:
2528
+ await ductape.analytics.flush();
2529
+ ductape.analytics.clearSession();
2530
+ `.trim(),
2531
+
2249
2532
  features: `
2250
2533
  DUCTAPE FEATURES
2251
2534
 
@@ -2933,9 +3216,15 @@ SESSIONS SERVICE
2933
3216
  const { token } = await ductape.sessions.refresh({ refreshToken: '...' });
2934
3217
 
2935
3218
  ANALYTICS SERVICE
2936
- ductape.analytics.pageview({ page: '/dashboard' });
2937
- ductape.analytics.track('button_click', { button: 'sign-up' });
2938
- ductape.analytics.identify({ userId: 'u_123', traits: { plan: 'pro' } });
3219
+ ductape.analytics.identify('player-session:eyJ...'); // full Ductape session token
3220
+ await ductape.analytics.pageview({ path: '/dashboard', title: document.title });
3221
+ await ductape.analytics.track({
3222
+ event: 'button_clicked',
3223
+ properties: { button: 'sign-up' },
3224
+ });
3225
+ await ductape.analytics.flush();
3226
+ ductape.analytics.clearSession(); // logout/account switch
3227
+ See ductape_docs({ topic: "frontend-analytics" }) before enabling auto-capture.
2939
3228
 
2940
3229
  FRAMEWORK-SPECIFIC PACKAGES
2941
3230
  For React and Vue projects, use the dedicated packages instead of managing the client manually:
@@ -3037,13 +3326,15 @@ AGENT HOOKS
3037
3326
  useAgentSignal(hookOptions?) → { mutate, isLoading, error }
3038
3327
 
3039
3328
  BROKER HOOKS
3040
- useBroker(broker, options?)
3329
+ useBroker()
3041
3330
  → { isConnected, isConnecting, error, connect, disconnect }
3331
+ connect({ broker, session?, product?, env? }) forwards the complete options object to
3332
+ @ductape/client. Pass session for player-scoped authorization.
3042
3333
  useBrokerPublish(hookOptions?)
3043
3334
  → { mutate({ topic, message, headers?, key? }), isLoading, error, data }
3044
3335
  useBrokerSubscription(subscribeOptions, hookOptions?)
3045
3336
  → { data: BrokerMessage[], isSubscribed, error, unsubscribe, resubscribe }
3046
- subscribeOptions: { topic, group? }
3337
+ subscribeOptions: { broker?, topic, group?, session?, product?, env? }
3047
3338
 
3048
3339
  GRAPH HOOKS
3049
3340
  useGraph(graphTag) → { isConnected, isConnecting, error, connect, disconnect }
@@ -3113,7 +3404,10 @@ ACTIONS HOOKS
3113
3404
  useActionRun(hookOptions?) → { mutate, isLoading, error, data }
3114
3405
 
3115
3406
  ANALYTICS HOOK
3116
- useAnalytics() → { pageview, track, identify }
3407
+ useAnalytics() → { pageview, track, identify, visitorId, enableAutoCapture, flush }
3408
+ The currently installed hook does NOT return clearSession or disableAutoCapture.
3409
+ Use useDuctape().client.analytics.clearSession()/disableAutoCapture(), or verify a newer installed
3410
+ package export before generating those hook calls. See ductape_docs({ topic: "frontend-analytics" }).
3117
3411
 
3118
3412
  hookOptions pattern (applies to all hooks):
3119
3413
  enabled? boolean — false skips auto-fetch/subscribe
@@ -3283,7 +3577,10 @@ ACTIONS COMPOSABLES
3283
3577
  useActionRun(composableOptions?) → { mutate, isLoading, error, data }
3284
3578
 
3285
3579
  ANALYTICS COMPOSABLE
3286
- useAnalytics() → { pageview, track, identify }
3580
+ useAnalytics() → { pageview, track, identify, visitorId, enableAutoCapture, flush }
3581
+ The currently installed composable does NOT return clearSession or disableAutoCapture.
3582
+ Use useDuctape().client.analytics.clearSession()/disableAutoCapture(), or verify a newer installed
3583
+ package export before generating those composable calls. See ductape_docs({ topic: "frontend-analytics" }).
3287
3584
 
3288
3585
  composableOptions pattern (applies to all composables):
3289
3586
  enabled? boolean — false skips auto-fetch/subscribe
@@ -3682,7 +3979,7 @@ async function main() {
3682
3979
  'index strategy, operation types) that should be confirmed with the user first.\n\n' +
3683
3980
  'Available topics: transactions, presave, triggers, aggregations, migrations, indexes, performance, actions, ' +
3684
3981
  'graphs, storage, cloud, vector, warehouse, secrets, apps, products, sessions, caches, ' +
3685
- 'notifications, resilience, features, events, logs, client, react, vue',
3982
+ 'notifications, resilience, features, events, logs, frontend, frontend-analytics, client, react, vue',
3686
3983
  inputSchema: docsInputSchema,
3687
3984
  },
3688
3985
  docsHandler,