MCP Starter Server

  • src
#!/usr/bin/env node import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { CallToolRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema, } from "@modelcontextprotocol/sdk/types.js"; import axios from "axios"; // Configure Axios instance with Attio API credentials from environment const api = axios.create({ baseURL: "https://api.attio.com/v2", headers: { "Authorization": `Bearer ${process.env.ATTIO_API_KEY}`, "Content-Type": "application/json", }, }); const server = new Server( { name: "attio-mcp-server", version: "0.0.1", }, { capabilities: { resources: {}, tools: {}, }, }, ); // Helper function to create detailed error responses function createErrorResult(error: Error, url: string, method: string, responseData: any) { return { content: [ { type: "text", text: `ERROR: ${error.message}\n\n` + `=== Request Details ===\n` + `- Method: ${method}\n` + `- URL: ${url}\n\n` + `=== Response Details ===\n` + `- Status: ${responseData.status}\n` + `- Headers: ${JSON.stringify(responseData.headers || {}, null, 2)}\n` + `- Data: ${JSON.stringify(responseData.data || {}, null, 2)}\n` }, ], isError: true, error: { code: responseData.status || 500, message: error.message, details: responseData.data?.error || "Unknown error occurred" } }; } // Example: List Resources Handler (List Companies) server.setRequestHandler(ListResourcesRequestSchema, async (request) => { const path = "/objects/companies/records/query"; try { const response = await api.post(path, { limit: 20, sorts: [{ attribute: 'last_interaction', field: 'interacted_at', direction: 'desc' }] }); const companies = response.data.data || []; return { resources: companies.map((company: any) => ({ uri: `attio://companies/${company.id?.record_id}`, name: company.values?.name?.[0]?.value || "Unknown Company", mimeType: "application/json", })), description: `Found ${companies.length} companies that you have interacted with most recently`, }; } catch (error) { return createErrorResult( error instanceof Error ? error : new Error("Unknown error"), path, "POST", (error as any).response?.data || {} ); } }); // Example: Read Resource Handler (Get Company Details) server.setRequestHandler(ReadResourceRequestSchema, async (request) => { const companyId = request.params.uri.replace("attio://companies/", ""); try { const path = `/objects/companies/records/${companyId}`; const response = await api.get(path); return { contents: [ { uri: request.params.uri, text: JSON.stringify(response.data, null, 2), mimeType: "application/json", }, ], }; } catch (error) { return createErrorResult( error instanceof Error ? error : new Error("Unknown error"), `/objects/companies/${companyId}`, "GET", (error as any).response?.data || {} ); } }); // Example: List Tools Handler server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ { name: "search-companies", description: "Search for companies by name", inputSchema: { type: "object", properties: { query: { type: "string", description: "Company name or keyword to search for", }, }, required: ["query"], }, }, { name: "read-company-details", description: "Read details of a company", inputSchema: { type: "object", properties: { uri: { type: "string", description: "URI of the company to read", }, }, required: ["uri"], }, }, { name: "read-company-notes", description: "Read notes for a company", inputSchema: { type: "object", properties: { uri: { type: "string", description: "URI of the company to read notes for", }, limit: { type: "number", description: "Maximum number of notes to fetch (optional, default 10)", }, offset: { type: "number", description: "Number of notes to skip (optional, default 0)", }, }, required: ["uri"], }, }, { name: "create-company-note", description: "Add a new note to a company", inputSchema: { type: "object", properties: { companyId: { type: "string", description: "ID of the company to add the note to", }, noteTitle: { type: "string", description: "Title of the note", }, noteText: { type: "string", description: "Text content of the note", }, }, required: ["companyId", "noteTitle", "noteText"], }, }, ], }; }); // Example: Call Tool Handler with enhanced error handling server.setRequestHandler(CallToolRequestSchema, async (request) => { const toolName = request.params.name; try { if (toolName === "search-companies") { const query = request.params.arguments?.query as string; const path = "/objects/companies/records/query"; try { const response = await api.post(path, { filter: { name: { "$contains": query }, } }); const results = response.data.data || []; const companies = results.map((company: any) => { const companyName = company.values?.name?.[0]?.value || "Unknown Company"; const companyId = company.id?.record_id || "Record ID not found"; return `${companyName}: attio://companies/${companyId}`; }) .join("\n"); return { content: [ { type: "text", text: `Found ${results.length} companies:\n${companies}`, }, ], isError: false, }; } catch (error) { return createErrorResult( error instanceof Error ? error : new Error("Unknown error"), path, "GET", (error as any).response?.data || {} ); } } if (toolName === "read-company-details") { const uri = request.params.arguments?.uri as string; const companyId = uri.replace("attio://companies/", ""); const path = `/objects/companies/records/${companyId}`; try { const response = await api.get(path); return { content: [ { type: "text", text: `Company details for ${companyId}:\n${JSON.stringify(response.data, null, 2)}`, }, ], isError: false, }; } catch (error) { return createErrorResult( error instanceof Error ? error : new Error("Unknown error"), path, "GET", (error as any).response?.data || {} ); } } if (toolName == 'read-company-notes') { const uri = request.params.arguments?.uri as string; const limit = request.params.arguments?.limit as number || 10; const offset = request.params.arguments?.offset as number || 0; const companyId = uri.replace("attio://companies/", ""); const path = `/notes?limit=${limit}&offset=${offset}&parent_object=companies&parent_record_id=${companyId}`; try { const response = await api.get(path); const notes = response.data.data || []; return { content: [ { type: "text", text: `Found ${notes.length} notes for company ${companyId}:\n${notes.map((note: any) => JSON.stringify(note)).join("----------\n")}`, }, ], isError: false, }; } catch (error) { return createErrorResult( error instanceof Error ? error : new Error("Unknown error"), path, "GET", (error as any).response?.data || {} ); } } if (toolName === "create-company-note") { const companyId = request.params.arguments?.companyId as string; const noteTitle = request.params.arguments?.noteTitle as string; const noteText = request.params.arguments?.noteText as string; const url = `notes`; try { const response = await api.post(url, { data: { format: "plaintext", parent_object: "companies", parent_record_id: companyId, title: `[AI] ${noteTitle}`, content: noteText }, }); return { content: [ { type: "text", text: `Note added to company ${companyId}: attio://notes/${response.data?.id?.note_id}`, }, ], isError: false, }; } catch (error) { return createErrorResult( error instanceof Error ? error : new Error("Unknown error"), url, "POST", (error as any).response?.data || {} ); } } throw new Error("Tool not found"); } catch (error) { return { content: [ { type: "text", text: `Error executing tool '${toolName}': ${(error as Error).message}`, }, ], isError: true, }; } }); // Main function async function main() { try { if (!process.env.ATTIO_API_KEY) { throw new Error("ATTIO_API_KEY environment variable not found"); } const transport = new StdioServerTransport(); await server.connect(transport); } catch (error) { console.error("Error starting server:", error); process.exit(1); } } main().catch(error => { console.error("Unhandled error:", error); process.exit(1); });