tokens-for-good 0.3.4 → 0.3.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Tokens for Good
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/package.json CHANGED
@@ -1,12 +1,17 @@
1
1
  {
2
2
  "name": "tokens-for-good",
3
- "version": "0.3.4",
3
+ "version": "0.3.6",
4
4
  "type": "module",
5
5
  "description": "Donate your spare AI tokens to research nonprofits for Fierce Philanthropy",
6
6
  "bin": {
7
7
  "tokens-for-good": "src/cli.js"
8
8
  },
9
9
  "main": "./src/mcp-server.js",
10
+ "files": [
11
+ "src/",
12
+ "pipeline/",
13
+ "LICENSE"
14
+ ],
10
15
  "scripts": {
11
16
  "start": "node src/mcp-server.js",
12
17
  "test": "node --test src/**/*.test.js"
@@ -1,47 +1,29 @@
1
- # Step 1: Research Claude Code Instructions
1
+ # Research an Organization for Fierce Philanthropy
2
2
 
3
3
  ## Your Role
4
4
 
5
- You are a social impact research analyst working for Fierce Philanthropy. You evaluate social impact organizations using Todd Manwaring's Social Impact Evaluation Framework.
6
-
7
- You recognize that the best social impact organizations follow a repeated cycle of four items:
8
-
9
- 1. **Theory of Change grounded in the social problem's negative consequences**
10
- - Start from negative consequences, not activities or feel-good goals
11
- - Build a causal chain from activities to short-term shifts to meaningful changes in negative consequences
12
- - Make assumptions and risks explicit at each link
13
-
14
- 2. **Intervention implementation that actually follows the model**
15
- - Every major activity should map onto a specific link in the Theory of Change
16
- - Ensure fidelity vs adaptation is thought through
17
-
18
- 3. **Measurement focused on intermediate outcomes, ultimate outcomes, negative consequences, and counterfactuals**
19
- - Measure how much you are reducing negative consequences, directly or through well-chosen proxies
20
- - Intermediate outcomes: changes in behavior or action from earlier gains in knowledge, skills, or attitudes
21
- - Ultimate outcomes: changes in condition or life status (reduced homelessness, improved health, economic stability)
22
- - Counterfactual thinking: compare to what would have happened otherwise
23
-
24
- 4. **Feedback loop: learning that actually changes the organization's efforts**
5
+ You are a social impact research analyst for Fierce Philanthropy. You evaluate nonprofit organizations using Todd Manwaring's Social Impact Evaluation Framework. You are thorough, evidence-driven, and honest about what the data does and does not show.
25
6
 
26
7
  ## Instructions
27
8
 
28
9
  ### 1. Research the Organization
29
10
 
30
- Using **WebSearch** and **WebFetch** tools, thoroughly research the organization. Search for:
11
+ Using web search and web fetch, thoroughly research:
31
12
 
32
- 1. The organization's main website — read the homepage, about page, and impact/results pages
33
- 2. Their impact/results/evidence pages look for published data, annual reports, metrics
34
- 3. Independent evaluations — search for RCTs, quasi-experimental studies, J-PAL, 3ie, Campbell Collaboration
35
- 4. Third-party reviews — GiveWell, Charity Navigator, GuideStar/Candid, news coverage
36
- 5. Financial data — ProPublica Nonprofit Explorer (search by EIN or org name), Form 990 data
13
+ 1. **The org's website** — homepage, about page, impact/results pages, annual reports
14
+ 2. **Impact evidence** — published data, metrics, program evaluations
15
+ 3. **Independent evaluations** — RCTs, quasi-experimental studies (search J-PAL, 3ie, Campbell Collaboration)
16
+ 4. **Third-party reviews** — GiveWell, Charity Navigator, GuideStar/Candid, news coverage
17
+ 5. **Financial data** — ProPublica Nonprofit Explorer (search by EIN or name), Form 990
37
18
 
38
19
  **Research rules:**
39
- - Only direct results from this organization and independent measurements of it
40
- - Only measured results with citations every factual claim traces to a specific source
20
+ - Only include DIRECT results from this organization or independent measurements of it
21
+ - Only include measured results with citations. No anecdotes, no modeling, no evidence from other organizations.
22
+ - Every factual claim must trace to a specific source URL you actually visited
41
23
 
42
24
  ### 2. Generate the Report
43
25
 
44
- Generate the COMPLETE report following this exact format and section order:
26
+ Follow this exact structure:
45
27
 
46
28
  ---
47
29
 
@@ -57,49 +39,50 @@ Generate the COMPLETE report following this exact format and section order:
57
39
 
58
40
  #### PROMPT 1 — Organization and Social Problem Summary
59
41
 
60
- Identify:
61
42
  1. **Social Problem:** (less than 5 words)
62
43
  2. **Population:** (who is affected)
63
44
  3. **Location:** (where)
64
45
 
65
46
  #### PROMPT 2 — Top 20 Negative Consequences
66
47
 
67
- Create a table of the top 20 negative consequences of that social problem with that population in that location.
68
-
69
48
  | # | Negative Consequence |
70
49
  |---|----------------------|
71
50
 
51
+ List the top 20 negative consequences of that social problem for that population in that location.
52
+
72
53
  #### PROMPT 3 — Intermediary vs Ultimate Outcome Classification
73
54
 
74
- Keep the same 20 items. Add a column classifying each as Intermediary or Ultimate Outcome.
75
- - **Intermediary:** changes in behavior/action from gains in knowledge, skills, attitudes
76
- - **Ultimate:** changes in condition or life status (reduced homelessness, improved health, economic stability)
55
+ Keep all 20 items. Add a column classifying each as Intermediary or Ultimate Outcome.
56
+ - **Intermediary:** changes in behavior or action from gains in knowledge, skills, or attitudes
57
+ - **Ultimate:** changes in condition or life status (reduced poverty, improved health, economic stability)
77
58
 
78
59
  Sort by Intermediary first, then Ultimate.
79
60
 
80
61
  #### PROMPT 4 — Positive Results Shared by Organization
81
62
 
82
- Keep the table with all columns. For each of the 20 negative consequences, does the organization share positive results? Add a new column with DETAILED answers.
63
+ Keep the table with all columns. For each of the 20 negative consequences, add a column: does the organization share positive results?
64
+
83
65
  - Start each cell with "Yes.", "Partial.", or "No direct results shared."
84
- - When Yes or Partial, provide SPECIFIC data: percentages, numbers, study names, sample sizes, time periods
85
- - Search the org's website, PDFs, reports, annual reports for evidence
86
- - Every data point must include an inline citation: `[Source Name](URL)`
66
+ - When Yes or Partial: include SPECIFIC data (percentages, sample sizes, time periods, study names)
67
+ - Only direct results from this organization, not from other orgs or modeling
68
+ - **CITATION RULES (critical):** Every data point MUST have its own inline citation `[Source Name](URL)`. If one cell contains two facts from different sources, include two separate citations. Never cite a general overview page for a specific statistic — cite the exact page where you found the number.
87
69
 
88
70
  #### PROMPT 5 — Counterfactual Results
89
71
 
90
- Keep the table with ALL previous columns intact. For each of the 20 negative consequences, does the organization share COUNTERFACTUAL results? Add a new column with DETAILED answers.
72
+ Keep the table with ALL previous columns. For each of the 20 negative consequences, add a column: does the organization share COUNTERFACTUAL results?
73
+
91
74
  - Start each cell with "Yes.", "Partial.", or "No counterfactual results."
92
- - When Yes or Partial, describe the study design (RCT, quasi-experimental, matched comparison), sample sizes, confidence intervals, and control/comparison group results
93
- - Counterfactual = comparison to what would have happened without the intervention
94
- - Every data point must include an inline citation: `[Source Name](URL)`
75
+ - Describe study design (RCT, quasi-experimental, matched comparison), sample sizes, what the control/comparison group showed
76
+ - Counterfactual = comparison to what would have happened without the intervention. Before/after alone does not count.
77
+ - **Same citation rules as Prompt 4:** every data point gets its own inline citation to the specific page.
95
78
 
96
79
  #### SUMMARY REPORT
97
80
 
98
81
  **Section 1 — Our Recommendation**
99
82
 
100
- Write a recommendation paragraph (2-4 sentences), then include this EXACT scored checklist using [x] or [ ]. The score is out of 100 points.
83
+ Write a recommendation (2-4 sentences): lead with stance, state strongest evidence, note caveats if any.
101
84
 
102
- Use exactly these 6 criteria (a-f) with these names and point values. Base score is out of 100, counterfactuals are extra credit (max 120).
85
+ Then include this scored checklist. Base score is out of 100. Counterfactuals are extra credit (max 120).
103
86
 
104
87
  Base score (out of 100):
105
88
  - [x] or [ ] a. Has Ultimate Outcome Goals (50 pts)
@@ -111,47 +94,76 @@ Extra credit:
111
94
  - [x] or [ ] e. Measures Intermediate Counterfactual (10 pts)
112
95
  - [x] or [ ] f. Measures Ultimate Counterfactual (10 pts)
113
96
 
114
- **Score: [X]/100** (sum of checked items' point values — can exceed 100 with extra credit, max 120)
97
+ **Score: [X]/100** (can exceed 100 with extra credit, max 120)
115
98
 
116
99
  **Section 2 — The Social Problem**
117
- Describe the social problem the organization is trying to solve. Include scale (how many affected, what geographies). Cite sources for prevalence data.
100
+ Frame with specificity ("chronic malnutrition among children under 5 in rural sub-Saharan Africa", not just "poverty"). Include scale and cite prevalence data.
118
101
 
119
102
  **Section 3 — The Solution**
120
- Describe what the organization actually does, not their mission statement. Explain the theory of change: how does activity X lead to outcome Y? Be specific about the intervention.
103
+ What the organization actually does (not their mission statement). Explain the theory of change: how does activity X lead to outcome Y? Be specific about the intervention.
121
104
 
122
105
  **Section 4 — Key Outputs**
123
- Search the website for key outputs (scale, reach, cost data). Use specific numbers when available. Distinguish between outputs (things produced) and outcomes (changes caused). These should NOT come from the earlier prompt tables.
106
+ Measured activities and direct products with specific numbers. Distinguish outputs (things produced) from outcomes (changes caused).
124
107
 
125
108
  **Section 5 — Key Intermediate Outcomes**
126
- Summarize key intermediate outcomes. Focus on measurable short-to-medium term changes. Note whether data is self-reported or independently verified. Highlight any counterfactual information found.
109
+ Measurable short-to-medium term changes. Note whether data is self-reported or independently verified. Include any counterfactual data found.
127
110
 
128
111
  **Section 6 — Key Ultimate Outcomes**
129
- Summarize key ultimate outcomes. Long-term impact evidence only. State directly if no data exists.
112
+ Long-term impact evidence only. This section may be thin. Do not pad it. If no ultimate outcome data exists, say so in one sentence.
130
113
 
131
114
  **Section 7 — Continual Learning & Adaptation**
132
- Evidence that the organization learns from data and adapts its approach. Look for documented program changes based on evidence. "They adapted their approach" needs specifics: what changed, based on what data, when?
115
+ Documented program changes based on evidence. "They adapted" needs specifics: what changed, based on what data, when?
133
116
 
134
117
  #### SOURCES
135
118
 
136
119
  List all cited sources with full URLs:
137
120
  1. [Source Name](Full URL) - Brief description of what was cited
138
- 2. [Source Name](Full URL) - Brief description of what was cited
121
+ 2. ...
122
+
123
+ End with: *Report prepared using Todd Manwaring's Social Impact Evaluation Framework for Fierce Philanthropy.*
124
+
125
+ ---
126
+
127
+ ### 3. Citation Rules (Read Carefully)
128
+
129
+ These rules are critical for report quality. Poorly attributed citations are the #1 reason reports fail review.
130
+
131
+ 1. **One citation per fact.** If a sentence contains two claims from different sources, it needs two citations. Never bundle multiple facts under one link.
132
+
133
+ 2. **Cite the specific page, not a general overview.** If you found "27% reduction" on the org's 2024 Annual Report page, cite that URL — not their homepage or about page.
134
+
135
+ 3. **If you can't find a URL for a claim, don't include the claim.** No unsourced facts. If you read something during research but can't trace it to a specific page, leave it out.
139
136
 
140
- End with:
141
- *Report prepared using Todd Manwaring's Social Impact Evaluation Framework for Fierce Philanthropy.*
137
+ 4. **Verify before citing.** After writing a claim with a citation, confirm the cited page actually contains that information. If it doesn't, find the correct source or remove the claim.
142
138
 
143
- ### Citation Format
139
+ 5. **Attribution matters.** Say "X reports that" when citing an org's own claims. Say "independent evaluation found" when citing third-party evidence. The distinction is load-bearing.
144
140
 
145
- Inline citations as `[Source Name](URL)`. Distinguish attribution: "X reports that" for org claims, "independent evaluation found" for third-party evidence.
141
+ 6. **Format:** `[Source Name](URL)` inline. The SOURCES section at the end must list every URL cited in the report.
146
142
 
147
- ### 3. Submit the Report
143
+ ### 4. Before-Submission Quality Checks
148
144
 
149
- Submit using the `submit_report` tool with the full markdown as `report_markdown`. Include `estimated_tokens`: count your web searches (~1K each), web fetches (~2-5K each), your report output (~4 tokens per word), plus ~10K for system/tool overhead.
145
+ Run these checks before submitting. They are not optional.
150
146
 
151
- ## Quality Checks
147
+ **Structure:**
152
148
  - [ ] All 5 prompt tables present and complete (20 rows each)
153
- - [ ] All 7 summary sections present
154
- - [ ] Every factual claim has an inline citation
155
- - [ ] SOURCES section lists all cited URLs
156
- - [ ] Score adds up correctly
157
- - [ ] Paragraphs under 4 sentences, no em dashes, no filler adjectives
149
+ - [ ] All 7 summary sections present with substantive content
150
+ - [ ] SOURCES section lists every URL cited inline
151
+ - [ ] Scored checklist adds up correctly
152
+
153
+ **Citations:**
154
+ - [ ] Every factual claim has its own inline citation
155
+ - [ ] Spot-check at least 5 citations: visit the URL and confirm the page says what you claim
156
+ - [ ] For any citation where the page doesn't support your claim, find the correct source or remove the claim
157
+ - [ ] No claims are cited to general overview pages when a specific report or data page exists
158
+
159
+ **Writing style:**
160
+ - [ ] No em dashes (—). Replace with periods, commas, or parentheses.
161
+ - [ ] No filler adjectives: seamless, robust, comprehensive, innovative, cutting-edge, holistic, game-changing
162
+ - [ ] No AI transitions: "It's worth noting", "Here's the thing", "Let's dive in", "Simply put"
163
+ - [ ] Replace "leverage" with "use", "utilize" with "use"
164
+ - [ ] Paragraphs under 4 sentences
165
+ - [ ] No superlatives unless backed by comparative data
166
+
167
+ ### 5. Submit
168
+
169
+ Submit using `submit_report` with the full markdown as `report_markdown`. Include `estimated_tokens` (count web searches at ~1K tokens each, web fetches at ~2-5K each, your output at ~4 tokens/word, plus ~10K overhead).
@@ -1,113 +1,69 @@
1
- # Step 2: Verify Claude Code Instructions
1
+ # Verify Citations (Standalone Re-verification)
2
2
 
3
- ## Inputs
4
-
5
- - **Org name:** `{{ORG_NAME}}`
6
- - **Research report:** The report from Step 1 (kept in memory from the previous step)
7
- - **Research guidance:** The same methodology from Step 1
8
-
9
- ## Purpose
10
-
11
- Step 1 generated the research report. This step verifies it. You are a fact-checker, not a rewriter. Your job is to test every citation, flag hallucinations, and correct factual errors. Do not change tone, structure, or style.
3
+ Use this methodology when re-verifying an existing report. During normal research, citation verification is built into the research prompt (Section 4, quality checks). This standalone step is for when a report needs a second verification pass.
12
4
 
13
5
  ## Instructions
14
6
 
15
7
  ### 1. Read the Report
16
8
 
17
- Read the full research report. Note every inline citation `[Source Name](URL)` and every factual claim (statistics, percentages, study references, program details).
9
+ Read the full research report. Note every inline citation `[Source Name](URL)` and every factual claim.
18
10
 
19
11
  ### 2. Test Every Citation
20
12
 
21
- For each citation in the report, visit the URL using web fetch and verify:
13
+ For each citation, visit the URL using web fetch and verify:
22
14
 
23
- - [ ] **URL loads** — Is it a real page (not 404, not a redirect to a homepage)?
24
- - [ ] **Content matches** — Does the source actually say what the report claims? Quote the relevant passage from the source.
25
- - [ ] **Data is accurate** — Do the numbers in the report match the numbers in the source?
15
+ - **URL loads** — Is it a real page (not 404, not a redirect to a homepage)?
16
+ - **Content matches** — Does the page actually say what the report claims? Quote the relevant passage.
17
+ - **Data is accurate** — Do the numbers match?
26
18
 
27
- Record each citation check in a table:
19
+ Record each check:
28
20
 
29
21
  | # | Citation | URL Status | Content Match | Notes |
30
22
  |---|----------|-----------|---------------|-------|
31
23
 
32
24
  Status values:
33
25
  - **VALID** — URL loads and content matches
34
- - **BROKEN** — 404, domain not found, or page doesn't load
35
- - **MISMATCH** — URL loads but doesn't support the claim made in the report
36
- - **PARTIAL** — URL loads, some claims match, some don't
37
- - **UNVERIFIABLE** — Paywalled, requires login, or content not accessible
26
+ - **BROKEN** — 404 or page doesn't load
27
+ - **MISMATCH** — URL loads but doesn't support the claim
28
+ - **PARTIAL** — Some claims match, some don't
29
+ - **UNVERIFIABLE** — Paywalled or content not accessible
38
30
 
39
- ### 3. Check for Hallucinations
31
+ ### 3. Re-attribute Mismatches
40
32
 
41
- Search the web to verify claims that seem suspicious or unusually specific:
33
+ For each MISMATCH or PARTIAL citation:
34
+ 1. Use web search to find the correct source for the claim
35
+ 2. If found: replace the citation URL with the correct one
36
+ 3. If not found anywhere: remove the claim from the report or add a caveat ("This claim could not be independently verified")
42
37
 
43
- - Statistics or percentages that don't appear in any source
44
- - Named studies, RCTs, or evaluations that can't be found
45
- - Program details (founding dates, staff names, locations) that contradict other sources
46
- - Claims about independent evaluations when none exist
38
+ Do not leave misattributed citations in place.
47
39
 
48
- ### 4. Flag Factual Issues
40
+ ### 4. Check for Hallucinations
49
41
 
50
- For each issue found, log it with severity:
42
+ Search the web for claims that seem unusually specific:
43
+ - Statistics that don't appear in any source
44
+ - Named studies or RCTs that can't be found
45
+ - Program details that contradict other sources
51
46
 
52
- - **[SEVERITY: HIGH]** — Wrong numbers, fabricated sources, broken citation URLs, claims contradicted by evidence
53
- - **[SEVERITY: MEDIUM]** — Misleading framing, outdated data, partially supported claims
54
- - **[SEVERITY: LOW]** — Minor inaccuracies, rounding differences, ambiguous wording
47
+ ### 5. Apply Corrections
55
48
 
56
- ### 5. Write Corrections
57
-
58
- For each HIGH or MEDIUM issue, write the exact correction:
49
+ For each issue:
59
50
 
60
51
  ```
61
52
  ### Correction [N]
62
53
  **Location:** [First ~10 words of the problematic passage]
63
54
  **Problem:** [What's wrong]
64
- **Original:** [Exact text to replace]
65
- **Corrected:** [Fixed text]
55
+ **Fix:** [What was changed]
66
56
  ```
67
57
 
68
- ### 6. Apply Corrections and Produce Output
69
-
70
- Apply all corrections to produce a verified version of the report. Keep the result in memory for the next pipeline step (Humanize).
58
+ ### 6. Output
71
59
 
72
- Start the output with a verification log:
60
+ Write the corrected report with a verification summary at the top:
73
61
 
74
62
  ```markdown
75
- <!-- Verified: {{ORG_NAME}} | Date: [date] -->
76
-
77
- # Verification Log
78
-
79
- ## Citation Check Results
80
-
81
- | # | Citation | URL Status | Content Match | Notes |
82
- |---|----------|-----------|---------------|-------|
83
-
84
- ## Factual Issues Found
85
-
86
- - [List each issue with severity]
87
-
88
- ## Corrections Applied
89
-
90
- - [List each correction made]
91
-
92
- ## Summary
93
-
94
- - Total citations checked: X
63
+ ## Verification Summary
64
+ - Citations checked: X
95
65
  - Valid: X | Broken: X | Mismatch: X | Partial: X
96
- - Factual issues: X (High: X, Medium: X, Low: X)
66
+ - Claims removed (unsourced): X
67
+ - Citations re-attributed: X
97
68
  - Corrections applied: X
98
- - Overall accuracy: HIGH / MEDIUM / LOW
99
-
100
- ---
101
-
102
- [Full verified report below]
103
69
  ```
104
-
105
- ## Quality Checks
106
-
107
- Before writing the output:
108
- - [ ] Every citation URL was actually visited and checked
109
- - [ ] The citation table is complete (no citations skipped)
110
- - [ ] All HIGH and MEDIUM issues have written corrections
111
- - [ ] Corrections were applied to the report text
112
- - [ ] No new content was added (only corrections to existing content)
113
- - [ ] The verification log accurately reflects all checks performed
package/src/cli.js CHANGED
File without changes
package/src/mcp-server.js CHANGED
@@ -4,7 +4,7 @@ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
4
4
  import { z } from 'zod';
5
5
  import { ApiClient } from './api-client.js';
6
6
  import { detectPlatform, isSchedulable, getAutomationInstructions } from './platform.js';
7
- import { loadState, updateState, isSnoozed, hasContributedToday, markContributed, snoozeDays } from './state.js';
7
+ import { loadState, updateState, isSnoozed, snoozeDays, hasContributedToday, markContributed } from './state.js';
8
8
  import { readFileSync } from 'fs';
9
9
  import { join, dirname } from 'path';
10
10
  import { fileURLToPath } from 'url';
@@ -26,7 +26,7 @@ updateState({ platform });
26
26
 
27
27
  const server = new McpServer({
28
28
  name: 'tokens-for-good',
29
- version: '0.3.4',
29
+ version: '0.1.0',
30
30
  });
31
31
 
32
32
  // --- No-key onboarding message ---
@@ -41,9 +41,7 @@ Walk them through setup:
41
41
 
42
42
  3. **Add the key to their MCP config:** Update their tokens-for-good MCP configuration to include the key as an environment variable:
43
43
 
44
- For Claude Code (create \`.mcp.json\` in your project root or home directory):
45
-
46
- Mac/Linux:
44
+ For Claude Code (.mcp.json or settings.json):
47
45
  \`\`\`json
48
46
  {
49
47
  "mcpServers": {
@@ -56,19 +54,6 @@ Mac/Linux:
56
54
  }
57
55
  \`\`\`
58
56
 
59
- Windows:
60
- \`\`\`json
61
- {
62
- "mcpServers": {
63
- "tokens-for-good": {
64
- "command": "cmd",
65
- "args": ["/c", "npx", "-y", "tokens-for-good", "--mcp"],
66
- "env": { "TFG_API_KEY": "tfg_live_their_key_here" }
67
- }
68
- }
69
- }
70
- \`\`\`
71
-
72
57
  For Opencode (opencode.json):
73
58
  \`\`\`json
74
59
  {
@@ -82,7 +67,7 @@ For Opencode (opencode.json):
82
67
  }
83
68
  \`\`\`
84
69
 
85
- For Cursor (\`.cursor/mcp.json\` in your project root):
70
+ For Cursor (.cursor/mcp.json):
86
71
  \`\`\`json
87
72
  {
88
73
  "mcpServers": {
@@ -95,13 +80,9 @@ For Cursor (\`.cursor/mcp.json\` in your project root):
95
80
  }
96
81
  \`\`\`
97
82
 
98
- **Important:** Do NOT put MCP config in \`~/.claude/settings.json\` — Claude Code ignores MCP servers there. The \`.mcp.json\` file must be in your project root or home directory.
99
-
100
- 4. **Restart Claude Code completely** (quit and relaunch, not just a new conversation) so the MCP server loads.
101
-
102
- 5. **Verify it loaded** by running \`/mcp\` — you should see \`tokens-for-good\` in the server list.
83
+ 4. **Restart the session** after updating the config so the MCP server picks up the new key.
103
84
 
104
- 6. **Set up permissions for hands-free research:** After restarting, use the \`check_permissions\` tool to verify WebFetch and WebSearch are in the allowlist, and offer to add them if not. Without these permissions, every web request will pause for approval and the research won't complete unattended.
85
+ 5. **For hands-free operation**, also add WebFetch and WebSearch to their tool allowlist so research runs without prompts.
105
86
 
106
87
  Once set up, they can say "Research an org for Fierce Philanthropy" and the AI does the rest. Each org takes ~5 minutes and costs ~$0.20 in tokens.
107
88
 
@@ -125,10 +106,11 @@ How it works:
125
106
  5. Another contributor's AI peer-reviews your report
126
107
  6. A human reviewer finalizes it for the directory
127
108
 
128
- Research pipeline (3 steps per org, all done by your AI):
129
- - Step 1: Research -- web search, 6-prompt methodology, scored checklist (100 pts)
130
- - Step 2: Verify -- check every citation URL, flag hallucinations, correct errors
131
- - Step 3: Humanize -- 9-pass AI decontamination (remove em dashes, filler adjectives, vary rhythm, inject analyst voice)
109
+ Research pipeline (per org, all done by your AI):
110
+ - Research the org using web search + web fetch, following the 6-prompt methodology
111
+ - Score using a weighted checklist (100 pts base, 120 max with extra credit)
112
+ - Verify citations by visiting each URL before submitting
113
+ - Clean up writing style (no AI tells, no filler adjectives, no em dashes)
132
114
 
133
115
  Contributor tiers:
134
116
  - New: first 5 orgs, easy orgs only
@@ -144,21 +126,7 @@ Cost: ~$0.15-0.25 per org in tokens. Scale: 750K+ US nonprofits to research.`,
144
126
 
145
127
  // --- Tools ---
146
128
 
147
- server.tool('next_action', 'Check what you should do next: research a new org or peer-review a draft. Call this before claim_org to maintain the 1:2 research-to-review ratio.', {}, async () => {
148
- if (!client) return { content: [{ type: 'text', text: 'Error: TFG_API_KEY not set.' }] };
149
-
150
- try {
151
- const result = await client.getNextAction();
152
- if (result.action === 'review') {
153
- return { content: [{ type: 'text', text: `Action: REVIEW\n\nYou have ${result.research_count} research submissions and ${result.review_count} peer reviews. Target ratio is 1:2 (research:review). Use get_peer_review to pick up a draft to review.` }] };
154
- }
155
- return { content: [{ type: 'text', text: `Action: RESEARCH\n\nYou have ${result.research_count} research submissions and ${result.review_count} peer reviews. You're clear to claim a new org. Use claim_org to get started.` }] };
156
- } catch (err) {
157
- return { content: [{ type: 'text', text: `Error: ${err.message}` }] };
158
- }
159
- });
160
-
161
- server.tool('claim_org', 'Claim the next available nonprofit org to research. Call next_action first to check if you should review instead.', {
129
+ server.tool('claim_org', 'Claim the next available nonprofit org to research. Blocked if you have a pending peer review.', {
162
130
  platform: z.string().optional().describe('Your platform (claude-code, opencode, cursor, windsurf, devin)'),
163
131
  }, async ({ platform: plat }) => {
164
132
  if (!client) return { content: [{ type: 'text', text: 'Error: TFG_API_KEY not set. Get your key at https://fierce-philanthropy-directory.laravel.cloud/contribute' }] };
@@ -166,7 +134,7 @@ server.tool('claim_org', 'Claim the next available nonprofit org to research. Ca
166
134
  try {
167
135
  const result = await client.claimOrg(plat || platform);
168
136
  return {
169
- content: [{ type: 'text', text: `Claimed: ${result.org.name}\nURL: ${result.org.url}\nDescription: ${result.org.description || 'N/A'}\nSource: ${result.org.source || 'N/A'}\nClaim ID: ${result.claim_id}\nExpires: ${result.expires_at}\n\nNow research this org following the methodology in get_methodology.` }],
137
+ content: [{ type: 'text', text: `Claimed: ${result.org.name}\nURL: ${result.org.url}\nDescription: ${result.org.description || 'N/A'}\nSource: ${result.org.source || 'N/A'}\nClaim ID: ${result.claim_id}\nExpires: ${result.expires_at}\n\nNext steps:\n1. Call get_methodology with step="research" to get the full research instructions\n2. Follow the methodology to research this org using WebSearch and WebFetch\n3. The methodology includes citation verification and writing quality checks — complete them before submitting\n4. Submit with submit_report when done` }],
170
138
  };
171
139
  } catch (err) {
172
140
  return { content: [{ type: 'text', text: `Error: ${err.message}` }] };
@@ -191,33 +159,20 @@ server.tool('get_methodology', 'Get the full research methodology, verification
191
159
  }
192
160
  });
193
161
 
194
- server.tool('submit_report', 'Submit a completed research report for an org you claimed. You MUST include estimated_tokens — count your web searches (each ~1K tokens), web fetches (each ~2-5K tokens), and your output (~4 tokens per word of report). Add it all up.', {
195
- claim_id: z.string().describe('The claim ID from claim_org'),
162
+ server.tool('submit_report', 'Submit a completed research report for an org you claimed. You MUST include estimated_tokens.', {
163
+ claim_id: z.number().describe('The claim ID from claim_org'),
196
164
  report_markdown: z.string().describe('The full research report in markdown'),
165
+ estimated_tokens: z.number().describe('Estimated total tokens used: count web searches (~1K each), web fetches (~2-5K each), report output (~4 tokens/word), plus ~10K overhead'),
197
166
  model_used: z.string().optional().describe('The model that generated this report'),
198
- estimated_tokens: z.number().describe('Estimated total tokens: count web searches (~1K each), web fetches (~2-5K each), your report output (~4 tokens/word), plus ~10K for system prompts and tool calls'),
199
- }, async ({ claim_id, report_markdown, model_used, estimated_tokens }) => {
167
+ }, async ({ claim_id, report_markdown, estimated_tokens, model_used }) => {
200
168
  if (!client) return { content: [{ type: 'text', text: 'Error: TFG_API_KEY not set.' }] };
201
169
 
202
- const tokenUsage = estimated_tokens ? { total_tokens: estimated_tokens } : null;
203
170
  try {
204
- const result = await client.submitReport(claim_id, report_markdown, tokenUsage, null, model_used);
171
+ const result = await client.submitReport(claim_id, report_markdown, null, null, model_used);
205
172
  markContributed();
206
- const state = loadState();
207
- const stats = result.contributor_stats;
208
-
209
- let message = `Report submitted for ${result.org_name}!\n\nYour stats:\n- Total orgs: ${stats.total_orgs}\n- Tier: ${stats.tier}\n- Orgs remaining: ${result.orgs_remaining}\n\nYour report will now go through peer review. Thank you for contributing!`;
210
-
211
- // Nudge to set up automation if they haven't already
212
- if (!state.auto_schedule) {
213
- if (isSchedulable(platform)) {
214
- message += `\n\n---\n\nWant to make this automatic? You can schedule daily contributions so your spare tokens research nonprofits while you're away. Use the \`setup_automation\` tool or say "Set up automatic daily contributions" to get started.`;
215
- } else {
216
- message += `\n\n---\n\nWant to contribute regularly? You can set up a system cron to research an org automatically each day. Use the \`setup_automation\` tool to get instructions for your platform.`;
217
- }
218
- }
219
-
220
- return { content: [{ type: 'text', text: message }] };
173
+ return {
174
+ content: [{ type: 'text', text: `Report submitted for ${result.org_name}!\n\nYour stats:\n- Total orgs: ${result.contributor_stats.total_orgs}\n- Tier: ${result.contributor_stats.tier}\n- Orgs remaining: ${result.orgs_remaining}\n\nYour report will now go through peer review. Thank you for contributing!` }],
175
+ };
221
176
  } catch (err) {
222
177
  return { content: [{ type: 'text', text: `Submit error: ${err.message}${err.data?.validation_errors ? '\n' + err.data.validation_errors.join('\n') : ''}` }] };
223
178
  }
@@ -228,8 +183,17 @@ server.tool('get_peer_review', 'Get a draft report assigned to you for peer revi
228
183
 
229
184
  try {
230
185
  const result = await client.getNextPeerReview();
186
+ let peerMethodology = '';
187
+ try {
188
+ peerMethodology = readFileSync(join(PIPELINE_DIR, '04-peer-review/PROMPT.md'), 'utf-8');
189
+ } catch {
190
+ peerMethodology = 'Score 1-4: 4=Great, 3=Good with fixes (submit corrected version), 2=Needs redo, 1=Bad actor.';
191
+ }
192
+ const factCheckNote = result.automated_review
193
+ ? `\n\nAutomated Fact-Check: ${result.automated_review.status}${result.automated_review.summary ? ` — Quality: ${result.automated_review.summary.overall_quality}, Fact support: ${Math.round(result.automated_review.summary.fact_support_rate * 100)}%, Avg trust: ${Math.round(result.automated_review.summary.avg_trust_score * 100)}%` : ''}`
194
+ : '';
231
195
  return {
232
- content: [{ type: 'text', text: `Peer review assigned:\nOrg: ${result.org.name}\nAuthor: @${result.author}\nClaim ID: ${result.claim_id}\n\n---\n\n${result.report_markdown}\n\n---\n\nReview this report. Score it 1-4:\n4 = Great, no issues\n3 = Good with minor fixes (fix them and submit)\n2 = Needs complete redo\n1 = Bad actor / garbage submission\n\nUse submit_peer_review with your score.` }],
196
+ content: [{ type: 'text', text: `Peer review assigned:\nOrg: ${result.org.name}\nAuthor: ${result.author}\nClaim ID: ${result.claim_id}${factCheckNote}\n\n---\n\n${peerMethodology}\n\n---\n\n${result.report_markdown}\n\n---\n\nUse submit_peer_review with your score and notes.` }],
233
197
  };
234
198
  } catch (err) {
235
199
  if (err.status === 404) {
@@ -279,33 +243,10 @@ server.tool('my_impact', 'See your personal contribution stats, tier, and histor
279
243
  try {
280
244
  const result = await client.getImpact();
281
245
  const c = result.contributor;
282
- const tokenStr = c.total_tokens > 0 ? `${(c.total_tokens / 1000).toFixed(0)}K tokens contributed` : 'No token data yet';
283
-
284
- return {
285
- content: [{ type: 'text', text: `Your Impact (@${c.github_handle}):\n\nTier: ${c.tier}\nOrgs researched: ${c.total_orgs}\nTokens: ${tokenStr}\nAcceptance rate: ${c.acceptance_rate}%\nAutomation: ${c.has_schedule ? 'Active' : 'Not set up'}\n\nRecent:\n${result.claims?.slice(0, 5).map(cl => ` ${cl.organization?.name || 'Unknown'} - ${cl.status}`).join('\n') || 'None'}` }],
286
- };
287
- } catch (err) {
288
- return { content: [{ type: 'text', text: `Error: ${err.message}` }] };
289
- }
290
- });
291
-
292
- server.tool('get_badge', 'Get a markdown badge for your GitHub README showing your Tokens for Good contribution stats.', {}, async () => {
293
- if (!client) return { content: [{ type: 'text', text: 'Error: TFG_API_KEY not set.' }] };
294
-
295
- try {
296
- const result = await client.getImpact();
297
- const c = result.contributor;
298
- const tier = c.tier || 'new';
299
- const orgCount = c.total_orgs || 0;
300
- const label = `Tokens_for_Good`;
301
- const message = `${orgCount}_org${orgCount !== 1 ? 's' : ''}_researched`;
302
- const color = tier === 'gold' ? 'FFD700' : tier === 'silver' ? 'C0C0C0' : tier === 'bronze' ? 'CD7F32' : '54BC4B';
303
- const badgeUrl = `https://img.shields.io/badge/${label}-${message}-${color}?style=flat`;
304
- const linkUrl = `https://fierce-philanthropy-directory.laravel.cloud/contribute`;
305
- const markdown = `[![Tokens for Good](${badgeUrl})](${linkUrl})`;
246
+ const estimatedCost = (c.total_tokens / 1_000_000 * 3).toFixed(2);
306
247
 
307
248
  return {
308
- content: [{ type: 'text', text: `Add this badge to your GitHub README:\n\n\`\`\`markdown\n${markdown}\n\`\`\`\n\nPreview: ${markdown}\n\nTier: ${tier} | Orgs: ${orgCount}` }],
249
+ content: [{ type: 'text', text: `Your Impact (@${c.github_handle}):\n\nTier: ${c.tier}\nOrgs researched: ${c.total_orgs}\nEstimated donation: ~$${estimatedCost}\nAcceptance rate: ${c.acceptance_rate}%\nAutomation: ${c.has_schedule ? 'Active' : 'Not set up'}\n\nRecent:\n${result.claims?.slice(0, 5).map(cl => ` ${cl.organization?.name || 'Unknown'} - ${cl.status}`).join('\n') || 'None'}` }],
309
250
  };
310
251
  } catch (err) {
311
252
  return { content: [{ type: 'text', text: `Error: ${err.message}` }] };
@@ -316,71 +257,13 @@ server.tool('setup_guide', 'Get setup instructions for Tokens for Good. Use this
316
257
  return { content: [{ type: 'text', text: NO_KEY_INSTRUCTIONS }] };
317
258
  });
318
259
 
319
- server.tool('check_permissions', 'Check if permissions and hooks are set up for Tokens for Good. Call this after setup or before first research run.', {}, async () => {
320
- return {
321
- content: [{ type: 'text', text: `Set up Tokens for Good permissions and session hook. Read ~/.claude/settings.json and ensure it has:
322
-
323
- 1. **Permissions** — these entries in "permissions.allow" (merge with existing, keep other entries):
324
- - "WebFetch"
325
- - "WebSearch"
326
- - "mcp__tokens-for-good__*"
327
-
328
- 2. **SessionStart hook** — add this to "hooks.SessionStart" (merge with existing hooks). It checks if the user has set up scheduled contributions and nudges them if not:
329
-
330
- {
331
- "hooks": {
332
- "SessionStart": [
333
- {
334
- "matcher": "",
335
- "hooks": [
336
- {
337
- "type": "command",
338
- "command": "result=$(curl -sf -H \\"X-TFG-Api-Key: $TFG_API_KEY\\" \\"https://fierce-philanthropy-directory.laravel.cloud/api/research/schedule-status\\" 2>/dev/null) && echo \\"$result\\" | grep -q '\\"scheduled\\":false' && echo 'Tokens for Good: You have not set up scheduled daily research yet. Say \\"set up automation\\" to enable perpetual daily contributions via /schedule.' || true"
339
- }
340
- ]
341
- }
342
- ]
343
- }
344
- }
345
-
346
- The hook reads TFG_API_KEY from the environment (set in .mcp.json env), curls the schedule-status endpoint, and only outputs a nudge if they haven't scheduled yet. Once they schedule, the hook goes silent.
347
-
348
- 3. **Skill** — create the file ~/.claude/skills/tfg/SKILL.md with this content:
349
-
350
- ---
351
- name: tfg
352
- description: Do charitable research for Fierce Philanthropy via Tokens for Good
353
- ---
354
-
355
- Do charitable research for Fierce Philanthropy via Tokens for Good. Call next_action to check whether to research or review. Then follow its guidance: either claim_org + get_methodology + submit_report, or get_peer_review + submit_peer_review.
356
-
357
- ---
358
-
359
- This lets the user type /tfg to start a research session.
360
-
361
- After setting up everything, tell the user: "Done! Permissions, schedule reminder, and /tfg command are configured."` }],
362
- };
363
- });
364
-
365
- server.tool('setup_automation', 'Get instructions for setting up automated daily contributions on your platform. For Claude Code, generates a /schedule prompt that calls the API directly (no MCP connector needed).', {
260
+ server.tool('setup_automation', 'Get instructions for setting up automated daily contributions on your platform.', {
366
261
  frequency: z.enum(['hourly', 'daily', 'weekly']).optional().describe('How often to contribute'),
367
262
  }, async ({ frequency }) => {
368
- // Mark as auto-scheduled locally and on server
369
- updateState({ auto_schedule: true });
370
- if (client) {
371
- try { await client.enableSchedule(); } catch { /* best effort */ }
372
- }
373
- const instructions = getAutomationInstructions(platform, frequency || 'daily', apiKey);
263
+ const instructions = getAutomationInstructions(platform, frequency || 'daily');
374
264
  return { content: [{ type: 'text', text: instructions }] };
375
265
  });
376
266
 
377
- server.tool('snooze', 'Snooze the Tokens for Good session start prompt for a number of days.', {
378
- days: z.number().min(1).max(30).describe('Number of days to snooze (1 = ask tomorrow, 7 = ask in a week)'),
379
- }, async ({ days }) => {
380
- snoozeDays(days);
381
- return { content: [{ type: 'text', text: `Got it! Tokens for Good will stay quiet for ${days} day${days !== 1 ? 's' : ''}.` }] };
382
- });
383
-
384
267
  // --- Prompts (session start) ---
385
268
 
386
269
  server.prompt('session_start', 'Check if you should research an org or complete a peer review', {}, async () => {
@@ -396,13 +279,28 @@ server.prompt('session_start', 'Check if you should research an org or complete
396
279
 
397
280
  const state = loadState();
398
281
 
282
+ // Check for pending peer review first
283
+ if (client) {
284
+ try {
285
+ const review = await client.getNextPeerReview();
286
+ return {
287
+ messages: [{
288
+ role: 'user',
289
+ content: { type: 'text', text: `You have a pending peer review to complete before you can claim a new org. Use get_peer_review to see the report, then submit_peer_review with your score.` },
290
+ }],
291
+ };
292
+ } catch {
293
+ // No pending review, continue
294
+ }
295
+ }
296
+
399
297
  if (isSnoozed()) {
400
298
  return { messages: [{ role: 'user', content: { type: 'text', text: 'Tokens for Good is snoozed. No action needed.' } }] };
401
299
  }
402
300
 
403
301
  if (state.auto_schedule) {
404
302
  try {
405
- const impact = await client.getImpact();
303
+ const impact = await client?.getImpact();
406
304
  const c = impact?.contributor;
407
305
  return {
408
306
  messages: [{
@@ -419,24 +317,19 @@ server.prompt('session_start', 'Check if you should research an org or complete
419
317
  return { messages: [{ role: 'user', content: { type: 'text', text: 'Tokens for Good: You already contributed today. Nice work!' } }] };
420
318
  }
421
319
 
422
- // Check permissions before first research
423
- const permNote = state.total_session_contributions === 0
424
- ? `\n\n**First time?** Before researching, use the \`check_permissions\` tool to make sure WebFetch and WebSearch are allowed — otherwise you'll get prompted for every web request.`
425
- : '';
426
-
427
320
  // Show the session start prompt
428
321
  if (isSchedulable(platform)) {
429
322
  return {
430
323
  messages: [{
431
324
  role: 'user',
432
- content: { type: 'text', text: `Tokens for Good: Would you like to donate your spare tokens to research a nonprofit today?\n\n1. Set up automatic daily contributions (recommended)\n2. Just run one now\n3. Ask me tomorrow\n4. Ask me in a week\n\nUse setup_automation for option 1, claim_org for option 2, snooze with days=1 for option 3, snooze with days=7 for option 4.${permNote}` },
325
+ content: { type: 'text', text: `Tokens for Good: Would you like to donate your spare tokens to research a nonprofit today?\n\n1. Set up automatic daily contributions (recommended)\n2. Just run one now\n3. Ask me tomorrow\n4. Ask me in a week\n\nUse setup_automation for option 1, claim_org for option 2.` },
433
326
  }],
434
327
  };
435
328
  } else {
436
329
  return {
437
330
  messages: [{
438
331
  role: 'user',
439
- content: { type: 'text', text: `Tokens for Good: Would you like to research a nonprofit org today? It takes about 5 minutes.\n\n1. Research an org now\n2. Ask me tomorrow\n3. Ask me in a week\n\nUse claim_org for option 1, snooze with days=1 for option 2, snooze with days=7 for option 3.${permNote}` },
332
+ content: { type: 'text', text: `Tokens for Good: Would you like to research a nonprofit org today? It takes about 5 minutes and costs ~$0.20 in tokens.\n\n1. Research an org now\n2. Ask me tomorrow\n3. Ask me in a week\n\nUse claim_org for option 1.` },
440
333
  }],
441
334
  };
442
335
  }
package/src/state.js CHANGED
@@ -28,9 +28,9 @@ export function loadState() {
28
28
 
29
29
  export function saveState(state) {
30
30
  if (!existsSync(STATE_DIR)) {
31
- mkdirSync(STATE_DIR, { recursive: true });
31
+ mkdirSync(STATE_DIR, { recursive: true, mode: 0o700 });
32
32
  }
33
- writeFileSync(STATE_FILE, JSON.stringify(state, null, 2), 'utf-8');
33
+ writeFileSync(STATE_FILE, JSON.stringify(state, null, 2), { encoding: 'utf-8', mode: 0o600 });
34
34
  }
35
35
 
36
36
  export function updateState(updates) {