paperclipai/paperclip · error · Error

Spreadsheet ${spreadsheetId} is not in the configured allowl

Error message

Spreadsheet ${spreadsheetId} is not in the configured allowlist.

What it means

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.

Source

Thrown at packages/google-sheets-mcp-server/src/tools.ts:136

  return {
    name,
    description,
    schema,
    annotations: annotationsFor(description, risk),
    execute: async (input) => {
      try {
        const parsed = schema.parse(input);
        return formatTextResponse(await execute(parsed));
      } catch (error) {
        return formatErrorResponse(error, options.secretRedactions ?? []);
      }
    },
  };
}

function assertAllowed(allowedSpreadsheetIds: Set<string>, spreadsheetId: string) {
  if (!allowedSpreadsheetIds.has(spreadsheetId)) {
    throw new Error(`Spreadsheet ${spreadsheetId} is not in the configured allowlist.`);
  }
}

export function createToolDefinitions(options: GoogleSheetsToolOptions): GoogleSheetsToolDefinition[] {
  const allowedSpreadsheetIds = Array.from(new Set(options.allowedSpreadsheetIds.map((id) => id.trim()).filter(Boolean)));
  const allowedSpreadsheetIdSet = new Set(allowedSpreadsheetIds);
  if (allowedSpreadsheetIds.length === 0) {
    throw new Error("At least one allowed spreadsheet ID is required.");
  }

  return [
    makeTool(
      options,
      "list_spreadsheets",
      "List the Google Sheets spreadsheets configured in this connection allowlist.",
      "read",
      z.object({}),
      async () => options.client.listSpreadsheets(allowedSpreadsheetIds),

View on GitHub (pinned to 67001ec6eb)

Solutions

  1. Call the list_spreadsheets tool to see exactly which IDs are allowlisted and use one of those.
  2. Add the missing spreadsheet ID to the allowedSpreadsheetIds option (env var / config) and restart the server.
  3. Verify you are passing the spreadsheet ID (the middle path segment of the URL), not the full URL or the sheet/tab name.

Example fix

// before
allowedSpreadsheetIds: ['1Bx...old']
// call uses new sheet -> error
// after
allowedSpreadsheetIds: ['1Bx...old', '9AbC...new']
Defensive patterns

Strategy: validation

Validate before calling

function isAllowed(spreadsheetId: string, allowed: string[]): boolean {
  return allowed.includes(spreadsheetId);
}
if (!isAllowed(id, configuredAllow)) {
  // surface the configured IDs to the user/agent instead of failing server-side
}

Try / catch

try { await tools.call('delete_rows', { spreadsheetId: id, ... }) }
catch (e) {
  if (/not in the configured allowlist/.test(String(e.message))) {
    const allowed = await tools.call('list_spreadsheets');
    // pick from allowed and retry, or escalate
  } else throw e;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of paperclipai/paperclip@67001ec6eb (2026-08-12). Data as JSON: /api/errors/18900a5a09ff5006. Report an issue: GitHub.