Google Docs Lightweight Google Docs integration with standalone OAuth authentication. No MCP server required. ⚠️ Requires Google Workspace account. Personal Gmail accounts are not supported. First-Time Setup Authenticate with Google (opens browser): Check authentication status: Logout when needed: Commands All operations via . Auto-authenticates on first use if not logged in. Document ID Format Google Docs uses document IDs like . You can: - Use the full URL (the ID will be extracted automatically) - Use just the document ID - Get document IDs from the command results Token Management Tokens…

\n ]\n for pattern in patterns:\n match = re.search(pattern, doc_id_or_url)\n if match:\n return match.group(1)\n return doc_id_or_url\n\n\ndef create_doc(title: str, markdown: Optional[str] = None, folder_name: Optional[str] = None) -> dict:\n \"\"\"Create a new Google Doc, optionally with markdown content.\"\"\"\n if markdown:\n # Create doc via Drive API with HTML content (converted from markdown)\n # For simplicity, we'll create a blank doc and then insert text\n # Full markdown support would require HTML conversion like the MCP server\n result = api_request(\n \"POST\",\n f\"{DOCS_API_BASE}/documents\",\n data={\"title\": title}\n )\n\n if \"error\" in result:\n return result\n\n doc_id = result.get(\"documentId\")\n if doc_id and markdown:\n # Insert the markdown as plain text (basic implementation)\n insert_result = insert_text(doc_id, markdown)\n if \"error\" in insert_result:\n return {\"documentId\": doc_id, \"title\": title, \"warning\": \"Doc created but text insertion failed\"}\n\n return {\"documentId\": doc_id, \"title\": result.get(\"title\", title)}\n else:\n # Create blank doc\n result = api_request(\n \"POST\",\n f\"{DOCS_API_BASE}/documents\",\n data={\"title\": title}\n )\n\n if \"error\" in result:\n return result\n\n return {\"documentId\": result.get(\"documentId\"), \"title\": result.get(\"title\", title)}\n\n\ndef find_docs(query: str, page_size: int = 10, page_token: Optional[str] = None) -> dict:\n \"\"\"Find Google Docs by title search using Drive API.\"\"\"\n # Build the query for Drive API\n # Search for documents with the query in the name\n drive_query = f\"mimeType='application/vnd.google-apps.document' and name contains '{query}' and trashed=false\"\n\n params = {\n \"q\": drive_query,\n \"pageSize\": page_size,\n \"fields\": \"nextPageToken, files(id, name, modifiedTime, createdTime)\"\n }\n if page_token:\n params[\"pageToken\"] = page_token\n\n result = api_request(\"GET\", f\"{DRIVE_API_BASE}/files\", params=params)\n\n if \"error\" in result:\n return result\n\n return {\n \"files\": result.get(\"files\", []),\n \"nextPageToken\": result.get(\"nextPageToken\")\n }\n\n\ndef get_text(document_id: str) -> dict:\n \"\"\"Get the text content of a Google Doc.\"\"\"\n doc_id = extract_doc_id(document_id)\n\n params = {\n \"fields\": \"title,body.content\"\n }\n\n result = api_request(\"GET\", f\"{DOCS_API_BASE}/documents/{doc_id}\", params=params)\n\n if \"error\" in result:\n return result\n\n # Extract text from the document structure\n text = \"\"\n body_content = result.get(\"body\", {}).get(\"content\", [])\n\n for element in body_content:\n text += _read_structural_element(element)\n\n return {\n \"documentId\": doc_id,\n \"title\": result.get(\"title\", \"\"),\n \"text\": text\n }\n\n\ndef _read_structural_element(element: dict) -> str:\n \"\"\"Extract text from a structural element.\"\"\"\n text = \"\"\n if \"paragraph\" in element:\n for p_element in element[\"paragraph\"].get(\"elements\", []):\n if \"textRun\" in p_element:\n text += p_element[\"textRun\"].get(\"content\", \"\")\n elif \"table\" in element:\n for row in element[\"table\"].get(\"tableRows\", []):\n for cell in row.get(\"tableCells\", []):\n for cell_content in cell.get(\"content\", []):\n text += _read_structural_element(cell_content)\n return text\n\n\ndef append_text(document_id: str, text: str) -> dict:\n \"\"\"Append text to the end of a Google Doc.\"\"\"\n doc_id = extract_doc_id(document_id)\n\n # First, get the document to find the end index\n params = {\"fields\": \"body.content\"}\n doc_result = api_request(\"GET\", f\"{DOCS_API_BASE}/documents/{doc_id}\", params=params)\n\n if \"error\" in doc_result:\n return doc_result\n\n # Find the end index\n body_content = doc_result.get(\"body\", {}).get(\"content\", [])\n if body_content:\n last_element = body_content[-1]\n end_index = last_element.get(\"endIndex\", 1)\n else:\n end_index = 1\n\n # Insert at the end (before the final newline)\n location_index = max(1, end_index - 1)\n\n # Batch update to insert text\n requests = [\n {\n \"insertText\": {\n \"location\": {\"index\": location_index},\n \"text\": text\n }\n }\n ]\n\n result = api_request(\n \"POST\",\n f\"{DOCS_API_BASE}/documents/{doc_id}:batchUpdate\",\n data={\"requests\": requests}\n )\n\n if \"error\" in result:\n return result\n\n return {\"success\": True, \"documentId\": doc_id, \"message\": \"Text appended successfully\"}\n\n\ndef insert_text(document_id: str, text: str) -> dict:\n \"\"\"Insert text at the beginning of a Google Doc.\"\"\"\n doc_id = extract_doc_id(document_id)\n\n # Insert at index 1 (beginning of document content)\n requests = [\n {\n \"insertText\": {\n \"location\": {\"index\": 1},\n \"text\": text\n }\n }\n ]\n\n result = api_request(\n \"POST\",\n f\"{DOCS_API_BASE}/documents/{doc_id}:batchUpdate\",\n data={\"requests\": requests}\n )\n\n if \"error\" in result:\n return result\n\n return {\"success\": True, \"documentId\": doc_id, \"message\": \"Text inserted successfully\"}\n\n\ndef replace_text(document_id: str, find_text: str, replace_with: str) -> dict:\n \"\"\"Replace all occurrences of text in a Google Doc.\"\"\"\n doc_id = extract_doc_id(document_id)\n\n # Use replaceAllText request\n requests = [\n {\n \"replaceAllText\": {\n \"containsText\": {\n \"text\": find_text,\n \"matchCase\": True\n },\n \"replaceText\": replace_with\n }\n }\n ]\n\n result = api_request(\n \"POST\",\n f\"{DOCS_API_BASE}/documents/{doc_id}:batchUpdate\",\n data={\"requests\": requests}\n )\n\n if \"error\" in result:\n return result\n\n # Get the number of replacements made\n replies = result.get(\"replies\", [])\n occurrences_changed = 0\n if replies:\n occurrences_changed = replies[0].get(\"replaceAllText\", {}).get(\"occurrencesChanged\", 0)\n\n return {\n \"success\": True,\n \"documentId\": doc_id,\n \"occurrencesChanged\": occurrences_changed,\n \"message\": f\"Replaced {occurrences_changed} occurrence(s)\"\n }\n\n\ndef main():\n parser = argparse.ArgumentParser(description=\"Google Docs API operations\")\n subparsers = parser.add_subparsers(dest=\"command\", required=True)\n\n # create\n create_parser = subparsers.add_parser(\"create\", help=\"Create a new Google Doc\")\n create_parser.add_argument(\"title\", help=\"Title for the new document\")\n create_parser.add_argument(\"--content\", help=\"Initial content (plain text or markdown)\")\n\n # find\n find_parser = subparsers.add_parser(\"find\", help=\"Find docs by title search\")\n find_parser.add_argument(\"query\", help=\"Search query\")\n find_parser.add_argument(\"--limit\", type=int, default=10, help=\"Max results to return\")\n find_parser.add_argument(\"--page-token\", help=\"Pagination token\")\n\n # get-text\n get_text_parser = subparsers.add_parser(\"get-text\", help=\"Get text content of a doc\")\n get_text_parser.add_argument(\"document_id\", help=\"Document ID or URL\")\n\n # append-text\n append_text_parser = subparsers.add_parser(\"append-text\", help=\"Append text to a doc\")\n append_text_parser.add_argument(\"document_id\", help=\"Document ID or URL\")\n append_text_parser.add_argument(\"text\", help=\"Text to append\")\n\n # insert-text\n insert_text_parser = subparsers.add_parser(\"insert-text\", help=\"Insert text at beginning of a doc\")\n insert_text_parser.add_argument(\"document_id\", help=\"Document ID or URL\")\n insert_text_parser.add_argument(\"text\", help=\"Text to insert\")\n\n # replace-text\n replace_text_parser = subparsers.add_parser(\"replace-text\", help=\"Replace text in a doc\")\n replace_text_parser.add_argument(\"document_id\", help=\"Document ID or URL\")\n replace_text_parser.add_argument(\"find\", help=\"Text to find\")\n replace_text_parser.add_argument(\"replace\", help=\"Replacement text\")\n\n args = parser.parse_args()\n\n if args.command == \"create\":\n result = create_doc(args.title, args.content)\n elif args.command == \"find\":\n result = find_docs(args.query, args.limit, args.page_token)\n elif args.command == \"get-text\":\n result = get_text(args.document_id)\n elif args.command == \"append-text\":\n result = append_text(args.document_id, args.text)\n elif args.command == \"insert-text\":\n result = insert_text(args.document_id, args.text)\n elif args.command == \"replace-text\":\n result = replace_text(args.document_id, args.find, args.replace)\n else:\n result = {\"error\": f\"Unknown command: {args.command}\"}\n\n print(json.dumps(result, indent=2))\n\n if isinstance(result, dict) and \"error\" in result:\n sys.exit(1)\n\n\nif __name__ == \"__main__\":\n main()\n","content_type":"text/x-python; charset=utf-8","language":"python","size":10920,"content_sha256":"392e11c4c4efd72c89ed311a21d224f07a5e116625f144dd93a694d150f394bd"},{"filename":"scripts/requirements.txt","content":"keyring>=24.0.0\n","content_type":"text/plain; charset=utf-8","language":null,"size":16,"content_sha256":"2f694984e4915066821c2d53c6d95034061b2569a579fcc85ee80d78f630a689"}],"content_json":{"type":"doc","content":[{"type":"heading","attrs":{"level":1},"content":[{"text":"Google Docs","type":"text"}]},{"type":"paragraph","content":[{"text":"Lightweight Google Docs integration with standalone OAuth authentication. No MCP server required.","type":"text"}]},{"type":"blockquote","content":[{"type":"paragraph","content":[{"text":"⚠️ Requires Google Workspace account.","type":"text","marks":[{"type":"strong"}]},{"text":" Personal Gmail accounts are not supported.","type":"text"}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"First-Time Setup","type":"text"}]},{"type":"paragraph","content":[{"text":"Authenticate with Google (opens browser):","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"python scripts/auth.py login","type":"text"}]},{"type":"paragraph","content":[{"text":"Check authentication status:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"python scripts/auth.py status","type":"text"}]},{"type":"paragraph","content":[{"text":"Logout when needed:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"python scripts/auth.py logout","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Commands","type":"text"}]},{"type":"paragraph","content":[{"text":"All operations via ","type":"text"},{"text":"scripts/docs.py","type":"text","marks":[{"type":"code_inline"}]},{"text":". Auto-authenticates on first use if not logged in.","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# Create a new document\npython scripts/docs.py create \"Meeting Notes\"\n\n# Create a document with initial content\npython scripts/docs.py create \"Project Plan\" --content \"# Overview\\n\\nThis is the project plan.\"\n\n# Find documents by title\npython scripts/docs.py find \"meeting\" --limit 10\n\n# Get text content of a document\npython scripts/docs.py get-text 1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms\n\n# Get text using a full URL\npython scripts/docs.py get-text \"https://docs.google.com/document/d/1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms/edit\"\n\n# Append text to end of document\npython scripts/docs.py append-text 1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms \"New paragraph at the end.\"\n\n# Insert text at beginning of document\npython scripts/docs.py insert-text 1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms \"Text at the beginning.\\n\\n\"\n\n# Replace text in document\npython scripts/docs.py replace-text 1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms \"old text\" \"new text\"","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Document ID Format","type":"text"}]},{"type":"paragraph","content":[{"text":"Google Docs uses document IDs like ","type":"text"},{"text":"1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms","type":"text","marks":[{"type":"code_inline"}]},{"text":". You can:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Use the full URL (the ID will be extracted automatically)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Use just the document ID","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Get document IDs from the ","type":"text"},{"text":"find","type":"text","marks":[{"type":"code_inline"}]},{"text":" command results","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Token Management","type":"text"}]},{"type":"paragraph","content":[{"text":"Tokens stored securely using the system keyring:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"macOS","type":"text","marks":[{"type":"strong"}]},{"text":": Keychain","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Windows","type":"text","marks":[{"type":"strong"}]},{"text":": Windows Credential Locker","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Linux","type":"text","marks":[{"type":"strong"}]},{"text":": Secret Service API (GNOME Keyring, KDE Wallet, etc.)","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Service name: ","type":"text"},{"text":"google-docs-skill-oauth","type":"text","marks":[{"type":"code_inline"}]}]},{"type":"paragraph","content":[{"text":"Access tokens are automatically refreshed when expired using Google's cloud function.","type":"text"}]}]},"metadata":{"date":"2026-11-02","name":"google-docs","author":"@skillopedia","source":{"stars":450,"repo_name":"ai-skills","origin_url":"https://github.com/sanjay3290/ai-skills/blob/HEAD/skills/google-docs/SKILL.md","repo_owner":"sanjay3290","body_sha256":"bb8bd2ceb839d4051980066fcf105335380172fbeb5ffd586d4a86ab08a9e578","cluster_key":"56bb6d5db0af6203b82617613445b9bb5eb274317e84937ef7a3a8b466e72648","clean_bundle":{"format":"clean-skill-bundle-v1","source":"sanjay3290/ai-skills/skills/google-docs/SKILL.md","text_bytes":27151,"source_commit":"10cba52d57682430fe8430d70c86448270155b9a","skills_sh_rank":18376,"attachment_count":5,"text_attachments":5,"attachment_storage":"skill_attachments","binary_attachments":0},"cluster_size":1,"skill_md_path":"skills/google-docs/SKILL.md","import_metadata":{"date":"2026-11-02","author":"@skillopedia","version":"v1","category":"integrations-apis","category_label":"Integrations"},"exact_dupes_collapsed_into_this":0},"license":"Apache-2.0","version":"v1","category":"integrations-apis","metadata":{"author":"sanjay3290","version":"1.0"},"import_tag":"skills-sh-top20k-2026-11","description":"Interact with Google Docs - create documents, search by title, read content, and edit text.\nUse when user asks to: create a Google Doc, find a document, read doc content, add text to a doc,\nor replace text in a document. Lightweight alternative to full Google Workspace MCP server with\nstandalone OAuth authentication.\n"}},"renderedAt":1793661577905}

Google Docs Lightweight Google Docs integration with standalone OAuth authentication. No MCP server required. ⚠️ Requires Google Workspace account. Personal Gmail accounts are not supported. First-Time Setup Authenticate with Google (opens browser): Check authentication status: Logout when needed: Commands All operations via . Auto-authenticates on first use if not logged in. Document ID Format Google Docs uses document IDs like . You can: - Use the full URL (the ID will be extracted automatically) - Use just the document ID - Get document IDs from the command results Token Management Tokens…