feat(mcps): add klz-payload-mcp on port 3006 for customer data

This commit is contained in:
2026-03-05 12:42:20 +01:00
parent daa2750f89
commit d0a17a8a31
6 changed files with 230 additions and 0 deletions

View File

@@ -35,6 +35,14 @@ module.exports = {
cwd: './packages/glitchtip-mcp',
watch: false,
},
{
name: 'klz-payload-mcp',
script: 'node',
args: 'dist/start.js',
cwd: './packages/klz-payload-mcp',
watch: false,
},
]
};

View File

@@ -0,0 +1,25 @@
{
"name": "@mintel/klz-payload-mcp",
"version": "1.9.10",
"description": "KLZ PayloadCMS MCP server for technical product data and leads",
"main": "dist/index.js",
"type": "module",
"scripts": {
"build": "tsc",
"start": "node dist/start.js",
"dev": "tsx watch src/index.ts"
},
"dependencies": {
"@modelcontextprotocol/sdk": "^1.27.1",
"axios": "^1.7.2",
"dotenv": "^17.3.1",
"express": "^5.2.1",
"zod": "^3.23.8"
},
"devDependencies": {
"@types/express": "^5.0.6",
"@types/node": "^20.14.10",
"typescript": "^5.5.3",
"tsx": "^4.19.2"
}
}

View File

@@ -0,0 +1,137 @@
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
import express, { Request, Response } from 'express';
import {
CallToolRequestSchema,
ListToolsRequestSchema,
Tool,
} from "@modelcontextprotocol/sdk/types.js";
import axios from "axios";
import https from "https";
const PAYLOAD_URL = process.env.PAYLOAD_URL || "https://klz.infra.mintel.me";
const PAYLOAD_API_KEY = process.env.PAYLOAD_API_KEY;
if (!PAYLOAD_API_KEY) {
console.error("Warning: PAYLOAD_API_KEY is not set. API calls will fail.");
}
const httpsAgent = new https.Agent({
rejectUnauthorized: false, // For internal infra
});
const payloadClient = axios.create({
baseURL: `${PAYLOAD_URL}/api`,
headers: { Authorization: `users API-Key ${PAYLOAD_API_KEY}` },
httpsAgent
});
const SEARCH_PRODUCTS_TOOL: Tool = {
name: "payload_search_products",
description: "Search for technical product specifications (cables, cross-sections) in KLZ Payload CMS",
inputSchema: {
type: "object",
properties: {
query: { type: "string", description: "Search query or part number" },
limit: { type: "number", description: "Maximum number of results" },
},
},
};
const LIST_LEADS_TOOL: Tool = {
name: "payload_list_leads",
description: "List recent lead inquiries and contact requests",
inputSchema: {
type: "object",
properties: {
limit: { type: "number", description: "Maximum number of leads" },
},
},
};
const server = new Server(
{ name: "klz-payload-mcp", version: "1.0.0" },
{ capabilities: { tools: {} } }
);
server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
SEARCH_PRODUCTS_TOOL,
LIST_LEADS_TOOL,
],
}));
server.setRequestHandler(CallToolRequestSchema, async (request) => {
if (request.params.name === "payload_search_products") {
const { query, limit = 10 } = request.params.arguments as any;
try {
const res = await payloadClient.get('/products', {
params: {
where: query ? {
or: [
{ title: { contains: query } },
{ slug: { contains: query } },
{ description: { contains: query } }
]
} : {},
limit
}
});
return { content: [{ type: "text", text: JSON.stringify(res.data.docs, null, 2) }] };
} catch (e: any) {
return { isError: true, content: [{ type: "text", text: `Error: ${e.message}` }] };
}
}
if (request.params.name === "payload_list_leads") {
const { limit = 10 } = request.params.arguments as any;
try {
const res = await payloadClient.get('/leads', {
params: { limit, sort: '-createdAt' }
});
return { content: [{ type: "text", text: JSON.stringify(res.data.docs, null, 2) }] };
} catch (e: any) {
return { isError: true, content: [{ type: "text", text: `Error: ${e.message}` }] };
}
}
throw new Error(`Unknown tool: ${request.params.name}`);
});
async function run() {
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('KLZ Payload MCP server is running on stdio');
} else {
const app = express();
let transport: SSEServerTransport | null = null;
app.get('/sse', async (req: Request, res: Response) => {
console.error('New SSE connection established');
transport = new SSEServerTransport('/message', res);
await server.connect(transport);
});
app.post('/message', async (req: Request, res: Response) => {
if (!transport) {
res.status(400).send('No active SSE connection');
return;
}
await transport.handlePostMessage(req, res);
});
const PORT = process.env.KLZ_PAYLOAD_MCP_PORT || 3006;
app.listen(PORT, () => {
console.error(`KLZ Payload MCP server running on http://localhost:${PORT}/sse`);
});
}
}
run().catch((err) => {
console.error("Fatal error:", err);
process.exit(1);
});

View File

@@ -0,0 +1,13 @@
import { config } from 'dotenv';
import { resolve } from 'path';
import { fileURLToPath } from 'url';
const __dirname = fileURLToPath(new URL('.', import.meta.url));
config({ path: resolve(__dirname, '../../../.env.local') });
config({ path: resolve(__dirname, '../../../.env') });
import('./index.js').catch(err => {
console.error('Failed to start KLZ Payload MCP Server:', err);
process.exit(1);
});

View File

@@ -0,0 +1,16 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": [
"src/**/*"
]
}

31
pnpm-lock.yaml generated
View File

@@ -483,6 +483,37 @@ importers:
specifier: ^5.0.0
version: 5.9.3
packages/klz-payload-mcp:
dependencies:
'@modelcontextprotocol/sdk':
specifier: ^1.27.1
version: 1.27.1(zod@3.25.76)
axios:
specifier: ^1.7.2
version: 1.13.5
dotenv:
specifier: ^17.3.1
version: 17.3.1
express:
specifier: ^5.2.1
version: 5.2.1
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
tsx:
specifier: ^4.19.2
version: 4.21.0
typescript:
specifier: ^5.5.3
version: 5.9.3
packages/mail:
dependencies:
'@react-email/components':