Skip to content

Commit 9022cea

Browse files
john-walkoeclaude
andcommitted
Add tool search, centralized key storage, PTAB updates, and thread-safe proxy startup
Implements four major enhancements to improve context efficiency, user experience, API compatibility, and reliability: 1. Tool Search Configuration (60-75% token reduction) - Add SERVER_INSTRUCTIONS for Claude Code v2.1.7+ tool search - Make 3 tools always-available: fpd_search_petitions_minimal, fpd_get_petition_details, fpd_get_guidance - Enable progressive disclosure for remaining tools (on-demand loading) 2. Centralized Linux Key Storage - Add key detection functions to Validation-Helpers.sh - Implement prompt_and_validate_uspto_key() with existing key detection - Implement prompt_and_validate_mistral_key() with existing key detection - Users only enter API keys once across all 4 USPTO MCPs (PFW, FPD, PTAB, Citations) 3. PTAB Tool Reference Updates - Update all references from ptab_search_proceedings_minimal to search_trials_minimal - Reflect PTAB API changes (new tool names and progressive disclosure) - Add PTAB fields parameter documentation to tool_reflections.py - Update 4 prompt files: art_unit_quality_assessment, company_petition_risk_assessment_pfw, patent_vulnerability_assessment_ptab, complete_portfolio_due_diligence_pfw_ptab 4. On-Demand Proxy Startup Fix - Add asyncio.Lock to prevent race conditions on concurrent downloads - Implement double-check pattern (fast path + lock + double-check) - Add health check verification after proxy startup - Enhanced error handling and logging with emoji status messages All changes verified and tested according to implementation plan. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
1 parent 40671c3 commit 9022cea

File tree

7 files changed

+362
-29
lines changed

7 files changed

+362
-29
lines changed

deploy/Validation-Helpers.sh

Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,247 @@ validate_mistral_api_key() {
8484
return 0
8585
}
8686

87+
# ============================================
88+
# Existing Key Detection Functions
89+
# ============================================
90+
91+
# Check if USPTO API key exists in secure storage
92+
check_existing_uspto_key() {
93+
if [[ -f "$HOME/.uspto_api_key" ]]; then
94+
return 0 # Exists
95+
else
96+
return 1 # Does not exist
97+
fi
98+
}
99+
100+
# Check if Mistral API key exists in secure storage
101+
check_existing_mistral_key() {
102+
if [[ -f "$HOME/.mistral_api_key" ]]; then
103+
return 0 # Exists
104+
else
105+
return 1 # Does not exist
106+
fi
107+
}
108+
109+
# Load existing USPTO key from secure storage (uses Python)
110+
load_existing_uspto_key() {
111+
# Use Python to load from secure storage
112+
python3 << 'EOF'
113+
import sys
114+
from pathlib import Path
115+
sys.path.insert(0, str(Path.cwd() / 'src'))
116+
117+
try:
118+
from fpd_mcp.shared_secure_storage import get_uspto_api_key
119+
key = get_uspto_api_key()
120+
if key:
121+
print(key)
122+
sys.exit(0)
123+
else:
124+
sys.exit(1)
125+
except Exception as e:
126+
sys.exit(1)
127+
EOF
128+
}
129+
130+
# Load existing Mistral key from secure storage (uses Python)
131+
load_existing_mistral_key() {
132+
# Use Python to load from secure storage
133+
python3 << 'EOF'
134+
import sys
135+
from pathlib import Path
136+
sys.path.insert(0, str(Path.cwd() / 'src'))
137+
138+
try:
139+
from fpd_mcp.shared_secure_storage import get_mistral_api_key
140+
key = get_mistral_api_key()
141+
if key:
142+
print(key)
143+
sys.exit(0)
144+
else:
145+
sys.exit(1)
146+
except Exception as e:
147+
sys.exit(1)
148+
EOF
149+
}
150+
151+
# Mask API key for display (show last 5 characters)
152+
mask_api_key() {
153+
local key="$1"
154+
local key_length=${#key}
155+
156+
if [[ $key_length -le 5 ]]; then
157+
echo "$key" # Too short to mask
158+
else
159+
echo "...${key: -5}"
160+
fi
161+
}
162+
163+
# Prompt user if they want to use existing key
164+
prompt_use_existing_key() {
165+
local key_type="$1" # "USPTO" or "Mistral"
166+
local masked_key="$2"
167+
168+
echo ""
169+
echo -e "${GREEN}✓ Detected existing $key_type API key in secure storage${NC}"
170+
echo -e " Key (masked): ${masked_key}"
171+
echo ""
172+
read -p "Would you like to use this existing key? (Y/n): " USE_EXISTING
173+
USE_EXISTING=${USE_EXISTING:-Y}
174+
175+
if [[ "$USE_EXISTING" =~ ^[Yy]$ ]]; then
176+
return 0 # Use existing
177+
else
178+
return 1 # Don't use existing
179+
fi
180+
}
181+
182+
# Securely read API key (no echo to terminal)
183+
read_api_key_secure() {
184+
local prompt="$1"
185+
local varname="$2"
186+
187+
read -s -p "$prompt: " $varname
188+
echo # Newline after hidden input
189+
}
190+
191+
# Prompt for API key with validation loop (with existing key detection)
192+
prompt_and_validate_uspto_key() {
193+
local key=""
194+
local max_attempts=3
195+
local attempt=0
196+
197+
# STEP 1: Check if key already exists in secure storage
198+
if check_existing_uspto_key; then
199+
echo -e "${YELLOW}ℹ Checking existing USPTO API key from another USPTO MCP installation...${NC}"
200+
201+
# Try to load the existing key
202+
local existing_key=$(load_existing_uspto_key)
203+
if [[ $? -eq 0 && -n "$existing_key" ]]; then
204+
# Mask the key for display
205+
local masked_key=$(mask_api_key "$existing_key")
206+
207+
# Ask user if they want to use it
208+
if prompt_use_existing_key "USPTO" "$masked_key"; then
209+
echo -e "${GREEN}✓ Using existing USPTO API key from secure storage${NC}"
210+
echo "$existing_key"
211+
return 0
212+
else
213+
echo -e "${YELLOW}ℹ You chose to enter a new USPTO API key${NC}"
214+
echo -e "${RED}⚠ This will OVERWRITE the existing key for ALL USPTO MCPs${NC}"
215+
read -p "Are you sure you want to continue? (y/N): " CONFIRM_OVERWRITE
216+
if [[ ! "$CONFIRM_OVERWRITE" =~ ^[Yy]$ ]]; then
217+
echo -e "${GREEN}ℹ Keeping existing key${NC}"
218+
echo "$existing_key"
219+
return 0
220+
fi
221+
fi
222+
else
223+
echo -e "${YELLOW}⚠ Existing key file found but could not load (may be corrupted)${NC}"
224+
echo -e "${YELLOW}ℹ You will need to enter a new key${NC}"
225+
fi
226+
fi
227+
228+
# STEP 2: Prompt for new key (either no existing key, or user wants to override)
229+
while [[ $attempt -lt $max_attempts ]]; do
230+
((attempt++))
231+
232+
read_api_key_secure "Enter your USPTO API key" key
233+
234+
if [[ -z "$key" ]]; then
235+
echo -e "${RED}ERROR: USPTO API key cannot be empty${NC}"
236+
if [[ $attempt -lt $max_attempts ]]; then
237+
echo -e "${YELLOW}Attempt $attempt of $max_attempts${NC}"
238+
fi
239+
continue
240+
fi
241+
242+
if validate_uspto_api_key "$key"; then
243+
# Success - return key via echo
244+
echo "$key"
245+
return 0
246+
else
247+
if [[ $attempt -lt $max_attempts ]]; then
248+
echo -e "${YELLOW}Attempt $attempt of $max_attempts - please try again${NC}"
249+
echo -e "${YELLOW}USPTO API key format: 30 lowercase letters (a-z)${NC}"
250+
fi
251+
fi
252+
done
253+
254+
echo -e "${RED}ERROR: Failed to provide valid USPTO API key after $max_attempts attempts${NC}"
255+
return 1
256+
}
257+
258+
# Prompt for Mistral API key with validation loop (optional, with existing key detection)
259+
prompt_and_validate_mistral_key() {
260+
local key=""
261+
local max_attempts=3
262+
local attempt=0
263+
264+
# STEP 1: Check if key already exists in secure storage
265+
if check_existing_mistral_key; then
266+
echo -e "${YELLOW}ℹ Checking existing Mistral API key from another USPTO MCP installation...${NC}"
267+
268+
# Try to load the existing key
269+
local existing_key=$(load_existing_mistral_key)
270+
if [[ $? -eq 0 && -n "$existing_key" ]]; then
271+
# Mask the key for display
272+
local masked_key=$(mask_api_key "$existing_key")
273+
274+
# Ask user if they want to use it
275+
if prompt_use_existing_key "Mistral" "$masked_key"; then
276+
echo -e "${GREEN}✓ Using existing Mistral API key from secure storage${NC}"
277+
echo "$existing_key"
278+
return 0
279+
else
280+
echo -e "${YELLOW}ℹ You chose to enter a new Mistral API key${NC}"
281+
echo -e "${RED}⚠ This will OVERWRITE the existing key for ALL USPTO MCPs${NC}"
282+
read -p "Are you sure you want to continue? (y/N): " CONFIRM_OVERWRITE
283+
if [[ ! "$CONFIRM_OVERWRITE" =~ ^[Yy]$ ]]; then
284+
echo -e "${GREEN}ℹ Keeping existing key${NC}"
285+
echo "$existing_key"
286+
return 0
287+
fi
288+
fi
289+
else
290+
echo -e "${YELLOW}⚠ Existing key file found but could not load (may be corrupted)${NC}"
291+
echo -e "${YELLOW}ℹ You will need to enter a new key${NC}"
292+
fi
293+
fi
294+
295+
# STEP 2: Prompt for new key (either no existing key, or user wants to override)
296+
echo -e "${YELLOW}ℹ Mistral API key is OPTIONAL (for OCR on scanned documents)${NC}"
297+
echo -e "${YELLOW}ℹ Press Enter to skip, or enter your 32-character Mistral API key${NC}"
298+
echo
299+
300+
while [[ $attempt -lt $max_attempts ]]; do
301+
((attempt++))
302+
303+
read_api_key_secure "Enter your Mistral API key (or press Enter to skip)" key
304+
305+
# Empty is OK (optional)
306+
if [[ -z "$key" ]]; then
307+
echo -e "${YELLOW}ℹ Skipping Mistral API key (OCR disabled)${NC}"
308+
echo "" # Return empty string
309+
return 0
310+
fi
311+
312+
if validate_mistral_api_key "$key"; then
313+
# Success - return key
314+
echo "$key"
315+
return 0
316+
else
317+
if [[ $attempt -lt $max_attempts ]]; then
318+
echo -e "${YELLOW}Attempt $attempt of $max_attempts - please try again${NC}"
319+
echo -e "${YELLOW}Mistral API key format: 32 alphanumeric characters (a-z, A-Z, 0-9)${NC}"
320+
fi
321+
fi
322+
done
323+
324+
echo -e "${RED}ERROR: Failed to provide valid Mistral API key after $max_attempts attempts${NC}"
325+
return 1
326+
}
327+
87328
# Test function (run with: bash Validation-Helpers.sh test)
88329
if [[ "${BASH_SOURCE[0]}" == "${0}" ]] && [[ "$1" == "test" ]]; then
89330
echo "Testing API Key Validation..."

src/fpd_mcp/config/tool_reflections.py

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -254,7 +254,7 @@ def _get_workflows_ptab_section() -> str:
254254
```
255255
3. **PTAB:** Check for post-grant challenges
256256
```python
257-
ptab_search_proceedings_minimal(patent_number='11788453')
257+
search_trials_minimal(patent_number='11788453')
258258
```
259259
4. Correlation Analysis:
260260
- Patents with denied examiner dispute petitions → Higher PTAB vulnerability
@@ -267,6 +267,29 @@ def _get_workflows_ptab_section() -> str:
267267
- High petition frequency + PTAB institution = Pattern of prosecution problems
268268
269269
**Value:** Predict PTAB vulnerability based on prosecution petition patterns
270+
271+
### PTAB Search Tool Parameters
272+
273+
**All PTAB search tools support:**
274+
- `patent_number`: Patent number (e.g., '10701173')
275+
- `petitioner_name`: Party filing the challenge
276+
- `patent_owner_name`: Patent owner name
277+
- `trial_type`: 'IPR', 'PGR', 'CBM'
278+
- `trial_status`: Status category
279+
- `tech_center`: Technology center (e.g., '2600')
280+
- `filing_date_from/to`: Date range filters
281+
- `fields`: Ultra-minimal field selection (99% context reduction)
282+
- `limit`: Max results (default 50, max 100)
283+
284+
**Example - Ultra-minimal PTAB query:**
285+
```python
286+
# Only 2 fields - 99% context reduction
287+
search_trials_minimal(
288+
patent_number='10701173',
289+
fields=['trialNumber', 'trialMetaData.trialStatusCategory'],
290+
limit=20
291+
)
292+
```
270293
"""
271294

272295

@@ -382,7 +405,7 @@ def _get_workflows_complete_section() -> str:
382405
383406
4. **PTAB Challenge Analysis (PTAB)** - For granted patents
384407
```python
385-
ptab_search_proceedings_minimal(patent_number=patent_num)
408+
search_trials_minimal(patent_number=patent_num)
386409
```
387410
Assess post-grant challenge exposure
388411
@@ -645,7 +668,7 @@ def _get_tools_section() -> str:
645668
- Action: Full details and document access
646669
647670
**Stage 5: Cross-MCP**
648-
- Tools: pfw_search_applications, ptab_search_proceedings
671+
- Tools: pfw_search_applications, search_trials
649672
- Action: Cross-reference with prosecution and PTAB
650673
"""
651674

0 commit comments

Comments
 (0)