@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 +233 -0
- package/build/business-central-client.d.ts +59 -0
- package/build/business-central-client.js +114 -0
- package/build/index.d.ts +2 -0
- package/build/index.js +262 -0
- package/package.json +33 -0
- package/smithery.yaml +28 -0
- package/src/business-central-client.ts +158 -0
- package/src/index.ts +303 -0
- package/tsconfig.json +18 -0
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
|
+
}
|
package/build/index.d.ts
ADDED
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
|
+
}
|