#
tokens: 4135/50000 18/18 files
lines: off (toggle) GitHub
raw markdown copy
# Directory Structure

```
├── .gitignore
├── .python-version
├── aer_mcp.py
├── app
│   ├── __init__.py
│   ├── aea
│   │   ├── __init__.py
│   │   ├── aej
│   │   │   └── __init__.py
│   │   ├── aer
│   │   │   └── __init__.py
│   │   ├── aeri
│   │   │   └── __init__.py
│   │   ├── app
│   │   │   └── __init__.py
│   │   ├── mac
│   │   │   └── __init__.py
│   │   ├── mapping.py
│   │   ├── mic
│   │   │   └── __init__.py
│   │   └── pol
│   │       └── __init__.py
│   └── utils
│       ├── __init__.py
│       ├── doi.py
│       └── fake_ua.py
├── LICENSE
├── pyproject.toml
├── README.md
└── uv.lock
```

# Files

--------------------------------------------------------------------------------
/.python-version:
--------------------------------------------------------------------------------

```
3.12

```

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
# Python-generated files
__pycache__/
*.py[oc]
build/
dist/
wheels/
*.egg-info

# Virtual environments
.venv

# dev
dev/

# test
test/
tests/
test.py

# VScode
.vscode

# PyCharm
.idea

# OS X
*.DS_Store


```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
# AER-MCP

A MCP Server for finding from AEA.

> 🔍 Search papers from AEA with LLM
> 
> 🛠️ Still Under Developing
> 
> 💡 Inspiration: [arxiv-mcp-server](https://github.com/blazickjp/arxiv-mcp-server)

## 💡 Quickly Start
### Localization
```bash
git clone https://github.com/sepinetam/aer-mcp.git
cd aer-mcp
uv venv .venv
```

Then, config it
```json
{
  "mcpServers": {
    "aer-mcp": {
      "command": "uv",
      "args": [
        "--directory",
        "/path/to/the/repo/",
        "run",
        "aer_mcp.py"
      ]
    }
  }
}
```

## ⛓️ More Academic MCP Servers
- [Stata-MCP](https://github.com/sepinetam/stata-mcp)
- [NBER-MCP](https://github.com/sepinetam/nber-mcp)
- [arxiv-mcp-server](https://github.com/blazickjp/arxiv-mcp-server)



```

--------------------------------------------------------------------------------
/app/__init__.py:
--------------------------------------------------------------------------------

```python
__version__ = "0.1.0"

```

--------------------------------------------------------------------------------
/app/utils/__init__.py:
--------------------------------------------------------------------------------

```python
#!/usr/bin/python3
# -*- coding: utf-8 -*-
#
# Copyright (C) 2025 - Present Sepine Tam, Inc. All Rights Reserved
#
# @Author : Sepine Tam
# @Email  : [email protected]
# @File   : __init__.py

```

--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------

```toml
[project]
name = "aer-mcp"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.12"
dependencies = [
    "bs4>=0.0.2",
    "fake-useragent>=2.2.0",
    "mcp[cli]>=1.6.0",
    "requests>=2.32.3",
    "selenium>=4.31.0",
]

```

--------------------------------------------------------------------------------
/app/aea/app/__init__.py:
--------------------------------------------------------------------------------

```python
#!/usr/bin/python3
# -*- coding: utf-8 -*-
#
# Copyright (C) 2025 - Present Sepine Tam, Inc. All Rights Reserved
#
# @Author : Sepine Tam
# @Email  : [email protected]
# @File   : __init__.py

from app.aea import AEA


class APP(AEA):
    def __init__(self):
        super().__init__()
        self.journal = "app"
        self.base_url = f"https://www.aeaweb.org/journals/{self.journal}/"

```

--------------------------------------------------------------------------------
/app/aea/mac/__init__.py:
--------------------------------------------------------------------------------

```python
#!/usr/bin/python3
# -*- coding: utf-8 -*-
#
# Copyright (C) 2025 - Present Sepine Tam, Inc. All Rights Reserved
#
# @Author : Sepine Tam
# @Email  : [email protected]
# @File   : __init__.py

from app.aea import AEA


class MAC(AEA):
    def __init__(self):
        super().__init__()
        self.journal = "mac"
        self.base_url = f"https://www.aeaweb.org/journals/{self.journal}/"

```

--------------------------------------------------------------------------------
/app/aea/mic/__init__.py:
--------------------------------------------------------------------------------

```python
#!/usr/bin/python3
# -*- coding: utf-8 -*-
#
# Copyright (C) 2025 - Present Sepine Tam, Inc. All Rights Reserved
#
# @Author : Sepine Tam
# @Email  : [email protected]
# @File   : __init__.py

from app.aea import AEA


class MIC(AEA):
    def __init__(self):
        super().__init__()
        self.journal = "mic"
        self.base_url = f"https://www.aeaweb.org/journals/{self.journal}/"

```

--------------------------------------------------------------------------------
/app/aea/pol/__init__.py:
--------------------------------------------------------------------------------

```python
#!/usr/bin/python3
# -*- coding: utf-8 -*-
#
# Copyright (C) 2025 - Present Sepine Tam, Inc. All Rights Reserved
#
# @Author : Sepine Tam
# @Email  : [email protected]
# @File   : __init__.py

from app.aea import AEA


class POL(AEA):
    def __init__(self):
        super().__init__()
        self.journal = "pol"
        self.base_url = f"https://www.aeaweb.org/journals/{self.journal}/"

```

--------------------------------------------------------------------------------
/app/aea/aej/__init__.py:
--------------------------------------------------------------------------------

```python
#!/usr/bin/python3
# -*- coding: utf-8 -*-
#
# Copyright (C) 2025 - Present Sepine Tam, Inc. All Rights Reserved
#
# @Author : Sepine Tam
# @Email  : [email protected]
# @File   : __init__.py

from app.aea import AEA


class AEJ(AEA):
    def __init__(self):
        super().__init__()
        self.journal = "aej"
        self.base_url = f"https://www.aeaweb.org/journals/{self.journal}/"


```

--------------------------------------------------------------------------------
/app/aea/aeri/__init__.py:
--------------------------------------------------------------------------------

```python
#!/usr/bin/python3
# -*- coding: utf-8 -*-
#
# Copyright (C) 2025 - Present Sepine Tam, Inc. All Rights Reserved
#
# @Author : Sepine Tam
# @Email  : [email protected]
# @File   : __init__.py

from app.aea import AEA


class AERI(AEA):
    def __init__(self):
        super().__init__()
        self.journal = "aeri"
        self.base_url = f"https://www.aeaweb.org/journals/{self.journal}/"


```

--------------------------------------------------------------------------------
/app/aea/mapping.py:
--------------------------------------------------------------------------------

```python
#!/usr/bin/python3
# -*- coding: utf-8 -*-
#
# Copyright (C) 2025 - Present Sepine Tam, Inc. All Rights Reserved
#
# @Author : Sepine Tam
# @Email  : [email protected]
# @File   : mapping.py

from app.aea import aer, aeri, app, pol, mac, mic

def mapping(class_name: str):
    mapping_dict = {
        "aer": aer.AER,
        "aeri": aeri.AERI,
        "app": app.APP,
        "pol": pol.POL,
        "mac": mac.MAC,
        "mic": mic.MIC
    }

    return mapping_dict.get(class_name)

```

--------------------------------------------------------------------------------
/app/aea/aer/__init__.py:
--------------------------------------------------------------------------------

```python
#!/usr/bin/python3
# -*- coding: utf-8 -*-
#
# Copyright (C) 2025 - Present Sepine Tam, Inc. All Rights Reserved
#
# @Author : Sepine Tam
# @Email  : [email protected]
# @File   : __init__.py

from app.aea import AEA


class AER(AEA):
    def __init__(self):
        super().__init__()
        self.journal = "aer"
        self.base_url = f"https://www.aeaweb.org/journals/{self.journal}/"


if __name__ == "__main__":
    aer = AER()
    print(aer.article_path_url("10.1257/aer.115.4.1059"))

```

--------------------------------------------------------------------------------
/app/utils/fake_ua.py:
--------------------------------------------------------------------------------

```python
#!/usr/bin/python3
# -*- coding: utf-8 -*-
#
# Copyright (C) 2025 - Present Sepine Tam, Inc. All Rights Reserved
#
# @Author : Sepine Tam
# @Email  : [email protected]
# @File   : fake_ua.py

import random
from fake_useragent import UserAgent
import time


def get_random_headers():
    ua = UserAgent()
    user_agent = ua.random

    ip = f"{random.randint(1, 255)}.{random.randint(1, 255)}.{random.randint(1, 255)}.{random.randint(1, 255)}"

    referers = [
        'https://www.google.com/',
        'https://www.bing.com/',
        'https://www.aeaweb.org/',
        'https://scholar.google.com/',
        'https://www.statamcp.com/'
    ]

    headers = {
        'User-Agent': user_agent,
        'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
        # 'Accept-Language': random.choice(languages),
        'Accept-Encoding': 'gzip, deflate, br',
        'Connection': 'keep-alive',
        'Upgrade-Insecure-Requests': '1',
        'X-Forwarded-For': ip,
        'X-Real-IP': ip,
        'Client-IP': ip,
        'Referer': random.choice(referers),
        'Cache-Control': 'max-age=0',
        'Pragma': 'no-cache'
    }
    return headers


```

--------------------------------------------------------------------------------
/app/utils/doi.py:
--------------------------------------------------------------------------------

```python
#!/usr/bin/python3
# -*- coding: utf-8 -*-
#
# Copyright (C) 2025 - Present Sepine Tam, Inc. All Rights Reserved
#
# @Author : Sepine Tam
# @Email  : [email protected]
# @File   : doi.py

import requests
from typing import Any


def get_doi_metadata(doi: str) -> Any | None:
    """
    Get metadata of academic paper via doi of paper

    Args:
        doi (str): The doi of paper, e.g. '10.1257/aer.20181249'

    Returns:
        dict: metadata dictionary
    """
    # API URL
    base_url = "https://api.crossref.org/works/"
    url = base_url + doi

    # request header, the official recommend add your email (not necessary)
    mail_address = "[email protected]"
    headers = {
        'User-Agent': f'Python-Script/1.0 (mailto:{mail_address})'
    }

    try:
        # get requests
        response = requests.get(url, headers=headers)

        # check whether success
        if response.status_code == 200:
            data = response.json()
            return data['message']
        else:
            print(f"WRONG, STATUS_CODE: {response.status_code}")
            return None
    except Exception as e:
        print(f"WRONG: {str(e)}")
        return None


def get_abstract_from_crossref(doi):
    url = f"https://api.crossref.org/works/{doi}"
    headers = {"Accept": "application/json"}
    response = requests.get(url, headers=headers)

    if response.status_code == 200:
        data = response.json()
        if 'abstract' in data['message']:
            return data['message']['abstract']
        else:
            return "Abstract not available in Crossref"
    else:
        return f"Error: {response.status_code}"


def key_points(doi):
    metadata: dict = get_doi_metadata(doi)
    _results = {'doi': doi, 'date': metadata['deposited']['date-time'], 'title': metadata['title'],
                'authors': metadata['author'], 'resource': metadata['resource'],
                'abstract': get_abstract_from_crossref(doi)}
    return _results


# 使用示例
if __name__ == "__main__":
    sample_doi = "10.1257/aer.20181249"
    result = get_doi_metadata(sample_doi)
    print(result.keys())

```

--------------------------------------------------------------------------------
/app/aea/__init__.py:
--------------------------------------------------------------------------------

```python
#!/usr/bin/python3
# -*- coding: utf-8 -*-
#
# Copyright (C) 2025 - Present Sepine Tam, Inc. All Rights Reserved
#
# @Author : Sepine Tam
# @Email  : [email protected]
# @File   : __init__.py

import requests
from bs4 import BeautifulSoup

from app.utils import fake_ua

class AEA:
    def __init__(self):
        self.home_url = "https://www.aeaweb.org/"
        self.base_url = None

    def article_path_url(self, doi):
        url = self.home_url + f"articles?id={doi}"
        return url

    @staticmethod
    def _extract_id(response: requests.models.Response) -> list:
        if response.status_code != 200:
            return []
        soup = BeautifulSoup(response.text, 'html.parser')
        container = soup.find(class_="journal-article-group")

        if not container:
            return []

        articles = container.find_all('article', class_="journal-article")
        article_ids = [article.get('id') for article in articles if article.has_attr('id')]
        return article_ids

    def search(self,
               q: str,
               search_in_title: bool = True,
               search_in_abstract: bool = True,
               search_in_author: bool = True,
               jel_class: str = None,
               journal: bool = True
               ) -> list:
        if self.base_url:
            base_url = self.base_url + "search-results?"
        else:
            base_url = "https://www.aeaweb.org/journals/aer/search-results?"

        params = [f"ArticleSearch[within][articletitle]={int(search_in_title)}",
                  f"ArticleSearch[within][articleabstract]={int(search_in_abstract)}",
                  f"ArticleSearch[within][authorlast]={int(search_in_author)}",
                  f"JelClass[value]={jel_class or 0}",
                  f"journal={int(journal)}", f"ArticleSearch[q]={q}"]

        search_url = base_url + "&".join(params)

        fake_headers = fake_ua.get_random_headers()
        resp = requests.get(search_url, headers=fake_headers)

        return self._extract_id(resp)


if __name__ == "__main__":
    sample_doi = "10.1257/aer.115.4.1059"
    aea = AEA()
    print(aea.search("skill"))

```

--------------------------------------------------------------------------------
/aer_mcp.py:
--------------------------------------------------------------------------------

```python
from app.aea import mapping
from app.utils.doi import key_points

from mcp.server.fastmcp import FastMCP

mcp = FastMCP(name="AER-MCP")

_journal_dict: dict = {
    "American Economic Review": "aer",
    "AER: Insight": "aeri",
    "AEJ: Applied Economics": "app",
    "AEJ: Economic Policy": "pol",
    "AEJ: Macroeconomics": "mac",
    "AEJ: Microeconomics": "mic",
}


# @mcp.tool()
# def journal_dict() -> dict:
#     """
#     Get journal list and the short code
#
#     Returns:
#         The content of journal in type of dict
#     """
#     return _journal_dict

@mcp.tool()
def aea_search(q: str, journal_list: list = None, max_search: int = None) -> dict:
    """
    Search from ARA Journal.

    Args:
        q (str):
            The question for search, the language of search should be English.
        journal_list (list):
            The journal list which search from,
            The default is ['aer']
        max_search (int):
            The max search results of returns.
            Limits the total number of results across all journals and higher speed of run.

    Returns:
        The search result with dict type.

    Notes:
        Total optional of journal is listed in ['aer', 'aeri', 'app', 'pol', 'mac', 'mic']
        The journal short name stands the follow dictionary:
        {
            "American Economic Review": "aer",
            "AER: Insight": "aeri",
            "AEJ: Applied Economics": "app",
            "AEJ: Economic Policy": "pol",
            "AEJ: Macroeconomics": "mac",
            "AEJ: Microeconomics": "mic",
        }

    Examples:
        >>> aea_search("monetary policy")
        >>> aea_search("labor market", journal_list=["aer", "mac", "app"])
        >>> aea_search("skill and age", journal_list=["aer", "aeri"], max_search=30)
    """
    if journal_list is None:
        # journal_list = _journal_dict.values()
        journal_list = ["aer", "aeri"]

    _results: dict = {}
    _count = 0
    for journal in journal_list:
        _results[journal]: list = []
        j_con_doi_list: list = mapping.mapping(journal)().search(q=q)
        for doi in j_con_doi_list:
            if (key := key_points(doi)) is not None:
                _results[journal].append(key)
                _count += 1
            if _count > max_search:
                return _results

    return _results


if __name__ == "__main__":
    mcp.run(transport="stdio")

```