# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
Feishu (飞书) MCP server providing tools for Bitable (multidimensional tables) and Sheets (spreadsheet). Built with official MCP SDK and lark-oapi SDK.
**Current Status**: Bitable tools fully implemented (10 tools). Sheets tools planned (17 tools, not yet implemented).
## Critical Architecture Details
### Standard Package Structure
**Important**: This project uses `src/yuppie_mcp_feishu/` as a proper Python package with absolute imports.
Import examples:
```python
# Correct - absolute imports from package
from yuppie_mcp_feishu.impl import create_bitable_record_impl
from yuppie_mcp_feishu.client import get_client
from yuppie_mcp_feishu.config import get_config
# Wrong - no relative imports within the package
# from .impl import ... # NO
# from impl import ... # NO - requires full package path
```
### Two-Layer Architecture (Refactored from FastMCP)
The codebase was refactored from FastMCP decorators to the official MCP SDK for better compatibility:
1. **Implementation Layer** (`impl.py`)
- Pure API call functions (`_xxx_impl()`)
- Return raw response objects from lark-oapi
- Can be tested independently
- **No MCP framework dependencies**
2. **Server Layer** (`server.py`)
- Centralized tool routing via `@server.call_tool()`
- Manual dispatch based on tool name
- Single error handling point for all tools
- Uses official MCP SDK (`mcp.server.Server`)
Example from `yuppie_mcp_feishu/server.py`:
```python
@server.call_tool()
async def handle_call_tool(name: str, arguments: dict) -> list[types.TextContent]:
"""Centralized tool dispatcher - routes by tool name"""
if name == "create_bitable_record":
response = create_bitable_record_impl(app_token, table_id, fields)
elif name == "update_bitable_record":
response = update_bitable_record_impl(app_token, table_id, record_id, fields)
# ... more tools
# Unified error handling
if not response.success():
error_data = {"success": False, "code": response.code, "msg": response.msg}
return [types.TextContent(type="text", text=lark.JSON.marshal(error_data))]
return [types.TextContent(type="text", text=lark.JSON.marshal(response.data))]
```
### No Tool Decorators Pattern
Unlike FastMCP-based servers, this codebase does **NOT** use `@mcp.tool()` decorators. Instead:
- Tool definitions are in `@server.list_tools()` decorator
- Tool routing is in `@server.call_tool()` via if-elif chains
- All error handling is centralized in `handle_call_tool()`
### Lark-OAPI Builder Pattern Gotchas
The lark-oapi SDK uses builder pattern with **inconsistent method names** across request types. Always check the actual builder:
```python
# BatchCreate/BatchUpdate: .records()
BatchCreateAppTableRecordRequestBody.builder().records(records).build()
# BatchGet: .record_ids() (plural!)
BatchGetAppTableRecordRequestBody.builder().record_ids(record_ids).build()
# BatchDelete: .records() (not record_ids!)
BatchDeleteAppTableRecordRequestBody.builder().records(record_ids).build()
```
### API Response Structure Variations
Different Feishu APIs return different response structures:
```python
# search_bitable_records: items at root
data.get("items", []) # NOT data.get("data", {}).get("items")
# batch_create_bitable_records: records at root
data.get("records", []) # NOT data.get("data", {}).get("records")
# create/update/single operations: varies
data.get("record", {}) or data.get("app", {})
```
## Development Commands
### Environment Setup
```bash
# Install dependencies
uv sync --all-extras # includes pytest for testing
# Configure credentials
cp .env.example .env
# Edit .env with FEISHU_APP_ID and FEISHU_APP_SECRET
```
### Code Quality
```bash
# Format code
uv run black src/ tests/
# Lint code
uv run ruff check src/ tests/
# Type check
uv run mypy src/
# Run all three (recommended)
uv run black src/ tests/ && uv run ruff check src/ tests/ && uv run mypy src/
```
### Running
```bash
# Run MCP server (installed package)
yuppie-mcp-feishu
# Or via uvx (uses installed package from PyPI)
uvx yuppie-mcp-feishu
# Or directly with Python module
python -m yuppie_mcp_feishu
```
### Testing (Pytest-based)
The project uses pytest for testing with a modular structure:
```bash
# Run all tests
pytest tests/ -v
# Run specific module
pytest tests/yuppie_mcp_feishu/tools/test_bitable_app.py -v
# Run specific test function
pytest tests/yuppie_mcp_feishu/tools/test_bitable_record.py::test_create_bitable_record -v
# Run with coverage
pytest tests/ --cov=src/yuppie_mcp_feishu --cov-report=html
# Run tests with PYTHONPATH set
PYTHONPATH=src pytest tests/ -v
```
**Required Environment Variable for Testing:**
```bash
export FEISHU_DEFAULT_APP_TOKEN=your_app_token_here # Required - specify a test Bitable app
```
Tests use pytest fixtures with automatic dependency injection:
- `test_config`: Test configuration object (session-scoped)
- `default_app_token`: Reads from `FEISHU_DEFAULT_APP_TOKEN` (via test_config)
- `test_table_id`: Creates/retrieves a test table (session-scoped)
- `test_record_id`: Creates a test record (function-scoped, per-test)
- `test_record_ids`: Batch creates test records (function-scoped, per-test)
**Important**: Application-level tests (create/copy apps) have been removed from the automated test suite. All tests run in the specified default Bitable app to avoid creating duplicate applications.
### Building and Publishing
```bash
# Build distribution packages
rm -rf dist/
python -m build
# Check packages
twine check dist/*
# Upload to TestPyPI (for testing)
twine upload --repository testpypi dist/*
# Upload to PyPI (production)
twine upload dist/*
```
See `PUBLISH_TO_PYPI.md` for detailed publishing instructions.
## Project Structure
```
src/
└── yuppie_mcp_feishu/ # Main package (src layout)
├── __init__.py # Package entry point, exports main()
├── server.py # MCP server with tool routing
├── impl.py # All API implementations (pure functions)
├── config.py # Pydantic config, loads from env
└── client.py # lark-oapi client singleton with token management
tests/ # Pytest test suite (mirrors src structure)
├── conftest.py # pytest fixtures configuration
└── yuppie_mcp_feishu/
├── tools/
│ ├── test_bitable_app.py # Table operation tests
│ └── test_bitable_record.py # Record CRUD tests
└── integration/
└── test_bitable_workflow.py # Workflow integration tests
```
**Key Architectural Changes from v0.3.x:**
- ❌ Removed: `tools/` subdirectory, `exceptions.py`, `main.py`
- ❌ Removed: FastMCP framework, `@mcp.tool()` decorators
- ✅ Added: Official MCP SDK, centralized `server.py`, two-layer architecture
- ✅ Simplified: All implementations in `impl.py`, all routing in `server.py`
## Client and Token Management
The Feishu client (`src/yuppie_mcp_feishu/client.py`) manages tenant_access_token automatically:
- Token lifespan: 2 hours
- SDK handles refresh automatically
- Client is a singleton via `get_client()`
- Uses `enable_set_token(False)` to let SDK manage tokens
Never manually set or refresh tokens - just call `get_client()`.
## Error Handling Pattern
Error handling is **centralized** in `server.py`'s `handle_call_tool()` function:
```python
# Unified error handling for all tools
if not response.success():
error_data = {
"success": False,
"code": response.code,
"msg": response.msg,
"error": getattr(response, "error", None),
"log_id": response.get_log_id(),
}
return [types.TextContent(type="text", text=lark.JSON.marshal(error_data))]
```
No decorator-based error handling - all errors caught and transformed in the dispatcher.
## Adding New Tools
Follow the two-layer pattern:
### 1. Add Implementation (in `impl.py`)
```python
def your_operation_impl(param1: str, param2: str):
"""Pure API implementation - no MCP dependencies"""
from yuppie_mcp_feishu.client import get_client
client = get_client()
request = YourRequest.builder().param1(param1).build()
return client.api.v1.resource.your_method(request)
```
### 2. Register Tool Schema (in `server.py`'s `handle_list_tools()`)
```python
types.Tool(
name="your_operation",
description="Tool description",
inputSchema={
"type": "object",
"properties": {
"param1": {"type": "string"},
"param2": {"type": "string"},
},
"required": ["param1", "param2"],
},
)
```
### 3. Add Routing Logic (in `server.py`'s `handle_call_tool()`)
```python
elif name == "your_operation":
param1 = arguments.get("param1")
param2 = arguments.get("param2")
if not param1 or not param2:
raise ValueError("Missing required parameters: param1, param2")
response = your_operation_impl(param1, param2)
```
## MCP Client Configuration
### Claude Desktop
`~/Library/Application Support/Claude/claude_desktop_config.json`:
```json
{
"mcpServers": {
"feishu": {
"command": "uvx",
"args": ["yuppie-mcp-feishu"],
"env": {
"FEISHU_APP_ID": "your_app_id",
"FEISHU_APP_SECRET": "your_app_secret"
}
}
}
}
```
### Cherry Studio
Use STDIO type with:
- Command: `uvx`
- Args: `yuppie-mcp-feishu`
- Env: `FEISHU_APP_ID`, `FEISHU_APP_SECRET`
### Remote Deployment (阿里云, etc.)
For cloud deployments, `uvx yuppie-mcp-feishu` works directly since the package is now properly structured:
```json
{
"mcpServers": {
"yuppie-mcp-feishu": {
"command": "uvx",
"args": ["yuppie-mcp-feishu"],
"env": {
"FEISHU_APP_ID": "cli_a83d2667c93dd00d",
"FEISHU_APP_SECRET": "gznbWwHCAHx9g1ExGDNK0gv0Kdu46o4j"
}
}
}
}
```
## Dependencies
- **UV**: Package manager (not pip/poetry)
- **mcp**: Official MCP SDK (>= 1.2.0)
- **lark-oapi**: Feishu Python SDK (>= 1.5.2)
- **pydantic**: Config validation (>= 2.0.0)
- **python-dotenv**: Env loading (>= 1.0.0)
- **pytest**: Testing framework (dev dependency)
- **black**: Code formatting (dev dependency)
- **ruff**: Fast Python linter (dev dependency)
- **mypy**: Type checking (dev dependency)
- **Python**: >= 3.10
## Planned Features (需求文档.md)
Sheets API has 17 endpoints planned but not yet implemented:
- Spreadsheet operations (create, get)
- Sheet operations (add, copy, delete, update, query)
- Row/column operations (add, insert, update, move, delete)
- Data operations (read, write, append, prepend, images)