@open-loyalty/mcp-server 1.0.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.
Files changed (99) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +654 -0
  3. package/dist/client/http.d.ts +8 -0
  4. package/dist/client/http.js +69 -0
  5. package/dist/config.d.ts +17 -0
  6. package/dist/config.js +40 -0
  7. package/dist/index.d.ts +2 -0
  8. package/dist/index.js +20 -0
  9. package/dist/server.d.ts +4 -0
  10. package/dist/server.js +334 -0
  11. package/dist/tools/achievement.d.ts +983 -0
  12. package/dist/tools/achievement.js +311 -0
  13. package/dist/tools/admin.d.ts +153 -0
  14. package/dist/tools/admin.js +193 -0
  15. package/dist/tools/analytics.d.ts +162 -0
  16. package/dist/tools/analytics.js +245 -0
  17. package/dist/tools/apikey.d.ts +72 -0
  18. package/dist/tools/apikey.js +78 -0
  19. package/dist/tools/audit.d.ts +107 -0
  20. package/dist/tools/audit.js +90 -0
  21. package/dist/tools/badge.d.ts +135 -0
  22. package/dist/tools/badge.js +165 -0
  23. package/dist/tools/campaign.d.ts +1775 -0
  24. package/dist/tools/campaign.js +724 -0
  25. package/dist/tools/export.d.ts +110 -0
  26. package/dist/tools/export.js +147 -0
  27. package/dist/tools/import.d.ts +110 -0
  28. package/dist/tools/import.js +126 -0
  29. package/dist/tools/index.d.ts +22 -0
  30. package/dist/tools/index.js +527 -0
  31. package/dist/tools/member.d.ts +345 -0
  32. package/dist/tools/member.js +358 -0
  33. package/dist/tools/member.test.d.ts +1 -0
  34. package/dist/tools/member.test.js +213 -0
  35. package/dist/tools/points.d.ts +188 -0
  36. package/dist/tools/points.js +306 -0
  37. package/dist/tools/points.test.d.ts +1 -0
  38. package/dist/tools/points.test.js +292 -0
  39. package/dist/tools/reward.d.ts +261 -0
  40. package/dist/tools/reward.js +371 -0
  41. package/dist/tools/reward.test.d.ts +1 -0
  42. package/dist/tools/reward.test.js +240 -0
  43. package/dist/tools/role.d.ts +161 -0
  44. package/dist/tools/role.js +160 -0
  45. package/dist/tools/segment.d.ts +797 -0
  46. package/dist/tools/segment.js +299 -0
  47. package/dist/tools/store.d.ts +101 -0
  48. package/dist/tools/store.js +117 -0
  49. package/dist/tools/tierset.d.ts +288 -0
  50. package/dist/tools/tierset.js +244 -0
  51. package/dist/tools/transaction.d.ts +357 -0
  52. package/dist/tools/transaction.js +242 -0
  53. package/dist/tools/transaction.test.d.ts +1 -0
  54. package/dist/tools/transaction.test.js +235 -0
  55. package/dist/tools/wallet-type.d.ts +32 -0
  56. package/dist/tools/wallet-type.js +58 -0
  57. package/dist/tools/webhook.d.ts +179 -0
  58. package/dist/tools/webhook.js +171 -0
  59. package/dist/types/schemas/achievement.d.ts +1116 -0
  60. package/dist/types/schemas/achievement.js +172 -0
  61. package/dist/types/schemas/admin.d.ts +263 -0
  62. package/dist/types/schemas/admin.js +99 -0
  63. package/dist/types/schemas/analytics.d.ts +542 -0
  64. package/dist/types/schemas/analytics.js +130 -0
  65. package/dist/types/schemas/badge.d.ts +131 -0
  66. package/dist/types/schemas/badge.js +48 -0
  67. package/dist/types/schemas/campaign.d.ts +2005 -0
  68. package/dist/types/schemas/campaign.js +189 -0
  69. package/dist/types/schemas/common.d.ts +52 -0
  70. package/dist/types/schemas/common.js +26 -0
  71. package/dist/types/schemas/export.d.ts +127 -0
  72. package/dist/types/schemas/export.js +43 -0
  73. package/dist/types/schemas/import.d.ts +344 -0
  74. package/dist/types/schemas/import.js +68 -0
  75. package/dist/types/schemas/member.d.ts +443 -0
  76. package/dist/types/schemas/member.js +92 -0
  77. package/dist/types/schemas/points.d.ts +188 -0
  78. package/dist/types/schemas/points.js +54 -0
  79. package/dist/types/schemas/reward.d.ts +278 -0
  80. package/dist/types/schemas/reward.js +69 -0
  81. package/dist/types/schemas/role.d.ts +260 -0
  82. package/dist/types/schemas/role.js +75 -0
  83. package/dist/types/schemas/segment.d.ts +592 -0
  84. package/dist/types/schemas/segment.js +114 -0
  85. package/dist/types/schemas/tierset.d.ts +552 -0
  86. package/dist/types/schemas/tierset.js +87 -0
  87. package/dist/types/schemas/transaction.d.ts +1022 -0
  88. package/dist/types/schemas/transaction.js +63 -0
  89. package/dist/types/schemas/wallet-type.d.ts +99 -0
  90. package/dist/types/schemas/wallet-type.js +17 -0
  91. package/dist/types/schemas/webhook.d.ts +195 -0
  92. package/dist/types/schemas/webhook.js +39 -0
  93. package/dist/utils/cursor.d.ts +84 -0
  94. package/dist/utils/cursor.js +117 -0
  95. package/dist/utils/errors.d.ts +12 -0
  96. package/dist/utils/errors.js +69 -0
  97. package/dist/utils/pagination.d.ts +39 -0
  98. package/dist/utils/pagination.js +77 -0
  99. package/package.json +65 -0
@@ -0,0 +1,240 @@
1
+ // src/tools/reward.test.ts
2
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
3
+ import { setupMockAxios, teardownMockAxios, getMockAxios } from '../../tests/mocks/http.mock.js';
4
+ import { rewardFixtures } from '../../tests/fixtures/reward.fixtures.js';
5
+ import { rewardList, rewardCreate, rewardGet, rewardUpdate, rewardActivate, rewardDeactivate, rewardBuy, rewardRedeem, rewardCategoryList, } from './reward.js';
6
+ describe('Reward Operations', () => {
7
+ beforeEach(() => {
8
+ setupMockAxios();
9
+ });
10
+ afterEach(() => {
11
+ teardownMockAxios();
12
+ });
13
+ describe('rewardList', () => {
14
+ it('should list rewards and map items correctly', async () => {
15
+ const mockAxios = getMockAxios();
16
+ mockAxios.onGet('/default/reward').reply(200, rewardFixtures.listResponse);
17
+ const result = await rewardList({});
18
+ expect(result.rewards).toHaveLength(2);
19
+ expect(result.rewards[0].rewardId).toBe('reward-uuid-1');
20
+ expect(result.rewards[0].name).toBe('Discount Coupon');
21
+ expect(result.rewards[0].type).toBe('static_coupon');
22
+ expect(result.rewards[0].costInPoints).toBe(100);
23
+ expect(result.total.all).toBe(10);
24
+ expect(result.total.filtered).toBe(2);
25
+ });
26
+ it('should include pagination params', async () => {
27
+ const mockAxios = getMockAxios();
28
+ mockAxios.onGet(/\/default\/reward/).reply(200, rewardFixtures.listResponse);
29
+ await rewardList({ page: 2, perPage: 25 });
30
+ const url = mockAxios.history.get[0].url;
31
+ expect(url).toContain('_page=2');
32
+ expect(url).toContain('_itemsOnPage=25');
33
+ });
34
+ it('should include filter params', async () => {
35
+ const mockAxios = getMockAxios();
36
+ mockAxios.onGet(/\/default\/reward/).reply(200, rewardFixtures.listResponse);
37
+ await rewardList({
38
+ active: true,
39
+ type: 'static_coupon',
40
+ categoryId: 'cat-uuid',
41
+ });
42
+ const url = mockAxios.history.get[0].url;
43
+ expect(url).toContain('active=true');
44
+ expect(url).toContain('reward=static_coupon');
45
+ expect(url).toContain('categoryId=cat-uuid');
46
+ });
47
+ });
48
+ describe('rewardCreate', () => {
49
+ it('should create reward with reward wrapper', async () => {
50
+ const mockAxios = getMockAxios();
51
+ mockAxios.onPost('/default/reward').reply(200, rewardFixtures.createResponse);
52
+ const result = await rewardCreate({
53
+ name: 'Test Reward',
54
+ type: 'static_coupon',
55
+ costInPoints: 100,
56
+ });
57
+ expect(result.rewardId).toBe('reward-uuid-123');
58
+ expect(result.name).toBe('Test Reward');
59
+ expect(result.costInPoints).toBe(100);
60
+ // Verify request body format with reward wrapper
61
+ const requestData = JSON.parse(mockAxios.history.post[0].data);
62
+ expect(requestData.reward).toBeDefined();
63
+ expect(requestData.reward.name).toBe('Test Reward');
64
+ expect(requestData.reward.type).toBe('static_coupon');
65
+ expect(requestData.reward.costInPoints).toBe(100);
66
+ });
67
+ it('should include optional fields', async () => {
68
+ const mockAxios = getMockAxios();
69
+ mockAxios.onPost('/default/reward').reply(200, rewardFixtures.createResponse);
70
+ await rewardCreate({
71
+ name: 'Full Reward',
72
+ type: 'static_coupon',
73
+ costInPoints: 200,
74
+ description: 'A test reward',
75
+ couponValue: 10,
76
+ couponValueType: 'Money',
77
+ daysValid: 30,
78
+ active: true,
79
+ categories: ['cat-1', 'cat-2'],
80
+ levels: ['level-1'],
81
+ target: 'level',
82
+ });
83
+ const requestData = JSON.parse(mockAxios.history.post[0].data);
84
+ expect(requestData.reward.description).toBe('A test reward');
85
+ expect(requestData.reward.couponValue).toBe(10);
86
+ expect(requestData.reward.couponValueType).toBe('Money');
87
+ expect(requestData.reward.daysValid).toBe(30);
88
+ expect(requestData.reward.categories).toEqual(['cat-1', 'cat-2']);
89
+ expect(requestData.reward.target).toBe('level');
90
+ });
91
+ });
92
+ describe('rewardGet', () => {
93
+ it('should get reward details', async () => {
94
+ const mockAxios = getMockAxios();
95
+ mockAxios.onGet('/default/reward/reward-uuid').reply(200, rewardFixtures.getResponse);
96
+ const result = await rewardGet({ rewardId: 'reward-uuid' });
97
+ expect(result.rewardId).toBe('reward-uuid-123');
98
+ expect(result.name).toBe('Test Reward');
99
+ expect(result.type).toBe('static_coupon');
100
+ expect(result.costInPoints).toBe(100);
101
+ expect(result.active).toBe(true);
102
+ });
103
+ });
104
+ describe('rewardUpdate', () => {
105
+ it('should update reward with reward wrapper', async () => {
106
+ const mockAxios = getMockAxios();
107
+ mockAxios.onPut('/default/reward/reward-uuid').reply(200);
108
+ await rewardUpdate({
109
+ rewardId: 'reward-uuid',
110
+ name: 'Updated Reward',
111
+ costInPoints: 150,
112
+ });
113
+ const requestData = JSON.parse(mockAxios.history.put[0].data);
114
+ expect(requestData.reward).toBeDefined();
115
+ expect(requestData.reward.name).toBe('Updated Reward');
116
+ expect(requestData.reward.costInPoints).toBe(150);
117
+ });
118
+ it('should include optional arrays', async () => {
119
+ const mockAxios = getMockAxios();
120
+ mockAxios.onPut('/default/reward/reward-uuid').reply(200);
121
+ await rewardUpdate({
122
+ rewardId: 'reward-uuid',
123
+ categories: ['cat-1'],
124
+ levels: ['level-1', 'level-2'],
125
+ segments: ['segment-1'],
126
+ });
127
+ const requestData = JSON.parse(mockAxios.history.put[0].data);
128
+ expect(requestData.reward.categories).toEqual(['cat-1']);
129
+ expect(requestData.reward.levels).toEqual(['level-1', 'level-2']);
130
+ expect(requestData.reward.segments).toEqual(['segment-1']);
131
+ });
132
+ });
133
+ describe('rewardActivate', () => {
134
+ it('should call activate endpoint', async () => {
135
+ const mockAxios = getMockAxios();
136
+ mockAxios.onPost('/default/reward/reward-uuid/activate').reply(200);
137
+ await rewardActivate({ rewardId: 'reward-uuid' });
138
+ expect(mockAxios.history.post[0].url).toBe('/default/reward/reward-uuid/activate');
139
+ });
140
+ });
141
+ describe('rewardDeactivate', () => {
142
+ it('should call deactivate endpoint', async () => {
143
+ const mockAxios = getMockAxios();
144
+ mockAxios.onPost('/default/reward/reward-uuid/deactivate').reply(200);
145
+ await rewardDeactivate({ rewardId: 'reward-uuid' });
146
+ expect(mockAxios.history.post[0].url).toBe('/default/reward/reward-uuid/deactivate');
147
+ });
148
+ });
149
+ describe('rewardBuy', () => {
150
+ it('should buy reward and return first issued', async () => {
151
+ const mockAxios = getMockAxios();
152
+ mockAxios.onPost('/default/reward/reward-uuid/buy').reply(200, rewardFixtures.buyResponse);
153
+ const result = await rewardBuy({
154
+ rewardId: 'reward-uuid',
155
+ memberId: 'member-uuid',
156
+ });
157
+ expect(result.issuedRewardId).toBe('issued-uuid-123');
158
+ expect(result.couponCode).toBe('COUPON-ABC123');
159
+ const requestData = JSON.parse(mockAxios.history.post[0].data);
160
+ expect(requestData.customerId).toBe('member-uuid');
161
+ });
162
+ it('should include optional parameters', async () => {
163
+ const mockAxios = getMockAxios();
164
+ mockAxios.onPost('/default/reward/reward-uuid/buy').reply(200, rewardFixtures.buyResponse);
165
+ await rewardBuy({
166
+ rewardId: 'reward-uuid',
167
+ memberId: 'member-uuid',
168
+ quantity: 3,
169
+ couponValue: 50,
170
+ withoutPoints: true,
171
+ });
172
+ const requestData = JSON.parse(mockAxios.history.post[0].data);
173
+ expect(requestData.quantity).toBe(3);
174
+ expect(requestData.couponValue).toBe(50);
175
+ expect(requestData.withoutPoints).toBe(true);
176
+ });
177
+ it('should throw error for insufficient points', async () => {
178
+ const mockAxios = getMockAxios();
179
+ mockAxios.onPost('/default/reward/reward-uuid/buy').reply(400, {
180
+ message: 'NotEnoughPoints',
181
+ });
182
+ // API returns 400 error which gets thrown
183
+ await expect(rewardBuy({
184
+ rewardId: 'reward-uuid',
185
+ memberId: 'member-uuid',
186
+ })).rejects.toThrow();
187
+ });
188
+ });
189
+ describe('rewardRedeem', () => {
190
+ it('should redeem coupon', async () => {
191
+ const mockAxios = getMockAxios();
192
+ mockAxios.onPost('/default/member/member-uuid/reward/redeem').reply(200, rewardFixtures.redeemResponse);
193
+ const result = await rewardRedeem({
194
+ memberId: 'member-uuid',
195
+ couponCode: 'COUPON-ABC123',
196
+ });
197
+ expect(result.code).toBe('COUPON-ABC123');
198
+ expect(result.used).toBe(true);
199
+ const requestData = JSON.parse(mockAxios.history.post[0].data);
200
+ expect(requestData.couponCode).toBe('COUPON-ABC123');
201
+ });
202
+ it('should throw error for non-existent coupon', async () => {
203
+ const mockAxios = getMockAxios();
204
+ mockAxios.onPost('/default/member/member-uuid/reward/redeem').reply(400, {
205
+ message: 'CouponDoesNotExist',
206
+ });
207
+ // API returns 400 error which gets thrown
208
+ await expect(rewardRedeem({
209
+ memberId: 'member-uuid',
210
+ couponCode: 'INVALID-CODE',
211
+ })).rejects.toThrow();
212
+ });
213
+ });
214
+ describe('rewardCategoryList', () => {
215
+ it('should list categories and map rewardCategoryId to categoryId', async () => {
216
+ const mockAxios = getMockAxios();
217
+ mockAxios.onGet('/default/rewardCategory').reply(200, rewardFixtures.categoryListResponse);
218
+ const result = await rewardCategoryList({});
219
+ expect(result.categories).toHaveLength(2);
220
+ expect(result.categories[0].categoryId).toBe('cat-uuid-1');
221
+ expect(result.categories[0].name).toBe('Discounts');
222
+ expect(result.categories[0].active).toBe(true);
223
+ });
224
+ it('should include pagination params', async () => {
225
+ const mockAxios = getMockAxios();
226
+ mockAxios.onGet(/\/default\/rewardCategory/).reply(200, rewardFixtures.categoryListResponse);
227
+ await rewardCategoryList({ page: 2, perPage: 10 });
228
+ const url = mockAxios.history.get[0].url;
229
+ expect(url).toContain('_page=2');
230
+ expect(url).toContain('_itemsOnPage=10');
231
+ });
232
+ it('should filter by active status', async () => {
233
+ const mockAxios = getMockAxios();
234
+ mockAxios.onGet(/\/default\/rewardCategory/).reply(200, rewardFixtures.categoryListResponse);
235
+ await rewardCategoryList({ active: true });
236
+ const url = mockAxios.history.get[0].url;
237
+ expect(url).toContain('active=true');
238
+ });
239
+ });
240
+ });
@@ -0,0 +1,161 @@
1
+ import { z } from "zod";
2
+ import { Role, RoleListItem, ACLResource, RolePermissionInput } from "../types/schemas/role.js";
3
+ export declare const RoleListInputSchema: {
4
+ page: z.ZodOptional<z.ZodNumber>;
5
+ perPage: z.ZodOptional<z.ZodNumber>;
6
+ name: z.ZodOptional<z.ZodString>;
7
+ master: z.ZodOptional<z.ZodBoolean>;
8
+ default: z.ZodOptional<z.ZodBoolean>;
9
+ };
10
+ export declare const RoleCreateInputSchema: {
11
+ name: z.ZodString;
12
+ permissions: z.ZodOptional<z.ZodArray<z.ZodObject<{
13
+ resource: z.ZodString;
14
+ access: z.ZodString;
15
+ filterQuery: z.ZodOptional<z.ZodString>;
16
+ }, "strip", z.ZodTypeAny, {
17
+ resource: string;
18
+ access: string;
19
+ filterQuery?: string | undefined;
20
+ }, {
21
+ resource: string;
22
+ access: string;
23
+ filterQuery?: string | undefined;
24
+ }>, "many">>;
25
+ stores: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
26
+ default: z.ZodOptional<z.ZodBoolean>;
27
+ };
28
+ export declare const RoleGetInputSchema: {
29
+ roleId: z.ZodUnion<[z.ZodString, z.ZodNumber]>;
30
+ };
31
+ export declare const RoleUpdateInputSchema: {
32
+ roleId: z.ZodUnion<[z.ZodString, z.ZodNumber]>;
33
+ name: z.ZodOptional<z.ZodString>;
34
+ permissions: z.ZodOptional<z.ZodArray<z.ZodObject<{
35
+ resource: z.ZodString;
36
+ access: z.ZodString;
37
+ filterQuery: z.ZodOptional<z.ZodString>;
38
+ }, "strip", z.ZodTypeAny, {
39
+ resource: string;
40
+ access: string;
41
+ filterQuery?: string | undefined;
42
+ }, {
43
+ resource: string;
44
+ access: string;
45
+ filterQuery?: string | undefined;
46
+ }>, "many">>;
47
+ stores: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
48
+ default: z.ZodOptional<z.ZodBoolean>;
49
+ };
50
+ export declare const RoleDeleteInputSchema: {
51
+ roleId: z.ZodUnion<[z.ZodString, z.ZodNumber]>;
52
+ };
53
+ export declare function roleList(input: {
54
+ page?: number;
55
+ perPage?: number;
56
+ name?: string;
57
+ master?: boolean;
58
+ default?: boolean;
59
+ }): Promise<{
60
+ items: RoleListItem[];
61
+ total?: Record<string, unknown>;
62
+ }>;
63
+ export declare function roleCreate(input: {
64
+ name: string;
65
+ permissions?: RolePermissionInput[];
66
+ stores?: string[];
67
+ default?: boolean;
68
+ }): Promise<void>;
69
+ export declare function roleGet(input: {
70
+ roleId: string | number;
71
+ }): Promise<Role>;
72
+ export declare function roleUpdate(input: {
73
+ roleId: string | number;
74
+ name?: string;
75
+ permissions?: RolePermissionInput[];
76
+ stores?: string[];
77
+ default?: boolean;
78
+ }): Promise<void>;
79
+ export declare function roleDelete(input: {
80
+ roleId: string | number;
81
+ }): Promise<void>;
82
+ export declare function aclGetResources(): Promise<{
83
+ items: ACLResource[];
84
+ total?: Record<string, unknown>;
85
+ }>;
86
+ export declare const roleToolDefinitions: readonly [{
87
+ readonly name: "openloyalty_role_list";
88
+ readonly description: "List all roles with optional filtering. Returns paginated list of roles with id, name, permissions, master status, and default status.";
89
+ readonly inputSchema: {
90
+ page: z.ZodOptional<z.ZodNumber>;
91
+ perPage: z.ZodOptional<z.ZodNumber>;
92
+ name: z.ZodOptional<z.ZodString>;
93
+ master: z.ZodOptional<z.ZodBoolean>;
94
+ default: z.ZodOptional<z.ZodBoolean>;
95
+ };
96
+ readonly handler: typeof roleList;
97
+ }, {
98
+ readonly name: "openloyalty_role_create";
99
+ readonly description: "Create a new role with permissions. Requires name. Optionally specify permissions (array of {resource, access, filterQuery}) and stores. Returns void on success (201 Created).";
100
+ readonly inputSchema: {
101
+ name: z.ZodString;
102
+ permissions: z.ZodOptional<z.ZodArray<z.ZodObject<{
103
+ resource: z.ZodString;
104
+ access: z.ZodString;
105
+ filterQuery: z.ZodOptional<z.ZodString>;
106
+ }, "strip", z.ZodTypeAny, {
107
+ resource: string;
108
+ access: string;
109
+ filterQuery?: string | undefined;
110
+ }, {
111
+ resource: string;
112
+ access: string;
113
+ filterQuery?: string | undefined;
114
+ }>, "many">>;
115
+ stores: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
116
+ default: z.ZodOptional<z.ZodBoolean>;
117
+ };
118
+ readonly handler: typeof roleCreate;
119
+ }, {
120
+ readonly name: "openloyalty_role_get";
121
+ readonly description: "Get full role details by ID. Returns role with name, permissions array, master status, default status, and stores.";
122
+ readonly inputSchema: {
123
+ roleId: z.ZodUnion<[z.ZodString, z.ZodNumber]>;
124
+ };
125
+ readonly handler: typeof roleGet;
126
+ }, {
127
+ readonly name: "openloyalty_role_update";
128
+ readonly description: "Update a role's name, permissions, or stores. Returns void on success (204 No Content).";
129
+ readonly inputSchema: {
130
+ roleId: z.ZodUnion<[z.ZodString, z.ZodNumber]>;
131
+ name: z.ZodOptional<z.ZodString>;
132
+ permissions: z.ZodOptional<z.ZodArray<z.ZodObject<{
133
+ resource: z.ZodString;
134
+ access: z.ZodString;
135
+ filterQuery: z.ZodOptional<z.ZodString>;
136
+ }, "strip", z.ZodTypeAny, {
137
+ resource: string;
138
+ access: string;
139
+ filterQuery?: string | undefined;
140
+ }, {
141
+ resource: string;
142
+ access: string;
143
+ filterQuery?: string | undefined;
144
+ }>, "many">>;
145
+ stores: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
146
+ default: z.ZodOptional<z.ZodBoolean>;
147
+ };
148
+ readonly handler: typeof roleUpdate;
149
+ }, {
150
+ readonly name: "openloyalty_role_delete";
151
+ readonly description: "Delete a role by ID. Returns void on success (204 No Content). Cannot delete roles that are assigned to admin users.";
152
+ readonly inputSchema: {
153
+ roleId: z.ZodUnion<[z.ZodString, z.ZodNumber]>;
154
+ };
155
+ readonly handler: typeof roleDelete;
156
+ }, {
157
+ readonly name: "openloyalty_acl_get_resources";
158
+ readonly description: "Get list of available ACL resources. Returns items with code and name for each resource type. Use this to discover available permissions before creating roles.";
159
+ readonly inputSchema: {};
160
+ readonly handler: typeof aclGetResources;
161
+ }];
@@ -0,0 +1,160 @@
1
+ import { z } from "zod";
2
+ import { apiGet, apiPost, apiPut, apiDelete } from "../client/http.js";
3
+ import { formatApiError } from "../utils/errors.js";
4
+ // Input Schemas
5
+ export const RoleListInputSchema = {
6
+ page: z.number().optional().describe("Page number (default: 1)."),
7
+ perPage: z.number().optional().describe("Items per page (default: 25)."),
8
+ name: z.string().optional().describe("Filter by role name."),
9
+ master: z.boolean().optional().describe("Filter by master role status."),
10
+ default: z.boolean().optional().describe("Filter by default role status."),
11
+ };
12
+ export const RoleCreateInputSchema = {
13
+ name: z.string().describe("Role name (required)."),
14
+ permissions: z.array(z.object({
15
+ resource: z.string().describe("Resource name (e.g., ANALYTICS, EVENTS)."),
16
+ access: z.string().describe("Access level (e.g., VIEW, MODIFY)."),
17
+ filterQuery: z.string().optional().describe("Optional filter query."),
18
+ })).optional().describe("Array of permission definitions."),
19
+ stores: z.array(z.string()).optional().describe("Store codes to assign access (e.g., ['DEFAULT'])."),
20
+ default: z.boolean().optional().describe("Whether this is a default role for new admins."),
21
+ };
22
+ export const RoleGetInputSchema = {
23
+ roleId: z.union([z.string(), z.number()]).describe("The role ID to retrieve."),
24
+ };
25
+ export const RoleUpdateInputSchema = {
26
+ roleId: z.union([z.string(), z.number()]).describe("The role ID to update."),
27
+ name: z.string().optional().describe("Role name."),
28
+ permissions: z.array(z.object({
29
+ resource: z.string().describe("Resource name."),
30
+ access: z.string().describe("Access level."),
31
+ filterQuery: z.string().optional().describe("Optional filter query."),
32
+ })).optional().describe("Array of permission definitions."),
33
+ stores: z.array(z.string()).optional().describe("Store codes to assign access."),
34
+ default: z.boolean().optional().describe("Whether this is a default role."),
35
+ };
36
+ export const RoleDeleteInputSchema = {
37
+ roleId: z.union([z.string(), z.number()]).describe("The role ID to delete."),
38
+ };
39
+ // Handler functions
40
+ export async function roleList(input) {
41
+ const params = new URLSearchParams();
42
+ if (input.page)
43
+ params.append("_page", String(input.page));
44
+ if (input.perPage)
45
+ params.append("_itemsOnPage", String(input.perPage));
46
+ if (input.name)
47
+ params.append("name", input.name);
48
+ if (input.master !== undefined)
49
+ params.append("master", String(input.master));
50
+ if (input.default !== undefined)
51
+ params.append("default", String(input.default));
52
+ const queryString = params.toString();
53
+ const url = `/acl/role${queryString ? `?${queryString}` : ""}`;
54
+ try {
55
+ const response = await apiGet(url);
56
+ return response;
57
+ }
58
+ catch (error) {
59
+ throw formatApiError(error, "openloyalty_role_list");
60
+ }
61
+ }
62
+ export async function roleCreate(input) {
63
+ const payload = {
64
+ name: input.name,
65
+ };
66
+ if (input.permissions)
67
+ payload.permissions = input.permissions;
68
+ if (input.stores)
69
+ payload.stores = input.stores;
70
+ if (input.default !== undefined)
71
+ payload.default = input.default;
72
+ try {
73
+ await apiPost("/acl/role", { role: payload });
74
+ }
75
+ catch (error) {
76
+ throw formatApiError(error, "openloyalty_role_create");
77
+ }
78
+ }
79
+ export async function roleGet(input) {
80
+ try {
81
+ const response = await apiGet(`/acl/role/${input.roleId}`);
82
+ return response;
83
+ }
84
+ catch (error) {
85
+ throw formatApiError(error, "openloyalty_role_get");
86
+ }
87
+ }
88
+ export async function roleUpdate(input) {
89
+ const payload = {};
90
+ if (input.name)
91
+ payload.name = input.name;
92
+ if (input.permissions)
93
+ payload.permissions = input.permissions;
94
+ if (input.stores)
95
+ payload.stores = input.stores;
96
+ if (input.default !== undefined)
97
+ payload.default = input.default;
98
+ try {
99
+ await apiPut(`/acl/role/${input.roleId}`, { role: payload });
100
+ }
101
+ catch (error) {
102
+ throw formatApiError(error, "openloyalty_role_update");
103
+ }
104
+ }
105
+ export async function roleDelete(input) {
106
+ try {
107
+ await apiDelete(`/acl/role/${input.roleId}`);
108
+ }
109
+ catch (error) {
110
+ throw formatApiError(error, "openloyalty_role_delete");
111
+ }
112
+ }
113
+ export async function aclGetResources() {
114
+ try {
115
+ const response = await apiGet("/acl/resources");
116
+ return response;
117
+ }
118
+ catch (error) {
119
+ throw formatApiError(error, "openloyalty_acl_get_resources");
120
+ }
121
+ }
122
+ // Tool definitions
123
+ export const roleToolDefinitions = [
124
+ {
125
+ name: "openloyalty_role_list",
126
+ description: "List all roles with optional filtering. Returns paginated list of roles with id, name, permissions, master status, and default status.",
127
+ inputSchema: RoleListInputSchema,
128
+ handler: roleList,
129
+ },
130
+ {
131
+ name: "openloyalty_role_create",
132
+ description: "Create a new role with permissions. Requires name. Optionally specify permissions (array of {resource, access, filterQuery}) and stores. Returns void on success (201 Created).",
133
+ inputSchema: RoleCreateInputSchema,
134
+ handler: roleCreate,
135
+ },
136
+ {
137
+ name: "openloyalty_role_get",
138
+ description: "Get full role details by ID. Returns role with name, permissions array, master status, default status, and stores.",
139
+ inputSchema: RoleGetInputSchema,
140
+ handler: roleGet,
141
+ },
142
+ {
143
+ name: "openloyalty_role_update",
144
+ description: "Update a role's name, permissions, or stores. Returns void on success (204 No Content).",
145
+ inputSchema: RoleUpdateInputSchema,
146
+ handler: roleUpdate,
147
+ },
148
+ {
149
+ name: "openloyalty_role_delete",
150
+ description: "Delete a role by ID. Returns void on success (204 No Content). Cannot delete roles that are assigned to admin users.",
151
+ inputSchema: RoleDeleteInputSchema,
152
+ handler: roleDelete,
153
+ },
154
+ {
155
+ name: "openloyalty_acl_get_resources",
156
+ description: "Get list of available ACL resources. Returns items with code and name for each resource type. Use this to discover available permissions before creating roles.",
157
+ inputSchema: {},
158
+ handler: aclGetResources,
159
+ },
160
+ ];