Skip to content

feat: add Harness File Store MCP support with multipart uploads#211

Draft
ritek01 wants to merge 3 commits into
mainfrom
feat/harness-file-store
Draft

feat: add Harness File Store MCP support with multipart uploads#211
ritek01 wants to merge 3 commits into
mainfrom
feat/harness-file-store

Conversation

@ritek01
Copy link
Copy Markdown

@ritek01 ritek01 commented May 15, 2026

Expose list/get/create/update/delete and folder list_children via the registry; teach HarnessClient to post FormData without forcing JSON.

This pull request adds full support for Harness File Store operations, including robust handling of multipart/form-data requests, and introduces a new file_store toolset for managing files and folders. The changes ensure that multipart bodies are properly detected and handled throughout the client, avoiding incorrect content-type headers and unnecessary JSON serialization. Validation and body injection logic are also updated to account for FormData bodies. Comprehensive tests are added to verify correct behavior.

Harness Client: Multipart/form-data Handling

  • Added isFormDataBody utility and updated all request logic in HarnessClient to detect FormData bodies, avoid forcing JSON or YAML content-type, and ensure multipart requests are sent correctly. Also updated logging to redact multipart bodies and fetch logic to use the correct body type. [1] [2] [3] [4] [5] [6] [7]

Registry: File Store Toolset and Multipart-aware Logic

  • Introduced a new file_store toolset in the registry, with resource definitions, operations for list/get/create/update/delete, and action for listing folder children. Includes body builders for multipart requests and comprehensive field schemas. [1] [2] [3] [4]
  • Updated scope body injection and validation logic to skip FormData bodies, ensuring multipart requests are not mutated or validated as JSON. [1] [2] [3]

Tests

  • Added a test to verify that posting FormData does not force a JSON content-type header and that the body is sent as-is.

Description

Type of Change

  • Bug fix
  • New feature
  • Refactor
  • Documentation
  • Other

Checklist

  • Tests pass
  • Typecheck passes

Copy link
Copy Markdown
Contributor

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

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

Stale comment

Found a few architecture-standard issues on the live PR head (c9e36c6).

  1. file_store.list_children breaks the shared harness_execute contract because the action ignores the resource's primary resource_id mapping.
  2. Invalid content_base64 does not fail loud and can silently upload corrupted bytes.
  3. The README count bump documents larger totals but still omits the new File Store resource/toolset from the discoverability tables.
  4. The File Store delete description points agents to filters.force_delete, but harness_delete only accepts params.

Assumptions / notes:

  • Reviewed against the live PR head, not just the trigger snapshot.
  • CI is green, and I also ran local verification after installing from the existing lockfile.

Verification:

  • pnpm test tests/client/harness-client.test.ts tests/registry/file-store-multipart.test.ts
  • pnpm typecheck
  • pnpm build
  • pnpm docs:check

One nuance from the docs check: it passes here because scripts/generate-docs.js only patches numeric counts, so it does not catch the missing File Store entries in the README tables.

Open in Web View Automation 

Sent by Cursor Automation: Sunil On Demand Architecture Review

if (rawBody !== undefined && typeof rawBody === "object" && rawBody !== null && !Array.isArray(rawBody)) {
return rawBody;
}
const id = input.folder_identifier ?? input.identifier;
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.

file_store registers identifierFields: ["file_store_id"], so the generic harness_execute(resource_id=...) path maps the primary ID onto input.file_store_id before dispatch. This helper only checks folder_identifier / identifier, which means the shared execute contract is broken for list_children: a normal resource_id call is ignored unless the caller redundantly provides an undocumented alias.

Please read input.file_store_id here (or adjust the action contract / identifierFields) and add a tool-level regression test for harness_execute(..., resource_id=...).

(typeof b.file_name === "string" && b.file_name) ||
name;
if (typeof b.content_base64 === "string" && b.content_base64 !== "") {
const buf = Buffer.from(b.content_base64, "base64");
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.

Buffer.from(str, "base64") is permissive and will happily turn malformed input into garbage bytes instead of throwing. I verified locally that buildFileStoreMultipartBody(..., { content_base64: "not-base64" }) still produces a non-empty Blob, so this write path can succeed with corrupted content instead of failing loud.

Given the repo's validation standard, please validate content_base64 (for example via a canonical base64/round-trip check) before building the multipart body, and add a negative test.

Comment thread README.md
## Resource Types

169 resource types organized across 31 toolsets. Each resource type supports a subset of CRUD operations and optional execute actions.
170 resource types organized across 32 toolsets. Each resource type supports a subset of CRUD operations and optional execute actions.
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.

The numeric totals were updated here, but the README still never documents the new surface area: there is no File Store section in the Resource Types catalog below, and there is still no file_store row in the "Available toolset names" table.

pnpm docs:check won't catch this because scripts/generate-docs.js only patches counts. Please either document the new resource/toolset in the README tables or strengthen the docs parity check so discoverability stays consistent with the registry.

Comment thread src/registry/toolsets/file-store.ts Outdated
ritek01 and others added 2 commits May 15, 2026 12:44
Expose list/get/create/update/delete and folder list_children via the
registry; teach HarnessClient to post FormData without forcing JSON.

Co-authored-by: Cursor <cursoragent@cursor.com>
@Kazzhar Kazzhar force-pushed the feat/harness-file-store branch from c9e36c6 to 3451df9 Compare May 15, 2026 19:44
Copy link
Copy Markdown
Contributor

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

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

  • Criticalsrc/registry/toolsets/file-store.ts:51-60, src/registry/index.ts:691-695, tests/registry/file-store-multipart.test.ts:55-68: buildFileStoreMultipartBody() silently falls back to "Root" when parent_identifier is omitted, even for update. Because FormData bodies now bypass the generic required-field validation, a rename/content-only update turns into a move-to-root request instead of failing loud. The new tests only cover the explicit-parent case, so this regression is currently unguarded.
  • Importantsrc/registry/toolsets/file-store.ts:259-260, src/tools/harness-delete.ts:20-27: the new delete description tells callers to use filters.force_delete, but harness_delete does not expose a filters argument; only params is accepted. Agents following harness_describe will generate an invalid tool call for the one File Store delete override this PR adds.
  • ImportantREADME.md:955-957, README.md:1403-1475, src/registry/index.ts:147-158: the README increments the counts to 170 resource types / 33 toolsets, but it still never documents the actual new file_store toolset or resource name in Available toolset names or under ## Resource Types. That leaves the docs internally inconsistent: users can see the counts changed, but still cannot discover the new toolset/resource name to enable, filter, or query it.
Open in Web View Automation 

Sent by Cursor Automation: Sunil On Demand Architecture Review

Copy link
Copy Markdown
Contributor

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

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

High

  1. src/registry/toolsets/file-store.ts: list_children breaks the generic resource_id contract. harness_execute maps resource_id to file_store_id, but the shorthand folder builder ignores that field and only accepts folder_identifier / identifier, so the primary execute path fails unless callers duplicate the folder id in params.
  2. src/registry/toolsets/file-store.ts: update requests silently fall back parent_identifier to "Root". The File Store update API requires the actual parent identifier, so a rename or content replace without that field can move the node to the root instead of failing fast.
  3. src/registry/toolsets/file-store.ts: malformed content_base64 is accepted and decoded into garbage bytes. Buffer.from(..., "base64") does not validate input, so this violates the server’s fail-loudly standard and can corrupt uploaded content.

Assumptions

  • Reviewed the live PR head 3451df9.
  • CI is green.
  • I couldn’t rerun local vitest / tsc in this workspace because node_modules is not present.

Change summary

  • The multipart upload plumbing and new file_store registry entry fit the existing direction, but the three issues above still keep it short of Sunil’s shared-contract / fail-loudly standards.
Open in Web View Automation 

Sent by Cursor Automation: Sunil On Demand Architecture Review

}

const parentIdentifier =
(b.parent_identifier ?? b.parentIdentifier ?? "Root") as unknown;
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.

Defaulting a missing parent_identifier to "Root" is risky for updates. The File Store update API requires the real parentIdentifier, so a caller that only wants to rename a file or replace its content will silently move the node to the root instead of getting a validation error. This should fail loud on update when the parent id is missing, rather than substituting a different location.

(typeof b.file_name === "string" && b.file_name) ||
name;
if (typeof b.content_base64 === "string" && b.content_base64 !== "") {
const buf = Buffer.from(b.content_base64, "base64");
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.

Buffer.from(value, "base64") does not validate that the input is actually valid base64; malformed strings decode into garbage bytes instead of throwing. That means content_base64 can succeed with corrupted uploads, which cuts against the server’s fail-loudly standard. Please validate the encoding before constructing the blob and add a focused regression test for the malformed-input case.

if (rawBody !== undefined && typeof rawBody === "object" && rawBody !== null && !Array.isArray(rawBody)) {
return rawBody;
}
const id = input.folder_identifier ?? input.identifier;
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.

This shorthand path ignores the resource’s primary identifier field. harness_execute maps resource_id to file_store_id for file_store, but buildFolderNodesBody() only looks at folder_identifier / identifier, so harness_execute(resource_type="file_store", action="list_children", resource_id="...") still fails unless the caller redundantly passes the folder id again in params. That breaks the shared generic-tool contract; this builder should honor input.file_store_id as well.

Signed-off-by: Karthik Nayak <karthik.nayak@harness.io>
Copy link
Copy Markdown
Contributor

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

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

Findings

  1. src/registry/toolsets/file-store.ts: buildFileStoreMultipartBody() still defaults a missing parent_identifier to Root on update. The Harness File Store update API expects the real parent identifier, so a rename/content-only update can silently move a node to the root instead of failing loudly.
  2. src/registry/toolsets/file-store.ts: content_base64 is still not validated before Buffer.from(..., "base64"). Node accepts malformed input here; I reproduced content_base64: "not-base64" producing a 7-byte blob, so corrupted uploads can succeed instead of surfacing an error.
  3. src/registry/toolsets/file-store.ts: list_children still breaks the shared generic-tool contract. harness_execute(resource_id=...) maps the primary ID to file_store_id, but buildFolderNodesBody() only reads folder_identifier / identifier, so the execute path fails unless callers redundantly pass the folder ID again in params.
  4. README.md: the counts were updated, but the new file_store resource/toolset still is not documented in the Resource Types or Available toolset tables, so README discoverability is out of sync with the registry.

Assumption

I checked the Harness File Store docs while reviewing this; they describe parentIdentifier as required on update, which is why I’m treating the Root fallback as behaviorally unsafe rather than just a docs mismatch.

Change Summary

The multipart plumbing in src/client/harness-client.ts and src/registry/index.ts looks good. The remaining gaps are in the file_store toolset contract plus README parity.

Verification

pnpm typecheck
pnpm exec vitest run tests/registry/file-store-multipart.test.ts tests/client/harness-client.test.ts
pnpm exec vitest run tests/tools/tool-handlers.test.ts -t "harness_execute"

Open in Web View Automation 

Sent by Cursor Automation: Sunil On Demand Architecture Review

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.

2 participants