Files
KiC-AI/plugins/nexar_server.py
T
2026-03-08 03:17:21 +01:00

272 lines
11 KiB
Python

#!/usr/bin/env python3
"""
Nexar API MCP Server
Provides component search and pricing via Model Context Protocol (MCP)
"""
import json
import sys
import logging
from typing import Dict, List, Any, Optional
# Set up logging
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
class NexarServer:
"""MCP Server for Nexar API integration"""
def __init__(self):
self.capabilities = {
"tools": [
{
"name": "search_parts",
"description": "Search for electronic components and get pricing",
"inputSchema": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "Component search query (part number, description, etc.)"
},
"limit": {
"type": "integer",
"description": "Maximum number of results to return",
"default": 5
}
},
"required": ["query"]
}
},
{
"name": "get_pricing",
"description": "Get detailed pricing for a specific part",
"inputSchema": {
"type": "object",
"properties": {
"part_number": {
"type": "string",
"description": "Exact part number to get pricing for"
}
},
"required": ["part_number"]
}
}
]
}
def handle_initialize(self, params: Dict[str, Any]) -> Dict[str, Any]:
"""Handle MCP initialize request"""
return {
"protocolVersion": "2025-03-26",
"capabilities": self.capabilities,
"serverInfo": {
"name": "nexar-server",
"version": "1.0.0"
}
}
def handle_list_tools(self, params: Dict[str, Any]) -> Dict[str, Any]:
"""Handle list_tools request"""
return {"tools": self.capabilities["tools"]}
def handle_call_tool(self, params: Dict[str, Any]) -> Dict[str, Any]:
"""Handle tool call request"""
tool_name = params.get("name")
arguments = params.get("arguments", {})
if tool_name == "search_parts":
return self._search_parts(arguments)
elif tool_name == "get_pricing":
return self._get_pricing(arguments)
else:
raise ValueError(f"Unknown tool: {tool_name}")
def _search_parts(self, args: Dict[str, Any]) -> Dict[str, Any]:
"""Search for parts - demo implementation with realistic data"""
query = args.get("query", "")
limit = args.get("limit", 5)
logger.info(f"Searching for parts: {query} (limit: {limit})")
# Demo data with realistic component information
demo_parts = [
{
"part_number": "STM32F407VGT6",
"manufacturer": "STMicroelectronics",
"description": "ARM Cortex-M4 32b MCU+FPU 210DMIPS 1MB Flash 192+4KB RAM USB OTG HS/FS 82 IOs",
"category": "Microcontrollers",
"pricing": {
"Digi-Key": {"price_1": 8.45, "price_10": 7.89, "price_100": 6.98, "stock": 2547},
"Mouser": {"price_1": 8.52, "price_10": 7.95, "price_100": 7.02, "stock": 1834},
"Farnell": {"price_1": 9.12, "price_10": 8.34, "price_100": 7.45, "stock": 892}
}
},
{
"part_number": "STM32F4DISCOVERY",
"manufacturer": "STMicroelectronics",
"description": "Development board for STM32F407VG MCU",
"category": "Development Boards",
"pricing": {
"Digi-Key": {"price_1": 23.50, "price_10": 22.15, "price_100": 19.85, "stock": 456},
"Mouser": {"price_1": 24.12, "price_10": 22.78, "price_100": 20.34, "stock": 234}
}
},
{
"part_number": "ESP32-WROOM-32E",
"manufacturer": "Espressif Systems",
"description": "WiFi+BT SoC Module 2.4GHz 32-bit",
"category": "RF Modules",
"pricing": {
"Digi-Key": {"price_1": 3.45, "price_10": 2.98, "price_100": 2.15, "stock": 5670},
"Mouser": {"price_1": 3.52, "price_10": 3.05, "price_100": 2.22, "stock": 4123},
"Farnell": {"price_1": 3.78, "price_10": 3.25, "price_100": 2.45, "stock": 2890}
}
},
{
"part_number": "LM358P",
"manufacturer": "Texas Instruments",
"description": "Dual Operational Amplifier, 8-Pin PDIP",
"category": "Amplifiers",
"pricing": {
"Digi-Key": {"price_1": 0.45, "price_10": 0.38, "price_100": 0.25, "stock": 12450},
"Mouser": {"price_1": 0.48, "price_10": 0.41, "price_100": 0.28, "stock": 8967},
"Farnell": {"price_1": 0.52, "price_10": 0.44, "price_100": 0.31, "stock": 6789}
}
},
{
"part_number": "ATMEGA328P-PU",
"manufacturer": "Microchip Technology",
"description": "8-bit AVR Microcontroller, 32KB Flash, 28-Pin PDIP",
"category": "Microcontrollers",
"pricing": {
"Digi-Key": {"price_1": 2.45, "price_10": 2.15, "price_100": 1.85, "stock": 3456},
"Mouser": {"price_1": 2.52, "price_10": 2.22, "price_100": 1.91, "stock": 2789},
"Farnell": {"price_1": 2.67, "price_10": 2.35, "price_100": 2.05, "stock": 1567}
}
}
]
# Simple search matching
results = []
query_lower = query.lower()
for part in demo_parts:
if (query_lower in part["part_number"].lower() or
query_lower in part["description"].lower() or
query_lower in part["manufacturer"].lower()):
results.append(part)
if len(results) >= limit:
break
return {
"content": [
{
"type": "text",
"text": f"Found {len(results)} parts matching '{query}'"
}
],
"isError": False,
"_meta": {
"parts": results
}
}
def _get_pricing(self, args: Dict[str, Any]) -> Dict[str, Any]:
"""Get detailed pricing for a specific part"""
part_number = args.get("part_number", "")
logger.info(f"Getting pricing for part: {part_number}")
# This would normally call the real Nexar API
# For demo, return sample pricing
return {
"content": [
{
"type": "text",
"text": f"Pricing for {part_number} (demo data)"
}
],
"isError": False,
"_meta": {
"pricing": {
"Digi-Key": {"price_1": 5.99, "price_10": 5.49, "stock": 1000},
"Mouser": {"price_1": 6.15, "price_10": 5.65, "stock": 750}
}
}
}
def run(self):
"""Main server loop - reads JSON-RPC requests from stdin"""
logger.info("Nexar MCP Server starting...")
try:
while True:
line = sys.stdin.readline()
if not line:
break
line = line.strip()
if not line:
continue
try:
request = json.loads(line)
logger.debug(f"Received request: {request}")
# Handle different MCP methods
method = request.get("method")
params = request.get("params", {})
request_id = request.get("id")
if method == "initialize":
result = self.handle_initialize(params)
elif method == "tools/list":
result = self.handle_list_tools(params)
elif method == "tools/call":
result = self.handle_call_tool(params)
else:
raise ValueError(f"Unknown method: {method}")
# Send response
response = {
"jsonrpc": "2.0",
"id": request_id,
"result": result
}
response_json = json.dumps(response)
print(response_json)
sys.stdout.flush()
logger.debug(f"Sent response: {response_json}")
except json.JSONDecodeError as e:
logger.error(f"JSON decode error: {e}")
error_response = {
"jsonrpc": "2.0",
"id": request.get("id") if 'request' in locals() else None,
"error": {"code": -32700, "message": "Parse error"}
}
print(json.dumps(error_response))
sys.stdout.flush()
except Exception as e:
logger.error(f"Request processing error: {e}")
error_response = {
"jsonrpc": "2.0",
"id": request.get("id") if 'request' in locals() else None,
"error": {"code": -32603, "message": str(e)}
}
print(json.dumps(error_response))
sys.stdout.flush()
except KeyboardInterrupt:
logger.info("Server shutting down...")
except Exception as e:
logger.error(f"Server error: {e}")
if __name__ == "__main__":
server = NexarServer()
server.run()