{"record":{"id":"18900a5a09ff5006","repo":"paperclipai/paperclip","slug":"spreadsheet-spreadsheetid-is-not-in-the-configu","errorCode":null,"errorMessage":"Spreadsheet ${spreadsheetId} is not in the configured allowlist.","messagePattern":"Spreadsheet (.+?) is not in the configured allowlist\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/google-sheets-mcp-server/src/tools.ts","lineNumber":136,"sourceCode":"  return {\n    name,\n    description,\n    schema,\n    annotations: annotationsFor(description, risk),\n    execute: async (input) => {\n      try {\n        const parsed = schema.parse(input);\n        return formatTextResponse(await execute(parsed));\n      } catch (error) {\n        return formatErrorResponse(error, options.secretRedactions ?? []);\n      }\n    },\n  };\n}\n\nfunction assertAllowed(allowedSpreadsheetIds: Set<string>, spreadsheetId: string) {\n  if (!allowedSpreadsheetIds.has(spreadsheetId)) {\n    throw new Error(`Spreadsheet ${spreadsheetId} is not in the configured allowlist.`);\n  }\n}\n\nexport function createToolDefinitions(options: GoogleSheetsToolOptions): GoogleSheetsToolDefinition[] {\n  const allowedSpreadsheetIds = Array.from(new Set(options.allowedSpreadsheetIds.map((id) => id.trim()).filter(Boolean)));\n  const allowedSpreadsheetIdSet = new Set(allowedSpreadsheetIds);\n  if (allowedSpreadsheetIds.length === 0) {\n    throw new Error(\"At least one allowed spreadsheet ID is required.\");\n  }\n\n  return [\n    makeTool(\n      options,\n      \"list_spreadsheets\",\n      \"List the Google Sheets spreadsheets configured in this connection allowlist.\",\n      \"read\",\n      z.object({}),\n      async () => options.client.listSpreadsheets(allowedSpreadsheetIds),","sourceCodeStart":118,"sourceCodeEnd":154,"githubUrl":"https://github.com/paperclipai/paperclip/blob/67001ec6eb96ae601aa27bc91d9b2415d665334a/packages/google-sheets-mcp-server/src/tools.ts#L118-L154","documentation":"createToolDefinitions builds an allowlist set of spreadsheet IDs and assertAllowed rejects any tool invocation whose spreadsheetId is not a member of that set. This is the server's primary data-boundary control: an agent or caller can only touch spreadsheets the operator explicitly pre-authorized at config time.","triggerScenarios":"Any tool that calls assertAllowed (e.g. delete_rows, clear_values, read/write tools) is invoked with a spreadsheetId that was not passed in options.allowedSpreadsheetIds when createToolDefinitions was constructed. Typo, wrong env var, copy-paste of an ID from another account, or a hallucinated ID all trigger it.","commonSituations":"Operator sets GOOGLE_SHEETS_SPREADSHEET_IDS to sheet A but the agent references sheet B; URL-key vs ID confusion (pasting the long docs URL instead of the /d/<id>/ segment); trailing whitespace in the configured ID that survived trim but the caller did not trim.","solutions":["Call the list_spreadsheets tool to see exactly which IDs are allowlisted and use one of those.","Add the missing spreadsheet ID to the allowedSpreadsheetIds option (env var / config) and restart the server.","Verify you are passing the spreadsheet ID (the middle path segment of the URL), not the full URL or the sheet/tab name."],"exampleFix":"// before\nallowedSpreadsheetIds: ['1Bx...old']\n// call uses new sheet -> error\n// after\nallowedSpreadsheetIds: ['1Bx...old', '9AbC...new']","handlingStrategy":"validation","validationCode":"function isAllowed(spreadsheetId: string, allowed: string[]): boolean {\n  return allowed.includes(spreadsheetId);\n}\nif (!isAllowed(id, configuredAllow)) {\n  // surface the configured IDs to the user/agent instead of failing server-side\n}","typeGuard":null,"tryCatchPattern":"try { await tools.call('delete_rows', { spreadsheetId: id, ... }) }\ncatch (e) {\n  if (/not in the configured allowlist/.test(String(e.message))) {\n    const allowed = await tools.call('list_spreadsheets');\n    // pick from allowed and retry, or escalate\n  } else throw e;\n}","preventionTips":["Always call list_spreadsheets first when unsure which IDs are authorized.","Pass full spreadsheet IDs, never URLs or tab names.","Keep the configured allowlist and the IDs your agent references in the same config source."],"tags":["authorization","allowlist","google-sheets-mcp","data-boundary"],"backgroundTag":null,"analyzedSha":"67001ec6eb96ae601aa27bc91d9b2415d665334a","analyzedAt":"2026-08-12T12:05:45.408Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}