@besales/mcp 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +127 -0
- package/bin/besales-mcp.js +10 -0
- package/dist/auth/oauth-client.d.ts +32 -0
- package/dist/auth/oauth-client.js +180 -0
- package/dist/auth/oauth-client.js.map +1 -0
- package/dist/auth/token-storage.d.ts +7 -0
- package/dist/auth/token-storage.js +17 -0
- package/dist/auth/token-storage.js.map +1 -0
- package/dist/cli.d.ts +22 -0
- package/dist/cli.js +70 -0
- package/dist/cli.js.map +1 -0
- package/dist/http/api-client.d.ts +51 -0
- package/dist/http/api-client.js +115 -0
- package/dist/http/api-client.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/package-metadata.d.ts +1 -0
- package/dist/package-metadata.js +6 -0
- package/dist/package-metadata.js.map +1 -0
- package/dist/resources/concepts/external-execution.md +22 -0
- package/dist/resources/concepts/handoff.md +16 -0
- package/dist/resources/concepts/icp.md +16 -0
- package/dist/resources/concepts/sandbox.md +17 -0
- package/dist/resources/concepts/triggers.md +16 -0
- package/dist/resources/registry.d.ts +5 -0
- package/dist/resources/registry.js +33 -0
- package/dist/resources/registry.js.map +1 -0
- package/dist/schemas/mcp-tools.json +1192 -0
- package/dist/server.d.ts +20 -0
- package/dist/server.js +74 -0
- package/dist/server.js.map +1 -0
- package/dist/tools/contracts.d.ts +22 -0
- package/dist/tools/contracts.js +24 -0
- package/dist/tools/contracts.js.map +1 -0
- package/dist/tools/definitions.d.ts +307 -0
- package/dist/tools/definitions.js +859 -0
- package/dist/tools/definitions.js.map +1 -0
- package/dist/tools/registry.d.ts +11 -0
- package/dist/tools/registry.js +52 -0
- package/dist/tools/registry.js.map +1 -0
- package/dist/tools/result.d.ts +4 -0
- package/dist/tools/result.js +78 -0
- package/dist/tools/result.js.map +1 -0
- package/dist/types/api-contract.gen.d.ts +6975 -0
- package/dist/types/api-contract.gen.js +6 -0
- package/dist/types/api-contract.gen.js.map +1 -0
- package/dist/utils/logger.d.ts +2 -0
- package/dist/utils/logger.js +13 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/mask.d.ts +1 -0
- package/dist/utils/mask.js +7 -0
- package/dist/utils/mask.js.map +1 -0
- package/docs/host-setup.md +256 -0
- package/package.json +81 -0
- package/scripts/install-claude-desktop.js +77 -0
- package/scripts/mock-api-server.js +56 -0
- package/scripts/mock-credentials.js +34 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-contract.gen.js","sourceRoot":"","sources":["../../src/types/api-contract.gen.ts"],"names":[],"mappings":"AAAA;;;GAGG"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import pino from 'pino';
|
|
2
|
+
export const logger = pino({
|
|
3
|
+
level: process.env.LOG_LEVEL ?? (process.env.NODE_ENV === 'test' ? 'silent' : 'info'),
|
|
4
|
+
redact: [
|
|
5
|
+
'api_key',
|
|
6
|
+
'*.api_key',
|
|
7
|
+
'*.*.api_key',
|
|
8
|
+
'apiKey',
|
|
9
|
+
'*.apiKey',
|
|
10
|
+
'*.*.apiKey',
|
|
11
|
+
],
|
|
12
|
+
}, pino.destination(2));
|
|
13
|
+
//# sourceMappingURL=logger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.js","sourceRoot":"","sources":["../../src/utils/logger.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,MAAM,CAAC;AAExB,MAAM,CAAC,MAAM,MAAM,GAAG,IAAI,CACxB;IACE,KAAK,EAAE,OAAO,CAAC,GAAG,CAAC,SAAS,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC;IACrF,MAAM,EAAE;QACN,SAAS;QACT,WAAW;QACX,aAAa;QACb,QAAQ;QACR,UAAU;QACV,YAAY;KACb;CACF,EACD,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,CACpB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function maskApiKey(apiKey: string): string;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mask.js","sourceRoot":"","sources":["../../src/utils/mask.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,UAAU,CAAC,MAAc;IACvC,IAAI,MAAM,CAAC,MAAM,GAAG,EAAE,EAAE,CAAC;QACvB,OAAO,KAAK,CAAC;IACf,CAAC;IAED,OAAO,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC;AACpC,CAAC"}
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
# MCP Host Setup Runbook
|
|
2
|
+
|
|
3
|
+
This runbook covers local development setup for `@besales/mcp` in the MCP hosts
|
|
4
|
+
we support during v1.5 validation: MCP Inspector, Claude Desktop, Claude Code,
|
|
5
|
+
and Codex CLI.
|
|
6
|
+
|
|
7
|
+
## Scope
|
|
8
|
+
|
|
9
|
+
`besales-mcp` is a thin MCP server for Animaly v1.5. It never calls
|
|
10
|
+
`prompt-services` directly. Every tool call goes through `ai-aniomaly`
|
|
11
|
+
`/api/v2/*`, and `ai-aniomaly` decides whether the operation is internal or
|
|
12
|
+
external.
|
|
13
|
+
|
|
14
|
+
External execution is host-neutral:
|
|
15
|
+
|
|
16
|
+
1. The host calls a `*_get_instructions` tool.
|
|
17
|
+
2. `ai-aniomaly` creates an operation request and returns stages.
|
|
18
|
+
3. The host/model executes the stages locally.
|
|
19
|
+
4. The host calls the matching `*_submit` tool.
|
|
20
|
+
|
|
21
|
+
Claude Desktop, Claude Code, and Codex CLI all use this same two-step MCP flow.
|
|
22
|
+
|
|
23
|
+
## Local Preconditions
|
|
24
|
+
|
|
25
|
+
Run these from `/Users/peresvets/apps/animaly/besales-mcp`.
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
yarn install
|
|
29
|
+
yarn build
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
For live local API testing, `ai-aniomaly` must be running on port `3000` and the
|
|
33
|
+
v1.5 migrations must already be applied:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
curl -fsS http://localhost:3000/health
|
|
37
|
+
curl -fsS http://localhost:3000/.well-known/jwks.json
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Use this base URL for local host configs:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
export BESALES_API_BASE_URL=http://localhost:3000/api/v2
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
`prompt-services` is not a host precondition. Hosts talk only to `besales-mcp`,
|
|
47
|
+
and `besales-mcp` talks only to `ai-aniomaly`.
|
|
48
|
+
|
|
49
|
+
For production users, omit `BESALES_API_BASE_URL`. The package defaults to
|
|
50
|
+
`https://app.besales.ai/api/v2`.
|
|
51
|
+
|
|
52
|
+
## Credentials
|
|
53
|
+
|
|
54
|
+
Live tool calls require an MCP API key stored in the local macOS keychain:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
BESALES_API_BASE_URL=http://localhost:3000/api/v2 node bin/besales-mcp.js connect
|
|
58
|
+
BESALES_API_BASE_URL=http://localhost:3000/api/v2 node bin/besales-mcp.js status
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
The `connect` command opens the browser and completes
|
|
62
|
+
`/api/v2/auth/mcp-connect` through the loopback callback. Use `disconnect` to
|
|
63
|
+
clear credentials:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
BESALES_API_BASE_URL=http://localhost:3000/api/v2 node bin/besales-mcp.js disconnect
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
The connected workspace is stored alongside the API key. Workspace-level tools
|
|
70
|
+
default to that workspace; users should not be asked to provide `workspace_id`.
|
|
71
|
+
To switch workspaces, disconnect, switch or create the desired workspace in the
|
|
72
|
+
web app, then connect again.
|
|
73
|
+
|
|
74
|
+
For MCP protocol smoke without live auth, use the local mock API:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
yarn dev:mock
|
|
78
|
+
yarn dev:mock:seed
|
|
79
|
+
BESALES_API_BASE_URL=http://127.0.0.1:3100/api/v2 node bin/besales-mcp.js
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Clear mock credentials after the smoke:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
yarn dev:mock:clear
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## MCP Inspector
|
|
89
|
+
|
|
90
|
+
Inspector is the first check because it verifies the MCP protocol without IDE
|
|
91
|
+
state.
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
BESALES_API_BASE_URL=http://localhost:3000/api/v2 \
|
|
95
|
+
yarn dlx @modelcontextprotocol/inspector node bin/besales-mcp.js
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Headless count checks:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
BESALES_API_BASE_URL=http://localhost:3000/api/v2 \
|
|
102
|
+
yarn dlx @modelcontextprotocol/inspector --cli --method tools/list \
|
|
103
|
+
node bin/besales-mcp.js
|
|
104
|
+
|
|
105
|
+
BESALES_API_BASE_URL=http://localhost:3000/api/v2 \
|
|
106
|
+
yarn dlx @modelcontextprotocol/inspector --cli --method resources/list \
|
|
107
|
+
node bin/besales-mcp.js
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Expected:
|
|
111
|
+
|
|
112
|
+
- 31 tools are listed.
|
|
113
|
+
- 5 resources are listed.
|
|
114
|
+
- `besales://concepts/external-execution` opens.
|
|
115
|
+
- A direct tool, for example `besales_icp_create`, reaches `ai-aniomaly`.
|
|
116
|
+
- An external pair returns instructions and accepts submit.
|
|
117
|
+
|
|
118
|
+
For mock-only protocol checks, point Inspector at `http://127.0.0.1:3100/api/v2`
|
|
119
|
+
after `yarn dev:mock` and `yarn dev:mock:seed`.
|
|
120
|
+
|
|
121
|
+
## Claude Desktop
|
|
122
|
+
|
|
123
|
+
Install the local server entry:
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
yarn install:claude-desktop
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
The installer writes this entry to
|
|
130
|
+
`~/Library/Application Support/Claude/claude_desktop_config.json` and keeps a
|
|
131
|
+
timestamped backup:
|
|
132
|
+
|
|
133
|
+
```json
|
|
134
|
+
{
|
|
135
|
+
"mcpServers": {
|
|
136
|
+
"besales": {
|
|
137
|
+
"command": "/absolute/path/to/node",
|
|
138
|
+
"args": ["/Users/peresvets/apps/animaly/besales-mcp/bin/besales-mcp.js"],
|
|
139
|
+
"env": {
|
|
140
|
+
"BESALES_API_BASE_URL": "http://localhost:3000/api/v2"
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Restart Claude Desktop after changing the config. Then open the MCP/server view
|
|
148
|
+
and verify `besales` is connected.
|
|
149
|
+
|
|
150
|
+
Smoke:
|
|
151
|
+
|
|
152
|
+
1. Ask Claude Desktop to list available Besales tools.
|
|
153
|
+
2. Call `besales_icp_create` against a test workspace.
|
|
154
|
+
3. Call one external pair, for example
|
|
155
|
+
`besales_prompt_generate_get_instructions`, execute the returned stages
|
|
156
|
+
locally, and submit with `besales_prompt_generate_submit`.
|
|
157
|
+
|
|
158
|
+
## Claude Code
|
|
159
|
+
|
|
160
|
+
If `claude` is on `PATH`, add the server at user scope:
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
claude mcp add --scope user \
|
|
164
|
+
-e BESALES_API_BASE_URL=http://localhost:3000/api/v2 \
|
|
165
|
+
-- besales node /Users/peresvets/apps/animaly/besales-mcp/bin/besales-mcp.js
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
If the app-managed binary is not on `PATH`, use the absolute Claude Code binary
|
|
169
|
+
or edit `~/.claude.json` with the same `mcpServers.besales` entry.
|
|
170
|
+
|
|
171
|
+
Verify:
|
|
172
|
+
|
|
173
|
+
```bash
|
|
174
|
+
claude mcp list
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Start a fresh Claude Code session from `/Users/peresvets/apps/animaly`, run
|
|
178
|
+
`/mcp`, and check that `besales` is connected. Then run the same direct tool and
|
|
179
|
+
external-pair smoke as Claude Desktop.
|
|
180
|
+
|
|
181
|
+
## Codex CLI
|
|
182
|
+
|
|
183
|
+
Codex CLI manages stdio MCP servers through `codex mcp`.
|
|
184
|
+
|
|
185
|
+
```bash
|
|
186
|
+
codex mcp add besales \
|
|
187
|
+
--env BESALES_API_BASE_URL=http://localhost:3000/api/v2 \
|
|
188
|
+
-- node /Users/peresvets/apps/animaly/besales-mcp/bin/besales-mcp.js
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
Equivalent `~/.codex/config.toml` shape:
|
|
192
|
+
|
|
193
|
+
```toml
|
|
194
|
+
[mcp_servers.besales]
|
|
195
|
+
command = "node"
|
|
196
|
+
args = ["/Users/peresvets/apps/animaly/besales-mcp/bin/besales-mcp.js"]
|
|
197
|
+
|
|
198
|
+
[mcp_servers.besales.env]
|
|
199
|
+
BESALES_API_BASE_URL = "http://localhost:3000/api/v2"
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
Verify:
|
|
203
|
+
|
|
204
|
+
```bash
|
|
205
|
+
codex mcp list
|
|
206
|
+
codex mcp get besales
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
Start a new Codex CLI session after adding the server. Confirm `besales_*` tools
|
|
210
|
+
are present, then run:
|
|
211
|
+
|
|
212
|
+
1. One direct tool (`besales_icp_create`).
|
|
213
|
+
2. One external pair (`*_get_instructions` then matching `*_submit`).
|
|
214
|
+
|
|
215
|
+
For the ICP analysis pair, `besales_icp_analysis_submit` personas must include
|
|
216
|
+
`segment_id`. Use either an existing ICP segment id or the `id` of a segment in
|
|
217
|
+
the same submit payload.
|
|
218
|
+
|
|
219
|
+
Codex App uses the same Codex MCP configuration, but an already-running desktop
|
|
220
|
+
session may need an app/session restart before new tools appear.
|
|
221
|
+
|
|
222
|
+
### Codex App Deferred Tool Discovery
|
|
223
|
+
|
|
224
|
+
Codex App can defer MCP tool schemas until the model searches for them. This is
|
|
225
|
+
host behavior, not a `besales-mcp` server problem: `codex mcp list/get besales`
|
|
226
|
+
and MCP Inspector `tools/list` are still the source checks for whether the
|
|
227
|
+
server is installed and exposing all 31 tools.
|
|
228
|
+
|
|
229
|
+
User-facing prompts must stay natural. Users should ask for the business action,
|
|
230
|
+
for example:
|
|
231
|
+
|
|
232
|
+
```text
|
|
233
|
+
Use besales MCP to create an ICP and run the ICP analysis external flow.
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
The assistant must not ask the user to mention `tool_search`. If a requested
|
|
237
|
+
`besales_*` tool is not already visible in Codex App, the assistant should run
|
|
238
|
+
tool discovery itself by searching for `besales`, the operation name, or the
|
|
239
|
+
specific tool name, then call the discovered tool. Do not conclude that a
|
|
240
|
+
`besales_*` tool is unavailable until deferred discovery has been attempted.
|
|
241
|
+
|
|
242
|
+
## Smoke Matrix
|
|
243
|
+
|
|
244
|
+
Use this matrix for local validation before staging/prod checks.
|
|
245
|
+
|
|
246
|
+
| Host | Config check | Protocol check | Direct tool | External pair |
|
|
247
|
+
|---|---|---|---|---|
|
|
248
|
+
| MCP Inspector | command launches | 31 tools / 5 resources | `besales_icp_create` | instructions -> submit |
|
|
249
|
+
| Claude Desktop | config JSON has `besales` | server connected | `besales_icp_create` | instructions -> submit |
|
|
250
|
+
| Claude Code | `claude mcp list` has `besales` | `/mcp` connected | `besales_icp_create` | instructions -> submit |
|
|
251
|
+
| Codex CLI | `codex mcp list` has `besales` | new session sees tools | `besales_icp_create` | instructions -> submit |
|
|
252
|
+
|
|
253
|
+
Do not mark the ROADMAP manual E2E acceptance from mock-only checks. Mock mode
|
|
254
|
+
only verifies host wiring and MCP protocol shape. ROADMAP acceptance requires a
|
|
255
|
+
live `ai-aniomaly` backend, real stored MCP credentials, and successful direct
|
|
256
|
+
plus external tool calls.
|
package/package.json
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@besales/mcp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Model Context Protocol server for Animaly / Besales",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"homepage": "https://github.com/be-sales/mcp#readme",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/be-sales/mcp.git"
|
|
10
|
+
},
|
|
11
|
+
"bugs": {
|
|
12
|
+
"url": "https://github.com/be-sales/mcp/issues"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"mcp",
|
|
16
|
+
"model-context-protocol",
|
|
17
|
+
"besales",
|
|
18
|
+
"animaly",
|
|
19
|
+
"claude",
|
|
20
|
+
"codex"
|
|
21
|
+
],
|
|
22
|
+
"publishConfig": {
|
|
23
|
+
"access": "public",
|
|
24
|
+
"registry": "https://registry.npmjs.org"
|
|
25
|
+
},
|
|
26
|
+
"type": "module",
|
|
27
|
+
"main": "./dist/index.js",
|
|
28
|
+
"types": "./dist/index.d.ts",
|
|
29
|
+
"exports": {
|
|
30
|
+
".": {
|
|
31
|
+
"types": "./dist/index.d.ts",
|
|
32
|
+
"import": "./dist/index.js"
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
"files": [
|
|
36
|
+
"dist",
|
|
37
|
+
"bin",
|
|
38
|
+
"scripts",
|
|
39
|
+
"docs",
|
|
40
|
+
"README.md"
|
|
41
|
+
],
|
|
42
|
+
"bin": {
|
|
43
|
+
"besales-mcp": "bin/besales-mcp.js"
|
|
44
|
+
},
|
|
45
|
+
"scripts": {
|
|
46
|
+
"build": "tsc && mkdir -p dist/schemas dist/resources/concepts && cp src/schemas/mcp-tools.json dist/schemas/mcp-tools.json && cp src/resources/concepts/*.md dist/resources/concepts/",
|
|
47
|
+
"start": "node bin/besales-mcp.js",
|
|
48
|
+
"dev:mock": "node scripts/mock-api-server.js",
|
|
49
|
+
"dev:mock:seed": "node scripts/mock-credentials.js seed",
|
|
50
|
+
"dev:mock:clear": "node scripts/mock-credentials.js clear",
|
|
51
|
+
"install:claude-desktop": "node scripts/install-claude-desktop.js",
|
|
52
|
+
"prepack": "yarn build",
|
|
53
|
+
"prepublishOnly": "yarn lint:errors && yarn test && yarn build",
|
|
54
|
+
"test": "vitest run",
|
|
55
|
+
"lint:fix": "eslint src bin scripts eslint.config.js --fix",
|
|
56
|
+
"lint:errors": "eslint src bin scripts eslint.config.js",
|
|
57
|
+
"types:gen:api": "mkdir -p src/types && openapi-typescript ../docs/specs/v2/contracts/openapi-besales-api.yaml -o src/types/api-contract.gen.ts",
|
|
58
|
+
"types:gen:mcp": "mkdir -p src/schemas && cp ../docs/specs/v2/contracts/mcp-tools.json src/schemas/mcp-tools.json"
|
|
59
|
+
},
|
|
60
|
+
"engines": {
|
|
61
|
+
"node": ">=20.0.0"
|
|
62
|
+
},
|
|
63
|
+
"packageManager": "yarn@4.13.0",
|
|
64
|
+
"dependencies": {
|
|
65
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
66
|
+
"axios": "^1.16.0",
|
|
67
|
+
"keytar": "^7.9.0",
|
|
68
|
+
"open": "^11.0.0",
|
|
69
|
+
"pino": "^10.3.1",
|
|
70
|
+
"zod": "^4.4.3"
|
|
71
|
+
},
|
|
72
|
+
"devDependencies": {
|
|
73
|
+
"@types/node": "^25.7.0",
|
|
74
|
+
"@typescript-eslint/eslint-plugin": "^8.59.3",
|
|
75
|
+
"@typescript-eslint/parser": "^8.59.3",
|
|
76
|
+
"eslint": "^10.3.0",
|
|
77
|
+
"openapi-typescript": "^7.0.0",
|
|
78
|
+
"typescript": "^5.5.0",
|
|
79
|
+
"vitest": "^4.1.6"
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { copyFile, mkdir, readFile, rename, writeFile } from 'node:fs/promises';
|
|
4
|
+
import { existsSync } from 'node:fs';
|
|
5
|
+
import { homedir } from 'node:os';
|
|
6
|
+
import { dirname, join } from 'node:path';
|
|
7
|
+
import { fileURLToPath } from 'node:url';
|
|
8
|
+
|
|
9
|
+
const CLAUDE_CONFIG_PATH = join(
|
|
10
|
+
homedir(),
|
|
11
|
+
'Library/Application Support/Claude/claude_desktop_config.json',
|
|
12
|
+
);
|
|
13
|
+
const LOCAL_API_BASE_URL = 'http://localhost:3000/api/v2';
|
|
14
|
+
const packageRoot = dirname(fileURLToPath(new URL('../package.json', import.meta.url)));
|
|
15
|
+
const binPath = join(packageRoot, 'bin/besales-mcp.js');
|
|
16
|
+
|
|
17
|
+
function timestamp() {
|
|
18
|
+
return new Date().toISOString().replace(/[:.]/g, '-');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function ensureObject(value) {
|
|
22
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
23
|
+
return {};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return value;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async function readConfig() {
|
|
30
|
+
if (!existsSync(CLAUDE_CONFIG_PATH)) {
|
|
31
|
+
return {};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const text = await readFile(CLAUDE_CONFIG_PATH, 'utf-8');
|
|
35
|
+
|
|
36
|
+
if (!text.trim()) {
|
|
37
|
+
return {};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return ensureObject(JSON.parse(text));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async function install() {
|
|
44
|
+
await mkdir(dirname(CLAUDE_CONFIG_PATH), { recursive: true });
|
|
45
|
+
|
|
46
|
+
if (existsSync(CLAUDE_CONFIG_PATH)) {
|
|
47
|
+
const backupPath = `${CLAUDE_CONFIG_PATH}.backup-${timestamp()}`;
|
|
48
|
+
await copyFile(CLAUDE_CONFIG_PATH, backupPath);
|
|
49
|
+
console.log(`Backed up Claude Desktop config to ${backupPath}`);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const config = await readConfig();
|
|
53
|
+
const mcpServers = ensureObject(config.mcpServers);
|
|
54
|
+
|
|
55
|
+
mcpServers.besales = {
|
|
56
|
+
command: process.execPath,
|
|
57
|
+
args: [binPath],
|
|
58
|
+
env: {
|
|
59
|
+
BESALES_API_BASE_URL: LOCAL_API_BASE_URL,
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
config.mcpServers = mcpServers;
|
|
63
|
+
|
|
64
|
+
const tempPath = `${CLAUDE_CONFIG_PATH}.tmp-${process.pid}`;
|
|
65
|
+
await writeFile(tempPath, `${JSON.stringify(config, null, 2)}\n`, 'utf-8');
|
|
66
|
+
await rename(tempPath, CLAUDE_CONFIG_PATH);
|
|
67
|
+
|
|
68
|
+
console.log(`Installed besales MCP config at ${CLAUDE_CONFIG_PATH}`);
|
|
69
|
+
console.log(`Node: ${process.execPath}`);
|
|
70
|
+
console.log(`Command args: ${binPath}`);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
install().catch((error) => {
|
|
74
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
75
|
+
console.error(`Failed to install Claude Desktop config: ${message}`);
|
|
76
|
+
process.exit(1);
|
|
77
|
+
});
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createServer } from 'node:http';
|
|
3
|
+
|
|
4
|
+
const port = Number(process.env.BESALES_MOCK_API_PORT ?? 3100);
|
|
5
|
+
|
|
6
|
+
const server = createServer((request, response) => {
|
|
7
|
+
const url = new URL(request.url ?? '/', `http://127.0.0.1:${port}`);
|
|
8
|
+
|
|
9
|
+
if (!url.pathname.startsWith('/api/v2/')) {
|
|
10
|
+
response.writeHead(404, { 'Content-Type': 'application/json' });
|
|
11
|
+
response.end(JSON.stringify({ message: 'Mock endpoint not found' }));
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
let body = '';
|
|
16
|
+
request.on('data', (chunk) => {
|
|
17
|
+
body += String(chunk);
|
|
18
|
+
});
|
|
19
|
+
request.on('end', () => {
|
|
20
|
+
let parsedBody;
|
|
21
|
+
try {
|
|
22
|
+
parsedBody = body ? JSON.parse(body) : undefined;
|
|
23
|
+
} catch {
|
|
24
|
+
response.writeHead(400, { 'Content-Type': 'application/json' });
|
|
25
|
+
response.end(JSON.stringify({ message: 'Malformed JSON body' }));
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const isInstructions = url.pathname.endsWith('/instructions');
|
|
30
|
+
const isSubmit = url.pathname.endsWith('/submit') || url.pathname.includes('/submit');
|
|
31
|
+
|
|
32
|
+
response.writeHead(isSubmit ? 201 : 200, { 'Content-Type': 'application/json' });
|
|
33
|
+
response.end(
|
|
34
|
+
JSON.stringify({
|
|
35
|
+
mock: true,
|
|
36
|
+
method: request.method,
|
|
37
|
+
path: url.pathname,
|
|
38
|
+
receivedBody: parsedBody,
|
|
39
|
+
operationId: '00000000-0000-4000-8000-000000000099',
|
|
40
|
+
stages: isInstructions
|
|
41
|
+
? [
|
|
42
|
+
{
|
|
43
|
+
name: 'mock_stage',
|
|
44
|
+
systemPrompt: 'Mock system prompt',
|
|
45
|
+
schema: { type: 'object' },
|
|
46
|
+
},
|
|
47
|
+
]
|
|
48
|
+
: undefined,
|
|
49
|
+
}),
|
|
50
|
+
);
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
server.listen(port, '127.0.0.1', () => {
|
|
55
|
+
console.log(`Mock Animaly API listening at http://127.0.0.1:${port}/api/v2`);
|
|
56
|
+
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import * as keytar from 'keytar';
|
|
3
|
+
|
|
4
|
+
const SERVICE = '@besales/mcp';
|
|
5
|
+
const API_KEY_ACCOUNT = 'api_key';
|
|
6
|
+
const WORKSPACE_ID_ACCOUNT = 'workspace_id';
|
|
7
|
+
const DEFAULT_MOCK_API_KEY = 'mcp_mock_key_for_local_inspector';
|
|
8
|
+
const DEFAULT_MOCK_WORKSPACE_ID = '00000000-0000-4000-8000-000000000001';
|
|
9
|
+
const WARNING_MESSAGE = [
|
|
10
|
+
'Warning: this command uses the same keytar entries as a real besales-mcp connection.',
|
|
11
|
+
'It can overwrite or clear local production credentials for this user.',
|
|
12
|
+
].join(' ');
|
|
13
|
+
|
|
14
|
+
const command = process.argv[2] ?? 'seed';
|
|
15
|
+
const apiKey = process.env.BESALES_MOCK_API_KEY ?? DEFAULT_MOCK_API_KEY;
|
|
16
|
+
const workspaceId = process.env.BESALES_MOCK_WORKSPACE_ID ?? DEFAULT_MOCK_WORKSPACE_ID;
|
|
17
|
+
|
|
18
|
+
if (command === 'seed') {
|
|
19
|
+
console.warn(WARNING_MESSAGE);
|
|
20
|
+
await keytar.setPassword(SERVICE, WORKSPACE_ID_ACCOUNT, workspaceId);
|
|
21
|
+
await keytar.setPassword(SERVICE, API_KEY_ACCOUNT, apiKey);
|
|
22
|
+
console.log(`Seeded mock credentials for workspace ${workspaceId}`);
|
|
23
|
+
} else if (command === 'clear') {
|
|
24
|
+
console.warn(WARNING_MESSAGE);
|
|
25
|
+
await Promise.all([
|
|
26
|
+
keytar.deletePassword(SERVICE, API_KEY_ACCOUNT),
|
|
27
|
+
keytar.deletePassword(SERVICE, WORKSPACE_ID_ACCOUNT),
|
|
28
|
+
]);
|
|
29
|
+
console.log('Cleared mock credentials');
|
|
30
|
+
} else {
|
|
31
|
+
console.error(`Unknown command: ${command}`);
|
|
32
|
+
console.error('Usage: node scripts/mock-credentials.js [seed|clear]');
|
|
33
|
+
process.exitCode = 1;
|
|
34
|
+
}
|