@hayodev/crystallize-mcp 0.1.1
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/LICENSE +21 -0
- package/README.md +304 -0
- package/build/src/audit.d.ts +23 -0
- package/build/src/audit.d.ts.map +1 -0
- package/build/src/audit.js +38 -0
- package/build/src/audit.js.map +1 -0
- package/build/src/bin/crystallize-mcp.d.ts +12 -0
- package/build/src/bin/crystallize-mcp.d.ts.map +1 -0
- package/build/src/bin/crystallize-mcp.js +45 -0
- package/build/src/bin/crystallize-mcp.js.map +1 -0
- package/build/src/bin/setup.d.ts +12 -0
- package/build/src/bin/setup.d.ts.map +1 -0
- package/build/src/bin/setup.js +171 -0
- package/build/src/bin/setup.js.map +1 -0
- package/build/src/client.d.ts +28 -0
- package/build/src/client.d.ts.map +1 -0
- package/build/src/client.js +124 -0
- package/build/src/client.js.map +1 -0
- package/build/src/credentials.d.ts +25 -0
- package/build/src/credentials.d.ts.map +1 -0
- package/build/src/credentials.js +104 -0
- package/build/src/credentials.js.map +1 -0
- package/build/src/errors.d.ts +12 -0
- package/build/src/errors.d.ts.map +1 -0
- package/build/src/errors.js +66 -0
- package/build/src/errors.js.map +1 -0
- package/build/src/index.d.ts +12 -0
- package/build/src/index.d.ts.map +1 -0
- package/build/src/index.js +83 -0
- package/build/src/index.js.map +1 -0
- package/build/src/pii.d.ts +20 -0
- package/build/src/pii.d.ts.map +1 -0
- package/build/src/pii.js +85 -0
- package/build/src/pii.js.map +1 -0
- package/build/src/tools/catalogue.d.ts +7 -0
- package/build/src/tools/catalogue.d.ts.map +1 -0
- package/build/src/tools/catalogue.js +329 -0
- package/build/src/tools/catalogue.js.map +1 -0
- package/build/src/tools/customers.d.ts +7 -0
- package/build/src/tools/customers.d.ts.map +1 -0
- package/build/src/tools/customers.js +278 -0
- package/build/src/tools/customers.js.map +1 -0
- package/build/src/tools/discovery.d.ts +10 -0
- package/build/src/tools/discovery.d.ts.map +1 -0
- package/build/src/tools/discovery.js +285 -0
- package/build/src/tools/discovery.js.map +1 -0
- package/build/src/tools/orders.d.ts +7 -0
- package/build/src/tools/orders.d.ts.map +1 -0
- package/build/src/tools/orders.js +250 -0
- package/build/src/tools/orders.js.map +1 -0
- package/build/src/tools/shapes.d.ts +7 -0
- package/build/src/tools/shapes.d.ts.map +1 -0
- package/build/src/tools/shapes.js +194 -0
- package/build/src/tools/shapes.js.map +1 -0
- package/build/src/types.d.ts +43 -0
- package/build/src/types.d.ts.map +1 -0
- package/build/src/types.js +5 -0
- package/build/src/types.js.map +1 -0
- package/package.json +75 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 HayoDev
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
# Crystallize MCP
|
|
2
|
+
|
|
3
|
+
[](https://npmjs.org/package/@hayodev/crystallize-mcp)
|
|
4
|
+
[](https://npmjs.org/package/@hayodev/crystallize-mcp)
|
|
5
|
+
[](https://github.com/HayoDev/crystallize-mcp/blob/main/LICENSE)
|
|
6
|
+
[](https://npmjs.org/package/@hayodev/crystallize-mcp)
|
|
7
|
+
|
|
8
|
+
MCP server for [Crystallize](https://crystallize.com) headless commerce. Gives AI agents read access to your catalogue, products, shapes, orders, customers, and tenant config ā with deep links back to the Crystallize UI.
|
|
9
|
+
|
|
10
|
+
Works with Claude Code, Claude Desktop, Cursor, Windsurf, Copilot, and any MCP-compatible client.
|
|
11
|
+
|
|
12
|
+
## Getting started
|
|
13
|
+
|
|
14
|
+
Standard MCP config (works in any client):
|
|
15
|
+
|
|
16
|
+
```json
|
|
17
|
+
{
|
|
18
|
+
"mcpServers": {
|
|
19
|
+
"crystallize": {
|
|
20
|
+
"command": "npx",
|
|
21
|
+
"args": ["-y", "@hayodev/crystallize-mcp@latest"],
|
|
22
|
+
"env": {
|
|
23
|
+
"CRYSTALLIZE_TENANT_IDENTIFIER": "your-tenant"
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Add `CRYSTALLIZE_ACCESS_TOKEN_ID` and `CRYSTALLIZE_ACCESS_TOKEN_SECRET` to the `env` block for PIM tools (shapes, orders, customers). See [Authentication](#authentication).
|
|
31
|
+
|
|
32
|
+
<details>
|
|
33
|
+
<summary>Claude Code</summary>
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
claude mcp add crystallize \
|
|
37
|
+
-e CRYSTALLIZE_TENANT_IDENTIFIER=your-tenant \
|
|
38
|
+
-e CRYSTALLIZE_ACCESS_TOKEN_ID=your-token-id \
|
|
39
|
+
-e CRYSTALLIZE_ACCESS_TOKEN_SECRET=your-token-secret \
|
|
40
|
+
-- npx -y @hayodev/crystallize-mcp@latest
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Use `--scope project` to write to `.mcp.json` (shared with your team) or `--scope user` for personal use across all projects.
|
|
44
|
+
|
|
45
|
+
Or run the guided setup wizard, which can optionally store tokens in the OS keychain instead of plain text:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
npx @hayodev/crystallize-mcp --setup
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
</details>
|
|
52
|
+
|
|
53
|
+
<details>
|
|
54
|
+
<summary>Claude Desktop</summary>
|
|
55
|
+
|
|
56
|
+
Run the guided wizard ā it writes directly to `claude_desktop_config.json` and can store tokens in the macOS Keychain so they never appear in the config file:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
npx @hayodev/crystallize-mcp --setup --global
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Or add the standard config manually to `~/Library/Application Support/Claude/claude_desktop_config.json`.
|
|
63
|
+
|
|
64
|
+
</details>
|
|
65
|
+
|
|
66
|
+
<details>
|
|
67
|
+
<summary>Cursor</summary>
|
|
68
|
+
|
|
69
|
+
Add the standard config to your Cursor MCP settings (`~/.cursor/mcp.json` or the project-level `.cursor/mcp.json`).
|
|
70
|
+
|
|
71
|
+
</details>
|
|
72
|
+
|
|
73
|
+
<details>
|
|
74
|
+
<summary>VS Code / GitHub Copilot</summary>
|
|
75
|
+
|
|
76
|
+
Add the standard config to `.vscode/mcp.json` in your project root.
|
|
77
|
+
|
|
78
|
+
```json
|
|
79
|
+
{
|
|
80
|
+
"servers": {
|
|
81
|
+
"crystallize": {
|
|
82
|
+
"command": "npx",
|
|
83
|
+
"args": ["-y", "@hayodev/crystallize-mcp@latest"],
|
|
84
|
+
"env": {
|
|
85
|
+
"CRYSTALLIZE_TENANT_IDENTIFIER": "your-tenant",
|
|
86
|
+
"CRYSTALLIZE_ACCESS_TOKEN_ID": "your-token-id",
|
|
87
|
+
"CRYSTALLIZE_ACCESS_TOKEN_SECRET": "your-token-secret"
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
</details>
|
|
95
|
+
|
|
96
|
+
<details>
|
|
97
|
+
<summary>Windsurf</summary>
|
|
98
|
+
|
|
99
|
+
Add the standard config to `~/.codeium/windsurf/mcp_config.json`.
|
|
100
|
+
|
|
101
|
+
</details>
|
|
102
|
+
|
|
103
|
+
<details>
|
|
104
|
+
<summary>Gemini CLI</summary>
|
|
105
|
+
|
|
106
|
+
Add the standard config to `~/.gemini/settings.json`.
|
|
107
|
+
|
|
108
|
+
</details>
|
|
109
|
+
|
|
110
|
+
<details>
|
|
111
|
+
<summary>JetBrains AI Assistant</summary>
|
|
112
|
+
|
|
113
|
+
Add the standard config to `.junie/mcp.json` in your project root.
|
|
114
|
+
|
|
115
|
+
</details>
|
|
116
|
+
|
|
117
|
+
<details>
|
|
118
|
+
<summary>Warp</summary>
|
|
119
|
+
|
|
120
|
+
Add the standard config to `~/.warp/mcp.json`.
|
|
121
|
+
|
|
122
|
+
</details>
|
|
123
|
+
|
|
124
|
+
<details>
|
|
125
|
+
<summary>Raycast</summary>
|
|
126
|
+
|
|
127
|
+
Open "Install MCP Server" in Raycast and fill in:
|
|
128
|
+
|
|
129
|
+
- **Command**: `npx`
|
|
130
|
+
- **Arguments**: `-y @hayodev/crystallize-mcp@latest`
|
|
131
|
+
- **Environment**: add `CRYSTALLIZE_TENANT_IDENTIFIER` and your token vars
|
|
132
|
+
|
|
133
|
+
Or copy the standard config JSON above before opening the command ā Raycast will auto-fill the form.
|
|
134
|
+
|
|
135
|
+
</details>
|
|
136
|
+
|
|
137
|
+
<details>
|
|
138
|
+
<summary>From source</summary>
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
git clone https://github.com/HayoDev/crystallize-mcp.git
|
|
142
|
+
cd crystallize-mcp
|
|
143
|
+
npm install && npm run build
|
|
144
|
+
npx . --setup --local # writes .mcp.json pointing to local build
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Or point your MCP client directly at the built entry point:
|
|
148
|
+
|
|
149
|
+
```json
|
|
150
|
+
{
|
|
151
|
+
"mcpServers": {
|
|
152
|
+
"crystallize": {
|
|
153
|
+
"command": "node",
|
|
154
|
+
"args": ["/path/to/crystallize-mcp/build/src/bin/crystallize-mcp.js"],
|
|
155
|
+
"env": {
|
|
156
|
+
"CRYSTALLIZE_TENANT_IDENTIFIER": "your-tenant"
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
</details>
|
|
164
|
+
|
|
165
|
+
## Tools (12)
|
|
166
|
+
|
|
167
|
+
### Catalogue (4 tools)
|
|
168
|
+
|
|
169
|
+
| Tool | Description |
|
|
170
|
+
| ---------------------- | ---------------------------------------------------- |
|
|
171
|
+
| `browse_catalogue` | Traverse the item tree by path |
|
|
172
|
+
| `get_item` | Fetch an item by path or ID with full component data |
|
|
173
|
+
| `search_catalogue` | Keyword search across all items |
|
|
174
|
+
| `get_product_variants` | List variants with pricing and stock |
|
|
175
|
+
|
|
176
|
+
### Discovery (3 tools)
|
|
177
|
+
|
|
178
|
+
| Tool | Description |
|
|
179
|
+
| ----------------------- | --------------------------------------------------------------------- |
|
|
180
|
+
| `list_discovery_shapes` | List all shapes with their queryable fields |
|
|
181
|
+
| `browse_shape` | Browse items of a shape with filters, pagination, and field selection |
|
|
182
|
+
| `get_shape_fields` | Detailed field info for a specific shape |
|
|
183
|
+
|
|
184
|
+
### Shapes & Tenant (3 tools, requires auth)
|
|
185
|
+
|
|
186
|
+
| Tool | Description |
|
|
187
|
+
| ----------------- | -------------------------------------------- |
|
|
188
|
+
| `list_shapes` | All shapes with component summaries |
|
|
189
|
+
| `get_shape` | Full component definition for a shape |
|
|
190
|
+
| `get_tenant_info` | Tenant configuration and available languages |
|
|
191
|
+
|
|
192
|
+
### Orders (2 tools, requires auth)
|
|
193
|
+
|
|
194
|
+
| Tool | Description |
|
|
195
|
+
| ------------- | ----------------------------------------------------- |
|
|
196
|
+
| `list_orders` | List orders for a customer with pagination |
|
|
197
|
+
| `get_order` | Full order details ā cart, payments, customer, totals |
|
|
198
|
+
|
|
199
|
+
### Customers (2 tools, requires auth)
|
|
200
|
+
|
|
201
|
+
| Tool | Description |
|
|
202
|
+
| ---------------- | ------------------------------------------------------------ |
|
|
203
|
+
| `list_customers` | Search and list customers with pagination |
|
|
204
|
+
| `get_customer` | Full customer profile ā addresses, meta, external references |
|
|
205
|
+
|
|
206
|
+
## Authentication
|
|
207
|
+
|
|
208
|
+
Catalogue and Discovery tools work without auth ā just set `CRYSTALLIZE_TENANT_IDENTIFIER`.
|
|
209
|
+
|
|
210
|
+
For PIM tools (shapes, tenant info, orders, customers), create an access token at:
|
|
211
|
+
`https://app.crystallize.com/{tenant}/en/settings/access-tokens`
|
|
212
|
+
|
|
213
|
+
> **Note:** Crystallize tokens inherit permissions from the user who created them. To restrict an agent to read-only access, generate the token under a user with a read-only role. See [Crystallize Roles](https://crystallize.com/docs/configuration/roles).
|
|
214
|
+
|
|
215
|
+
### Environment variables
|
|
216
|
+
|
|
217
|
+
| Variable | Required | Description |
|
|
218
|
+
| --------------------------------- | -------- | ---------------------------------------------------------------------------------- |
|
|
219
|
+
| `CRYSTALLIZE_TENANT_IDENTIFIER` | Yes | Your tenant identifier from `app.crystallize.com/{tenant}` |
|
|
220
|
+
| `CRYSTALLIZE_ACCESS_TOKEN_ID` | No | Access token ID for PIM API |
|
|
221
|
+
| `CRYSTALLIZE_ACCESS_TOKEN_SECRET` | No | Access token secret (paired with token ID) |
|
|
222
|
+
| `CRYSTALLIZE_STATIC_AUTH_TOKEN` | No | Static auth token (alternative to ID/secret pair) |
|
|
223
|
+
| `CRYSTALLIZE_ACCESS_MODE` | No | `read` (default), `write`, or `admin` ā controls which tools are registered |
|
|
224
|
+
| `CRYSTALLIZE_PII_MODE` | No | `full` (default), `masked`, or `none` ā controls PII in customer/order responses |
|
|
225
|
+
| `CRYSTALLIZE_AUDIT_LOG` | No | Path to write an audit log ā `~` is expanded (e.g. `~/.crystallize-mcp/audit.log`) |
|
|
226
|
+
|
|
227
|
+
### PII mode (opt-in)
|
|
228
|
+
|
|
229
|
+
By default all customer and order data is returned as-is (`full`). Set `CRYSTALLIZE_PII_MODE` to opt in to data minimisation:
|
|
230
|
+
|
|
231
|
+
| Mode | Behaviour |
|
|
232
|
+
| -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
233
|
+
| `full` | Default ā all fields returned unchanged |
|
|
234
|
+
| `masked` | Emails ā `h***@example.com`, phones ā `***-1234`, addresses ā city + country only |
|
|
235
|
+
| `none` | Contact/PII fields stripped ā names, emails, phones, addresses, meta, and external references removed. Non-contact data (order lines, payment types, totals) may still be present. |
|
|
236
|
+
|
|
237
|
+
Applies to `list_customers`, `get_customer`, `list_orders`, and `get_order`. No effect on catalogue or shape tools.
|
|
238
|
+
|
|
239
|
+
Relevant for teams handling real customer data, GDPR Article 25 compliance (data minimisation by design), or environments where the AI doesn't need raw contact details to do its job. A common pattern is `masked` on production and `full` on dev.
|
|
240
|
+
|
|
241
|
+
### Audit log (opt-in)
|
|
242
|
+
|
|
243
|
+
Set `CRYSTALLIZE_AUDIT_LOG` to an absolute file path to enable structured logging of every tool call:
|
|
244
|
+
|
|
245
|
+
```json
|
|
246
|
+
{
|
|
247
|
+
"ts": "2026-04-03T14:38:41Z",
|
|
248
|
+
"tool": "list_customers",
|
|
249
|
+
"params": { "first": 10 },
|
|
250
|
+
"result": "ok",
|
|
251
|
+
"tenant": "hageland-prod"
|
|
252
|
+
}
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
One JSON line per call ā timestamp, tool name, params, result (`ok`/`error`), and tenant. Response content is never logged.
|
|
256
|
+
|
|
257
|
+
> **Note:** Params are logged as-is and may contain PII ā for example, a `searchTerm` of `hani@example.com` or a `customerIdentifier`. Treat the audit log file as sensitive data and restrict access accordingly. Param scrubbing is on the roadmap but not yet implemented.
|
|
258
|
+
|
|
259
|
+
Easy to pipe into log aggregators (Datadog, CloudWatch, Splunk) ā but ensure your pipeline handles the file with appropriate access controls.
|
|
260
|
+
|
|
261
|
+
### Keychain storage (optional)
|
|
262
|
+
|
|
263
|
+
The setup wizard (`npx @hayodev/crystallize-mcp --setup`) can store tokens in the OS keychain (macOS Keychain, Windows Credential Manager, or libsecret on Linux) so they never appear as plain text in config files. This is particularly useful for:
|
|
264
|
+
|
|
265
|
+
- **Claude Desktop users** ā config is written to a JSON file with no CLI equivalent for secret management
|
|
266
|
+
- **Shared `.mcp.json`** ā when your project config is committed to git, keychain storage keeps tokens out of the repository
|
|
267
|
+
|
|
268
|
+
When tokens are in the keychain, the config only needs `CRYSTALLIZE_TENANT_IDENTIFIER` ā credentials are resolved automatically at startup.
|
|
269
|
+
|
|
270
|
+
Note: CLI-based MCP clients (`claude mcp add`, Cursor, Copilot, etc.) store env vars as plain text in their config files and do not integrate with the OS keychain directly. If plain text env vars in a local config file are acceptable for your setup, the wizard's keychain option is not needed.
|
|
271
|
+
|
|
272
|
+
### Access mode
|
|
273
|
+
|
|
274
|
+
`CRYSTALLIZE_ACCESS_MODE` controls which tools the MCP server registers at startup:
|
|
275
|
+
|
|
276
|
+
- **`read`** (default) ā read-only tools only
|
|
277
|
+
- **`write`** ā includes tools that can create/update content (Phase 3)
|
|
278
|
+
- **`admin`** ā full access including webhooks and tenant config (Phase 3)
|
|
279
|
+
|
|
280
|
+
## Deep links
|
|
281
|
+
|
|
282
|
+
Every response includes clickable links to the Crystallize UI:
|
|
283
|
+
|
|
284
|
+
- Items ā `app.crystallize.com/@{tenant}/{language}/catalogue/{type}/{itemId}`
|
|
285
|
+
- Shapes ā `app.crystallize.com/@{tenant}/{language}/settings/shapes/{identifier}`
|
|
286
|
+
- Orders ā `app.crystallize.com/@{tenant}/{language}/orders/{orderId}`
|
|
287
|
+
|
|
288
|
+
The language segment is automatically set from the tenant's default language, bootstrapped at server startup ā no configuration needed. Tools that accept a `language` parameter (catalogue, search) use the requested language in both the API call and the generated link.
|
|
289
|
+
|
|
290
|
+
## Development
|
|
291
|
+
|
|
292
|
+
```bash
|
|
293
|
+
npm install
|
|
294
|
+
npm run build
|
|
295
|
+
npm run dev # build + start
|
|
296
|
+
npm run lint # oxlint
|
|
297
|
+
npm run format # oxlint --fix + oxfmt
|
|
298
|
+
npm run typecheck # tsc --noEmit
|
|
299
|
+
npm run test # build + node --test
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
## License
|
|
303
|
+
|
|
304
|
+
MIT
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Audit logger ā writes one JSON line per tool call to a configured file.
|
|
3
|
+
*
|
|
4
|
+
* Never logs response content ā only what was requested and the shape of the result.
|
|
5
|
+
*/
|
|
6
|
+
export interface AuditEntry {
|
|
7
|
+
ts: string;
|
|
8
|
+
tool: string;
|
|
9
|
+
params: Record<string, unknown>;
|
|
10
|
+
result: 'ok' | 'error';
|
|
11
|
+
tenant: string;
|
|
12
|
+
}
|
|
13
|
+
export declare class AuditLogger {
|
|
14
|
+
private readonly path;
|
|
15
|
+
private enabled;
|
|
16
|
+
constructor(path: string);
|
|
17
|
+
log(entry: AuditEntry): void;
|
|
18
|
+
}
|
|
19
|
+
/** Return a non-content status string for the audit log. */
|
|
20
|
+
export declare function summariseResult(result: {
|
|
21
|
+
isError?: boolean;
|
|
22
|
+
}): 'ok' | 'error';
|
|
23
|
+
//# sourceMappingURL=audit.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audit.d.ts","sourceRoot":"","sources":["../../src/audit.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,MAAM,EAAE,IAAI,GAAG,OAAO,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAS;IAC9B,OAAO,CAAC,OAAO,CAAQ;gBAEX,IAAI,EAAE,MAAM;IAUxB,GAAG,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI;CAW7B;AAED,4DAA4D;AAC5D,wBAAgB,eAAe,CAAC,MAAM,EAAE;IAAE,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,IAAI,GAAG,OAAO,CAE7E"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Audit logger ā writes one JSON line per tool call to a configured file.
|
|
3
|
+
*
|
|
4
|
+
* Never logs response content ā only what was requested and the shape of the result.
|
|
5
|
+
*/
|
|
6
|
+
import { appendFileSync, mkdirSync } from 'node:fs';
|
|
7
|
+
import { dirname } from 'node:path';
|
|
8
|
+
export class AuditLogger {
|
|
9
|
+
path;
|
|
10
|
+
enabled = true;
|
|
11
|
+
constructor(path) {
|
|
12
|
+
this.path = path;
|
|
13
|
+
try {
|
|
14
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
// Audit logging should never crash the server
|
|
18
|
+
this.enabled = false;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
log(entry) {
|
|
22
|
+
if (!this.enabled) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
try {
|
|
26
|
+
appendFileSync(this.path, JSON.stringify(entry) + '\n');
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
// Audit logging should never crash the server
|
|
30
|
+
this.enabled = false;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/** Return a non-content status string for the audit log. */
|
|
35
|
+
export function summariseResult(result) {
|
|
36
|
+
return result.isError ? 'error' : 'ok';
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=audit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audit.js","sourceRoot":"","sources":["../../src/audit.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACpD,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAUpC,MAAM,OAAO,WAAW;IACL,IAAI,CAAS;IACtB,OAAO,GAAG,IAAI,CAAC;IAEvB,YAAY,IAAY;QACtB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC;YACH,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAChD,CAAC;QAAC,MAAM,CAAC;YACP,8CAA8C;YAC9C,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC;QACvB,CAAC;IACH,CAAC;IAED,GAAG,CAAC,KAAiB;QACnB,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;YAClB,OAAO;QACT,CAAC;QACD,IAAI,CAAC;YACH,cAAc,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,CAAC;QAC1D,CAAC;QAAC,MAAM,CAAC;YACP,8CAA8C;YAC9C,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC;QACvB,CAAC;IACH,CAAC;CACF;AAED,4DAA4D;AAC5D,MAAM,UAAU,eAAe,CAAC,MAA6B;IAC3D,OAAO,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC;AACzC,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Crystallize MCP Server ā CLI entry point.
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* crystallize-mcp Start the MCP server (stdio transport)
|
|
7
|
+
* crystallize-mcp --setup Interactive setup for Claude Code (.mcp.json)
|
|
8
|
+
* crystallize-mcp --setup --global Setup for Claude Desktop
|
|
9
|
+
* crystallize-mcp --setup --local Setup pointing at local build (for development)
|
|
10
|
+
*/
|
|
11
|
+
export {};
|
|
12
|
+
//# sourceMappingURL=crystallize-mcp.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"crystallize-mcp.d.ts","sourceRoot":"","sources":["../../../src/bin/crystallize-mcp.ts"],"names":[],"mappings":";AAEA;;;;;;;;GAQG"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Crystallize MCP Server ā CLI entry point.
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* crystallize-mcp Start the MCP server (stdio transport)
|
|
7
|
+
* crystallize-mcp --setup Interactive setup for Claude Code (.mcp.json)
|
|
8
|
+
* crystallize-mcp --setup --global Setup for Claude Desktop
|
|
9
|
+
* crystallize-mcp --setup --local Setup pointing at local build (for development)
|
|
10
|
+
*/
|
|
11
|
+
// Handle --setup before importing heavy deps
|
|
12
|
+
if (process.argv.includes('--setup')) {
|
|
13
|
+
import('./setup.js').catch((err) => {
|
|
14
|
+
console.error('Setup failed:', err);
|
|
15
|
+
process.exit(1);
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
startServer().catch((error) => {
|
|
20
|
+
console.error('Failed to start Crystallize MCP server:', error);
|
|
21
|
+
process.exit(1);
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
async function startServer() {
|
|
25
|
+
const { StdioServerTransport } = await import('@modelcontextprotocol/sdk/server/stdio.js');
|
|
26
|
+
const { createCrystallizeMcpServer } = await import('../index.js');
|
|
27
|
+
const { CrystallizeClient } = await import('../client.js');
|
|
28
|
+
const crystallize = await CrystallizeClient.fromEnvOrKeychain();
|
|
29
|
+
const { server, client } = createCrystallizeMcpServer(crystallize);
|
|
30
|
+
const transport = new StdioServerTransport();
|
|
31
|
+
await server.connect(transport);
|
|
32
|
+
console.error(`Crystallize MCP server running`);
|
|
33
|
+
console.error(` Tenant: ${client.config.tenantIdentifier}`);
|
|
34
|
+
console.error(` Access mode: ${client.config.accessMode}`);
|
|
35
|
+
console.error(` Language: ${client.config.defaultLanguage ?? 'en (fallback)'}`);
|
|
36
|
+
console.error(` PII mode: ${client.config.piiMode}`);
|
|
37
|
+
if (client.config.auditLog) {
|
|
38
|
+
console.error(` Audit log: ${client.config.auditLog}`);
|
|
39
|
+
}
|
|
40
|
+
console.error(` Auth: ${client.config.accessTokenId ? 'token' : client.config.staticAuthToken ? 'static' : 'none (public catalogue only)'}`);
|
|
41
|
+
}
|
|
42
|
+
process.on('SIGINT', () => process.exit(0));
|
|
43
|
+
process.on('SIGTERM', () => process.exit(0));
|
|
44
|
+
export {};
|
|
45
|
+
//# sourceMappingURL=crystallize-mcp.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"crystallize-mcp.js","sourceRoot":"","sources":["../../../src/bin/crystallize-mcp.ts"],"names":[],"mappings":";AAEA;;;;;;;;GAQG;AAEH,6CAA6C;AAC7C,IAAI,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;IACrC,MAAM,CAAC,YAAY,CAAC,CAAC,KAAK,CAAC,CAAC,GAAY,EAAE,EAAE;QAC1C,OAAO,CAAC,KAAK,CAAC,eAAe,EAAE,GAAG,CAAC,CAAC;QACpC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC,CAAC;AACL,CAAC;KAAM,CAAC;IACN,WAAW,EAAE,CAAC,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;QACrC,OAAO,CAAC,KAAK,CAAC,yCAAyC,EAAE,KAAK,CAAC,CAAC;QAChE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC,CAAC;AACL,CAAC;AAED,KAAK,UAAU,WAAW;IACxB,MAAM,EAAE,oBAAoB,EAAE,GAC5B,MAAM,MAAM,CAAC,2CAA2C,CAAC,CAAC;IAC5D,MAAM,EAAE,0BAA0B,EAAE,GAAG,MAAM,MAAM,CAAC,aAAa,CAAC,CAAC;IACnE,MAAM,EAAE,iBAAiB,EAAE,GAAG,MAAM,MAAM,CAAC,cAAc,CAAC,CAAC;IAE3D,MAAM,WAAW,GAAG,MAAM,iBAAiB,CAAC,iBAAiB,EAAE,CAAC;IAChE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,0BAA0B,CAAC,WAAW,CAAC,CAAC;IAEnE,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAEhC,OAAO,CAAC,KAAK,CAAC,gCAAgC,CAAC,CAAC;IAChD,OAAO,CAAC,KAAK,CAAC,aAAa,MAAM,CAAC,MAAM,CAAC,gBAAgB,EAAE,CAAC,CAAC;IAC7D,OAAO,CAAC,KAAK,CAAC,kBAAkB,MAAM,CAAC,MAAM,CAAC,UAAU,EAAE,CAAC,CAAC;IAC5D,OAAO,CAAC,KAAK,CACX,eAAe,MAAM,CAAC,MAAM,CAAC,eAAe,IAAI,eAAe,EAAE,CAClE,CAAC;IACF,OAAO,CAAC,KAAK,CAAC,eAAe,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC;IACtD,IAAI,MAAM,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC;QAC3B,OAAO,CAAC,KAAK,CAAC,gBAAgB,MAAM,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC;IAC1D,CAAC;IACD,OAAO,CAAC,KAAK,CACX,WAAW,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,eAAe,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,8BAA8B,EAAE,CAC/H,CAAC;AACJ,CAAC;AAED,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;AAC5C,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Interactive setup for crystallize-mcp.
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* npx crystallize-mcp --setup
|
|
7
|
+
* npx crystallize-mcp --setup --global
|
|
8
|
+
*
|
|
9
|
+
* Generates the MCP config for Claude Desktop or Claude Code.
|
|
10
|
+
*/
|
|
11
|
+
export {};
|
|
12
|
+
//# sourceMappingURL=setup.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../../../src/bin/setup.ts"],"names":[],"mappings":";AAEA;;;;;;;;GAQG"}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Interactive setup for crystallize-mcp.
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* npx crystallize-mcp --setup
|
|
7
|
+
* npx crystallize-mcp --setup --global
|
|
8
|
+
*
|
|
9
|
+
* Generates the MCP config for Claude Desktop or Claude Code.
|
|
10
|
+
*/
|
|
11
|
+
import { createInterface } from 'node:readline';
|
|
12
|
+
import { writeFileSync, readFileSync, existsSync } from 'node:fs';
|
|
13
|
+
import { resolve as resolvePath } from 'node:path';
|
|
14
|
+
import { homedir } from 'node:os';
|
|
15
|
+
import { isKeychainAvailable, writeCredentials } from '../credentials.js';
|
|
16
|
+
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
17
|
+
function ask(question, defaultValue) {
|
|
18
|
+
const suffix = defaultValue ? ` (${defaultValue})` : '';
|
|
19
|
+
return new Promise(res => {
|
|
20
|
+
rl.question(`${question}${suffix}: `, answer => {
|
|
21
|
+
res(answer.trim() || defaultValue || '');
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
async function main() {
|
|
26
|
+
const isGlobal = process.argv.includes('--global');
|
|
27
|
+
const isLocal = process.argv.includes('--local');
|
|
28
|
+
console.error('\nš§ Crystallize MCP Setup\n');
|
|
29
|
+
console.error('This will generate the MCP configuration for your Crystallize tenant.\n');
|
|
30
|
+
// Tenant identifier (required)
|
|
31
|
+
const tenant = await ask('Tenant identifier (from app.crystallize.com/{tenant})');
|
|
32
|
+
if (!tenant) {
|
|
33
|
+
console.error('\nā Tenant identifier is required.');
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
// Tenant ID (MongoDB UUID ā find it at app.crystallize.com/{tenant}/en/settings/tenant)
|
|
37
|
+
const tenantId = await ask('Tenant ID (UUID from Settings ā Tenant info, optional)');
|
|
38
|
+
// Auth tokens (optional)
|
|
39
|
+
console.error('\nAuth tokens are optional. Without them, you can still browse the public');
|
|
40
|
+
console.error('catalogue and use the Discovery API. Add tokens for PIM access (shapes, tenant info).\n');
|
|
41
|
+
const wantsAuth = await ask('Add auth tokens? (y/n)', 'n');
|
|
42
|
+
let tokenId = '';
|
|
43
|
+
let tokenSecret = '';
|
|
44
|
+
let staticToken = '';
|
|
45
|
+
if (wantsAuth.toLowerCase() === 'y') {
|
|
46
|
+
console.error('\nCreate tokens at: https://app.crystallize.com/' +
|
|
47
|
+
tenant +
|
|
48
|
+
'/en/settings/access-tokens\n');
|
|
49
|
+
const authType = await ask('Auth type: (1) Token ID + Secret (2) Static token', '1');
|
|
50
|
+
if (authType === '2') {
|
|
51
|
+
staticToken = await ask('Static auth token');
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
tokenId = await ask('Access Token ID');
|
|
55
|
+
tokenSecret = await ask('Access Token Signature Secret');
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
// Access mode
|
|
59
|
+
const accessMode = await ask('Access mode: read / write / admin', 'read');
|
|
60
|
+
// Keychain offer ā only if tokens were entered and keychain is reachable
|
|
61
|
+
const hasSecrets = tokenId || tokenSecret || staticToken;
|
|
62
|
+
let useKeychain = false;
|
|
63
|
+
if (hasSecrets) {
|
|
64
|
+
const keychainAvailable = await isKeychainAvailable();
|
|
65
|
+
if (keychainAvailable) {
|
|
66
|
+
console.error('\nš OS keychain detected (Keychain / Credential Manager / libsecret).');
|
|
67
|
+
const answer = await ask('Store tokens in OS keychain instead of config file? (recommended) y/n', 'y');
|
|
68
|
+
useKeychain = answer.toLowerCase() !== 'n';
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
console.error('\nā ļø OS keychain not available ā tokens will be written to the config file.');
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
// Store to keychain if chosen
|
|
75
|
+
if (useKeychain && hasSecrets) {
|
|
76
|
+
await writeCredentials({
|
|
77
|
+
accessTokenId: tokenId || undefined,
|
|
78
|
+
accessTokenSecret: tokenSecret || undefined,
|
|
79
|
+
staticAuthToken: staticToken || undefined,
|
|
80
|
+
});
|
|
81
|
+
console.error('ā
Tokens saved to OS keychain.');
|
|
82
|
+
}
|
|
83
|
+
// Build env config ā omit secrets if stored in keychain
|
|
84
|
+
const env = {
|
|
85
|
+
CRYSTALLIZE_TENANT_IDENTIFIER: tenant,
|
|
86
|
+
};
|
|
87
|
+
if (tenantId) {
|
|
88
|
+
env.CRYSTALLIZE_TENANT_ID = tenantId;
|
|
89
|
+
}
|
|
90
|
+
if (!useKeychain) {
|
|
91
|
+
if (tokenId) {
|
|
92
|
+
env.CRYSTALLIZE_ACCESS_TOKEN_ID = tokenId;
|
|
93
|
+
}
|
|
94
|
+
if (tokenSecret) {
|
|
95
|
+
env.CRYSTALLIZE_ACCESS_TOKEN_SECRET = tokenSecret;
|
|
96
|
+
}
|
|
97
|
+
if (staticToken) {
|
|
98
|
+
env.CRYSTALLIZE_STATIC_AUTH_TOKEN = staticToken;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (accessMode !== 'read') {
|
|
102
|
+
env.CRYSTALLIZE_ACCESS_MODE = accessMode;
|
|
103
|
+
}
|
|
104
|
+
// Build MCP config entry
|
|
105
|
+
const mcpEntry = isLocal
|
|
106
|
+
? {
|
|
107
|
+
command: 'node',
|
|
108
|
+
args: [resolvePath(process.cwd(), 'build/src/bin/crystallize-mcp.js')],
|
|
109
|
+
env,
|
|
110
|
+
}
|
|
111
|
+
: {
|
|
112
|
+
command: 'npx',
|
|
113
|
+
args: ['-y', '@hayodev/crystallize-mcp@latest'],
|
|
114
|
+
env,
|
|
115
|
+
};
|
|
116
|
+
if (isGlobal) {
|
|
117
|
+
// Claude Desktop config
|
|
118
|
+
const configPath = process.platform === 'darwin'
|
|
119
|
+
? resolvePath(homedir(), 'Library/Application Support/Claude/claude_desktop_config.json')
|
|
120
|
+
: resolvePath(homedir(), 'AppData/Roaming/Claude/claude_desktop_config.json');
|
|
121
|
+
let config = {};
|
|
122
|
+
if (existsSync(configPath)) {
|
|
123
|
+
try {
|
|
124
|
+
config = JSON.parse(readFileSync(configPath, 'utf-8'));
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
// start fresh
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
const servers = (config.mcpServers ?? {});
|
|
131
|
+
servers.crystallize = mcpEntry;
|
|
132
|
+
config.mcpServers = servers;
|
|
133
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n');
|
|
134
|
+
console.error(`\nā
Added to Claude Desktop config: ${configPath}`);
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
// Local .mcp.json for Claude Code
|
|
138
|
+
const configPath = resolvePath(process.cwd(), '.mcp.json');
|
|
139
|
+
let config = {};
|
|
140
|
+
if (existsSync(configPath)) {
|
|
141
|
+
try {
|
|
142
|
+
config = JSON.parse(readFileSync(configPath, 'utf-8'));
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
145
|
+
// start fresh
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
const servers = (config.mcpServers ?? {});
|
|
149
|
+
servers.crystallize = mcpEntry;
|
|
150
|
+
config.mcpServers = servers;
|
|
151
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n');
|
|
152
|
+
console.error(`\nā
Created ${configPath}`);
|
|
153
|
+
}
|
|
154
|
+
console.error('\nDone! The crystallize MCP server is now configured.');
|
|
155
|
+
console.error(`Tenant: ${tenant}`);
|
|
156
|
+
if (hasSecrets) {
|
|
157
|
+
const authType = tokenId ? 'token pair' : 'static token';
|
|
158
|
+
const storage = useKeychain ? 'OS keychain' : 'config file';
|
|
159
|
+
console.error(`Auth: ${authType} (stored in ${storage})`);
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
console.error('Auth: none (public catalogue only)');
|
|
163
|
+
}
|
|
164
|
+
console.error(`Mode: ${accessMode}\n`);
|
|
165
|
+
rl.close();
|
|
166
|
+
}
|
|
167
|
+
main().catch((err) => {
|
|
168
|
+
console.error('Setup failed:', err);
|
|
169
|
+
process.exit(1);
|
|
170
|
+
});
|
|
171
|
+
//# sourceMappingURL=setup.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"setup.js","sourceRoot":"","sources":["../../../src/bin/setup.ts"],"names":[],"mappings":"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"}
|