@kynver-app/mcp-estimator 0.2.8 → 0.2.11

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.
Files changed (40) hide show
  1. package/dist/lib/mcp-sse-server.d.ts +15 -0
  2. package/dist/lib/mcp-sse-server.js +103 -2
  3. package/dist/server.js +16 -0
  4. package/dist/tools/catalog-tools.js +1 -1
  5. package/dist/tools/email-pdf-tools.d.ts +45 -0
  6. package/dist/tools/email-pdf-tools.js +21 -0
  7. package/dist/tools/intake-tools.d.ts +463 -0
  8. package/dist/tools/intake-tools.js +82 -0
  9. package/dist/tools/price-catalog-tools.d.ts +96 -0
  10. package/dist/tools/price-catalog-tools.js +72 -0
  11. package/dist/tools/remodel-deliverable-tools.d.ts +1868 -0
  12. package/dist/tools/remodel-deliverable-tools.js +138 -0
  13. package/dist/tools/remodel-estimate-tools.d.ts +525 -0
  14. package/dist/tools/remodel-estimate-tools.js +71 -0
  15. package/dist/tools/takeoff-tools.d.ts +274 -0
  16. package/dist/tools/takeoff-tools.js +38 -4
  17. package/dist/tools/vision-tools.js +2 -2
  18. package/package.json +6 -1
  19. package/src/index.ts +0 -18
  20. package/src/lib/kynver-client.ts +0 -136
  21. package/src/lib/mcp-sse-server.ts +0 -249
  22. package/src/lib/package-version.ts +0 -55
  23. package/src/server.ts +0 -141
  24. package/src/sse.ts +0 -10
  25. package/src/tools/catalog-tools.ts +0 -217
  26. package/src/tools/client-tools.ts +0 -114
  27. package/src/tools/company-tools.ts +0 -108
  28. package/src/tools/email-pdf-tools.ts +0 -27
  29. package/src/tools/estimate-tools.ts +0 -281
  30. package/src/tools/intake-tools.ts +0 -95
  31. package/src/tools/photo-tools.ts +0 -60
  32. package/src/tools/price-catalog-tools.ts +0 -80
  33. package/src/tools/remodel-deliverable-tools.ts +0 -163
  34. package/src/tools/remodel-estimate-tools.ts +0 -80
  35. package/src/tools/stats-pdf-tools.ts +0 -80
  36. package/src/tools/takeoff-tools.ts +0 -319
  37. package/src/tools/template-tools.ts +0 -80
  38. package/src/tools/version-tools.ts +0 -39
  39. package/src/tools/vision-tools.ts +0 -48
  40. package/tsconfig.json +0 -15
@@ -0,0 +1,71 @@
1
+ import { z } from "zod";
2
+ import * as client from "../lib/kynver-client.js";
3
+ const scopeSchema = z.object({
4
+ key: z.string(),
5
+ label: z.string().optional(),
6
+ quantity: z.number().optional(),
7
+ unit: z.string().optional(),
8
+ serviceCode: z.string().optional(),
9
+ budgetLump: z.number().optional(),
10
+ notes: z.string().optional(),
11
+ });
12
+ const buildRemodelSchema = z.object({
13
+ intake: z.object({
14
+ projectName: z.string().optional(),
15
+ scopes: z.array(scopeSchema),
16
+ alternates: z
17
+ .array(z.object({
18
+ key: z.string(),
19
+ label: z.string(),
20
+ scopeKeys: z.array(z.string()).optional(),
21
+ lumpAdd: z.number().optional(),
22
+ notes: z.string().optional(),
23
+ }))
24
+ .optional(),
25
+ includeStandardAlternates: z.boolean().optional(),
26
+ expandScopeComponents: z.boolean().optional(),
27
+ exclusions: z.array(z.string()).optional(),
28
+ assumptions: z.array(z.string()).optional(),
29
+ notes: z.string().optional(),
30
+ }),
31
+ takeoffRows: z
32
+ .array(z.object({
33
+ id: z.string().optional(),
34
+ description: z.string(),
35
+ quantity: z.number(),
36
+ unit: z.string(),
37
+ serviceCode: z.string().nullable().optional(),
38
+ materialType: z.string().nullable().optional(),
39
+ }))
40
+ .optional(),
41
+ overrides: z
42
+ .array(z.object({
43
+ scopeKey: z.string().optional(),
44
+ serviceCode: z.string().optional(),
45
+ description: z.string().optional(),
46
+ quantity: z.number().optional(),
47
+ unitMaterial: z.number().optional(),
48
+ unitLabor: z.number().optional(),
49
+ lumpTotal: z.number().optional(),
50
+ notes: z.string().optional(),
51
+ }))
52
+ .optional(),
53
+ pricing: z
54
+ .object({
55
+ contingencyPercent: z.number().optional(),
56
+ markupPercent: z.number().optional(),
57
+ profitPercent: z.number().optional(),
58
+ })
59
+ .optional(),
60
+ });
61
+ export const buildRemodelEstimate = {
62
+ name: "estimator_build_remodel_estimate",
63
+ description: "Build a reviewable residential remodel estimate from normalized intake scopes, optional takeoff quantities, catalog prices, and overrides. Lump room scopes (kitchen, baths) auto-expand into a per-trade breakdown of line items (demolition, cabinetry, countertops, plumbing, electrical, appliances, flooring, tile, finishes) — each with its own scope-of-work description carried in notes — so client-facing output is itemized, not a single vague lump (set intake.expandScopeComponents=false for the legacy single-line behavior). Returns line items with provenance, alternates (both baths, upstairs expansion, roof, wall moving), a default+custom assumptions list, default+custom exclusions, contingency/markup/profit breakdown, and reviewMarkdown. Pass project-specific items via intake.assumptions and intake.exclusions. Does not persist — use estimator_generate_estimate_from_takeoff or estimator_create_repair after review. Room scope keys: kitchen, bath_full, bath_half, bath_primary, bath_secondary. Trade/quantity scope keys: cabinets, drywall, paint, framing, roof, flooring, electrical_kitchen, electrical_bath. Whole-project phase scope keys: general_conditions, permits, demolition, protection, final_clean.",
64
+ inputSchemaZod: buildRemodelSchema,
65
+ execute: async (args) => {
66
+ if (!client.hasApiKey())
67
+ return client.noApiKeyMessage();
68
+ const data = await client.post("/estimator/estimates/build-remodel", args);
69
+ return JSON.stringify(data);
70
+ },
71
+ };
@@ -32,18 +32,155 @@ declare const uploadTakeoffInputSchema: z.ZodObject<{
32
32
  fileName: z.ZodString;
33
33
  base64: z.ZodString;
34
34
  mimeType: z.ZodOptional<z.ZodString>;
35
+ remodelHints: z.ZodOptional<z.ZodObject<{
36
+ cabinetLinearFt: z.ZodOptional<z.ZodNumber>;
37
+ rooms: z.ZodOptional<z.ZodArray<z.ZodObject<{
38
+ name: z.ZodString;
39
+ lengthFt: z.ZodOptional<z.ZodNumber>;
40
+ widthFt: z.ZodOptional<z.ZodNumber>;
41
+ }, "strip", z.ZodTypeAny, {
42
+ name: string;
43
+ lengthFt?: number | undefined;
44
+ widthFt?: number | undefined;
45
+ }, {
46
+ name: string;
47
+ lengthFt?: number | undefined;
48
+ widthFt?: number | undefined;
49
+ }>, "many">>;
50
+ baths: z.ZodOptional<z.ZodArray<z.ZodObject<{
51
+ type: z.ZodOptional<z.ZodEnum<["full", "half", "three_quarter", "unknown"]>>;
52
+ count: z.ZodOptional<z.ZodNumber>;
53
+ label: z.ZodOptional<z.ZodString>;
54
+ }, "strip", z.ZodTypeAny, {
55
+ type?: "unknown" | "full" | "half" | "three_quarter" | undefined;
56
+ count?: number | undefined;
57
+ label?: string | undefined;
58
+ }, {
59
+ type?: "unknown" | "full" | "half" | "three_quarter" | undefined;
60
+ count?: number | undefined;
61
+ label?: string | undefined;
62
+ }>, "many">>;
63
+ flooringSqFt: z.ZodOptional<z.ZodNumber>;
64
+ drywallSqFt: z.ZodOptional<z.ZodNumber>;
65
+ paintSqFt: z.ZodOptional<z.ZodNumber>;
66
+ roofSqFt: z.ZodOptional<z.ZodNumber>;
67
+ flags: z.ZodOptional<z.ZodObject<{
68
+ wallMovingRisk: z.ZodOptional<z.ZodBoolean>;
69
+ electricalSurpriseRisk: z.ZodOptional<z.ZodBoolean>;
70
+ plumbingSurpriseRisk: z.ZodOptional<z.ZodBoolean>;
71
+ }, "strip", z.ZodTypeAny, {
72
+ wallMovingRisk?: boolean | undefined;
73
+ electricalSurpriseRisk?: boolean | undefined;
74
+ plumbingSurpriseRisk?: boolean | undefined;
75
+ }, {
76
+ wallMovingRisk?: boolean | undefined;
77
+ electricalSurpriseRisk?: boolean | undefined;
78
+ plumbingSurpriseRisk?: boolean | undefined;
79
+ }>>;
80
+ notes: z.ZodOptional<z.ZodString>;
81
+ }, "strip", z.ZodTypeAny, {
82
+ notes?: string | undefined;
83
+ cabinetLinearFt?: number | undefined;
84
+ rooms?: {
85
+ name: string;
86
+ lengthFt?: number | undefined;
87
+ widthFt?: number | undefined;
88
+ }[] | undefined;
89
+ baths?: {
90
+ type?: "unknown" | "full" | "half" | "three_quarter" | undefined;
91
+ count?: number | undefined;
92
+ label?: string | undefined;
93
+ }[] | undefined;
94
+ flooringSqFt?: number | undefined;
95
+ drywallSqFt?: number | undefined;
96
+ paintSqFt?: number | undefined;
97
+ roofSqFt?: number | undefined;
98
+ flags?: {
99
+ wallMovingRisk?: boolean | undefined;
100
+ electricalSurpriseRisk?: boolean | undefined;
101
+ plumbingSurpriseRisk?: boolean | undefined;
102
+ } | undefined;
103
+ }, {
104
+ notes?: string | undefined;
105
+ cabinetLinearFt?: number | undefined;
106
+ rooms?: {
107
+ name: string;
108
+ lengthFt?: number | undefined;
109
+ widthFt?: number | undefined;
110
+ }[] | undefined;
111
+ baths?: {
112
+ type?: "unknown" | "full" | "half" | "three_quarter" | undefined;
113
+ count?: number | undefined;
114
+ label?: string | undefined;
115
+ }[] | undefined;
116
+ flooringSqFt?: number | undefined;
117
+ drywallSqFt?: number | undefined;
118
+ paintSqFt?: number | undefined;
119
+ roofSqFt?: number | undefined;
120
+ flags?: {
121
+ wallMovingRisk?: boolean | undefined;
122
+ electricalSurpriseRisk?: boolean | undefined;
123
+ plumbingSurpriseRisk?: boolean | undefined;
124
+ } | undefined;
125
+ }>>;
35
126
  }, "strip", z.ZodTypeAny, {
36
127
  base64: string;
37
128
  projectId: string;
38
129
  kind: string;
39
130
  fileName: string;
40
131
  mimeType?: string | undefined;
132
+ remodelHints?: {
133
+ notes?: string | undefined;
134
+ cabinetLinearFt?: number | undefined;
135
+ rooms?: {
136
+ name: string;
137
+ lengthFt?: number | undefined;
138
+ widthFt?: number | undefined;
139
+ }[] | undefined;
140
+ baths?: {
141
+ type?: "unknown" | "full" | "half" | "three_quarter" | undefined;
142
+ count?: number | undefined;
143
+ label?: string | undefined;
144
+ }[] | undefined;
145
+ flooringSqFt?: number | undefined;
146
+ drywallSqFt?: number | undefined;
147
+ paintSqFt?: number | undefined;
148
+ roofSqFt?: number | undefined;
149
+ flags?: {
150
+ wallMovingRisk?: boolean | undefined;
151
+ electricalSurpriseRisk?: boolean | undefined;
152
+ plumbingSurpriseRisk?: boolean | undefined;
153
+ } | undefined;
154
+ } | undefined;
41
155
  }, {
42
156
  base64: string;
43
157
  projectId: string;
44
158
  kind: string;
45
159
  fileName: string;
46
160
  mimeType?: string | undefined;
161
+ remodelHints?: {
162
+ notes?: string | undefined;
163
+ cabinetLinearFt?: number | undefined;
164
+ rooms?: {
165
+ name: string;
166
+ lengthFt?: number | undefined;
167
+ widthFt?: number | undefined;
168
+ }[] | undefined;
169
+ baths?: {
170
+ type?: "unknown" | "full" | "half" | "three_quarter" | undefined;
171
+ count?: number | undefined;
172
+ label?: string | undefined;
173
+ }[] | undefined;
174
+ flooringSqFt?: number | undefined;
175
+ drywallSqFt?: number | undefined;
176
+ paintSqFt?: number | undefined;
177
+ roofSqFt?: number | undefined;
178
+ flags?: {
179
+ wallMovingRisk?: boolean | undefined;
180
+ electricalSurpriseRisk?: boolean | undefined;
181
+ plumbingSurpriseRisk?: boolean | undefined;
182
+ } | undefined;
183
+ } | undefined;
47
184
  }>;
48
185
  declare const listTakeoffRowsSchema: z.ZodObject<{
49
186
  projectId: z.ZodString;
@@ -230,18 +367,155 @@ export declare const uploadTakeoffInput: {
230
367
  fileName: z.ZodString;
231
368
  base64: z.ZodString;
232
369
  mimeType: z.ZodOptional<z.ZodString>;
370
+ remodelHints: z.ZodOptional<z.ZodObject<{
371
+ cabinetLinearFt: z.ZodOptional<z.ZodNumber>;
372
+ rooms: z.ZodOptional<z.ZodArray<z.ZodObject<{
373
+ name: z.ZodString;
374
+ lengthFt: z.ZodOptional<z.ZodNumber>;
375
+ widthFt: z.ZodOptional<z.ZodNumber>;
376
+ }, "strip", z.ZodTypeAny, {
377
+ name: string;
378
+ lengthFt?: number | undefined;
379
+ widthFt?: number | undefined;
380
+ }, {
381
+ name: string;
382
+ lengthFt?: number | undefined;
383
+ widthFt?: number | undefined;
384
+ }>, "many">>;
385
+ baths: z.ZodOptional<z.ZodArray<z.ZodObject<{
386
+ type: z.ZodOptional<z.ZodEnum<["full", "half", "three_quarter", "unknown"]>>;
387
+ count: z.ZodOptional<z.ZodNumber>;
388
+ label: z.ZodOptional<z.ZodString>;
389
+ }, "strip", z.ZodTypeAny, {
390
+ type?: "unknown" | "full" | "half" | "three_quarter" | undefined;
391
+ count?: number | undefined;
392
+ label?: string | undefined;
393
+ }, {
394
+ type?: "unknown" | "full" | "half" | "three_quarter" | undefined;
395
+ count?: number | undefined;
396
+ label?: string | undefined;
397
+ }>, "many">>;
398
+ flooringSqFt: z.ZodOptional<z.ZodNumber>;
399
+ drywallSqFt: z.ZodOptional<z.ZodNumber>;
400
+ paintSqFt: z.ZodOptional<z.ZodNumber>;
401
+ roofSqFt: z.ZodOptional<z.ZodNumber>;
402
+ flags: z.ZodOptional<z.ZodObject<{
403
+ wallMovingRisk: z.ZodOptional<z.ZodBoolean>;
404
+ electricalSurpriseRisk: z.ZodOptional<z.ZodBoolean>;
405
+ plumbingSurpriseRisk: z.ZodOptional<z.ZodBoolean>;
406
+ }, "strip", z.ZodTypeAny, {
407
+ wallMovingRisk?: boolean | undefined;
408
+ electricalSurpriseRisk?: boolean | undefined;
409
+ plumbingSurpriseRisk?: boolean | undefined;
410
+ }, {
411
+ wallMovingRisk?: boolean | undefined;
412
+ electricalSurpriseRisk?: boolean | undefined;
413
+ plumbingSurpriseRisk?: boolean | undefined;
414
+ }>>;
415
+ notes: z.ZodOptional<z.ZodString>;
416
+ }, "strip", z.ZodTypeAny, {
417
+ notes?: string | undefined;
418
+ cabinetLinearFt?: number | undefined;
419
+ rooms?: {
420
+ name: string;
421
+ lengthFt?: number | undefined;
422
+ widthFt?: number | undefined;
423
+ }[] | undefined;
424
+ baths?: {
425
+ type?: "unknown" | "full" | "half" | "three_quarter" | undefined;
426
+ count?: number | undefined;
427
+ label?: string | undefined;
428
+ }[] | undefined;
429
+ flooringSqFt?: number | undefined;
430
+ drywallSqFt?: number | undefined;
431
+ paintSqFt?: number | undefined;
432
+ roofSqFt?: number | undefined;
433
+ flags?: {
434
+ wallMovingRisk?: boolean | undefined;
435
+ electricalSurpriseRisk?: boolean | undefined;
436
+ plumbingSurpriseRisk?: boolean | undefined;
437
+ } | undefined;
438
+ }, {
439
+ notes?: string | undefined;
440
+ cabinetLinearFt?: number | undefined;
441
+ rooms?: {
442
+ name: string;
443
+ lengthFt?: number | undefined;
444
+ widthFt?: number | undefined;
445
+ }[] | undefined;
446
+ baths?: {
447
+ type?: "unknown" | "full" | "half" | "three_quarter" | undefined;
448
+ count?: number | undefined;
449
+ label?: string | undefined;
450
+ }[] | undefined;
451
+ flooringSqFt?: number | undefined;
452
+ drywallSqFt?: number | undefined;
453
+ paintSqFt?: number | undefined;
454
+ roofSqFt?: number | undefined;
455
+ flags?: {
456
+ wallMovingRisk?: boolean | undefined;
457
+ electricalSurpriseRisk?: boolean | undefined;
458
+ plumbingSurpriseRisk?: boolean | undefined;
459
+ } | undefined;
460
+ }>>;
233
461
  }, "strip", z.ZodTypeAny, {
234
462
  base64: string;
235
463
  projectId: string;
236
464
  kind: string;
237
465
  fileName: string;
238
466
  mimeType?: string | undefined;
467
+ remodelHints?: {
468
+ notes?: string | undefined;
469
+ cabinetLinearFt?: number | undefined;
470
+ rooms?: {
471
+ name: string;
472
+ lengthFt?: number | undefined;
473
+ widthFt?: number | undefined;
474
+ }[] | undefined;
475
+ baths?: {
476
+ type?: "unknown" | "full" | "half" | "three_quarter" | undefined;
477
+ count?: number | undefined;
478
+ label?: string | undefined;
479
+ }[] | undefined;
480
+ flooringSqFt?: number | undefined;
481
+ drywallSqFt?: number | undefined;
482
+ paintSqFt?: number | undefined;
483
+ roofSqFt?: number | undefined;
484
+ flags?: {
485
+ wallMovingRisk?: boolean | undefined;
486
+ electricalSurpriseRisk?: boolean | undefined;
487
+ plumbingSurpriseRisk?: boolean | undefined;
488
+ } | undefined;
489
+ } | undefined;
239
490
  }, {
240
491
  base64: string;
241
492
  projectId: string;
242
493
  kind: string;
243
494
  fileName: string;
244
495
  mimeType?: string | undefined;
496
+ remodelHints?: {
497
+ notes?: string | undefined;
498
+ cabinetLinearFt?: number | undefined;
499
+ rooms?: {
500
+ name: string;
501
+ lengthFt?: number | undefined;
502
+ widthFt?: number | undefined;
503
+ }[] | undefined;
504
+ baths?: {
505
+ type?: "unknown" | "full" | "half" | "three_quarter" | undefined;
506
+ count?: number | undefined;
507
+ label?: string | undefined;
508
+ }[] | undefined;
509
+ flooringSqFt?: number | undefined;
510
+ drywallSqFt?: number | undefined;
511
+ paintSqFt?: number | undefined;
512
+ roofSqFt?: number | undefined;
513
+ flags?: {
514
+ wallMovingRisk?: boolean | undefined;
515
+ electricalSurpriseRisk?: boolean | undefined;
516
+ plumbingSurpriseRisk?: boolean | undefined;
517
+ } | undefined;
518
+ } | undefined;
245
519
  }>;
246
520
  execute: (args: z.infer<typeof uploadTakeoffInputSchema>) => Promise<string>;
247
521
  };
@@ -9,12 +9,44 @@ const createTakeoffProjectSchema = z.object({
9
9
  companyId: z.string().optional(),
10
10
  });
11
11
  const listTakeoffProjectsSchema = z.object({});
12
+ const remodelHintsSchema = z
13
+ .object({
14
+ cabinetLinearFt: z.number().nonnegative().optional(),
15
+ rooms: z
16
+ .array(z.object({
17
+ name: z.string(),
18
+ lengthFt: z.number().positive().optional(),
19
+ widthFt: z.number().positive().optional(),
20
+ }))
21
+ .optional(),
22
+ baths: z
23
+ .array(z.object({
24
+ type: z.enum(["full", "half", "three_quarter", "unknown"]).optional(),
25
+ count: z.number().int().positive().optional(),
26
+ label: z.string().optional(),
27
+ }))
28
+ .optional(),
29
+ flooringSqFt: z.number().nonnegative().optional(),
30
+ drywallSqFt: z.number().nonnegative().optional(),
31
+ paintSqFt: z.number().nonnegative().optional(),
32
+ roofSqFt: z.number().nonnegative().optional(),
33
+ flags: z
34
+ .object({
35
+ wallMovingRisk: z.boolean().optional(),
36
+ electricalSurpriseRisk: z.boolean().optional(),
37
+ plumbingSurpriseRisk: z.boolean().optional(),
38
+ })
39
+ .optional(),
40
+ notes: z.string().optional(),
41
+ })
42
+ .optional();
12
43
  const uploadTakeoffInputSchema = z.object({
13
44
  projectId: z.string().uuid(),
14
45
  kind: z.string(),
15
46
  fileName: z.string(),
16
47
  base64: z.string(),
17
48
  mimeType: z.string().optional(),
49
+ remodelHints: remodelHintsSchema,
18
50
  });
19
51
  const listTakeoffRowsSchema = z.object({
20
52
  projectId: z.string().uuid(),
@@ -102,17 +134,19 @@ export const listTakeoffProjects = {
102
134
  };
103
135
  export const uploadTakeoffInput = {
104
136
  name: "estimator_upload_takeoff_input",
105
- description: "Register a takeoff file upload. Pass base64 file content via the API route from your client, or use the web UI multipart upload at POST /api/estimator/takeoffs/{id}/inputs with kind + file.",
137
+ description: "Upload a takeoff input file (base64) for a project. kind: eagleview_pdf|eagleview_xml|eagleview_json|eagleview_csv|spec_pdf|spreadsheet|photo|voice|other. For remodel photo/drawing takeoff, use kind=photo or spec_pdf and pass optional remodelHints (cabinet LF, room dims, bath counts, flooring/drywall/paint/roof sf, risk flags) — quantities are reviewable, not auto-measured from pixels.",
106
138
  inputSchemaZod: uploadTakeoffInputSchema,
107
139
  execute: async (args) => {
108
140
  if (!client.hasApiKey())
109
141
  return client.noApiKeyMessage();
110
- return JSON.stringify({
111
- message: "Use POST /api/estimator/takeoffs/{projectId}/inputs multipart (kind + file) from an authenticated client. MCP base64 upload route can be added when hosted file relay is available.",
112
- projectId: args.projectId,
142
+ const data = await client.post(`/estimator/takeoffs/${args.projectId}/inputs`, {
113
143
  kind: args.kind,
114
144
  fileName: args.fileName,
145
+ base64: args.base64,
146
+ ...(args.mimeType ? { mimeType: args.mimeType } : {}),
147
+ ...(args.remodelHints ? { remodelHints: args.remodelHints } : {}),
115
148
  });
149
+ return JSON.stringify(data);
116
150
  },
117
151
  };
118
152
  export const runTakeoffExtract = {
@@ -15,7 +15,7 @@ export const analyzePhotos = {
15
15
  const data = await client.post("/estimator/analyze", {
16
16
  type: "photos",
17
17
  images: args.images,
18
- prompt: args.prompt,
18
+ ...(args.prompt ? { prompt: args.prompt } : {}),
19
19
  });
20
20
  return JSON.stringify(data);
21
21
  },
@@ -35,7 +35,7 @@ export const analyzeDocument = {
35
35
  const data = await client.post("/estimator/analyze", {
36
36
  type: "document",
37
37
  text: args.text,
38
- prompt: args.prompt,
38
+ ...(args.prompt ? { prompt: args.prompt } : {}),
39
39
  });
40
40
  return JSON.stringify(data);
41
41
  },
package/package.json CHANGED
@@ -1,6 +1,11 @@
1
1
  {
2
2
  "name": "@kynver-app/mcp-estimator",
3
- "version": "0.2.8",
3
+ "version": "0.2.11",
4
+ "files": [
5
+ "dist",
6
+ "!dist/**/__tests__/**",
7
+ "!dist/**/*.map"
8
+ ],
4
9
  "description": "Kynver Estimator MCP server — chat, estimate, and catalog tools",
5
10
  "type": "module",
6
11
  "bin": {
package/src/index.ts DELETED
@@ -1,18 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * Kynver Estimator MCP server — stdio entry point.
4
- * Run: node dist/index.js
5
- * Env: KYNVER_API_URL (default https://kynver.com), KYNVER_API_KEY (required for all tools)
6
- */
7
-
8
- import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
9
- import { handleCliVersionFlag } from "./lib/package-version.js";
10
- import { createEstimatorServer } from "./server.js";
11
-
12
- if (handleCliVersionFlag(process.argv.slice(2), import.meta.url, "kynver-mcp-estimator")) {
13
- // exits
14
- }
15
-
16
- const server = createEstimatorServer();
17
- const transport = new StdioServerTransport();
18
- await server.connect(transport);
@@ -1,136 +0,0 @@
1
- /**
2
- * Thin fetch wrapper for Kynver API. Base URL from KYNVER_API_URL; optional Bearer from KYNVER_API_KEY.
3
- *
4
- * Supports per-request user identity via AsyncLocalStorage. When a userId is
5
- * set in the request context (e.g. from the SSE route), all API calls include
6
- * X-Kynver-User-Id and X-Kynver-Service-Secret headers so the Kynver API
7
- * executes operations on behalf of the correct user.
8
- */
9
-
10
- import { AsyncLocalStorage } from "node:async_hooks";
11
-
12
- // ---------------------------------------------------------------------------
13
- // Request context — allows the SSE route to set userId per-connection
14
- // ---------------------------------------------------------------------------
15
-
16
- interface RequestContext {
17
- userId?: string;
18
- }
19
-
20
- export const requestContext = new AsyncLocalStorage<RequestContext>();
21
-
22
- // ---------------------------------------------------------------------------
23
- // Config
24
- // ---------------------------------------------------------------------------
25
-
26
- const baseUrl = process.env.KYNVER_API_URL ?? "https://kynver.com";
27
- const apiKey = process.env.KYNVER_API_KEY ?? "";
28
-
29
- function assertSafeApiPath(path: string): void {
30
- const p = path.startsWith("/") ? path : `/${path}`;
31
- if (p.includes("..") || p.includes("\0")) {
32
- throw new Error("Invalid API path: path traversal is not allowed");
33
- }
34
- }
35
-
36
- function url(path: string): string {
37
- assertSafeApiPath(path);
38
- const p = path.startsWith("/") ? path : `/${path}`;
39
- const apiPath = p.startsWith("/api") ? p : `/api${p}`;
40
- return `${baseUrl.replace(/\/$/, "")}${apiPath}`;
41
- }
42
-
43
- function getHeaders(): Record<string, string> {
44
- const headers: Record<string, string> = {
45
- "Content-Type": "application/json",
46
- };
47
- if (apiKey) {
48
- headers["Authorization"] = `Bearer ${apiKey}`;
49
- }
50
-
51
- const ctx = requestContext.getStore();
52
- const userId = ctx?.userId ?? process.env.KYNVER_USER_ID?.trim();
53
- const serviceSecret = process.env.KYNVER_SERVICE_SECRET?.trim();
54
-
55
- if (userId) {
56
- if (!serviceSecret) {
57
- throw new Error(
58
- "X-Kynver-User-Id was set in context but KYNVER_SERVICE_SECRET is missing; refusing to send an unscoped request.",
59
- );
60
- }
61
- headers["X-Kynver-User-Id"] = userId;
62
- headers["X-Kynver-Service-Secret"] = serviceSecret;
63
- }
64
-
65
- return headers;
66
- }
67
-
68
- // ---------------------------------------------------------------------------
69
- // Response handling
70
- // ---------------------------------------------------------------------------
71
-
72
- async function handleResponse<T>(res: Response): Promise<T> {
73
- const text = await res.text();
74
- if (!res.ok) {
75
- let msg = text;
76
- try {
77
- const j = JSON.parse(text) as { error?: string; message?: string };
78
- msg = j.error ?? j.message ?? text;
79
- } catch {
80
- // use text as-is
81
- }
82
- throw new Error(msg || `HTTP ${res.status}`);
83
- }
84
- if (!text) return undefined as T;
85
- try {
86
- return JSON.parse(text) as T;
87
- } catch {
88
- return text as unknown as T;
89
- }
90
- }
91
-
92
- // ---------------------------------------------------------------------------
93
- // HTTP methods
94
- // ---------------------------------------------------------------------------
95
-
96
- export async function get<T = unknown>(path: string): Promise<T> {
97
- const res = await fetch(url(path), {
98
- method: "GET",
99
- headers: getHeaders(),
100
- });
101
- return handleResponse<T>(res);
102
- }
103
-
104
- export async function post<T = unknown>(path: string, body: unknown): Promise<T> {
105
- const res = await fetch(url(path), {
106
- method: "POST",
107
- headers: getHeaders(),
108
- body: JSON.stringify(body),
109
- });
110
- return handleResponse<T>(res);
111
- }
112
-
113
- export async function patch<T = unknown>(path: string, body: unknown): Promise<T> {
114
- const res = await fetch(url(path), {
115
- method: "PATCH",
116
- headers: getHeaders(),
117
- body: JSON.stringify(body),
118
- });
119
- return handleResponse<T>(res);
120
- }
121
-
122
- export async function del<T = unknown>(path: string): Promise<T> {
123
- const res = await fetch(url(path), {
124
- method: "DELETE",
125
- headers: getHeaders(),
126
- });
127
- return handleResponse<T>(res);
128
- }
129
-
130
- export function hasApiKey(): boolean {
131
- return !!apiKey;
132
- }
133
-
134
- export function noApiKeyMessage(): string {
135
- return "No API key configured. Set KYNVER_API_KEY in your MCP server config.";
136
- }