@mrfentmen/skincare-ingredient-stack-check-mcp 0.0.0-stage → 1.0.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/README.md CHANGED
@@ -1,3 +1,96 @@
1
- # Temporary Holding Version
1
+ # Skincare Ingredient Stack Audit
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Audit a skincare routine for active-ingredient overlap, missing steps, duplicate products and shelf-life risk. Real local JSON analysis, 25 MCP tools, no paywall.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install -g @mrfentmen/skincare-ingredient-stack-check-mcp
9
+ ```
10
+
11
+ ## Use with an MCP client
12
+
13
+ ```json
14
+ {
15
+ "mcpServers": {
16
+ "skincare-ingredient-stack-check-mcp": {
17
+ "command": "npx",
18
+ "args": ["-y", "@mrfentmen/skincare-ingredient-stack-check-mcp"]
19
+ }
20
+ }
21
+ }
22
+ ```
23
+
24
+ ## Input
25
+
26
+ Pass an array, an object with an `items` array, or a JSON file path. Every item needs a name and category. Cost, quantity, dates, condition, tags and notes are optional.
27
+
28
+ ```json
29
+ {
30
+ "items": [
31
+ {
32
+ "id": "skin-cleanser",
33
+ "name": "Gentle gel cleanser",
34
+ "category": "cleanser",
35
+ "tags": [
36
+ "gentle"
37
+ ],
38
+ "cost": 18,
39
+ "quantity": 1,
40
+ "acquired": "2026-05-01",
41
+ "expires": "2027-05-01",
42
+ "condition": "good"
43
+ },
44
+ {
45
+ "id": "skin-retinol",
46
+ "name": "Retinol serum",
47
+ "category": "treatment",
48
+ "tags": [
49
+ "retinol"
50
+ ],
51
+ "cost": 40,
52
+ "quantity": 1,
53
+ "acquired": "2026-08-01",
54
+ "expires": "2027-02-01",
55
+ "condition": "good"
56
+ }
57
+ ]
58
+ }
59
+ ```
60
+
61
+ ## Tools (25)
62
+
63
+ | Tool | What it does |
64
+ | --- | --- |
65
+ | `inventory_ingredient_stack` | Count items by category and show spend, quantity, freshness and the primary metric. |
66
+ | `score_ingredient_stack_readiness` | Score readiness from 0 to 100 with coverage, condition, data and metric components. |
67
+ | `audit_ingredient_stack_coverage` | Show category coverage ratios and unknown categories. |
68
+ | `flag_ingredient_stack_gaps` | List missing categories, expired items, expiring items and dependency gaps. |
69
+ | `rank_ingredient_stack_risks` | Rank coverage, expiry, budget, duplicate, condition and data risks. |
70
+ | `check_ingredient_stack_constraints` | Check item cap, budget, coverage, expiry and metric targets. |
71
+ | `compare_ingredient_stack_baseline` | Compare category counts with the configured baseline. |
72
+ | `estimate_ingredient_stack_active_overlap` | Calculate the primary metric for every item with mean and pass count. |
73
+ | `find_ingredient_stack_quick_wins` | Find low-effort fixes from small gaps, missing data, expiry and duplicates. |
74
+ | `map_ingredient_stack_dependencies` | Map dependency groups and show which ones are missing. |
75
+ | `test_ingredient_stack_edge_cases` | Test empty, single-item, duplicate-id, zero-cost, expiry and unknown-category cases. |
76
+ | `summarize_ingredient_stack_findings` | Summarize counts, top gaps, top risks and data issues. |
77
+ | `prioritize_ingredient_stack_fixes` | Rank fixes by impact divided by effort. |
78
+ | `validate_ingredient_stack_inputs` | Validate arbitrary JSON and report schema errors and warnings. |
79
+ | `detect_ingredient_stack_duplicates` | Group items with the same category and normalized name. |
80
+ | `measure_ingredient_stack_variance` | Measure metric spread, category variance and tag diversity. |
81
+ | `benchmark_ingredient_stack_options` | Benchmark lean, balanced and full sizes against readiness and data targets. |
82
+ | `explain_ingredient_stack_tradeoffs` | Explain size, cost, freshness and data-quality tradeoffs. |
83
+ | `draft_ingredient_stack_checklist` | Draft a checklist from gaps, high risks, missing data and budget. |
84
+ | `export_ingredient_stack_audit` | Export the full audit with metrics, gaps, risks, constraints and next steps. |
85
+ | `track_ingredient_stack_changes` | Compare two snapshots by item id and list added, removed and changed items. |
86
+ | `review_ingredient_stack_history` | Review dated snapshots and show readiness, cost and metric trends. |
87
+ | `score_ingredient_stack_confidence` | Score trust from data completeness, item count and field coverage. |
88
+ | `recommend_ingredient_stack_next_steps` | Recommend the next five actions with quick wins. |
89
+ | `open_ingredient_stack_questions` | List open questions that block a better audit. |
90
+
91
+ ## Rules
92
+
93
+ - Local computation only. No network calls, no telemetry, no paywall.
94
+ - Missing dates, costs and quantities are reported, not invented.
95
+ - Dates use `YYYY-MM-DD`.
96
+ - MIT licence.
@@ -0,0 +1,2 @@
1
+ import type { ServerConfig } from "./engine.js";
2
+ export declare const config: ServerConfig;
package/dist/config.js ADDED
@@ -0,0 +1,170 @@
1
+ export const config = {
2
+ "name": "skincare-ingredient-stack-check-mcp",
3
+ "title": "Skincare Ingredient Stack Audit",
4
+ "description": "Audit a skincare routine for active-ingredient overlap, missing steps, duplicate products and shelf-life risk. Real local JSON analysis, 25 MCP tools, no paywall.",
5
+ "prefix": "ingredient_stack",
6
+ "entity": "skincare product",
7
+ "categories": [
8
+ {
9
+ "id": "cleanser",
10
+ "label": "Cleansers",
11
+ "baseline": 2
12
+ },
13
+ {
14
+ "id": "treatment",
15
+ "label": "Treatments",
16
+ "baseline": 3
17
+ },
18
+ {
19
+ "id": "moisturizer",
20
+ "label": "Moisturizers",
21
+ "baseline": 2
22
+ },
23
+ {
24
+ "id": "spf",
25
+ "label": "SPF",
26
+ "baseline": 2
27
+ },
28
+ {
29
+ "id": "tool",
30
+ "label": "Tools",
31
+ "baseline": 1
32
+ }
33
+ ],
34
+ "metric": {
35
+ "id": "active_overlap",
36
+ "label": "Active overlap",
37
+ "unit": "duplicate tags",
38
+ "target": 0,
39
+ "direction": "lower"
40
+ },
41
+ "target": {
42
+ "maxItems": 20,
43
+ "budget": 500,
44
+ "coverage": 0.8,
45
+ "expiryDays": 60
46
+ },
47
+ "dependencies": [
48
+ {
49
+ "id": "cleanse",
50
+ "label": "Cleansing step",
51
+ "categories": [
52
+ "cleanser"
53
+ ]
54
+ },
55
+ {
56
+ "id": "treat",
57
+ "label": "Treatment step",
58
+ "categories": [
59
+ "treatment"
60
+ ]
61
+ },
62
+ {
63
+ "id": "moisturize",
64
+ "label": "Moisturizing step",
65
+ "categories": [
66
+ "moisturizer"
67
+ ]
68
+ },
69
+ {
70
+ "id": "protect",
71
+ "label": "Sun protection",
72
+ "categories": [
73
+ "spf"
74
+ ]
75
+ }
76
+ ],
77
+ "sample": [
78
+ {
79
+ "id": "skin-cleanser",
80
+ "name": "Gentle gel cleanser",
81
+ "category": "cleanser",
82
+ "tags": [
83
+ "gentle"
84
+ ],
85
+ "cost": 18,
86
+ "quantity": 1,
87
+ "acquired": "2026-05-01",
88
+ "expires": "2027-05-01",
89
+ "condition": "good"
90
+ },
91
+ {
92
+ "id": "skin-retinol",
93
+ "name": "Retinol serum",
94
+ "category": "treatment",
95
+ "tags": [
96
+ "retinol"
97
+ ],
98
+ "cost": 40,
99
+ "quantity": 1,
100
+ "acquired": "2026-08-01",
101
+ "expires": "2027-02-01",
102
+ "condition": "good"
103
+ },
104
+ {
105
+ "id": "skin-vitc",
106
+ "name": "Vitamin C serum",
107
+ "category": "treatment",
108
+ "tags": [
109
+ "vitamin-c"
110
+ ],
111
+ "cost": 35,
112
+ "quantity": 1,
113
+ "acquired": "2026-09-01",
114
+ "expires": "2027-03-01",
115
+ "condition": "good"
116
+ },
117
+ {
118
+ "id": "skin-niacinamide",
119
+ "name": "Niacinamide serum",
120
+ "category": "treatment",
121
+ "tags": [
122
+ "niacinamide"
123
+ ],
124
+ "cost": 22,
125
+ "quantity": 1,
126
+ "acquired": "2026-07-01",
127
+ "expires": "2027-01-01",
128
+ "condition": "good"
129
+ },
130
+ {
131
+ "id": "skin-moisturizer",
132
+ "name": "Ceramide cream",
133
+ "category": "moisturizer",
134
+ "tags": [
135
+ "ceramide"
136
+ ],
137
+ "cost": 28,
138
+ "quantity": 1,
139
+ "acquired": "2026-06-01",
140
+ "expires": "2027-06-01",
141
+ "condition": "good"
142
+ },
143
+ {
144
+ "id": "skin-spf",
145
+ "name": "Mineral SPF 50",
146
+ "category": "spf",
147
+ "tags": [
148
+ "spf"
149
+ ],
150
+ "cost": 24,
151
+ "quantity": 1,
152
+ "acquired": "2026-05-01",
153
+ "expires": "2027-05-01",
154
+ "condition": "good"
155
+ },
156
+ {
157
+ "id": "skin-retinol-night",
158
+ "name": "Retinol night cream",
159
+ "category": "treatment",
160
+ "tags": [
161
+ "retinol"
162
+ ],
163
+ "cost": 38,
164
+ "quantity": 1,
165
+ "acquired": "2026-04-01",
166
+ "expires": "2026-12-01",
167
+ "condition": "fair"
168
+ }
169
+ ]
170
+ };
@@ -0,0 +1,270 @@
1
+ import { z } from "zod";
2
+ export declare const CONDITIONS: readonly ["new", "good", "fair", "worn"];
3
+ export type Condition = (typeof CONDITIONS)[number];
4
+ export interface CategoryConfig {
5
+ id: string;
6
+ label: string;
7
+ baseline: number;
8
+ }
9
+ export interface MetricConfig {
10
+ id: string;
11
+ label: string;
12
+ unit: string;
13
+ target: number;
14
+ direction: "lower" | "higher";
15
+ }
16
+ export interface DependencyGroup {
17
+ id: string;
18
+ label: string;
19
+ categories: string[];
20
+ }
21
+ export interface ServerConfig {
22
+ name: string;
23
+ title: string;
24
+ description: string;
25
+ prefix: string;
26
+ entity: string;
27
+ categories: CategoryConfig[];
28
+ metric: MetricConfig;
29
+ target: {
30
+ maxItems: number;
31
+ budget: number | null;
32
+ coverage: number;
33
+ expiryDays: number;
34
+ };
35
+ dependencies: DependencyGroup[];
36
+ sample: RawItem[];
37
+ }
38
+ export declare const itemSchema: z.ZodObject<{
39
+ id: z.ZodOptional<z.ZodString>;
40
+ name: z.ZodString;
41
+ category: z.ZodString;
42
+ tags: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
43
+ cost: z.ZodOptional<z.ZodNumber>;
44
+ quantity: z.ZodOptional<z.ZodNumber>;
45
+ acquired: z.ZodOptional<z.ZodString>;
46
+ expires: z.ZodOptional<z.ZodString>;
47
+ lastUsed: z.ZodOptional<z.ZodString>;
48
+ condition: z.ZodOptional<z.ZodEnum<["new", "good", "fair", "worn"]>>;
49
+ notes: z.ZodOptional<z.ZodString>;
50
+ }, "strip", z.ZodTypeAny, {
51
+ name: string;
52
+ category: string;
53
+ id?: string | undefined;
54
+ tags?: string[] | undefined;
55
+ cost?: number | undefined;
56
+ quantity?: number | undefined;
57
+ acquired?: string | undefined;
58
+ expires?: string | undefined;
59
+ lastUsed?: string | undefined;
60
+ condition?: "new" | "good" | "fair" | "worn" | undefined;
61
+ notes?: string | undefined;
62
+ }, {
63
+ name: string;
64
+ category: string;
65
+ id?: string | undefined;
66
+ tags?: string[] | undefined;
67
+ cost?: number | undefined;
68
+ quantity?: number | undefined;
69
+ acquired?: string | undefined;
70
+ expires?: string | undefined;
71
+ lastUsed?: string | undefined;
72
+ condition?: "new" | "good" | "fair" | "worn" | undefined;
73
+ notes?: string | undefined;
74
+ }>;
75
+ export type RawItem = z.infer<typeof itemSchema>;
76
+ export declare const targetSchema: z.ZodObject<{
77
+ maxItems: z.ZodOptional<z.ZodNumber>;
78
+ budget: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
79
+ coverage: z.ZodOptional<z.ZodNumber>;
80
+ expiryDays: z.ZodOptional<z.ZodNumber>;
81
+ }, "strip", z.ZodTypeAny, {
82
+ maxItems?: number | undefined;
83
+ budget?: number | null | undefined;
84
+ coverage?: number | undefined;
85
+ expiryDays?: number | undefined;
86
+ }, {
87
+ maxItems?: number | undefined;
88
+ budget?: number | null | undefined;
89
+ coverage?: number | undefined;
90
+ expiryDays?: number | undefined;
91
+ }>;
92
+ export type TargetInput = z.infer<typeof targetSchema>;
93
+ export interface TargetProfile {
94
+ maxItems: number;
95
+ budget: number | null;
96
+ coverage: number;
97
+ expiryDays: number;
98
+ }
99
+ export interface DataIssue {
100
+ code: string;
101
+ severity: "error" | "warning";
102
+ message: string;
103
+ itemId?: string;
104
+ }
105
+ export interface CollectionItem {
106
+ id: string;
107
+ name: string;
108
+ category: string;
109
+ tags: string[];
110
+ cost: number | null;
111
+ quantity: number | null;
112
+ acquired: string | null;
113
+ expires: string | null;
114
+ lastUsed: string | null;
115
+ condition: Condition;
116
+ notes: string | null;
117
+ dataFields: number;
118
+ }
119
+ export interface LoadedCollection {
120
+ items: CollectionItem[];
121
+ issues: DataIssue[];
122
+ source: string;
123
+ rawCount: number;
124
+ }
125
+ export interface Gap {
126
+ type: "category" | "expiry" | "dependency";
127
+ key: string;
128
+ missing: number;
129
+ severity: "high" | "medium" | "low";
130
+ message: string;
131
+ }
132
+ export interface Risk {
133
+ code: string;
134
+ severity: "critical" | "high" | "medium" | "low";
135
+ message: string;
136
+ impact: number;
137
+ }
138
+ export interface Constraint {
139
+ key: string;
140
+ label: string;
141
+ pass: boolean;
142
+ actual: unknown;
143
+ target: unknown;
144
+ message: string;
145
+ }
146
+ export interface DuplicateGroup {
147
+ key: string;
148
+ count: number;
149
+ ids: string[];
150
+ names: string[];
151
+ }
152
+ export interface Fix {
153
+ action: string;
154
+ impact: number;
155
+ effort: number;
156
+ score: number;
157
+ source: string;
158
+ }
159
+ export interface Analysis {
160
+ target: TargetProfile;
161
+ itemCount: number;
162
+ items: CollectionItem[];
163
+ categoryCounts: Record<string, number>;
164
+ unknownCategories: string[];
165
+ totalCost: number;
166
+ knownCostCount: number;
167
+ totalQuantity: number;
168
+ knownQuantityCount: number;
169
+ expired: CollectionItem[];
170
+ expiring: CollectionItem[];
171
+ metricRows: Array<{
172
+ id: string;
173
+ name: string;
174
+ value: number | null;
175
+ }>;
176
+ metricMean: number;
177
+ metricPassCount: number;
178
+ metricKnownCount: number;
179
+ categoryCoverage: Record<string, number>;
180
+ coverageScore: number;
181
+ conditionScore: number;
182
+ dataCompleteness: number;
183
+ readiness: number;
184
+ confidence: number;
185
+ gaps: Gap[];
186
+ risks: Risk[];
187
+ constraints: Constraint[];
188
+ duplicates: DuplicateGroup[];
189
+ variance: {
190
+ metric: {
191
+ count: number;
192
+ mean: number;
193
+ variance: number;
194
+ stddev: number;
195
+ min: number;
196
+ max: number;
197
+ };
198
+ categories: {
199
+ mean: number;
200
+ variance: number;
201
+ };
202
+ tagDiversity: number;
203
+ };
204
+ benchmark: {
205
+ rows: Array<{
206
+ metric: string;
207
+ value: number;
208
+ target: string;
209
+ pass: boolean;
210
+ }>;
211
+ passed: number;
212
+ total: number;
213
+ verdict: "strong" | "mixed" | "thin";
214
+ };
215
+ options: Array<{
216
+ name: string;
217
+ maxItems: number;
218
+ expected: Record<string, number>;
219
+ fit: number;
220
+ }>;
221
+ tradeoffs: Array<{
222
+ title: string;
223
+ current: string;
224
+ note: string;
225
+ }>;
226
+ checklist: Array<{
227
+ item: string;
228
+ done: boolean;
229
+ }>;
230
+ quickWins: Fix[];
231
+ fixes: Fix[];
232
+ nextSteps: Fix[];
233
+ questions: string[];
234
+ edgeCases: string[];
235
+ dataIssues: DataIssue[];
236
+ summary: {
237
+ items: number;
238
+ readiness: number;
239
+ confidence: number;
240
+ totalCost: number;
241
+ metricMean: number;
242
+ gaps: number;
243
+ risks: number;
244
+ duplicates: number;
245
+ dataIssues: number;
246
+ };
247
+ }
248
+ export declare function loadCollection(input: {
249
+ items?: unknown[];
250
+ content?: string;
251
+ path?: string;
252
+ cwd?: string;
253
+ }): Promise<LoadedCollection>;
254
+ export declare function normalizeTarget(input?: TargetInput): TargetProfile;
255
+ export declare function analyze(loaded: LoadedCollection, targetInput?: TargetProfile): Analysis;
256
+ export declare function changeSet(before: LoadedCollection, after: LoadedCollection): {
257
+ counts: {
258
+ before: number;
259
+ after: number;
260
+ added: number;
261
+ removed: number;
262
+ changed: number;
263
+ };
264
+ added: CollectionItem[];
265
+ removed: CollectionItem[];
266
+ changed: {
267
+ before: CollectionItem | undefined;
268
+ after: CollectionItem;
269
+ }[];
270
+ };