feat(mcps): add kabelfachmann MCP with Kabelhandbuch integration and remove legacy PM2 orchestration
Some checks failed
Monorepo Pipeline / ⚡ Prioritize Release (push) Successful in 2s
Monorepo Pipeline / 🧪 Test (push) Successful in 1m6s
Monorepo Pipeline / 🏗️ Build (push) Successful in 2m52s
Monorepo Pipeline / 🧹 Lint (push) Successful in 3m1s
Monorepo Pipeline / 🚀 Release (push) Has been skipped
Monorepo Pipeline / 🐳 Build Gatekeeper (Product) (push) Has been skipped
Monorepo Pipeline / 🐳 Build Build-Base (push) Has been skipped
Monorepo Pipeline / 🐳 Build Production Runtime (push) Has been skipped
🏥 Server Maintenance / 🧹 Prune & Clean (push) Failing after 4s
Some checks failed
Monorepo Pipeline / ⚡ Prioritize Release (push) Successful in 2s
Monorepo Pipeline / 🧪 Test (push) Successful in 1m6s
Monorepo Pipeline / 🏗️ Build (push) Successful in 2m52s
Monorepo Pipeline / 🧹 Lint (push) Successful in 3m1s
Monorepo Pipeline / 🚀 Release (push) Has been skipped
Monorepo Pipeline / 🐳 Build Gatekeeper (Product) (push) Has been skipped
Monorepo Pipeline / 🐳 Build Build-Base (push) Has been skipped
Monorepo Pipeline / 🐳 Build Production Runtime (push) Has been skipped
🏥 Server Maintenance / 🧹 Prune & Clean (push) Failing after 4s
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -51,3 +51,7 @@ apps/web/out/estimations/
|
||||
# Memory MCP
|
||||
data/qdrant/
|
||||
packages/memory-mcp/models/
|
||||
|
||||
# Kabelfachmann MCP
|
||||
packages/kabelfachmann-mcp/data/
|
||||
packages/kabelfachmann-mcp/models/
|
||||
@@ -3,8 +3,8 @@ services:
|
||||
image: qdrant/qdrant:latest
|
||||
container_name: qdrant-mcp
|
||||
ports:
|
||||
- "6333:6333"
|
||||
- "6334:6334"
|
||||
- "6335:6333"
|
||||
- "6336:6334"
|
||||
volumes:
|
||||
- ./data/qdrant:/qdrant/storage
|
||||
restart: unless-stopped
|
||||
@@ -85,6 +85,20 @@ services:
|
||||
networks:
|
||||
- mcp-network
|
||||
|
||||
kabelfachmann-mcp:
|
||||
build:
|
||||
context: ./packages/kabelfachmann-mcp
|
||||
container_name: kabelfachmann-mcp
|
||||
env_file:
|
||||
- .env
|
||||
ports:
|
||||
- "3007:3007"
|
||||
depends_on:
|
||||
- qdrant
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- mcp-network
|
||||
|
||||
networks:
|
||||
mcp-network:
|
||||
driver: bridge
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
module.exports = {
|
||||
apps: [
|
||||
{
|
||||
name: 'gitea-mcp',
|
||||
script: 'node',
|
||||
args: 'dist/start.js',
|
||||
cwd: './packages/gitea-mcp',
|
||||
watch: false,
|
||||
},
|
||||
{
|
||||
name: 'memory-mcp',
|
||||
script: 'node',
|
||||
args: 'dist/start.js',
|
||||
cwd: './packages/memory-mcp',
|
||||
watch: false,
|
||||
},
|
||||
{
|
||||
name: 'umami-mcp',
|
||||
script: 'node',
|
||||
args: 'dist/start.js',
|
||||
cwd: './packages/umami-mcp',
|
||||
watch: false,
|
||||
},
|
||||
{
|
||||
name: 'serpbear-mcp',
|
||||
script: 'node',
|
||||
args: 'dist/start.js',
|
||||
cwd: './packages/serpbear-mcp',
|
||||
watch: false,
|
||||
},
|
||||
{
|
||||
name: 'glitchtip-mcp',
|
||||
script: 'node',
|
||||
args: 'dist/start.js',
|
||||
cwd: './packages/glitchtip-mcp',
|
||||
watch: false,
|
||||
},
|
||||
{
|
||||
name: 'klz-payload-mcp',
|
||||
script: 'node',
|
||||
args: 'dist/start.js',
|
||||
cwd: './packages/klz-payload-mcp',
|
||||
watch: false,
|
||||
},
|
||||
]
|
||||
};
|
||||
|
||||
|
||||
@@ -7,11 +7,10 @@
|
||||
"dev": "pnpm -r dev",
|
||||
"dev:gatekeeper": "bash -c 'trap \"COMPOSE_PROJECT_NAME=gatekeeper docker-compose -f docker-compose.gatekeeper.yml down\" EXIT INT TERM; docker network create infra 2>/dev/null || true && COMPOSE_PROJECT_NAME=gatekeeper docker-compose -f docker-compose.gatekeeper.yml down && COMPOSE_PROJECT_NAME=gatekeeper docker-compose -f docker-compose.gatekeeper.yml up --build --remove-orphans'",
|
||||
"dev:mcps:up": "docker-compose -f docker-compose.mcps.yml up -d",
|
||||
"dev:mcps:down": "docker-compose -f docker-compose.mcps.yml down && pm2 delete ecosystem.mcps.config.cjs || true",
|
||||
"dev:mcps:down": "docker-compose -f docker-compose.mcps.yml down",
|
||||
"dev:mcps:watch": "pnpm -r --filter=\"./packages/*-mcp\" exec tsc -w",
|
||||
"dev:mcps": "npm run dev:mcps:up && pm2 start ecosystem.mcps.config.cjs --watch && npm run dev:mcps:watch",
|
||||
"start:mcps:run": "pm2 start ecosystem.mcps.config.cjs",
|
||||
"start:mcps": "npm run dev:mcps:up && npm run start:mcps:run",
|
||||
"dev:mcps": "npm run dev:mcps:up && npm run dev:mcps:watch",
|
||||
"start:mcps": "npm run dev:mcps:up",
|
||||
"lint": "pnpm -r --filter='./packages/**' --filter='./apps/**' lint",
|
||||
"test": "pnpm -r test",
|
||||
"changeset": "changeset",
|
||||
|
||||
11
packages/kabelfachmann-mcp/Dockerfile
Normal file
11
packages/kabelfachmann-mcp/Dockerfile
Normal file
@@ -0,0 +1,11 @@
|
||||
FROM node:20-slim
|
||||
WORKDIR /app
|
||||
COPY package.json ./
|
||||
|
||||
# Install prod dependencies
|
||||
RUN npm install --omit=dev --legacy-peer-deps
|
||||
|
||||
COPY ./dist ./dist
|
||||
COPY ./data ./data
|
||||
|
||||
ENTRYPOINT ["node", "dist/index.js"]
|
||||
31
packages/kabelfachmann-mcp/package.json
Normal file
31
packages/kabelfachmann-mcp/package.json
Normal file
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"name": "@mintel/kabelfachmann-mcp",
|
||||
"version": "1.0.0",
|
||||
"description": "Kabelfachmann MCP server",
|
||||
"main": "dist/index.js",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"start": "node dist/index.js",
|
||||
"dev": "tsx watch src/index.ts",
|
||||
"ingest": "tsx src/ingest.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/sdk": "^1.5.0",
|
||||
"@qdrant/js-client-rest": "^1.12.0",
|
||||
"@xenova/transformers": "^2.17.2",
|
||||
"dotenv": "^17.3.1",
|
||||
"express": "^5.2.1",
|
||||
"node-fetch": "^3.3.2",
|
||||
"onnxruntime-node": "^1.14.0",
|
||||
"pdf-parse": "^1.1.1",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/express": "^5.0.6",
|
||||
"@types/node": "^20.14.10",
|
||||
"@types/pdf-parse": "^1.1.4",
|
||||
"tsx": "^4.19.1",
|
||||
"typescript": "^5.5.3"
|
||||
}
|
||||
}
|
||||
142
packages/kabelfachmann-mcp/src/index.ts
Normal file
142
packages/kabelfachmann-mcp/src/index.ts
Normal file
@@ -0,0 +1,142 @@
|
||||
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
|
||||
import express from "express";
|
||||
import { z } from "zod";
|
||||
import { QdrantMemoryService } from "./qdrant.js";
|
||||
import { askOpenRouter } from "./llm.js";
|
||||
|
||||
async function main() {
|
||||
const server = new McpServer({
|
||||
name: "@mintel/kabelfachmann-mcp",
|
||||
version: "1.0.0",
|
||||
});
|
||||
|
||||
const qdrantService = new QdrantMemoryService();
|
||||
|
||||
server.tool(
|
||||
"ask_kabelfachmann",
|
||||
"Ask the KLZ Kabelfachmann a question about cables based on the KLZ technical handbook. This consultant knows everything about cable specifications, geometries, weights, ampacity (Strombelastbarkeit), and materials.",
|
||||
{
|
||||
query: z
|
||||
.string()
|
||||
.describe(
|
||||
"The user's question regarding cables or a specific cable type.",
|
||||
),
|
||||
},
|
||||
async (args) => {
|
||||
console.error(`Kabelfachmann received query: ${args.query}`);
|
||||
|
||||
// Retrieve relevant chunks from the handbook
|
||||
const results = await qdrantService.retrieveMemory(args.query, 10);
|
||||
|
||||
const contextText = results
|
||||
.map(
|
||||
(r) =>
|
||||
`--- Excerpt (Relevance: ${r.score.toFixed(2)}) ---\n${r.content}`,
|
||||
)
|
||||
.join("\n\n");
|
||||
|
||||
if (!contextText) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: "Der Kabelfachmann konnte keine relevanten Informationen im Handbuch finden.",
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
const systemPrompt = `Du bist der "KLZ Kabelfachmann" (KLZ Cable Expert), ein professioneller beratender KI-Experte.
|
||||
Du arbeitest für die Kabeltechnik-Firma "KLZ".
|
||||
Beantworte die folgende Frage des Nutzers fachlich absolut korrekt und **nur** basierend auf den bereitgestellten Auszügen aus dem KLZ Kabelhandbuch.
|
||||
Wenn die Information nicht im Kontext enthalten ist, sage höflich, dass dir dazu keine KLZ-Daten vorliegen. Erfinde niemals Spezifikationen oder Daten.
|
||||
Halte dich relativ knapp und präzise, aber professionell (Siezen).
|
||||
Hier ist der Kontext aus dem Handbuch:
|
||||
|
||||
${contextText}`;
|
||||
|
||||
try {
|
||||
const answer = await askOpenRouter(systemPrompt, args.query);
|
||||
return {
|
||||
content: [{ type: "text", text: answer }],
|
||||
};
|
||||
} catch (error: any) {
|
||||
console.error("Error querying OpenRouter:", error);
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Ein Fehler ist bei der KI-Anfrage aufgetreten: ${error.message}`,
|
||||
},
|
||||
],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
const isStdio = process.argv.includes("--stdio");
|
||||
|
||||
if (isStdio) {
|
||||
const { StdioServerTransport } =
|
||||
await import("@modelcontextprotocol/sdk/server/stdio.js");
|
||||
const transport = new StdioServerTransport();
|
||||
await server.connect(transport);
|
||||
console.error("Kabelfachmann MCP server is running on stdio");
|
||||
|
||||
try {
|
||||
await qdrantService.initialize();
|
||||
} catch (e) {
|
||||
console.error("Failed to initialize local dependencies:", e);
|
||||
}
|
||||
} else {
|
||||
const app = express();
|
||||
let transport: SSEServerTransport | null = null;
|
||||
|
||||
app.get("/sse", async (req, res) => {
|
||||
console.error("New SSE connection established");
|
||||
|
||||
if (transport) {
|
||||
try {
|
||||
await transport.close();
|
||||
} catch (e) {
|
||||
console.error("Error closing previous transport:", e);
|
||||
}
|
||||
}
|
||||
|
||||
transport = new SSEServerTransport("/message", res);
|
||||
try {
|
||||
await server.connect(transport);
|
||||
} catch (e) {
|
||||
console.error("Failed to connect new transport:", e);
|
||||
}
|
||||
});
|
||||
|
||||
app.post("/message", async (req, res) => {
|
||||
if (!transport) {
|
||||
res.status(400).send("No active SSE connection");
|
||||
return;
|
||||
}
|
||||
await transport.handlePostMessage(req, res);
|
||||
});
|
||||
|
||||
const PORT = process.env.KABELFACHMANN_MCP_PORT || 3007;
|
||||
const HOST = process.env.HOST || "0.0.0.0";
|
||||
app.listen(PORT as number, HOST, async () => {
|
||||
console.error(
|
||||
`Kabelfachmann MCP server running on http://${HOST}:${PORT}/sse`,
|
||||
);
|
||||
try {
|
||||
await qdrantService.initialize();
|
||||
} catch (e) {
|
||||
console.error("Failed to initialize local dependencies:", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
console.error("Fatal error:", error);
|
||||
process.exit(1);
|
||||
});
|
||||
76
packages/kabelfachmann-mcp/src/ingest.ts
Normal file
76
packages/kabelfachmann-mcp/src/ingest.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import pdf from "pdf-parse";
|
||||
import { QdrantMemoryService } from "./qdrant.js";
|
||||
|
||||
async function start() {
|
||||
const qdrant = new QdrantMemoryService(
|
||||
process.env.QDRANT_URL || "http://localhost:6333",
|
||||
);
|
||||
await qdrant.initialize();
|
||||
|
||||
const pdfPath = path.join(process.cwd(), "data", "pdf", "kabelhandbuch.pdf");
|
||||
console.error(`Reading PDF from ${pdfPath}...`);
|
||||
|
||||
let dataBuffer;
|
||||
try {
|
||||
dataBuffer = fs.readFileSync(pdfPath);
|
||||
} catch (e) {
|
||||
console.error(
|
||||
"PDF file not found. Ensure it exists at data/pdf/kabelhandbuch.pdf",
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const data = await pdf(dataBuffer);
|
||||
const text = data.text;
|
||||
|
||||
// chunk text
|
||||
// A simple chunking strategy by paragraph or chunks of ~1000 characters
|
||||
const paragraphs = text
|
||||
.split(/\n\s*\n/)
|
||||
.map((p) => p.trim())
|
||||
.filter((p) => p.length > 50);
|
||||
|
||||
let currentChunk = "";
|
||||
const chunks: string[] = [];
|
||||
const MAX_CHUNK_LENGTH = 1500;
|
||||
|
||||
for (const p of paragraphs) {
|
||||
if (currentChunk.length + p.length > MAX_CHUNK_LENGTH) {
|
||||
chunks.push(currentChunk);
|
||||
currentChunk = p;
|
||||
} else {
|
||||
currentChunk += (currentChunk.length ? "\n\n" : "") + p;
|
||||
}
|
||||
}
|
||||
if (currentChunk.length > 0) {
|
||||
chunks.push(currentChunk);
|
||||
}
|
||||
|
||||
console.error(
|
||||
`Split PDF into ${chunks.length} chunks. Ingesting to Qdrant...`,
|
||||
);
|
||||
|
||||
let successCount = 0;
|
||||
for (let i = 0; i < chunks.length; i++) {
|
||||
const chunk = chunks[i];
|
||||
const success = await qdrant.storeMemory(`Handbuch Teil ${i + 1}`, chunk);
|
||||
if (success) {
|
||||
successCount++;
|
||||
}
|
||||
if ((i + 1) % 10 === 0) {
|
||||
console.error(`Ingested ${i + 1}/${chunks.length} chunks...`);
|
||||
}
|
||||
}
|
||||
|
||||
console.error(
|
||||
`Ingestion complete! Successfully stored ${successCount}/${chunks.length} chunks.`,
|
||||
);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
start().catch((e) => {
|
||||
console.error(e);
|
||||
process.exit(1);
|
||||
});
|
||||
41
packages/kabelfachmann-mcp/src/llm.ts
Normal file
41
packages/kabelfachmann-mcp/src/llm.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import fetch from "node-fetch";
|
||||
|
||||
export async function askOpenRouter(
|
||||
systemPrompt: string,
|
||||
userPrompt: string,
|
||||
): Promise<string> {
|
||||
const apiKey = process.env.OPENROUTER_API_KEY;
|
||||
if (!apiKey) {
|
||||
throw new Error("OPENROUTER_API_KEY is not set");
|
||||
}
|
||||
|
||||
const response = await fetch(
|
||||
"https://openrouter.ai/api/v1/chat/completions",
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
Authorization: `Bearer ${apiKey}`,
|
||||
"HTTP-Referer": "https://mintel.me",
|
||||
"X-Title": "Mintel MCP",
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: "google/gemini-3-flash-preview",
|
||||
messages: [
|
||||
{ role: "system", content: systemPrompt },
|
||||
{ role: "user", content: userPrompt },
|
||||
],
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
const text = await response.text();
|
||||
throw new Error(
|
||||
`OpenRouter API error: ${response.status} ${response.statusText} - ${text}`,
|
||||
);
|
||||
}
|
||||
|
||||
const data = (await response.json()) as any;
|
||||
return data.choices[0].message.content;
|
||||
}
|
||||
104
packages/kabelfachmann-mcp/src/qdrant.ts
Normal file
104
packages/kabelfachmann-mcp/src/qdrant.ts
Normal file
@@ -0,0 +1,104 @@
|
||||
import { pipeline, env } from "@xenova/transformers";
|
||||
import { QdrantClient } from "@qdrant/js-client-rest";
|
||||
import crypto from "crypto";
|
||||
|
||||
env.allowRemoteModels = true;
|
||||
env.localModelPath = "./models";
|
||||
|
||||
export class QdrantMemoryService {
|
||||
private client: QdrantClient;
|
||||
private collectionName = "kabelfachmann";
|
||||
private embedder: any = null;
|
||||
|
||||
constructor(
|
||||
url: string = process.env.QDRANT_URL || "http://qdrant-mcp:6333",
|
||||
) {
|
||||
this.client = new QdrantClient({ url });
|
||||
}
|
||||
|
||||
async initialize() {
|
||||
console.error("Loading embedding model...");
|
||||
this.embedder = await pipeline(
|
||||
"feature-extraction",
|
||||
"Xenova/all-MiniLM-L6-v2",
|
||||
);
|
||||
|
||||
console.error(`Checking for collection: ${this.collectionName}`);
|
||||
try {
|
||||
const collections = await this.client.getCollections();
|
||||
const exists = collections.collections.some(
|
||||
(c) => c.name === this.collectionName,
|
||||
);
|
||||
|
||||
if (!exists) {
|
||||
console.error(`Creating collection: ${this.collectionName}`);
|
||||
await this.client.createCollection(this.collectionName, {
|
||||
vectors: {
|
||||
size: 384,
|
||||
distance: "Cosine",
|
||||
},
|
||||
});
|
||||
console.error("Collection created successfully.");
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Failed to initialize Qdrant collection:", e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
private async getEmbedding(text: string): Promise<number[]> {
|
||||
if (!this.embedder) {
|
||||
throw new Error("Embedder not initialized. Call initialize() first.");
|
||||
}
|
||||
const output = await this.embedder(text, {
|
||||
pooling: "mean",
|
||||
normalize: true,
|
||||
});
|
||||
return Array.from(output.data);
|
||||
}
|
||||
|
||||
async storeMemory(label: string, content: string): Promise<boolean> {
|
||||
try {
|
||||
const vector = await this.getEmbedding(content);
|
||||
const id = crypto.randomUUID();
|
||||
|
||||
await this.client.upsert(this.collectionName, {
|
||||
wait: true,
|
||||
points: [
|
||||
{
|
||||
id,
|
||||
vector,
|
||||
payload: { label, content, timestamp: new Date().toISOString() },
|
||||
},
|
||||
],
|
||||
});
|
||||
return true;
|
||||
} catch (e) {
|
||||
console.error("Failed to store memory:", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async retrieveMemory(
|
||||
query: string,
|
||||
limit: number = 5,
|
||||
): Promise<Array<{ label: string; content: string; score: number }>> {
|
||||
try {
|
||||
const vector = await this.getEmbedding(query);
|
||||
const searchResults = await this.client.search(this.collectionName, {
|
||||
vector,
|
||||
limit,
|
||||
with_payload: true,
|
||||
});
|
||||
|
||||
return searchResults.map((result) => ({
|
||||
label: String(result.payload?.label || ""),
|
||||
content: String(result.payload?.content || ""),
|
||||
score: result.score,
|
||||
}));
|
||||
} catch (e) {
|
||||
console.error("Failed to retrieve memory:", e);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
16
packages/kabelfachmann-mcp/src/start.ts
Normal file
16
packages/kabelfachmann-mcp/src/start.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { config } from "dotenv";
|
||||
import { resolve } from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
|
||||
const __dirname = fileURLToPath(new URL(".", import.meta.url));
|
||||
|
||||
// Try to load .env.local first (contains credentials usually)
|
||||
config({ quiet: true, path: resolve(__dirname, "../../../.env.local") });
|
||||
// Fallback to .env (contains defaults)
|
||||
config({ quiet: true, path: resolve(__dirname, "../../../.env") });
|
||||
|
||||
// Now boot the compiled MCP index
|
||||
import("./index.js").catch((err) => {
|
||||
console.error("Failed to start MCP Server:", err);
|
||||
process.exit(1);
|
||||
});
|
||||
38
packages/kabelfachmann-mcp/test-kabelfachmann.js
Normal file
38
packages/kabelfachmann-mcp/test-kabelfachmann.js
Normal file
@@ -0,0 +1,38 @@
|
||||
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
||||
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
|
||||
|
||||
async function main() {
|
||||
console.log("Connecting to Kabelfachmann MCP on localhost:3007/sse...");
|
||||
const transport = new SSEClientTransport(
|
||||
new URL("http://localhost:3007/sse"),
|
||||
);
|
||||
const client = new Client(
|
||||
{ name: "test-client", version: "1.0.0" },
|
||||
{ capabilities: {} },
|
||||
);
|
||||
|
||||
await client.connect(transport);
|
||||
console.log("Connected! Requesting tools...");
|
||||
|
||||
const tools = await client.listTools();
|
||||
console.log(
|
||||
"Available tools:",
|
||||
tools.tools.map((t) => t.name),
|
||||
);
|
||||
|
||||
console.log("Calling ask_kabelfachmann...");
|
||||
const result = await client.callTool({
|
||||
name: "ask_kabelfachmann",
|
||||
arguments: {
|
||||
query:
|
||||
"Was ist der Mindestbiegeradius von einem NYY-J 5x1,5 Kabel laut Handbuch?",
|
||||
},
|
||||
});
|
||||
|
||||
console.log("\n--- RESULT ---");
|
||||
console.log(JSON.stringify(result, null, 2));
|
||||
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
15
packages/kabelfachmann-mcp/tsconfig.json
Normal file
15
packages/kabelfachmann-mcp/tsconfig.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "NodeNext",
|
||||
"moduleResolution": "NodeNext",
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"resolveJsonModule": true
|
||||
},
|
||||
"include": ["src/**/*"]
|
||||
}
|
||||
@@ -4,126 +4,157 @@ import * as path from "node:path";
|
||||
import * as os from "node:os";
|
||||
|
||||
async function getOrchestrator() {
|
||||
const OPENROUTER_KEY =
|
||||
process.env.OPENROUTER_KEY || process.env.OPENROUTER_API_KEY;
|
||||
const REPLICATE_KEY = process.env.REPLICATE_API_KEY;
|
||||
const OPENROUTER_KEY =
|
||||
process.env.OPENROUTER_KEY || process.env.OPENROUTER_API_KEY;
|
||||
const REPLICATE_KEY = process.env.REPLICATE_API_KEY;
|
||||
|
||||
if (!OPENROUTER_KEY) {
|
||||
throw new Error(
|
||||
"Missing OPENROUTER_API_KEY in .env (Required for AI generation)",
|
||||
);
|
||||
}
|
||||
|
||||
const importDynamic = new Function("modulePath", "return import(modulePath)");
|
||||
const { AiBlogPostOrchestrator } = await importDynamic(
|
||||
"@mintel/content-engine",
|
||||
if (!OPENROUTER_KEY) {
|
||||
throw new Error(
|
||||
"Missing OPENROUTER_API_KEY in .env (Required for AI generation)",
|
||||
);
|
||||
}
|
||||
|
||||
return new AiBlogPostOrchestrator({
|
||||
apiKey: OPENROUTER_KEY,
|
||||
replicateApiKey: REPLICATE_KEY,
|
||||
model: "google/gemini-3-flash-preview",
|
||||
});
|
||||
const importDynamic = new Function("modulePath", "return import(modulePath)");
|
||||
const { AiBlogPostOrchestrator } = await importDynamic(
|
||||
"@mintel/content-engine",
|
||||
);
|
||||
|
||||
return new AiBlogPostOrchestrator({
|
||||
apiKey: OPENROUTER_KEY,
|
||||
replicateApiKey: REPLICATE_KEY,
|
||||
model: "google/gemini-3-flash-preview",
|
||||
});
|
||||
}
|
||||
|
||||
export const generateSlugEndpoint = async (req: PayloadRequest) => {
|
||||
try {
|
||||
let body: any = {};
|
||||
try {
|
||||
const { title, draftContent, oldSlug, instructions } = (await req.json?.() || {}) as any;
|
||||
const orchestrator = await getOrchestrator();
|
||||
const newSlug = await orchestrator.generateSlug(
|
||||
draftContent,
|
||||
title,
|
||||
instructions,
|
||||
);
|
||||
|
||||
if (oldSlug && oldSlug !== newSlug) {
|
||||
await req.payload.create({
|
||||
collection: "redirects" as any,
|
||||
data: {
|
||||
from: oldSlug,
|
||||
to: newSlug,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return Response.json({ success: true, slug: newSlug });
|
||||
} catch (e: any) {
|
||||
return Response.json({ success: false, error: e.message }, { status: 500 });
|
||||
if (req.body) body = (await req.json?.()) || {};
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
const { title, draftContent, oldSlug, instructions } = body;
|
||||
const orchestrator = await getOrchestrator();
|
||||
const newSlug = await orchestrator.generateSlug(
|
||||
draftContent,
|
||||
title,
|
||||
instructions,
|
||||
);
|
||||
|
||||
if (oldSlug && oldSlug !== newSlug) {
|
||||
await req.payload.create({
|
||||
collection: "redirects" as any,
|
||||
data: {
|
||||
from: oldSlug,
|
||||
to: newSlug,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return Response.json({ success: true, slug: newSlug });
|
||||
} catch (e: any) {
|
||||
return Response.json({ success: false, error: e.message }, { status: 500 });
|
||||
}
|
||||
};
|
||||
|
||||
export const generateThumbnailEndpoint = async (req: PayloadRequest) => {
|
||||
try {
|
||||
let body: any = {};
|
||||
try {
|
||||
const { draftContent, title, instructions } = (await req.json?.() || {}) as any;
|
||||
const OPENROUTER_KEY =
|
||||
process.env.OPENROUTER_KEY || process.env.OPENROUTER_API_KEY;
|
||||
const REPLICATE_KEY = process.env.REPLICATE_API_KEY;
|
||||
|
||||
if (!OPENROUTER_KEY) throw new Error("Missing OPENROUTER_API_KEY in .env");
|
||||
if (!REPLICATE_KEY) throw new Error("Missing REPLICATE_API_KEY in .env");
|
||||
|
||||
const importDynamic = new Function("modulePath", "return import(modulePath)");
|
||||
const { AiBlogPostOrchestrator } = await importDynamic("@mintel/content-engine");
|
||||
const { ThumbnailGenerator } = await importDynamic("@mintel/thumbnail-generator");
|
||||
|
||||
const orchestrator = new AiBlogPostOrchestrator({
|
||||
apiKey: OPENROUTER_KEY,
|
||||
replicateApiKey: REPLICATE_KEY,
|
||||
model: "google/gemini-3-flash-preview",
|
||||
});
|
||||
|
||||
const tg = new ThumbnailGenerator({ replicateApiKey: REPLICATE_KEY });
|
||||
|
||||
const prompt = await orchestrator.generateVisualPrompt(
|
||||
draftContent || title || "Technology",
|
||||
instructions,
|
||||
);
|
||||
|
||||
const tmpPath = path.join(os.tmpdir(), `mintel-thumb-${Date.now()}.png`);
|
||||
await tg.generateImage(prompt, tmpPath);
|
||||
|
||||
const fileData = await fs.readFile(tmpPath);
|
||||
const stat = await fs.stat(tmpPath);
|
||||
const fileName = path.basename(tmpPath);
|
||||
|
||||
const newMedia = await req.payload.create({
|
||||
collection: "media" as any,
|
||||
data: {
|
||||
alt: title ? `Thumbnail for ${title}` : "AI Generated Thumbnail",
|
||||
},
|
||||
file: {
|
||||
data: fileData,
|
||||
name: fileName,
|
||||
mimetype: "image/png",
|
||||
size: stat.size,
|
||||
},
|
||||
});
|
||||
|
||||
await fs.unlink(tmpPath).catch(() => { });
|
||||
|
||||
return Response.json({ success: true, mediaId: newMedia.id });
|
||||
} catch (e: any) {
|
||||
return Response.json({ success: false, error: e.message }, { status: 500 });
|
||||
if (req.body) body = (await req.json?.()) || {};
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
const { draftContent, title, instructions } = body;
|
||||
const OPENROUTER_KEY =
|
||||
process.env.OPENROUTER_KEY || process.env.OPENROUTER_API_KEY;
|
||||
const REPLICATE_KEY = process.env.REPLICATE_API_KEY;
|
||||
|
||||
if (!OPENROUTER_KEY) throw new Error("Missing OPENROUTER_API_KEY in .env");
|
||||
if (!REPLICATE_KEY) throw new Error("Missing REPLICATE_API_KEY in .env");
|
||||
|
||||
const importDynamic = new Function(
|
||||
"modulePath",
|
||||
"return import(modulePath)",
|
||||
);
|
||||
const { AiBlogPostOrchestrator } = await importDynamic(
|
||||
"@mintel/content-engine",
|
||||
);
|
||||
const { ThumbnailGenerator } = await importDynamic(
|
||||
"@mintel/thumbnail-generator",
|
||||
);
|
||||
|
||||
const orchestrator = new AiBlogPostOrchestrator({
|
||||
apiKey: OPENROUTER_KEY,
|
||||
replicateApiKey: REPLICATE_KEY,
|
||||
model: "google/gemini-3-flash-preview",
|
||||
});
|
||||
|
||||
const tg = new ThumbnailGenerator({ replicateApiKey: REPLICATE_KEY });
|
||||
|
||||
const prompt = await orchestrator.generateVisualPrompt(
|
||||
draftContent || title || "Technology",
|
||||
instructions,
|
||||
);
|
||||
|
||||
const tmpPath = path.join(os.tmpdir(), `mintel-thumb-${Date.now()}.png`);
|
||||
await tg.generateImage(prompt, tmpPath);
|
||||
|
||||
const fileData = await fs.readFile(tmpPath);
|
||||
const stat = await fs.stat(tmpPath);
|
||||
const fileName = path.basename(tmpPath);
|
||||
|
||||
const newMedia = await req.payload.create({
|
||||
collection: "media" as any,
|
||||
data: {
|
||||
alt: title ? `Thumbnail for ${title}` : "AI Generated Thumbnail",
|
||||
},
|
||||
file: {
|
||||
data: fileData,
|
||||
name: fileName,
|
||||
mimetype: "image/png",
|
||||
size: stat.size,
|
||||
},
|
||||
});
|
||||
|
||||
await fs.unlink(tmpPath).catch(() => {});
|
||||
|
||||
return Response.json({ success: true, mediaId: newMedia.id });
|
||||
} catch (e: any) {
|
||||
return Response.json({ success: false, error: e.message }, { status: 500 });
|
||||
}
|
||||
};
|
||||
|
||||
export const generateSingleFieldEndpoint = async (req: PayloadRequest) => {
|
||||
try {
|
||||
let body: any = {};
|
||||
try {
|
||||
const { documentTitle, documentContent, fieldName, fieldDescription, instructions } = (await req.json?.() || {}) as any;
|
||||
if (req.body) body = (await req.json?.()) || {};
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
const {
|
||||
documentTitle,
|
||||
documentContent,
|
||||
fieldName,
|
||||
fieldDescription,
|
||||
instructions,
|
||||
} = body;
|
||||
|
||||
const OPENROUTER_KEY =
|
||||
process.env.OPENROUTER_KEY || process.env.OPENROUTER_API_KEY;
|
||||
if (!OPENROUTER_KEY) throw new Error("Missing OPENROUTER_API_KEY");
|
||||
const OPENROUTER_KEY =
|
||||
process.env.OPENROUTER_KEY || process.env.OPENROUTER_API_KEY;
|
||||
if (!OPENROUTER_KEY) throw new Error("Missing OPENROUTER_API_KEY");
|
||||
|
||||
const contextDocsData = await req.payload.find({
|
||||
collection: "context-files" as any,
|
||||
limit: 100,
|
||||
});
|
||||
const projectContext = contextDocsData.docs
|
||||
.map((doc: any) => `--- ${doc.filename} ---\n${doc.content}`)
|
||||
.join("\n\n");
|
||||
const contextDocsData = await req.payload.find({
|
||||
collection: "context-files" as any,
|
||||
limit: 100,
|
||||
});
|
||||
const projectContext = contextDocsData.docs
|
||||
.map((doc: any) => `--- ${doc.filename} ---\n${doc.content}`)
|
||||
.join("\n\n");
|
||||
|
||||
const prompt = `You are an expert AI assistant perfectly trained for generating exact data values for CMS components.
|
||||
const prompt = `You are an expert AI assistant perfectly trained for generating exact data values for CMS components.
|
||||
PROJECT STRATEGY & CONTEXT:
|
||||
${projectContext}
|
||||
|
||||
@@ -138,21 +169,21 @@ CRITICAL RULES:
|
||||
3. If the field implies a diagram or flow, output RAW Mermaid.js code.
|
||||
4. If it's standard text, write professional B2B German. No quotes, no conversational filler.`;
|
||||
|
||||
const res = await fetch("https://openrouter.ai/api/v1/chat/completions", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
Authorization: `Bearer ${OPENROUTER_KEY}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: "google/gemini-3-flash-preview",
|
||||
messages: [{ role: "user", content: prompt }],
|
||||
}),
|
||||
});
|
||||
const data = await res.json();
|
||||
const text = data.choices?.[0]?.message?.content?.trim() || "";
|
||||
return Response.json({ success: true, text });
|
||||
} catch (e: any) {
|
||||
return Response.json({ success: false, error: e.message }, { status: 500 });
|
||||
}
|
||||
}
|
||||
const res = await fetch("https://openrouter.ai/api/v1/chat/completions", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
Authorization: `Bearer ${OPENROUTER_KEY}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: "google/gemini-3-flash-preview",
|
||||
messages: [{ role: "user", content: prompt }],
|
||||
}),
|
||||
});
|
||||
const data = await res.json();
|
||||
const text = data.choices?.[0]?.message?.content?.trim() || "";
|
||||
return Response.json({ success: true, text });
|
||||
} catch (e: any) {
|
||||
return Response.json({ success: false, error: e.message }, { status: 500 });
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,75 +1,108 @@
|
||||
import { PayloadRequest } from 'payload'
|
||||
import { PayloadRequest } from "payload";
|
||||
import { parseMarkdownToLexical } from "../utils/lexicalParser.js";
|
||||
|
||||
export const optimizePostEndpoint = async (req: PayloadRequest) => {
|
||||
try {
|
||||
let body: any = {};
|
||||
try {
|
||||
const { draftContent, instructions } = (await req.json?.() || {}) as { draftContent: string; instructions?: string };
|
||||
|
||||
if (!draftContent) {
|
||||
return Response.json({ error: 'Missing draftContent' }, { status: 400 })
|
||||
}
|
||||
|
||||
const globalAiSettings = (await req.payload.findGlobal({ slug: "ai-settings" })) as any;
|
||||
const customSources =
|
||||
globalAiSettings?.customSources?.map((s: any) => s.sourceName) || [];
|
||||
|
||||
const OPENROUTER_KEY =
|
||||
process.env.OPENROUTER_KEY || process.env.OPENROUTER_API_KEY;
|
||||
const REPLICATE_KEY = process.env.REPLICATE_API_KEY;
|
||||
|
||||
if (!OPENROUTER_KEY) {
|
||||
return Response.json({ error: "OPENROUTER_KEY not found in environment." }, { status: 500 })
|
||||
}
|
||||
|
||||
// Dynamically import to avoid bundling it into client components that might accidentally import this file
|
||||
const importDynamic = new Function("modulePath", "return import(modulePath)");
|
||||
const { AiBlogPostOrchestrator } = await importDynamic("@mintel/content-engine");
|
||||
|
||||
const orchestrator = new AiBlogPostOrchestrator({
|
||||
apiKey: OPENROUTER_KEY,
|
||||
replicateApiKey: REPLICATE_KEY,
|
||||
model: "google/gemini-3-flash-preview",
|
||||
});
|
||||
|
||||
const contextDocsData = await req.payload.find({
|
||||
collection: "context-files" as any,
|
||||
limit: 100,
|
||||
});
|
||||
const projectContext = contextDocsData.docs.map((doc: any) => doc.content);
|
||||
|
||||
const optimizedMarkdown = await orchestrator.optimizeDocument({
|
||||
content: draftContent,
|
||||
projectContext,
|
||||
availableComponents: [],
|
||||
instructions,
|
||||
internalLinks: [],
|
||||
customSources,
|
||||
});
|
||||
|
||||
if (!optimizedMarkdown || typeof optimizedMarkdown !== "string") {
|
||||
return Response.json({ error: "AI returned invalid markup." }, { status: 500 })
|
||||
}
|
||||
|
||||
const blocks = parseMarkdownToLexical(optimizedMarkdown);
|
||||
|
||||
return Response.json({
|
||||
success: true,
|
||||
lexicalAST: {
|
||||
root: {
|
||||
type: "root",
|
||||
format: "",
|
||||
indent: 0,
|
||||
version: 1,
|
||||
children: blocks,
|
||||
direction: "ltr",
|
||||
},
|
||||
},
|
||||
})
|
||||
} catch (error: any) {
|
||||
console.error("Failed to optimize post in endpoint:", error);
|
||||
return Response.json({
|
||||
success: false,
|
||||
error: error.message || "An unknown error occurred during optimization.",
|
||||
}, { status: 500 })
|
||||
if (req.body) {
|
||||
// req.json() acts as a method in Next.js/Payload req wrapper
|
||||
body = (await req.json?.()) || {};
|
||||
}
|
||||
} catch (e) {
|
||||
// Ignore JSON parse error, body remains empty
|
||||
}
|
||||
}
|
||||
|
||||
const { draftContent, instructions } = body as {
|
||||
draftContent?: string;
|
||||
instructions?: string;
|
||||
};
|
||||
|
||||
if (!draftContent) {
|
||||
return Response.json(
|
||||
{ success: false, error: "Missing draftContent" },
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
const globalAiSettings = (await req.payload.findGlobal({
|
||||
slug: "ai-settings",
|
||||
})) as any;
|
||||
const customSources =
|
||||
globalAiSettings?.customSources?.map((s: any) => s.sourceName) || [];
|
||||
|
||||
const OPENROUTER_KEY =
|
||||
process.env.OPENROUTER_KEY || process.env.OPENROUTER_API_KEY;
|
||||
const REPLICATE_KEY = process.env.REPLICATE_API_KEY;
|
||||
|
||||
if (!OPENROUTER_KEY) {
|
||||
return Response.json(
|
||||
{ error: "OPENROUTER_KEY not found in environment." },
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
|
||||
// Dynamically import to avoid bundling it into client components that might accidentally import this file
|
||||
const importDynamic = new Function(
|
||||
"modulePath",
|
||||
"return import(modulePath)",
|
||||
);
|
||||
const { AiBlogPostOrchestrator } = await importDynamic(
|
||||
"@mintel/content-engine",
|
||||
);
|
||||
|
||||
const orchestrator = new AiBlogPostOrchestrator({
|
||||
apiKey: OPENROUTER_KEY,
|
||||
replicateApiKey: REPLICATE_KEY,
|
||||
model: "google/gemini-3-flash-preview",
|
||||
});
|
||||
|
||||
const contextDocsData = await req.payload.find({
|
||||
collection: "context-files" as any,
|
||||
limit: 100,
|
||||
});
|
||||
const projectContext = contextDocsData.docs.map((doc: any) => doc.content);
|
||||
|
||||
const optimizedMarkdown = await orchestrator.optimizeDocument({
|
||||
content: draftContent,
|
||||
projectContext,
|
||||
availableComponents: [],
|
||||
instructions,
|
||||
internalLinks: [],
|
||||
customSources,
|
||||
});
|
||||
|
||||
if (!optimizedMarkdown || typeof optimizedMarkdown !== "string") {
|
||||
return Response.json(
|
||||
{ error: "AI returned invalid markup." },
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
|
||||
const blocks = parseMarkdownToLexical(optimizedMarkdown);
|
||||
|
||||
return Response.json({
|
||||
success: true,
|
||||
lexicalAST: {
|
||||
root: {
|
||||
type: "root",
|
||||
format: "",
|
||||
indent: 0,
|
||||
version: 1,
|
||||
children: blocks,
|
||||
direction: "ltr",
|
||||
},
|
||||
},
|
||||
});
|
||||
} catch (error: any) {
|
||||
console.error("Failed to optimize post in endpoint:", error);
|
||||
return Response.json(
|
||||
{
|
||||
success: false,
|
||||
error:
|
||||
error.message || "An unknown error occurred during optimization.",
|
||||
},
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
123
pnpm-lock.yaml
generated
123
pnpm-lock.yaml
generated
@@ -235,7 +235,7 @@ importers:
|
||||
version: 5.9.3
|
||||
vitest:
|
||||
specifier: ^3.0.5
|
||||
version: 3.2.4(@types/debug@4.1.12)(@types/node@20.19.33)(@vitest/ui@4.0.18(vitest@4.0.18))(happy-dom@20.5.3)(jsdom@27.4.0(canvas@3.2.1))(lightningcss@1.30.2)(sass@1.97.3)(terser@5.46.0)
|
||||
version: 3.2.4(@types/debug@4.1.12)(@types/node@20.19.33)(@vitest/ui@4.0.18)(happy-dom@20.5.3)(jsdom@27.4.0(canvas@3.2.1))(lightningcss@1.30.2)(sass@1.97.3)(terser@5.46.0)
|
||||
|
||||
packages/content-engine:
|
||||
dependencies:
|
||||
@@ -477,6 +477,52 @@ importers:
|
||||
specifier: ^5.0.0
|
||||
version: 5.9.3
|
||||
|
||||
packages/kabelfachmann-mcp:
|
||||
dependencies:
|
||||
'@modelcontextprotocol/sdk':
|
||||
specifier: ^1.5.0
|
||||
version: 1.27.1(zod@3.25.76)
|
||||
'@qdrant/js-client-rest':
|
||||
specifier: ^1.12.0
|
||||
version: 1.17.0(typescript@5.9.3)
|
||||
'@xenova/transformers':
|
||||
specifier: ^2.17.2
|
||||
version: 2.17.2
|
||||
dotenv:
|
||||
specifier: ^17.3.1
|
||||
version: 17.3.1
|
||||
express:
|
||||
specifier: ^5.2.1
|
||||
version: 5.2.1
|
||||
node-fetch:
|
||||
specifier: ^3.3.2
|
||||
version: 3.3.2
|
||||
onnxruntime-node:
|
||||
specifier: ^1.14.0
|
||||
version: 1.14.0
|
||||
pdf-parse:
|
||||
specifier: ^1.1.1
|
||||
version: 1.1.4
|
||||
zod:
|
||||
specifier: ^3.23.8
|
||||
version: 3.25.76
|
||||
devDependencies:
|
||||
'@types/express':
|
||||
specifier: ^5.0.6
|
||||
version: 5.0.6
|
||||
'@types/node':
|
||||
specifier: ^20.14.10
|
||||
version: 20.19.33
|
||||
'@types/pdf-parse':
|
||||
specifier: ^1.1.4
|
||||
version: 1.1.5
|
||||
tsx:
|
||||
specifier: ^4.19.1
|
||||
version: 4.21.0
|
||||
typescript:
|
||||
specifier: ^5.5.3
|
||||
version: 5.9.3
|
||||
|
||||
packages/klz-payload-mcp:
|
||||
dependencies:
|
||||
'@modelcontextprotocol/sdk':
|
||||
@@ -537,7 +583,7 @@ importers:
|
||||
version: 5.9.3
|
||||
vitest:
|
||||
specifier: ^3.0.4
|
||||
version: 3.2.4(@types/debug@4.1.12)(@types/node@22.19.10)(@vitest/ui@4.0.18(vitest@4.0.18))(happy-dom@20.5.3)(jsdom@27.4.0(canvas@3.2.1))(lightningcss@1.30.2)(sass@1.97.3)(terser@5.46.0)
|
||||
version: 3.2.4(@types/debug@4.1.12)(@types/node@22.19.10)(@vitest/ui@4.0.18)(happy-dom@20.5.3)(jsdom@27.4.0(canvas@3.2.1))(lightningcss@1.30.2)(sass@1.97.3)(terser@5.46.0)
|
||||
|
||||
packages/meme-generator:
|
||||
dependencies:
|
||||
@@ -596,7 +642,7 @@ importers:
|
||||
version: 5.9.3
|
||||
vitest:
|
||||
specifier: ^2.1.3
|
||||
version: 2.1.9(@types/node@20.19.33)(@vitest/ui@4.0.18(vitest@4.0.18))(happy-dom@20.5.3)(jsdom@27.4.0(canvas@3.2.1))(lightningcss@1.30.2)(sass@1.97.3)(terser@5.46.0)
|
||||
version: 2.1.9(@types/node@20.19.33)(@vitest/ui@4.0.18)(happy-dom@20.5.3)(jsdom@27.4.0(canvas@3.2.1))(lightningcss@1.30.2)(sass@1.97.3)(terser@5.46.0)
|
||||
|
||||
packages/next-config:
|
||||
dependencies:
|
||||
@@ -752,7 +798,7 @@ importers:
|
||||
version: 5.9.3
|
||||
vitest:
|
||||
specifier: ^2.0.0
|
||||
version: 2.1.9(@types/node@22.19.10)(@vitest/ui@4.0.18(vitest@4.0.18))(happy-dom@20.5.3)(jsdom@27.4.0(canvas@3.2.1))(lightningcss@1.30.2)(sass@1.97.3)(terser@5.46.0)
|
||||
version: 2.1.9(@types/node@22.19.10)(@vitest/ui@4.0.18)(happy-dom@20.5.3)(jsdom@27.4.0(canvas@3.2.1))(lightningcss@1.30.2)(sass@1.97.3)(terser@5.46.0)
|
||||
|
||||
packages/page-audit:
|
||||
dependencies:
|
||||
@@ -897,7 +943,7 @@ importers:
|
||||
version: 5.9.3
|
||||
vitest:
|
||||
specifier: ^3.0.5
|
||||
version: 3.2.4(@types/debug@4.1.12)(@types/node@20.19.33)(@vitest/ui@4.0.18(vitest@4.0.18))(happy-dom@20.5.3)(jsdom@27.4.0(canvas@3.2.1))(lightningcss@1.30.2)(sass@1.97.3)(terser@5.46.0)
|
||||
version: 3.2.4(@types/debug@4.1.12)(@types/node@20.19.33)(@vitest/ui@4.0.18)(happy-dom@20.5.3)(jsdom@27.4.0(canvas@3.2.1))(lightningcss@1.30.2)(sass@1.97.3)(terser@5.46.0)
|
||||
|
||||
packages/serpbear-mcp:
|
||||
dependencies:
|
||||
@@ -3457,6 +3503,9 @@ packages:
|
||||
'@types/parse-json@4.0.2':
|
||||
resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==}
|
||||
|
||||
'@types/pdf-parse@1.1.5':
|
||||
resolution: {integrity: sha512-kBfrSXsloMnUJOKi25s3+hRmkycHfLK6A09eRGqF/N8BkQoPUmaCr+q8Cli5FnfohEz/rsv82zAiPz/LXtOGhA==}
|
||||
|
||||
'@types/pg-pool@2.0.7':
|
||||
resolution: {integrity: sha512-U4CwmGVQcbEuqpyju8/ptOKg6gEC+Tqsvj2xS9o1g71bUh8twxnC6ZL5rZKCsGN0iyH0CwgUyc9VR5owNQF9Ng==}
|
||||
|
||||
@@ -4590,6 +4639,10 @@ packages:
|
||||
resolution: {integrity: sha512-wAV9QHOsNbwnWdNW2FYvE1P56wtgSbM+3SZcdGiWQILwVjACCXDCI3Ai8QlCjMDB8YK5zySiXZYBiwGmNY3lnw==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
data-uri-to-buffer@4.0.1:
|
||||
resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==}
|
||||
engines: {node: '>= 12'}
|
||||
|
||||
data-uri-to-buffer@6.0.2:
|
||||
resolution: {integrity: sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==}
|
||||
engines: {node: '>= 14'}
|
||||
@@ -5178,6 +5231,10 @@ packages:
|
||||
picomatch:
|
||||
optional: true
|
||||
|
||||
fetch-blob@3.2.0:
|
||||
resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==}
|
||||
engines: {node: ^12.20 || >= 14.13}
|
||||
|
||||
fflate@0.8.2:
|
||||
resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==}
|
||||
|
||||
@@ -5288,6 +5345,10 @@ packages:
|
||||
resolution: {integrity: sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==}
|
||||
engines: {node: '>= 12.20'}
|
||||
|
||||
formdata-polyfill@4.0.10:
|
||||
resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==}
|
||||
engines: {node: '>=12.20.0'}
|
||||
|
||||
forwarded-parse@2.1.2:
|
||||
resolution: {integrity: sha512-alTFZZQDKMporBH77856pXgzhEzaUVmLCDk+egLgIgHst3Tpndzz8MnKe+GzRJRfvVdn69HhpW7cmXzvtLvJAw==}
|
||||
|
||||
@@ -6510,6 +6571,9 @@ packages:
|
||||
engines: {node: '>=10.5.0'}
|
||||
deprecated: Use your platform's native DOMException instead
|
||||
|
||||
node-ensure@0.0.0:
|
||||
resolution: {integrity: sha512-DRI60hzo2oKN1ma0ckc6nQWlHU69RH6xN0sjQTjMpChPfTYvKZdcQFfdYK2RWbJcKyUizSIy/l8OTGxMAM1QDw==}
|
||||
|
||||
node-fetch@2.7.0:
|
||||
resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==}
|
||||
engines: {node: 4.x || >=6.0.0}
|
||||
@@ -6519,6 +6583,10 @@ packages:
|
||||
encoding:
|
||||
optional: true
|
||||
|
||||
node-fetch@3.3.2:
|
||||
resolution: {integrity: sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==}
|
||||
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
||||
|
||||
node-releases@2.0.27:
|
||||
resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==}
|
||||
|
||||
@@ -6780,6 +6848,10 @@ packages:
|
||||
peerDependencies:
|
||||
graphql: ^16.8.1
|
||||
|
||||
pdf-parse@1.1.4:
|
||||
resolution: {integrity: sha512-XRIRcLgk6ZnUbsHsYXExMw+krrPE81hJ6FQPLdBNhhBefqIQKXu/WeTgNBGSwPrfU0v+UCEwn7AoAUOsVKHFvQ==}
|
||||
engines: {node: '>=6.8.1'}
|
||||
|
||||
peberminta@0.9.0:
|
||||
resolution: {integrity: sha512-XIxfHpEuSJbITd1H3EeQwpcZbTLHc+VVr8ANI9t5sit565tsI4/xK3KWTUFE2e6QiangUkh3B0jihzmGnNrRsQ==}
|
||||
|
||||
@@ -8320,6 +8392,10 @@ packages:
|
||||
wcwidth@1.0.1:
|
||||
resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==}
|
||||
|
||||
web-streams-polyfill@3.3.3:
|
||||
resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==}
|
||||
engines: {node: '>= 8'}
|
||||
|
||||
web-streams-polyfill@4.0.0-beta.3:
|
||||
resolution: {integrity: sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==}
|
||||
engines: {node: '>= 14'}
|
||||
@@ -11311,6 +11387,10 @@ snapshots:
|
||||
|
||||
'@types/parse-json@4.0.2': {}
|
||||
|
||||
'@types/pdf-parse@1.1.5':
|
||||
dependencies:
|
||||
'@types/node': 20.19.33
|
||||
|
||||
'@types/pg-pool@2.0.7':
|
||||
dependencies:
|
||||
'@types/pg': 8.15.6
|
||||
@@ -12529,6 +12609,8 @@ snapshots:
|
||||
|
||||
dargs@8.1.0: {}
|
||||
|
||||
data-uri-to-buffer@4.0.1: {}
|
||||
|
||||
data-uri-to-buffer@6.0.2: {}
|
||||
|
||||
data-urls@5.0.0:
|
||||
@@ -13311,6 +13393,11 @@ snapshots:
|
||||
optionalDependencies:
|
||||
picomatch: 4.0.3
|
||||
|
||||
fetch-blob@3.2.0:
|
||||
dependencies:
|
||||
node-domexception: 1.0.0
|
||||
web-streams-polyfill: 3.3.3
|
||||
|
||||
fflate@0.8.2: {}
|
||||
|
||||
figlet@1.10.0:
|
||||
@@ -13441,6 +13528,10 @@ snapshots:
|
||||
node-domexception: 1.0.0
|
||||
web-streams-polyfill: 4.0.0-beta.3
|
||||
|
||||
formdata-polyfill@4.0.10:
|
||||
dependencies:
|
||||
fetch-blob: 3.2.0
|
||||
|
||||
forwarded-parse@2.1.2: {}
|
||||
|
||||
forwarded@0.2.0: {}
|
||||
@@ -14688,10 +14779,18 @@ snapshots:
|
||||
|
||||
node-domexception@1.0.0: {}
|
||||
|
||||
node-ensure@0.0.0: {}
|
||||
|
||||
node-fetch@2.7.0:
|
||||
dependencies:
|
||||
whatwg-url: 5.0.0
|
||||
|
||||
node-fetch@3.3.2:
|
||||
dependencies:
|
||||
data-uri-to-buffer: 4.0.1
|
||||
fetch-blob: 3.2.0
|
||||
formdata-polyfill: 4.0.10
|
||||
|
||||
node-releases@2.0.27: {}
|
||||
|
||||
normalize-path@3.0.0: {}
|
||||
@@ -15010,6 +15109,10 @@ snapshots:
|
||||
- typescript
|
||||
- utf-8-validate
|
||||
|
||||
pdf-parse@1.1.4:
|
||||
dependencies:
|
||||
node-ensure: 0.0.0
|
||||
|
||||
peberminta@0.9.0: {}
|
||||
|
||||
peek-readable@5.4.2: {}
|
||||
@@ -16772,7 +16875,7 @@ snapshots:
|
||||
tsx: 4.21.0
|
||||
yaml: 2.8.2
|
||||
|
||||
vitest@2.1.9(@types/node@20.19.33)(@vitest/ui@4.0.18(vitest@4.0.18))(happy-dom@20.5.3)(jsdom@27.4.0(canvas@3.2.1))(lightningcss@1.30.2)(sass@1.97.3)(terser@5.46.0):
|
||||
vitest@2.1.9(@types/node@20.19.33)(@vitest/ui@4.0.18)(happy-dom@20.5.3)(jsdom@27.4.0(canvas@3.2.1))(lightningcss@1.30.2)(sass@1.97.3)(terser@5.46.0):
|
||||
dependencies:
|
||||
'@vitest/expect': 2.1.9
|
||||
'@vitest/mocker': 2.1.9(vite@5.4.21(@types/node@20.19.33)(lightningcss@1.30.2)(sass@1.97.3)(terser@5.46.0))
|
||||
@@ -16810,7 +16913,7 @@ snapshots:
|
||||
- supports-color
|
||||
- terser
|
||||
|
||||
vitest@2.1.9(@types/node@22.19.10)(@vitest/ui@4.0.18(vitest@4.0.18))(happy-dom@20.5.3)(jsdom@27.4.0(canvas@3.2.1))(lightningcss@1.30.2)(sass@1.97.3)(terser@5.46.0):
|
||||
vitest@2.1.9(@types/node@22.19.10)(@vitest/ui@4.0.18)(happy-dom@20.5.3)(jsdom@27.4.0(canvas@3.2.1))(lightningcss@1.30.2)(sass@1.97.3)(terser@5.46.0):
|
||||
dependencies:
|
||||
'@vitest/expect': 2.1.9
|
||||
'@vitest/mocker': 2.1.9(vite@5.4.21(@types/node@22.19.10)(lightningcss@1.30.2)(sass@1.97.3)(terser@5.46.0))
|
||||
@@ -16848,7 +16951,7 @@ snapshots:
|
||||
- supports-color
|
||||
- terser
|
||||
|
||||
vitest@3.2.4(@types/debug@4.1.12)(@types/node@20.19.33)(@vitest/ui@4.0.18(vitest@4.0.18))(happy-dom@20.5.3)(jsdom@27.4.0(canvas@3.2.1))(lightningcss@1.30.2)(sass@1.97.3)(terser@5.46.0):
|
||||
vitest@3.2.4(@types/debug@4.1.12)(@types/node@20.19.33)(@vitest/ui@4.0.18)(happy-dom@20.5.3)(jsdom@27.4.0(canvas@3.2.1))(lightningcss@1.30.2)(sass@1.97.3)(terser@5.46.0):
|
||||
dependencies:
|
||||
'@types/chai': 5.2.3
|
||||
'@vitest/expect': 3.2.4
|
||||
@@ -16890,7 +16993,7 @@ snapshots:
|
||||
- supports-color
|
||||
- terser
|
||||
|
||||
vitest@3.2.4(@types/debug@4.1.12)(@types/node@22.19.10)(@vitest/ui@4.0.18(vitest@4.0.18))(happy-dom@20.5.3)(jsdom@27.4.0(canvas@3.2.1))(lightningcss@1.30.2)(sass@1.97.3)(terser@5.46.0):
|
||||
vitest@3.2.4(@types/debug@4.1.12)(@types/node@22.19.10)(@vitest/ui@4.0.18)(happy-dom@20.5.3)(jsdom@27.4.0(canvas@3.2.1))(lightningcss@1.30.2)(sass@1.97.3)(terser@5.46.0):
|
||||
dependencies:
|
||||
'@types/chai': 5.2.3
|
||||
'@vitest/expect': 3.2.4
|
||||
@@ -16993,6 +17096,8 @@ snapshots:
|
||||
dependencies:
|
||||
defaults: 1.0.4
|
||||
|
||||
web-streams-polyfill@3.3.3: {}
|
||||
|
||||
web-streams-polyfill@4.0.0-beta.3: {}
|
||||
|
||||
webidl-conversions@3.0.1: {}
|
||||
|
||||
Reference in New Issue
Block a user