@kynver-app/mcp-estimator 0.2.10 → 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.d.ts +15 -0
- package/dist/lib/mcp-sse-server.js +99 -11
- 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
|
@@ -23,6 +23,21 @@ export declare function headerValue(req: IncomingMessage, name: string): string
|
|
|
23
23
|
export declare function timingSafeEqualString(a: string, b: string): boolean;
|
|
24
24
|
export declare function hasForbiddenQueryAuth(url: URL): boolean;
|
|
25
25
|
export declare function resolveListenHost(): string;
|
|
26
|
+
/** Positive integer from env, else fallback. */
|
|
27
|
+
export declare function intFromEnv(name: string, fallback: number): number;
|
|
28
|
+
/**
|
|
29
|
+
* Resolve + validate the Kynver API origin the server sends bearer keys to.
|
|
30
|
+
* A poisoned/misconfigured value would exfiltrate API keys, so fail closed on
|
|
31
|
+
* anything that isn't https (http is allowed only for localhost dev).
|
|
32
|
+
*/
|
|
33
|
+
export declare function resolveApiBaseUrl(): string;
|
|
34
|
+
/** First client IP from x-forwarded-for, else the socket address. */
|
|
35
|
+
export declare function clientIp(req: IncomingMessage): string;
|
|
36
|
+
type RateLimiter = {
|
|
37
|
+
check: (key: string) => boolean;
|
|
38
|
+
};
|
|
39
|
+
/** Minimal in-process fixed-window limiter (the SSE server is single-process). */
|
|
40
|
+
export declare function createFixedWindowRateLimiter(limit: number, windowMs: number): RateLimiter;
|
|
26
41
|
export declare function parseJsonBody(req: IncomingMessage, maxBytes?: number): Promise<unknown>;
|
|
27
42
|
export declare function validateMcporterServerName(name: string): string;
|
|
28
43
|
export declare function startKynverMcpSseServer(options: StartKynverMcpSseServerOptions): void;
|
|
@@ -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,13 +19,65 @@ 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";
|
|
28
25
|
}
|
|
26
|
+
/** Positive integer from env, else fallback. */
|
|
27
|
+
export function intFromEnv(name, fallback) {
|
|
28
|
+
const raw = process.env[name];
|
|
29
|
+
if (!raw)
|
|
30
|
+
return fallback;
|
|
31
|
+
const n = parseInt(raw, 10);
|
|
32
|
+
return Number.isFinite(n) && n > 0 ? n : fallback;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Resolve + validate the Kynver API origin the server sends bearer keys to.
|
|
36
|
+
* A poisoned/misconfigured value would exfiltrate API keys, so fail closed on
|
|
37
|
+
* anything that isn't https (http is allowed only for localhost dev).
|
|
38
|
+
*/
|
|
39
|
+
export function resolveApiBaseUrl() {
|
|
40
|
+
const raw = process.env.KYNVER_API_URL?.trim();
|
|
41
|
+
if (!raw)
|
|
42
|
+
return "https://kynver.com";
|
|
43
|
+
let parsed;
|
|
44
|
+
try {
|
|
45
|
+
parsed = new URL(raw);
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
throw new Error(`Invalid KYNVER_API_URL: ${raw}`);
|
|
49
|
+
}
|
|
50
|
+
const isLocalhost = parsed.hostname === "localhost" || parsed.hostname === "127.0.0.1";
|
|
51
|
+
if (parsed.protocol !== "https:" && !(parsed.protocol === "http:" && isLocalhost)) {
|
|
52
|
+
throw new Error(`KYNVER_API_URL must be https (got "${parsed.protocol}//${parsed.hostname}"); API keys are sent to this origin.`);
|
|
53
|
+
}
|
|
54
|
+
return raw.replace(/\/$/, "");
|
|
55
|
+
}
|
|
56
|
+
/** First client IP from x-forwarded-for, else the socket address. */
|
|
57
|
+
export function clientIp(req) {
|
|
58
|
+
const fwd = headerValue(req, "x-forwarded-for");
|
|
59
|
+
if (fwd)
|
|
60
|
+
return fwd.split(",")[0].trim();
|
|
61
|
+
return req.socket?.remoteAddress ?? "unknown";
|
|
62
|
+
}
|
|
63
|
+
/** Minimal in-process fixed-window limiter (the SSE server is single-process). */
|
|
64
|
+
export function createFixedWindowRateLimiter(limit, windowMs) {
|
|
65
|
+
const hits = new Map();
|
|
66
|
+
return {
|
|
67
|
+
check(key) {
|
|
68
|
+
const now = Date.now();
|
|
69
|
+
const entry = hits.get(key);
|
|
70
|
+
if (!entry || now >= entry.resetAt) {
|
|
71
|
+
hits.set(key, { count: 1, resetAt: now + windowMs });
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
if (entry.count >= limit)
|
|
75
|
+
return false;
|
|
76
|
+
entry.count += 1;
|
|
77
|
+
return true;
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
}
|
|
29
81
|
export function parseJsonBody(req, maxBytes = MAX_SSE_BODY_BYTES) {
|
|
30
82
|
return new Promise((resolve, reject) => {
|
|
31
83
|
const chunks = [];
|
|
@@ -75,7 +127,7 @@ function resolveInternalAuth(req, serviceSecret) {
|
|
|
75
127
|
}
|
|
76
128
|
function verifyPostAuth(req, url, sessionAuth, serviceSecret) {
|
|
77
129
|
if (sessionAuth.kind === "apiKey") {
|
|
78
|
-
const token = bearerToken(req)
|
|
130
|
+
const token = bearerToken(req);
|
|
79
131
|
return token.length > 0 && timingSafeEqualString(token, sessionAuth.apiKey);
|
|
80
132
|
}
|
|
81
133
|
const internal = resolveInternalAuth(req, serviceSecret);
|
|
@@ -106,23 +158,42 @@ function json(res, status, body) {
|
|
|
106
158
|
}
|
|
107
159
|
export function startKynverMcpSseServer(options) {
|
|
108
160
|
const port = parseInt(process.env.PORT ?? String(options.defaultPort), 10);
|
|
109
|
-
const baseUrl =
|
|
161
|
+
const baseUrl = resolveApiBaseUrl();
|
|
110
162
|
const serviceSecret = process.env.KYNVER_SERVICE_SECRET ?? "";
|
|
111
163
|
const host = resolveListenHost();
|
|
112
164
|
const sessions = {};
|
|
165
|
+
// Resource / abuse limits (env-overridable; in-process per server).
|
|
166
|
+
const maxSessions = intFromEnv("MCP_SSE_MAX_SESSIONS", 200);
|
|
167
|
+
const maxSessionsPerUser = intFromEnv("MCP_SSE_MAX_SESSIONS_PER_USER", 20);
|
|
168
|
+
const idleTimeoutMs = intFromEnv("MCP_SSE_IDLE_TIMEOUT_MS", 5 * 60 * 1000);
|
|
169
|
+
const rateLimitPerMin = intFromEnv("MCP_SSE_RATE_LIMIT_PER_MIN", 120);
|
|
170
|
+
const rateLimiter = createFixedWindowRateLimiter(rateLimitPerMin, 60 * 1000);
|
|
171
|
+
// Close idle SSE sessions so a flood of half-open connections can't pin memory.
|
|
172
|
+
const idleSweep = setInterval(() => {
|
|
173
|
+
const now = Date.now();
|
|
174
|
+
for (const [sid, entry] of Object.entries(sessions)) {
|
|
175
|
+
if (now - entry.lastActivity > idleTimeoutMs) {
|
|
176
|
+
delete sessions[sid];
|
|
177
|
+
void entry.transport.close().catch(() => { });
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}, 60 * 1000);
|
|
181
|
+
idleSweep.unref?.();
|
|
113
182
|
const httpServer = createServer(async (req, res) => {
|
|
114
183
|
const url = parseUrl(req, port);
|
|
115
184
|
const path = url.pathname;
|
|
116
185
|
if (req.method === "GET" && path === "/sse") {
|
|
186
|
+
// Pre-auth rate limit by client IP (we don't know the user yet).
|
|
187
|
+
if (!rateLimiter.check(`sse:${clientIp(req)}`)) {
|
|
188
|
+
json(res, 429, { error: "Too many requests. Slow down and retry." });
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
117
191
|
if (hasForbiddenQueryAuth(url)) {
|
|
118
192
|
json(res, 400, {
|
|
119
193
|
error: "Internal auth via query params is not allowed. Use X-Kynver-User-Id and X-Kynver-Service-Secret headers.",
|
|
120
194
|
});
|
|
121
195
|
return;
|
|
122
196
|
}
|
|
123
|
-
if (url.searchParams.has("apiKey")) {
|
|
124
|
-
console.warn("[MCP SSE] WARNING: apiKey in query string may leak via logs. Prefer Authorization header when your client supports it.");
|
|
125
|
-
}
|
|
126
197
|
let userId = null;
|
|
127
198
|
let auth = null;
|
|
128
199
|
const internal = serviceSecret ? resolveInternalAuth(req, serviceSecret) : null;
|
|
@@ -131,7 +202,7 @@ export function startKynverMcpSseServer(options) {
|
|
|
131
202
|
auth = internal.auth;
|
|
132
203
|
}
|
|
133
204
|
else {
|
|
134
|
-
const apiKey = bearerToken(req)
|
|
205
|
+
const apiKey = bearerToken(req);
|
|
135
206
|
if (apiKey) {
|
|
136
207
|
userId = await resolveUserFromApiKey(baseUrl, apiKey);
|
|
137
208
|
if (userId)
|
|
@@ -144,9 +215,20 @@ export function startKynverMcpSseServer(options) {
|
|
|
144
215
|
});
|
|
145
216
|
return;
|
|
146
217
|
}
|
|
218
|
+
// Connection caps — bound total + per-user concurrent SSE sessions so an
|
|
219
|
+
// authenticated caller can't exhaust memory/sockets by opening many.
|
|
220
|
+
const allEntries = Object.values(sessions);
|
|
221
|
+
if (allEntries.length >= maxSessions) {
|
|
222
|
+
json(res, 503, { error: "Server at capacity. Try again shortly." });
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
if (allEntries.filter((s) => s.userId === userId).length >= maxSessionsPerUser) {
|
|
226
|
+
json(res, 429, { error: "Too many concurrent connections for this account." });
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
147
229
|
console.log(`[MCP SSE] New connection (${options.label}, user: ${userId.slice(0, 8)}...)`);
|
|
148
230
|
const transport = new SSEServerTransport("/messages", res);
|
|
149
|
-
sessions[transport.sessionId] = { transport, userId, auth };
|
|
231
|
+
sessions[transport.sessionId] = { transport, userId, auth, lastActivity: Date.now() };
|
|
150
232
|
res.on("close", () => {
|
|
151
233
|
delete sessions[transport.sessionId];
|
|
152
234
|
});
|
|
@@ -165,6 +247,12 @@ export function startKynverMcpSseServer(options) {
|
|
|
165
247
|
json(res, 401, { error: "Unauthorized. Re-send the same credentials used to open the SSE session." });
|
|
166
248
|
return;
|
|
167
249
|
}
|
|
250
|
+
// Per-user rate limit on tool/message traffic.
|
|
251
|
+
if (!rateLimiter.check(`msg:${entry.userId}`)) {
|
|
252
|
+
json(res, 429, { error: "Too many requests. Slow down and retry." });
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
entry.lastActivity = Date.now();
|
|
168
256
|
try {
|
|
169
257
|
const body = await parseJsonBody(req);
|
|
170
258
|
await options.requestContext.run({ userId: entry.userId }, async () => {
|
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
|
-
}
|