flonat-research 0.1.0
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/.claude/agents/domain-reviewer.md +336 -0
- package/.claude/agents/fixer.md +226 -0
- package/.claude/agents/paper-critic.md +370 -0
- package/.claude/agents/peer-reviewer.md +289 -0
- package/.claude/agents/proposal-reviewer.md +215 -0
- package/.claude/agents/referee2-reviewer.md +367 -0
- package/.claude/agents/references/journal-referee-profiles.md +354 -0
- package/.claude/agents/references/paper-critic/council-personas.md +77 -0
- package/.claude/agents/references/paper-critic/council-prompts.md +198 -0
- package/.claude/agents/references/peer-reviewer/report-template.md +199 -0
- package/.claude/agents/references/peer-reviewer/sa-prompts.md +260 -0
- package/.claude/agents/references/peer-reviewer/security-scan.md +188 -0
- package/.claude/agents/references/proposal-reviewer/report-template.md +144 -0
- package/.claude/agents/references/proposal-reviewer/sa-prompts.md +149 -0
- package/.claude/agents/references/referee-config.md +114 -0
- package/.claude/agents/references/referee2-reviewer/audit-checklists.md +287 -0
- package/.claude/agents/references/referee2-reviewer/report-template.md +334 -0
- package/.claude/rules/design-before-results.md +52 -0
- package/.claude/rules/ignore-agents-md.md +17 -0
- package/.claude/rules/ignore-gemini-md.md +17 -0
- package/.claude/rules/lean-claude-md.md +45 -0
- package/.claude/rules/learn-tags.md +99 -0
- package/.claude/rules/overleaf-separation.md +67 -0
- package/.claude/rules/plan-first.md +175 -0
- package/.claude/rules/read-docs-first.md +50 -0
- package/.claude/rules/scope-discipline.md +28 -0
- package/.claude/settings.json +125 -0
- package/.context/current-focus.md +33 -0
- package/.context/preferences/priorities.md +36 -0
- package/.context/preferences/task-naming.md +28 -0
- package/.context/profile.md +29 -0
- package/.context/projects/_index.md +41 -0
- package/.context/projects/papers/nudge-exp.md +22 -0
- package/.context/projects/papers/uncertainty.md +31 -0
- package/.context/resources/claude-scientific-writer-review.md +48 -0
- package/.context/resources/cunningham-multi-analyst-agents.md +104 -0
- package/.context/resources/cunningham-multilang-code-audit.md +62 -0
- package/.context/resources/google-ai-co-scientist-review.md +72 -0
- package/.context/resources/karpathy-llm-council-review.md +58 -0
- package/.context/resources/multi-coder-reliability-protocol.md +175 -0
- package/.context/resources/pedro-santanna-takeaways.md +96 -0
- package/.context/resources/venue-rankings/abs_ajg_2024.csv +1823 -0
- package/.context/resources/venue-rankings/abs_ajg_2024_econ.csv +356 -0
- package/.context/resources/venue-rankings/cabs_4_4star_theory.csv +40 -0
- package/.context/resources/venue-rankings/core_2026.csv +801 -0
- package/.context/resources/venue-rankings.md +147 -0
- package/.context/workflows/README.md +69 -0
- package/.context/workflows/daily-review.md +91 -0
- package/.context/workflows/meeting-actions.md +108 -0
- package/.context/workflows/replication-protocol.md +155 -0
- package/.context/workflows/weekly-review.md +113 -0
- package/.mcp-server-biblio/formatters.py +158 -0
- package/.mcp-server-biblio/pyproject.toml +11 -0
- package/.mcp-server-biblio/server.py +678 -0
- package/.mcp-server-biblio/sources/__init__.py +14 -0
- package/.mcp-server-biblio/sources/base.py +73 -0
- package/.mcp-server-biblio/sources/formatters.py +83 -0
- package/.mcp-server-biblio/sources/models.py +22 -0
- package/.mcp-server-biblio/sources/multi_source.py +243 -0
- package/.mcp-server-biblio/sources/openalex_source.py +183 -0
- package/.mcp-server-biblio/sources/scopus_source.py +309 -0
- package/.mcp-server-biblio/sources/wos_source.py +508 -0
- package/.mcp-server-biblio/uv.lock +896 -0
- package/.scripts/README.md +161 -0
- package/.scripts/ai_pattern_density.py +446 -0
- package/.scripts/conf +445 -0
- package/.scripts/config.py +122 -0
- package/.scripts/count_inventory.py +275 -0
- package/.scripts/daily_digest.py +288 -0
- package/.scripts/done +177 -0
- package/.scripts/extract_meeting_actions.py +223 -0
- package/.scripts/focus +176 -0
- package/.scripts/generate-codex-agents-md.py +217 -0
- package/.scripts/inbox +194 -0
- package/.scripts/notion_helpers.py +325 -0
- package/.scripts/openalex/query_helpers.py +306 -0
- package/.scripts/papers +227 -0
- package/.scripts/query +223 -0
- package/.scripts/session-history.py +201 -0
- package/.scripts/skill-health.py +516 -0
- package/.scripts/skill-log-miner.py +273 -0
- package/.scripts/sync-to-codex.sh +252 -0
- package/.scripts/task +213 -0
- package/.scripts/tasks +190 -0
- package/.scripts/week +206 -0
- package/CLAUDE.md +197 -0
- package/LICENSE +21 -0
- package/MEMORY.md +38 -0
- package/README.md +269 -0
- package/docs/agents.md +44 -0
- package/docs/bibliography-setup.md +55 -0
- package/docs/council-mode.md +36 -0
- package/docs/getting-started.md +245 -0
- package/docs/hooks.md +38 -0
- package/docs/mcp-servers.md +82 -0
- package/docs/notion-setup.md +109 -0
- package/docs/rules.md +33 -0
- package/docs/scripts.md +303 -0
- package/docs/setup-overview/setup-overview.pdf +0 -0
- package/docs/skills.md +70 -0
- package/docs/system.md +159 -0
- package/hooks/block-destructive-git.sh +66 -0
- package/hooks/context-monitor.py +114 -0
- package/hooks/postcompact-restore.py +157 -0
- package/hooks/precompact-autosave.py +181 -0
- package/hooks/promise-checker.sh +124 -0
- package/hooks/protect-source-files.sh +81 -0
- package/hooks/resume-context-loader.sh +53 -0
- package/hooks/startup-context-loader.sh +102 -0
- package/package.json +51 -0
- package/packages/cli-council/.github/workflows/claude-code-review.yml +44 -0
- package/packages/cli-council/.github/workflows/claude.yml +50 -0
- package/packages/cli-council/README.md +100 -0
- package/packages/cli-council/pyproject.toml +43 -0
- package/packages/cli-council/src/cli_council/__init__.py +19 -0
- package/packages/cli-council/src/cli_council/__main__.py +185 -0
- package/packages/cli-council/src/cli_council/backends/__init__.py +8 -0
- package/packages/cli-council/src/cli_council/backends/base.py +81 -0
- package/packages/cli-council/src/cli_council/backends/claude.py +25 -0
- package/packages/cli-council/src/cli_council/backends/codex.py +27 -0
- package/packages/cli-council/src/cli_council/backends/gemini.py +26 -0
- package/packages/cli-council/src/cli_council/checkpoint.py +212 -0
- package/packages/cli-council/src/cli_council/config.py +51 -0
- package/packages/cli-council/src/cli_council/council.py +391 -0
- package/packages/cli-council/src/cli_council/models.py +46 -0
- package/packages/llm-council/.github/workflows/claude-code-review.yml +44 -0
- package/packages/llm-council/.github/workflows/claude.yml +50 -0
- package/packages/llm-council/README.md +453 -0
- package/packages/llm-council/pyproject.toml +42 -0
- package/packages/llm-council/src/llm_council/__init__.py +23 -0
- package/packages/llm-council/src/llm_council/__main__.py +259 -0
- package/packages/llm-council/src/llm_council/checkpoint.py +193 -0
- package/packages/llm-council/src/llm_council/client.py +253 -0
- package/packages/llm-council/src/llm_council/config.py +232 -0
- package/packages/llm-council/src/llm_council/council.py +482 -0
- package/packages/llm-council/src/llm_council/models.py +46 -0
- package/packages/mcp-bibliography/MEMORY.md +31 -0
- package/packages/mcp-bibliography/_app.py +226 -0
- package/packages/mcp-bibliography/formatters.py +158 -0
- package/packages/mcp-bibliography/log/2026-03-13-2100.md +35 -0
- package/packages/mcp-bibliography/pyproject.toml +15 -0
- package/packages/mcp-bibliography/run.sh +20 -0
- package/packages/mcp-bibliography/scholarly_formatters.py +83 -0
- package/packages/mcp-bibliography/server.py +1857 -0
- package/packages/mcp-bibliography/tools/__init__.py +28 -0
- package/packages/mcp-bibliography/tools/_registry.py +19 -0
- package/packages/mcp-bibliography/tools/altmetric.py +107 -0
- package/packages/mcp-bibliography/tools/core.py +92 -0
- package/packages/mcp-bibliography/tools/dblp.py +52 -0
- package/packages/mcp-bibliography/tools/openalex.py +296 -0
- package/packages/mcp-bibliography/tools/opencitations.py +102 -0
- package/packages/mcp-bibliography/tools/openreview.py +179 -0
- package/packages/mcp-bibliography/tools/orcid.py +131 -0
- package/packages/mcp-bibliography/tools/scholarly.py +575 -0
- package/packages/mcp-bibliography/tools/unpaywall.py +63 -0
- package/packages/mcp-bibliography/tools/zenodo.py +123 -0
- package/packages/mcp-bibliography/uv.lock +711 -0
- package/scripts/setup.sh +143 -0
- package/skills/beamer-deck/SKILL.md +199 -0
- package/skills/beamer-deck/references/quality-rubric.md +54 -0
- package/skills/beamer-deck/references/review-prompts.md +106 -0
- package/skills/bib-validate/SKILL.md +261 -0
- package/skills/bib-validate/references/council-mode.md +34 -0
- package/skills/bib-validate/references/deep-verify.md +79 -0
- package/skills/bib-validate/references/fix-mode.md +36 -0
- package/skills/bib-validate/references/openalex-verification.md +45 -0
- package/skills/bib-validate/references/preprint-check.md +31 -0
- package/skills/bib-validate/references/ref-manager-crossref.md +41 -0
- package/skills/bib-validate/references/report-template.md +82 -0
- package/skills/code-archaeology/SKILL.md +141 -0
- package/skills/code-review/SKILL.md +265 -0
- package/skills/code-review/references/quality-rubric.md +67 -0
- package/skills/consolidate-memory/SKILL.md +208 -0
- package/skills/context-status/SKILL.md +126 -0
- package/skills/creation-guard/SKILL.md +230 -0
- package/skills/devils-advocate/SKILL.md +130 -0
- package/skills/devils-advocate/references/competing-hypotheses.md +83 -0
- package/skills/init-project/SKILL.md +115 -0
- package/skills/init-project-course/references/memory-and-settings.md +92 -0
- package/skills/init-project-course/references/organise-templates.md +94 -0
- package/skills/init-project-course/skill.md +147 -0
- package/skills/init-project-light/skill.md +139 -0
- package/skills/init-project-research/SKILL.md +368 -0
- package/skills/init-project-research/references/atlas-pipeline-sync.md +70 -0
- package/skills/init-project-research/references/atlas-schema.md +81 -0
- package/skills/init-project-research/references/confirmation-report.md +39 -0
- package/skills/init-project-research/references/domain-profile-template.md +104 -0
- package/skills/init-project-research/references/interview-round3.md +34 -0
- package/skills/init-project-research/references/literature-discovery.md +43 -0
- package/skills/init-project-research/references/scaffold-details.md +197 -0
- package/skills/init-project-research/templates/field-calibration.md +60 -0
- package/skills/init-project-research/templates/pipeline-manifest.md +63 -0
- package/skills/init-project-research/templates/run-all.sh +116 -0
- package/skills/init-project-research/templates/seed-files.md +337 -0
- package/skills/insights-deck/SKILL.md +151 -0
- package/skills/interview-me/SKILL.md +157 -0
- package/skills/latex/SKILL.md +141 -0
- package/skills/latex/references/latex-configs.md +183 -0
- package/skills/latex-autofix/SKILL.md +230 -0
- package/skills/latex-autofix/references/known-errors.md +183 -0
- package/skills/latex-autofix/references/quality-rubric.md +50 -0
- package/skills/latex-health-check/SKILL.md +161 -0
- package/skills/learn/SKILL.md +220 -0
- package/skills/learn/scripts/validate_skill.py +265 -0
- package/skills/lessons-learned/SKILL.md +201 -0
- package/skills/literature/SKILL.md +335 -0
- package/skills/literature/references/agent-templates.md +393 -0
- package/skills/literature/references/bibliometric-apis.md +44 -0
- package/skills/literature/references/cli-council-search.md +79 -0
- package/skills/literature/references/openalex-api-guide.md +371 -0
- package/skills/literature/references/openalex-common-queries.md +381 -0
- package/skills/literature/references/openalex-workflows.md +248 -0
- package/skills/literature/references/reference-manager-sync.md +36 -0
- package/skills/literature/references/scopus-api-guide.md +208 -0
- package/skills/literature/references/wos-api-guide.md +308 -0
- package/skills/multi-perspective/SKILL.md +311 -0
- package/skills/multi-perspective/references/computational-many-analysts.md +77 -0
- package/skills/pipeline-manifest/SKILL.md +226 -0
- package/skills/pre-submission-report/SKILL.md +153 -0
- package/skills/process-reviews/SKILL.md +244 -0
- package/skills/process-reviews/references/rr-routing.md +101 -0
- package/skills/project-deck/SKILL.md +87 -0
- package/skills/project-safety/SKILL.md +135 -0
- package/skills/proofread/SKILL.md +254 -0
- package/skills/proofread/references/quality-rubric.md +104 -0
- package/skills/python-env/SKILL.md +57 -0
- package/skills/quarto-deck/SKILL.md +226 -0
- package/skills/quarto-deck/references/markdown-format.md +143 -0
- package/skills/quarto-deck/references/quality-rubric.md +54 -0
- package/skills/save-context/SKILL.md +174 -0
- package/skills/session-log/SKILL.md +98 -0
- package/skills/shared/concept-validation-gate.md +161 -0
- package/skills/shared/council-protocol.md +265 -0
- package/skills/shared/distribution-diagnostics.md +164 -0
- package/skills/shared/engagement-stratified-sampling.md +218 -0
- package/skills/shared/escalation-protocol.md +74 -0
- package/skills/shared/external-audit-protocol.md +205 -0
- package/skills/shared/intercoder-reliability.md +256 -0
- package/skills/shared/mcp-degradation.md +81 -0
- package/skills/shared/method-probing-questions.md +163 -0
- package/skills/shared/multi-language-conventions.md +143 -0
- package/skills/shared/paid-api-safety.md +174 -0
- package/skills/shared/palettes.md +90 -0
- package/skills/shared/progressive-disclosure.md +92 -0
- package/skills/shared/project-documentation-content.md +443 -0
- package/skills/shared/project-documentation-format.md +281 -0
- package/skills/shared/project-documentation.md +100 -0
- package/skills/shared/publication-output.md +138 -0
- package/skills/shared/quality-scoring.md +70 -0
- package/skills/shared/reference-resolution.md +77 -0
- package/skills/shared/research-quality-rubric.md +165 -0
- package/skills/shared/rhetoric-principles.md +54 -0
- package/skills/shared/skill-design-patterns.md +272 -0
- package/skills/shared/skill-index.md +240 -0
- package/skills/shared/system-documentation.md +334 -0
- package/skills/shared/tikz-rules.md +402 -0
- package/skills/shared/validation-tiers.md +121 -0
- package/skills/shared/venue-guides/README.md +46 -0
- package/skills/shared/venue-guides/cell_press_style.md +483 -0
- package/skills/shared/venue-guides/conferences_formatting.md +564 -0
- package/skills/shared/venue-guides/cs_conference_style.md +463 -0
- package/skills/shared/venue-guides/examples/cell_summary_example.md +247 -0
- package/skills/shared/venue-guides/examples/medical_structured_abstract.md +313 -0
- package/skills/shared/venue-guides/examples/nature_abstract_examples.md +213 -0
- package/skills/shared/venue-guides/examples/neurips_introduction_example.md +245 -0
- package/skills/shared/venue-guides/journals_formatting.md +486 -0
- package/skills/shared/venue-guides/medical_journal_styles.md +535 -0
- package/skills/shared/venue-guides/ml_conference_style.md +556 -0
- package/skills/shared/venue-guides/nature_science_style.md +405 -0
- package/skills/shared/venue-guides/reviewer_expectations.md +417 -0
- package/skills/shared/venue-guides/venue_writing_styles.md +321 -0
- package/skills/split-pdf/SKILL.md +172 -0
- package/skills/split-pdf/methodology.md +48 -0
- package/skills/sync-notion/SKILL.md +93 -0
- package/skills/system-audit/SKILL.md +157 -0
- package/skills/system-audit/references/sub-agent-prompts.md +294 -0
- package/skills/task-management/SKILL.md +131 -0
- package/skills/update-focus/SKILL.md +204 -0
- package/skills/update-project-doc/SKILL.md +194 -0
- package/skills/validate-bib/SKILL.md +242 -0
- package/skills/validate-bib/references/council-mode.md +34 -0
- package/skills/validate-bib/references/deep-verify.md +71 -0
- package/skills/validate-bib/references/openalex-verification.md +45 -0
- package/skills/validate-bib/references/preprint-check.md +31 -0
- package/skills/validate-bib/references/report-template.md +62 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"""OpenCitations tools (2 tools, always available)."""
|
|
2
|
+
|
|
3
|
+
from mcp.types import Tool, TextContent
|
|
4
|
+
|
|
5
|
+
from _app import _opencitations_client
|
|
6
|
+
from tools._registry import register
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
# ---------- Handlers ----------
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
async def _handle_opencitations_citations(args: dict) -> list[TextContent]:
|
|
13
|
+
doi = args["doi"]
|
|
14
|
+
limit = args.get("limit")
|
|
15
|
+
|
|
16
|
+
citations = await _opencitations_client.get_citations(doi, limit=limit)
|
|
17
|
+
|
|
18
|
+
if not citations:
|
|
19
|
+
return [TextContent(type="text", text=f"No citations found in OpenCitations for: {doi}")]
|
|
20
|
+
|
|
21
|
+
count = await _opencitations_client.get_citation_count(doi)
|
|
22
|
+
|
|
23
|
+
lines = [f"## OpenCitations: Papers citing {doi}\n"]
|
|
24
|
+
lines.append(f"**Total citations:** {count}\n")
|
|
25
|
+
lines.append("| # | Citing DOI | Date |")
|
|
26
|
+
lines.append("|---|-----------|------|")
|
|
27
|
+
|
|
28
|
+
for i, c in enumerate(citations[:50], 1):
|
|
29
|
+
citing_doi = c.citing
|
|
30
|
+
date = c.creation or "—"
|
|
31
|
+
lines.append(f"| {i} | [{citing_doi}](https://doi.org/{citing_doi}) | {date} |")
|
|
32
|
+
|
|
33
|
+
if len(citations) > 50:
|
|
34
|
+
lines.append(f"\n*Showing 50 of {len(citations)} citations*")
|
|
35
|
+
|
|
36
|
+
lines.append(f"\n*Source: OpenCitations COCI (fully open citation index)*")
|
|
37
|
+
return [TextContent(type="text", text="\n".join(lines))]
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
async def _handle_opencitations_references(args: dict) -> list[TextContent]:
|
|
41
|
+
doi = args["doi"]
|
|
42
|
+
limit = args.get("limit")
|
|
43
|
+
|
|
44
|
+
references = await _opencitations_client.get_references(doi, limit=limit)
|
|
45
|
+
|
|
46
|
+
if not references:
|
|
47
|
+
return [TextContent(type="text", text=f"No references found in OpenCitations for: {doi}")]
|
|
48
|
+
|
|
49
|
+
lines = [f"## OpenCitations: References of {doi}\n"]
|
|
50
|
+
lines.append("| # | Referenced DOI | Date |")
|
|
51
|
+
lines.append("|---|--------------|------|")
|
|
52
|
+
|
|
53
|
+
for i, r in enumerate(references, 1):
|
|
54
|
+
cited_doi = r.cited
|
|
55
|
+
date = r.creation or "—"
|
|
56
|
+
lines.append(f"| {i} | [{cited_doi}](https://doi.org/{cited_doi}) | {date} |")
|
|
57
|
+
|
|
58
|
+
lines.append(f"\n*{len(references)} references (Source: OpenCitations COCI)*")
|
|
59
|
+
return [TextContent(type="text", text="\n".join(lines))]
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
# ---------- Registration ----------
|
|
63
|
+
|
|
64
|
+
_TOOLS = [
|
|
65
|
+
(
|
|
66
|
+
Tool(
|
|
67
|
+
name="opencitations_citations",
|
|
68
|
+
description=(
|
|
69
|
+
"Get papers that cite a given DOI using the fully open COCI citation index. "
|
|
70
|
+
"Returns citing DOIs with dates. Complements Semantic Scholar citations with "
|
|
71
|
+
"a fully open, non-proprietary citation graph."
|
|
72
|
+
),
|
|
73
|
+
inputSchema={
|
|
74
|
+
"type": "object",
|
|
75
|
+
"properties": {
|
|
76
|
+
"doi": {"type": "string", "description": "DOI to find citations for (with or without prefix)"},
|
|
77
|
+
"limit": {"type": "integer", "description": "Max results (default: all citations)"},
|
|
78
|
+
},
|
|
79
|
+
"required": ["doi"],
|
|
80
|
+
},
|
|
81
|
+
),
|
|
82
|
+
_handle_opencitations_citations,
|
|
83
|
+
),
|
|
84
|
+
(
|
|
85
|
+
Tool(
|
|
86
|
+
name="opencitations_references",
|
|
87
|
+
description="Get papers referenced by a given DOI (backward citations / bibliography). Returns cited DOIs. Use for tracing intellectual lineage.",
|
|
88
|
+
inputSchema={
|
|
89
|
+
"type": "object",
|
|
90
|
+
"properties": {
|
|
91
|
+
"doi": {"type": "string", "description": "DOI to find references for"},
|
|
92
|
+
"limit": {"type": "integer", "description": "Max results (default: all references)"},
|
|
93
|
+
},
|
|
94
|
+
"required": ["doi"],
|
|
95
|
+
},
|
|
96
|
+
),
|
|
97
|
+
_handle_opencitations_references,
|
|
98
|
+
),
|
|
99
|
+
]
|
|
100
|
+
|
|
101
|
+
for tool, handler in _TOOLS:
|
|
102
|
+
register(tool, handler)
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
"""OpenReview tools (3 tools, always available)."""
|
|
2
|
+
|
|
3
|
+
from mcp.types import Tool, TextContent
|
|
4
|
+
|
|
5
|
+
from _app import _openreview_client, format_papers_table
|
|
6
|
+
from tools._registry import register
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
# ---------- Handlers ----------
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
async def _handle_openreview_venue(args: dict) -> list[TextContent]:
|
|
13
|
+
venue_id = args["venue_id"]
|
|
14
|
+
limit = min(args.get("limit", 25), 1000)
|
|
15
|
+
|
|
16
|
+
papers = await _openreview_client.get_venue_submissions(venue_id, limit=limit)
|
|
17
|
+
|
|
18
|
+
if not papers:
|
|
19
|
+
return [TextContent(type="text", text=f"No submissions found for venue: {venue_id}")]
|
|
20
|
+
|
|
21
|
+
lines = [f"## OpenReview: {venue_id}\n"]
|
|
22
|
+
lines.append("| # | Title | Authors | Keywords | Area | Forum ID |")
|
|
23
|
+
lines.append("|---|-------|---------|----------|------|----------|")
|
|
24
|
+
|
|
25
|
+
for i, p in enumerate(papers, 1):
|
|
26
|
+
title = p.title[:60] + ("..." if len(p.title) > 60 else "")
|
|
27
|
+
authors = ", ".join(p.authors[:3]) + ("..." if len(p.authors) > 3 else "")
|
|
28
|
+
kw = ", ".join(p.keywords[:3]) if p.keywords else "—"
|
|
29
|
+
area = p.primary_area or "—"
|
|
30
|
+
if len(area) > 30:
|
|
31
|
+
area = area[:30] + "..."
|
|
32
|
+
lines.append(f"| {i} | [{title}](https://openreview.net/forum?id={p.forum_id}) | {authors} | {kw} | {area} | `{p.forum_id}` |")
|
|
33
|
+
|
|
34
|
+
lines.append(f"\n*{len(papers)} submissions from OpenReview*")
|
|
35
|
+
return [TextContent(type="text", text="\n".join(lines))]
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
async def _handle_openreview_reviews(args: dict) -> list[TextContent]:
|
|
39
|
+
forum_id = args["forum_id"]
|
|
40
|
+
|
|
41
|
+
paper = await _openreview_client.get_paper_with_reviews(forum_id)
|
|
42
|
+
|
|
43
|
+
if not paper:
|
|
44
|
+
return [TextContent(type="text", text=f"Paper not found: {forum_id}")]
|
|
45
|
+
|
|
46
|
+
lines = [f"## {paper.title}\n"]
|
|
47
|
+
lines.append(f"**Forum:** [openreview.net/forum?id={forum_id}](https://openreview.net/forum?id={forum_id})")
|
|
48
|
+
if paper.authors:
|
|
49
|
+
lines.append(f"**Authors:** {', '.join(paper.authors[:5])}")
|
|
50
|
+
if paper.venue:
|
|
51
|
+
lines.append(f"**Venue:** {paper.venue}")
|
|
52
|
+
if paper.primary_area:
|
|
53
|
+
lines.append(f"**Area:** {paper.primary_area}")
|
|
54
|
+
if paper.keywords:
|
|
55
|
+
lines.append(f"**Keywords:** {', '.join(paper.keywords)}")
|
|
56
|
+
if paper.tldr:
|
|
57
|
+
lines.append(f"**TLDR:** {paper.tldr}")
|
|
58
|
+
if paper.abstract:
|
|
59
|
+
lines.append(f"\n**Abstract:** {paper.abstract[:500]}{'...' if len(paper.abstract or '') > 500 else ''}")
|
|
60
|
+
|
|
61
|
+
if paper.reviews:
|
|
62
|
+
lines.append(f"\n### Reviews ({len(paper.reviews)})\n")
|
|
63
|
+
for i, r in enumerate(paper.reviews, 1):
|
|
64
|
+
lines.append(f"#### Reviewer {i}")
|
|
65
|
+
if r.rating:
|
|
66
|
+
lines.append(f"- **Rating:** {r.rating}")
|
|
67
|
+
if r.soundness:
|
|
68
|
+
lines.append(f"- **Soundness:** {r.soundness}")
|
|
69
|
+
if r.presentation:
|
|
70
|
+
lines.append(f"- **Presentation:** {r.presentation}")
|
|
71
|
+
if r.contribution:
|
|
72
|
+
lines.append(f"- **Contribution:** {r.contribution}")
|
|
73
|
+
if r.confidence:
|
|
74
|
+
lines.append(f"- **Confidence:** {r.confidence}")
|
|
75
|
+
if r.strengths:
|
|
76
|
+
lines.append(f"- **Strengths:** {r.strengths}")
|
|
77
|
+
if r.weaknesses:
|
|
78
|
+
lines.append(f"- **Weaknesses:** {r.weaknesses}")
|
|
79
|
+
lines.append("")
|
|
80
|
+
else:
|
|
81
|
+
lines.append("\n*No reviews available.*")
|
|
82
|
+
|
|
83
|
+
lines.append(f"\n*Source: OpenReview API v2*")
|
|
84
|
+
return [TextContent(type="text", text="\n".join(lines))]
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
async def _handle_openreview_search(args: dict) -> list[TextContent]:
|
|
88
|
+
query = args["query"]
|
|
89
|
+
venue_id = args.get("venue_id")
|
|
90
|
+
limit = min(args.get("limit", 25), 100)
|
|
91
|
+
|
|
92
|
+
papers = await _openreview_client.search(query, venue_id=venue_id, limit=limit)
|
|
93
|
+
|
|
94
|
+
if not papers:
|
|
95
|
+
return [TextContent(type="text", text=f"No OpenReview results for: {query}")]
|
|
96
|
+
|
|
97
|
+
lines = [f"## OpenReview Search: {query}\n"]
|
|
98
|
+
if venue_id:
|
|
99
|
+
lines[0] = f"## OpenReview Search: {query} (venue: {venue_id})\n"
|
|
100
|
+
|
|
101
|
+
lines.append("| # | Title | Authors | Venue | Forum ID |")
|
|
102
|
+
lines.append("|---|-------|---------|-------|----------|")
|
|
103
|
+
|
|
104
|
+
for i, p in enumerate(papers, 1):
|
|
105
|
+
title = p.title[:60] + ("..." if len(p.title) > 60 else "")
|
|
106
|
+
authors = ", ".join(p.authors[:2]) + ("..." if len(p.authors) > 2 else "")
|
|
107
|
+
venue = p.venue or p.venue_id or "—"
|
|
108
|
+
if len(venue) > 30:
|
|
109
|
+
venue = venue[:30] + "..."
|
|
110
|
+
lines.append(f"| {i} | [{title}](https://openreview.net/forum?id={p.forum_id}) | {authors} | {venue} | `{p.forum_id}` |")
|
|
111
|
+
|
|
112
|
+
lines.append(f"\n*{len(papers)} results from OpenReview*")
|
|
113
|
+
return [TextContent(type="text", text="\n".join(lines))]
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
# ---------- Tool definitions + registration ----------
|
|
117
|
+
|
|
118
|
+
_TOOLS = [
|
|
119
|
+
(
|
|
120
|
+
Tool(
|
|
121
|
+
name="openreview_venue_submissions",
|
|
122
|
+
description=(
|
|
123
|
+
"Get submissions for an AI/ML conference from OpenReview. Returns titles, abstracts, "
|
|
124
|
+
"authors, keywords, and primary areas. Supports: NeurIPS, ICLR, ICML, ACL, EMNLP, "
|
|
125
|
+
"AISTATS, UAI, CoRL, AAAI. Use shorthand like 'neurips/2024' or full ID."
|
|
126
|
+
),
|
|
127
|
+
inputSchema={
|
|
128
|
+
"type": "object",
|
|
129
|
+
"properties": {
|
|
130
|
+
"venue_id": {"type": "string", "description": "Venue ID: shorthand (neurips/2024, iclr/2025) or full (NeurIPS.cc/2024/Conference)"},
|
|
131
|
+
"limit": {"type": "integer", "description": "Max results (default 25, max 1000)"},
|
|
132
|
+
},
|
|
133
|
+
"required": ["venue_id"],
|
|
134
|
+
},
|
|
135
|
+
),
|
|
136
|
+
_handle_openreview_venue,
|
|
137
|
+
),
|
|
138
|
+
(
|
|
139
|
+
Tool(
|
|
140
|
+
name="openreview_paper_reviews",
|
|
141
|
+
description=(
|
|
142
|
+
"Get a paper and all its reviews from OpenReview. Returns the submission plus "
|
|
143
|
+
"reviewer ratings, soundness, strengths, weaknesses, and questions. "
|
|
144
|
+
"Provide the forum ID (from openreview_venue_submissions results)."
|
|
145
|
+
),
|
|
146
|
+
inputSchema={
|
|
147
|
+
"type": "object",
|
|
148
|
+
"properties": {
|
|
149
|
+
"forum_id": {"type": "string", "description": "OpenReview forum ID for the paper"},
|
|
150
|
+
},
|
|
151
|
+
"required": ["forum_id"],
|
|
152
|
+
},
|
|
153
|
+
),
|
|
154
|
+
_handle_openreview_reviews,
|
|
155
|
+
),
|
|
156
|
+
(
|
|
157
|
+
Tool(
|
|
158
|
+
name="openreview_search",
|
|
159
|
+
description=(
|
|
160
|
+
"Search OpenReview for papers by text query. Optionally filter by venue. "
|
|
161
|
+
"Returns submissions matching the query. Use for finding specific papers "
|
|
162
|
+
"or exploring what's been submitted to a conference on a topic."
|
|
163
|
+
),
|
|
164
|
+
inputSchema={
|
|
165
|
+
"type": "object",
|
|
166
|
+
"properties": {
|
|
167
|
+
"query": {"type": "string", "description": "Search query (keywords, title fragment)"},
|
|
168
|
+
"venue_id": {"type": "string", "description": "Optional venue filter (e.g. neurips/2024)"},
|
|
169
|
+
"limit": {"type": "integer", "description": "Max results (default 25)"},
|
|
170
|
+
},
|
|
171
|
+
"required": ["query"],
|
|
172
|
+
},
|
|
173
|
+
),
|
|
174
|
+
_handle_openreview_search,
|
|
175
|
+
),
|
|
176
|
+
]
|
|
177
|
+
|
|
178
|
+
for tool, handler in _TOOLS:
|
|
179
|
+
register(tool, handler)
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
"""ORCID tools (2 tools, conditional on credentials)."""
|
|
2
|
+
|
|
3
|
+
from mcp.types import Tool, TextContent
|
|
4
|
+
|
|
5
|
+
from _app import _orcid_client
|
|
6
|
+
from tools._registry import register
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
# ---------- Handlers ----------
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
async def _handle_orcid_search(args: dict) -> list[TextContent]:
|
|
13
|
+
if not _orcid_client:
|
|
14
|
+
return [TextContent(type="text", text="**Error:** ORCID not configured (set ORCID_CLIENT_ID + ORCID_CLIENT_SECRET)")]
|
|
15
|
+
|
|
16
|
+
query = args["query"]
|
|
17
|
+
limit = min(args.get("limit", 10), 100)
|
|
18
|
+
|
|
19
|
+
results = await _orcid_client.search(query, limit=limit)
|
|
20
|
+
|
|
21
|
+
if not results:
|
|
22
|
+
return [TextContent(type="text", text=f"No ORCID profiles found for: {query}")]
|
|
23
|
+
|
|
24
|
+
lines = [f"## ORCID Search: {query}\n"]
|
|
25
|
+
lines.append(f"| # | ORCID iD | Name | Affiliations |")
|
|
26
|
+
lines.append(f"|---|----------|------|-------------|")
|
|
27
|
+
|
|
28
|
+
for i, r in enumerate(results, 1):
|
|
29
|
+
name = r.credit_name or f"{r.given_names} {r.family_name}"
|
|
30
|
+
institutions = ", ".join(r.institutions[:3]) if r.institutions else "—"
|
|
31
|
+
lines.append(f"| {i} | [{r.orcid_id}](https://orcid.org/{r.orcid_id}) | {name} | {institutions} |")
|
|
32
|
+
|
|
33
|
+
lines.append(f"\n*{len(results)} result(s) from ORCID registry*")
|
|
34
|
+
return [TextContent(type="text", text="\n".join(lines))]
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
async def _handle_orcid_get_researcher(args: dict) -> list[TextContent]:
|
|
38
|
+
if not _orcid_client:
|
|
39
|
+
return [TextContent(type="text", text="**Error:** ORCID not configured (set ORCID_CLIENT_ID + ORCID_CLIENT_SECRET)")]
|
|
40
|
+
|
|
41
|
+
orcid_id = args["orcid_id"]
|
|
42
|
+
include_works = args.get("include_works", True)
|
|
43
|
+
max_works = min(args.get("max_works", 50), 200)
|
|
44
|
+
|
|
45
|
+
researcher = await _orcid_client.get_researcher(
|
|
46
|
+
orcid_id,
|
|
47
|
+
include_works=include_works,
|
|
48
|
+
max_works=max_works,
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
if not researcher:
|
|
52
|
+
return [TextContent(type="text", text=f"ORCID profile not found: {orcid_id}")]
|
|
53
|
+
|
|
54
|
+
lines = [f"## {researcher.display_name}\n"]
|
|
55
|
+
lines.append(f"**ORCID:** [{researcher.orcid_id}]({researcher.profile_url})")
|
|
56
|
+
|
|
57
|
+
if researcher.affiliations:
|
|
58
|
+
lines.append(f"**Affiliations:** {', '.join(researcher.affiliations)}")
|
|
59
|
+
|
|
60
|
+
if researcher.biography:
|
|
61
|
+
bio = researcher.biography[:500]
|
|
62
|
+
if len(researcher.biography) > 500:
|
|
63
|
+
bio += "..."
|
|
64
|
+
lines.append(f"\n**Biography:** {bio}")
|
|
65
|
+
|
|
66
|
+
if researcher.keywords:
|
|
67
|
+
lines.append(f"**Keywords:** {', '.join(researcher.keywords)}")
|
|
68
|
+
|
|
69
|
+
if researcher.urls:
|
|
70
|
+
url_parts = [f"[{name}]({url})" for name, url in list(researcher.urls.items())[:5]]
|
|
71
|
+
lines.append(f"**Links:** {' · '.join(url_parts)}")
|
|
72
|
+
|
|
73
|
+
if researcher.works:
|
|
74
|
+
lines.append(f"\n### Publications ({researcher.works_count} total, showing {len(researcher.works)})\n")
|
|
75
|
+
lines.append("| Year | Title | DOI | Type |")
|
|
76
|
+
lines.append("|------|-------|-----|------|")
|
|
77
|
+
|
|
78
|
+
for w in sorted(researcher.works, key=lambda x: x.year or 0, reverse=True):
|
|
79
|
+
year = str(w.year) if w.year else "—"
|
|
80
|
+
title = w.title[:80] + ("..." if len(w.title) > 80 else "")
|
|
81
|
+
doi_link = f"[{w.doi}](https://doi.org/{w.doi})" if w.doi else "—"
|
|
82
|
+
wtype = (w.work_type or "—").replace("-", " ")
|
|
83
|
+
lines.append(f"| {year} | {title} | {doi_link} | {wtype} |")
|
|
84
|
+
|
|
85
|
+
lines.append(f"\n*Source: ORCID Public API v3.0*")
|
|
86
|
+
return [TextContent(type="text", text="\n".join(lines))]
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
# ---------- Registration (conditional) ----------
|
|
90
|
+
|
|
91
|
+
if _orcid_client:
|
|
92
|
+
register(
|
|
93
|
+
Tool(
|
|
94
|
+
name="orcid_search_researchers",
|
|
95
|
+
description=(
|
|
96
|
+
"Search the ORCID registry for researchers by name, affiliation, or keyword. "
|
|
97
|
+
"Returns ORCID iDs with names and institutional affiliations. "
|
|
98
|
+
"Query syntax: family-name:Smith, given-names:John, affiliation-org-name:Warwick, keyword:MCDM. "
|
|
99
|
+
"Combine with AND/OR. Use this to find a researcher's ORCID iD for disambiguation."
|
|
100
|
+
),
|
|
101
|
+
inputSchema={
|
|
102
|
+
"type": "object",
|
|
103
|
+
"properties": {
|
|
104
|
+
"query": {"type": "string", "description": "ORCID search query (Lucene syntax: family-name:X AND affiliation-org-name:Y)"},
|
|
105
|
+
"limit": {"type": "integer", "description": "Max results (default 10, max 100)"},
|
|
106
|
+
},
|
|
107
|
+
"required": ["query"],
|
|
108
|
+
},
|
|
109
|
+
),
|
|
110
|
+
_handle_orcid_search,
|
|
111
|
+
)
|
|
112
|
+
register(
|
|
113
|
+
Tool(
|
|
114
|
+
name="orcid_get_researcher",
|
|
115
|
+
description=(
|
|
116
|
+
"Get a researcher's full ORCID profile: name, biography, affiliations, keywords, "
|
|
117
|
+
"URLs, and publication list with DOIs. Provide an ORCID iD (e.g. 0000-0001-2345-6789). "
|
|
118
|
+
"Use orcid_search_researchers first to find the iD if you only have a name."
|
|
119
|
+
),
|
|
120
|
+
inputSchema={
|
|
121
|
+
"type": "object",
|
|
122
|
+
"properties": {
|
|
123
|
+
"orcid_id": {"type": "string", "description": "ORCID identifier (e.g. 0000-0001-2345-6789 or https://orcid.org/0000-0001-2345-6789)"},
|
|
124
|
+
"include_works": {"type": "boolean", "description": "Include publication list (default true)"},
|
|
125
|
+
"max_works": {"type": "integer", "description": "Max works to return (default 50)"},
|
|
126
|
+
},
|
|
127
|
+
"required": ["orcid_id"],
|
|
128
|
+
},
|
|
129
|
+
),
|
|
130
|
+
_handle_orcid_get_researcher,
|
|
131
|
+
)
|