@retailcrm/embed-ui-v1-endpoint 0.9.22-alpha.4 → 0.9.22-alpha.7

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 CHANGED
@@ -100,6 +100,14 @@ npx @retailcrm/embed-ui-v1-endpoint init-config
100
100
  npx @retailcrm/embed-ui-v1-endpoint init-config --mcp-client-configs cursor,junie,vscode
101
101
  ```
102
102
 
103
+ Для Codex CLI можно добавить `codex` в список. В этом случае будет создана или
104
+ обновлена запись в `.codex/config.toml`; user-level `codex mcp add` команда не
105
+ запускается автоматически.
106
+
107
+ Корневой `.mcp.json` рассчитан на Claude Code project scope и использует
108
+ `${CLAUDE_PROJECT_DIR:-.}/node_modules/.bin/embed-ui-v1-endpoint-mcp`. Для Cursor
109
+ и VS Code генерируются client-specific project configs с `${workspaceFolder}`.
110
+
103
111
  С `--force` можно обновить уже существующие управляемые записи. Команда
104
112
  обновляет только запись `retailcrm-embed-ui-v1-endpoint`, а остальные серверы и
105
113
  пользовательские настройки клиентских конфигов оставляет без изменений.
@@ -10,22 +10,36 @@ const AGENTS_SECTION_HEADER = '## @retailcrm/embed-ui-v1-endpoint'
10
10
  const README_MCP_SECTION_HEADER = '## MCP For AI Assistants'
11
11
  const README_MCP_MARKER = 'embed-ui-v1-endpoint://targets'
12
12
  const MCP_SERVER_NAME = 'retailcrm-embed-ui-v1-endpoint'
13
- const MCP_SERVER_CONFIG = {
14
- command: 'npx',
15
- args: ['-y', '-p', PACKAGE_NAME, 'embed-ui-v1-endpoint-mcp'],
16
- }
13
+ const MCP_BIN_NAME = process.platform === 'win32' ? 'embed-ui-v1-endpoint-mcp.cmd' : 'embed-ui-v1-endpoint-mcp'
14
+ const RELATIVE_MCP_BIN_PATH = `./node_modules/.bin/${MCP_BIN_NAME}`
15
+ const CLAUDE_PROJECT_MCP_BIN_PATH = `\${CLAUDE_PROJECT_DIR:-.}/node_modules/.bin/${MCP_BIN_NAME}`
16
+ const WORKSPACE_MCP_BIN_PATH = `\${workspaceFolder}/node_modules/.bin/${MCP_BIN_NAME}`
17
17
  const MCP_CLIENT_CONFIGS = {
18
+ codex: {
19
+ type: 'codex-file',
20
+ filePath: '.codex/config.toml',
21
+ command: RELATIVE_MCP_BIN_PATH,
22
+ },
18
23
  cursor: {
24
+ type: 'file',
19
25
  filePath: '.cursor/mcp.json',
20
26
  rootField: 'mcpServers',
27
+ command: WORKSPACE_MCP_BIN_PATH,
21
28
  },
22
29
  junie: {
30
+ type: 'file',
23
31
  filePath: '.junie/mcp/mcp.json',
24
32
  rootField: 'mcpServers',
33
+ command: RELATIVE_MCP_BIN_PATH,
25
34
  },
26
35
  vscode: {
36
+ type: 'file',
27
37
  filePath: '.vscode/mcp.json',
28
38
  rootField: 'servers',
39
+ command: WORKSPACE_MCP_BIN_PATH,
40
+ config: {
41
+ type: 'stdio',
42
+ },
29
43
  },
30
44
  }
31
45
 
@@ -35,7 +49,7 @@ const HELP_TEXT = `Usage:
35
49
 
36
50
  Options:
37
51
  -f, --force Replace existing managed sections and MCP server entries
38
- --mcp-client-configs Comma-separated MCP client configs to create (cursor,junie,vscode)
52
+ --mcp-client-configs Comma-separated project-level MCP client configs to create (codex,cursor,junie,vscode)
39
53
  --dry-run Print planned config changes without writing files
40
54
  -h, --help Show this help
41
55
 
@@ -44,9 +58,20 @@ Examples:
44
58
  npx ${PACKAGE_NAME} init-agents ./my-project
45
59
  npx ${PACKAGE_NAME} init-agents --force
46
60
  npx ${PACKAGE_NAME} init-config ./my-project
47
- npx ${PACKAGE_NAME} init-config ./my-project --mcp-client-configs cursor,junie,vscode
61
+ npx ${PACKAGE_NAME} init-config ./my-project --mcp-client-configs codex,cursor,junie,vscode
48
62
  `
49
63
 
64
+ const resolveLocalMcpBinPath = (target) => path.join(target, 'node_modules', '.bin', MCP_BIN_NAME)
65
+
66
+ const createMcpServerConfig = (command, config = {}) => ({
67
+ ...config,
68
+ command,
69
+ })
70
+
71
+ const printMcpNotice = (message) => {
72
+ console.log(`MCP: ${message}`)
73
+ }
74
+
50
75
  const parseArgs = (argv) => {
51
76
  const options = {
52
77
  command: null,
@@ -128,15 +153,15 @@ When working with \`${PACKAGE_NAME}\` in this project:
128
153
  5. When the task involves widget targets, target placement, target contexts, target metadata, or choosing a target, use the package MCP server if it is available.
129
154
  6. First read \`embed-ui-v1-endpoint://targets\` to discover available target profiles.
130
155
  7. Then read the relevant \`embed-ui-v1-endpoint://targets/<encoded-target>\` resource before answering or changing code related to that target.
131
- 8. If MCP resources are not available, use the generated YAML profiles from \`./node_modules/${PACKAGE_NAME}/docs/targets/*.yml\` as the fallback source.
132
- 9. Prefer target profiles over guessing target placement, contexts, or semantic intent from names alone.
156
+ 8. A project \`.mcp.json\` may require restarting or reconnecting the AI client before MCP resources appear in the current session.
157
+ 9. If MCP resources are not available, use the generated YAML profiles from \`./node_modules/${PACKAGE_NAME}/docs/targets/*.yml\` as the fallback source.
158
+ 10. Prefer target profiles over guessing target placement, contexts, or semantic intent from names alone.
133
159
 
134
160
  Suggested MCP stdio server configuration:
135
161
 
136
162
  \`\`\`json
137
163
  {
138
- "command": "npx",
139
- "args": ["-y", "-p", "${PACKAGE_NAME}", "embed-ui-v1-endpoint-mcp"]
164
+ "command": "${CLAUDE_PROJECT_MCP_BIN_PATH}"
140
165
  }
141
166
  \`\`\`
142
167
  `
@@ -145,17 +170,19 @@ Suggested MCP stdio server configuration:
145
170
  const createMcpReadmeSection = (clientConfigs) => {
146
171
  const clientConfigText = clientConfigs.length
147
172
  ? `Client MCP configs were also requested: ${clientConfigs.map((clientConfig) => `\`${clientConfig}\``).join(', ')}. Review the generated files and restart the AI client if it is already open.`
148
- : 'Client MCP configs are not created by default. For supported project-level configs, rerun init with `--mcp-client-configs cursor,junie,vscode`.'
173
+ : 'Client MCP configs are not created by default. For supported project-level configs, rerun init with `--mcp-client-configs codex,cursor,junie,vscode`.'
149
174
 
150
175
  return `${README_MCP_SECTION_HEADER}
151
176
 
152
177
  The project has an MCP server configuration for \`${PACKAGE_NAME}\`.
153
178
  It exposes AI-friendly widget target descriptions as MCP resources.
179
+ If the AI client was already running, restart or reconnect it before expecting these resources
180
+ to appear in that session.
154
181
 
155
182
  Basic check:
156
183
 
157
184
  \`\`\`bash
158
- npx -p ${PACKAGE_NAME} embed-ui-v1-endpoint-mcp
185
+ ./node_modules/.bin/embed-ui-v1-endpoint-mcp
159
186
  \`\`\`
160
187
 
161
188
  Primary resources:
@@ -165,10 +192,46 @@ Primary resources:
165
192
 
166
193
  ${clientConfigText}
167
194
 
195
+ The root \`.mcp.json\` is compatible with Claude Code project scope and uses
196
+ \`${CLAUDE_PROJECT_MCP_BIN_PATH}\` so the server is resolved from the project directory.
197
+ Cursor and VS Code project configs use \`${WORKSPACE_MCP_BIN_PATH}\`. Codex and Junie use
198
+ \`${RELATIVE_MCP_BIN_PATH}\`.
199
+
200
+ ### Codex CLI
201
+
202
+ Codex supports project-scoped MCP config in \`.codex/config.toml\` for trusted projects.
203
+ Create it with:
204
+
205
+ \`\`\`bash
206
+ npx ${PACKAGE_NAME} init-config --mcp-client-configs codex
207
+ codex mcp list
208
+ \`\`\`
209
+
210
+ If the server does not appear, trust the project in Codex and restart the session. The
211
+ project-level config keeps this repository pinned to its own local
212
+ \`./node_modules/.bin/embed-ui-v1-endpoint-mcp\` binary.
213
+
168
214
  ### User-Level MCP Clients
169
215
 
170
216
  Some clients store MCP servers in a user-level config outside this repository. Init does not edit
171
- those files. Add the same server manually and restart the client.
217
+ those files. Add the same server manually and restart the client. Use this only when this machine
218
+ works with one Embed UI project/version, because multiple user-level servers from different
219
+ projects can expose the same resource URIs and confuse the AI client.
220
+
221
+ Codex CLI user-level setup:
222
+
223
+ \`\`\`bash
224
+ codex mcp add ${MCP_SERVER_NAME} -- "$(realpath ./node_modules/.bin/embed-ui-v1-endpoint-mcp)"
225
+ codex mcp list
226
+ codex mcp get ${MCP_SERVER_NAME}
227
+ \`\`\`
228
+
229
+ Equivalent \`~/.codex/config.toml\` block:
230
+
231
+ \`\`\`toml
232
+ [mcp_servers.${MCP_SERVER_NAME}]
233
+ command = "/absolute/path/to/project/node_modules/.bin/embed-ui-v1-endpoint-mcp"
234
+ \`\`\`
172
235
 
173
236
  Claude Desktop config paths:
174
237
 
@@ -181,8 +244,7 @@ Config snippet:
181
244
  {
182
245
  "mcpServers": {
183
246
  "${MCP_SERVER_NAME}": {
184
- "command": "npx",
185
- "args": ["-y", "-p", "${PACKAGE_NAME}", "embed-ui-v1-endpoint-mcp"]
247
+ "command": "/absolute/path/to/project/node_modules/.bin/embed-ui-v1-endpoint-mcp"
186
248
  }
187
249
  }
188
250
  }
@@ -271,6 +333,44 @@ const writeJson = (filePath, value, dryRun) => {
271
333
  fs.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}${DEFAULT_NEWLINE}`, 'utf8')
272
334
  }
273
335
 
336
+ const createCodexMcpTomlSection = (serverConfig) => {
337
+ return `[mcp_servers.${MCP_SERVER_NAME}]
338
+ command = "${serverConfig.command.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"
339
+ `
340
+ }
341
+
342
+ const writeCodexMcpServerConfig = (target, options, serverConfig) => {
343
+ const relativePath = MCP_CLIENT_CONFIGS.codex.filePath
344
+ const filePath = path.join(target, relativePath)
345
+ const fileExists = fs.existsSync(filePath)
346
+ const currentContent = fileExists ? fs.readFileSync(filePath, 'utf8') : ''
347
+ const section = createCodexMcpTomlSection(serverConfig)
348
+ const escapedServerName = MCP_SERVER_NAME.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
349
+ const sectionPattern = new RegExp(`(^|\\n)\\[mcp_servers\\.${escapedServerName}\\][\\s\\S]*?(?=\\n\\[|$)`, 'u')
350
+ const hasServerSection = sectionPattern.test(currentContent)
351
+
352
+ if (hasServerSection && !options.force) {
353
+ console.log(`${relativePath} already contains ${MCP_SERVER_NAME}`)
354
+ console.log('Nothing was changed. Re-run with --force to refresh that server entry.')
355
+ return false
356
+ }
357
+
358
+ const nextContent = hasServerSection
359
+ ? currentContent
360
+ .replace(sectionPattern, (match, prefix) => `${prefix}${section.trimEnd()}`)
361
+ .replace(/\s+$/u, '') + DEFAULT_NEWLINE
362
+ : appendSection(currentContent, section)
363
+
364
+ if (!options.dryRun) {
365
+ fs.mkdirSync(path.dirname(filePath), { recursive: true })
366
+ fs.writeFileSync(filePath, nextContent, 'utf8')
367
+ }
368
+
369
+ const action = fileExists ? 'updated' : 'created'
370
+ console.log(`${relativePath} ${options.dryRun ? `would be ${action}` : `was ${action}`}`)
371
+ return true
372
+ }
373
+
274
374
  const initAgents = (target, force) => {
275
375
  if (!fs.existsSync(target)) {
276
376
  throw new Error(`Target path does not exist: ${target}`)
@@ -314,7 +414,7 @@ const initAgents = (target, force) => {
314
414
  console.log(`The ${PACKAGE_NAME} instructions were appended to the end of the file.`)
315
415
  }
316
416
 
317
- const writeMcpServerConfig = (target, relativePath, rootField, options) => {
417
+ const writeMcpServerConfig = (target, relativePath, rootField, options, serverConfig) => {
318
418
  const filePath = path.join(target, relativePath)
319
419
  const fileExists = fs.existsSync(filePath)
320
420
  const config = readJsonObject(filePath)
@@ -323,14 +423,22 @@ const writeMcpServerConfig = (target, relativePath, rootField, options) => {
323
423
  if (servers[MCP_SERVER_NAME] && !options.force) {
324
424
  console.log(`${relativePath} already contains ${MCP_SERVER_NAME}`)
325
425
  console.log('Nothing was changed. Re-run with --force to refresh that server entry.')
326
- return
426
+ return false
327
427
  }
328
428
 
329
- servers[MCP_SERVER_NAME] = MCP_SERVER_CONFIG
429
+ servers[MCP_SERVER_NAME] = serverConfig
330
430
  writeJson(filePath, config, options.dryRun)
331
431
 
332
432
  const action = fileExists ? 'updated' : 'created'
333
433
  console.log(`${relativePath} ${options.dryRun ? `would be ${action}` : `was ${action}`}`)
434
+ return true
435
+ }
436
+
437
+ const printFileClientMcpNotice = (clientConfig, target, serverConfig) => {
438
+ const config = MCP_CLIENT_CONFIGS[clientConfig]
439
+
440
+ printMcpNotice(`${clientConfig} MCP config points to local binary ${serverConfig.command}. Restart or reconnect the client to use new resources.`)
441
+ printMcpNotice(`${clientConfig} config file: ${path.join(target, config.filePath)}`)
334
442
  }
335
443
 
336
444
  const resolveMcpClientConfigs = (tokens) => {
@@ -379,12 +487,30 @@ const initConfig = (target, options) => {
379
487
  }
380
488
 
381
489
  const clientConfigs = resolveMcpClientConfigs(options.mcpClientConfigs)
490
+ const serverConfig = createMcpServerConfig(CLAUDE_PROJECT_MCP_BIN_PATH)
491
+ const absoluteMcpBinPath = resolveLocalMcpBinPath(target)
382
492
 
383
- writeMcpServerConfig(target, '.mcp.json', 'mcpServers', options)
493
+ writeMcpServerConfig(target, '.mcp.json', 'mcpServers', options, serverConfig)
494
+ printMcpNotice(`Project MCP config points to local binary ${serverConfig.command}. Restart or reconnect MCP clients to use new resources.`)
495
+ if (!fs.existsSync(absoluteMcpBinPath)) {
496
+ printMcpNotice(`Local MCP binary is not available yet. Install project dependencies before starting MCP clients: ${absoluteMcpBinPath}`)
497
+ }
384
498
 
385
499
  for (const clientConfig of clientConfigs) {
386
500
  const config = MCP_CLIENT_CONFIGS[clientConfig]
387
- writeMcpServerConfig(target, config.filePath, config.rootField, options)
501
+
502
+ if (config.type === 'codex-file') {
503
+ const codexServerConfig = createMcpServerConfig(config.command)
504
+
505
+ writeCodexMcpServerConfig(target, options, codexServerConfig)
506
+ printMcpNotice(`codex project config points to local binary ${codexServerConfig.command}. Trust the project and restart Codex to use new resources.`)
507
+ continue
508
+ }
509
+
510
+ const clientServerConfig = createMcpServerConfig(config.command, config.config)
511
+
512
+ writeMcpServerConfig(target, config.filePath, config.rootField, options, clientServerConfig)
513
+ printFileClientMcpNotice(clientConfig, target, clientServerConfig)
388
514
  }
389
515
 
390
516
  updateMcpReadmeNotes(target, clientConfigs, options)
@@ -67,6 +67,18 @@ const createEndpointMcpServer = () => {
67
67
  name: "@retailcrm/embed-ui-v1-endpoint",
68
68
  version: readPackageVersion()
69
69
  });
70
+ const compatibilityTool = server.registerTool("mcp-compatibility-noop", {
71
+ description: "Disabled compatibility tool used only to make tools/list return an empty list for clients that probe it."
72
+ }, () => ({
73
+ content: []
74
+ }));
75
+ const compatibilityPrompt = server.registerPrompt("mcp-compatibility-noop", {
76
+ description: "Disabled compatibility prompt used only to make prompts/list return an empty list for clients that probe it."
77
+ }, () => ({
78
+ messages: []
79
+ }));
80
+ compatibilityTool.disable();
81
+ compatibilityPrompt.disable();
70
82
  const targets = readTargetProfiles();
71
83
  server.registerResource("v1-endpoint targets index", "embed-ui-v1-endpoint://targets", {
72
84
  title: "v1-endpoint widget targets index",
@@ -47,6 +47,18 @@ const createEndpointMcpServer = () => {
47
47
  name: "@retailcrm/embed-ui-v1-endpoint",
48
48
  version: readPackageVersion()
49
49
  });
50
+ const compatibilityTool = server.registerTool("mcp-compatibility-noop", {
51
+ description: "Disabled compatibility tool used only to make tools/list return an empty list for clients that probe it."
52
+ }, () => ({
53
+ content: []
54
+ }));
55
+ const compatibilityPrompt = server.registerPrompt("mcp-compatibility-noop", {
56
+ description: "Disabled compatibility prompt used only to make prompts/list return an empty list for clients that probe it."
57
+ }, () => ({
58
+ messages: []
59
+ }));
60
+ compatibilityTool.disable();
61
+ compatibilityPrompt.disable();
50
62
  const targets = readTargetProfiles();
51
63
  server.registerResource("v1-endpoint targets index", "embed-ui-v1-endpoint://targets", {
52
64
  title: "v1-endpoint widget targets index",
package/package.json CHANGED
@@ -1,15 +1,18 @@
1
1
  {
2
2
  "name": "@retailcrm/embed-ui-v1-endpoint",
3
3
  "bin": {
4
- "embed-ui-v1-endpoint": "./bin/embed-ui-v1-endpoint.mjs",
5
- "embed-ui-v1-endpoint-mcp": "./bin/embed-ui-v1-endpoint-mcp.mjs"
4
+ "embed-ui-v1-endpoint": "bin/embed-ui-v1-endpoint.mjs",
5
+ "embed-ui-v1-endpoint-mcp": "bin/embed-ui-v1-endpoint-mcp.mjs"
6
6
  },
7
7
  "type": "module",
8
- "version": "0.9.22-alpha.4",
8
+ "version": "0.9.22-alpha.7",
9
9
  "description": "Endpoint API for integrations in RetailCRM",
10
10
  "license": "MIT",
11
11
  "author": "RetailDriverLLC <integration@retailcrm.ru>",
12
- "repository": "git@github.com:retailcrm/embed-ui.git",
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "git+ssh://git@github.com/retailcrm/embed-ui.git"
15
+ },
13
16
  "exports": {
14
17
  ".": {
15
18
  "types": "./dist/remote.d.ts",
@@ -97,17 +100,17 @@
97
100
  "peerDependencies": {
98
101
  "@omnicajs/vue-remote": "^0.2.23",
99
102
  "@remote-ui/rpc": "^1.4",
100
- "@retailcrm/embed-ui-v1-contexts": "^0.9.22-alpha.4",
101
- "@retailcrm/embed-ui-v1-types": "^0.9.22-alpha.4",
103
+ "@retailcrm/embed-ui-v1-contexts": "^0.9.22-alpha.7",
104
+ "@retailcrm/embed-ui-v1-types": "^0.9.22-alpha.7",
102
105
  "pinia": "^2.2",
103
106
  "vue": "^3.5"
104
107
  },
105
108
  "dependencies": {
106
109
  "@modelcontextprotocol/sdk": "^1.29.0",
107
110
  "@remote-ui/rpc": "^1.4.7",
108
- "@retailcrm/embed-ui-v1-components": "^0.9.22-alpha.4",
109
- "@retailcrm/embed-ui-v1-contexts": "^0.9.22-alpha.4",
110
- "@retailcrm/embed-ui-v1-types": "^0.9.22-alpha.4"
111
+ "@retailcrm/embed-ui-v1-components": "^0.9.22-alpha.7",
112
+ "@retailcrm/embed-ui-v1-contexts": "^0.9.22-alpha.7",
113
+ "@retailcrm/embed-ui-v1-types": "^0.9.22-alpha.7"
111
114
  },
112
115
  "devDependencies": {
113
116
  "@retailcrm/image-preview": "^1.0.2",