#
tokens: 2361/50000 4/4 files
lines: on (toggle) GitHub
raw markdown copy reset
# Directory Structure

```
├── .gitignore
├── LICENSE
├── main.py
├── README.md
└── requirements.txt
```

# Files

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

```
 1 | # Python
 2 | __pycache__/
 3 | venv/
 4 | .env/
 5 | .venv/
 6 | 
 7 | # VS Code
 8 | .vscode/
 9 | 
10 | # macOS
11 | .DS_Store
12 | 
13 | # Local env files
14 | .envrc
15 | .env.*
```

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

```markdown
  1 | # mcp-medium-accelerator
  2 | 
  3 | Questo MCP server permette a LLM come ad esempio Claude Desktop, dando in input una URL tipo "https://medium.com/tag/frontend/archive" di estrapolare tutti i link degli ultimi articoli. Creare un riassunto, anche in italiano degli articoli che interessano all'utente. Salvare il riassunto in una memoria locale da poter interpellare in qualsiasi momento.
  4 | 
  5 | ---
  6 | 
  7 | ## Requisiti
  8 | 
  9 | Assicurati di avere installato:
 10 | 
 11 | - Python ≥ 3.10
 12 | - Claude Desktop
 13 | 
 14 | ---
 15 | 
 16 | ## Installazione locale
 17 | 
 18 | 1. Clona il repository:
 19 | 
 20 | ```bash
 21 | git clone https://github.com/crtdaniele/mcp-medium-accelerator
 22 | cd mcp-medium-accelerator
 23 | ```
 24 | 
 25 | 2. Crea e attiva un ambiente virtuale:
 26 | 
 27 | ```bash
 28 | python -m venv venv
 29 | source venv/bin/activate
 30 | ```
 31 | 
 32 | 3. Installa le dipendenze:
 33 | 
 34 | ```bash
 35 | pip install -r requirements.txt
 36 | ```
 37 | 
 38 | 4. (Facoltativo) Aggiorna il file requirements.txt dopo aver aggiunto nuove librerie:
 39 | 
 40 | ```bash
 41 | pip freeze > requirements.txt
 42 | ```
 43 | 
 44 | ## Avvio del server MCP
 45 | 
 46 | Per eseguire il server MCP in modalità sviluppo con hot reload:
 47 | 
 48 | ```bash
 49 | mcp dev main.py
 50 | ```
 51 | 
 52 | Per eseguire il server in modalità normale:
 53 | 
 54 | ```bash
 55 | mcp run main.py
 56 | ```
 57 | 
 58 | ## Tool
 59 | 
 60 | Tool disponibili:
 61 | 
 62 | - **extract_article_links:**
 63 | 
 64 | Estrae i link degli articoli da un URL di archivio Medium. Restituisce una lista di link agli articoli.
 65 | 
 66 | - **extract_article_text:**
 67 | 
 68 | Estrae il contenuto di un articolo da un URL di Medium. Restituisce il contenuto dell’articolo. Chiede all’utente se desidera salvare il riassunto tramite save_summary.
 69 | 
 70 | - **save_summary:**
 71 | 
 72 | Salva un riassunto di un articolo con titolo, URL e tag. Restituisce un messaggio di stato.
 73 | 
 74 | - **list_summaries:**
 75 | 
 76 | Elenca tutti i riassunti salvati. Restituisce una lista di riassunti.
 77 | 
 78 | ## Installazione su Claude Desktop
 79 | 
 80 | ```bash
 81 | mcp install main.py
 82 | ```
 83 | 
 84 | Oppure configura manualmente il file settings.json (Claude Desktop > Settings > Advanced):
 85 | 
 86 | ```bash
 87 | {
 88 |   "mcpServers": {
 89 |     "mcp-medium-accelerator": {
 90 |       "command": "/opt/homebrew/bin/uv",
 91 |       "args": [
 92 |         "run",
 93 |         "--with",
 94 |         "bs4",
 95 |         "--with",
 96 |         "httpx",
 97 |         "--with",
 98 |         "datetime",
 99 |         "--with",
100 |         "tinydb",
101 |         "--with",
102 |         "mcp[cli]",
103 |         "mcp",
104 |         "run",
105 |         "/your-local-path/main.py"
106 |       ]
107 |     }
108 |   }
109 | }
110 | ```
111 | 
112 | ## Licenza
113 | 
114 | MIT License.
115 | © 2025 Daniele Carta
116 | 
117 | # Contribuire
118 | 
119 | Pull request benvenute!
120 | 
121 | Segnala bug o richiedi funzionalità aprendo una issue.
122 | 
```

--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------

```
 1 | annotated-types==0.7.0
 2 | anyio==4.9.0
 3 | beautifulsoup4==4.13.4
 4 | bs4==0.0.2
 5 | certifi==2025.4.26
 6 | charset-normalizer==3.4.2
 7 | click==8.2.1
 8 | h11==0.16.0
 9 | httpcore==1.0.9
10 | httpx==0.28.1
11 | httpx-sse==0.4.0
12 | idna==3.10
13 | markdown-it-py==3.0.0
14 | mcp==1.9.2
15 | mdurl==0.1.2
16 | pydantic==2.11.5
17 | pydantic-settings==2.9.1
18 | pydantic_core==2.33.2
19 | Pygments==2.19.1
20 | python-dotenv==1.1.0
21 | python-multipart==0.0.20
22 | requests==2.32.3
23 | rich==14.0.0
24 | shellingham==1.5.4
25 | sniffio==1.3.1
26 | soupsieve==2.7
27 | sse-starlette==2.3.5
28 | starlette==0.47.0
29 | tinydb==4.8.2
30 | typer==0.16.0
31 | typing-inspection==0.4.1
32 | typing_extensions==4.13.2
33 | urllib3==2.4.0
34 | uvicorn==0.34.2
```

--------------------------------------------------------------------------------
/main.py:
--------------------------------------------------------------------------------

```python
 1 | from mcp.server.fastmcp import FastMCP
 2 | import httpx
 3 | from bs4 import BeautifulSoup
 4 | import os
 5 | from tinydb import TinyDB, Query
 6 | from datetime import datetime
 7 | 
 8 | mcp = FastMCP("mcp-medium-accelerator")
 9 | 
10 | home_dir = os.path.expanduser("~")
11 | data_dir = os.path.join(home_dir, "mcp-medium-accelerator_data")
12 | os.makedirs(data_dir, exist_ok=True)
13 | 
14 | db_path = os.path.join(data_dir, "summaries.json")
15 | db = TinyDB(db_path)
16 | summaries_table = db.table("summaries")
17 | 
18 | @mcp.tool(
19 |     description="Extracts article links from a Medium archive URL. Returns a list of article links.",
20 |     name="extract_article_links",
21 | )
22 | def extract_article_links(archive_url: str, limit: int = 10) -> list[str]:
23 |     response = httpx.get(archive_url)
24 |     soup = BeautifulSoup(response.text, "html.parser")
25 |     links = []
26 | 
27 |     for div in soup.find_all("article", attrs={"data-testid": "post-preview"}):
28 |         for inner_div in div.find_all("div"):
29 |             if inner_div.has_attr("data-href"):
30 |                 div_with_data_href = inner_div["data-href"]
31 |                 break
32 |         if div_with_data_href:
33 |             href = div_with_data_href
34 |             if href not in links:
35 |                 links.append(href)
36 |             if len(links) >= limit:
37 |                 break
38 | 
39 |     return links
40 | 
41 | @mcp.tool(
42 |     description="Saves a summary of an article with its title, URL, and tags. Returns a status message.",
43 |     name="save_summary",
44 | )
45 | def save_summary(title: str, url: str, summary: str, tags: list[str] = []):
46 |     entry = {
47 |         "title": title,
48 |         "url": url,
49 |         "summary": summary,
50 |         "tags": tags,
51 |         "saved_at": datetime.utcnow().isoformat()
52 |     }
53 | 
54 |     Article = Query()
55 |     if summaries_table.contains(Article.url == url):
56 |         return {"status": "already_saved"}
57 | 
58 |     summaries_table.insert(entry)
59 |     return {"status": "ok"}
60 | 
61 | @mcp.tool(
62 |     description="Lists all saved summaries. Returns a list of summaries.",
63 |     name="list_summaries",
64 | )
65 | def list_summaries():
66 |     return summaries_table.all()
67 | 
68 | @mcp.tool(
69 |     description="Extracts the text content from a saved article summary. Ask if the user want to save it with save_summary.",
70 |     name="extract_article_text",
71 | )
72 | def extract_article_text(url: str):
73 |     response = httpx.get(url)
74 |     soup = BeautifulSoup(response.text, "html.parser")
75 |     article = soup.find("article")
76 |     return article.get_text() if article else ""
```