@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.
|
@@ -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,
|
|
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(
|
|
50
|
+
function scanSource(projectRoot, roots) {
|
|
51
51
|
const chunks = [];
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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
|
|
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
|
|
106
|
-
provider: /DuctapeProvider
|
|
107
|
-
|
|
108
|
-
analytics: /
|
|
109
|
-
|
|
110
|
-
|
|
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);
|