@kynver-app/mcp-estimator 0.2.11 → 0.2.12
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/lib/mcp-sse-server.js +3 -16
- package/package.json +1 -1
- package/dist/tools/create-estimate.d.ts +0 -107
- package/dist/tools/create-estimate.js +0 -64
- package/dist/tools/estimator-chat.d.ts +0 -25
- package/dist/tools/estimator-chat.js +0 -15
- package/dist/tools/get-estimate.d.ts +0 -19
- package/dist/tools/get-estimate.js +0 -14
- package/dist/tools/list-catalog.d.ts +0 -25
- package/dist/tools/list-catalog.js +0 -22
|
@@ -4,7 +4,7 @@ import { createServer } from "node:http";
|
|
|
4
4
|
import { timingSafeEqual } from "node:crypto";
|
|
5
5
|
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
|
|
6
6
|
export const MAX_SSE_BODY_BYTES = 1 * 1024 * 1024;
|
|
7
|
-
const FORBIDDEN_QUERY_KEYS = ["secret", "userId"];
|
|
7
|
+
const FORBIDDEN_QUERY_KEYS = ["apiKey", "secret", "userId"];
|
|
8
8
|
export function headerValue(req, name) {
|
|
9
9
|
const v = req.headers[name];
|
|
10
10
|
return typeof v === "string" ? v : Array.isArray(v) ? (v[0] ?? null) : null;
|
|
@@ -19,9 +19,6 @@ export function timingSafeEqualString(a, b) {
|
|
|
19
19
|
export function hasForbiddenQueryAuth(url) {
|
|
20
20
|
return FORBIDDEN_QUERY_KEYS.some((key) => url.searchParams.has(key));
|
|
21
21
|
}
|
|
22
|
-
function queryApiKey(url) {
|
|
23
|
-
return url.searchParams.get("apiKey")?.trim() ?? "";
|
|
24
|
-
}
|
|
25
22
|
export function resolveListenHost() {
|
|
26
23
|
const configured = process.env.MCP_SSE_HOST?.trim();
|
|
27
24
|
return configured || "127.0.0.1";
|
|
@@ -130,7 +127,7 @@ function resolveInternalAuth(req, serviceSecret) {
|
|
|
130
127
|
}
|
|
131
128
|
function verifyPostAuth(req, url, sessionAuth, serviceSecret) {
|
|
132
129
|
if (sessionAuth.kind === "apiKey") {
|
|
133
|
-
const token = bearerToken(req)
|
|
130
|
+
const token = bearerToken(req);
|
|
134
131
|
return token.length > 0 && timingSafeEqualString(token, sessionAuth.apiKey);
|
|
135
132
|
}
|
|
136
133
|
const internal = resolveInternalAuth(req, serviceSecret);
|
|
@@ -170,7 +167,6 @@ export function startKynverMcpSseServer(options) {
|
|
|
170
167
|
const maxSessionsPerUser = intFromEnv("MCP_SSE_MAX_SESSIONS_PER_USER", 20);
|
|
171
168
|
const idleTimeoutMs = intFromEnv("MCP_SSE_IDLE_TIMEOUT_MS", 5 * 60 * 1000);
|
|
172
169
|
const rateLimitPerMin = intFromEnv("MCP_SSE_RATE_LIMIT_PER_MIN", 120);
|
|
173
|
-
const rejectQueryApiKey = /^(1|true|yes)$/i.test(process.env.MCP_SSE_REJECT_QUERY_APIKEY ?? "");
|
|
174
170
|
const rateLimiter = createFixedWindowRateLimiter(rateLimitPerMin, 60 * 1000);
|
|
175
171
|
// Close idle SSE sessions so a flood of half-open connections can't pin memory.
|
|
176
172
|
const idleSweep = setInterval(() => {
|
|
@@ -198,15 +194,6 @@ export function startKynverMcpSseServer(options) {
|
|
|
198
194
|
});
|
|
199
195
|
return;
|
|
200
196
|
}
|
|
201
|
-
if (url.searchParams.has("apiKey")) {
|
|
202
|
-
if (rejectQueryApiKey) {
|
|
203
|
-
json(res, 400, {
|
|
204
|
-
error: "apiKey via query param is disabled. Use Authorization: Bearer <key>.",
|
|
205
|
-
});
|
|
206
|
-
return;
|
|
207
|
-
}
|
|
208
|
-
console.warn("[MCP SSE] WARNING: apiKey in query string may leak via logs. Prefer Authorization header when your client supports it.");
|
|
209
|
-
}
|
|
210
197
|
let userId = null;
|
|
211
198
|
let auth = null;
|
|
212
199
|
const internal = serviceSecret ? resolveInternalAuth(req, serviceSecret) : null;
|
|
@@ -215,7 +202,7 @@ export function startKynverMcpSseServer(options) {
|
|
|
215
202
|
auth = internal.auth;
|
|
216
203
|
}
|
|
217
204
|
else {
|
|
218
|
-
const apiKey = bearerToken(req)
|
|
205
|
+
const apiKey = bearerToken(req);
|
|
219
206
|
if (apiKey) {
|
|
220
207
|
userId = await resolveUserFromApiKey(baseUrl, apiKey);
|
|
221
208
|
if (userId)
|
package/package.json
CHANGED
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
declare const inputSchema: z.ZodObject<{
|
|
3
|
-
type: z.ZodEnum<["replacement", "repair_service"]>;
|
|
4
|
-
projectName: z.ZodString;
|
|
5
|
-
roofMaterial: z.ZodOptional<z.ZodString>;
|
|
6
|
-
footprintSqFt: z.ZodOptional<z.ZodNumber>;
|
|
7
|
-
pitch: z.ZodOptional<z.ZodString>;
|
|
8
|
-
layers: z.ZodOptional<z.ZodNumber>;
|
|
9
|
-
ridgeLengthFt: z.ZodOptional<z.ZodNumber>;
|
|
10
|
-
eavesFt: z.ZodOptional<z.ZodNumber>;
|
|
11
|
-
lineItems: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
12
|
-
serviceCode: z.ZodString;
|
|
13
|
-
materialType: z.ZodOptional<z.ZodString>;
|
|
14
|
-
quantity: z.ZodNumber;
|
|
15
|
-
}, "strip", z.ZodTypeAny, {
|
|
16
|
-
serviceCode: string;
|
|
17
|
-
quantity: number;
|
|
18
|
-
materialType?: string | undefined;
|
|
19
|
-
}, {
|
|
20
|
-
serviceCode: string;
|
|
21
|
-
quantity: number;
|
|
22
|
-
materialType?: string | undefined;
|
|
23
|
-
}>, "many">>;
|
|
24
|
-
}, "strip", z.ZodTypeAny, {
|
|
25
|
-
type: "replacement" | "repair_service";
|
|
26
|
-
projectName: string;
|
|
27
|
-
roofMaterial?: string | undefined;
|
|
28
|
-
footprintSqFt?: number | undefined;
|
|
29
|
-
pitch?: string | undefined;
|
|
30
|
-
layers?: number | undefined;
|
|
31
|
-
ridgeLengthFt?: number | undefined;
|
|
32
|
-
eavesFt?: number | undefined;
|
|
33
|
-
lineItems?: {
|
|
34
|
-
serviceCode: string;
|
|
35
|
-
quantity: number;
|
|
36
|
-
materialType?: string | undefined;
|
|
37
|
-
}[] | undefined;
|
|
38
|
-
}, {
|
|
39
|
-
type: "replacement" | "repair_service";
|
|
40
|
-
projectName: string;
|
|
41
|
-
roofMaterial?: string | undefined;
|
|
42
|
-
footprintSqFt?: number | undefined;
|
|
43
|
-
pitch?: string | undefined;
|
|
44
|
-
layers?: number | undefined;
|
|
45
|
-
ridgeLengthFt?: number | undefined;
|
|
46
|
-
eavesFt?: number | undefined;
|
|
47
|
-
lineItems?: {
|
|
48
|
-
serviceCode: string;
|
|
49
|
-
quantity: number;
|
|
50
|
-
materialType?: string | undefined;
|
|
51
|
-
}[] | undefined;
|
|
52
|
-
}>;
|
|
53
|
-
export declare const name = "estimator_create_estimate";
|
|
54
|
-
export declare const description = "Create a roofing estimate. For replacement: provide dimensions and material. For repair: provide service items with quantities.";
|
|
55
|
-
export declare const inputSchemaZod: z.ZodObject<{
|
|
56
|
-
type: z.ZodEnum<["replacement", "repair_service"]>;
|
|
57
|
-
projectName: z.ZodString;
|
|
58
|
-
roofMaterial: z.ZodOptional<z.ZodString>;
|
|
59
|
-
footprintSqFt: z.ZodOptional<z.ZodNumber>;
|
|
60
|
-
pitch: z.ZodOptional<z.ZodString>;
|
|
61
|
-
layers: z.ZodOptional<z.ZodNumber>;
|
|
62
|
-
ridgeLengthFt: z.ZodOptional<z.ZodNumber>;
|
|
63
|
-
eavesFt: z.ZodOptional<z.ZodNumber>;
|
|
64
|
-
lineItems: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
65
|
-
serviceCode: z.ZodString;
|
|
66
|
-
materialType: z.ZodOptional<z.ZodString>;
|
|
67
|
-
quantity: z.ZodNumber;
|
|
68
|
-
}, "strip", z.ZodTypeAny, {
|
|
69
|
-
serviceCode: string;
|
|
70
|
-
quantity: number;
|
|
71
|
-
materialType?: string | undefined;
|
|
72
|
-
}, {
|
|
73
|
-
serviceCode: string;
|
|
74
|
-
quantity: number;
|
|
75
|
-
materialType?: string | undefined;
|
|
76
|
-
}>, "many">>;
|
|
77
|
-
}, "strip", z.ZodTypeAny, {
|
|
78
|
-
type: "replacement" | "repair_service";
|
|
79
|
-
projectName: string;
|
|
80
|
-
roofMaterial?: string | undefined;
|
|
81
|
-
footprintSqFt?: number | undefined;
|
|
82
|
-
pitch?: string | undefined;
|
|
83
|
-
layers?: number | undefined;
|
|
84
|
-
ridgeLengthFt?: number | undefined;
|
|
85
|
-
eavesFt?: number | undefined;
|
|
86
|
-
lineItems?: {
|
|
87
|
-
serviceCode: string;
|
|
88
|
-
quantity: number;
|
|
89
|
-
materialType?: string | undefined;
|
|
90
|
-
}[] | undefined;
|
|
91
|
-
}, {
|
|
92
|
-
type: "replacement" | "repair_service";
|
|
93
|
-
projectName: string;
|
|
94
|
-
roofMaterial?: string | undefined;
|
|
95
|
-
footprintSqFt?: number | undefined;
|
|
96
|
-
pitch?: string | undefined;
|
|
97
|
-
layers?: number | undefined;
|
|
98
|
-
ridgeLengthFt?: number | undefined;
|
|
99
|
-
eavesFt?: number | undefined;
|
|
100
|
-
lineItems?: {
|
|
101
|
-
serviceCode: string;
|
|
102
|
-
quantity: number;
|
|
103
|
-
materialType?: string | undefined;
|
|
104
|
-
}[] | undefined;
|
|
105
|
-
}>;
|
|
106
|
-
export declare function execute(args: z.infer<typeof inputSchema>): Promise<string>;
|
|
107
|
-
export {};
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
import * as client from "../lib/kynver-client.js";
|
|
3
|
-
const lineItemSchema = z.object({
|
|
4
|
-
serviceCode: z.string(),
|
|
5
|
-
materialType: z.string().optional(),
|
|
6
|
-
quantity: z.number(),
|
|
7
|
-
});
|
|
8
|
-
const inputSchema = z.object({
|
|
9
|
-
type: z.enum(["replacement", "repair_service"]),
|
|
10
|
-
projectName: z.string(),
|
|
11
|
-
roofMaterial: z.string().optional(),
|
|
12
|
-
// For replacement
|
|
13
|
-
footprintSqFt: z.number().optional(),
|
|
14
|
-
pitch: z.string().optional(),
|
|
15
|
-
layers: z.number().optional(),
|
|
16
|
-
ridgeLengthFt: z.number().optional(),
|
|
17
|
-
eavesFt: z.number().optional(),
|
|
18
|
-
// For repair
|
|
19
|
-
lineItems: z.array(lineItemSchema).optional(),
|
|
20
|
-
});
|
|
21
|
-
export const name = "estimator_create_estimate";
|
|
22
|
-
export const description = "Create a roofing estimate. For replacement: provide dimensions and material. For repair: provide service items with quantities.";
|
|
23
|
-
export const inputSchemaZod = inputSchema;
|
|
24
|
-
export async function execute(args) {
|
|
25
|
-
if (!client.hasApiKey())
|
|
26
|
-
return client.noApiKeyMessage();
|
|
27
|
-
// Build a descriptive message for the estimator agent
|
|
28
|
-
let message;
|
|
29
|
-
if (args.type === "replacement") {
|
|
30
|
-
const parts = [
|
|
31
|
-
`Create a roof replacement estimate for project "${args.projectName}".`,
|
|
32
|
-
];
|
|
33
|
-
if (args.roofMaterial)
|
|
34
|
-
parts.push(`Material: ${args.roofMaterial}.`);
|
|
35
|
-
if (args.footprintSqFt != null)
|
|
36
|
-
parts.push(`Footprint: ${args.footprintSqFt} sq ft.`);
|
|
37
|
-
if (args.pitch)
|
|
38
|
-
parts.push(`Pitch: ${args.pitch}.`);
|
|
39
|
-
if (args.layers != null)
|
|
40
|
-
parts.push(`Layers: ${args.layers}.`);
|
|
41
|
-
if (args.ridgeLengthFt != null)
|
|
42
|
-
parts.push(`Ridge length: ${args.ridgeLengthFt} ft.`);
|
|
43
|
-
if (args.eavesFt != null)
|
|
44
|
-
parts.push(`Eaves: ${args.eavesFt} ft.`);
|
|
45
|
-
message = parts.join(" ");
|
|
46
|
-
}
|
|
47
|
-
else {
|
|
48
|
-
const parts = [
|
|
49
|
-
`Create a roof repair/service estimate for project "${args.projectName}".`,
|
|
50
|
-
];
|
|
51
|
-
if (args.roofMaterial)
|
|
52
|
-
parts.push(`Material: ${args.roofMaterial}.`);
|
|
53
|
-
if (args.lineItems && args.lineItems.length > 0) {
|
|
54
|
-
const itemDescriptions = args.lineItems.map((item) => {
|
|
55
|
-
const mat = item.materialType ? ` (${item.materialType})` : "";
|
|
56
|
-
return `${item.serviceCode}${mat} x${item.quantity}`;
|
|
57
|
-
});
|
|
58
|
-
parts.push(`Line items: ${itemDescriptions.join(", ")}.`);
|
|
59
|
-
}
|
|
60
|
-
message = parts.join(" ");
|
|
61
|
-
}
|
|
62
|
-
const data = await client.post("/v1/agents/kynver-estimator/chat", { message });
|
|
63
|
-
return JSON.stringify(data);
|
|
64
|
-
}
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
declare const inputSchema: z.ZodObject<{
|
|
3
|
-
message: z.ZodString;
|
|
4
|
-
conversationId: z.ZodOptional<z.ZodString>;
|
|
5
|
-
}, "strip", z.ZodTypeAny, {
|
|
6
|
-
message: string;
|
|
7
|
-
conversationId?: string | undefined;
|
|
8
|
-
}, {
|
|
9
|
-
message: string;
|
|
10
|
-
conversationId?: string | undefined;
|
|
11
|
-
}>;
|
|
12
|
-
export declare const name = "estimator_chat";
|
|
13
|
-
export declare const description = "Chat with the Kynver roofing estimator agent. Describe work to get estimates, manage your price catalog, or ask questions about your pricing.";
|
|
14
|
-
export declare const inputSchemaZod: z.ZodObject<{
|
|
15
|
-
message: z.ZodString;
|
|
16
|
-
conversationId: z.ZodOptional<z.ZodString>;
|
|
17
|
-
}, "strip", z.ZodTypeAny, {
|
|
18
|
-
message: string;
|
|
19
|
-
conversationId?: string | undefined;
|
|
20
|
-
}, {
|
|
21
|
-
message: string;
|
|
22
|
-
conversationId?: string | undefined;
|
|
23
|
-
}>;
|
|
24
|
-
export declare function execute(args: z.infer<typeof inputSchema>): Promise<string>;
|
|
25
|
-
export {};
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
import * as client from "../lib/kynver-client.js";
|
|
3
|
-
const inputSchema = z.object({
|
|
4
|
-
message: z.string(),
|
|
5
|
-
conversationId: z.string().optional(),
|
|
6
|
-
});
|
|
7
|
-
export const name = "estimator_chat";
|
|
8
|
-
export const description = "Chat with the Kynver roofing estimator agent. Describe work to get estimates, manage your price catalog, or ask questions about your pricing.";
|
|
9
|
-
export const inputSchemaZod = inputSchema;
|
|
10
|
-
export async function execute(args) {
|
|
11
|
-
if (!client.hasApiKey())
|
|
12
|
-
return client.noApiKeyMessage();
|
|
13
|
-
const data = await client.post("/v1/agents/kynver-estimator/chat", { message: args.message, conversationId: args.conversationId });
|
|
14
|
-
return JSON.stringify(data);
|
|
15
|
-
}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
declare const inputSchema: z.ZodObject<{
|
|
3
|
-
estimateId: z.ZodString;
|
|
4
|
-
}, "strip", z.ZodTypeAny, {
|
|
5
|
-
estimateId: string;
|
|
6
|
-
}, {
|
|
7
|
-
estimateId: string;
|
|
8
|
-
}>;
|
|
9
|
-
export declare const name = "estimator_get_estimate";
|
|
10
|
-
export declare const description = "Get a specific estimate with all line items and totals.";
|
|
11
|
-
export declare const inputSchemaZod: z.ZodObject<{
|
|
12
|
-
estimateId: z.ZodString;
|
|
13
|
-
}, "strip", z.ZodTypeAny, {
|
|
14
|
-
estimateId: string;
|
|
15
|
-
}, {
|
|
16
|
-
estimateId: string;
|
|
17
|
-
}>;
|
|
18
|
-
export declare function execute(args: z.infer<typeof inputSchema>): Promise<string>;
|
|
19
|
-
export {};
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
import * as client from "../lib/kynver-client.js";
|
|
3
|
-
const inputSchema = z.object({
|
|
4
|
-
estimateId: z.string(),
|
|
5
|
-
});
|
|
6
|
-
export const name = "estimator_get_estimate";
|
|
7
|
-
export const description = "Get a specific estimate with all line items and totals.";
|
|
8
|
-
export const inputSchemaZod = inputSchema;
|
|
9
|
-
export async function execute(args) {
|
|
10
|
-
if (!client.hasApiKey())
|
|
11
|
-
return client.noApiKeyMessage();
|
|
12
|
-
const data = await client.get(`/estimator/estimates/${args.estimateId}`);
|
|
13
|
-
return JSON.stringify(data);
|
|
14
|
-
}
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
declare const inputSchema: z.ZodObject<{
|
|
3
|
-
category: z.ZodOptional<z.ZodString>;
|
|
4
|
-
materialType: z.ZodOptional<z.ZodString>;
|
|
5
|
-
}, "strip", z.ZodTypeAny, {
|
|
6
|
-
materialType?: string | undefined;
|
|
7
|
-
category?: string | undefined;
|
|
8
|
-
}, {
|
|
9
|
-
materialType?: string | undefined;
|
|
10
|
-
category?: string | undefined;
|
|
11
|
-
}>;
|
|
12
|
-
export declare const name = "estimator_list_catalog";
|
|
13
|
-
export declare const description = "List items in the service catalog (your prices). Optionally filter by category or material type.";
|
|
14
|
-
export declare const inputSchemaZod: z.ZodObject<{
|
|
15
|
-
category: z.ZodOptional<z.ZodString>;
|
|
16
|
-
materialType: z.ZodOptional<z.ZodString>;
|
|
17
|
-
}, "strip", z.ZodTypeAny, {
|
|
18
|
-
materialType?: string | undefined;
|
|
19
|
-
category?: string | undefined;
|
|
20
|
-
}, {
|
|
21
|
-
materialType?: string | undefined;
|
|
22
|
-
category?: string | undefined;
|
|
23
|
-
}>;
|
|
24
|
-
export declare function execute(args: z.infer<typeof inputSchema>): Promise<string>;
|
|
25
|
-
export {};
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
import * as client from "../lib/kynver-client.js";
|
|
3
|
-
const inputSchema = z.object({
|
|
4
|
-
category: z.string().optional(),
|
|
5
|
-
materialType: z.string().optional(),
|
|
6
|
-
});
|
|
7
|
-
export const name = "estimator_list_catalog";
|
|
8
|
-
export const description = "List items in the service catalog (your prices). Optionally filter by category or material type.";
|
|
9
|
-
export const inputSchemaZod = inputSchema;
|
|
10
|
-
export async function execute(args) {
|
|
11
|
-
if (!client.hasApiKey())
|
|
12
|
-
return client.noApiKeyMessage();
|
|
13
|
-
const params = new URLSearchParams();
|
|
14
|
-
if (args.category)
|
|
15
|
-
params.set("category", args.category);
|
|
16
|
-
if (args.materialType)
|
|
17
|
-
params.set("materialType", args.materialType);
|
|
18
|
-
const q = params.toString();
|
|
19
|
-
const path = q ? `/estimator/catalog?${q}` : "/estimator/catalog";
|
|
20
|
-
const data = await client.get(path);
|
|
21
|
-
return JSON.stringify(data);
|
|
22
|
-
}
|