--- name: feishu-docs description: Create and edit Feishu/Lark documents programmatically via the Docx Open API — create docs, add content blocks, read raw text, and manage permissions. Use when the user asks to create a Feishu doc from scratch or add structured content. version: 1.0.0 author: agent metadata: hermes: tags: [feishu, lark, documents, docx, api, content] related_skills: [homelab-services] --- # Feishu Docs — Document Creation & Editing ## When to Use - User asks you to create a Feishu document (docx) with structured content - User wants a report, summary, or reference saved as a Feishu doc - User says "帮我创建一份飞书文档" or similar - Supplement to lark-mcp MCP tools (which cover search, import, bitable, IM but not block-level editing) ## Trigger Conditions - Keywords: 飞书文档, 创建文档, 写个文档, Feishu doc, Lark docx - NOT: 飞书表格 (use lark-mcp bitable tools), 发送消息 (use `send_message` or lark-mcp IM tools) ## Architecture Two complementary systems: | System | Strengths | Gaps | |--------|-----------|------| | **lark-mcp** (MCP server) | Bitable CRUD, IM/groups, document search, markdown→doc import | No block-level editing, no doc creation | | **Feishu Docx API** (this skill) | Create docs, add/edit/delete blocks, read raw content | Requires manual API calls via Python | Use lark-mcp for everything it covers. Fall back to direct API for document creation and block editing. ## Prerequisites The Feishu app must have these permissions in the developer console: - `docx:document` — create and manage documents - `drive:drive` — access cloud documents (for import API) Without these, the API returns 404. User must add them at https://open.feishu.cn/app. Credentials are in `~/.hermes/.env`: ``` FEISHU_APP_ID=cli_xxx FEISHU_APP_SECRET=xxx ``` ## Workflow: Create a Document from Scratch ### Step 1: Get tenant access token ```python import requests r = requests.post( "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal", json={"app_id": aid, "app_secret": sec}, timeout=10 ) token = r.json()["tenant_access_token"] headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"} ``` ### Step 2: Create empty document ```python r = requests.post( "https://open.feishu.cn/open-apis/docx/v1/documents", headers=headers, json={"title": "文档标题"}, timeout=10 ) doc_id = r.json()["data"]["document"]["document_id"] # URL: https://xiteng.feishu.cn/docx/{doc_id} ``` ### Step 3: Add content blocks The document root is also the root block. Add children to it: ```python blocks = [ {"block_type": 3, "heading1": {"elements": [ {"text_run": {"content": "大标题"}} ]}}, {"block_type": 2, "text": {"elements": [ {"text_run": {"content": "正文段落"}} ]}}, {"block_type": 12, "bullet": {"elements": [ {"text_run": {"content": "列表项"}} ]}}, ] # Batch up to 50 blocks per request r = requests.post( f"https://open.feishu.cn/open-apis/docx/v1/documents/{doc_id}/blocks/{doc_id}/children", headers=headers, json={"children": blocks}, timeout=15 ) ``` ### Block type reference | block_type | Name | Structure | |------------|------|-----------| | 2 | Text paragraph | `{"text": {"elements": [{"text_run": {"content": "..."}}]}}` | | 3 | Heading 1 | `{"heading1": {"elements": ...}}` | | 4 | Heading 2 | `{"heading2": {"elements": ...}}` | | 5 | Heading 3 | `{"heading3": {"elements": ...}}` | | 6 | Heading 4 | `{"heading4": {"elements": ...}}` | | 12 | Bullet list | `{"bullet": {"elements": ...}}` | | 13 | Ordered list | `{"ordered": {"elements": ...}}` | | 14 | Code block | `{"code": {"elements": ..., "style": {"language": 1}}}` | | 17 | Callout/hint | `{"callout": {"elements": ...}}` | | 22 | Divider | `{"divider": {}}` | | 27 | Table | Complex — see API docs | ### Step 4: Read back and verify ```python r = requests.get( f"https://open.feishu.cn/open-apis/docx/v1/documents/{doc_id}/raw_content", headers={"Authorization": f"Bearer {token}"}, timeout=10 ) content = r.json()["data"]["content"] ``` ## Common Pitfalls ### 1. 404 on import API — missing permissions The `drive/v1/files/import` API requires `drive:drive` permission. Without it, returns 404. **Fix**: User must add the permission in Feishu Developer Console → App → Permissions. ### 2. ENV var redaction in scripts When writing Python scripts that read `~/.hermes/.env`, do NOT write lines like: ```python # ❌ Redaction will eat the string elif line.startswith("FEISHU_APP_SECRET=*** ... ``` Instead construct the key name dynamically: ```python # ✅ Safe from redaction if k == "FEISHU_APP_SECRET": ... # or prefix = "FEISHU_APP" if k == prefix + "_SECRET": ... ``` ### 3. Block batch limit The children API accepts at most 50 blocks per request. Split large documents into batches. ### 4. Document ID = Root block ID The `document_id` returned from create is also the root block ID. Use it for both: - `GET /docx/v1/documents/{doc_id}` — get document metadata - `POST /docx/v1/documents/{doc_id}/blocks/{doc_id}/children` — add blocks ## Integration with lark-mcp When lark-mcp is connected (see `references/lark-mcp-setup.md`), use it for: - `docx_builtin_search` — search existing documents - `docx_v1_document_rawContent` — read document plain text - `docx_builtin_import` — quick markdown→doc conversion (creates new doc) - `bitable_*` — multi-dimensional table operations - `im_*` — messages and groups Use direct API (this skill) when you need: - Create a blank document with a specific title - Add/edit/delete individual blocks - Modify existing document structure ## Verification Checklist - [ ] `FEISHU_APP_ID` and `FEISHU_APP_SECRET` are set in `.env` - [ ] App has `docx:document` permission - [ ] Document created: `GET /docx/v1/documents/{id}` returns 200 - [ ] Content added: `GET /docx/v1/documents/{id}/blocks` returns blocks - [ ] User can open the URL in Feishu ## Related Files - `scripts/create_doc.py` — reusable script: create doc + add content from markdown - `references/lark-mcp-setup.md` — MCP server installation and credential management - `references/docx-block-types.md` — full block type reference with examples