Skip to content

Add MCP tools to delete database tables, team databases, shared library entries and git tokens - #8857

Merged
andypalmi merged 9 commits into
mainfrom
feat/mcp-team-data-delete-tools
Oct 14, 2026
Merged

andypalmi merged 9 commits into
mainfrom
feat/mcp-team-data-delete-tools

Conversation

@andypalmi

@andypalmi andypalmi commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

Closes #7710

Adds the four delete tools for team data, all annotated destructiveHint: true so they are served as destructive tools: platform_delete_database_table, platform_delete_team_database, platform_delete_library_entry (new library.js) and platform_delete_git_token (in teams.js, next to platform_list_team_git_tokens). Each description states what is lost and what else breaks, so the caller can confirm with the user first. Every tool replies { status: "okay" } on success, except the library one which passes the route reply through, { status, deleteCount }. Error responses are passed through unchanged.

Things I checked against the real routes:

  1. DELETE .../tables/:tableName takes the schema as an optional trailing path segment, not a query parameter. schemaName is required, as in the get and query table tools, so the tool always deletes the table in the given schema. An empty or dot table or schema name drops out of the URL and would delete the table from whichever schema it is in, so the tool rejects those.
  2. The table route replies 204 with an empty body, which the response formatter cannot parse, so the tool reports { status: "okay" } through emptySuccessAsOkay. The database and git token routes reply {}, which is mapped to the same shape.
  3. Deleting a database drops every table in every schema and its login, and it does not need to be empty. A second delete returns 404, so all four tools set idempotentHint: true.
  4. The library route is /storage/library/:libraryId/*, with no /api/v1 prefix as the issue lists it. The tool takes teamId, since the library id is the team hashid.
  5. A library delete with an exact path match removes that one entry. Otherwise the path is a folder and everything beneath it is removed, across all types. type only narrows the match, and without it one arbitrary entry is removed when several types share a path.
  6. The tool refuses empty, . and .. segments and encodes each segment. The URL resolves . and .. segments, even encoded ones, before the route sees the path, so without this check a/../b would delete b.
  7. The library, git token and tables routes say in their 404 when the feature is disabled for the team, and the tools pass it through.
  8. Deleting a git token that a pipeline stage uses fails with 409 token_in_use naming the pipelines, which the description explains.
  9. Ids that go into the URL path are checked by the shared path guard from Reject path-breaking ids in MCP tool inputs #8843, because inject resolves dot segments and an id such as ../teams/<id> would reach another route.
  10. All four deletes are audit logged by the routes. A failed database drop, for example while a client is still connected, returns 500 and keeps the database, and a retry finishes the delete.
  11. Read-only tokens get 403 on all four routes, and a team Member gets 403 on the table, database and git token deletes (owner only) but may delete library entries.

Issues found while testing, fixed in the routes:

  1. Shared library routes return 404 to personal access tokens and MCP tokens #8847: the shared library routes returned 404 to every personal access token and OAuth token. Fixed in Let user tokens use the team shared library #8858.
  2. Shared library delete removes entries it should not: empty path wipes the library and "_" and "%" act as wildcards #8848: an empty library path deleted the whole library, and _ and % acted as wildcards. Fixed in Match shared library folders literally and reject an empty delete path #8878.
  3. Deleting a team database reports success and audit-logs it even when the database drop failed #8849: deleting a team database reported success and audit-logged it even when the drop failed. Fixed in Report a failed team database delete #8880.
  4. Deleting a git token that pipeline stages use leaves the stages broken with an opaque error #8850: deleting a git token left the pipeline stages that use it broken. Fixed in Refuse to delete a git token that pipeline stages use #8881.
  5. Git token and shared library changes are not audit logged #8851: git token and shared library changes were not audit logged. Fixed in Audit log git token and shared library changes #8882.
  6. Table delete returns "Table not found" for a table name used in two schemas #8852: a table name used in two schemas gave a misleading 404. Fixed in Ask for a schema when a table name exists in several schemas #8883.
  7. Shared library and git token routes do not say the feature is disabled in their 404 #8855: the library and git token routes did not say the feature is disabled in their 404. Fixed in Name the disabled feature in git and shared library 404s #8886.

Merge after #8878 and #8880.

@andypalmi
andypalmi requested a review from cstns October 8, 2026 12:54
@codecov

codecov Bot commented Oct 8, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 79.05%. Comparing base (10a3702) to head (0bf7e15).
⚠️ Report is 2 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #8857      +/-   ##
==========================================
+ Coverage   79.01%   79.05%   +0.03%     
==========================================
  Files         475      476       +1     
  Lines       25820    25863      +43     
  Branches     6867     6875       +8     
==========================================
+ Hits        20402    20445      +43     
  Misses       5418     5418              
Flag Coverage Δ
backend 79.05% <100.00%> (+0.03%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

},
handler: async (args, { inject }) => {
const segments = args.path.split('/')
if (segments.some(segment => segment === '' || segment === '.' || segment === '..')) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A folder delete goes through byPath, which matches with an unescaped LIKE '<path>%', so deleting my_utils also wipes myXutils/... for the whole team, with no undo. The description warns about it but the call still goes through. Until #8848 lands, might be worth just refusing folder paths with _ or % here, it's one line and it takes the data loss off the table.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is fixed in the route by #8878, which matches folder paths literally. I removed the warning from the description, and this merges after #8878.

Comment thread forge/ee/lib/mcp/tools/library.js Outdated
CAUTION: flows and snippets that other members of the team load from the library are gone for everyone. Confirm with the user before calling this, naming the entry or the folder.
Entries of different types can share a path. Pass type to delete only the entry of that type; without it one of the entries at that path is deleted, and which one is not defined. A folder delete without type covers all types.
Replies { status: "okay", deleteCount } with the number of entries removed. A path that matches nothing returns 404, so repeating a call is harmless; the 404 is the same when the shared library is not enabled for the team, the team does not exist, or the caller is not a member of it.`,
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false },

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

idempotentHint: true (and "repeating a call is harmless") doesn't hold for folder deletes: a retry deletes anything added under that path since the first call. Exact entry deletes are fine. Probably worth dropping the hint on this one, or at least scoping the wording to single entries.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. I dropped idempotentHint and the "harmless" wording. The description now says a repeated folder delete also removes anything added under the folder since the first call.

if (response.statusCode >= 400) {
return response
}
return { statusCode: response.statusCode, json: () => ({ status: 'okay' }) }

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because of #8849 the route can answer 200 even when the drop failed, so this okay (and the description saying a repeat gives 404) tells the agent the data is gone when it might not be. Can't fix that here, but maybe a line in the description pointing at #8849 so the agent doesn't promise the user more than we know.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is fixed in the route by #8880. A failed drop now returns 500 and keeps the database, and a retry finishes the delete. The description now says this, and this merges after #8880.

…lete-tools

# Conflicts:
#	forge/ee/lib/mcp/tools/library.js
#	forge/ee/lib/mcp/tools/teams.js
#	test/unit/forge/ee/lib/mcp/tools/library_spec.js
#	test/unit/forge/ee/lib/mcp/tools/teams_spec.js
@andypalmi
andypalmi requested a review from cstns October 12, 2026 15:27
@andypalmi
andypalmi enabled auto-merge (squash) October 14, 2026 07:13

@cstns cstns left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

merge after #8880

@andypalmi
andypalmi merged commit 111f500 into main Oct 14, 2026
29 checks passed
@andypalmi
andypalmi deleted the feat/mcp-team-data-delete-tools branch October 14, 2026 07:17

This branch was successfully deployed

1 active deployment
staging — 0bf7e154 Deployed Oct 14, 2026 by andypalmi via Remove application #12464
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5.12-c Delete and destructive tools (phase 2)

2 participants