Skip to content

Commit 01f6c1a

Browse files
feat(mcp): add MCP bridge + Cowork kit + business-user docs
Turn lark-cli into an MCP server so Claude Desktop (Cowork) and claude.ai (web) can drive Lark/Feishu without Claude Code. - cmd/mcp: `lark-cli mcp serve` (stdio + streamable-HTTP), 21 curated tools + `lark_api` passthrough; bearer-token gate via LARK_MCP_BEARER_TOKEN (constant-time), /health endpoint, audit log. - shortcuts/im: port `+card-send` (YAML card spec -> Feishu card JSON). - .claude: 23 Cowork workflow skills + /mcp-* slash commands. - docs: business-user guide (desktop, web/tunnel, auth, skills, tools, security, troubleshooting) + 30-slide deck. - examples/mcp-hosts, scripts/setup-mcp.sh, MCP_QUICKSTART.md. Secrets (Lark token, bearer token) stay in OS keychain / env vars; nothing sensitive committed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 8c3cba1 commit 01f6c1a

121 files changed

Lines changed: 14799 additions & 2 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.claude/commands/mcp-add.md

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
---
2+
description: Walk through adding a new tool to the lark-cli MCP bridge
3+
argument-hint: <tool-name> [underlying shortcut, e.g. "im +messages-pin"]
4+
allowed-tools: Bash, Read, Edit, Write, Grep, Glob
5+
---
6+
7+
You will add a new MCP tool to `cmd/mcp/tools.go`. Follow these
8+
steps exactly; do not skip the verification.
9+
10+
## 1. Resolve the shortcut
11+
12+
If the user supplied the underlying shortcut as the second argument,
13+
use it directly. Otherwise, ask the user OR delegate to the
14+
`shortcut-explorer` agent to find the right service/verb pair (conceptual `service +command`).
15+
16+
Confirm the shortcut's exact flag names by reading
17+
`shortcuts/<domain>/<file>.go`. **Never guess flags.**
18+
19+
## 2. Pick a tool name
20+
21+
Format: `lark_<domain>_<verb>` — e.g. `lark_im_pin`, `lark_doc_export`.
22+
Stay consistent with the existing 20 tools.
23+
24+
## 3. Edit `cmd/mcp/tools.go`
25+
26+
1. Append a new `toolXxx()` factory near the bottom of the file.
27+
2. Register it in `allTools()`, grouped with siblings of the same
28+
domain.
29+
3. Define the JSON Schema as a raw string literal. Required fields
30+
go in the top-level `required` array.
31+
4. In the `Build` closure:
32+
- Use `argString(args, "key")` for required strings.
33+
- Use `appendFlag(argv, "flag-name", value)` for optional flags.
34+
- Add `appendIdentity(argv, args)` at the end so callers can
35+
pass `as: "bot"`.
36+
- Map MCP arg names with `_` to CLI flag names with `-`.
37+
38+
## 4. Verify
39+
40+
```bash
41+
go build -o ~/bin/lark-cli .
42+
~/bin/lark-cli mcp tools | grep <tool-name>
43+
```
44+
45+
Then run the smoke handshake (from `cmd/mcp/README.md` §Verify) and
46+
issue a real `tools/call` for the new tool with safe arguments
47+
(`--dry-run` flag if the shortcut supports it).
48+
49+
## 5. Update docs
50+
51+
- Append the tool to the catalogue table in `cmd/mcp/README.md`.
52+
53+
## 6. Report
54+
55+
Summarise:
56+
57+
- Tool name + 1-line description.
58+
- Exact CLI invocation it generates.
59+
- Verification output (build status, mcp tools listing line, smoke
60+
test result).
61+
- Any flag names you could not confirm from source.
62+
63+
## Hard rules
64+
65+
- No third-party MCP library imports.
66+
- No `fmt.Println` or any stdout write inside `cmd/mcp/`. Stdout
67+
is JSON-RPC only.
68+
- One MCP tool wraps exactly one shortcut. Composite flows go to
69+
`lark_api` or are left to the model to chain.

.claude/commands/mcp-call.md

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
---
2+
description: Invoke one MCP tool end-to-end (handshake + tools/call) and print the result
3+
argument-hint: <tool-name> [json-args, default {}]
4+
allowed-tools: Bash, Read
5+
---
6+
7+
One-shot wrapper around `lark-cli mcp serve` for fast dev iteration.
8+
Use after editing `cmd/mcp/tools.go` to confirm a tool actually
9+
works without launching Claude Desktop.
10+
11+
## 1. Resolve inputs
12+
13+
- Tool name: `$1`. Required. If empty, abort and tell the user the
14+
usage: `/mcp-call <tool-name> '<json-args>'`.
15+
- Args: `$2`. Default `{}` if not supplied.
16+
- Validate `$2` parses as JSON before spending the handshake:
17+
18+
```bash
19+
echo "$2" | python3 -c "import json,sys;json.loads(sys.stdin.read())" \
20+
|| { echo "Args is not valid JSON"; exit 1; }
21+
```
22+
23+
## 2. Locate the binary
24+
25+
Prefer `~/bin/lark-cli` (dev build). Fall back to `which lark-cli`.
26+
Print the absolute path you picked.
27+
28+
## 3. Confirm the tool is in the catalogue
29+
30+
```bash
31+
$BIN mcp tools 2>&1 | grep -F "$1" \
32+
|| { echo "Tool '$1' not in catalogue — rebuild via /mcp-rebuild"; exit 1; }
33+
```
34+
35+
## 4. Send the call
36+
37+
```bash
38+
printf '%s\n' \
39+
'{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"mcp-call","version":"0"}}}' \
40+
'{"jsonrpc":"2.0","method":"notifications/initialized"}' \
41+
"{\"jsonrpc\":\"2.0\",\"id\":2,\"method\":\"tools/call\",\"params\":{\"name\":\"$1\",\"arguments\":$ARGS}}" \
42+
| $BIN mcp serve 2>/tmp/mcp-call.stderr
43+
```
44+
45+
Where `$ARGS` is `$2` (already validated JSON).
46+
47+
## 5. Parse and present
48+
49+
Three lines on stdout. Discard the first (init response) and
50+
focus on the third (the `tools/call` result):
51+
52+
```bash
53+
... | python3 -c "
54+
import json, sys
55+
lines = [l for l in sys.stdin if l.strip()]
56+
resp = json.loads(lines[-1])
57+
result = resp.get('result', {})
58+
err = result.get('isError')
59+
print('isError:', bool(err))
60+
for block in result.get('content', []):
61+
print(block.get('text', ''))
62+
"
63+
```
64+
65+
## 6. Report
66+
67+
- Tool called + args used.
68+
- `isError: true/false`.
69+
- Result content (truncated to ~40 lines).
70+
- If `isError: true`: tail `/tmp/mcp-call.stderr` for the stderr
71+
the subprocess emitted — that's where the real error is.
72+
73+
## Safety
74+
75+
- **Refuse destructive args** unless the user explicitly types
76+
"yes, run it for real". Specifically: `lark_im_send` with a real
77+
`chat_id`, any `*-delete` tool, anything where the shortcut does
78+
not advertise `--dry-run`. Default to `--dry-run` if the
79+
underlying shortcut supports it (delegate to `shortcut-explorer`
80+
to check).
81+
- Always `rm -f /tmp/mcp-call.stderr` after.

.claude/commands/mcp-doctor.md

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
---
2+
description: One-page health report for the MCP bridge — build, handshake, sample call, host config, logs
3+
allowed-tools: Bash, Read, Grep
4+
---
5+
6+
Aggregate every layer of the bridge into a single, scannable report.
7+
Run when the user says "is everything ok", "status check", "what's
8+
the state of MCP", or before a release.
9+
10+
## Sections (run in order, stop only on hard failure)
11+
12+
### 1. Binary
13+
14+
```bash
15+
BIN=$(command -v lark-cli || echo "$HOME/bin/lark-cli")
16+
[ -x "$BIN" ] || { echo "no binary"; exit 1; }
17+
"$BIN" --version
18+
```
19+
20+
### 2. Catalogue
21+
22+
```bash
23+
"$BIN" mcp tools 2>&1 | tail -3
24+
```
25+
26+
Count tools. Compare to the count of `func toolXxx() tool {`
27+
in `cmd/mcp/tools.go` — should match.
28+
29+
### 3. Handshake
30+
31+
Reuse the recipe from `/mcp-test`. Capture stdout line count
32+
(expect 2) and stderr length.
33+
34+
### 4. Sample `tools/call`
35+
36+
Pick the safest tool from the catalogue (read-only, no required
37+
arg): `lark_calendar_agenda` with `{}` (today is the default), or
38+
`lark_task_my` with `{}`. Both are safe. If neither exists, skip
39+
this step and note "no safe sample".
40+
41+
### 5. Host config
42+
43+
```bash
44+
HOST_CFG="$HOME/Library/Application Support/Claude/claude_desktop_config.json"
45+
if [ -f "$HOST_CFG" ]; then
46+
python3 -c "
47+
import json
48+
c = json.load(open('$HOST_CFG'))
49+
servers = c.get('mcpServers', {})
50+
for name, s in servers.items():
51+
if 'lark' in name.lower():
52+
print(name, '→', s.get('command'), s.get('args'))
53+
"
54+
fi
55+
```
56+
57+
### 6. Recent host log
58+
59+
`tail -20` of the latest `~/Library/Logs/Claude/mcp*.log` if any.
60+
61+
### 7. Auth
62+
63+
`lark-cli auth status` first 3 lines + `lark-cli profile list` head.
64+
65+
## Report shape
66+
67+
```
68+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
69+
MCP doctor — <timestamp>
70+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
71+
Binary <path> <version> ✅ / ❌
72+
Catalogue <N> tools (source: <M>) ✅ / ⚠ drift / ❌
73+
Handshake initialize + tools/list ✅ / ❌
74+
Sample call <tool> → isError=<bool> ✅ / ❌
75+
Host config registered as "<name>" → <path> ✅ / ❌ / not-installed
76+
Host log last error: "<line or —>"
77+
Auth profile=<name> identity=<u/b> ✅ / ❌
78+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
79+
VERDICT HEALTHY / DEGRADED / BROKEN
80+
NEXT ACTION <one sentence, only if not HEALTHY>
81+
```
82+
83+
Then stop. Do not start work — the user might just be checking.
84+
85+
## Hard rules
86+
87+
- Pure observation. Do not mutate any state — no `auth login`, no
88+
host config edits, no rebuilds.
89+
- If a step requires sudo or a destructive command to even read
90+
(rare here), skip it and note "skipped, requires elevation".
91+
- Always clean up temp files: `rm -f /tmp/mcp-doctor.*`.

.claude/commands/mcp-rebuild.md

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
---
2+
description: Rebuild lark-cli and reinstall the binary so Claude Desktop picks up changes
3+
argument-hint: [install-path, default ~/bin/lark-cli]
4+
allowed-tools: Bash, Read
5+
---
6+
7+
Rebuild the local binary after MCP changes and (re)install it where
8+
the MCP host will find it.
9+
10+
## 1. Pick the install path
11+
12+
Use `$1` if supplied. Otherwise default to `~/bin/lark-cli`.
13+
If the user explicitly wants `/usr/local/bin/lark-cli`, warn that
14+
sudo will be required and ask for confirmation.
15+
16+
## 2. Build
17+
18+
```bash
19+
go build -o /tmp/lark-cli .
20+
ls -la /tmp/lark-cli
21+
```
22+
23+
If `go build` fails, stop and surface the error.
24+
25+
## 3. Smoke-check the build
26+
27+
```bash
28+
/tmp/lark-cli --version
29+
/tmp/lark-cli mcp tools | tail -3
30+
```
31+
32+
Both must succeed. The `mcp tools` output should show "20 tools total"
33+
(or however many are defined in `cmd/mcp/tools.go`).
34+
35+
## 4. Install
36+
37+
```bash
38+
cp /tmp/lark-cli <install-path>
39+
<install-path> --version
40+
```
41+
42+
## 5. Remind to restart
43+
44+
After installing, the MCP host (Claude Desktop, Claude Code) still
45+
holds a handle to the **old** binary process. Tell the user:
46+
47+
- Claude Desktop: Cmd+Q (full quit) and reopen.
48+
- Claude Code: the next session picks up the new binary; no restart
49+
needed for ad-hoc CLI use.
50+
51+
## 6. Report
52+
53+
- Install path.
54+
- Binary version string and modification time.
55+
- Tool count.
56+
- Whether a restart is needed for the active host.
57+
58+
Do not edit any source. This command is build-and-install only.

.claude/commands/mcp-test.md

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
---
2+
description: Smoke-test the lark-cli MCP server (initialize + tools/list + optional tools/call)
3+
argument-hint: [tool-name] [json-args]
4+
allowed-tools: Bash, Read
5+
---
6+
7+
Verify the MCP bridge end-to-end without needing Claude Desktop.
8+
9+
## 1. Locate the binary
10+
11+
Prefer `~/bin/lark-cli` (development build). Fall back to
12+
`/usr/local/bin/lark-cli` if that's where the user installed it.
13+
Print the path you picked.
14+
15+
## 2. Run the handshake
16+
17+
```bash
18+
printf '%s\n' \
19+
'{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"smoke","version":"0"}}}' \
20+
'{"jsonrpc":"2.0","method":"notifications/initialized"}' \
21+
'{"jsonrpc":"2.0","id":2,"method":"tools/list"}' \
22+
| <binary-path> mcp serve 2>/tmp/mcp-smoke.log
23+
```
24+
25+
Expected: two JSON-RPC response lines on stdout. The first must
26+
contain `"protocolVersion":"2024-11-05"`. The second must contain
27+
`"tools":[`.
28+
29+
## 3. (Optional) Real tool call
30+
31+
If the user supplied `$1` (tool name), build a third request:
32+
33+
```bash
34+
printf '%s\n' \
35+
<initialize line> \
36+
<initialized notification> \
37+
'{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"<tool>","arguments":<args>}}' \
38+
| <binary-path> mcp serve
39+
```
40+
41+
Use `$2` (JSON object) for arguments, or `{}` if not provided.
42+
43+
## 4. Report
44+
45+
- Binary path used.
46+
- Did initialize succeed? (Y/N)
47+
- Tool count in `tools/list` response.
48+
- If `tools/call` run: did it return `isError: true` or success?
49+
- Tail of `/tmp/mcp-smoke.log` for the server's stderr logs.
50+
51+
Stop after reporting. Do not modify any files.

.claude/commands/mcp-tools.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
---
2+
description: List the MCP tools currently exposed by `lark-cli mcp serve`
3+
allowed-tools: Bash
4+
---
5+
6+
Run `lark-cli mcp tools` against the active install and print the
7+
catalogue. This is a one-shot read; no edits.
8+
9+
```bash
10+
lark-cli mcp tools 2>&1 || ~/bin/lark-cli mcp tools 2>&1
11+
```
12+
13+
If neither works, surface the error verbatim — the binary is likely
14+
not on PATH yet. Suggest running `/mcp-rebuild` to (re)install.
15+
16+
After printing the catalogue, summarise in one sentence which tool
17+
domains are covered (IM, Calendar, Docs, Base, Contact, Task, Drive,
18+
plus the generic `lark_api` passthrough — note any gaps).

0 commit comments

Comments
 (0)