@knowall-ai/mcp-business-central 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,233 @@
1
+ # Microsoft Business Central MCP Server
2
+
3
+ <img width="1536" height="1024" alt="mcp-business-central" src="https://github.com/user-attachments/assets/13932bfd-a5b9-4668-a7cd-ac9549a09673" />
4
+
5
+ Model Context Protocol (MCP) server for Microsoft Dynamics 365 Business Central. Provides AI assistants with direct access to Business Central data through properly formatted API v2.0 calls.
6
+
7
+ ## Features
8
+
9
+ - ✅ **Correct API URLs**: Uses proper `/companies(id)/resource` format (no ODataV4 segment)
10
+ - ✅ **Zero Installation**: Run with `npx` - no pre-installation required
11
+ - ✅ **Azure CLI Auth**: Leverages existing Azure CLI authentication
12
+ - ✅ **Clean Tool Names**: No prefixes, just `get_schema`, `list_items`, etc.
13
+ - ✅ **Full CRUD**: Create, read, update, and delete Business Central records
14
+
15
+ ## Installation
16
+
17
+ ### Using npx (Recommended)
18
+
19
+ No installation needed! Configure in Claude Desktop or Claude Code:
20
+
21
+ ```json
22
+ {
23
+ "mcpServers": {
24
+ "business-central": {
25
+ "type": "stdio",
26
+ "command": "cmd",
27
+ "args": ["/c", "npx", "-y", "@knowall-ai/mcp-business-central"],
28
+ "env": {
29
+ "BC_URL_SERVER": "https://api.businesscentral.dynamics.com/v2.0/{tenant-id}/{environment}/api/v2.0",
30
+ "BC_COMPANY": "Your Company Name",
31
+ "BC_AUTH_TYPE": "azure_cli"
32
+ }
33
+ }
34
+ }
35
+ }
36
+ ```
37
+
38
+ **Note for Windows**: Use `cmd` with `/c` as shown above for proper npx execution.
39
+
40
+ ### Local Development
41
+
42
+ ```bash
43
+ git clone https://github.com/knowall-ai/mcp-business-central.git
44
+ cd mcp-business-central
45
+ npm install
46
+ npm run build
47
+ node build/index.js
48
+ ```
49
+
50
+ ## Configuration
51
+
52
+ ### Environment Variables
53
+
54
+ | Variable | Required | Description | Example |
55
+ |----------|----------|-------------|---------|
56
+ | `BC_URL_SERVER` | Yes | Business Central API base URL | `https://api.businesscentral.dynamics.com/v2.0/{tenant}/Production/api/v2.0` |
57
+ | `BC_COMPANY` | Yes | Company display name | `KnowAll Ltd` |
58
+ | `BC_AUTH_TYPE` | No | Authentication type (default: `azure_cli`) | `azure_cli` |
59
+
60
+ ### Getting Your Configuration Values
61
+
62
+ 1. **Tenant ID**: Find in Azure Portal → Azure Active Directory → Overview
63
+ 2. **Environment**: Usually `Production` or `Sandbox`
64
+ 3. **Company Name**: The display name shown in Business Central
65
+
66
+ Example URL format:
67
+ ```
68
+ https://api.businesscentral.dynamics.com/v2.0/f36f6414-cb7d-4545-9cf2-7574f7b5c584/Production/api/v2.0
69
+ ```
70
+
71
+ ## Prerequisites
72
+
73
+ - **Azure CLI**: Must be installed and authenticated
74
+ - Install: https://docs.microsoft.com/cli/azure/install-azure-cli
75
+ - Login: `az login`
76
+ - Get token: `az account get-access-token --resource https://api.businesscentral.dynamics.com`
77
+
78
+ ## Available Tools
79
+
80
+ ### 1. `get_schema`
81
+ Get OData metadata for a Business Central resource.
82
+
83
+ **Parameters:**
84
+ - `resource` (string, required): Resource name (e.g., `customers`, `contacts`, `salesOpportunities`)
85
+
86
+ **Example:**
87
+ ```json
88
+ {
89
+ "resource": "customers"
90
+ }
91
+ ```
92
+
93
+ ### 2. `list_items`
94
+ List items with optional filtering and pagination.
95
+
96
+ **Parameters:**
97
+ - `resource` (string, required): Resource name
98
+ - `filter` (string, optional): OData filter expression
99
+ - `top` (number, optional): Maximum number of items to return
100
+ - `skip` (number, optional): Number of items to skip for pagination
101
+
102
+ **Example:**
103
+ ```json
104
+ {
105
+ "resource": "customers",
106
+ "filter": "displayName eq 'Contoso'",
107
+ "top": 10
108
+ }
109
+ ```
110
+
111
+ ### 3. `get_items_by_field`
112
+ Get items matching a specific field value.
113
+
114
+ **Parameters:**
115
+ - `resource` (string, required): Resource name
116
+ - `field` (string, required): Field name to filter by
117
+ - `value` (string, required): Value to match
118
+
119
+ **Example:**
120
+ ```json
121
+ {
122
+ "resource": "contacts",
123
+ "field": "companyName",
124
+ "value": "Contoso Ltd"
125
+ }
126
+ ```
127
+
128
+ ### 4. `create_item`
129
+ Create a new item in Business Central.
130
+
131
+ **Parameters:**
132
+ - `resource` (string, required): Resource name
133
+ - `item_data` (object, required): Item data to create
134
+
135
+ **Example:**
136
+ ```json
137
+ {
138
+ "resource": "contacts",
139
+ "item_data": {
140
+ "displayName": "John Doe",
141
+ "companyName": "Contoso Ltd",
142
+ "email": "john.doe@contoso.com"
143
+ }
144
+ }
145
+ ```
146
+
147
+ ### 5. `update_item`
148
+ Update an existing item.
149
+
150
+ **Parameters:**
151
+ - `resource` (string, required): Resource name
152
+ - `item_id` (string, required): Item ID (GUID)
153
+ - `item_data` (object, required): Fields to update
154
+
155
+ **Example:**
156
+ ```json
157
+ {
158
+ "resource": "customers",
159
+ "item_id": "1366066e-7688-f011-b9d1-6045bde9b95f",
160
+ "item_data": {
161
+ "displayName": "Updated Name"
162
+ }
163
+ }
164
+ ```
165
+
166
+ ### 6. `delete_item`
167
+ Delete an item from Business Central.
168
+
169
+ **Parameters:**
170
+ - `resource` (string, required): Resource name
171
+ - `item_id` (string, required): Item ID (GUID)
172
+
173
+ **Example:**
174
+ ```json
175
+ {
176
+ "resource": "contacts",
177
+ "item_id": "a1b2c3d4-e5f6-g7h8-i9j0-k1l2m3n4o5p6"
178
+ }
179
+ ```
180
+
181
+ ## Common Resources
182
+
183
+ - `companies` - Company information
184
+ - `customers` - Customer records
185
+ - `contacts` - Contact records
186
+ - `salesOpportunities` - Sales opportunities
187
+ - `salesQuotes` - Sales quotes
188
+ - `salesOrders` - Sales orders
189
+ - `salesInvoices` - Sales invoices
190
+ - `items` - Product/service items
191
+ - `vendors` - Vendor records
192
+
193
+ ## Troubleshooting
194
+
195
+ ### 401 Unauthorized
196
+ - Ensure Azure CLI is logged in: `az login`
197
+ - Verify you have access to Business Central in your tenant
198
+ - Test token retrieval: `az account get-access-token --resource https://api.businesscentral.dynamics.com`
199
+
200
+ ### Company Not Found
201
+ - Check company name matches exactly (case-sensitive)
202
+ - Verify company exists: Access Business Central web UI
203
+ - Ensure URL includes correct tenant ID and environment
204
+
205
+ ### Resource Not Found
206
+ - Check resource name spelling (e.g., `customers` not `customer`)
207
+ - Some resources may not be available in your Business Central version
208
+ - Use `get_schema` to explore available resources
209
+
210
+ ## Development
211
+
212
+ ```bash
213
+ # Install dependencies
214
+ npm install
215
+
216
+ # Build TypeScript
217
+ npm run build
218
+
219
+ # Watch mode for development
220
+ npm run dev
221
+ ```
222
+
223
+ ## License
224
+
225
+ MIT
226
+
227
+ ## Contributing
228
+
229
+ Issues and pull requests welcome at https://github.com/knowall-ai/mcp-business-central
230
+
231
+ ## Related Projects
232
+
233
+ - MCP Specification: [modelcontextprotocol.io](https://modelcontextprotocol.io)
@@ -0,0 +1,59 @@
1
+ export interface BusinessCentralConfig {
2
+ serverUrl: string;
3
+ companyName: string;
4
+ authType: 'azure_cli';
5
+ }
6
+ export interface Company {
7
+ id: string;
8
+ systemVersion: string;
9
+ timestamp: number;
10
+ name: string;
11
+ displayName: string;
12
+ businessProfileId: string;
13
+ systemCreatedAt: string;
14
+ systemCreatedBy: string;
15
+ systemModifiedAt: string;
16
+ systemModifiedBy: string;
17
+ }
18
+ export declare class BusinessCentralClient {
19
+ private config;
20
+ private companyId?;
21
+ private credential?;
22
+ constructor(config: BusinessCentralConfig);
23
+ /**
24
+ * Get the company ID by looking up the company name
25
+ */
26
+ private getCompanyId;
27
+ /**
28
+ * Make an authenticated request to Business Central API
29
+ */
30
+ private request;
31
+ /**
32
+ * Get OData metadata for a resource
33
+ */
34
+ getSchema(resource: string): Promise<any>;
35
+ /**
36
+ * List items from a resource with optional filtering and pagination
37
+ */
38
+ listItems(resource: string, options?: {
39
+ filter?: string;
40
+ top?: number;
41
+ skip?: number;
42
+ }): Promise<any>;
43
+ /**
44
+ * Get items by field value
45
+ */
46
+ getItemsByField(resource: string, field: string, value: string): Promise<any>;
47
+ /**
48
+ * Create a new item
49
+ */
50
+ createItem(resource: string, data: any): Promise<any>;
51
+ /**
52
+ * Update an existing item
53
+ */
54
+ updateItem(resource: string, itemId: string, data: any): Promise<any>;
55
+ /**
56
+ * Delete an item
57
+ */
58
+ deleteItem(resource: string, itemId: string): Promise<void>;
59
+ }
@@ -0,0 +1,114 @@
1
+ import { AzureCliCredential } from '@azure/identity';
2
+ export class BusinessCentralClient {
3
+ config;
4
+ companyId;
5
+ credential;
6
+ constructor(config) {
7
+ this.config = config;
8
+ if (config.authType === 'azure_cli') {
9
+ this.credential = new AzureCliCredential();
10
+ }
11
+ }
12
+ /**
13
+ * Get the company ID by looking up the company name
14
+ */
15
+ async getCompanyId() {
16
+ if (this.companyId) {
17
+ return this.companyId;
18
+ }
19
+ const url = `${this.config.serverUrl}/companies?$filter=displayName eq '${this.config.companyName}'`;
20
+ const response = await this.request('GET', url);
21
+ if (!response.value || response.value.length === 0) {
22
+ throw new Error(`Company '${this.config.companyName}' not found`);
23
+ }
24
+ const companyId = response.value[0].id;
25
+ this.companyId = companyId;
26
+ return companyId;
27
+ }
28
+ /**
29
+ * Make an authenticated request to Business Central API
30
+ */
31
+ async request(method, url, body) {
32
+ if (!this.credential) {
33
+ throw new Error('Authentication not configured');
34
+ }
35
+ // Get access token for Business Central
36
+ const tokenResponse = await this.credential.getToken('https://api.businesscentral.dynamics.com/.default');
37
+ const headers = {
38
+ 'Authorization': `Bearer ${tokenResponse.token}`,
39
+ 'Content-Type': 'application/json',
40
+ 'Accept': 'application/json'
41
+ };
42
+ const options = {
43
+ method,
44
+ headers,
45
+ };
46
+ if (body) {
47
+ options.body = JSON.stringify(body);
48
+ }
49
+ const response = await fetch(url, options);
50
+ if (!response.ok) {
51
+ const errorText = await response.text();
52
+ throw new Error(`Business Central API error (${response.status}): ${errorText}`);
53
+ }
54
+ return response.json();
55
+ }
56
+ /**
57
+ * Get OData metadata for a resource
58
+ */
59
+ async getSchema(resource) {
60
+ const companyId = await this.getCompanyId();
61
+ const url = `${this.config.serverUrl}/companies(${companyId})/${resource}/$metadata`;
62
+ return this.request('GET', url);
63
+ }
64
+ /**
65
+ * List items from a resource with optional filtering and pagination
66
+ */
67
+ async listItems(resource, options) {
68
+ const companyId = await this.getCompanyId();
69
+ let url = `${this.config.serverUrl}/companies(${companyId})/${resource}`;
70
+ const params = new URLSearchParams();
71
+ if (options?.filter)
72
+ params.append('$filter', options.filter);
73
+ if (options?.top)
74
+ params.append('$top', options.top.toString());
75
+ if (options?.skip)
76
+ params.append('$skip', options.skip.toString());
77
+ if (params.toString()) {
78
+ url += `?${params.toString()}`;
79
+ }
80
+ return this.request('GET', url);
81
+ }
82
+ /**
83
+ * Get items by field value
84
+ */
85
+ async getItemsByField(resource, field, value) {
86
+ return this.listItems(resource, {
87
+ filter: `${field} eq '${value}'`
88
+ });
89
+ }
90
+ /**
91
+ * Create a new item
92
+ */
93
+ async createItem(resource, data) {
94
+ const companyId = await this.getCompanyId();
95
+ const url = `${this.config.serverUrl}/companies(${companyId})/${resource}`;
96
+ return this.request('POST', url, data);
97
+ }
98
+ /**
99
+ * Update an existing item
100
+ */
101
+ async updateItem(resource, itemId, data) {
102
+ const companyId = await this.getCompanyId();
103
+ const url = `${this.config.serverUrl}/companies(${companyId})/${resource}(${itemId})`;
104
+ return this.request('PATCH', url, data);
105
+ }
106
+ /**
107
+ * Delete an item
108
+ */
109
+ async deleteItem(resource, itemId) {
110
+ const companyId = await this.getCompanyId();
111
+ const url = `${this.config.serverUrl}/companies(${companyId})/${resource}(${itemId})`;
112
+ await this.request('DELETE', url);
113
+ }
114
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/build/index.js ADDED
@@ -0,0 +1,262 @@
1
+ #!/usr/bin/env node
2
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
3
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
+ import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
5
+ import { BusinessCentralClient } from './business-central-client.js';
6
+ // Read configuration from environment variables
7
+ const BC_URL_SERVER = process.env.BC_URL_SERVER;
8
+ const BC_COMPANY = process.env.BC_COMPANY;
9
+ const BC_AUTH_TYPE = process.env.BC_AUTH_TYPE || 'azure_cli';
10
+ if (!BC_URL_SERVER || !BC_COMPANY) {
11
+ console.error('Error: BC_URL_SERVER and BC_COMPANY environment variables are required');
12
+ process.exit(1);
13
+ }
14
+ if (BC_AUTH_TYPE !== 'azure_cli') {
15
+ console.error('Error: Only azure_cli authentication is currently supported');
16
+ process.exit(1);
17
+ }
18
+ // Create Business Central client
19
+ const bcClient = new BusinessCentralClient({
20
+ serverUrl: BC_URL_SERVER,
21
+ companyName: BC_COMPANY,
22
+ authType: BC_AUTH_TYPE
23
+ });
24
+ // Create MCP server
25
+ const server = new Server({
26
+ name: 'business-central',
27
+ version: '0.1.0',
28
+ }, {
29
+ capabilities: {
30
+ tools: {},
31
+ },
32
+ });
33
+ // Register tool handlers
34
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
35
+ return {
36
+ tools: [
37
+ {
38
+ name: 'get_schema',
39
+ description: 'Get schema information for a Business Central resource',
40
+ inputSchema: {
41
+ type: 'object',
42
+ properties: {
43
+ resource: {
44
+ type: 'string',
45
+ description: 'The resource name (e.g., customers, contacts, salesOpportunities)',
46
+ },
47
+ },
48
+ required: ['resource'],
49
+ },
50
+ },
51
+ {
52
+ name: 'list_items',
53
+ description: 'Get items from Business Central with filtering and pagination',
54
+ inputSchema: {
55
+ type: 'object',
56
+ properties: {
57
+ resource: {
58
+ type: 'string',
59
+ description: 'The resource name (e.g., customers, contacts, salesOpportunities)',
60
+ },
61
+ filter: {
62
+ type: 'string',
63
+ description: 'OData filter expression (optional)',
64
+ },
65
+ top: {
66
+ type: 'number',
67
+ description: 'Maximum number of items to return (optional)',
68
+ },
69
+ skip: {
70
+ type: 'number',
71
+ description: 'Number of items to skip for pagination (optional)',
72
+ },
73
+ },
74
+ required: ['resource'],
75
+ },
76
+ },
77
+ {
78
+ name: 'get_items_by_field',
79
+ description: 'Get items matching a field value',
80
+ inputSchema: {
81
+ type: 'object',
82
+ properties: {
83
+ resource: {
84
+ type: 'string',
85
+ description: 'The resource name (e.g., customers, contacts)',
86
+ },
87
+ field: {
88
+ type: 'string',
89
+ description: 'The field name to filter by',
90
+ },
91
+ value: {
92
+ type: 'string',
93
+ description: 'The value to match',
94
+ },
95
+ },
96
+ required: ['resource', 'field', 'value'],
97
+ },
98
+ },
99
+ {
100
+ name: 'create_item',
101
+ description: 'Create a new item in Business Central',
102
+ inputSchema: {
103
+ type: 'object',
104
+ properties: {
105
+ resource: {
106
+ type: 'string',
107
+ description: 'The resource name (e.g., customers, contacts)',
108
+ },
109
+ item_data: {
110
+ type: 'object',
111
+ description: 'The item data to create',
112
+ },
113
+ },
114
+ required: ['resource', 'item_data'],
115
+ },
116
+ },
117
+ {
118
+ name: 'update_item',
119
+ description: 'Update an existing item in Business Central',
120
+ inputSchema: {
121
+ type: 'object',
122
+ properties: {
123
+ resource: {
124
+ type: 'string',
125
+ description: 'The resource name (e.g., customers, contacts)',
126
+ },
127
+ item_id: {
128
+ type: 'string',
129
+ description: 'The ID of the item to update',
130
+ },
131
+ item_data: {
132
+ type: 'object',
133
+ description: 'The item data to update',
134
+ },
135
+ },
136
+ required: ['resource', 'item_id', 'item_data'],
137
+ },
138
+ },
139
+ {
140
+ name: 'delete_item',
141
+ description: 'Delete an item from Business Central',
142
+ inputSchema: {
143
+ type: 'object',
144
+ properties: {
145
+ resource: {
146
+ type: 'string',
147
+ description: 'The resource name (e.g., customers, contacts)',
148
+ },
149
+ item_id: {
150
+ type: 'string',
151
+ description: 'The ID of the item to delete',
152
+ },
153
+ },
154
+ required: ['resource', 'item_id'],
155
+ },
156
+ },
157
+ ],
158
+ };
159
+ });
160
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
161
+ try {
162
+ const { name, arguments: args } = request.params;
163
+ switch (name) {
164
+ case 'get_schema': {
165
+ const { resource } = args;
166
+ const result = await bcClient.getSchema(resource);
167
+ return {
168
+ content: [
169
+ {
170
+ type: 'text',
171
+ text: JSON.stringify(result, null, 2),
172
+ },
173
+ ],
174
+ };
175
+ }
176
+ case 'list_items': {
177
+ const { resource, filter, top, skip } = args;
178
+ const result = await bcClient.listItems(resource, { filter, top, skip });
179
+ return {
180
+ content: [
181
+ {
182
+ type: 'text',
183
+ text: JSON.stringify(result, null, 2),
184
+ },
185
+ ],
186
+ };
187
+ }
188
+ case 'get_items_by_field': {
189
+ const { resource, field, value } = args;
190
+ const result = await bcClient.getItemsByField(resource, field, value);
191
+ return {
192
+ content: [
193
+ {
194
+ type: 'text',
195
+ text: JSON.stringify(result, null, 2),
196
+ },
197
+ ],
198
+ };
199
+ }
200
+ case 'create_item': {
201
+ const { resource, item_data } = args;
202
+ const result = await bcClient.createItem(resource, item_data);
203
+ return {
204
+ content: [
205
+ {
206
+ type: 'text',
207
+ text: JSON.stringify(result, null, 2),
208
+ },
209
+ ],
210
+ };
211
+ }
212
+ case 'update_item': {
213
+ const { resource, item_id, item_data } = args;
214
+ const result = await bcClient.updateItem(resource, item_id, item_data);
215
+ return {
216
+ content: [
217
+ {
218
+ type: 'text',
219
+ text: JSON.stringify(result, null, 2),
220
+ },
221
+ ],
222
+ };
223
+ }
224
+ case 'delete_item': {
225
+ const { resource, item_id } = args;
226
+ await bcClient.deleteItem(resource, item_id);
227
+ return {
228
+ content: [
229
+ {
230
+ type: 'text',
231
+ text: 'Item deleted successfully',
232
+ },
233
+ ],
234
+ };
235
+ }
236
+ default:
237
+ throw new Error(`Unknown tool: ${name}`);
238
+ }
239
+ }
240
+ catch (error) {
241
+ const errorMessage = error instanceof Error ? error.message : String(error);
242
+ return {
243
+ content: [
244
+ {
245
+ type: 'text',
246
+ text: `Error: ${errorMessage}`,
247
+ },
248
+ ],
249
+ isError: true,
250
+ };
251
+ }
252
+ });
253
+ // Start server
254
+ async function main() {
255
+ const transport = new StdioServerTransport();
256
+ await server.connect(transport);
257
+ console.error('Business Central MCP server running on stdio');
258
+ }
259
+ main().catch((error) => {
260
+ console.error('Server error:', error);
261
+ process.exit(1);
262
+ });
package/package.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "@knowall-ai/mcp-business-central",
3
+ "version": "0.1.0",
4
+ "description": "Model Context Protocol server for Microsoft Dynamics 365 Business Central",
5
+ "type": "module",
6
+ "bin": {
7
+ "mcp-business-central": "./build/index.js"
8
+ },
9
+ "scripts": {
10
+ "build": "tsc",
11
+ "prepare": "npm run build",
12
+ "dev": "tsc --watch"
13
+ },
14
+ "keywords": [
15
+ "mcp",
16
+ "business-central",
17
+ "dynamics-365",
18
+ "model-context-protocol"
19
+ ],
20
+ "author": "KnowAll AI",
21
+ "license": "MIT",
22
+ "dependencies": {
23
+ "@modelcontextprotocol/sdk": "^1.0.0",
24
+ "@azure/identity": "^4.0.0"
25
+ },
26
+ "devDependencies": {
27
+ "@types/node": "^20.0.0",
28
+ "typescript": "^5.0.0"
29
+ },
30
+ "engines": {
31
+ "node": ">=18"
32
+ }
33
+ }
package/smithery.yaml ADDED
@@ -0,0 +1,28 @@
1
+ startCommand:
2
+ type: stdio
3
+ configSchema:
4
+ type: object
5
+ required:
6
+ - bcServerUrl
7
+ - bcCompany
8
+ properties:
9
+ bcServerUrl:
10
+ type: string
11
+ default: "https://api.businesscentral.dynamics.com/v2.0/{tenant}/{environment}/api/v2.0"
12
+ description: The Business Central API server URL (include tenant and environment).
13
+ bcCompany:
14
+ type: string
15
+ default: ""
16
+ description: The company name to connect to in Business Central.
17
+ bcAuthType:
18
+ type: string
19
+ default: azure_cli
20
+ description: Authentication type (currently only azure_cli is supported).
21
+ commandFunction:
22
+ config => {
23
+ const env = {};
24
+ if (config.bcServerUrl) env.BC_URL_SERVER = config.bcServerUrl;
25
+ if (config.bcCompany) env.BC_COMPANY = config.bcCompany;
26
+ if (config.bcAuthType) env.BC_AUTH_TYPE = config.bcAuthType;
27
+ return {command: 'node', args: ['build/index.js'], env};
28
+ }
@@ -0,0 +1,158 @@
1
+ import { AzureCliCredential } from '@azure/identity';
2
+
3
+ export interface BusinessCentralConfig {
4
+ serverUrl: string;
5
+ companyName: string;
6
+ authType: 'azure_cli';
7
+ }
8
+
9
+ export interface Company {
10
+ id: string;
11
+ systemVersion: string;
12
+ timestamp: number;
13
+ name: string;
14
+ displayName: string;
15
+ businessProfileId: string;
16
+ systemCreatedAt: string;
17
+ systemCreatedBy: string;
18
+ systemModifiedAt: string;
19
+ systemModifiedBy: string;
20
+ }
21
+
22
+ export class BusinessCentralClient {
23
+ private config: BusinessCentralConfig;
24
+ private companyId?: string;
25
+ private credential?: AzureCliCredential;
26
+
27
+ constructor(config: BusinessCentralConfig) {
28
+ this.config = config;
29
+
30
+ if (config.authType === 'azure_cli') {
31
+ this.credential = new AzureCliCredential();
32
+ }
33
+ }
34
+
35
+ /**
36
+ * Get the company ID by looking up the company name
37
+ */
38
+ private async getCompanyId(): Promise<string> {
39
+ if (this.companyId) {
40
+ return this.companyId;
41
+ }
42
+
43
+ const url = `${this.config.serverUrl}/companies?$filter=displayName eq '${this.config.companyName}'`;
44
+ const response = await this.request('GET', url);
45
+
46
+ if (!response.value || response.value.length === 0) {
47
+ throw new Error(`Company '${this.config.companyName}' not found`);
48
+ }
49
+
50
+ const companyId: string = response.value[0].id;
51
+ this.companyId = companyId;
52
+ return companyId;
53
+ }
54
+
55
+ /**
56
+ * Make an authenticated request to Business Central API
57
+ */
58
+ private async request(method: string, url: string, body?: any): Promise<any> {
59
+ if (!this.credential) {
60
+ throw new Error('Authentication not configured');
61
+ }
62
+
63
+ // Get access token for Business Central
64
+ const tokenResponse = await this.credential.getToken('https://api.businesscentral.dynamics.com/.default');
65
+
66
+ const headers: Record<string, string> = {
67
+ 'Authorization': `Bearer ${tokenResponse.token}`,
68
+ 'Content-Type': 'application/json',
69
+ 'Accept': 'application/json'
70
+ };
71
+
72
+ const options: RequestInit = {
73
+ method,
74
+ headers,
75
+ };
76
+
77
+ if (body) {
78
+ options.body = JSON.stringify(body);
79
+ }
80
+
81
+ const response = await fetch(url, options);
82
+
83
+ if (!response.ok) {
84
+ const errorText = await response.text();
85
+ throw new Error(`Business Central API error (${response.status}): ${errorText}`);
86
+ }
87
+
88
+ return response.json();
89
+ }
90
+
91
+ /**
92
+ * Get OData metadata for a resource
93
+ */
94
+ async getSchema(resource: string): Promise<any> {
95
+ const companyId = await this.getCompanyId();
96
+ const url = `${this.config.serverUrl}/companies(${companyId})/${resource}/$metadata`;
97
+ return this.request('GET', url);
98
+ }
99
+
100
+ /**
101
+ * List items from a resource with optional filtering and pagination
102
+ */
103
+ async listItems(resource: string, options?: {
104
+ filter?: string;
105
+ top?: number;
106
+ skip?: number;
107
+ }): Promise<any> {
108
+ const companyId = await this.getCompanyId();
109
+ let url = `${this.config.serverUrl}/companies(${companyId})/${resource}`;
110
+
111
+ const params = new URLSearchParams();
112
+ if (options?.filter) params.append('$filter', options.filter);
113
+ if (options?.top) params.append('$top', options.top.toString());
114
+ if (options?.skip) params.append('$skip', options.skip.toString());
115
+
116
+ if (params.toString()) {
117
+ url += `?${params.toString()}`;
118
+ }
119
+
120
+ return this.request('GET', url);
121
+ }
122
+
123
+ /**
124
+ * Get items by field value
125
+ */
126
+ async getItemsByField(resource: string, field: string, value: string): Promise<any> {
127
+ return this.listItems(resource, {
128
+ filter: `${field} eq '${value}'`
129
+ });
130
+ }
131
+
132
+ /**
133
+ * Create a new item
134
+ */
135
+ async createItem(resource: string, data: any): Promise<any> {
136
+ const companyId = await this.getCompanyId();
137
+ const url = `${this.config.serverUrl}/companies(${companyId})/${resource}`;
138
+ return this.request('POST', url, data);
139
+ }
140
+
141
+ /**
142
+ * Update an existing item
143
+ */
144
+ async updateItem(resource: string, itemId: string, data: any): Promise<any> {
145
+ const companyId = await this.getCompanyId();
146
+ const url = `${this.config.serverUrl}/companies(${companyId})/${resource}(${itemId})`;
147
+ return this.request('PATCH', url, data);
148
+ }
149
+
150
+ /**
151
+ * Delete an item
152
+ */
153
+ async deleteItem(resource: string, itemId: string): Promise<void> {
154
+ const companyId = await this.getCompanyId();
155
+ const url = `${this.config.serverUrl}/companies(${companyId})/${resource}(${itemId})`;
156
+ await this.request('DELETE', url);
157
+ }
158
+ }
package/src/index.ts ADDED
@@ -0,0 +1,303 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
4
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
5
+ import {
6
+ CallToolRequestSchema,
7
+ ListToolsRequestSchema,
8
+ } from '@modelcontextprotocol/sdk/types.js';
9
+ import { BusinessCentralClient } from './business-central-client.js';
10
+
11
+ // Read configuration from environment variables
12
+ const BC_URL_SERVER = process.env.BC_URL_SERVER;
13
+ const BC_COMPANY = process.env.BC_COMPANY;
14
+ const BC_AUTH_TYPE = process.env.BC_AUTH_TYPE || 'azure_cli';
15
+
16
+ if (!BC_URL_SERVER || !BC_COMPANY) {
17
+ console.error('Error: BC_URL_SERVER and BC_COMPANY environment variables are required');
18
+ process.exit(1);
19
+ }
20
+
21
+ if (BC_AUTH_TYPE !== 'azure_cli') {
22
+ console.error('Error: Only azure_cli authentication is currently supported');
23
+ process.exit(1);
24
+ }
25
+
26
+ // Create Business Central client
27
+ const bcClient = new BusinessCentralClient({
28
+ serverUrl: BC_URL_SERVER,
29
+ companyName: BC_COMPANY,
30
+ authType: BC_AUTH_TYPE
31
+ });
32
+
33
+ // Create MCP server
34
+ const server = new Server(
35
+ {
36
+ name: 'business-central',
37
+ version: '0.1.0',
38
+ },
39
+ {
40
+ capabilities: {
41
+ tools: {},
42
+ },
43
+ }
44
+ );
45
+
46
+ // Register tool handlers
47
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
48
+ return {
49
+ tools: [
50
+ {
51
+ name: 'get_schema',
52
+ description: 'Get schema information for a Business Central resource',
53
+ inputSchema: {
54
+ type: 'object',
55
+ properties: {
56
+ resource: {
57
+ type: 'string',
58
+ description: 'The resource name (e.g., customers, contacts, salesOpportunities)',
59
+ },
60
+ },
61
+ required: ['resource'],
62
+ },
63
+ },
64
+ {
65
+ name: 'list_items',
66
+ description: 'Get items from Business Central with filtering and pagination',
67
+ inputSchema: {
68
+ type: 'object',
69
+ properties: {
70
+ resource: {
71
+ type: 'string',
72
+ description: 'The resource name (e.g., customers, contacts, salesOpportunities)',
73
+ },
74
+ filter: {
75
+ type: 'string',
76
+ description: 'OData filter expression (optional)',
77
+ },
78
+ top: {
79
+ type: 'number',
80
+ description: 'Maximum number of items to return (optional)',
81
+ },
82
+ skip: {
83
+ type: 'number',
84
+ description: 'Number of items to skip for pagination (optional)',
85
+ },
86
+ },
87
+ required: ['resource'],
88
+ },
89
+ },
90
+ {
91
+ name: 'get_items_by_field',
92
+ description: 'Get items matching a field value',
93
+ inputSchema: {
94
+ type: 'object',
95
+ properties: {
96
+ resource: {
97
+ type: 'string',
98
+ description: 'The resource name (e.g., customers, contacts)',
99
+ },
100
+ field: {
101
+ type: 'string',
102
+ description: 'The field name to filter by',
103
+ },
104
+ value: {
105
+ type: 'string',
106
+ description: 'The value to match',
107
+ },
108
+ },
109
+ required: ['resource', 'field', 'value'],
110
+ },
111
+ },
112
+ {
113
+ name: 'create_item',
114
+ description: 'Create a new item in Business Central',
115
+ inputSchema: {
116
+ type: 'object',
117
+ properties: {
118
+ resource: {
119
+ type: 'string',
120
+ description: 'The resource name (e.g., customers, contacts)',
121
+ },
122
+ item_data: {
123
+ type: 'object',
124
+ description: 'The item data to create',
125
+ },
126
+ },
127
+ required: ['resource', 'item_data'],
128
+ },
129
+ },
130
+ {
131
+ name: 'update_item',
132
+ description: 'Update an existing item in Business Central',
133
+ inputSchema: {
134
+ type: 'object',
135
+ properties: {
136
+ resource: {
137
+ type: 'string',
138
+ description: 'The resource name (e.g., customers, contacts)',
139
+ },
140
+ item_id: {
141
+ type: 'string',
142
+ description: 'The ID of the item to update',
143
+ },
144
+ item_data: {
145
+ type: 'object',
146
+ description: 'The item data to update',
147
+ },
148
+ },
149
+ required: ['resource', 'item_id', 'item_data'],
150
+ },
151
+ },
152
+ {
153
+ name: 'delete_item',
154
+ description: 'Delete an item from Business Central',
155
+ inputSchema: {
156
+ type: 'object',
157
+ properties: {
158
+ resource: {
159
+ type: 'string',
160
+ description: 'The resource name (e.g., customers, contacts)',
161
+ },
162
+ item_id: {
163
+ type: 'string',
164
+ description: 'The ID of the item to delete',
165
+ },
166
+ },
167
+ required: ['resource', 'item_id'],
168
+ },
169
+ },
170
+ ],
171
+ };
172
+ });
173
+
174
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
175
+ try {
176
+ const { name, arguments: args } = request.params;
177
+
178
+ switch (name) {
179
+ case 'get_schema': {
180
+ const { resource } = args as { resource: string };
181
+ const result = await bcClient.getSchema(resource);
182
+ return {
183
+ content: [
184
+ {
185
+ type: 'text',
186
+ text: JSON.stringify(result, null, 2),
187
+ },
188
+ ],
189
+ };
190
+ }
191
+
192
+ case 'list_items': {
193
+ const { resource, filter, top, skip } = args as {
194
+ resource: string;
195
+ filter?: string;
196
+ top?: number;
197
+ skip?: number;
198
+ };
199
+ const result = await bcClient.listItems(resource, { filter, top, skip });
200
+ return {
201
+ content: [
202
+ {
203
+ type: 'text',
204
+ text: JSON.stringify(result, null, 2),
205
+ },
206
+ ],
207
+ };
208
+ }
209
+
210
+ case 'get_items_by_field': {
211
+ const { resource, field, value } = args as {
212
+ resource: string;
213
+ field: string;
214
+ value: string;
215
+ };
216
+ const result = await bcClient.getItemsByField(resource, field, value);
217
+ return {
218
+ content: [
219
+ {
220
+ type: 'text',
221
+ text: JSON.stringify(result, null, 2),
222
+ },
223
+ ],
224
+ };
225
+ }
226
+
227
+ case 'create_item': {
228
+ const { resource, item_data } = args as {
229
+ resource: string;
230
+ item_data: any;
231
+ };
232
+ const result = await bcClient.createItem(resource, item_data);
233
+ return {
234
+ content: [
235
+ {
236
+ type: 'text',
237
+ text: JSON.stringify(result, null, 2),
238
+ },
239
+ ],
240
+ };
241
+ }
242
+
243
+ case 'update_item': {
244
+ const { resource, item_id, item_data } = args as {
245
+ resource: string;
246
+ item_id: string;
247
+ item_data: any;
248
+ };
249
+ const result = await bcClient.updateItem(resource, item_id, item_data);
250
+ return {
251
+ content: [
252
+ {
253
+ type: 'text',
254
+ text: JSON.stringify(result, null, 2),
255
+ },
256
+ ],
257
+ };
258
+ }
259
+
260
+ case 'delete_item': {
261
+ const { resource, item_id } = args as {
262
+ resource: string;
263
+ item_id: string;
264
+ };
265
+ await bcClient.deleteItem(resource, item_id);
266
+ return {
267
+ content: [
268
+ {
269
+ type: 'text',
270
+ text: 'Item deleted successfully',
271
+ },
272
+ ],
273
+ };
274
+ }
275
+
276
+ default:
277
+ throw new Error(`Unknown tool: ${name}`);
278
+ }
279
+ } catch (error) {
280
+ const errorMessage = error instanceof Error ? error.message : String(error);
281
+ return {
282
+ content: [
283
+ {
284
+ type: 'text',
285
+ text: `Error: ${errorMessage}`,
286
+ },
287
+ ],
288
+ isError: true,
289
+ };
290
+ }
291
+ });
292
+
293
+ // Start server
294
+ async function main() {
295
+ const transport = new StdioServerTransport();
296
+ await server.connect(transport);
297
+ console.error('Business Central MCP server running on stdio');
298
+ }
299
+
300
+ main().catch((error) => {
301
+ console.error('Server error:', error);
302
+ process.exit(1);
303
+ });
package/tsconfig.json ADDED
@@ -0,0 +1,18 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ES2022",
5
+ "moduleResolution": "node",
6
+ "lib": ["ES2022"],
7
+ "outDir": "./build",
8
+ "rootDir": "./src",
9
+ "strict": true,
10
+ "esModuleInterop": true,
11
+ "skipLibCheck": true,
12
+ "forceConsistentCasingInFileNames": true,
13
+ "resolveJsonModule": true,
14
+ "declaration": true
15
+ },
16
+ "include": ["src/**/*"],
17
+ "exclude": ["node_modules", "build"]
18
+ }