Skip to content

Commit 1e66969

Browse files
authored
Adding reviews (#41)
1 parent 0b052cb commit 1e66969

21 files changed

Lines changed: 1522 additions & 1 deletion

.changeset/red-chicken-own.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@commercetools/agent-essentials": minor
3+
"@commercetools/mcp-essentials": minor
4+
---
5+
6+
Adding reviews to MCP tools
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
---
2+
description: Refactoring a tool Function in @commercetools/agent-essentials
3+
globs:
4+
alwaysApply: false
5+
---
6+
# Refactoring a tool Function in @commercetools/agent-essentials
7+
8+
This document outlines the steps taken to refactor a function in @commercetools/agent-essentials to connect to commercetools API.
9+
10+
The following steps are defining implementation of an example `cart.read`. So the `(namespace)` here refers to `cart`.
11+
12+
# NOTES:
13+
1. CRUD functions are read, update and create. do not add delete functions.
14+
2. DO NOT MODIFY `parameters.ts` or `prompts.ts` or `tools.ts`
15+
3. when creating update base functions, use get (by id or key) base functions to first fetch the entity, and use the version from the fetched entity in the update payload
16+
17+
# Refactor steps
18+
19+
1. Create a `base.functions.ts` file in the `namespace` directory. This file will export a couple of basic CRUD functions that will be used inside other files in this namespace. you can extract this base functions from current `functions.ts` file in the namespace.
20+
- IMPORTANT: base functions should be generic as possible, e.g. instead of having a separate function for query cart for a specific user and another one for user in a store, we create one that accepts a "where" cluase.
21+
- Note: sometimes the commercetools SDK has different endpoint when a parameter is present, like query in store (look to the code sample below). in that case, we check that parameter inside the base function but keep the function as simple as possible and low complexity.
22+
- GOAL: the goal of this step is maximize reusability.
23+
- example: base functions created for 'cart.read' are: readCartById, readCartByKey, queryCart, queryCarts.
24+
- file example: `typescript/src/shared/cart/base.functions.ts`
25+
- code sample:
26+
27+
```ts
28+
const queryCart = async (
29+
apiRoot: ApiRoot,
30+
projectKey: string,
31+
queryArgs: any,
32+
storeKey?: string
33+
) => {
34+
if (storeKey) {
35+
const carts = await apiRoot
36+
.withProjectKey({projectKey})
37+
.inStoreKeyWithStoreKeyValue({storeKey})
38+
.carts()
39+
.get({queryArgs})
40+
.execute();
41+
return carts.body;
42+
}
43+
const carts = await apiRoot
44+
.withProjectKey({projectKey})
45+
.carts()
46+
.get({queryArgs})
47+
.execute();
48+
return carts.body;
49+
};
50+
```
51+
52+
2. Create a `customer.functions.ts` in the namespace directory. This file, uses `base.functions.ts`. it has CRUD functions when `context.customerId` is present.
53+
- GOAL: these functions are to limit the operations to the specific customerId.
54+
- example: when querying carts, it should always inject `context.customerId` to the query. If not possible, it should check the entity after it's fetched.
55+
- file example: `typescript/src/shared/cart/customer.functions.ts`
56+
57+
3. Create a `store.functions.ts` in the namespace directory. This file, uses `base.functions.ts`. it has CRUD functions when `context.storeKey` is present.
58+
- GOAL: these functions are to limit the operations to the specific store.
59+
- example: Limit carts fetched to a store.
60+
61+
4. Create a `admin.functions.ts` in the namespace directory. This file, uses `base.functions.ts`. it has CRUD functions when `context.isAdmin` is present.
62+
- GOAL: these functions doesn't have any limitations.
63+
- file example: `typescript/src/shared/cart/admin.functions.ts`
64+
65+
5. Modify `functions.ts` to import all exported methods from customer, admin and store. create a method called `contextTo<namespace>FunctionMapping` which accepts the context and returns an object of name to method mapping.
66+
- IMPORTANT: if no context is there, empty object should return
67+
- IMPORTANT: use type `Context` from `typescript/src/types/configuration.ts`
68+
- example:
69+
```ts
70+
export const contextToCartFunctionMapping = (context?: Context) => {
71+
if (context?.customerId) {
72+
return {
73+
read_cart: customer.readCart,
74+
// create_cart: customer.createCart,
75+
// update_cart: customer.updateCart,
76+
// replicate_cart: customer.replicateCart,
77+
};
78+
}
79+
if (context?.storeKey) {
80+
return {
81+
read_cart: store.readCart,
82+
// create_cart: store.createCart,
83+
// update_cart: store.updateCart,
84+
// replicate_cart: store.replicateCart,
85+
};
86+
}
87+
if (context?.isAdmin) { // IMPORTANT
88+
return {
89+
read_cart: admin.readCart,
90+
// create_cart: admin.createAdminCart,
91+
// update_cart: admin.updateAdminCart,
92+
// replicate_cart: admin.replicateAdminCart,
93+
};
94+
}
95+
};
96+
```
97+
98+
6. create a test file to check if correct context is loading right functions from `contextTo<namespace>FunctionMapping` and if none is provided, it should be empty
99+
7. update current tests to match the function calls
100+
8. refactor `typescript/src/shared/functions.ts` and import `import {contextToCartFunctionMapping} from './cart/functions';` then update this method return
101+
9. confirm that this method call `apiRoot.withProjectKey()` is only being called from base functions not in `customer.functions.ts` and not in `customer` and `admin`. if usage of `apiRoot.withProjectKey` found, move the method to base and reused from base functions.
102+
```
103+
export const contextToFunctionMapping = (context?: Context) => {
104+
return {
105+
...contextToOrderFunctionMapping(context),
106+
...contextToCartFunctionMapping(context),
107+
};
108+
};
109+
```

.cursor/rules/test.mdc

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
description: Writing tests
3+
globs:
4+
alwaysApply: true
5+
---
6+
Always follow these rules:
7+
- After creating a new functionality create test cases for all possible paths
8+
- After updating or refactoring update test cases or create new ones
9+
- Run related tests and make sure all pass
10+
- project uses `pnpm` as package manager
Lines changed: 223 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,223 @@
1+
---
2+
description: Creating a New tool Function in @commercetools/agent-essentials
3+
globs:
4+
alwaysApply: false
5+
---
6+
# Creating a New namespace tool Function in @commercetools/agent-essentials
7+
8+
This document outlines the steps taken to implement a new function in @commercetools/agent-essentials to connect to commercetools API.
9+
10+
The following steps are defining implementation of an example `products.create`. So the `(namespace)` here refers to `product`.
11+
12+
## Implementation Steps
13+
14+
1. Gather info regarding the (namespace)
15+
- Search commercetools docs at https://docs.commercetools.com/api and find the (namespace)
16+
- In this example `https://docs.commercetools.com/api/projects/products` and ProductDraft
17+
18+
2. Define Parameter Schema
19+
- Using the namespace fetched in previous step , Created `createProductParameters` in `typescript/src/shared/(namespace)/parameters.ts`
20+
- Used Zod for type validation
21+
- Included all required and optional fields from ProductDraft
22+
- Created a separate `productVariantDraft` schema for reusability
23+
24+
3. Create a `base.functions.ts` file in the `namespace` directory. This file will export a couple of basic CRUD functions that will be used inside other files in this namespace. you can extract this base functions from current `functions.ts` file in the namespace.
25+
- IMPORTANT: base functions should be generic as possible, e.g. instead of having a separate function for query cart for a specific user and another one for user in a store, we create one that accepts a "where" clause.
26+
- 3.1: sometimes the commercetools SDK has different endpoint when a parameter is present, like query in store (look to the code sample below). in that case, we check that parameter inside the base function but keep the function as simple as possible and low complexity.
27+
- 3.2: No delete operation.
28+
- 3.3: in the base update, call the base get function to fetch the version from the entity and use it in the update call.
29+
- GOAL: the goal of this step is maximize reusability.
30+
- example: base functions created for 'cart.read' are: readCartById, readCartByKey, queryCart, queryCarts.
31+
- file example: `typescript/src/shared/cart/base.functions.ts`
32+
***IMPORTANT***: functions.ts usually exports 3 functions: read, create and update. If there are more ways to read the entity (by id or key or query), embed all in one "read" exported function and adjust the parameters and prompts as well.
33+
34+
- code sample:
35+
36+
```ts
37+
const queryCart = async (
38+
apiRoot: ApiRoot,
39+
projectKey: string,
40+
queryArgs: any,
41+
storeKey?: string
42+
) => {
43+
if (storeKey) {
44+
const carts = await apiRoot
45+
.withProjectKey({projectKey})
46+
.inStoreKeyWithStoreKeyValue({storeKey})
47+
.carts()
48+
.get({queryArgs})
49+
.execute();
50+
return carts.body;
51+
}
52+
const carts = await apiRoot
53+
.withProjectKey({projectKey})
54+
.carts()
55+
.get({queryArgs})
56+
.execute();
57+
return carts.body;
58+
};
59+
```
60+
4. Create a `customer.functions.ts` in the namespace directory. This file, uses `base.functions.ts`. it has CRUD functions when `context.customerId` is present.
61+
- GOAL: these functions are to limit the operations to the specific customerId.
62+
- Note: Double check so there is no usage of `apiRoot.withProjectKey(...)` in this file since all actuall sdk api-calls should be in base.functions.ts
63+
- example: when querying carts, it should always inject `context.customerId` to the query. If not possible, it should check the entity after it's fetched.
64+
- file example: `typescript/src/shared/cart/customer.functions.ts`
65+
66+
5. Create a `store.functions.ts` in the namespace directory. This file, uses `base.functions.ts`. it has CRUD functions when `context.storeKey` is present.
67+
- GOAL: these functions are to limit the operations to the specific store.
68+
- Note: Double check so there is no usage of `apiRoot.withProjectKey(...)` in this file since all actuall sdk api-calls should be in base.functions.ts
69+
- example: Limit carts fetched to a store.
70+
71+
6. Create a `associate.functions.ts` in the namespace directory. This file, uses `base.functions.ts`. it has CRUD functions when `context.customerId` and `context.businessUnitKey` are present.
72+
- GOAL: these functions are to limit the operations to as-associate endpoints.
73+
- Note: Double check so there is no usage of `apiRoot.withProjectKey(...)` in this file since all actuall sdk api-calls should be in base.functions.ts
74+
75+
7. Create a `admin.functions.ts` in the namespace directory. This file, uses `base.functions.ts`. it has CRUD functions when `context.isAdmin` is present.
76+
- GOAL: these functions doesn't have any limitations.
77+
- Note: Double check so there is no usage of `apiRoot.withProjectKey(...)` in this file since all actuall sdk api-calls should be in base.functions.ts
78+
- file example: `typescript/src/shared/cart/admin.functions.ts`
79+
80+
8. Create `functions.ts` to import all exported methods from customer, admin and store. create a method called `contextTo<namespace>FunctionMapping` which accepts the context and returns an object of name to method mapping.
81+
- IMPORTANT: if no context is there, empty object should return
82+
- IMPORTANT: use type `Context` from `typescript/src/types/configuration.ts`
83+
- example:
84+
```ts
85+
export const contextToCartFunctionMapping = (context?: Context) => {
86+
if (context?.customerId && context?.businessUnitKey) {
87+
return {
88+
read_cart: associate.readCart,
89+
create_cart: associate.createCart,
90+
update_cart: customer.updateCart,
91+
replicate_cart: associate.replicateCart,
92+
};
93+
}
94+
if (context?.customerId) {
95+
return {
96+
read_cart: customer.readCart,
97+
create_cart: customer.createCart,
98+
update_cart: customer.updateCart,
99+
replicate_cart: customer.replicateCart,
100+
};
101+
}
102+
if (context?.storeKey) {
103+
return {
104+
read_cart: store.readCart,
105+
create_cart: store.createCart,
106+
update_cart: store.updateCart,
107+
replicate_cart: store.replicateCart,
108+
};
109+
}
110+
if (context?.isAdmin) { // IMPORTANT
111+
return {
112+
read_cart: admin.readCart,
113+
create_cart: admin.createAdminCart,
114+
update_cart: admin.updateAdminCart,
115+
replicate_cart: admin.replicateAdminCart,
116+
};
117+
}
118+
return {};
119+
};
120+
```
121+
9. refactor `typescript/src/shared/functions.ts` and import `import {contextToCartFunctionMapping} from './cart/functions';` then update this method return
122+
123+
124+
10. Create Prompt
125+
- Create a prompt describing the function and its' params in `typescript/src/shared/(namespace)/prompts.ts`
126+
- Prompts for create functions across `customer`, `store` and `admin` should be the same. same for other functions
127+
128+
Refactor `const tools: Tool[]` to const tools: Record<string, Tool>
129+
- example
130+
```
131+
const tools: Record<string, Tool> = {}
132+
read_category: {
133+
method: 'read_category',
134+
name: 'Read Category',
135+
description: readCategoryPrompt,
136+
parameters: readCategoryParameters,
137+
actions: {
138+
category: {
139+
read: true,
140+
},
141+
},
142+
}
143+
...
144+
```
145+
11. Create `typescript/src/shared/(namespace)/tools.ts` and export a method `contextToC<namespace>>Tools`. The method's return has to reflect the `typescript/src/shared/(namespae)/functions.ts` 's `contextTo<namespace>FunctionMapping` output
146+
- example:
147+
```ts
148+
export const contextToCategoryTools = (context?: Context) => {
149+
if (context?.customerId && context?.businessUnitKey) {
150+
return [tools.read_category, tools.create_category, tools.update_category]
151+
}
152+
if (context?.customerId) {
153+
return [tools.read_category]
154+
}
155+
if (context?.storeKey) {
156+
return []
157+
}
158+
if (context?.isAdmin) {
159+
return [tools.read_category, tools.create_category, tools.update_category]
160+
}
161+
return { // IMPORTANT: usually fallback return is empty only exceptions are filled.
162+
[]
163+
}
164+
};
165+
```
166+
12. Modify `typescript/src/shared/tools.ts`
167+
```
168+
export const contextToTools = (context?: Context) => {
169+
return {
170+
...contextToCategoryTools(context)
171+
}
172+
}
173+
```
174+
175+
13. Add new tool to ACCEPTED_TOOLS
176+
- add too to `modelcontextprotocol/src/index.ts`'s ACCEPTED_TOOLS
177+
178+
14. Add new tool to "bulk.create" functions.
179+
- Bulk function always use admin.functions
180+
- create the mapping in `typescript/src/shared/bulk/functions.ts` > `entityFunctionMap`
181+
- add the new namespace's parameter to `typescript/src/shared/bulk/parameters.ts`
182+
- modify bulk prompt to include the new entity `typescript/src/shared/bulk/prompts.ts`
183+
184+
15. Test Implementation
185+
- Created `createProduct.test.ts` in test directory
186+
- Implemented mock ApiRoot for testing
187+
- Added test cases for successful creation
188+
- Added test cases for error handling
189+
- Create test cases for success and error of the new tools in `modelcontextprotocol/src/test/main.test.ts`. These tests are only checking if `CommercetoolsAgentToolkit` and `StdioServerTransport` are being called with correct params
190+
example
191+
```
192+
it('should initialize the server with specific tools correctly', async () => {
193+
process.argv = [
194+
'node',
195+
'index.js',
196+
'--tools=products.create', // new function tool
197+
'--clientId=test_client_id',
198+
'--clientSecret=test_client_secret',
199+
'--authUrl=https://auth.commercetools.com',
200+
'--projectKey=test_project',
201+
'--apiUrl=https://api.commercetools.com',
202+
];
203+
204+
await main();
205+
206+
expect(CommercetoolsAgentToolkit).toHaveBeenCalledWith({
207+
clientId: 'test_client_id',
208+
clientSecret: 'test_client_secret',
209+
authUrl: 'https://auth.commercetools.com',
210+
projectKey: 'test_project',
211+
apiUrl: 'https://api.commercetools.com',
212+
configuration: {actions: {products: {create: true}}}, // new function tool
213+
});
214+
215+
expect(StdioServerTransport).toHaveBeenCalled();
216+
});
217+
```
218+
- create a test case to bulk namespace `typescript/src/shared/bulk/test/bulkCreate.test.ts`
219+
- Update `modelcontextprotocol/src/test/main.test.ts` test and include new tool in the test `should initialize the server with tools=all correctly`
220+
221+
16. Update README.md:
222+
- add new tool(s) to Available tools table in `modelcontextprotocol/README.md`
223+
- add new tool's api documentation in commercetools to `README.md`

SECURITY.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
### Reporting a vulnerability
44

5-
Please do not open GitHub issues or pull requests - this makes the problem immediately visible to everyone, including malicious actors.
5+
Please do not open GitHub issues or pull requests - this makes the problem immediately visible to everyone, including malicious actors.
66

77
Security issues in this open-source project can be safely reported via email to the project maintainers.
88
The security team will triage your report and respond according to its impact on users and systems.

modelcontextprotocol/src/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,9 @@ export const ACCEPTED_TOOLS = [
113113
'store.read',
114114
'store.create',
115115
'store.update',
116+
'review.read',
117+
'review.create',
118+
'review.update',
116119
];
117120

118121
// eslint-disable-next-line complexity

modelcontextprotocol/src/test/main.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ describe('main function', () => {
7676
inventory: {read: true, create: true, update: true},
7777
channel: {read: true, create: true, update: true},
7878
store: {read: true, create: true, update: true},
79+
review: {read: true, create: true, update: true},
7980
bulk: {create: true, update: true},
8081
'business-unit': {read: true, create: true, update: true},
8182
},

0 commit comments

Comments
 (0)