#!/usr/bin/env node
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
CallToolRequestSchema,
ListToolsRequestSchema,
Tool,
} from "@modelcontextprotocol/sdk/types.js";
import axios from "axios";
import { CiscoAuthClient } from "./auth.js";
import { logger } from "./logger.js";
// Load environment variables
logger.info('Cisco CX Cloud MCP Server starting...');
logger.debug('Environment loaded', {
logLevel: process.env.LOG_LEVEL || 'INFO',
logToFile: process.env.LOG_TO_FILE || 'false',
hasClientId: !!process.env.CISCO_CLIENT_ID,
hasClientSecret: !!process.env.CISCO_CLIENT_SECRET
});
const CX_CLOUD_BASE_URL = "https://apix.cisco.com/cs/api/v2";
// Initialize auth client
const authClient = new CiscoAuthClient(
process.env.CISCO_CLIENT_ID || "",
process.env.CISCO_CLIENT_SECRET || ""
);
// Helper function to make authenticated API calls
async function makeApiCall(endpoint: string, customerId?: string): Promise<any> {
const token = await authClient.getAccessToken();
let url = `${CX_CLOUD_BASE_URL}${endpoint}`;
if (customerId) {
url += `${endpoint.includes('?') ? '&' : '?'}customerId=${customerId}`;
}
logger.debug(`Making API call to: ${url}`);
try {
logger.apiRequest('GET', url, {
Authorization: 'Bearer ***',
});
const startTime = Date.now();
const response = await axios.get(url, {
headers: {
Authorization: `Bearer ${token}`,
},
});
const duration = Date.now() - startTime;
logger.apiResponse('GET', url, response.status, response.data);
logger.debug(`API call completed in ${duration}ms`);
return response.data;
} catch (error) {
logger.apiError('GET', url, error);
if (axios.isAxiosError(error)) {
throw new Error(
`API call failed: ${error.response?.status} - ${error.response?.data?.message || error.message}`
);
}
throw error;
}
}
const server = new Server(
{
name: "cisco-cx-cloud-mcp",
version: "1.0.0",
},
{
capabilities: {
tools: {},
},
}
);
// Define the tools
const tools: Tool[] = [
{
name: "get_customer_accounts",
description: "Get all accessible CX Cloud customer accounts and their IDs. Use this first to get customer IDs for other operations.",
inputSchema: {
type: "object",
properties: {},
},
},
{
name: "get_hardware_inventory",
description: "Get hardware inventory for a specific customer. Returns details about all hardware assets.",
inputSchema: {
type: "object",
properties: {
customerId: {
type: "string",
description: "The customer ID (get from get_customer_accounts first)",
},
},
required: ["customerId"],
},
},
{
name: "get_network_elements",
description: "Get network elements inventory for a specific customer. Returns network devices and their details.",
inputSchema: {
type: "object",
properties: {
customerId: {
type: "string",
description: "The customer ID",
},
},
required: ["customerId"],
},
},
{
name: "get_contracts",
description: "Get all contracts for a specific customer. Returns contract details including coverage periods.",
inputSchema: {
type: "object",
properties: {
customerId: {
type: "string",
description: "The customer ID",
},
},
required: ["customerId"],
},
},
{
name: "get_covered_assets",
description: "Get all assets covered by contracts for a specific customer.",
inputSchema: {
type: "object",
properties: {
customerId: {
type: "string",
description: "The customer ID",
},
},
required: ["customerId"],
},
},
{
name: "get_uncovered_assets",
description: "Get all assets NOT covered by contracts for a specific customer. Useful for identifying coverage gaps.",
inputSchema: {
type: "object",
properties: {
customerId: {
type: "string",
description: "The customer ID",
},
},
required: ["customerId"],
},
},
{
name: "get_field_notices",
description: "Get field notices for a specific customer. Returns important product notifications and bulletins.",
inputSchema: {
type: "object",
properties: {
customerId: {
type: "string",
description: "The customer ID",
},
},
required: ["customerId"],
},
},
{
name: "get_hardware_eol",
description: "Get hardware end-of-life information for a specific customer. Shows which hardware is reaching end-of-life.",
inputSchema: {
type: "object",
properties: {
customerId: {
type: "string",
description: "The customer ID",
},
},
required: ["customerId"],
},
},
{
name: "get_software_eol",
description: "Get software end-of-life information for a specific customer. Shows which software versions are reaching end-of-life.",
inputSchema: {
type: "object",
properties: {
customerId: {
type: "string",
description: "The customer ID",
},
},
required: ["customerId"],
},
},
{
name: "get_security_advisories",
description: "Get security advisories for a specific customer. Returns security alerts and recommendations.",
inputSchema: {
type: "object",
properties: {
customerId: {
type: "string",
description: "The customer ID",
},
},
required: ["customerId"],
},
},
];
// List tools handler
server.setRequestHandler(ListToolsRequestSchema, async () => {
logger.debug('ListTools request received');
return {
tools,
};
});
// Call tool handler
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
const startTime = Date.now();
logger.toolInvoked(name, args);
try {
switch (name) {
case "get_customer_accounts": {
const data = await makeApiCall("/customer-info/customer-details");
return {
content: [
{
type: "text",
text: JSON.stringify(data, null, 2),
},
],
};
}
case "get_hardware_inventory": {
const customerId = args?.customerId as string;
if (!customerId) {
throw new Error("customerId is required");
}
const data = await makeApiCall("/inventory/hardware", customerId);
return {
content: [
{
type: "text",
text: JSON.stringify(data, null, 2),
},
],
};
}
case "get_network_elements": {
const customerId = args?.customerId as string;
if (!customerId) {
throw new Error("customerId is required");
}
const data = await makeApiCall("/inventory/network-elements", customerId);
return {
content: [
{
type: "text",
text: JSON.stringify(data, null, 2),
},
],
};
}
case "get_contracts": {
const customerId = args?.customerId as string;
if (!customerId) {
throw new Error("customerId is required");
}
const data = await makeApiCall("/contracts/contract-details", customerId);
return {
content: [
{
type: "text",
text: JSON.stringify(data, null, 2),
},
],
};
}
case "get_covered_assets": {
const customerId = args?.customerId as string;
if (!customerId) {
throw new Error("customerId is required");
}
const data = await makeApiCall("/contracts/coverage", customerId);
return {
content: [
{
type: "text",
text: JSON.stringify(data, null, 2),
},
],
};
}
case "get_uncovered_assets": {
const customerId = args?.customerId as string;
if (!customerId) {
throw new Error("customerId is required");
}
const data = await makeApiCall("/contracts/not-covered", customerId);
return {
content: [
{
type: "text",
text: JSON.stringify(data, null, 2),
},
],
};
}
case "get_field_notices": {
const customerId = args?.customerId as string;
if (!customerId) {
throw new Error("customerId is required");
}
const data = await makeApiCall("/product-alerts/field-notices", customerId);
return {
content: [
{
type: "text",
text: JSON.stringify(data, null, 2),
},
],
};
}
case "get_hardware_eol": {
const customerId = args?.customerId as string;
if (!customerId) {
throw new Error("customerId is required");
}
const data = await makeApiCall("/product-alerts/hardware-eol", customerId);
return {
content: [
{
type: "text",
text: JSON.stringify(data, null, 2),
},
],
};
}
case "get_software_eol": {
const customerId = args?.customerId as string;
if (!customerId) {
throw new Error("customerId is required");
}
const data = await makeApiCall("/product-alerts/software-eol", customerId);
return {
content: [
{
type: "text",
text: JSON.stringify(data, null, 2),
},
],
};
}
case "get_security_advisories": {
const customerId = args?.customerId as string;
if (!customerId) {
throw new Error("customerId is required");
}
const data = await makeApiCall("/product-alerts/security-advisories", customerId);
return {
content: [
{
type: "text",
text: JSON.stringify(data, null, 2),
},
],
};
}
default:
logger.warn(`Unknown tool requested: ${name}`);
throw new Error(`Unknown tool: ${name}`);
}
// Log successful completion
const duration = Date.now() - startTime;
logger.toolCompleted(name, true, duration);
} catch (error) {
const duration = Date.now() - startTime;
logger.toolCompleted(name, false, duration);
if (error instanceof Error) {
logger.error(`Tool execution failed: ${name}`, error);
return {
content: [
{
type: "text",
text: `Error: ${error.message}`,
},
],
isError: true,
};
}
throw error;
}
});
// Start the server
async function main() {
// Validate required environment variables
if (!process.env.CISCO_CLIENT_ID || !process.env.CISCO_CLIENT_SECRET) {
logger.error("Missing required environment variables: CISCO_CLIENT_ID and CISCO_CLIENT_SECRET");
console.error("Error: CISCO_CLIENT_ID and CISCO_CLIENT_SECRET must be set in .env file");
process.exit(1);
}
logger.info('Connecting to MCP transport...');
const transport = new StdioServerTransport();
await server.connect(transport);
logger.info('Cisco CX Cloud MCP Server running on stdio');
logger.info('Server ready to accept requests');
console.error("Cisco CX Cloud MCP Server running on stdio");
}
main().catch((error) => {
logger.error("Fatal server error", error);
console.error("Server error:", error);
process.exit(1);
});