@besales/mcp 0.1.0 → 0.11.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/README.md +272 -17
- package/dist/auth/connection-store.d.ts +58 -0
- package/dist/auth/connection-store.js +208 -0
- package/dist/auth/connection-store.js.map +1 -0
- package/dist/auth/oauth-client.d.ts +27 -2
- package/dist/auth/oauth-client.js +62 -11
- package/dist/auth/oauth-client.js.map +1 -1
- package/dist/auth/session-workspace.d.ts +2 -0
- package/dist/auth/session-workspace.js +20 -0
- package/dist/auth/session-workspace.js.map +1 -0
- package/dist/auth/token-storage.d.ts +19 -5
- package/dist/auth/token-storage.js +11 -6
- package/dist/auth/token-storage.js.map +1 -1
- package/dist/cli.d.ts +2 -7
- package/dist/cli.js +111 -33
- package/dist/cli.js.map +1 -1
- package/dist/http/api-client.d.ts +4 -13
- package/dist/http/api-client.js +18 -18
- package/dist/http/api-client.js.map +1 -1
- package/dist/index.d.ts +8 -6
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/instructions/server-instructions.d.ts +15 -0
- package/dist/instructions/server-instructions.js +243 -0
- package/dist/instructions/server-instructions.js.map +1 -0
- package/dist/package-metadata.js +7 -1
- package/dist/package-metadata.js.map +1 -1
- package/dist/resources/concepts/feedback-sheets.md +77 -0
- package/dist/resources/concepts/sandbox.md +13 -0
- package/dist/resources/concepts/workbook-classification.md +241 -0
- package/dist/resources/docs/agent-behavior.md +393 -0
- package/dist/resources/docs/crm-integration.md +535 -0
- package/dist/resources/docs/files-and-uploads.md +295 -0
- package/dist/resources/docs/knowledge-base.md +521 -0
- package/dist/resources/docs/pipeline-builder.md +221 -0
- package/dist/resources/docs/pipeline-settings-deep.md +221 -0
- package/dist/resources/docs/platforms.md +513 -0
- package/dist/resources/docs/prompt-anatomy.md +298 -0
- package/dist/resources/docs/prompt-principles.md +289 -0
- package/dist/resources/registry.js +34 -12
- package/dist/resources/registry.js.map +1 -1
- package/dist/resources/workflows/compare-models.md +46 -0
- package/dist/resources/workflows/connect-crm-from-scratch.md +89 -0
- package/dist/resources/workflows/connect-datasource-from-scratch.md +92 -0
- package/dist/resources/workflows/extract-from-document.md +36 -0
- package/dist/resources/workflows/iterate-with-sandbox.md +31 -0
- package/dist/resources/workflows/platform-setup-from-scratch.md +113 -0
- package/dist/resources/workflows/production-readiness-check.md +41 -0
- package/dist/schemas/mcp-tools.json +2636 -182
- package/dist/server.js +2 -0
- package/dist/server.js.map +1 -1
- package/dist/tools/definitions/agent-design.d.ts +215 -0
- package/dist/tools/definitions/agent-design.js +643 -0
- package/dist/tools/definitions/agent-design.js.map +1 -0
- package/dist/tools/definitions/crm-platform.d.ts +211 -0
- package/dist/tools/definitions/crm-platform.js +1070 -0
- package/dist/tools/definitions/crm-platform.js.map +1 -0
- package/dist/tools/definitions/datasource.d.ts +40 -0
- package/dist/tools/definitions/datasource.js +196 -0
- package/dist/tools/definitions/datasource.js.map +1 -0
- package/dist/tools/definitions/knowledge.d.ts +215 -0
- package/dist/tools/definitions/knowledge.js +782 -0
- package/dist/tools/definitions/knowledge.js.map +1 -0
- package/dist/tools/definitions/model-comparison.d.ts +25 -0
- package/dist/tools/definitions/model-comparison.js +101 -0
- package/dist/tools/definitions/model-comparison.js.map +1 -0
- package/dist/tools/definitions/platform-setup.d.ts +412 -0
- package/dist/tools/definitions/platform-setup.js +738 -0
- package/dist/tools/definitions/platform-setup.js.map +1 -0
- package/dist/tools/definitions/session.d.ts +11 -0
- package/dist/tools/definitions/session.js +86 -0
- package/dist/tools/definitions/session.js.map +1 -0
- package/dist/tools/definitions/shared.d.ts +742 -0
- package/dist/tools/definitions/shared.js +773 -0
- package/dist/tools/definitions/shared.js.map +1 -0
- package/dist/tools/definitions.d.ts +873 -88
- package/dist/tools/definitions.js +14 -856
- package/dist/tools/definitions.js.map +1 -1
- package/dist/tools/registry.d.ts +3 -1
- package/dist/tools/registry.js +90 -11
- package/dist/tools/registry.js.map +1 -1
- package/dist/tools/result.d.ts +1 -1
- package/dist/tools/result.js +12 -4
- package/dist/tools/result.js.map +1 -1
- package/dist/utils/logger.js +2 -1
- package/dist/utils/logger.js.map +1 -1
- package/docs/host-setup.md +34 -15
- package/package.json +2 -2
- package/scripts/install-claude-desktop.js +89 -11
- package/scripts/mock-api-server.js +1 -1
- package/scripts/mock-credentials.js +49 -6
- package/dist/types/api-contract.gen.d.ts +0 -6975
- package/dist/types/api-contract.gen.js +0 -6
- package/dist/types/api-contract.gen.js.map +0 -1
package/README.md
CHANGED
|
@@ -6,7 +6,13 @@ Status: public npm package candidate.
|
|
|
6
6
|
|
|
7
7
|
## Install
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
Requirements:
|
|
10
|
+
|
|
11
|
+
- Node.js 20+
|
|
12
|
+
- BeSales account with an active workspace
|
|
13
|
+
- Claude Desktop, Claude Code, Codex CLI, or another MCP-capable host
|
|
14
|
+
|
|
15
|
+
No global install is required. Production users can run the package with `npx`:
|
|
10
16
|
|
|
11
17
|
```bash
|
|
12
18
|
npx -y @besales/mcp connect
|
|
@@ -14,6 +20,189 @@ npx -y @besales/mcp status
|
|
|
14
20
|
npx -y @besales/mcp
|
|
15
21
|
```
|
|
16
22
|
|
|
23
|
+
`connect` opens the browser consent flow and stores an MCP API key plus the
|
|
24
|
+
selected workspace in the local keychain. Workspace-level tools use that
|
|
25
|
+
connected workspace by default, so normal users should not provide
|
|
26
|
+
`workspace_id` manually.
|
|
27
|
+
|
|
28
|
+
For production, do not set `BESALES_API_BASE_URL` or
|
|
29
|
+
`BESALES_OAUTH_AUTHORIZE_URL`. They default to:
|
|
30
|
+
|
|
31
|
+
- `BESALES_API_BASE_URL` → `https://core.besales.ai/api/v2` (backend API host —
|
|
32
|
+
used for MCP tool calls)
|
|
33
|
+
- `BESALES_OAUTH_AUTHORIZE_URL` →
|
|
34
|
+
`https://app.besales.ai/settings/mcp/consent` (frontend bridge page — opened
|
|
35
|
+
in the browser during `connect`; it mints a one-time setup ticket and forwards
|
|
36
|
+
to the backend consent page. After approval `connect` receives a one-time `code`
|
|
37
|
+
on the loopback callback and exchanges it with the PKCE `code_verifier` at
|
|
38
|
+
`/api/v2/auth/mcp-token` — the API key never travels in a URL)
|
|
39
|
+
|
|
40
|
+
For staging or local development override them per command:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
BESALES_API_BASE_URL=http://localhost:3000/api/v2 \
|
|
44
|
+
BESALES_OAUTH_AUTHORIZE_URL=http://localhost:5173/settings/mcp/consent \
|
|
45
|
+
npx -y @besales/mcp connect
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Configure Your MCP Host
|
|
49
|
+
|
|
50
|
+
After `connect`, add the server to your MCP host and restart the host.
|
|
51
|
+
|
|
52
|
+
Claude Desktop config:
|
|
53
|
+
|
|
54
|
+
```json
|
|
55
|
+
{
|
|
56
|
+
"mcpServers": {
|
|
57
|
+
"besales": {
|
|
58
|
+
"command": "npx",
|
|
59
|
+
"args": ["-y", "@besales/mcp"]
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Claude Code:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
claude mcp add --scope user besales -- npx -y @besales/mcp
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Codex CLI:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
codex mcp add besales -- npx -y @besales/mcp
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Equivalent Codex config:
|
|
78
|
+
|
|
79
|
+
```toml
|
|
80
|
+
[mcp_servers.besales]
|
|
81
|
+
command = "npx"
|
|
82
|
+
args = ["-y", "@besales/mcp"]
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
For staging/local backends, include both backend and frontend bridge URLs in the
|
|
86
|
+
host env. For example:
|
|
87
|
+
|
|
88
|
+
```toml
|
|
89
|
+
[mcp_servers.besales.env]
|
|
90
|
+
BESALES_API_BASE_URL = "http://localhost:3000/api/v2"
|
|
91
|
+
BESALES_OAUTH_AUTHORIZE_URL = "http://localhost:5173/settings/mcp/consent"
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Multiple Workspaces
|
|
95
|
+
|
|
96
|
+
`besales-mcp` can hold keys for several workspaces at once — even across
|
|
97
|
+
different accounts. Connect each one **once** (switch the account/workspace in
|
|
98
|
+
the browser before each `connect`); keys are cached in your OS keychain and
|
|
99
|
+
switching afterwards needs no browser.
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
npx -y @besales/mcp connect # workspace A (browser logged into account A)
|
|
103
|
+
npx -y @besales/mcp connect # workspace B (re-login in the browser first)
|
|
104
|
+
npx -y @besales/mcp connections # list all connections, marks the active one
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Bind a host session to a specific workspace with `BESALES_WORKSPACE_ID` in that
|
|
108
|
+
server's `env` block — the cleanest "one session = one workspace" setup (one
|
|
109
|
+
project/profile per workspace):
|
|
110
|
+
|
|
111
|
+
```json
|
|
112
|
+
{
|
|
113
|
+
"mcpServers": {
|
|
114
|
+
"besales": {
|
|
115
|
+
"command": "npx",
|
|
116
|
+
"args": ["-y", "@besales/mcp"],
|
|
117
|
+
"env": { "BESALES_WORKSPACE_ID": "<workspace-uuid>" }
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
```toml
|
|
124
|
+
[mcp_servers.besales.env]
|
|
125
|
+
BESALES_WORKSPACE_ID = "<workspace-uuid>"
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Workspace selection precedence, per tool call:
|
|
129
|
+
|
|
130
|
+
1. `BESALES_WORKSPACE_ID` (env) — authoritative; an explicit, mismatching
|
|
131
|
+
`workspace_id` argument is refused.
|
|
132
|
+
2. an explicit `workspace_id` argument (only when the env var is not set).
|
|
133
|
+
3. the session binding set by the `besales_workspace_use` tool (in-memory, this
|
|
134
|
+
session/process only — see below).
|
|
135
|
+
4. the global active workspace set by `besales-mcp use <id>`.
|
|
136
|
+
5. the only connected workspace (error if several are connected and none chosen).
|
|
137
|
+
|
|
138
|
+
`active` is global across all env-less sessions, and `BESALES_WORKSPACE_ID`
|
|
139
|
+
always overrides it. Get workspace ids from `besales-mcp connections`.
|
|
140
|
+
|
|
141
|
+
### GUI hosts without per-session env (e.g. Claude Desktop)
|
|
142
|
+
|
|
143
|
+
When the host has no place to set `BESALES_WORKSPACE_ID` per session (you just
|
|
144
|
+
click "new session"), there are two ways to keep several workspaces apart.
|
|
145
|
+
|
|
146
|
+
**Recommended — one pinned named server per workspace (survives reconnects).**
|
|
147
|
+
Run once per workspace:
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
yarn install:claude-desktop --name besales-acme --workspace <uuid-A>
|
|
151
|
+
yarn install:claude-desktop --name besales-globex --workspace <uuid-B>
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
This adds env-pinned `mcpServers` entries (with a timestamped config backup);
|
|
155
|
+
restart Claude Desktop. Their tools appear namespaced —
|
|
156
|
+
`mcp__besales-acme__besales_*` (always workspace A) and
|
|
157
|
+
`mcp__besales-globex__besales_*` (always B). Because the pin lives in the host
|
|
158
|
+
config, it **survives MCP reconnects** and **never silently drifts** to the wrong
|
|
159
|
+
workspace. Both toolsets are visible in every session; you just address the one
|
|
160
|
+
you mean.
|
|
161
|
+
|
|
162
|
+
**Quick / in-session — `besales_workspace_use` (does NOT survive a reconnect).**
|
|
163
|
+
Ask the assistant to call **`besales_workspace_use <workspaceId>`** to bind the
|
|
164
|
+
current session in-memory. Each host session runs its own `besales-mcp` process,
|
|
165
|
+
so it is per-session and touches neither the global `active` nor other sessions.
|
|
166
|
+
Caveat: the binding is **in-memory only** — when the host respawns the server
|
|
167
|
+
(reconnect, which GUI hosts do often), it is **lost and falls back to the global
|
|
168
|
+
`active`**. Use it for a stable stretch, not as a durable lock; for that use the
|
|
169
|
+
pinned named servers above. `besales_workspace_current` shows the current binding
|
|
170
|
+
+ source and lists connected workspaces; it is **refused** under a
|
|
171
|
+
`BESALES_WORKSPACE_ID` pin (env wins).
|
|
172
|
+
|
|
173
|
+
- `BESALES_WORKSPACE_ID` is read **once at process start** — exporting it after a
|
|
174
|
+
session is already open has no effect. Restart the session to re-pin via env.
|
|
175
|
+
|
|
176
|
+
## First Smoke
|
|
177
|
+
|
|
178
|
+
Start a fresh host session after changing MCP config, then ask a natural
|
|
179
|
+
business request:
|
|
180
|
+
|
|
181
|
+
```text
|
|
182
|
+
Use besales MCP to create an ICP for SaaS B2B sales automation.
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
The assistant should discover and call the relevant `besales_*` tools itself.
|
|
186
|
+
Users should not mention `tool_search` and should not be asked for a
|
|
187
|
+
`workspace_id`.
|
|
188
|
+
|
|
189
|
+
Expected quick checks:
|
|
190
|
+
|
|
191
|
+
1. `npx -y @besales/mcp status` shows connected credentials.
|
|
192
|
+
2. The host shows the `besales` MCP server as connected.
|
|
193
|
+
3. Direct tools such as `besales_icp_create` work.
|
|
194
|
+
4. External flows use `*_get_instructions`, execute stages locally in the host,
|
|
195
|
+
then call the matching `*_submit` tool.
|
|
196
|
+
|
|
197
|
+
To switch workspace, connect each one once (see [Multiple Workspaces](#multiple-workspaces))
|
|
198
|
+
and either set `BESALES_WORKSPACE_ID` per host session or run
|
|
199
|
+
`npx -y @besales/mcp use <workspaceId>`. No browser re-auth is needed once a
|
|
200
|
+
workspace is connected.
|
|
201
|
+
|
|
202
|
+
If the host still asks for `workspace_id`, it is usually using an old cached
|
|
203
|
+
tool schema or an old package version. Restart the host/session and use
|
|
204
|
+
`@besales/mcp@latest` in the host args if needed.
|
|
205
|
+
|
|
17
206
|
## Development
|
|
18
207
|
|
|
19
208
|
```bash
|
|
@@ -56,40 +245,98 @@ yarn dev:mock:clear
|
|
|
56
245
|
## CLI
|
|
57
246
|
|
|
58
247
|
```bash
|
|
59
|
-
node bin/besales-mcp.js connect
|
|
60
|
-
node bin/besales-mcp.js
|
|
61
|
-
node bin/besales-mcp.js
|
|
62
|
-
node bin/besales-mcp.js
|
|
248
|
+
node bin/besales-mcp.js connect # connect (or refresh) a workspace
|
|
249
|
+
node bin/besales-mcp.js connections # list connected workspaces (* = active)
|
|
250
|
+
node bin/besales-mcp.js use <workspaceId> # set the active (default) workspace
|
|
251
|
+
node bin/besales-mcp.js status # show the active workspace
|
|
252
|
+
node bin/besales-mcp.js disconnect # disconnect the active workspace only
|
|
253
|
+
node bin/besales-mcp.js disconnect <id> # disconnect one workspace
|
|
254
|
+
node bin/besales-mcp.js disconnect --all # disconnect every workspace
|
|
255
|
+
node bin/besales-mcp.js # start the MCP server
|
|
63
256
|
```
|
|
64
257
|
|
|
65
|
-
`BESALES_API_BASE_URL` defaults to `https://
|
|
66
|
-
|
|
258
|
+
`BESALES_API_BASE_URL` defaults to `https://core.besales.ai/api/v2` (backend).
|
|
259
|
+
`BESALES_OAUTH_AUTHORIZE_URL` defaults to
|
|
260
|
+
`https://app.besales.ai/settings/mcp/consent` (frontend bridge). For local
|
|
261
|
+
development point them at your local backend and frontend respectively.
|
|
67
262
|
|
|
68
263
|
Tool calls require stored credentials. Run `node bin/besales-mcp.js connect`
|
|
69
|
-
against a backend that implements
|
|
264
|
+
against a backend that implements the MCP OAuth endpoints
|
|
265
|
+
(`/api/v2/auth/mcp-connect*` + `/api/v2/auth/mcp-token`), or run
|
|
70
266
|
`yarn dev:mock:seed` for mock-only smoke tests.
|
|
71
267
|
|
|
72
|
-
For production users, do not set `BESALES_API_BASE_URL
|
|
268
|
+
For production users, do not set `BESALES_API_BASE_URL` or
|
|
269
|
+
`BESALES_OAUTH_AUTHORIZE_URL`. Run:
|
|
73
270
|
|
|
74
271
|
```bash
|
|
75
272
|
npx -y @besales/mcp connect
|
|
76
273
|
```
|
|
77
274
|
|
|
78
|
-
The browser consent flow stores the
|
|
79
|
-
Workspace-level tools default to
|
|
80
|
-
provide `workspace_id` manually. To
|
|
81
|
-
|
|
82
|
-
|
|
275
|
+
The browser consent flow stores the connected workspace key in the local
|
|
276
|
+
keychain. Workspace-level tools default to the session's workspace, so users
|
|
277
|
+
should not provide `workspace_id` manually. To work with several workspaces,
|
|
278
|
+
connect each once and pin sessions via `BESALES_WORKSPACE_ID` (or switch the
|
|
279
|
+
global default with `besales-mcp use <id>`) — see
|
|
280
|
+
[Multiple Workspaces](#multiple-workspaces).
|
|
281
|
+
|
|
282
|
+
## Available tools (128 tools by category)
|
|
283
|
+
|
|
284
|
+
Counts are derived from `src/schemas/mcp-tools.json` (`summary.tools_by_category`)
|
|
285
|
+
and asserted in `src/tools/registry.spec.ts`:
|
|
286
|
+
|
|
287
|
+
| Category | Tools | Notes |
|
|
288
|
+
|---|---|---|
|
|
289
|
+
| ICP (profiles, dialogue import, analysis) | 5 | incl. `besales_icp_analysis_*` external pair |
|
|
290
|
+
| ICP — read (profile / segments / scenarios / samples) | 4 | `besales_icp_list` / `_get` / `_scenarios_list` / `_sample_messages` (read-only over existing GET `/api/v2/icp/*`) |
|
|
291
|
+
| Prompt generation / analysis / lint / patch sessions | 13 | `besales_prompt_*`, `besales_qa_generation_*` external pairs |
|
|
292
|
+
| Sandbox / simulation / test-scenario generation | 7 | `besales_sandbox_*`, `besales_simulation_*`, `besales_test_scenario_*` |
|
|
293
|
+
| Model comparison (bake-off) | 4 | `besales_model_catalog_list` + `besales_model_comparison_run_start` / `_get` / `_eval_submit` (on-demand multi-model prompt comparison) |
|
|
294
|
+
| Agent / router / triggers / behavior / facts | 13 | Platform Setup Bridge (incl. `besales_behavior_update`, `besales_pipeline_settings_update`, `besales_platform_settings_update`) + `besales_facts_*` |
|
|
295
|
+
| Knowledge — spaces | 6 | `besales_knowledge_space_*` |
|
|
296
|
+
| Knowledge — documents | 4 | incl. `besales_knowledge_document_upload` |
|
|
297
|
+
| Knowledge — Q&A | 7 | CRUD + semantic search + move |
|
|
298
|
+
| Knowledge — websites | 4 | crawl + Pinecone upsert |
|
|
299
|
+
| Knowledge — tables | 6 | Google Sheets link + reindex |
|
|
300
|
+
| Workbook ingestion (xlsx / Google Sheets) | 3 | `besales_workbook_*` |
|
|
301
|
+
| Feedback sheets (Google Sheets QA) | 5 | `besales_feedback_sheet_*` |
|
|
302
|
+
| Variables (unified) | 5 | `besales_variable_*` |
|
|
303
|
+
| CRM — pipelines, statuses, platform link | 11 | `besales_crm_pipeline_*`, `besales_platform_pipeline_*` |
|
|
304
|
+
| CRM — connection / field mapping / operators / activate | 7 | `besales_crm_create_*` (AmoCRM/Bitrix24 via browser-secrets), `besales_crm_field_mapping_*`, `besales_crm_operator_*`, `besales_crm_activate` (live token check + activate, deactivates other workspace CRMs) |
|
|
305
|
+
| Platform — provisioning / OAuth | 3 | `besales_platform_create_init`, `besales_platform_create_status`, `besales_platform_create_oauth_init` (Instagram/Avito) |
|
|
306
|
+
| Platform — clone | 3 | `besales_platform_clone_*` |
|
|
307
|
+
| Data sources (1С, категория «БД») | 9 | `besales_datasource_create_*` (Setup URL pattern), `_list` / `_health` / `_catalogs` / `_catalog_roles` / `_role_assign` / `_enable` / `_vectorize`. 1С — товарная БД через OData; чтение/запись — действия триггеров, не tools агента |
|
|
308
|
+
| Files | 3 | `besales_file_upload_request` + status + list |
|
|
309
|
+
| Context / overview / audit | 3 | `besales_workspace_overview`, `besales_platform_context_get`, `besales_audit_revert` |
|
|
310
|
+
| Session / workspace binding | 2 | `besales_workspace_use`, `besales_workspace_current` (in-memory per-session, local — no backend) |
|
|
311
|
+
|
|
312
|
+
Recent capabilities:
|
|
313
|
+
|
|
314
|
+
- **OAuth PKCE provisioning** — `besales_platform_create_oauth_init` opens the
|
|
315
|
+
authorization flow in the browser for Instagram/Avito so the OAuth code never
|
|
316
|
+
passes through the LLM.
|
|
317
|
+
- **CRM connection via browser-secrets** — `besales_crm_create_init` provisions
|
|
318
|
+
AmoCRM/Bitrix24 (and GetCourse/Telegram) using the Setup URL pattern, where the
|
|
319
|
+
user pastes credentials in the browser instead of through the chat.
|
|
320
|
+
- **Setup URL pattern** — `*_init` returns a one-time URL; the user completes
|
|
321
|
+
sensitive input in the browser and the host polls `*_status` until ready.
|
|
322
|
+
- **File upload** — two-step flow (`besales_file_upload_request` →
|
|
323
|
+
`besales_file_upload_status`) so binaries are uploaded directly, not via MCP.
|
|
324
|
+
- **Platform provisioning & clone** — create channels and clone an existing
|
|
325
|
+
platform configuration (`besales_platform_clone_preview` / `_execute`).
|
|
326
|
+
- **Knowledge ingestion** — full namespace/document/Q&A/website/table CRUD plus
|
|
327
|
+
workbook ingestion from xlsx and Google Sheets.
|
|
83
328
|
|
|
84
329
|
## Resources
|
|
85
330
|
|
|
86
|
-
The package exposes
|
|
331
|
+
The package exposes 7 MCP concept resources:
|
|
87
332
|
|
|
88
333
|
- `besales://concepts/icp`
|
|
89
334
|
- `besales://concepts/triggers`
|
|
90
335
|
- `besales://concepts/sandbox`
|
|
91
336
|
- `besales://concepts/handoff`
|
|
92
337
|
- `besales://concepts/external-execution`
|
|
338
|
+
- `besales://concepts/feedback-sheets`
|
|
339
|
+
- `besales://concepts/workbook-classification`
|
|
93
340
|
|
|
94
341
|
Inspect them locally:
|
|
95
342
|
|
|
@@ -97,7 +344,7 @@ Inspect them locally:
|
|
|
97
344
|
yarn dlx @modelcontextprotocol/inspector node bin/besales-mcp.js
|
|
98
345
|
```
|
|
99
346
|
|
|
100
|
-
The Inspector should show
|
|
347
|
+
The Inspector should show 128 tools and 23 resources.
|
|
101
348
|
|
|
102
349
|
## Claude Desktop
|
|
103
350
|
|
|
@@ -124,4 +371,12 @@ Claude Code, and Codex CLI are documented in
|
|
|
124
371
|
- This package calls Animaly only through `ai-aniomaly` `/api/v2/*`.
|
|
125
372
|
- Direct calls to `prompt-services` are intentionally out of scope.
|
|
126
373
|
|
|
127
|
-
The package exposes
|
|
374
|
+
The package exposes 128 active MCP tools from `src/schemas/mcp-tools.json`.
|
|
375
|
+
|
|
376
|
+
### Token scopes
|
|
377
|
+
|
|
378
|
+
Read and PromptHub/ICP/sandbox operations are covered by the composite `mcp:*`
|
|
379
|
+
scope granted during `connect`. Setup mutations (knowledge ingestion, variables,
|
|
380
|
+
CRM pipelines, platform settings/provisioning, clone, the Platform Setup Bridge,
|
|
381
|
+
etc.) additionally require the `mcp:platform-setup` scope — a plain `mcp:*` key
|
|
382
|
+
on its own does not authorize setup mutations.
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { type CredentialStore } from './token-storage.js';
|
|
2
|
+
export declare const INDEX_ACCOUNT = "index";
|
|
3
|
+
export declare const ACTIVE_ACCOUNT = "active";
|
|
4
|
+
/** Имя keychain-account'а, хранящего api_key конкретного воркспейса. */
|
|
5
|
+
export declare function keyAccount(workspaceId: string): string;
|
|
6
|
+
/** Метаданные подключения (без секрета) — то, что лежит в `index`. */
|
|
7
|
+
export interface ConnectionMeta {
|
|
8
|
+
workspaceId: string;
|
|
9
|
+
keyPrefix: string;
|
|
10
|
+
connectedAt: string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Ошибка резолва воркспейса: не подключён / неоднозначно / env-конфликт / нет ключа.
|
|
14
|
+
* Несёт КОНКРЕТНОЕ сообщение для агента и мапится в `result.ts` ДО `ApiClientError`,
|
|
15
|
+
* иначе схлопнулась бы в generic «Not connected. Run `besales-mcp connect`».
|
|
16
|
+
*/
|
|
17
|
+
export declare class WorkspaceResolutionError extends Error {
|
|
18
|
+
constructor(message: string);
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Мульти-воркспейс хранилище поверх keychain: несколько ключей (по одному на
|
|
22
|
+
* воркспейс), индекс метаданных без секретов и указатель `active`. Источник
|
|
23
|
+
* выбора воркспейса для сессии — см. resolveWorkspace() в tools/registry.ts.
|
|
24
|
+
*/
|
|
25
|
+
export declare class ConnectionStore {
|
|
26
|
+
private readonly storage;
|
|
27
|
+
private legacyMigrationPromise?;
|
|
28
|
+
constructor(storage?: CredentialStore);
|
|
29
|
+
/**
|
|
30
|
+
* Одноразовая миграция legacy-пары (`api_key` + `workspace_id`) в новую схему.
|
|
31
|
+
* Мемоизирована per-instance; promise сбрасывается при ошибке, чтобы не залипнуть
|
|
32
|
+
* на rejected состоянии и повторить при следующем вызове.
|
|
33
|
+
*/
|
|
34
|
+
migrateLegacy(): Promise<void>;
|
|
35
|
+
list(): Promise<ConnectionMeta[]>;
|
|
36
|
+
getKey(workspaceId: string): Promise<string | null>;
|
|
37
|
+
getActive(): Promise<string | null>;
|
|
38
|
+
setActive(workspaceId: string): Promise<void>;
|
|
39
|
+
/**
|
|
40
|
+
* Добавить/обновить подключение воркспейса. Атомарно: при сбое любой записи
|
|
41
|
+
* восстанавливает прежние key/index/active (refresh того же воркспейса не теряет
|
|
42
|
+
* рабочий ключ). Первое подключение становится `active`.
|
|
43
|
+
*/
|
|
44
|
+
addOrReplace(workspaceId: string, apiKey: string): Promise<void>;
|
|
45
|
+
remove(workspaceId: string): Promise<void>;
|
|
46
|
+
/**
|
|
47
|
+
* Полная очистка (best-effort): удаляет ВСЕ `key:*` (включая osиротевшие после
|
|
48
|
+
* краша между записью ключа и индекса), `index`, `active` и legacy-пару. Каждое
|
|
49
|
+
* удаление независимо (Promise.allSettled) — частичный сбой не оставляет
|
|
50
|
+
* неудалённый секрет молча, а поднимает ошибку.
|
|
51
|
+
*/
|
|
52
|
+
clear(): Promise<void>;
|
|
53
|
+
private runLegacyMigration;
|
|
54
|
+
private readIndex;
|
|
55
|
+
private writeIndex;
|
|
56
|
+
private restore;
|
|
57
|
+
private restoreAccount;
|
|
58
|
+
}
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { logger } from '../utils/logger.js';
|
|
2
|
+
import { maskApiKey } from '../utils/mask.js';
|
|
3
|
+
import { LEGACY_API_KEY_ACCOUNT, LEGACY_WORKSPACE_ID_ACCOUNT, TokenStorage, } from './token-storage.js';
|
|
4
|
+
export const INDEX_ACCOUNT = 'index';
|
|
5
|
+
export const ACTIVE_ACCOUNT = 'active';
|
|
6
|
+
const KEY_ACCOUNT_PREFIX = 'key:';
|
|
7
|
+
/** Имя keychain-account'а, хранящего api_key конкретного воркспейса. */
|
|
8
|
+
export function keyAccount(workspaceId) {
|
|
9
|
+
return `${KEY_ACCOUNT_PREFIX}${workspaceId}`;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Ошибка резолва воркспейса: не подключён / неоднозначно / env-конфликт / нет ключа.
|
|
13
|
+
* Несёт КОНКРЕТНОЕ сообщение для агента и мапится в `result.ts` ДО `ApiClientError`,
|
|
14
|
+
* иначе схлопнулась бы в generic «Not connected. Run `besales-mcp connect`».
|
|
15
|
+
*/
|
|
16
|
+
export class WorkspaceResolutionError extends Error {
|
|
17
|
+
constructor(message) {
|
|
18
|
+
super(message);
|
|
19
|
+
this.name = 'WorkspaceResolutionError';
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Мульти-воркспейс хранилище поверх keychain: несколько ключей (по одному на
|
|
24
|
+
* воркспейс), индекс метаданных без секретов и указатель `active`. Источник
|
|
25
|
+
* выбора воркспейса для сессии — см. resolveWorkspace() в tools/registry.ts.
|
|
26
|
+
*/
|
|
27
|
+
export class ConnectionStore {
|
|
28
|
+
storage;
|
|
29
|
+
legacyMigrationPromise;
|
|
30
|
+
constructor(storage = new TokenStorage()) {
|
|
31
|
+
this.storage = storage;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Одноразовая миграция legacy-пары (`api_key` + `workspace_id`) в новую схему.
|
|
35
|
+
* Мемоизирована per-instance; promise сбрасывается при ошибке, чтобы не залипнуть
|
|
36
|
+
* на rejected состоянии и повторить при следующем вызове.
|
|
37
|
+
*/
|
|
38
|
+
async migrateLegacy() {
|
|
39
|
+
if (!this.legacyMigrationPromise) {
|
|
40
|
+
this.legacyMigrationPromise = this.runLegacyMigration().catch((error) => {
|
|
41
|
+
this.legacyMigrationPromise = undefined;
|
|
42
|
+
throw error;
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
return this.legacyMigrationPromise;
|
|
46
|
+
}
|
|
47
|
+
async list() {
|
|
48
|
+
return this.readIndex();
|
|
49
|
+
}
|
|
50
|
+
async getKey(workspaceId) {
|
|
51
|
+
return this.storage.get(keyAccount(workspaceId));
|
|
52
|
+
}
|
|
53
|
+
async getActive() {
|
|
54
|
+
return this.storage.get(ACTIVE_ACCOUNT);
|
|
55
|
+
}
|
|
56
|
+
async setActive(workspaceId) {
|
|
57
|
+
const index = await this.readIndex();
|
|
58
|
+
if (!index.some((connection) => connection.workspaceId === workspaceId)) {
|
|
59
|
+
throw new WorkspaceResolutionError(`Workspace ${workspaceId} is not connected. Run \`besales-mcp connect\` first.`);
|
|
60
|
+
}
|
|
61
|
+
await this.storage.set(ACTIVE_ACCOUNT, workspaceId);
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Добавить/обновить подключение воркспейса. Атомарно: при сбое любой записи
|
|
65
|
+
* восстанавливает прежние key/index/active (refresh того же воркспейса не теряет
|
|
66
|
+
* рабочий ключ). Первое подключение становится `active`.
|
|
67
|
+
*/
|
|
68
|
+
async addOrReplace(workspaceId, apiKey) {
|
|
69
|
+
const [prevKey, prevIndexRaw, prevActive] = await Promise.all([
|
|
70
|
+
this.storage.get(keyAccount(workspaceId)),
|
|
71
|
+
this.storage.get(INDEX_ACCOUNT),
|
|
72
|
+
this.storage.get(ACTIVE_ACCOUNT),
|
|
73
|
+
]);
|
|
74
|
+
try {
|
|
75
|
+
await this.storage.set(keyAccount(workspaceId), apiKey);
|
|
76
|
+
const index = await this.readIndex();
|
|
77
|
+
const meta = {
|
|
78
|
+
workspaceId,
|
|
79
|
+
keyPrefix: maskApiKey(apiKey),
|
|
80
|
+
connectedAt: new Date().toISOString(),
|
|
81
|
+
};
|
|
82
|
+
const next = [
|
|
83
|
+
...index.filter((connection) => connection.workspaceId !== workspaceId),
|
|
84
|
+
meta,
|
|
85
|
+
];
|
|
86
|
+
await this.writeIndex(next);
|
|
87
|
+
if (!prevActive) {
|
|
88
|
+
await this.storage.set(ACTIVE_ACCOUNT, workspaceId);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
await this.restore(workspaceId, prevKey, prevIndexRaw, prevActive);
|
|
93
|
+
throw error;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
async remove(workspaceId) {
|
|
97
|
+
// index пишем ДО удаления ключа: при сбое останется orphan-ключ (его подметёт
|
|
98
|
+
// clear()), а не висячая запись index без ключа.
|
|
99
|
+
const index = await this.readIndex();
|
|
100
|
+
await this.writeIndex(index.filter((connection) => connection.workspaceId !== workspaceId));
|
|
101
|
+
await this.storage.delete(keyAccount(workspaceId));
|
|
102
|
+
const active = await this.storage.get(ACTIVE_ACCOUNT);
|
|
103
|
+
if (active === workspaceId) {
|
|
104
|
+
await this.storage.delete(ACTIVE_ACCOUNT);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Полная очистка (best-effort): удаляет ВСЕ `key:*` (включая osиротевшие после
|
|
109
|
+
* краша между записью ключа и индекса), `index`, `active` и legacy-пару. Каждое
|
|
110
|
+
* удаление независимо (Promise.allSettled) — частичный сбой не оставляет
|
|
111
|
+
* неудалённый секрет молча, а поднимает ошибку.
|
|
112
|
+
*/
|
|
113
|
+
async clear() {
|
|
114
|
+
const targets = new Set([
|
|
115
|
+
INDEX_ACCOUNT,
|
|
116
|
+
ACTIVE_ACCOUNT,
|
|
117
|
+
LEGACY_API_KEY_ACCOUNT,
|
|
118
|
+
LEGACY_WORKSPACE_ID_ACCOUNT,
|
|
119
|
+
]);
|
|
120
|
+
for (const connection of await this.readIndex()) {
|
|
121
|
+
targets.add(keyAccount(connection.workspaceId));
|
|
122
|
+
}
|
|
123
|
+
try {
|
|
124
|
+
for (const account of await this.storage.accounts()) {
|
|
125
|
+
if (account.startsWith(KEY_ACCOUNT_PREFIX)) {
|
|
126
|
+
targets.add(account);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
logger.warn('Could not enumerate keychain accounts during clear — using index only');
|
|
132
|
+
}
|
|
133
|
+
const results = await Promise.allSettled([...targets].map((account) => this.storage.delete(account)));
|
|
134
|
+
const failed = results.filter((result) => result.status === 'rejected').length;
|
|
135
|
+
if (failed > 0) {
|
|
136
|
+
throw new Error(`Failed to remove ${failed} keychain entr${failed === 1 ? 'y' : 'ies'} during clear`);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
async runLegacyMigration() {
|
|
140
|
+
const existing = await this.readIndex();
|
|
141
|
+
if (existing.length > 0) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
const [legacyKey, legacyWorkspaceId] = await Promise.all([
|
|
145
|
+
this.storage.get(LEGACY_API_KEY_ACCOUNT),
|
|
146
|
+
this.storage.get(LEGACY_WORKSPACE_ID_ACCOUNT),
|
|
147
|
+
]);
|
|
148
|
+
if (!legacyKey || !legacyWorkspaceId) {
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
// crash-safe: key + index + active пишутся ДО удаления legacy.
|
|
152
|
+
await this.storage.set(keyAccount(legacyWorkspaceId), legacyKey);
|
|
153
|
+
await this.writeIndex([
|
|
154
|
+
{
|
|
155
|
+
workspaceId: legacyWorkspaceId,
|
|
156
|
+
keyPrefix: maskApiKey(legacyKey),
|
|
157
|
+
connectedAt: new Date().toISOString(),
|
|
158
|
+
},
|
|
159
|
+
]);
|
|
160
|
+
await this.storage.set(ACTIVE_ACCOUNT, legacyWorkspaceId);
|
|
161
|
+
await this.storage.delete(LEGACY_API_KEY_ACCOUNT);
|
|
162
|
+
await this.storage.delete(LEGACY_WORKSPACE_ID_ACCOUNT);
|
|
163
|
+
logger.info({ workspaceId: legacyWorkspaceId }, 'Migrated legacy credentials to multi-workspace store');
|
|
164
|
+
}
|
|
165
|
+
async readIndex() {
|
|
166
|
+
const raw = await this.storage.get(INDEX_ACCOUNT);
|
|
167
|
+
if (!raw) {
|
|
168
|
+
return [];
|
|
169
|
+
}
|
|
170
|
+
try {
|
|
171
|
+
const parsed = JSON.parse(raw);
|
|
172
|
+
if (!Array.isArray(parsed)) {
|
|
173
|
+
return [];
|
|
174
|
+
}
|
|
175
|
+
return parsed.filter(isConnectionMeta);
|
|
176
|
+
}
|
|
177
|
+
catch {
|
|
178
|
+
logger.warn('Connection index is corrupt — treating as empty');
|
|
179
|
+
return [];
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
async writeIndex(connections) {
|
|
183
|
+
await this.storage.set(INDEX_ACCOUNT, JSON.stringify(connections));
|
|
184
|
+
}
|
|
185
|
+
async restore(workspaceId, prevKey, prevIndexRaw, prevActive) {
|
|
186
|
+
await this.restoreAccount(keyAccount(workspaceId), prevKey);
|
|
187
|
+
await this.restoreAccount(INDEX_ACCOUNT, prevIndexRaw);
|
|
188
|
+
await this.restoreAccount(ACTIVE_ACCOUNT, prevActive);
|
|
189
|
+
}
|
|
190
|
+
async restoreAccount(account, previousValue) {
|
|
191
|
+
if (previousValue === null) {
|
|
192
|
+
await this.storage.delete(account);
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
await this.storage.set(account, previousValue);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
function isConnectionMeta(value) {
|
|
200
|
+
if (!value || typeof value !== 'object') {
|
|
201
|
+
return false;
|
|
202
|
+
}
|
|
203
|
+
const candidate = value;
|
|
204
|
+
return (typeof candidate.workspaceId === 'string' &&
|
|
205
|
+
typeof candidate.keyPrefix === 'string' &&
|
|
206
|
+
typeof candidate.connectedAt === 'string');
|
|
207
|
+
}
|
|
208
|
+
//# sourceMappingURL=connection-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"connection-store.js","sourceRoot":"","sources":["../../src/auth/connection-store.ts"],"names":[],"mappings":"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"}
|