@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.
- package/CHANGELOG.md +177 -0
- package/README.md +49 -37
- package/dist/config/index.js +4 -1
- package/dist/handlers/createEnvironmentHandler.js +34 -1
- package/dist/handlers/createProjectHandler.js +62 -10
- package/dist/handlers/index.js +4 -14
- package/dist/handlers/searchEnvironmentsHandler.js +122 -0
- package/dist/handlers/searchExecutionsHandler.js +71 -0
- package/dist/handlers/searchProjectsHandler.js +72 -0
- package/dist/handlers/testPageChangesHandler.js +46 -5
- package/dist/handlers/triggerCrawlHandler.js +37 -7
- package/dist/handlers/updateEnvironmentHandler.js +94 -15
- package/dist/index.js +15 -2
- package/dist/services/index.js +3 -3
- package/dist/tools/createEnvironment.js +5 -1
- package/dist/tools/createProject.js +6 -4
- package/dist/tools/index.js +9 -42
- package/dist/tools/searchEnvironments.js +35 -0
- package/dist/tools/searchExecutions.js +31 -0
- package/dist/tools/searchProjects.js +30 -0
- package/dist/types/index.js +52 -71
- package/package.json +8 -2
- package/dist/handlers/cancelExecutionHandler.js +0 -41
- package/dist/handlers/createCredentialHandler.js +0 -60
- package/dist/handlers/deleteCredentialHandler.js +0 -51
- package/dist/handlers/getCredentialHandler.js +0 -49
- package/dist/handlers/getEnvironmentHandler.js +0 -49
- package/dist/handlers/getExecutionHandler.js +0 -37
- package/dist/handlers/getProjectHandler.js +0 -37
- package/dist/handlers/listCredentialsHandler.js +0 -93
- package/dist/handlers/listEnvironmentsHandler.js +0 -63
- package/dist/handlers/listExecutionsHandler.js +0 -35
- package/dist/handlers/listProjectsHandler.js +0 -32
- package/dist/handlers/listReposHandler.js +0 -27
- package/dist/handlers/listTeamsHandler.js +0 -27
- package/dist/handlers/updateCredentialHandler.js +0 -70
- package/dist/tools/cancelExecution.js +0 -22
- package/dist/tools/createCredential.js +0 -52
- package/dist/tools/deleteCredential.js +0 -24
- package/dist/tools/getCredential.js +0 -24
- package/dist/tools/getEnvironment.js +0 -23
- package/dist/tools/getExecution.js +0 -22
- package/dist/tools/getProject.js +0 -22
- package/dist/tools/listCredentials.js +0 -30
- package/dist/tools/listEnvironments.js +0 -28
- package/dist/tools/listExecutions.js +0 -24
- package/dist/tools/listProjects.js +0 -27
- package/dist/tools/listRepos.js +0 -23
- package/dist/tools/listTeams.js +0 -23
- 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 **
|
|
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
|
-
|
|
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 —
|
|
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
|
-
|
|
56
|
+
One focused check per call. The agent has a ~25-step internal budget; split broader suites across multiple calls.
|
|
53
57
|
|
|
54
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 |
|
|
72
|
-
|
|
73
|
-
| `
|
|
74
|
-
| `
|
|
75
|
-
| `
|
|
76
|
-
|
|
77
|
-
|
|
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
|
-
###
|
|
74
|
+
### Projects
|
|
80
75
|
|
|
81
76
|
| Tool | Purpose |
|
|
82
77
|
|------|---------|
|
|
83
|
-
| `
|
|
84
|
-
| `
|
|
85
|
-
| `
|
|
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
|
-
###
|
|
82
|
+
### Environments (credential sub-actions folded in)
|
|
90
83
|
|
|
91
84
|
| Tool | Purpose |
|
|
92
85
|
|------|---------|
|
|
93
|
-
| `
|
|
94
|
-
| `
|
|
95
|
-
| `
|
|
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
|
|
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)
|
|
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
|
|
package/dist/config/index.js
CHANGED
|
@@ -28,7 +28,10 @@ const configSchema = z.object({
|
|
|
28
28
|
version: z.string(),
|
|
29
29
|
}),
|
|
30
30
|
api: z.object({
|
|
31
|
-
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
|
|
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
|
-
|
|
11
|
-
|
|
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(
|
|
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);
|
package/dist/handlers/index.js
CHANGED
|
@@ -1,22 +1,12 @@
|
|
|
1
1
|
export * from './testPageChangesHandler.js';
|
|
2
2
|
export * from './triggerCrawlHandler.js';
|
|
3
|
-
export * from './
|
|
4
|
-
export * from './
|
|
5
|
-
export * from './
|
|
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
|
-
|
|
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
|
+
}
|