mcpField
A
academic-citation-manager

academic-citation-manager

github.com/wentorai生産性30757 回インストール19 週間前に更新
リポジトリ

academic-citation-manager について

BibTeXを基準形式として学術引用を管理するスキルです。DOIなどからのメタデータ取得、形式変換、重複処理、検証に対応し、LaTeXやMarkdownの執筆ワークフローに利用できます。

主な機能

  1. 01DOIなどから引用メタデータを取得
  2. 02複数の引用形式を相互変換
  3. 03重複エントリーの検出と統合
  4. 04CrossRefとOpenAlexによる検証
  5. 05書式付き参考文献の生成

活用シーン

  1. 01複数の情報源の文献を整理
  2. 02投稿先に応じた引用形式の切り替え
  3. 03DOI・年・重複エントリーの確認

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/wentorai/research-plugins --skill academic-citation-manager

インストール後、Claude Code で/academic-citation-managerを入力して読み込まれたか確認します。

詳細

学術文献引用マネージャー

BibTeX、APA 第7版、MLA 第9版、シカゴ、バンクーバー、IEEE など、複数の形式で学術文献の引用を管理できます。DOI からの自動取得、形式間の変換、重複の排除、検証に対応します。このスキルは、文献情報の取得から原稿の最終的な書式設定まで、引用のライフサイクル全体を扱います。

概要

引用の管理は、学術論文の執筆において継続的な負担となっています。研究者は、データベース、PDF、同僚、ウェブページなど複数のソースから文献を集め、異なる形式で保存し、投稿先の各ジャーナルが指定するスタイルで出力する必要があります。著者名の誤記、年の間違い、無効な DOI、書式の不統一といった引用の誤りは、編集部での却下や査読者からの指摘のよくある原因です。

このスキルは、GUI の文献管理ツールが提供する範囲を超えた、包括的な文献引用の管理ワークフローを提供します。数秒で DOI から完全なメタデータを取得し、任意の引用形式間で変換し、重複項目を検出して統合し、CrossRef および OpenAlex のデータベースと照合して項目を検証し、主要な引用スタイルに対応した書誌を適切な書式で生成できます。

テキストベースでスクリプト化できるため、LaTeX のワークフロー、Markdown の執筆パイプライン、自動文書生成との連携に最適です。すべての引用データは標準的な BibTeX 形式で正本として保存し、原稿の要件に応じて必要な形式へ変換できます。

引用情報の取得

DOI から
import requests

def get_bibtex_from_doi(doi):
    """Retrieve BibTeX entry from a DOI via CrossRef."""
    url = f"https://doi.org/{doi}"
    headers = {"Accept": "application/x-bibtex"}
    response = requests.get(url, headers=headers, allow_redirects=True)
    if response.status_code == 200:
        return response.text
    return None

# Example
bibtex = get_bibtex_from_doi("10.1038/s41586-021-03819-2")
print(bibtex)
# @article{Jumper_2021,
#   title={Highly accurate protein structure prediction with AlphaFold},
#   author={Jumper, John and Evans, Richard and ...},
#   journal={Nature},
#   volume={596},
#   pages={583--589},
#   year={2021},
#   publisher={Springer}
# }
OpenAlex から
def get_citation_from_openalex(work_id):
    """Retrieve citation data from OpenAlex API."""
    url = f"https://api.openalex.org/works/{work_id}"
    headers = {"User-Agent": "ResearchPlugins/1.0 (https://wentor.ai)"}
    response = requests.get(url, headers=headers)
    if response.status_code == 200:
        data = response.json()
        return format_as_bibtex(data)
    return None

def format_as_bibtex(oa_data):
    """Convert OpenAlex data to BibTeX."""
    authorships = oa_data.get("authorships", [])
    author_str = " and ".join(a["author"]["display_name"] for a in authorships)
    first_author = authorships[0]["author"]["display_name"].split()[-1] if authorships else "Unknown"
    year = str(oa_data.get("publication_year", ""))
    key = f"{first_author}_{year}"

    venue = oa_data.get("primary_location", {}) or {}
    journal = (venue.get("source") or {}).get("display_name", "")

    return f"""@article{{{key},
  title={{{oa_data.get('title', '')}}},
  author={{{author_str}}},
  year={{{year}}},
  journal={{{journal}}},
  doi={{{oa_data.get('doi', '')}}}
}}"""
arXiv ID から
def get_bibtex_from_arxiv(arxiv_id):
    """Retrieve BibTeX from arXiv."""
    import feedparser
    url = f"http://export.arxiv.org/api/query?id_list={arxiv_id}"
    feed = feedparser.parse(url)
    if feed.entries:
        entry = feed.entries[0]
        authors = " and ".join(a["name"] for a in entry.authors)
        first_author = entry.authors[0]["name"].split()[-1]
        year = entry.published[:4]
        return f"""@article{{{first_author}_{year},
  title={{{entry.title.replace(chr(10), ' ')}}},
  author={{{authors}}},
  year={{{year}}},
  journal={{arXiv preprint arXiv:{arxiv_id}}},
  url={{https://arxiv.org/abs/{arxiv_id}}}
}}"""
    return None

形式の変換

BibTeX から APA 第7版へ
def bibtex_to_apa7(entry):
    """Convert a parsed BibTeX entry to APA 7th edition format."""
    authors = format_apa_authors(entry["author"])
    year = entry.get("year", "n.d.")
    title = entry["title"]
    journal = entry.get("journal", "")
    volume = entry.get("volume", "")
    issue = entry.get("number", "")
    pages = entry.get("pages", "")
    doi = entry.get("doi", "")

    # Article format
    citation = f"{authors} ({year}). {title}. "
    if journal:
        citation += f"*{journal}*"
        if volume:
            citation += f", *{volume}*"
        if issue:
            citation += f"({issue})"
        if pages:
            citation += f", {pages}"
        citation += "."
    if doi:
        citation += f" https://doi.org/{doi}"

    return citation

def format_apa_authors(author_string):
    """Format author names in APA style: Last, F. M."""
    authors = [a.strip() for a in author_string.split(" and ")]
    formatted = []
    for author in authors:
        parts = author.split(", ") if ", " in author else author.rsplit(" ", 1)[::-1]
        if len(parts) >= 2:
            last = parts[0]
            firsts = parts[1].split()
            initials = " ".join(f"{f[0]}." for f in firsts)
            formatted.append(f"{last}, {initials}")
        else:
            formatted.append(parts[0])

    if len(formatted) == 1:
        return formatted[0]
    elif len(formatted) == 2:
        return f"{formatted[0]}, & {formatted[1]}"
    elif len(formatted) <= 20:
        return ", ".join(formatted[:-1]) + f", & {formatted[-1]}"
    else:
        return ", ".join(formatted[:19]) + f", ... {formatted[-1]}"
形式の例

同じ文献を異なるスタイルで表記した例です。

BibTeX:

@article{Jumper_2021,
  title={Highly accurate protein structure prediction with AlphaFold},
  author={Jumper, John and Evans, Richard and Pritzel, Alexander},
  journal={Nature},
  volume={596},
  pages={583--589},
  year={2021},
  doi={10.1038/s41586-021-03819-2}
}

APA 第7版: Jumper, J., Evans, R., & Pritzel, A. (2021). Highly accurate protein structure prediction with AlphaFold. Nature, 596, 583-589. https://doi.org/10.1038/s41586-021-03819-2

MLA 第9版: Jumper, John, Richard Evans, and Alexander Pritzel. "Highly Accurate Protein Structure Prediction with AlphaFold." Nature, vol. 596, 2021, pp. 583-89.

シカゴ(著者・年方式): Jumper, John, Richard Evans, and Alexander Pritzel. 2021. "Highly Accurate Protein Structure Prediction with AlphaFold." Nature 596: 583-89.

バンクーバー: Jumper J, Evans R, Pritzel A. Highly accurate protein structure prediction with AlphaFold. Nature. 2021;596:583-9.

IEEE: J. Jumper, R. Evans, and A. Pritzel, "Highly accurate protein structure prediction with AlphaFold," Nature, vol. 596, pp. 583-589, 2021.

重複の排除

重複項目の検出
from difflib import SequenceMatcher

def find_duplicates(bib_entries, threshold=0.85):
    """Find duplicate BibTeX entries by title similarity."""
    duplicates = []
    titles = [(key, normalize_title(entry["title"]))
              for key, entry in bib_entries.items()]

    for i in range(len(titles)):
        for j in range(i + 1, len(titles)):
            similarity = SequenceMatcher(
                None, titles[i][1], titles[j][1]
            ).ratio()
            if similarity >= threshold:
                duplicates.append({
                    "entry_a": titles[i][0],
                    "entry_b": titles[j][0],
                    "similarity": similarity
                })
    return duplicates

def normalize_title(title):
    """Normalize title for comparison."""
    import re
    title = title.lower()
    title = re.sub(r'[{}\\]', '', title)  # Remove LaTeX formatting
    title = re.sub(r'[^a-z0-9\s]', '', title)  # Remove punctuation
    title = ' '.join(title.split())  # Normalize whitespace
    return title

def merge_duplicates(entry_a, entry_b):
    """Merge two duplicate entries, preferring the more complete one."""
    merged = {}
    all_fields = set(list(entry_a.keys()) + list(entry_b.keys()))
    for field in all_fields:
        val_a = entry_a.get(field, "")
        val_b = entry_b.get(field, "")
        # Prefer the longer (more complete) value
        merged[field] = val_a if len(str(val_a)) >= len(str(val_b)) else val_b
    return merged

検証

CrossRef による検証
def validate_citation(doi):
    """Validate a citation against CrossRef metadata."""
    url = f"https://api.crossref.org/works/{doi}"
    response = requests.get(url)
    if response.status_code != 200:
        return {"valid": False, "error": "DOI not found in CrossRef"}

    data = response.json()["message"]
    return {
        "valid": True,
        "title": data.get("title", [None])[0],
        "authors": [f"{a.get('family', '')}, {a.get('given', '')}"
                    for a in data.get("author", [])],
        "year": data.get("published-print", {}).get("date-parts", [[None]])[0][0],
        "journal": data.get("container-title", [None])[0],
        "type": data.get("type", "unknown")
    }
よくある引用の誤り
誤り検出方法修正方法
DOI がないdoi フィールドが空かどうかを確認しますタイトルから CrossRef を検索します
年が違うCrossRef と照合しますCrossRef の年を使用します
著者名の表記揺れORCID とファジーマッチングしますORCID の名前に統一します
重複項目タイトルの類似度が 85% を超えています1つの項目に統合します
無効な URLHTTP HEAD リクエストが 4xx/5xx を返しますURL を更新するか削除します
項目が不完全スタイルに必要なフィールドがありませんDOI から取得します

執筆ツールとの連携

LaTeX
% In document preamble
\usepackage[backend=biber,style=apa]{biblatex}
\addbibresource{references.bib}

% In text
\textcite{Jumper_2021} showed that...
As demonstrated by previous work \parencite{Jumper_2021}...

% At end of document
\printbibliography
Pandoc Markdown
Previous work [@Jumper_2021] showed that...

## References
pandoc paper.md --citeproc --bibliography=references.bib \
  --csl=apa.csl -o paper.pdf

参考資料

コメント