@kynver-app/mcp-estimator 0.1.1
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/index.d.ts +7 -0
- package/dist/index.js +11 -0
- package/dist/lib/kynver-client.d.ts +20 -0
- package/dist/lib/kynver-client.js +110 -0
- package/dist/lib/mcp-sse-server.d.ts +29 -0
- package/dist/lib/mcp-sse-server.js +191 -0
- package/dist/server.d.ts +6 -0
- package/dist/server.js +78 -0
- package/dist/sse.d.ts +1 -0
- package/dist/sse.js +9 -0
- package/dist/tools/catalog-tools.d.ts +387 -0
- package/dist/tools/catalog-tools.js +193 -0
- package/dist/tools/client-tools.d.ts +193 -0
- package/dist/tools/client-tools.js +105 -0
- package/dist/tools/company-tools.d.ts +163 -0
- package/dist/tools/company-tools.js +98 -0
- package/dist/tools/create-estimate.d.ts +107 -0
- package/dist/tools/create-estimate.js +64 -0
- package/dist/tools/estimate-tools.d.ts +531 -0
- package/dist/tools/estimate-tools.js +259 -0
- package/dist/tools/estimator-chat.d.ts +25 -0
- package/dist/tools/estimator-chat.js +15 -0
- package/dist/tools/get-estimate.d.ts +19 -0
- package/dist/tools/get-estimate.js +14 -0
- package/dist/tools/list-catalog.d.ts +25 -0
- package/dist/tools/list-catalog.js +22 -0
- package/dist/tools/photo-tools.d.ts +77 -0
- package/dist/tools/photo-tools.js +51 -0
- package/dist/tools/stats-pdf-tools.d.ts +113 -0
- package/dist/tools/stats-pdf-tools.js +73 -0
- package/dist/tools/template-tools.d.ts +96 -0
- package/dist/tools/template-tools.js +70 -0
- package/dist/tools/version-tools.d.ts +46 -0
- package/dist/tools/version-tools.js +33 -0
- package/dist/tools/vision-tools.d.ts +52 -0
- package/dist/tools/vision-tools.js +42 -0
- package/package.json +27 -0
- package/src/index.ts +13 -0
- package/src/lib/kynver-client.ts +136 -0
- package/src/lib/mcp-sse-server.ts +249 -0
- package/src/server.ts +101 -0
- package/src/sse.ts +10 -0
- package/src/tools/catalog-tools.ts +217 -0
- package/src/tools/client-tools.ts +114 -0
- package/src/tools/company-tools.ts +108 -0
- package/src/tools/estimate-tools.ts +281 -0
- package/src/tools/photo-tools.ts +60 -0
- package/src/tools/stats-pdf-tools.ts +80 -0
- package/src/tools/template-tools.ts +80 -0
- package/src/tools/version-tools.ts +39 -0
- package/src/tools/vision-tools.ts +48 -0
- package/tsconfig.json +15 -0
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
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
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
8
|
+
import { createEstimatorServer } from "./server.js";
|
|
9
|
+
const server = createEstimatorServer();
|
|
10
|
+
const transport = new StdioServerTransport();
|
|
11
|
+
await server.connect(transport);
|
|
@@ -0,0 +1,20 @@
|
|
|
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
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
10
|
+
interface RequestContext {
|
|
11
|
+
userId?: string;
|
|
12
|
+
}
|
|
13
|
+
export declare const requestContext: AsyncLocalStorage<RequestContext>;
|
|
14
|
+
export declare function get<T = unknown>(path: string): Promise<T>;
|
|
15
|
+
export declare function post<T = unknown>(path: string, body: unknown): Promise<T>;
|
|
16
|
+
export declare function patch<T = unknown>(path: string, body: unknown): Promise<T>;
|
|
17
|
+
export declare function del<T = unknown>(path: string): Promise<T>;
|
|
18
|
+
export declare function hasApiKey(): boolean;
|
|
19
|
+
export declare function noApiKeyMessage(): string;
|
|
20
|
+
export {};
|
|
@@ -0,0 +1,110 @@
|
|
|
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
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
10
|
+
export const requestContext = new AsyncLocalStorage();
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
// Config
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
const baseUrl = process.env.KYNVER_API_URL ?? "https://kynver.com";
|
|
15
|
+
const apiKey = process.env.KYNVER_API_KEY ?? "";
|
|
16
|
+
function assertSafeApiPath(path) {
|
|
17
|
+
const p = path.startsWith("/") ? path : `/${path}`;
|
|
18
|
+
if (p.includes("..") || p.includes("\0")) {
|
|
19
|
+
throw new Error("Invalid API path: path traversal is not allowed");
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function url(path) {
|
|
23
|
+
assertSafeApiPath(path);
|
|
24
|
+
const p = path.startsWith("/") ? path : `/${path}`;
|
|
25
|
+
const apiPath = p.startsWith("/api") ? p : `/api${p}`;
|
|
26
|
+
return `${baseUrl.replace(/\/$/, "")}${apiPath}`;
|
|
27
|
+
}
|
|
28
|
+
function getHeaders() {
|
|
29
|
+
const headers = {
|
|
30
|
+
"Content-Type": "application/json",
|
|
31
|
+
};
|
|
32
|
+
if (apiKey) {
|
|
33
|
+
headers["Authorization"] = `Bearer ${apiKey}`;
|
|
34
|
+
}
|
|
35
|
+
const ctx = requestContext.getStore();
|
|
36
|
+
const userId = ctx?.userId ?? process.env.KYNVER_USER_ID?.trim();
|
|
37
|
+
const serviceSecret = process.env.KYNVER_SERVICE_SECRET?.trim();
|
|
38
|
+
if (userId) {
|
|
39
|
+
if (!serviceSecret) {
|
|
40
|
+
throw new Error("X-Kynver-User-Id was set in context but KYNVER_SERVICE_SECRET is missing; refusing to send an unscoped request.");
|
|
41
|
+
}
|
|
42
|
+
headers["X-Kynver-User-Id"] = userId;
|
|
43
|
+
headers["X-Kynver-Service-Secret"] = serviceSecret;
|
|
44
|
+
}
|
|
45
|
+
return headers;
|
|
46
|
+
}
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
// Response handling
|
|
49
|
+
// ---------------------------------------------------------------------------
|
|
50
|
+
async function handleResponse(res) {
|
|
51
|
+
const text = await res.text();
|
|
52
|
+
if (!res.ok) {
|
|
53
|
+
let msg = text;
|
|
54
|
+
try {
|
|
55
|
+
const j = JSON.parse(text);
|
|
56
|
+
msg = j.error ?? j.message ?? text;
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
// use text as-is
|
|
60
|
+
}
|
|
61
|
+
throw new Error(msg || `HTTP ${res.status}`);
|
|
62
|
+
}
|
|
63
|
+
if (!text)
|
|
64
|
+
return undefined;
|
|
65
|
+
try {
|
|
66
|
+
return JSON.parse(text);
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
return text;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
// ---------------------------------------------------------------------------
|
|
73
|
+
// HTTP methods
|
|
74
|
+
// ---------------------------------------------------------------------------
|
|
75
|
+
export async function get(path) {
|
|
76
|
+
const res = await fetch(url(path), {
|
|
77
|
+
method: "GET",
|
|
78
|
+
headers: getHeaders(),
|
|
79
|
+
});
|
|
80
|
+
return handleResponse(res);
|
|
81
|
+
}
|
|
82
|
+
export async function post(path, body) {
|
|
83
|
+
const res = await fetch(url(path), {
|
|
84
|
+
method: "POST",
|
|
85
|
+
headers: getHeaders(),
|
|
86
|
+
body: JSON.stringify(body),
|
|
87
|
+
});
|
|
88
|
+
return handleResponse(res);
|
|
89
|
+
}
|
|
90
|
+
export async function patch(path, body) {
|
|
91
|
+
const res = await fetch(url(path), {
|
|
92
|
+
method: "PATCH",
|
|
93
|
+
headers: getHeaders(),
|
|
94
|
+
body: JSON.stringify(body),
|
|
95
|
+
});
|
|
96
|
+
return handleResponse(res);
|
|
97
|
+
}
|
|
98
|
+
export async function del(path) {
|
|
99
|
+
const res = await fetch(url(path), {
|
|
100
|
+
method: "DELETE",
|
|
101
|
+
headers: getHeaders(),
|
|
102
|
+
});
|
|
103
|
+
return handleResponse(res);
|
|
104
|
+
}
|
|
105
|
+
export function hasApiKey() {
|
|
106
|
+
return !!apiKey;
|
|
107
|
+
}
|
|
108
|
+
export function noApiKeyMessage() {
|
|
109
|
+
return "No API key configured. Set KYNVER_API_KEY in your MCP server config.";
|
|
110
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { type IncomingMessage } from "node:http";
|
|
2
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
export declare const MAX_SSE_BODY_BYTES: number;
|
|
4
|
+
export type SessionAuth = {
|
|
5
|
+
kind: "apiKey";
|
|
6
|
+
apiKey: string;
|
|
7
|
+
} | {
|
|
8
|
+
kind: "internal";
|
|
9
|
+
userId: string;
|
|
10
|
+
};
|
|
11
|
+
type RequestContextStore = {
|
|
12
|
+
run: <T>(store: {
|
|
13
|
+
userId?: string;
|
|
14
|
+
}, fn: () => Promise<T>) => Promise<T>;
|
|
15
|
+
};
|
|
16
|
+
export type StartKynverMcpSseServerOptions = {
|
|
17
|
+
label: string;
|
|
18
|
+
defaultPort: number;
|
|
19
|
+
createMcpServer: () => McpServer | Promise<McpServer>;
|
|
20
|
+
requestContext: RequestContextStore;
|
|
21
|
+
};
|
|
22
|
+
export declare function headerValue(req: IncomingMessage, name: string): string | null;
|
|
23
|
+
export declare function timingSafeEqualString(a: string, b: string): boolean;
|
|
24
|
+
export declare function hasForbiddenQueryAuth(url: URL): boolean;
|
|
25
|
+
export declare function resolveListenHost(): string;
|
|
26
|
+
export declare function parseJsonBody(req: IncomingMessage, maxBytes?: number): Promise<unknown>;
|
|
27
|
+
export declare function validateMcporterServerName(name: string): string;
|
|
28
|
+
export declare function startKynverMcpSseServer(options: StartKynverMcpSseServerOptions): void;
|
|
29
|
+
export {};
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
// @generated — synced from packages/kynver-mcp-sse-lib/src/index.ts via scripts/sync-mcp-sse-server.mjs
|
|
2
|
+
// Do not edit by hand; change the canonical file and re-run the sync script.
|
|
3
|
+
import { createServer } from "node:http";
|
|
4
|
+
import { timingSafeEqual } from "node:crypto";
|
|
5
|
+
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
|
|
6
|
+
export const MAX_SSE_BODY_BYTES = 1 * 1024 * 1024;
|
|
7
|
+
const FORBIDDEN_QUERY_KEYS = ["secret", "userId"];
|
|
8
|
+
export function headerValue(req, name) {
|
|
9
|
+
const v = req.headers[name];
|
|
10
|
+
return typeof v === "string" ? v : Array.isArray(v) ? (v[0] ?? null) : null;
|
|
11
|
+
}
|
|
12
|
+
export function timingSafeEqualString(a, b) {
|
|
13
|
+
const left = Buffer.from(a);
|
|
14
|
+
const right = Buffer.from(b);
|
|
15
|
+
if (left.length !== right.length)
|
|
16
|
+
return false;
|
|
17
|
+
return timingSafeEqual(left, right);
|
|
18
|
+
}
|
|
19
|
+
export function hasForbiddenQueryAuth(url) {
|
|
20
|
+
return FORBIDDEN_QUERY_KEYS.some((key) => url.searchParams.has(key));
|
|
21
|
+
}
|
|
22
|
+
function queryApiKey(url) {
|
|
23
|
+
return url.searchParams.get("apiKey")?.trim() ?? "";
|
|
24
|
+
}
|
|
25
|
+
export function resolveListenHost() {
|
|
26
|
+
const configured = process.env.MCP_SSE_HOST?.trim();
|
|
27
|
+
return configured || "127.0.0.1";
|
|
28
|
+
}
|
|
29
|
+
export function parseJsonBody(req, maxBytes = MAX_SSE_BODY_BYTES) {
|
|
30
|
+
return new Promise((resolve, reject) => {
|
|
31
|
+
const chunks = [];
|
|
32
|
+
let totalSize = 0;
|
|
33
|
+
req.on("data", (chunk) => {
|
|
34
|
+
totalSize += chunk.length;
|
|
35
|
+
if (totalSize > maxBytes) {
|
|
36
|
+
req.destroy();
|
|
37
|
+
reject(new Error("Request body too large"));
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
chunks.push(chunk);
|
|
41
|
+
});
|
|
42
|
+
req.on("end", () => {
|
|
43
|
+
try {
|
|
44
|
+
resolve(JSON.parse(Buffer.concat(chunks).toString()));
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
reject(error);
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
req.on("error", reject);
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
export function validateMcporterServerName(name) {
|
|
54
|
+
const trimmed = name.trim();
|
|
55
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(trimmed)) {
|
|
56
|
+
throw new Error("Invalid mcporter server name.");
|
|
57
|
+
}
|
|
58
|
+
return trimmed;
|
|
59
|
+
}
|
|
60
|
+
function bearerToken(req) {
|
|
61
|
+
const auth = headerValue(req, "authorization");
|
|
62
|
+
if (!auth)
|
|
63
|
+
return "";
|
|
64
|
+
const match = /^Bearer\s+(.+)$/i.exec(auth);
|
|
65
|
+
return match?.[1]?.trim() ?? "";
|
|
66
|
+
}
|
|
67
|
+
function resolveInternalAuth(req, serviceSecret) {
|
|
68
|
+
const internalUserId = headerValue(req, "x-kynver-user-id");
|
|
69
|
+
const internalSecret = headerValue(req, "x-kynver-service-secret");
|
|
70
|
+
if (!internalUserId || !serviceSecret || !internalSecret)
|
|
71
|
+
return null;
|
|
72
|
+
if (!timingSafeEqualString(internalSecret, serviceSecret))
|
|
73
|
+
return null;
|
|
74
|
+
return { userId: internalUserId, auth: { kind: "internal", userId: internalUserId } };
|
|
75
|
+
}
|
|
76
|
+
function verifyPostAuth(req, url, sessionAuth, serviceSecret) {
|
|
77
|
+
if (sessionAuth.kind === "apiKey") {
|
|
78
|
+
const token = bearerToken(req) || queryApiKey(url);
|
|
79
|
+
return token.length > 0 && timingSafeEqualString(token, sessionAuth.apiKey);
|
|
80
|
+
}
|
|
81
|
+
const internal = resolveInternalAuth(req, serviceSecret);
|
|
82
|
+
return (internal !== null &&
|
|
83
|
+
internal.userId === sessionAuth.userId &&
|
|
84
|
+
internal.auth.kind === "internal");
|
|
85
|
+
}
|
|
86
|
+
async function resolveUserFromApiKey(baseUrl, apiKey) {
|
|
87
|
+
try {
|
|
88
|
+
const res = await fetch(`${baseUrl.replace(/\/$/, "")}/api/users/me`, {
|
|
89
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
90
|
+
});
|
|
91
|
+
if (!res.ok)
|
|
92
|
+
return null;
|
|
93
|
+
const data = (await res.json());
|
|
94
|
+
return data.id ?? null;
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
function parseUrl(req, port) {
|
|
101
|
+
return new URL(req.url ?? "/", `http://localhost:${port}`);
|
|
102
|
+
}
|
|
103
|
+
function json(res, status, body) {
|
|
104
|
+
res.writeHead(status, { "Content-Type": "application/json" });
|
|
105
|
+
res.end(JSON.stringify(body));
|
|
106
|
+
}
|
|
107
|
+
export function startKynverMcpSseServer(options) {
|
|
108
|
+
const port = parseInt(process.env.PORT ?? String(options.defaultPort), 10);
|
|
109
|
+
const baseUrl = process.env.KYNVER_API_URL ?? "https://kynver.com";
|
|
110
|
+
const serviceSecret = process.env.KYNVER_SERVICE_SECRET ?? "";
|
|
111
|
+
const host = resolveListenHost();
|
|
112
|
+
const sessions = {};
|
|
113
|
+
const httpServer = createServer(async (req, res) => {
|
|
114
|
+
const url = parseUrl(req, port);
|
|
115
|
+
const path = url.pathname;
|
|
116
|
+
if (req.method === "GET" && path === "/sse") {
|
|
117
|
+
if (hasForbiddenQueryAuth(url)) {
|
|
118
|
+
json(res, 400, {
|
|
119
|
+
error: "Internal auth via query params is not allowed. Use X-Kynver-User-Id and X-Kynver-Service-Secret headers.",
|
|
120
|
+
});
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
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
|
+
let userId = null;
|
|
127
|
+
let auth = null;
|
|
128
|
+
const internal = serviceSecret ? resolveInternalAuth(req, serviceSecret) : null;
|
|
129
|
+
if (internal) {
|
|
130
|
+
userId = internal.userId;
|
|
131
|
+
auth = internal.auth;
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
const apiKey = bearerToken(req) || queryApiKey(url);
|
|
135
|
+
if (apiKey) {
|
|
136
|
+
userId = await resolveUserFromApiKey(baseUrl, apiKey);
|
|
137
|
+
if (userId)
|
|
138
|
+
auth = { kind: "apiKey", apiKey };
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
if (!userId || !auth) {
|
|
142
|
+
json(res, 401, {
|
|
143
|
+
error: "Unauthorized. Pass API key via Authorization: Bearer <key> or internal X-Kynver-User-Id + X-Kynver-Service-Secret headers.",
|
|
144
|
+
});
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
console.log(`[MCP SSE] New connection (${options.label}, user: ${userId.slice(0, 8)}...)`);
|
|
148
|
+
const transport = new SSEServerTransport("/messages", res);
|
|
149
|
+
sessions[transport.sessionId] = { transport, userId, auth };
|
|
150
|
+
res.on("close", () => {
|
|
151
|
+
delete sessions[transport.sessionId];
|
|
152
|
+
});
|
|
153
|
+
const server = await options.createMcpServer();
|
|
154
|
+
await server.connect(transport);
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
if (req.method === "POST" && path === "/messages") {
|
|
158
|
+
const sessionId = url.searchParams.get("sessionId");
|
|
159
|
+
const entry = sessionId ? sessions[sessionId] : undefined;
|
|
160
|
+
if (!entry) {
|
|
161
|
+
json(res, 400, { error: "No active SSE session for this sessionId" });
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
if (!verifyPostAuth(req, url, entry.auth, serviceSecret)) {
|
|
165
|
+
json(res, 401, { error: "Unauthorized. Re-send the same credentials used to open the SSE session." });
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
try {
|
|
169
|
+
const body = await parseJsonBody(req);
|
|
170
|
+
await options.requestContext.run({ userId: entry.userId }, async () => {
|
|
171
|
+
await entry.transport.handlePostMessage(req, res, body);
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
catch (error) {
|
|
175
|
+
json(res, 400, {
|
|
176
|
+
error: error instanceof Error ? error.message : "Invalid request body",
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
if (req.method === "GET" && path === "/health") {
|
|
182
|
+
res.writeHead(200, { "Content-Type": "text/plain" });
|
|
183
|
+
res.end("ok");
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
json(res, 404, { error: "Not found" });
|
|
187
|
+
});
|
|
188
|
+
httpServer.listen(port, host, () => {
|
|
189
|
+
console.log(`[MCP SSE] ${options.label} listening on ${host}:${port}`);
|
|
190
|
+
});
|
|
191
|
+
}
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Creates and configures the estimator MCP server with all tools registered.
|
|
3
|
+
* Separated from transport so it can be used with stdio (CLI) or SSE (hosted).
|
|
4
|
+
*/
|
|
5
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
6
|
+
export declare function createEstimatorServer(): McpServer;
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Creates and configures the estimator MCP server with all tools registered.
|
|
3
|
+
* Separated from transport so it can be used with stdio (CLI) or SSE (hosted).
|
|
4
|
+
*/
|
|
5
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
6
|
+
import * as catalogTools from "./tools/catalog-tools.js";
|
|
7
|
+
import * as estimateTools from "./tools/estimate-tools.js";
|
|
8
|
+
import * as templateTools from "./tools/template-tools.js";
|
|
9
|
+
import * as versionTools from "./tools/version-tools.js";
|
|
10
|
+
import * as photoTools from "./tools/photo-tools.js";
|
|
11
|
+
import * as statsPdfTools from "./tools/stats-pdf-tools.js";
|
|
12
|
+
import * as visionTools from "./tools/vision-tools.js";
|
|
13
|
+
import * as companyTools from "./tools/company-tools.js";
|
|
14
|
+
import * as clientTools from "./tools/client-tools.js";
|
|
15
|
+
function toolResult(text) {
|
|
16
|
+
return { content: [{ type: "text", text }] };
|
|
17
|
+
}
|
|
18
|
+
export function createEstimatorServer() {
|
|
19
|
+
const server = new McpServer({ name: "kynver-mcp-estimator", version: "0.1.0" }, { capabilities: { tools: {} } });
|
|
20
|
+
function register(name, config, run) {
|
|
21
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
22
|
+
server.registerTool(name, config, async (args) => toolResult(await run(args)));
|
|
23
|
+
}
|
|
24
|
+
// --- Catalog tools (8) ---
|
|
25
|
+
register(catalogTools.importCatalog.name, { description: catalogTools.importCatalog.description, inputSchema: catalogTools.importCatalog.inputSchemaZod }, (args) => catalogTools.importCatalog.execute(args));
|
|
26
|
+
register(catalogTools.reviewImport.name, { description: catalogTools.reviewImport.description, inputSchema: catalogTools.reviewImport.inputSchemaZod }, (args) => catalogTools.reviewImport.execute(args));
|
|
27
|
+
register(catalogTools.commitImport.name, { description: catalogTools.commitImport.description, inputSchema: catalogTools.commitImport.inputSchemaZod }, (args) => catalogTools.commitImport.execute(args));
|
|
28
|
+
register(catalogTools.setupPrice.name, { description: catalogTools.setupPrice.description, inputSchema: catalogTools.setupPrice.inputSchemaZod }, (args) => catalogTools.setupPrice.execute(args));
|
|
29
|
+
register(catalogTools.updatePrice.name, { description: catalogTools.updatePrice.description, inputSchema: catalogTools.updatePrice.inputSchemaZod }, (args) => catalogTools.updatePrice.execute(args));
|
|
30
|
+
register(catalogTools.getCatalog.name, { description: catalogTools.getCatalog.description, inputSchema: catalogTools.getCatalog.inputSchemaZod }, (args) => catalogTools.getCatalog.execute(args));
|
|
31
|
+
register(catalogTools.getConfig.name, { description: catalogTools.getConfig.description, inputSchema: catalogTools.getConfig.inputSchemaZod }, (args) => catalogTools.getConfig.execute(args));
|
|
32
|
+
register(catalogTools.updateConfig.name, { description: catalogTools.updateConfig.description, inputSchema: catalogTools.updateConfig.inputSchemaZod }, (args) => catalogTools.updateConfig.execute(args));
|
|
33
|
+
register(catalogTools.importTemplate.name, { description: catalogTools.importTemplate.description, inputSchema: catalogTools.importTemplate.inputSchemaZod }, (args) => catalogTools.importTemplate.execute(args));
|
|
34
|
+
// --- Estimate tools (11) ---
|
|
35
|
+
register(estimateTools.createReplacement.name, { description: estimateTools.createReplacement.description, inputSchema: estimateTools.createReplacement.inputSchemaZod }, (args) => estimateTools.createReplacement.execute(args));
|
|
36
|
+
register(estimateTools.createRepair.name, { description: estimateTools.createRepair.description, inputSchema: estimateTools.createRepair.inputSchemaZod }, (args) => estimateTools.createRepair.execute(args));
|
|
37
|
+
register(estimateTools.getEstimate.name, { description: estimateTools.getEstimate.description, inputSchema: estimateTools.getEstimate.inputSchemaZod }, (args) => estimateTools.getEstimate.execute(args));
|
|
38
|
+
register(estimateTools.listEstimates.name, { description: estimateTools.listEstimates.description, inputSchema: estimateTools.listEstimates.inputSchemaZod }, (args) => estimateTools.listEstimates.execute(args));
|
|
39
|
+
register(estimateTools.addLineItem.name, { description: estimateTools.addLineItem.description, inputSchema: estimateTools.addLineItem.inputSchemaZod }, (args) => estimateTools.addLineItem.execute(args));
|
|
40
|
+
register(estimateTools.removeLineItem.name, { description: estimateTools.removeLineItem.description, inputSchema: estimateTools.removeLineItem.inputSchemaZod }, (args) => estimateTools.removeLineItem.execute(args));
|
|
41
|
+
register(estimateTools.updateLineItem.name, { description: estimateTools.updateLineItem.description, inputSchema: estimateTools.updateLineItem.inputSchemaZod }, (args) => estimateTools.updateLineItem.execute(args));
|
|
42
|
+
register(estimateTools.adjustToTarget.name, { description: estimateTools.adjustToTarget.description, inputSchema: estimateTools.adjustToTarget.inputSchemaZod }, (args) => estimateTools.adjustToTarget.execute(args));
|
|
43
|
+
register(estimateTools.updateEstimate.name, { description: estimateTools.updateEstimate.description, inputSchema: estimateTools.updateEstimate.inputSchemaZod }, (args) => estimateTools.updateEstimate.execute(args));
|
|
44
|
+
register(estimateTools.deleteEstimate.name, { description: estimateTools.deleteEstimate.description, inputSchema: estimateTools.deleteEstimate.inputSchemaZod }, (args) => estimateTools.deleteEstimate.execute(args));
|
|
45
|
+
register(estimateTools.duplicateEstimate.name, { description: estimateTools.duplicateEstimate.description, inputSchema: estimateTools.duplicateEstimate.inputSchemaZod }, (args) => estimateTools.duplicateEstimate.execute(args));
|
|
46
|
+
// --- Template tools (4) ---
|
|
47
|
+
register(templateTools.saveTemplate.name, { description: templateTools.saveTemplate.description, inputSchema: templateTools.saveTemplate.inputSchemaZod }, (args) => templateTools.saveTemplate.execute(args));
|
|
48
|
+
register(templateTools.listTemplates.name, { description: templateTools.listTemplates.description, inputSchema: templateTools.listTemplates.inputSchemaZod }, (args) => templateTools.listTemplates.execute(args));
|
|
49
|
+
register(templateTools.deleteTemplate.name, { description: templateTools.deleteTemplate.description, inputSchema: templateTools.deleteTemplate.inputSchemaZod }, (args) => templateTools.deleteTemplate.execute(args));
|
|
50
|
+
register(templateTools.createFromTemplate.name, { description: templateTools.createFromTemplate.description, inputSchema: templateTools.createFromTemplate.inputSchemaZod }, (args) => templateTools.createFromTemplate.execute(args));
|
|
51
|
+
// --- Version tools (2) ---
|
|
52
|
+
register(versionTools.listVersions.name, { description: versionTools.listVersions.description, inputSchema: versionTools.listVersions.inputSchemaZod }, (args) => versionTools.listVersions.execute(args));
|
|
53
|
+
register(versionTools.getVersion.name, { description: versionTools.getVersion.description, inputSchema: versionTools.getVersion.inputSchemaZod }, (args) => versionTools.getVersion.execute(args));
|
|
54
|
+
// --- Photo tools (3) ---
|
|
55
|
+
register(photoTools.uploadPhoto.name, { description: photoTools.uploadPhoto.description, inputSchema: photoTools.uploadPhoto.inputSchemaZod }, (args) => photoTools.uploadPhoto.execute(args));
|
|
56
|
+
register(photoTools.listPhotos.name, { description: photoTools.listPhotos.description, inputSchema: photoTools.listPhotos.inputSchemaZod }, (args) => photoTools.listPhotos.execute(args));
|
|
57
|
+
register(photoTools.deletePhoto.name, { description: photoTools.deletePhoto.description, inputSchema: photoTools.deletePhoto.inputSchemaZod }, (args) => photoTools.deletePhoto.execute(args));
|
|
58
|
+
// --- Stats / PDF tools (3) ---
|
|
59
|
+
register(statsPdfTools.getStats.name, { description: statsPdfTools.getStats.description, inputSchema: statsPdfTools.getStats.inputSchemaZod }, (args) => statsPdfTools.getStats.execute(args));
|
|
60
|
+
register(statsPdfTools.exportPdf.name, { description: statsPdfTools.exportPdf.description, inputSchema: statsPdfTools.exportPdf.inputSchemaZod }, (args) => statsPdfTools.exportPdf.execute(args));
|
|
61
|
+
register(statsPdfTools.updateOutputConfig.name, { description: statsPdfTools.updateOutputConfig.description, inputSchema: statsPdfTools.updateOutputConfig.inputSchemaZod }, (args) => statsPdfTools.updateOutputConfig.execute(args));
|
|
62
|
+
// --- Vision tools (2) ---
|
|
63
|
+
register(visionTools.analyzePhotos.name, { description: visionTools.analyzePhotos.description, inputSchema: visionTools.analyzePhotos.inputSchemaZod }, (args) => visionTools.analyzePhotos.execute(args));
|
|
64
|
+
register(visionTools.analyzeDocument.name, { description: visionTools.analyzeDocument.description, inputSchema: visionTools.analyzeDocument.inputSchemaZod }, (args) => visionTools.analyzeDocument.execute(args));
|
|
65
|
+
// --- Company tools (5) ---
|
|
66
|
+
register(companyTools.createCompany.name, { description: companyTools.createCompany.description, inputSchema: companyTools.createCompany.inputSchemaZod }, (args) => companyTools.createCompany.execute(args));
|
|
67
|
+
register(companyTools.listCompanies.name, { description: companyTools.listCompanies.description, inputSchema: companyTools.listCompanies.inputSchemaZod }, (args) => companyTools.listCompanies.execute(args));
|
|
68
|
+
register(companyTools.getCompany.name, { description: companyTools.getCompany.description, inputSchema: companyTools.getCompany.inputSchemaZod }, (args) => companyTools.getCompany.execute(args));
|
|
69
|
+
register(companyTools.updateCompany.name, { description: companyTools.updateCompany.description, inputSchema: companyTools.updateCompany.inputSchemaZod }, (args) => companyTools.updateCompany.execute(args));
|
|
70
|
+
register(companyTools.deleteCompany.name, { description: companyTools.deleteCompany.description, inputSchema: companyTools.deleteCompany.inputSchemaZod }, (args) => companyTools.deleteCompany.execute(args));
|
|
71
|
+
// --- Client tools (5) ---
|
|
72
|
+
register(clientTools.createClient.name, { description: clientTools.createClient.description, inputSchema: clientTools.createClient.inputSchemaZod }, (args) => clientTools.createClient.execute(args));
|
|
73
|
+
register(clientTools.listClients.name, { description: clientTools.listClients.description, inputSchema: clientTools.listClients.inputSchemaZod }, (args) => clientTools.listClients.execute(args));
|
|
74
|
+
register(clientTools.getClient.name, { description: clientTools.getClient.description, inputSchema: clientTools.getClient.inputSchemaZod }, (args) => clientTools.getClient.execute(args));
|
|
75
|
+
register(clientTools.updateClient.name, { description: clientTools.updateClient.description, inputSchema: clientTools.updateClient.inputSchemaZod }, (args) => clientTools.updateClient.execute(args));
|
|
76
|
+
register(clientTools.deleteClient.name, { description: clientTools.deleteClient.description, inputSchema: clientTools.deleteClient.inputSchemaZod }, (args) => clientTools.deleteClient.execute(args));
|
|
77
|
+
return server;
|
|
78
|
+
}
|
package/dist/sse.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/sse.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { startKynverMcpSseServer } from "./lib/mcp-sse-server.js";
|
|
2
|
+
import { createEstimatorServer } from "./server.js";
|
|
3
|
+
import { requestContext } from "./lib/kynver-client.js";
|
|
4
|
+
startKynverMcpSseServer({
|
|
5
|
+
label: "Estimator server",
|
|
6
|
+
defaultPort: 3100,
|
|
7
|
+
createMcpServer: createEstimatorServer,
|
|
8
|
+
requestContext,
|
|
9
|
+
});
|