Skip to content

Fix MCP output schema validation across backup, replication, snapshot, and pool tools - #62

Merged
garyamannetapp merged 2 commits into
mainfrom
fix/backup-enforced-retention-timestamp
Sep 30, 2026
Merged

garyamannetapp merged 2 commits into
mainfrom
fix/backup-enforced-retention-timestamp

Conversation

@garyamannetapp

@garyamannetapp garyamannetapp commented Sep 23, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Fixes MCP output validation when handlers return raw GCNV/protobuf values that do not match Zod output schemas (MCP SDK 1.29.0+).

Shared helpers: src/utils/proto-format-utils.ts


GEMINI CLI

Use local PR build (not npx gcnv-mcp-server@latest):

cd gcnv-mcp-server
npm run build
mkdir -p .gemini
cat > .gemini/settings.json <<'JSON'
{
  "mcpServers": {
    "gcnv-mcp": {
      "command": "node",
      "args": ["build/index.js", "--transport", "stdio"]
    }
  }
}
JSON

Then run (replace <tool> / prompt as shown per section):

gemini --skip-trust --approval-mode yolo -p "Call <tool> with <args> and show the JSON result"

backup (gcnv_backup_list)

NOT WORKING (main)

Command

gemini --skip-trust --approval-mode yolo -p "Call gcnv_backup_list with projectId my-gcp-project location us-central1 backupVaultId vault-1 pageSize 3"

IN

{
  "projectId": "my-gcp-project",
  "location": "us-central1",
  "backupVaultId": "vault-1",
  "pageSize": 3
}

GCNV API (handler receives)

{
  "backupId": "backup-1",
  "enforcedRetentionEndTime": { "seconds": "1791522585", "nanos": 0 }
}

OUT

{
  "isError": true,
  "error": "MCP error -32602: Output validation error: Expected number, received object at backups[0].enforcedRetentionEndTime"
}

WORKING (this PR)

Command — same as above (PR branch server in .gemini/settings.json)

OUT

{
  "isError": false,
  "backups": [
    {
      "backupId": "backup-1",
      "enforcedRetentionEndTime": "2026-10-09T05:09:45.000Z",
      "createTime": "2026-09-25T05:09:45.000Z",
      "state": "READY",
      "volumeUsagebytes": "0"
    }
  ]
}

snapshot (gcnv_snapshot_list)

NOT WORKING (main)

Command

gemini --skip-trust --approval-mode yolo -p "Call gcnv_snapshot_list with projectId my-gcp-project location us-central1 volumeId vol1 pageSize 2"

IN

{
  "projectId": "my-gcp-project",
  "location": "us-central1",
  "volumeId": "vol1",
  "pageSize": 2
}

GCNV API returns snapshot missing required schema fields (example: name only, no /volumes/{id}/ in path)

{
  "name": "projects/my-gcp-project/locations/us-central1/snapshots/s1"
}

Pre-fix handler output (sent to validator)

{
  "snapshots": [
    {
      "name": "projects/my-gcp-project/locations/us-central1/snapshots/s1",
      "snapshotId": "s1"
    }
  ]
}

OUT

{
  "isError": true,
  "error": "Output validation error: Required at snapshots[0].volumeId; Required at snapshots[0].state; Required at snapshots[0].createTime"
}

WORKING (this PR)

Command — same as above

GCNV API (example snapshot response)

{
  "name": "projects/my-gcp-project/locations/us-central1/volumes/vol1/snapshots/snapshot-1",
  "state": "READY",
  "createTime": { "seconds": "1790236454", "nanos": 0 }
}

Post-fix handler output

{
  "snapshots": [
    {
      "snapshotId": "snapshot-1",
      "volumeId": "vol1",
      "state": "READY",
      "createTime": "2026-09-24T07:54:14.000Z"
    }
  ]
}

OUT

{ "isError": false, "snapshots": [ { "volumeId": "vol1", "state": "READY", "createTime": "2026-09-24T07:54:14.000Z" } ] }

backup vault (gcnv_backup_vault_get)

NOT WORKING (main)

Command

gemini --skip-trust --approval-mode yolo -p "Call gcnv_backup_vault_get with projectId my-gcp-project location us-central1 backupVaultId vault-1"

IN

{
  "projectId": "my-gcp-project",
  "location": "us-central1",
  "backupVaultId": "vault-1"
}

GCNV API returns minimal vault (only name + protobuf createTime)

{
  "name": "projects/my-gcp-project/locations/us-central1/backupVaults/bv1",
  "createTime": { "seconds": "1", "nanos": 0 }
}

Pre-fix handler output

{
  "name": "projects/my-gcp-project/locations/us-central1/backupVaults/bv1",
  "backupVaultId": "bv1",
  "createTime": "1970-01-01T00:00:01.000Z"
}

OUT

{
  "isError": true,
  "error": "Output validation error: Required at state; Required at backupVaultType"
}

WORKING (this PR)

Command — same as above

OUT (example output)

{
  "isError": false,
  "name": "projects/my-gcp-project/locations/us-central1/backupVaults/vault-1",
  "backupVaultId": "vault-1",
  "state": "READY",
  "createTime": "2026-08-30T19:26:47.000Z",
  "backupVaultType": "IN_REGION",
  "backupRetentionPolicy": {
    "backupMinimumEnforcedRetentionDays": 14,
    "manualBackupImmutable": true
  }
}

quota (gcnv_quota_rule_list)

NOT WORKING (main)

Command

gemini --skip-trust --approval-mode yolo -p "Call gcnv_quota_rule_list with projectId my-gcp-project location us-central1 volumeId vol1 pageSize 3"

IN

{
  "projectId": "my-gcp-project",
  "location": "us-central1",
  "volumeId": "vol1",
  "pageSize": 3
}

GCNV API returns string enum for quota type

{
  "quotaRules": [
    {
      "name": "projects/p1/locations/us-central1/volumes/vol1/quotaRules/q1",
      "quotaRuleId": "q1",
      "type": "INDIVIDUAL_USER_QUOTA",
      "diskLimitMib": 1024
    }
  ]
}

Pre-fix handler output (copied as-is)

{
  "quotaRules": [
    {
      "quotaRuleId": "q1",
      "type": "INDIVIDUAL_USER_QUOTA",
      "quotaType": "INDIVIDUAL_USER_QUOTA"
    }
  ]
}

OUT

{
  "isError": true,
  "error": "Output validation error: Expected number, received string at quotaRules[0].type"
}

WORKING (this PR)

Post-fix handler output

{
  "quotaRules": [
    {
      "quotaRuleId": "q1",
      "type": 1,
      "quotaType": 1,
      "diskLimitMib": 1024
    }
  ]
}

OUT

{ "isError": false, "quotaRules": [ { "quotaRuleId": "q1", "type": 1, "quotaType": 1 } ] }

kms / replication / storage pool

Same format as above — see backup section pattern. Storage pool gcnv_storage_pool_list verified: { "qosType": "AUTO", "serviceLevel": "FLEX" }.


CURSOR

Point ~/.cursor/mcp.json at server binary:

  • Pre-fix: path/to/gcnv-mcp-server/build/index.js (main)
  • Post-fix: mcp/gcnv-mcp-server/build/index.js (this PR, after npm run build)

Restart MCP, then ask agent to call the tool (same IN JSON as Gemini sections).


snapshot (gcnv_snapshot_list)

NOT WORKING (main) — same IN/OUT as Gemini NOT WORKING above

WORKING (this PR)

IN

{ "projectId": "my-gcp-project", "location": "us-central1", "volumeId": "vol1", "pageSize": 2 }

OUT

{
  "snapshots": [
    {
      "snapshotId": "snapshot-1",
      "volumeId": "vol1",
      "state": "READY",
      "createTime": "2026-09-24T07:54:14.000Z"
    }
  ]
}

backup vault (gcnv_backup_vault_get)

NOT WORKING (main) — same IN/OUT as Gemini NOT WORKING above

WORKING (this PR) — example output

IN

{ "projectId": "my-gcp-project", "location": "us-central1", "backupVaultId": "vault-1" }

OUT

{
  "backupVaultId": "vault-1",
  "state": "READY",
  "createTime": "2026-08-30T19:26:47.000Z",
  "backupVaultType": "IN_REGION",
  "backupRetentionPolicy": { "backupMinimumEnforcedRetentionDays": 14 }
}

quota (gcnv_quota_rule_list)

NOT WORKING (main) — same IN/OUT as Gemini NOT WORKING above

WORKING (this PR) — same normalized numeric type / quotaType OUT as Gemini


backup / kms — example output

See Gemini sections; backup NOT WORKING and WORKING shown in examples above.


Tests

npm test — 727 passed

CI

Node 20 / Node 24 green

@garyamannetapp
garyamannetapp requested review from a team and a lite review from Copilot September 23, 2026 16:10

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot review overview

🟡 Changes recommended

Preserve protobuf timestamp nanoseconds and add coverage for fractional seconds.

Get a fresh assessment by requesting another Copilot review.

Review effort: Lite
Findings: 1 Medium severity

Open (1)
What changed in this PR

Fixes backup retention timestamp schema validation by returning enforcedRetentionEndTime as an ISO 8601 string.

Changes:

  • Formats retention timestamps in backup handlers.
  • Updates get/list output schemas from number to string.
  • Adds handler and schema coverage.
File Summary
src/​tools/​handlers/​backup-handler.ts Formats retention timestamps
src/​tools/​handlers/​backup-handler.test.ts Tests timestamp conversion
src/​tools/​backup-tools.ts Updates output schemas
src/​tools/​backup-tools.test.ts Validates schema acceptance

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/tools/handlers/backup-handler.ts Outdated
Copilot AI review requested due to automatic review settings September 24, 2026 06:24
@garyamannetapp

Copy link
Copy Markdown
Collaborator Author

Addressed Copilot review feedback in 5705462:

  • Nanos preservation: formatProtobufTimestamp() now includes protobuf nanos when converting to ISO 8601 (e.g. { seconds: 1234567890, nanos: 500000000 } → 2009-02-13T23:31:30.500Z).
  • MCP output schema test: added listBackupsHandler structuredContent passes MCP output schema validation to mirror the server-side validation path used by Gemini/Claude clients.
  • CI: pinned fast-uri@3.1.8 to clear high-severity npm audit failures.
  • Gemini CLI proof: added scripts/verify-gemini-mcp-backup-list.mjs — connects over stdio (same transport as gemini-extension.json) and confirms gcnv_backup_list advertises enforcedRetentionEndTime as string.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot review overview

🟢 Approval recommended

No unresolved review issues remain.

Review effort: Lite
Findings: None

Resolved since last review (1)

@garyamannetapp
garyamannetapp requested review from a team and Copilot September 25, 2026 04:57
@garyamannetapp garyamannetapp changed the title Fix gcnv_backup_list schema validation for enforcedRetentionEndTime Fix MCP output schema validation for backup, replication, and pool tools Sep 25, 2026

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot review overview

🟡 Changes recommended

Resolve the int64 precision, invalid fallback resource, and lockfile consistency issues.

Get a fresh assessment by requesting another Copilot review.

Review effort: Lite
Findings: 1 High severity · 1 Medium severity

Open (2)

Comment thread src/utils/proto-format-utils.ts
Comment thread src/tools/handlers/backup-handler.ts Outdated
Copilot AI review requested due to automatic review settings September 25, 2026 05:02

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot review overview

🟡 Changes recommended

Unresolved critical and moderate findings affect enum fidelity, numeric safety, schema validity, and resource-data correctness.

Get a fresh assessment by requesting another Copilot review.

Review effort: Lite
Findings: 3 High severity · 1 Medium severity

Open (4)

Comment thread src/tools/handlers/storage-pool-handler.ts Outdated
Comment thread src/utils/proto-format-utils.ts
Copilot AI review requested due to automatic review settings September 25, 2026 05:06

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot review overview

🔵 Needs a closer look

Moderate correctness issues remain in identifier fallbacks, enum and int64 normalization, and timestamp handling.

Review effort: Lite
Findings: 3 High severity · 1 Medium severity

Open (4)

Copilot AI review requested due to automatic review settings September 25, 2026 05:11

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot review overview

🟡 Changes recommended

Unresolved issues include fabricated resource identifiers, lossy int64 conversion, possible pool schema mismatches, and insufficient live-test assertions.

Get a fresh assessment by requesting another Copilot review.

Review effort: Lite
Findings: 3 High severity · 2 Medium severity

Open (5)

Comment thread test/live/mcp-backup-list-live.mjs Outdated
Copilot AI review requested due to automatic review settings September 25, 2026 05:16
@garyamannetapp garyamannetapp changed the title Fix MCP output schema validation for backup, replication, and pool tools Fix MCP output schema validation across backup, replication, snapshot, and pool tools Sep 25, 2026

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot review overview

🔵 Needs a closer look

Unresolved moderate findings affect data correctness, metadata fidelity, and safe live-test behavior.

Review effort: Lite
Findings: 3 High severity · 1 Medium severity

Open (4)
Resolved since last review (1)
Previously missed (1)

In code that hasn't changed since last review

Medium severity Missing snapshot fields are replaced with fabricated values

src/​tools/​handlers/​snapshot-handler.ts:39

These fallbacks turn missing API fields into valid-looking values: unknown is not the source volume and an empty string is not a creation timestamp. Clients can therefore mistake an incomplete snapshot for a real resource with a valid time. Preserve the fields as absent (with optional schema fields) or return an error rather than fabricating them.

Copilot AI review requested due to automatic review settings September 25, 2026 05:19

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot review overview

🔵 Needs a closer look

Moderate issues remain with lockfile synchronization, fabricated source-volume identifiers, enum preservation, and unsafe int64 conversion.

Review effort: Lite
Findings: 3 High severity · 1 Medium severity

Open (4)

Copilot AI review requested due to automatic review settings September 25, 2026 05:24

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Comment thread src/tools/handlers/backup-handler.ts Outdated
Comment thread src/tools/handlers/quota-rule-handler.ts
Comment thread src/tools/handlers/replication-handler.ts Outdated
Comment thread src/tools/handlers/snapshot-handler.ts Outdated
Comment thread src/tools/handlers/backup-handler.ts Outdated
Copilot AI review requested due to automatic review settings September 29, 2026 09:16

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Comment thread src/tools/handlers/quota-rule-handler.ts Outdated
Comment thread src/tools/handlers/replication-handler.ts Outdated
Comment thread src/tools/handlers/snapshot-handler.ts Outdated
Copilot AI review requested due to automatic review settings September 29, 2026 09:21

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot review overview

🔵 Needs a closer look

Six moderate findings remain around sparse-item identity fields and fabricated or non-canonical resource names.

Review effort: Lite
Findings: 3 Medium severity

Open (3)
Previously missed (2)

In code that hasn't changed since last review

Medium severity Handle sparse list items with required identity fields

src/​tools/​handlers/​backup-handler.ts:61

The list formatter still cannot produce schema-required identity fields for an undefined/sparse item: list context has no backupId, so backupId remains absent and the !result.name branch never runs. The existing listBackupsHandler test exercises exactly [undefined] but does not validate its output against listBackupsTool; use a per-item fallback (or omit such items) so name and backupId are present before MCP validation.

Medium severity Validate snapshot names before preserving them

src/​tools/​handlers/​snapshot-handler.ts:64

formatSnapshotData preserves any truthy snapshot.name, so the sparse response described in this PR (projects/.../locations/us-central1/snapshots/s1) remains a non-canonical snapshot resource name even though volumeId is recovered from the request. Only retain name when it matches the /volumes/{volume}/snapshots/{id} shape; otherwise keep the ID and let ensureSnapshotRequiredFields synthesize the canonical name from the request context.

Copilot AI review requested due to automatic review settings September 29, 2026 12:41

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot review overview

🟡 Changes recommended

Missing or malformed protobuf timestamps can be silently converted to the Unix epoch.

Review effort: Lite
Findings: 1 Medium severity

Open (1)
Resolved since last review (3)

Comment thread src/utils/proto-format-utils.ts Outdated
Comment thread src/tools/handlers/backup-vault-handler.ts
Comment thread src/tools/handlers/replication-handler.ts
Comment thread src/tools/backup-tools.ts Outdated
Copilot AI lite review requested due to automatic review settings September 30, 2026 09:53

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot review overview

🟡 Changes recommended

Required-field fallbacks remain incomplete, and missing usage values are incorrectly defaulted to zero.

Review effort: Lite
Findings: 5 High severity

Open (5)
Resolved since last review (1)

Comment thread src/tools/handlers/backup-handler.ts
Comment thread src/tools/handlers/backup-vault-handler.ts
Comment thread src/tools/handlers/quota-rule-handler.ts
Comment thread src/tools/handlers/replication-handler.ts
Comment thread src/tools/handlers/snapshot-handler.ts
Copilot AI lite review requested due to automatic review settings September 30, 2026 14:14

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot review overview

🟡 Changes recommended

Resolve the incompatible dependency override and incorrect backup usage fallback.

Review effort: Lite
Findings: 1 High severity

Open (1)
Resolved since last review (5)

Comment thread package.json Outdated
…, and pool tools.

Normalize protobuf timestamps, enums, and int64 fields in shared helpers and handlers so MCP SDK 1.29+ output validation passes. Add required-field fallbacks for sparse API responses, restore backup byte-size schema descriptions, reject incomplete timestamps, and pin brace-expansion for CI security audit.

Co-authored-by: Cursor <cursoragent@cursor.com>
@garyamannetapp
garyamannetapp force-pushed the fix/backup-enforced-retention-timestamp branch from f3f31fe to e38ceea Compare September 30, 2026 14:41
gnaveen-netapp
gnaveen-netapp previously approved these changes Sep 30, 2026
The global override forced 2.x onto minimatch 10, which requires brace-expansion 5.x.

Co-authored-by: Cursor <cursoragent@cursor.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot review overview

🔵 Needs a closer look

Unknown numeric quota enum values are currently dropped and should be preserved.

Review effort: Lite
Findings: None

Resolved since last review (1)

@garyamannetapp
garyamannetapp merged commit e042d46 into main Sep 30, 2026
11 of 13 checks passed
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.

3 participants