UNPKG

exactmcp

Version:

MCP server for Exact Online API integration

277 lines 12 kB
#!/usr/bin/env node "use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); const index_js_1 = require("@modelcontextprotocol/sdk/server/index.js"); const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js"); const types_js_1 = require("@modelcontextprotocol/sdk/types.js"); const exact_client_js_1 = require("./exact-client.js"); const dotenv = __importStar(require("dotenv")); dotenv.config(); const server = new index_js_1.Server({ name: 'exact-mcp-server', version: '1.0.0', }, { capabilities: { tools: {}, }, }); let exactClient = null; server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => { return { tools: [ { name: 'authenticate', description: 'Authenticate with Exact Online API using OAuth2', inputSchema: { type: 'object', properties: { clientId: { type: 'string', description: 'Exact Online App Client ID (optional if set in .env)', }, clientSecret: { type: 'string', description: 'Exact Online App Client Secret (optional if set in .env)', }, redirectUri: { type: 'string', description: 'OAuth redirect URI', default: 'http://localhost:8080/callback', }, }, required: [], }, }, { name: 'complete_auth', description: 'Complete OAuth authentication with authorization code', inputSchema: { type: 'object', properties: { authCode: { type: 'string', description: 'Authorization code from OAuth callback', }, }, required: ['authCode'], }, }, { name: 'get_sales_orders', description: 'Retrieve sales orders from Exact Online', inputSchema: { type: 'object', properties: { divisionCode: { type: 'string', description: 'Division code (company database)', }, filter: { type: 'string', description: 'OData filter query (optional)', }, select: { type: 'string', description: 'OData select fields (optional)', }, top: { type: 'number', description: 'Number of records to return (default: 50)', default: 50, }, }, required: ['divisionCode'], }, }, { name: 'get_items', description: 'Retrieve items from Exact Online', inputSchema: { type: 'object', properties: { divisionCode: { type: 'string', description: 'Division code (company database)', }, filter: { type: 'string', description: 'OData filter query (optional)', }, select: { type: 'string', description: 'OData select fields (optional)', }, top: { type: 'number', description: 'Number of records to return (default: 50)', default: 50, }, }, required: ['divisionCode'], }, }, { name: 'get_accounts', description: 'Retrieve accounts (customers/suppliers) from Exact Online', inputSchema: { type: 'object', properties: { divisionCode: { type: 'string', description: 'Division code (company database)', }, filter: { type: 'string', description: 'OData filter query (optional)', }, select: { type: 'string', description: 'OData select fields (optional)', }, top: { type: 'number', description: 'Number of records to return (default: 50)', default: 50, }, }, required: ['divisionCode'], }, }, ], }; }); server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; try { switch (name) { case 'authenticate': { const { clientId = process.env.EXACT_CLIENT_ID, clientSecret = process.env.EXACT_CLIENT_SECRET, redirectUri = process.env.EXACT_REDIRECT_URI || 'http://localhost:8080/callback' } = args; if (!clientId || !clientSecret) { throw new types_js_1.McpError(types_js_1.ErrorCode.InvalidRequest, 'clientId and clientSecret are required (either as parameters or environment variables)'); } exactClient = new exact_client_js_1.ExactOnlineClient(clientId, clientSecret, redirectUri); const authUrl = exactClient.getAuthorizationUrl(); return { content: [ { type: 'text', text: `Authentication initiated. Please visit the following URL to authorize:\n\n${authUrl}\n\nAfter authorization, you will be redirected. Copy the 'code' parameter from the redirect URL and use it with the 'complete_auth' tool.`, }, ], }; } case 'complete_auth': { const { authCode } = args; if (!exactClient) { throw new types_js_1.McpError(types_js_1.ErrorCode.InvalidRequest, 'Must authenticate first using the authenticate tool'); } const tokenResult = await exactClient.exchangeCodeForToken(authCode); const divisions = await exactClient.getDivisions(); return { content: [ { type: 'text', text: `Authentication successful! Token expires at: ${new Date(tokenResult.expires_at).toISOString()}\n\nAvailable divisions:\n${divisions.map(d => `- ${d.code}: ${d.description}`).join('\n')}`, }, ], }; } case 'get_sales_orders': { const { divisionCode, filter, select, top = 50 } = args; if (!exactClient || !exactClient.isAuthenticated()) { throw new types_js_1.McpError(types_js_1.ErrorCode.InvalidRequest, 'Must authenticate first'); } const orders = await exactClient.getSalesOrders(divisionCode, { filter, select, top }); return { content: [ { type: 'text', text: `Retrieved ${orders.length} sales orders:\n\n${JSON.stringify(orders, null, 2)}`, }, ], }; } case 'get_items': { const { divisionCode, filter, select, top = 50 } = args; if (!exactClient || !exactClient.isAuthenticated()) { throw new types_js_1.McpError(types_js_1.ErrorCode.InvalidRequest, 'Must authenticate first'); } const items = await exactClient.getItems(divisionCode, { filter, select, top }); return { content: [ { type: 'text', text: `Retrieved ${items.length} items:\n\n${JSON.stringify(items, null, 2)}`, }, ], }; } case 'get_accounts': { const { divisionCode, filter, select, top = 50 } = args; if (!exactClient || !exactClient.isAuthenticated()) { throw new types_js_1.McpError(types_js_1.ErrorCode.InvalidRequest, 'Must authenticate first'); } const accounts = await exactClient.getAccounts(divisionCode, { filter, select, top }); return { content: [ { type: 'text', text: `Retrieved ${accounts.length} accounts:\n\n${JSON.stringify(accounts, null, 2)}`, }, ], }; } default: throw new types_js_1.McpError(types_js_1.ErrorCode.MethodNotFound, `Unknown tool: ${name}`); } } catch (error) { if (error instanceof types_js_1.McpError) { throw error; } throw new types_js_1.McpError(types_js_1.ErrorCode.InternalError, `Tool execution failed: ${error}`); } }); async function main() { const transport = new stdio_js_1.StdioServerTransport(); await server.connect(transport); console.error('Exact MCP server running on stdio'); } main().catch((error) => { console.error('Server error:', error); process.exit(1); }); //# sourceMappingURL=index.js.map