@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.
Files changed (59) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +127 -0
  3. package/bin/besales-mcp.js +10 -0
  4. package/dist/auth/oauth-client.d.ts +32 -0
  5. package/dist/auth/oauth-client.js +180 -0
  6. package/dist/auth/oauth-client.js.map +1 -0
  7. package/dist/auth/token-storage.d.ts +7 -0
  8. package/dist/auth/token-storage.js +17 -0
  9. package/dist/auth/token-storage.js.map +1 -0
  10. package/dist/cli.d.ts +22 -0
  11. package/dist/cli.js +70 -0
  12. package/dist/cli.js.map +1 -0
  13. package/dist/http/api-client.d.ts +51 -0
  14. package/dist/http/api-client.js +115 -0
  15. package/dist/http/api-client.js.map +1 -0
  16. package/dist/index.d.ts +16 -0
  17. package/dist/index.js +10 -0
  18. package/dist/index.js.map +1 -0
  19. package/dist/package-metadata.d.ts +1 -0
  20. package/dist/package-metadata.js +6 -0
  21. package/dist/package-metadata.js.map +1 -0
  22. package/dist/resources/concepts/external-execution.md +22 -0
  23. package/dist/resources/concepts/handoff.md +16 -0
  24. package/dist/resources/concepts/icp.md +16 -0
  25. package/dist/resources/concepts/sandbox.md +17 -0
  26. package/dist/resources/concepts/triggers.md +16 -0
  27. package/dist/resources/registry.d.ts +5 -0
  28. package/dist/resources/registry.js +33 -0
  29. package/dist/resources/registry.js.map +1 -0
  30. package/dist/schemas/mcp-tools.json +1192 -0
  31. package/dist/server.d.ts +20 -0
  32. package/dist/server.js +74 -0
  33. package/dist/server.js.map +1 -0
  34. package/dist/tools/contracts.d.ts +22 -0
  35. package/dist/tools/contracts.js +24 -0
  36. package/dist/tools/contracts.js.map +1 -0
  37. package/dist/tools/definitions.d.ts +307 -0
  38. package/dist/tools/definitions.js +859 -0
  39. package/dist/tools/definitions.js.map +1 -0
  40. package/dist/tools/registry.d.ts +11 -0
  41. package/dist/tools/registry.js +52 -0
  42. package/dist/tools/registry.js.map +1 -0
  43. package/dist/tools/result.d.ts +4 -0
  44. package/dist/tools/result.js +78 -0
  45. package/dist/tools/result.js.map +1 -0
  46. package/dist/types/api-contract.gen.d.ts +6975 -0
  47. package/dist/types/api-contract.gen.js +6 -0
  48. package/dist/types/api-contract.gen.js.map +1 -0
  49. package/dist/utils/logger.d.ts +2 -0
  50. package/dist/utils/logger.js +13 -0
  51. package/dist/utils/logger.js.map +1 -0
  52. package/dist/utils/mask.d.ts +1 -0
  53. package/dist/utils/mask.js +7 -0
  54. package/dist/utils/mask.js.map +1 -0
  55. package/docs/host-setup.md +256 -0
  56. package/package.json +81 -0
  57. package/scripts/install-claude-desktop.js +77 -0
  58. package/scripts/mock-api-server.js +56 -0
  59. package/scripts/mock-credentials.js +34 -0
@@ -0,0 +1,6 @@
1
+ /**
2
+ * This file was auto-generated by openapi-typescript.
3
+ * Do not make direct changes to the file.
4
+ */
5
+ export {};
6
+ //# sourceMappingURL=api-contract.gen.js.map
@@ -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,2 @@
1
+ import pino from 'pino';
2
+ export declare const logger: pino.Logger<never, boolean>;
@@ -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,7 @@
1
+ export function maskApiKey(apiKey) {
2
+ if (apiKey.length < 12) {
3
+ return '***';
4
+ }
5
+ return `${apiKey.slice(0, 8)}...`;
6
+ }
7
+ //# sourceMappingURL=mask.js.map
@@ -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
+ }