@debugg-ai/debugg-ai-mcp 1.0.63 → 1.0.65

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 (50) hide show
  1. package/CHANGELOG.md +177 -0
  2. package/README.md +49 -37
  3. package/dist/config/index.js +4 -1
  4. package/dist/handlers/createEnvironmentHandler.js +34 -1
  5. package/dist/handlers/createProjectHandler.js +62 -10
  6. package/dist/handlers/index.js +4 -14
  7. package/dist/handlers/searchEnvironmentsHandler.js +122 -0
  8. package/dist/handlers/searchExecutionsHandler.js +71 -0
  9. package/dist/handlers/searchProjectsHandler.js +72 -0
  10. package/dist/handlers/testPageChangesHandler.js +46 -5
  11. package/dist/handlers/triggerCrawlHandler.js +37 -7
  12. package/dist/handlers/updateEnvironmentHandler.js +94 -15
  13. package/dist/index.js +15 -2
  14. package/dist/services/index.js +3 -3
  15. package/dist/tools/createEnvironment.js +5 -1
  16. package/dist/tools/createProject.js +6 -4
  17. package/dist/tools/index.js +9 -42
  18. package/dist/tools/searchEnvironments.js +35 -0
  19. package/dist/tools/searchExecutions.js +31 -0
  20. package/dist/tools/searchProjects.js +30 -0
  21. package/dist/types/index.js +52 -71
  22. package/package.json +8 -2
  23. package/dist/handlers/cancelExecutionHandler.js +0 -41
  24. package/dist/handlers/createCredentialHandler.js +0 -60
  25. package/dist/handlers/deleteCredentialHandler.js +0 -51
  26. package/dist/handlers/getCredentialHandler.js +0 -49
  27. package/dist/handlers/getEnvironmentHandler.js +0 -49
  28. package/dist/handlers/getExecutionHandler.js +0 -37
  29. package/dist/handlers/getProjectHandler.js +0 -37
  30. package/dist/handlers/listCredentialsHandler.js +0 -93
  31. package/dist/handlers/listEnvironmentsHandler.js +0 -63
  32. package/dist/handlers/listExecutionsHandler.js +0 -35
  33. package/dist/handlers/listProjectsHandler.js +0 -32
  34. package/dist/handlers/listReposHandler.js +0 -27
  35. package/dist/handlers/listTeamsHandler.js +0 -27
  36. package/dist/handlers/updateCredentialHandler.js +0 -70
  37. package/dist/tools/cancelExecution.js +0 -22
  38. package/dist/tools/createCredential.js +0 -52
  39. package/dist/tools/deleteCredential.js +0 -24
  40. package/dist/tools/getCredential.js +0 -24
  41. package/dist/tools/getEnvironment.js +0 -23
  42. package/dist/tools/getExecution.js +0 -22
  43. package/dist/tools/getProject.js +0 -22
  44. package/dist/tools/listCredentials.js +0 -30
  45. package/dist/tools/listEnvironments.js +0 -28
  46. package/dist/tools/listExecutions.js +0 -24
  47. package/dist/tools/listProjects.js +0 -27
  48. package/dist/tools/listRepos.js +0 -23
  49. package/dist/tools/listTeams.js +0 -23
  50. package/dist/tools/updateCredential.js +0 -28
package/CHANGELOG.md ADDED
@@ -0,0 +1,177 @@
1
+ # Changelog
2
+
3
+ All notable changes to the DebuggAI MCP project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ## [1.0.64] - 2026-04-23
11
+
12
+ > **⚠️ Semver violation — this is functionally a major release shipped as a patch.**
13
+ > The surface collapse below removes 14 tools. Callers pinned to `^1.0.63` will silently
14
+ > receive a breaking API on their next install. A republish as `2.0.0` (or a `2.0.0`
15
+ > bump with `1.0.64` deprecated) is recommended to restore semver discipline.
16
+
17
+ This is a **breaking release**. The MCP surface collapsed from 22 tools to 11 through a uniform `search_*` pattern plus credential-management consolidation into the environment tools. The full old→new mapping is below.
18
+
19
+ ### ⚠️ BREAKING CHANGES — 14 tools removed, replaced by 11-tool surface
20
+
21
+ | Removed tool | Replacement |
22
+ |---|---|
23
+ | `list_projects` | `search_projects({q?, page?, pageSize?})` (filter mode) |
24
+ | `get_project` | `search_projects({uuid})` (uuid mode — returns the curated detail shape) |
25
+ | `list_environments` | `search_environments({projectUuid?, q?, page?, pageSize?})` — credentials inlined per env |
26
+ | `get_environment` | `search_environments({uuid, projectUuid})` |
27
+ | `list_credentials` | `search_environments(...)` — credentials are inlined on each returned env (never include password) |
28
+ | `get_credential` | `search_environments({uuid, projectUuid})` — pull from the env's `credentials[]` |
29
+ | `create_credential` | `create_environment({name, url, credentials: [...]})` (seed on env create), or `update_environment({uuid, addCredentials: [...]})` |
30
+ | `update_credential` | `update_environment({uuid, updateCredentials: [{uuid, ...patch}]})` |
31
+ | `delete_credential` | `update_environment({uuid, removeCredentialIds: [uuid]})` |
32
+ | `list_teams` | `create_project({teamName, ...})` — backend name-resolved with exact-match + ambiguity handling |
33
+ | `list_repos` | `create_project({repoName, ...})` — same pattern |
34
+ | `list_executions` | `search_executions({status?, projectUuid?, page?, pageSize?})` |
35
+ | `get_execution` | `search_executions({uuid})` — full detail with `nodeExecutions` + state |
36
+ | `cancel_execution` | Dropped — backend spin-down is now automatic; no client action needed |
37
+
38
+ All `search_*` tools use a dual-mode signature: pass `{uuid}` for a single-record detail response, or pass filter params for a paginated summary list. 404 from the backend surfaces as `isError: true` with `{error: 'NotFound', message, uuid}`.
39
+
40
+ Credential mutations on `update_environment` execute as `remove → update → add` in a single call, so a freed label can be re-bound in one request. Per-cred failures surface in `credentialWarnings[]` without blocking the env update.
41
+
42
+ ### Added
43
+
44
+ - **`trigger_crawl` tool**: server-side browser-agent crawl to populate the project's knowledge graph. Returns `{executionId, status, targetUrl, durationMs, outcome?, crawlSummary?, knowledgeGraph?}` with `knowledgeGraph.imported` = true on successful KG ingestion. Supports localhost via automatic ngrok tunneling with per-process reuse.
45
+ - **`create_project` name-based resolution**: pass `teamName` instead of `teamUuid`, or `repoName` instead of `repoUuid`. Backend-side search with case-insensitive exact match. Returns `AmbiguousMatch` with candidates if multiple hits, `NotFound` if none.
46
+ - **`create_environment` credential seeding**: pass `credentials: [{label, username, password, role?}]` to create creds atomically with the env.
47
+ - **`update_environment` credential sub-actions**: `addCredentials[]`, `updateCredentials[]`, `removeCredentialIds[]` in one call.
48
+ - **`engines.node: ">=20.20.0"`** in `package.json`. Driven by `posthog-node@^5.26.0` requiring Node 20.20+.
49
+ - **Boot-smoke CI** (`.github/workflows/boot-smoke.yml`): matrix `{ubuntu, macos} × {Node 20, 22}` verifies the MCP server boots + completes `tools/list` with published-style spawn.
50
+ - **Eval runner tag filtering**: `--tag=<name>`, `--skip-tag=<name>`, `--flow=<csv>`; `--list` prints flows + tags. `--tag=fast` runs 12 non-browser flows in ~40s; `--tag=browser` runs heavy flows.
51
+ - **27 eval flows total** (up from 16 in prior unreleased work). New flows since the last published version: response-structure (20), tunnel reuse (21), long-running check (22), crawl triggers public + localhost + with-project (23/24/26), published-boot-smoke (25), localhost deep-path (27).
52
+ - **Response sanitization**: `check_app_in_browser` strips ngrok tunnel URLs from the full response including agent-authored `actionTrace[*].intent`.
53
+
54
+ ### Changed
55
+
56
+ - **Deferred API-key validation**: missing `DEBUGGAI_API_KEY` no longer crashes the subprocess at boot (the bug that surfaced in Claude Code as "Failed to reconnect to debugg-ai"). The server starts, `tools/list` succeeds, and the error surfaces only when a tool is actually invoked — as a structured `isError: true` response pointing the caller at the missing env var.
57
+ - **Boot-time behavior**: `index.ts` no longer calls `resolveProjectContext()` at startup. Project context resolves lazily on first tool call that needs it.
58
+ - **`services/projectContext.ts`**: promise-dedup pattern replaces the failure-caching singleton. Concurrent callers share one in-flight promise; results cached on success only, so transient network errors don't permanently disable context resolution.
59
+ - **Pagination mandatory on every list response**: `search_projects` / `search_environments` / `search_executions` accept optional `page` (1-indexed) and `pageSize` (default 20, max 200, oversized clamped). Response shape: `{filter, pageInfo: {page, pageSize, totalCount, totalPages, hasMore}, <items>}`.
60
+ - **Axios error handling**: handlers map `err.statusCode` (surfaced by the transport's response interceptor) to tool-level `NotFound` errors instead of checking `err.response?.status` which the interceptor strips.
61
+
62
+ ### Fixed
63
+
64
+ - **Progress-notification race** (bead `0bq`) in both `testPageChangesHandler` and `triggerCrawlHandler`: a progress callback firing after the handler resolved could tear down the stdio transport. Circuit breaker suppresses subsequent callbacks after the first throw; terminal-status detection emits the final `progress === total` notification inside `onUpdate` before the poll loop exits.
65
+ - **"Failed to reconnect to debugg-ai" UX** (bead `cma`): missing API key now surfaces as a per-tool-call error instead of a silent subprocess exit at boot. MCP clients see the server register normally and get a readable error only when a tool is actually invoked.
66
+ - **Credential role filter** (bead `hpo`): backend `?role=` filter on credentials list was returning all creds regardless. MCP now applies client-side role filtering as defense-in-depth.
67
+
68
+ ### Security invariants
69
+
70
+ - Passwords are write-only. No response body from any tool contains a password (verified by unit tests + eval flows 06/10/12/15).
71
+ - Tunnel URLs (`*.ngrok.debugg.ai`) are stripped from all `check_app_in_browser` responses including agent-authored text (verified by flow 05).
72
+ - 404s from the backend surface as `isError: true` with structured `{error: 'NotFound', ...}`, never as thrown exceptions.
73
+
74
+ ### Tool count
75
+
76
+ The server registers **11** tools (was 22 pre-collapse, 18 in the previous unreleased snapshot). Verified by eval flow `01-protocol.mjs` which locks the roster.
77
+
78
+ ## [1.0.15] - 2025-08-18
79
+
80
+ ### Added
81
+ - **Live Session Monitoring Tools**: Added 5 new MCP tools for real-time browser session monitoring
82
+ - `debugg_ai_start_live_session`: Launch live remote browser sessions with real-time monitoring
83
+ - `debugg_ai_stop_live_session`: Stop active live sessions
84
+ - `debugg_ai_get_live_session_status`: Monitor session status and health
85
+ - `debugg_ai_get_live_session_logs`: Retrieve console logs and network requests from live sessions
86
+ - `debugg_ai_get_live_session_screenshot`: Capture screenshots from active sessions
87
+ - **Enhanced Tunnel Management**: Complete rewrite of tunnel infrastructure with improved ngrok integration
88
+ - New `TunnelManager` service for high-level tunnel abstraction
89
+ - Automatic localhost URL detection and tunnel creation
90
+ - Better error handling and connection stability
91
+ - Integrated tunnel support in live session handlers
92
+ - **Browser Sessions Service**: New dedicated service for managing browser automation sessions
93
+ - **Comprehensive Test Infrastructure**: Added extensive test suite covering unit, integration, and end-to-end scenarios
94
+ - Handler tests for E2E suites and live sessions
95
+ - Backend services integration tests
96
+ - Network and MCP tools validation tests
97
+ - Mock infrastructure for reliable testing
98
+ - **Enhanced Project Analysis**: New utilities for analyzing codebases and extracting context
99
+ - **Improved Error Handling**: Centralized error management with structured error types
100
+ - **URL Parser Utilities**: Robust URL parsing and localhost detection capabilities
101
+ - **Configuration Management**: Centralized configuration system with environment-based settings
102
+ - **API Specification**: Complete OpenAPI specification for backend integration
103
+ - **GitHub Actions Workflows**: Automated publishing, version bumping, and validation workflows
104
+
105
+ ### Changed
106
+ - **Major Architecture Refactoring**: Reorganized services, handlers, and utilities into cleaner modular structure
107
+ - **Moved Tunnel Services**: Relocated tunnel management from `tunnels/` to `services/ngrok/` for better organization
108
+ - **Enhanced E2E Runner**: Improved test execution with better progress tracking and error handling
109
+ - **Updated Package Dependencies**: Upgraded to latest versions of core dependencies including MCP SDK
110
+ - **Improved Documentation**: Updated README with comprehensive setup and usage instructions
111
+ - **Enhanced Type Definitions**: Expanded type system with better validation schemas
112
+
113
+ ### Fixed
114
+ - **API Endpoint Updates**: Resolved compatibility issues with backend API changes
115
+ - **Image Support Improvements**: Enhanced handling of screenshots and visual test artifacts
116
+ - **Tunnel Connection Stability**: Fixed issues with ngrok tunnel reliability and reconnection
117
+ - **ES Module Compatibility**: Resolved module resolution issues for better Node.js compatibility
118
+
119
+ ### Security
120
+ - **License Addition**: Added Apache 2.0 license for proper open source compliance
121
+ - **Environment Variable Validation**: Enhanced validation of sensitive configuration data
122
+
123
+ ## [1.0.14] - 2025-06-09
124
+
125
+ ### Added
126
+ - Final screen shot included.
127
+
128
+ ## [1.0.12] - 2025-06-02
129
+
130
+ ### Added
131
+ - Readme docs issue
132
+
133
+ ## [1.0.11] - 2025-06-02
134
+
135
+ ### Added
136
+ - New readme with instructions on install, usage, etc.
137
+
138
+ ## [1.0.10] - 2025-05-29
139
+
140
+ ### Fixed
141
+ - Most MCP clients still don't support images. removed that as a response.
142
+
143
+
144
+ ## [1.0.7] - 2025-05-29
145
+
146
+ ### Fixed
147
+ - Fixed tunneling issues
148
+ - Remove notifications when a token is not provided in the original request
149
+
150
+ ## [1.0.2] - 2025-05-28
151
+
152
+ ### Fixed
153
+ - Fixed ES module path resolution issues
154
+ - Added proper shebang line to executable files
155
+ - Ensured executable permissions are set during build
156
+
157
+ ### Added
158
+ - Docker container support
159
+ - Improved error handling for E2E test runs
160
+
161
+ ## [1.0.1] - 2025-05-28
162
+
163
+ ### Fixed
164
+ - Fixed TypeScript configuration to target ES2022
165
+ - Resolved dependency issues with Zod library
166
+
167
+ ### Added
168
+ - Initial implementation of E2E test runner
169
+ - Integration with DebuggAI server client
170
+
171
+ ## [1.0.0] - 2025-05-28
172
+
173
+ ### Added
174
+ - Initial release of DebuggAI MCP
175
+ - Support for running UI tests via MCP protocol
176
+ - Integration with ngrok for tunnel creation
177
+ - Basic test reporting functionality
package/README.md CHANGED
@@ -8,6 +8,8 @@ AI-powered browser testing via the [Model Context Protocol](https://modelcontext
8
8
 
9
9
  ## Setup
10
10
 
11
+ **Requires Node.js 20.20.0 or later** (transitive requirement from `posthog-node@^5.26.0`).
12
+
11
13
  Get an API key at [debugg.ai](https://debugg.ai), then add to your MCP client config:
12
14
 
13
15
  ```json
@@ -32,16 +34,18 @@ docker run -i --rm --init -e DEBUGGAI_API_KEY=your_api_key quinnosha/debugg-ai-m
32
34
 
33
35
  ## Tools
34
36
 
35
- The server exposes **21** tools. The headline one is `check_app_in_browser`; the rest manage projects, environments, credentials, workflow execution history, and the teams/repos needed to create new projects.
37
+ The server exposes **11** tools grouped into Browser (2), Search (3), Projects (3), and Environments (3). The headline tool is `check_app_in_browser`; the rest manage projects, environments + their credentials, and execution history through a uniform `search_*` + CRUD pattern.
38
+
39
+ ### Browser
36
40
 
37
- ### `check_app_in_browser`
41
+ #### `check_app_in_browser`
38
42
 
39
- Runs an AI browser agent against your app. The agent navigates, interacts, and reports back with screenshots.
43
+ Runs an AI browser agent against your app. The agent navigates, interacts, and reports back with screenshots. Localhost URLs are auto-tunneled via ngrok.
40
44
 
41
45
  | Parameter | Type | Description |
42
46
  |-----------|------|-------------|
43
47
  | `description` | string **required** | What to test (natural language) |
44
- | `url` | string **required** | Target URL — a localhost URL (`http://localhost:3000`) is auto-tunneled via ngrok |
48
+ | `url` | string **required** | Target URL — `http://localhost:3000` is auto-tunneled |
45
49
  | `environmentId` | string | UUID of a specific environment |
46
50
  | `credentialId` | string | UUID of a specific credential |
47
51
  | `credentialRole` | string | Pick a credential by role (e.g. `admin`, `guest`) |
@@ -49,54 +53,43 @@ Runs an AI browser agent against your app. The agent navigates, interacts, and r
49
53
  | `password` | string | Password for login (ephemeral — not persisted) |
50
54
  | `repoName` | string | Override auto-detected git repo name (e.g. `my-org/my-repo`) |
51
55
 
52
- ### Project management
56
+ One focused check per call. The agent has a ~25-step internal budget; split broader suites across multiple calls.
53
57
 
54
- | Tool | Purpose |
55
- |------|---------|
56
- | `list_projects` | List projects accessible to your API key. Optional `q` for name/repo search. |
57
- | `get_project` | Fetch a project by `uuid`. Simplified shape (no team/runner internals). |
58
- | `create_project` | Create a new project. Needs `name`, `platform` (e.g. `web`), `teamUuid` (from `list_teams`), and `repoUuid` (from `list_repos`). |
59
- | `update_project` | PATCH a project's `name` or `description`. |
60
- | `delete_project` | Destructive delete. Cascades envs, creds, and history. |
58
+ #### `trigger_crawl`
61
59
 
62
- ### Teams and repos (prerequisites for `create_project`)
60
+ Fires a server-side browser-agent crawl to populate the project's knowledge graph. Localhost URLs tunnel automatically. Returns `{executionId, status, targetUrl, durationMs, outcome?, crawlSummary?, knowledgeGraph?}` with `knowledgeGraph.imported === true` on successful ingestion.
63
61
 
64
- | Tool | Purpose |
65
- |------|---------|
66
- | `list_teams` | Paginated list of teams accessible to the API key; optional `q` for search. |
67
- | `list_repos` | Paginated list of GitHub-linked repos; optional `q` for search. Use repos with `isGithubAuthorized: true` when creating a project. |
62
+ ### Search (dual-mode: uuid detail OR filtered list)
68
63
 
69
- ### Environment management (scoped to a project)
64
+ Each `search_*` tool has two modes. Pass `{uuid}` for a single-record detail response. Pass filter params for a paginated summary list. 404 from the backend surfaces as `isError: true` with `{error: 'NotFound', message, uuid}`.
70
65
 
71
- | Tool | Purpose |
72
- |------|---------|
73
- | `list_environments` | List envs for a project. Optional `q`, `projectUuid`. |
74
- | `create_environment` | Create a new env. Requires `name` + `url`. |
75
- | `get_environment` | Fetch an env by `uuid`. |
76
- | `update_environment` | PATCH `name` / `url` / `description`. |
77
- | `delete_environment` | Destructive delete. |
66
+ | Tool | UUID mode | Filter mode |
67
+ |------|-----------|-------------|
68
+ | `search_projects` | `{uuid}` curated project detail | `{q?, page?, pageSize?}` → paginated summaries |
69
+ | `search_environments` | `{uuid, projectUuid}` env with credentials inlined | `{projectUuid?, q?, page?, pageSize?}` → paginated envs, each with credentials array |
70
+ | `search_executions` | `{uuid}` full detail with `nodeExecutions` + state | `{status?, projectUuid?, page?, pageSize?}` → paginated summaries |
71
+
72
+ `projectUuid` is optional on `search_environments` if omitted, it auto-resolves from the git repo. Credentials are **always** returned without passwords.
78
73
 
79
- ### Credential management (scoped to an environment)
74
+ ### Projects
80
75
 
81
76
  | Tool | Purpose |
82
77
  |------|---------|
83
- | `list_credentials` | List creds. Optional `environmentId`, `q`, `role` (server-side filter). **Never returns passwords.** |
84
- | `create_credential` | Create a cred. Requires `environmentId`, `label`, `username`, `password`; optional `role`. |
85
- | `get_credential` | Fetch by `uuid` + `environmentId`. |
86
- | `update_credential` | Partial PATCH. Pass `password` to rotate — it is never echoed back. |
87
- | `delete_credential` | Destructive delete. |
78
+ | `create_project` | Requires `name` + `platform`. Team and repo resolve by **either** uuid **or** name: pass `teamUuid` OR `teamName`, and `repoUuid` OR `repoName`. Name resolution is case-insensitive exact match; `NotFound` if none, `AmbiguousMatch` with candidates if multiple. |
79
+ | `update_project` | PATCH `name`, `description`. |
80
+ | `delete_project` | Destructive cascades environments, credentials, and execution history. |
88
81
 
89
- ### Workflow execution history
82
+ ### Environments (credential sub-actions folded in)
90
83
 
91
84
  | Tool | Purpose |
92
85
  |------|---------|
93
- | `list_executions` | Paginated history. Optional `status`, `limit`. |
94
- | `get_execution` | Full detail for a single execution including node-level state. |
95
- | `cancel_execution` | Cancel an in-flight execution. |
86
+ | `create_environment` | Requires `name` + `url`. Optional `credentials: [{label, username, password, role?}]` seeds credentials in the same call. Per-cred failures surface in `credentialWarnings[]` without blocking env creation. |
87
+ | `update_environment` | PATCH env fields (`name`, `url`, `description`) plus credential sub-actions in one call: `addCredentials[]`, `updateCredentials: [{uuid, ...patch}]`, `removeCredentialIds: [uuid]`. Execution order: **remove → update → add** (freed labels can be re-added in one request). |
88
+ | `delete_environment` | Destructive cascades credentials. |
96
89
 
97
90
  ### Pagination
98
91
 
99
- Every `list_*` tool is paginated by default. Response shape:
92
+ Every filter-mode response is paginated. Response shape:
100
93
 
101
94
  ```json
102
95
  {
@@ -106,13 +99,32 @@ Every `list_*` tool is paginated by default. Response shape:
106
99
  }
107
100
  ```
108
101
 
109
- Pass optional `page` (1-indexed, default 1) and `pageSize` (default 20, max 200; oversized values are clamped) to any list tool. No tool ever silently truncates results.
102
+ Pass optional `page` (1-indexed, default 1) and `pageSize` (default 20, max 200; oversized values are clamped). No response is ever silently truncated.
110
103
 
111
104
  ### Security invariants
112
105
 
113
106
  - Passwords are write-only. They never appear in any response body from any tool.
114
107
  - Tunnel URLs (`*.ngrok.debugg.ai`) are stripped from all browser-agent responses, including agent-authored text.
115
108
  - 404s from the backend surface as `isError: true` with `{error: 'NotFound', ...}`, never as thrown exceptions.
109
+ - Missing `DEBUGGAI_API_KEY` surfaces as a structured tool error on first invocation — the server still registers and lists tools normally.
110
+
111
+ ## Migration from v1.x (breaking change in v2.0.0)
112
+
113
+ v2 collapsed a 22-tool surface to 11. Old-tool → new-tool mapping:
114
+
115
+ | Removed | Replacement |
116
+ |---------|-------------|
117
+ | `list_projects`, `get_project` | `search_projects` (uuid mode vs filter mode) |
118
+ | `list_environments`, `get_environment` | `search_environments` |
119
+ | `list_credentials`, `get_credential` | `search_environments` — credentials inline on each env |
120
+ | `create_credential` | `create_environment({credentials: [...]})` seed, or `update_environment({addCredentials: [...]})` |
121
+ | `update_credential` | `update_environment({updateCredentials: [{uuid, ...patch}]})` |
122
+ | `delete_credential` | `update_environment({removeCredentialIds: [uuid]})` |
123
+ | `list_teams`, `list_repos` | `create_project({teamName, repoName})` — name resolution with ambiguity handling |
124
+ | `list_executions`, `get_execution` | `search_executions` |
125
+ | `cancel_execution` | **Dropped** — backend spin-down is automatic |
126
+
127
+ Response-shape changes: the bare `count` field on list responses is gone — use `pageInfo.totalCount`.
116
128
 
117
129
  ## Configuration
118
130
 
@@ -28,7 +28,10 @@ const configSchema = z.object({
28
28
  version: z.string(),
29
29
  }),
30
30
  api: z.object({
31
- key: z.string().min(1, 'API key is required (set DEBUGGAI_API_KEY)'),
31
+ // key is validated at tool-call time (not at boot) so MCP clients can surface
32
+ // a proper error message instead of seeing the subprocess die → "Failed to
33
+ // reconnect". See bead cma + flow 25.
34
+ key: z.string(),
32
35
  tokenType: z.enum(['token', 'bearer']).default('token'),
33
36
  baseUrl: z.string().url().default('https://api.debugg.ai'),
34
37
  }),
@@ -20,7 +20,7 @@ export async function createEnvironmentHandler(input, _context) {
20
20
  if (!repoName) {
21
21
  const payload = {
22
22
  error: 'NoProjectResolved',
23
- message: 'No git repo detected and no projectUuid provided. Pass projectUuid (get it from list_projects) or invoke from a directory with a git origin.',
23
+ message: 'No git repo detected and no projectUuid provided. Pass projectUuid (get it from search_projects) or invoke from a directory with a git origin.',
24
24
  };
25
25
  return { content: [{ type: 'text', text: JSON.stringify(payload, null, 2) }], isError: true };
26
26
  }
@@ -44,6 +44,39 @@ export async function createEnvironmentHandler(input, _context) {
44
44
  projectUuid,
45
45
  environment: env,
46
46
  };
47
+ // Optional credentials seed: best-effort per-cred. Success goes to
48
+ // credentials[]; failure goes to credentialWarnings[] (never blocks env creation).
49
+ if (input.credentials && input.credentials.length > 0) {
50
+ const created = [];
51
+ const warnings = [];
52
+ for (const seed of input.credentials) {
53
+ try {
54
+ const cred = await client.createCredential(projectUuid, env.uuid, {
55
+ label: seed.label,
56
+ username: seed.username,
57
+ password: seed.password,
58
+ role: seed.role,
59
+ });
60
+ // Defensive: drop any stray password from the response shape
61
+ created.push({
62
+ uuid: cred.uuid,
63
+ label: cred.label,
64
+ username: cred.username,
65
+ role: cred.role ?? null,
66
+ environmentUuid: cred.environmentUuid,
67
+ });
68
+ }
69
+ catch (err) {
70
+ warnings.push({
71
+ label: seed.label,
72
+ error: err?.message ?? String(err),
73
+ });
74
+ }
75
+ }
76
+ payload.credentials = created;
77
+ if (warnings.length > 0)
78
+ payload.credentialWarnings = warnings;
79
+ }
47
80
  logger.toolComplete('create_environment', Date.now() - start);
48
81
  return { content: [{ type: 'text', text: JSON.stringify(payload, null, 2) }] };
49
82
  }
@@ -3,26 +3,78 @@ import { handleExternalServiceError } from '../utils/errors.js';
3
3
  import { DebuggAIServerClient } from '../services/index.js';
4
4
  import { config } from '../config/index.js';
5
5
  const logger = new Logger({ module: 'createProjectHandler' });
6
+ function errorResp(error, message, extra = {}) {
7
+ return {
8
+ content: [{
9
+ type: 'text',
10
+ text: JSON.stringify({ error, message, ...extra }, null, 2),
11
+ }],
12
+ isError: true,
13
+ };
14
+ }
15
+ /**
16
+ * Resolve a name to a single uuid via backend search + exact (case-insensitive)
17
+ * match. Returns either a uuid, a NotFound error, or an Ambiguous error with
18
+ * candidate options surfaced.
19
+ */
20
+ function resolveName(name, candidates, kind) {
21
+ const needle = name.toLowerCase();
22
+ const matches = candidates.filter(c => c.name.toLowerCase() === needle);
23
+ if (matches.length === 0) {
24
+ return {
25
+ error: `${kind}NotFound`,
26
+ message: `No ${kind.toLowerCase()} matching "${name}" found. ` +
27
+ (candidates.length > 0
28
+ ? `Available: ${candidates.slice(0, 10).map(c => `"${c.name}"`).join(', ')}`
29
+ : '(none accessible to this API key)'),
30
+ };
31
+ }
32
+ if (matches.length > 1) {
33
+ return {
34
+ error: 'AmbiguousMatch',
35
+ message: `Multiple ${kind.toLowerCase()}s match "${name}". Pass ${kind.toLowerCase()}Uuid explicitly.`,
36
+ candidates: matches.map(m => ({ uuid: m.uuid, name: m.name })),
37
+ };
38
+ }
39
+ return { uuid: matches[0].uuid };
40
+ }
6
41
  export async function createProjectHandler(input, _context) {
7
42
  const start = Date.now();
8
43
  logger.toolStart('create_project', {
9
- name: input.name,
10
- platform: input.platform,
11
- teamUuid: input.teamUuid,
12
- repoUuid: input.repoUuid,
44
+ name: input.name, platform: input.platform,
45
+ teamUuid: input.teamUuid, teamName: input.teamName,
46
+ repoUuid: input.repoUuid, repoName: input.repoName,
13
47
  });
14
48
  try {
15
49
  const client = new DebuggAIServerClient(config.api.key);
16
50
  await client.init();
51
+ // Resolve team
52
+ let teamUuid = input.teamUuid;
53
+ if (!teamUuid && input.teamName) {
54
+ const teamsResp = await client.listTeams({ page: 1, pageSize: 100 }, input.teamName);
55
+ const resolved = resolveName(input.teamName, teamsResp.teams, 'Team');
56
+ if ('error' in resolved)
57
+ return errorResp(resolved.error, resolved.message, { candidates: resolved.candidates });
58
+ teamUuid = resolved.uuid;
59
+ }
60
+ // Resolve repo
61
+ let repoUuid = input.repoUuid;
62
+ if (!repoUuid && input.repoName) {
63
+ const reposResp = await client.listRepos({ page: 1, pageSize: 100 }, input.repoName);
64
+ const resolved = resolveName(input.repoName, reposResp.repos, 'Repo');
65
+ if ('error' in resolved)
66
+ return errorResp(resolved.error, resolved.message, { candidates: resolved.candidates });
67
+ repoUuid = resolved.uuid;
68
+ }
69
+ if (!teamUuid || !repoUuid) {
70
+ // Schema-level invariant should have caught this, but defensive.
71
+ return errorResp('ValidationError', `Unable to resolve ${!teamUuid ? 'team' : 'repo'} — provide teamUuid/teamName and repoUuid/repoName.`);
72
+ }
17
73
  const project = await client.createProject({
18
- name: input.name,
19
- platform: input.platform,
20
- teamUuid: input.teamUuid,
21
- repoUuid: input.repoUuid,
74
+ name: input.name, platform: input.platform, teamUuid, repoUuid,
22
75
  });
23
- const payload = { created: true, project };
24
76
  logger.toolComplete('create_project', Date.now() - start);
25
- return { content: [{ type: 'text', text: JSON.stringify(payload, null, 2) }] };
77
+ return { content: [{ type: 'text', text: JSON.stringify({ created: true, project }, null, 2) }] };
26
78
  }
27
79
  catch (error) {
28
80
  logger.toolError('create_project', error, Date.now() - start);
@@ -1,22 +1,12 @@
1
1
  export * from './testPageChangesHandler.js';
2
2
  export * from './triggerCrawlHandler.js';
3
- export * from './listEnvironmentsHandler.js';
4
- export * from './listCredentialsHandler.js';
5
- export * from './listProjectsHandler.js';
3
+ export * from './searchProjectsHandler.js';
4
+ export * from './searchEnvironmentsHandler.js';
5
+ export * from './searchExecutionsHandler.js';
6
6
  export * from './createEnvironmentHandler.js';
7
- export * from './createCredentialHandler.js';
8
- export * from './getEnvironmentHandler.js';
9
7
  export * from './updateEnvironmentHandler.js';
10
8
  export * from './deleteEnvironmentHandler.js';
11
- export * from './getCredentialHandler.js';
12
- export * from './updateCredentialHandler.js';
13
- export * from './deleteCredentialHandler.js';
14
- export * from './getProjectHandler.js';
9
+ // Credential mutations are folded into create_environment + update_environment.
15
10
  export * from './updateProjectHandler.js';
16
11
  export * from './deleteProjectHandler.js';
17
- export * from './listExecutionsHandler.js';
18
- export * from './getExecutionHandler.js';
19
- export * from './cancelExecutionHandler.js';
20
- export * from './listTeamsHandler.js';
21
- export * from './listReposHandler.js';
22
12
  export * from './createProjectHandler.js';
@@ -0,0 +1,122 @@
1
+ /**
2
+ * search_environments handler (bead 5kw)
3
+ *
4
+ * Absorbs list_environments + get_environment + all credential search.
5
+ * Each environment in the response has its credentials expanded inline.
6
+ *
7
+ * Modes:
8
+ * uuid mode: {uuid, projectUuid?} → {filter:{uuid}, project, pageInfo:{totalCount:1,...},
9
+ * environments:[{...env, credentials:[...]}]}
10
+ * filter mode: {projectUuid?, q?, page?, pageSize?} → paginated list, creds inline per env
11
+ *
12
+ * Invariants:
13
+ * - NEVER returns a password field anywhere in the response (defensive strip at handler edge)
14
+ * - Git-fallback for projectUuid: detectRepoName() → findProjectByRepoName(); NoProjectResolved if both fail
15
+ * - NotFound on unknown uuid returns isError:true
16
+ */
17
+ import { Logger } from '../utils/logger.js';
18
+ import { handleExternalServiceError } from '../utils/errors.js';
19
+ import { DebuggAIServerClient } from '../services/index.js';
20
+ import { config } from '../config/index.js';
21
+ import { detectRepoName } from '../utils/gitContext.js';
22
+ import { toPaginationParams, makePageInfo } from '../utils/pagination.js';
23
+ const logger = new Logger({ module: 'searchEnvironmentsHandler' });
24
+ function stripPassword(cred) {
25
+ // Defensive: take only known-safe keys. Never spread the source.
26
+ return {
27
+ uuid: cred.uuid,
28
+ label: cred.label,
29
+ username: cred.username,
30
+ role: cred.role ?? null,
31
+ ...(cred.environmentUuid ? { environmentUuid: cred.environmentUuid } : {}),
32
+ };
33
+ }
34
+ function notFound(uuid) {
35
+ return {
36
+ content: [{
37
+ type: 'text',
38
+ text: JSON.stringify({ error: 'NotFound', message: `Environment ${uuid} not found.`, uuid }, null, 2),
39
+ }],
40
+ isError: true,
41
+ };
42
+ }
43
+ function noProjectResolved(pagination, reason) {
44
+ return {
45
+ content: [{
46
+ type: 'text',
47
+ text: JSON.stringify({
48
+ error: 'NoProjectResolved',
49
+ message: reason,
50
+ pageInfo: makePageInfo(pagination.page, pagination.pageSize, 0, null),
51
+ environments: [],
52
+ }, null, 2),
53
+ }],
54
+ };
55
+ }
56
+ export async function searchEnvironmentsHandler(input, _context) {
57
+ const start = Date.now();
58
+ const pagination = toPaginationParams({ page: input.page, pageSize: input.pageSize });
59
+ logger.toolStart('search_environments', { ...input, ...pagination });
60
+ try {
61
+ const client = new DebuggAIServerClient(config.api.key);
62
+ await client.init();
63
+ // ── Resolve projectUuid ──
64
+ let projectUuid = input.projectUuid;
65
+ let project = null;
66
+ if (!projectUuid) {
67
+ const repoName = detectRepoName();
68
+ if (!repoName) {
69
+ return noProjectResolved(pagination, 'No git repo detected and no projectUuid provided. Pass projectUuid (get via search_projects) or invoke from a directory with a git origin.');
70
+ }
71
+ const resolved = await client.findProjectByRepoName(repoName);
72
+ if (!resolved) {
73
+ return noProjectResolved(pagination, `No DebuggAI project found for repo "${repoName}". Pass projectUuid explicitly.`);
74
+ }
75
+ projectUuid = resolved.uuid;
76
+ project = { uuid: resolved.uuid, name: resolved.name, repoName: resolved.repo?.name ?? repoName };
77
+ }
78
+ else {
79
+ project = { uuid: projectUuid, name: null, repoName: null };
80
+ }
81
+ // ── uuid mode ──
82
+ if (input.uuid) {
83
+ try {
84
+ const env = await client.getEnvironment(projectUuid, input.uuid);
85
+ const creds = await client.listCredentialsForEnvironment(projectUuid, input.uuid).catch(() => []);
86
+ const payload = {
87
+ project,
88
+ filter: { uuid: input.uuid },
89
+ pageInfo: { page: 1, pageSize: 1, totalCount: 1, totalPages: 1, hasMore: false },
90
+ environments: [{ ...env, credentials: creds.map(stripPassword) }],
91
+ };
92
+ logger.toolComplete('search_environments', Date.now() - start);
93
+ return { content: [{ type: 'text', text: JSON.stringify(payload, null, 2) }] };
94
+ }
95
+ catch (err) {
96
+ if (err?.statusCode === 404 || err?.response?.status === 404)
97
+ return notFound(input.uuid);
98
+ throw err;
99
+ }
100
+ }
101
+ // ── Filter mode ──
102
+ const { pageInfo, environments } = await client.listEnvironmentsPaginated(projectUuid, pagination, input.q);
103
+ // Expand creds per env (sequential — bounded by page size, typically ≤20)
104
+ const withCreds = [];
105
+ for (const env of environments) {
106
+ const creds = await client.listCredentialsForEnvironment(projectUuid, env.uuid).catch(() => []);
107
+ withCreds.push({ ...env, credentials: creds.map(stripPassword) });
108
+ }
109
+ const payload = {
110
+ project,
111
+ filter: { q: input.q ?? null },
112
+ pageInfo,
113
+ environments: withCreds,
114
+ };
115
+ logger.toolComplete('search_environments', Date.now() - start);
116
+ return { content: [{ type: 'text', text: JSON.stringify(payload, null, 2) }] };
117
+ }
118
+ catch (error) {
119
+ logger.toolError('search_environments', error, Date.now() - start);
120
+ throw handleExternalServiceError(error, 'DebuggAI', 'search_environments');
121
+ }
122
+ }