@ductape/mcp 0.3.4 → 0.3.5

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.
@@ -18,6 +18,7 @@ export interface FrontendAnalyticsValidationResult {
18
18
  framework: 'react' | 'vue' | 'client';
19
19
  decision?: AnalyticsDecision;
20
20
  signals: Record<string, boolean>;
21
+ evidence: Record<string, string[]>;
21
22
  errors: string[];
22
23
  warnings: string[];
23
24
  }>;
@@ -1 +1 @@
1
- {"version":3,"file":"frontend-analytics-validator.d.ts","sourceRoot":"","sources":["../src/frontend-analytics-validator.ts"],"names":[],"mappings":"AASA,MAAM,MAAM,uBAAuB,GAAG,SAAS,GAAG,UAAU,GAAG,YAAY,CAAC;AAE5E,UAAU,iBAAiB;IACzB,MAAM,CAAC,EAAE,uBAAuB,CAAC;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,UAAU,GAAG,UAAU,CAAC;IACtC,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAMD,MAAM,WAAW,iCAAiC;IAChD,KAAK,EAAE,OAAO,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE;QAAE,YAAY,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;IACpE,YAAY,EAAE,KAAK,CAAC;QAClB,IAAI,EAAE,MAAM,CAAC;QACb,SAAS,EAAE,OAAO,GAAG,KAAK,GAAG,QAAQ,CAAC;QACtC,QAAQ,CAAC,EAAE,iBAAiB,CAAC;QAC7B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QACjC,MAAM,EAAE,MAAM,EAAE,CAAC;QACjB,QAAQ,EAAE,MAAM,EAAE,CAAC;KACpB,CAAC,CAAC;IACH,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB;AA0DD,wBAAgB,gCAAgC,CAC9C,UAAU,EAAE,MAAM,GACjB,iCAAiC,CAkGnC"}
1
+ {"version":3,"file":"frontend-analytics-validator.d.ts","sourceRoot":"","sources":["../src/frontend-analytics-validator.ts"],"names":[],"mappings":"AASA,MAAM,MAAM,uBAAuB,GAAG,SAAS,GAAG,UAAU,GAAG,YAAY,CAAC;AAE5E,UAAU,iBAAiB;IACzB,MAAM,CAAC,EAAE,uBAAuB,CAAC;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,UAAU,GAAG,UAAU,CAAC;IACtC,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAMD,MAAM,WAAW,iCAAiC;IAChD,KAAK,EAAE,OAAO,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE;QAAE,YAAY,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;IACpE,YAAY,EAAE,KAAK,CAAC;QAClB,IAAI,EAAE,MAAM,CAAC;QACb,SAAS,EAAE,OAAO,GAAG,KAAK,GAAG,QAAQ,CAAC;QACtC,QAAQ,CAAC,EAAE,iBAAiB,CAAC;QAC7B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QACjC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;QACnC,MAAM,EAAE,MAAM,EAAE,CAAC;QACjB,QAAQ,EAAE,MAAM,EAAE,CAAC;KACpB,CAAC,CAAC;IACH,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB;AA0DD,wBAAgB,gCAAgC,CAC9C,UAAU,EAAE,MAAM,GACjB,iCAAiC,CA2HnC"}
@@ -47,17 +47,18 @@ function packageFramework(manifest) {
47
47
  return 'client';
48
48
  return undefined;
49
49
  }
50
- function scanSource(applicationRoot) {
50
+ function scanSource(projectRoot, roots) {
51
51
  const chunks = [];
52
- walk(applicationRoot, (file) => {
53
- if (!SOURCE_EXTENSIONS.has(path.extname(file)))
54
- return;
55
- const stats = fs.statSync(file);
56
- if (stats.size > MAX_SOURCE_BYTES)
57
- return;
58
- chunks.push(fs.readFileSync(file, 'utf8'));
59
- });
60
- return chunks.join('\n');
52
+ for (const sourceRoot of roots)
53
+ walk(sourceRoot, (file) => {
54
+ if (!SOURCE_EXTENSIONS.has(path.extname(file)))
55
+ return;
56
+ const stats = fs.statSync(file);
57
+ if (stats.size > MAX_SOURCE_BYTES)
58
+ return;
59
+ chunks.push({ file: normalizedRelative(projectRoot, file), source: fs.readFileSync(file, 'utf8') });
60
+ });
61
+ return chunks;
61
62
  }
62
63
  function normalizedRelative(root, target) {
63
64
  return path.relative(root, target).split(path.sep).join('/') || '.';
@@ -87,6 +88,15 @@ export function validateFrontendAnalyticsProject(projectDir) {
87
88
  if (path.basename(file) === 'package.json')
88
89
  packageFiles.push(file);
89
90
  });
91
+ const packages = new Map();
92
+ for (const manifestFile of packageFiles) {
93
+ try {
94
+ const manifest = readJson(manifestFile);
95
+ if (typeof manifest.name === 'string')
96
+ packages.set(manifest.name, { root: path.dirname(manifestFile), manifest });
97
+ }
98
+ catch { /* Invalid manifests are ignored as application candidates below. */ }
99
+ }
90
100
  const applications = packageFiles.flatMap((manifestFile) => {
91
101
  let manifest;
92
102
  try {
@@ -100,19 +110,39 @@ export function validateFrontendAnalyticsProject(projectDir) {
100
110
  return [];
101
111
  const applicationRoot = path.dirname(manifestFile);
102
112
  const applicationPath = normalizedRelative(root, applicationRoot);
103
- const source = scanSource(applicationRoot);
113
+ const sourceRoots = [applicationRoot];
114
+ const visited = new Set([applicationRoot]);
115
+ for (let index = 0; index < sourceRoots.length; index += 1) {
116
+ const currentRoot = sourceRoots[index];
117
+ const current = index === 0
118
+ ? manifest
119
+ : [...packages.values()].find((entry) => entry.root === currentRoot)?.manifest;
120
+ if (!current)
121
+ continue;
122
+ const dependencies = { ...(current.dependencies ?? {}), ...(current.devDependencies ?? {}) };
123
+ const directSource = scanSource(root, [currentRoot]).map((item) => item.source).join('\n');
124
+ for (const dependencyName of Object.keys(dependencies)) {
125
+ const dependency = packages.get(dependencyName);
126
+ if (!dependency || visited.has(dependency.root))
127
+ continue;
128
+ const escaped = dependencyName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
129
+ if (!new RegExp(`['\"]${escaped}(?:/[^'\"]*)?['\"]`).test(directSource))
130
+ continue;
131
+ visited.add(dependency.root);
132
+ sourceRoots.push(dependency.root);
133
+ }
134
+ }
135
+ const files = scanSource(root, sourceRoots);
104
136
  const decision = decisions.applications?.[applicationPath];
105
- const signals = {
106
- provider: /DuctapeProvider/.test(source),
107
- authenticatedSessions: /useSession|\.sessions\.|sessionToken|refreshToken/.test(source),
108
- analytics: /useAnalytics|\.analytics\./.test(source),
109
- identify: /(?:analytics\.)?identify\s*\(/.test(source),
110
- clearSession: /(?:analytics\.)?clearSession\s*\(/.test(source),
111
- pageview: /(?:analytics\.)?pageview\s*\(/.test(source),
112
- namedEvents: /(?:analytics\.)?track\s*\(/.test(source),
113
- autoCapture: /enableAutoCapture\s*\(/.test(source),
114
- clientFailure: /client[_\-. ]error|frontend[_\-. ]error|error_boundary|unhandledrejection/i.test(source),
137
+ const patterns = {
138
+ provider: /DuctapeProvider/, authenticatedSessions: /useSession|\.sessions\.|sessionToken|refreshToken/,
139
+ analytics: /useAnalytics|\.analytics\./, identify: /(?:analytics\.)?identify\s*\(/,
140
+ clearSession: /(?:analytics\.)?clearSession\s*\(/, pageview: /(?:analytics\.)?pageview\s*\(/,
141
+ namedEvents: /(?:analytics\.)?track\s*\(/, autoCapture: /enableAutoCapture\s*\(/,
142
+ clientFailure: /client[_\-. ]error|frontend[_\-. ]error|error_boundary|unhandledrejection/i,
115
143
  };
144
+ const evidence = Object.fromEntries(Object.entries(patterns).map(([name, pattern]) => [name, files.filter((item) => pattern.test(item.source)).map((item) => item.file)]));
145
+ const signals = Object.fromEntries(Object.entries(evidence).map(([name, matches]) => [name, matches.length > 0]));
116
146
  const errors = [];
117
147
  const warnings = [];
118
148
  if (decisionError)
@@ -148,7 +178,7 @@ export function validateFrontendAnalyticsProject(projectDir) {
148
178
  warnings.push('No sanitized client-failure analytics signal was detected; review error boundaries and unhandled failures.');
149
179
  }
150
180
  }
151
- return [{ path: applicationPath, framework, decision, signals, errors, warnings }];
181
+ return [{ path: applicationPath, framework, decision, signals, evidence, errors, warnings }];
152
182
  });
153
183
  const errors = applications.reduce((count, app) => count + app.errors.length, 0);
154
184
  const warnings = applications.reduce((count, app) => count + app.warnings.length, 0);
package/dist/index.js CHANGED
@@ -1723,6 +1723,9 @@ const featuresProjectValidationInputSchema = z.object({
1723
1723
  const frontendAnalyticsProjectValidationInputSchema = z.object({
1724
1724
  project_dir: z.string().describe('Absolute path to a frontend project or monorepo. The validator is read-only and never modifies application code.'),
1725
1725
  });
1726
+ const sessionProjectValidationInputSchema = z.object({
1727
+ project_dir: z.string().describe('Absolute path to the project. Performs a read-only SDK session-propagation audit.'),
1728
+ });
1726
1729
  const readOnlyLocalAnnotations = {
1727
1730
  readOnlyHint: true,
1728
1731
  destructiveHint: false,
@@ -6106,6 +6109,10 @@ const frontendAnalyticsProjectValidationHandler = async (args) => {
6106
6109
  };
6107
6110
  }
6108
6111
  };
6112
+ const sessionProjectValidationHandler = async (args) => {
6113
+ const result = runCli('sessions validate --json', args.project_dir);
6114
+ return { content: [{ type: 'text', text: result.output || '(no output)' }], ...(result.success ? {} : { isError: true }) };
6115
+ };
6109
6116
  const cliInputSchema = z.object({
6110
6117
  command: z.string().describe('The ductape CLI command to run, without the leading "ductape" word. ' +
6111
6118
  'Examples: "products list", "products create --name \\"My Product\\" --tag my-product", ' +
@@ -6966,6 +6973,12 @@ async function main() {
6966
6973
  inputSchema: frontendAnalyticsProjectValidationInputSchema,
6967
6974
  annotations: readOnlyLocalAnnotations,
6968
6975
  }, frontendAnalyticsProjectValidationHandler);
6976
+ server.registerTool('ductape_sessions_validate_project', {
6977
+ title: 'Validate Ductape Session Propagation',
6978
+ description: 'Read-only project-wide audit. Every supported SDK call must explicitly pass a session, inherit it from Feature context, or carry a reasoned delegated/system marker.',
6979
+ inputSchema: sessionProjectValidationInputSchema,
6980
+ annotations: readOnlyLocalAnnotations,
6981
+ }, sessionProjectValidationHandler);
6969
6982
  server.registerTool('ductape_function_setup', {
6970
6983
  title: 'Ductape Portable Function Setup',
6971
6984
  description: 'Read-only: generate (without applying) the mandatory secure local + remote runtime setup for application functions used by Features. ' +
@@ -7217,6 +7230,7 @@ async function main() {
7217
7230
  server.tool('ductape_events_validate_project', eventsProjectValidationInputSchema.shape, readOnlyLocalAnnotations, eventsProjectValidationHandler);
7218
7231
  server.tool('ductape_features_validate_project', featuresProjectValidationInputSchema.shape, readOnlyLocalAnnotations, featuresProjectValidationHandler);
7219
7232
  server.tool('ductape_frontend_analytics_validate_project', frontendAnalyticsProjectValidationInputSchema.shape, readOnlyLocalAnnotations, frontendAnalyticsProjectValidationHandler);
7233
+ server.tool('ductape_sessions_validate_project', sessionProjectValidationInputSchema.shape, readOnlyLocalAnnotations, sessionProjectValidationHandler);
7220
7234
  server.tool('ductape_function_setup', portableFunctionSetupInputSchema.shape, portableFunctionSetupAnnotations, portableFunctionSetupHandler);
7221
7235
  server.tool('ductape_redis_setup', redisSetupInputSchema.shape, redisSetupHandler);
7222
7236
  server.tool('ductape_migration_plan', migrationInputSchema.shape, migrationHandler);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ductape/mcp",
3
- "version": "0.3.4",
3
+ "version": "0.3.5",
4
4
  "description": "MCP server that exposes Ductape SDK operations via the backend proxy",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",