Skip to content

Commit 2d4f8d0

Browse files
docs(guides): improve Context7 hooks setup guidance
Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
1 parent 7356f2d commit 2d4f8d0

File tree

2 files changed

+303
-1
lines changed

2 files changed

+303
-1
lines changed

docs/docs.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,8 @@
227227
"group": "Building",
228228
"pages": [
229229
"guides/building/droid-exec-tutorial",
230-
"guides/building/droid-vps-setup"
230+
"guides/building/droid-vps-setup",
231+
"guides/building/context7-hooks"
231232
]
232233
}
233234
]
Lines changed: 301 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,301 @@
1+
---
2+
title: "Context7 Hooks: Token Caps + Auto Archive"
3+
description: "Cap the number of tokens retrieved by Context7"
4+
---
5+
6+
<Note>
7+
Context7 already ships in Factory’s MCP registry. Once you authenticate it, you can plug in custom hooks to keep documentation pulls lightweight, logged, and repeatable.
8+
</Note>
9+
10+
<Tip>
11+
New to hooks? Start with [Get started with hooks](/cli/configuration/hooks-guide) for a walkthrough of the hooks UI and configuration model, then come back here to plug in the Context7-specific scripts.
12+
</Tip>
13+
14+
## Prerequisites
15+
16+
- Factory CLI installed
17+
- Context7 account + API token for MCP auth
18+
- `jq` installed (`brew install jq` on macOS)
19+
- A text editor—everything below builds the scripts from scratch
20+
- Hooks feature enabled (run `/settings`, toggle **Hooks** to **Enabled** so the `/hooks` command is available)
21+
22+
## Step 1 · Authenticate the Context7 MCP connector
23+
24+
<Steps>
25+
<Step title="Start the auth flow">
26+
Run the `/mcp` slash command to open the MCP manager. From the **Registry** list, select the `context7` entry to add it, then in the server detail view choose **Authenticate**. Follow the browser prompt; credentials are saved to `~/.factory/mcp-oauth.json`.
27+
</Step>
28+
<Step title="Verify connectivity">
29+
Open `/mcp` again, select the `context7` server, and confirm it shows as enabled and authenticated (you should be able to view its tools, including `get-library-docs`). If not, run **Authenticate** again.
30+
</Step>
31+
</Steps>
32+
33+
## Step 2 · Create the hook scripts
34+
35+
You can either have droid generate the scripts for you, or use a copy‑paste template.
36+
37+
### Option A · Ask droid to generate the scripts
38+
39+
If you want hooks in a project, in your project root, start a droid session and give it a prompt like:
40+
41+
```text
42+
In this repo, create ~/.factory/hooks/context7_token_limiter.sh and ~/.factory/hooks/context7_archive.sh.
43+
The first should be a PreToolUse hook that enforces a MAX_TOKENS limit (3000) on the tool context7___get-library-docs.
44+
The second should archive every successful response as Markdown with YAML frontmatter into ${FACTORY_PROJECT_DIR:-$PWD}/context7-archive.
45+
Use jq and follow the hooks JSON input/output contracts from the hooks reference docs.
46+
```
47+
48+
Review droid’s proposal, tweak as needed, then save the scripts under `~/.factory/hooks/` and make them executable:
49+
50+
```bash
51+
chmod +x ~/.factory/hooks/context7_token_limiter.sh ~/.factory/hooks/context7_archive.sh
52+
```
53+
54+
### Option B · Use the reference template
55+
56+
Ensure the `~/.factory/hooks` directory exists, then create these two files.
57+
58+
**`~/.factory/hooks/context7_token_limiter.sh`**
59+
60+
```bash
61+
#!/usr/bin/env bash
62+
set -euo pipefail
63+
umask 077
64+
65+
MAX_TOKENS="${MAX_TOKENS:-3000}"
66+
LOG_FILE="${LOG_FILE:-$HOME/.factory/hooks/context7.log}"
67+
68+
ts() { date -u +"%Y-%m-%dT%H:%M:%SZ"; }
69+
log() {
70+
mkdir -p "$(dirname "$LOG_FILE")" 2>/dev/null || true
71+
printf "[%s] %s\n" "$(ts)" "$1" >> "$LOG_FILE" 2>/dev/null || true
72+
}
73+
74+
if ! command -v jq >/dev/null 2>&1; then
75+
printf "Warning: jq not found. Allowing tool call.\n" >&2
76+
exit 0
77+
fi
78+
79+
payload="$(cat)"
80+
tool_name="$(printf "%s" "$payload" | jq -r '.tool_name // empty' 2>/dev/null || printf "")"
81+
82+
if [[ "$tool_name" != "context7___get-library-docs" ]]; then
83+
exit 0
84+
fi
85+
86+
tokens="$(printf "%s" "$payload" | jq -r 'if (.tool_input.tokens? // null) == null then "" else (.tool_input.tokens | tonumber | floor) end' 2>/dev/null || printf "")"
87+
88+
if [[ -z "$tokens" ]]; then
89+
exit 0
90+
fi
91+
92+
if ! [[ "$tokens" =~ ^[0-9]+$ ]]; then
93+
printf "Warning: invalid tokens parameter: %s\n" "$tokens" >&2
94+
exit 1
95+
fi
96+
97+
if (( tokens > MAX_TOKENS )); then
98+
log "BLOCKED: Context7 call with $tokens tokens (limit: $MAX_TOKENS)"
99+
cat >&2 <<EOWARN
100+
Context7 token limit exceeded: $tokens > $MAX_TOKENS
101+
102+
Keep queries iterative so each pull stays focused. Reduce the topic scope and re-run at <= $MAX_TOKENS tokens.
103+
EOWARN
104+
exit 2
105+
fi
106+
107+
exit 0
108+
```
109+
110+
**`~/.factory/hooks/context7_archive.sh`**
111+
112+
```bash
113+
#!/usr/bin/env bash
114+
set -euo pipefail
115+
umask 077
116+
117+
ARCHIVE_DIR="${ARCHIVE_DIR:-${FACTORY_PROJECT_DIR:-$PWD}/context7-archive}"
118+
MAX_TOPIC_LENGTH=${MAX_TOPIC_LENGTH:-50}
119+
DEBUG_LOG="$ARCHIVE_DIR/hook-debug.log"
120+
RAW_JSON="${RAW_JSON:-0}"
121+
122+
debug() {
123+
if [[ "${DEBUG:-0}" == "1" ]]; then
124+
mkdir -p "$ARCHIVE_DIR"
125+
echo "[$(date -u +"%Y-%m-%dT%H:%M:%SZ")] $*" >> "$DEBUG_LOG"
126+
fi
127+
}
128+
129+
if ! command -v jq >/dev/null 2>&1; then
130+
echo "Warning: jq not found. Context7 archive hook disabled." >&2
131+
exit 0
132+
fi
133+
134+
payload="$(cat)"
135+
tool_name="$(printf "%s" "$payload" | jq -r '.tool_name // empty' 2>/dev/null || printf "")"
136+
137+
if [[ "$tool_name" != "context7___get-library-docs" ]]; then
138+
debug "Skipping non-Context7 tool"
139+
exit 0
140+
fi
141+
142+
if [[ "$RAW_JSON" == "1" ]]; then
143+
results="$(printf "%s" "$payload" | jq -c '.tool_response' 2>/dev/null || printf "")"
144+
else
145+
results="$(printf "%s" "$payload" | jq -r '.tool_response | if type == "string" then . else (.text // tojson) end' 2>/dev/null || printf "")"
146+
fi
147+
148+
if [[ -z "$results" || "$results" == "null" ]]; then
149+
debug "No results to archive"
150+
exit 0
151+
fi
152+
153+
library_id="$(printf "%s" "$payload" | jq -r '.tool_input.context7CompatibleLibraryID // "unknown-library"' 2>/dev/null || printf "unknown-library")"
154+
topic="$(printf "%s" "$payload" | jq -r '.tool_input.topic // "untitled"' 2>/dev/null || printf "untitled")"
155+
156+
project_name="$(basename "${FACTORY_PROJECT_DIR:-$PWD}" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9-]/-/g')"
157+
library_slug="$(printf "%s" "$library_id" | sed 's|^/||' | cut -d/ -f2 | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9-]/-/g')"
158+
library_slug="${library_slug:-unknown}"
159+
topic_slug="$(printf "%s" "$topic" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9 -]//g' | sed 's/ */-/g' | sed 's/^-//' | sed 's/-$//')"
160+
topic_slug="${topic_slug:-untitled}"
161+
162+
if [[ ${#topic_slug} -gt $MAX_TOPIC_LENGTH ]]; then
163+
topic_slug="${topic_slug:0:$MAX_TOPIC_LENGTH}"
164+
topic_slug="${topic_slug%-*}"
165+
fi
166+
167+
mkdir -p "$ARCHIVE_DIR"
168+
stamp="$(date +"%Y%m%d")"
169+
base="${stamp}_${project_name}_${library_slug}_${topic_slug}"
170+
file="$ARCHIVE_DIR/${base}.md"
171+
counter=1
172+
while [[ -f "$file" ]]; do
173+
file="$ARCHIVE_DIR/${base}-${counter}.md"
174+
((counter++))
175+
done
176+
177+
cat > "$file" <<EOC
178+
---
179+
query_date: $(date -u +"%Y-%m-%d %H:%M:%S UTC")
180+
library: $library_id
181+
topic: $topic
182+
project: $project_name
183+
tool: $tool_name
184+
---
185+
186+
# Context7 Query: $topic
187+
188+
$results
189+
EOC
190+
191+
debug "Archived to $file"
192+
exit 0
193+
```
194+
195+
Then make both scripts executable:
196+
197+
```bash
198+
chmod +x ~/.factory/hooks/context7_token_limiter.sh ~/.factory/hooks/context7_archive.sh
199+
```
200+
201+
<Note>
202+
The matcher should target the LLM tool name `context7___get-library-docs`. If you’re unsure, inspect `~/.factory/hooks/context7.log` (written by the limiter) or open the latest transcript file (see the `transcript_path` field, typically under `~/.factory/projects/...`) to inspect the `tool_name` value.
203+
</Note>
204+
205+
## Step 3 · Token limiter (PreToolUse)
206+
207+
- **Hook file:** `~/.factory/hooks/context7_token_limiter.sh`
208+
- **Purpose:** Block any `context7___get-library-docs` call that requests more than 3,000 tokens.
209+
- **Useful env vars:**
210+
211+
```bash
212+
export MAX_TOKENS=3000
213+
export LOG_FILE="$HOME/.factory/hooks/context7.log" # Optional auditing
214+
```
215+
216+
When the script exits with code 2, Factory halts the tool call and surfaces the warning text to the assistant.
217+
218+
## Step 4 · Archive writer (PostToolUse)
219+
220+
- **Hook file:** `~/.factory/hooks/context7_archive.sh`
221+
- **Purpose:** Save every successful Context7 response as Markdown in `${FACTORY_PROJECT_DIR}/context7-archive` (falls back to your current repo if the env var is unset).
222+
- **Useful env vars:**
223+
224+
```bash
225+
export DEBUG=1 # Verbose logging to hook-debug.log
226+
export ARCHIVE_DIR="$HOME/context7-history" # Optional custom location
227+
export RAW_JSON=1 # Store raw JSON payloads instead of rendered text
228+
```
229+
230+
Each file includes YAML frontmatter so you can grep or index entries later (e.g., `20251114_myapp_nextjs_server-actions.md`).
231+
232+
## Step 5 · Register the hooks
233+
234+
You can register these hooks either through the `/hooks` UI (recommended) or by editing `~/.factory/settings.json` directly.
235+
236+
### Option A - Use the Hooks UI
237+
238+
1. Run `/settings` and make sure **Hooks** is set to **Enabled**.
239+
2. Run `/hooks`, select the **PreToolUse** event, and add a matcher `context7___get-library-docs`. Hit enter to save.
240+
3. Add a `command`: `~/.factory/hooks/context7_token_limiter.sh`, and store it in **User settings**.
241+
4. Repeat for **PostToolUse**, matcher `context7___get-library-docs`, command `~/.factory/hooks/context7_archive.sh`.
242+
243+
### Option B - Edit settings JSON
244+
245+
Open `~/.factory/settings.json` and add a `hooks` block like this (merging with any existing hooks):
246+
247+
```jsonc
248+
{
249+
"hooks": {
250+
"PreToolUse": [
251+
{
252+
"matcher": "context7___get-library-docs",
253+
"hooks": [
254+
{
255+
"type": "command",
256+
"command": "~/.factory/hooks/context7_token_limiter.sh",
257+
"timeout": 5000
258+
}
259+
]
260+
}
261+
],
262+
"PostToolUse": [
263+
{
264+
"matcher": "context7___get-library-docs",
265+
"hooks": [
266+
{
267+
"type": "command",
268+
"command": "~/.factory/hooks/context7_archive.sh",
269+
"timeout": 5000
270+
}
271+
]
272+
}
273+
]
274+
}
275+
}
276+
```
277+
278+
Using the exact LLM tool name `context7___get-library-docs` ensures you only target the Context7 docs fetch tool. You can also use regex matchers (see [Hooks reference](/reference/hooks-reference)) if you need to match multiple Context7 tools.
279+
280+
Restart Factory (or reopen your session) after editing your hooks configuration.
281+
282+
## Step 6 · Test the workflow
283+
284+
<Steps>
285+
<Step title="Limiter">
286+
Ask Context7 for something intentionally huge, e.g. “Pull the entire Factory documentation with context7 mcp". The hook should block it at 3,000 tokens. (Factory already has a local codemap for its docs; this request is purely for testing.)
287+
</Step>
288+
<Step title="Archive">
289+
Run a normal Context7 request. Confirm `context7-archive/` now contains a timestamped Markdown file with the query results.
290+
</Step>
291+
</Steps>
292+
293+
## Troubleshooting & customization
294+
295+
- **Matcher typos:** If the hooks never run, double-check the matcher value against `context7___get-library-docs`. One missing underscore is enough to break it.
296+
- **Missing `jq`:** Install it with `brew install jq` (macOS) or your distro’s package manager.
297+
- **Permissions:** Ensure every script in `~/.factory/hooks` is executable (`chmod +x ~/.factory/hooks/*`).
298+
- **Archive clutter:** Add `context7-archive/` to `.gitignore` if you don’t plan to commit the saved docs.
299+
- **Timeouts:** Increase the `timeout` field in `hooks.json` if you routinely archive very large responses.
300+
301+
With these two hooks in place, every Context7 pull stays within a predictable token budget and automatically lands in a searchable knowledge base.

0 commit comments

Comments
 (0)