mcp-access-inteligence-search
Version:
MCP server for Agents Intelligence API access intelligence search
279 lines (278 loc) • 11.4 kB
JavaScript
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
import { appendFileSync, existsSync, mkdirSync, readFileSync } from 'fs';
import { dirname, join } from 'path';
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const pkg = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf8'));
const { name, version } = pkg;
const JWT_TOKEN = process.env.JWT_TOKEN;
if (!JWT_TOKEN) {
throw new Error('JWT_TOKEN environment variable is required');
}
const API_BASE_URL = 'https://agents-backend-qqtpi.ondigitalocean.app';
// Set up logging
const LOG_DIR = join(__dirname, '..', 'logs');
const LOG_FILE = join(LOG_DIR, 'access-intelligence.log');
// Ensure log directory exists
if (!existsSync(LOG_DIR)) {
mkdirSync(LOG_DIR, { recursive: true });
}
// Logger function to write to file
function logToFile(message) {
const timestamp = new Date().toISOString();
const logEntry = `[${timestamp}] ${message}\n`;
try {
appendFileSync(LOG_FILE, logEntry);
}
catch (error) {
console.error(`Failed to write to log file: ${error}`);
}
}
// Define the Access Intelligence search tool
const ACCESS_INTELLIGENCE_TOOL = {
name: 'search',
description: 'Search for information sources to help with the message reply using Access Intelligence API',
inputSchema: {
type: 'object',
properties: {
query: {
type: 'string',
description: 'The search query to find relevant information sources to help with the message reply',
},
context: {
type: 'array',
items: {
type: 'string',
},
description: 'Optional context strings to help with the search',
},
},
required: ['query'],
},
};
// Create the server
const server = new Server({ name: 'access-intelligence', version }, {
capabilities: {
tools: {},
},
});
// Type guard for Access Intelligence search arguments
function isAccessIntelligenceArgs(args) {
return (typeof args === 'object' &&
args !== null &&
'query' in args &&
typeof args.query === 'string');
}
// Download a template by slug or id
async function downloadTemplate(identifier, params) {
logToFile(`Downloading template with identifier: "${identifier}"`);
try {
const url = new URL(`${API_BASE_URL}/template/${identifier}`);
if (params && Object.keys(params).length > 0) {
Object.entries(params).forEach(([key, value]) => {
url.searchParams.append(key, value);
});
logToFile(`Adding parameters to template request: ${JSON.stringify(params)}`);
}
const response = await fetch(url.toString(), {
method: 'GET',
headers: {
'Authorization': `Bearer ${JWT_TOKEN}`,
},
});
if (!response.ok) {
const errorText = await response.text();
const errorMessage = `Template download failed with status ${response.status}: ${errorText}`;
logToFile(`Template Download Error: ${errorMessage}`);
return null;
}
const template = await response.json();
logToFile(`Template response: ${JSON.stringify(template)}`);
const templateName = template.title || template.name || identifier;
logToFile(`Successfully downloaded template: "${templateName}"`);
return {
content: template.content,
name: templateName,
id: template.id,
slug: template.slug || identifier,
params: params
};
}
catch (error) {
const errorMessage = `Failed to download template: ${error instanceof Error ? error.message : String(error)}`;
logToFile(`Template Download Error: ${errorMessage}`);
return null;
}
}
// Perform Access Intelligence search
async function performAccessIntelligenceSearch(query, context = []) {
logToFile(`Access Intelligence Search Request - Query: "${query}", Context: ${JSON.stringify(context)}`);
try {
const response = await fetch(`${API_BASE_URL}/rag/search`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${JWT_TOKEN}`,
},
body: JSON.stringify({
query,
context,
}),
});
if (!response.ok) {
const errorText = await response.text();
const errorMessage = `API request failed with status ${response.status}: ${errorText}`;
logToFile(`Access Intelligence Search Error: ${errorMessage}`);
throw new Error(errorMessage);
}
const searchResults = await response.json();
logToFile(`Access Intelligence Search Response - Results: ${JSON.stringify(searchResults)}`);
if (!searchResults || searchResults.length === 0) {
logToFile("No search results returned from API");
return {
combinedText: "",
summary: "No access intelligence found matching your query."
};
}
const validResults = searchResults.filter(template => {
if (!template.id && !template.slug) {
logToFile(`Warning: Template result missing both id and slug: ${JSON.stringify(template)}`);
return false;
}
return true;
});
logToFile(`Found ${validResults.length} templates with valid identifiers out of ${searchResults.length} total results`);
if (validResults.length === 0) {
logToFile("No valid templates with identifiers found in search results");
return {
combinedText: "",
summary: "Found search results, but no valid access intelligence could be retrieved."
};
}
const downloadPromises = validResults.map(template => {
const identifier = template.id || template.slug;
const templateParams = {};
if (template.params && template.params.length > 0) {
template.params.forEach(param => {
if (param.recommended) {
templateParams[param.name] = param.recommended;
}
else {
switch (param.type.toLowerCase()) {
case 'number':
case 'integer':
if (param.name.toLowerCase() === 'limit') {
templateParams[param.name] = '10';
}
else {
templateParams[param.name] = '1';
}
break;
case 'string':
templateParams[param.name] = 'sample';
break;
case 'boolean':
templateParams[param.name] = 'true';
break;
default:
templateParams[param.name] = '';
}
}
});
logToFile(`Generated parameters for template ${identifier}: ${JSON.stringify(templateParams)}`);
}
return downloadTemplate(identifier, templateParams);
});
const downloadedTemplates = await Promise.all(downloadPromises);
const validTemplates = downloadedTemplates.filter((template) => template !== null);
if (validTemplates.length === 0) {
logToFile("No templates were successfully downloaded");
return {
combinedText: "",
summary: "Found access intelligence, but failed to download any of them."
};
}
const combinedText = validTemplates.map(template => {
let templateText = `# ${template.name}\n\n`;
if (template.params && Object.keys(template.params).length > 0) {
templateText += `Parameters used: ${JSON.stringify(template.params)}\n\n`;
}
templateText += template.content;
return templateText;
}).join('\n\n\n\n');
const templateSummary = validTemplates.map(template => template.name).join(', ');
logToFile(`Downloaded and combined ${validTemplates.length} templates: ${templateSummary}`);
return {
combinedText,
summary: `Found ${validTemplates.length} access intelligence results: ${templateSummary}`
};
}
catch (error) {
const errorMessage = `Failed to search access intelligence: ${error instanceof Error ? error.message : String(error)}`;
logToFile(`Access Intelligence Search Error: ${errorMessage}`);
throw new Error(errorMessage);
}
}
// Set up request handlers
server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [ACCESS_INTELLIGENCE_TOOL],
}));
server.setRequestHandler(CallToolRequestSchema, async (request) => {
try {
const { name, arguments: args } = request.params;
logToFile(`Tool Call Request - Tool: "${name}", Args: ${JSON.stringify(args)}`);
if (!args) {
throw new Error("No arguments provided");
}
if (name === "search") {
if (!isAccessIntelligenceArgs(args)) {
throw new Error("Invalid arguments for access intelligence search");
}
const { query, context = [] } = args;
const results = await performAccessIntelligenceSearch(query, context);
return {
content: [
{ type: "text", text: results.summary },
{ type: "text", text: results.combinedText }
],
isError: false,
};
}
else {
return {
content: [{ type: "text", text: `Unknown tool: ${name}` }],
isError: true,
};
}
}
catch (error) {
const errorMessage = `Error: ${error instanceof Error ? error.message : String(error)}`;
logToFile(`Tool Call Error: ${errorMessage}`);
return {
content: [
{
type: "text",
text: errorMessage,
},
],
isError: true,
};
}
});
// Start the server
async function runServer() {
logToFile("Server starting");
const transport = new StdioServerTransport();
await server.connect(transport);
logToFile("Server connected to stdio transport");
console.error("Access Intelligence MCP Server running on stdio");
}
runServer().catch((error) => {
logToFile(`Server startup error: ${error}`);
console.error("Server error:", error);
process.exit(1);
});