feat(mcp): add season filtering and presentation guide for better UX
This commit is contained in:
parent
b6f0af707c
commit
d48b91f554
9 changed files with 941 additions and 18 deletions
|
|
@ -83,6 +83,8 @@ function formatTorrent(t: TorrentInfo, compact?: boolean): string {
|
|||
|
||||
export interface FormatOptions {
|
||||
compact?: boolean;
|
||||
season?: number;
|
||||
episode?: number;
|
||||
}
|
||||
|
||||
function formatResult(
|
||||
|
|
@ -102,12 +104,39 @@ function formatResult(
|
|||
}
|
||||
|
||||
if (r.torrents.length > 0) {
|
||||
const top = r.torrents
|
||||
.sort((a, b) => (b.qualityScore ?? 0) - (a.qualityScore ?? 0))
|
||||
.slice(0, 5);
|
||||
lines.push(` Torrents (${r.torrents.length} total, top ${top.length}):`);
|
||||
for (const t of top) {
|
||||
lines.push(formatTorrent(t, opts?.compact));
|
||||
// Filter torrents by season/episode if specified
|
||||
let filteredTorrents = r.torrents;
|
||||
if (opts?.season !== undefined) {
|
||||
filteredTorrents = filteredTorrents.filter(
|
||||
(t) => t.season === opts.season,
|
||||
);
|
||||
if (opts?.episode !== undefined) {
|
||||
filteredTorrents = filteredTorrents.filter(
|
||||
(t) => t.episode === opts.episode,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (filteredTorrents.length > 0) {
|
||||
const top = filteredTorrents
|
||||
.sort((a, b) => (b.qualityScore ?? 0) - (a.qualityScore ?? 0))
|
||||
.slice(0, 5);
|
||||
const totalMsg =
|
||||
filteredTorrents.length !== r.torrents.length
|
||||
? `${filteredTorrents.length} matching, ${r.torrents.length} total`
|
||||
: `${r.torrents.length} total`;
|
||||
lines.push(` Torrents (${totalMsg}, top ${top.length}):`);
|
||||
for (const t of top) {
|
||||
lines.push(formatTorrent(t, opts?.compact));
|
||||
}
|
||||
} else {
|
||||
const seasonEpStr =
|
||||
opts?.episode !== undefined
|
||||
? `S${String(opts.season).padStart(2, "0")}E${String(opts.episode).padStart(2, "0")}`
|
||||
: `season ${opts?.season}`;
|
||||
lines.push(
|
||||
` No torrents available for ${seasonEpStr} (${r.torrents.length} torrents available for other seasons)`,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
lines.push(" No torrents available");
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import { registerAutocomplete } from "./tools/autocomplete.js";
|
|||
import { registerTrackInteraction } from "./tools/track-interaction.js";
|
||||
import { registerScanRequest } from "./tools/scan-request.js";
|
||||
import { registerStatsResource } from "./resources/stats.js";
|
||||
import { registerPresentationGuideResource } from "./resources/presentation-guide.js";
|
||||
import { registerPrompts } from "./prompts.js";
|
||||
|
||||
const client = new TorrentClawClient();
|
||||
|
|
@ -37,6 +38,7 @@ registerScanRequest(server, client);
|
|||
|
||||
// Register resources
|
||||
registerStatsResource(server, client);
|
||||
registerPresentationGuideResource(server);
|
||||
|
||||
// Register prompts
|
||||
registerPrompts(server);
|
||||
|
|
|
|||
|
|
@ -2,6 +2,57 @@ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|||
import { z } from "zod";
|
||||
|
||||
export function registerPrompts(server: McpServer): void {
|
||||
server.prompt(
|
||||
"presentation_guide",
|
||||
"Guide for presenting torrent search results in a user-friendly format",
|
||||
{},
|
||||
() => ({
|
||||
messages: [
|
||||
{
|
||||
role: "user",
|
||||
content: {
|
||||
type: "text",
|
||||
text: `When presenting torrent search results to users, follow these best practices:
|
||||
|
||||
1. **Magnet Links**: Always make magnet links clickable using markdown format:
|
||||
- Format: [📥 Download](magnet:?xt=urn:btih:HASH...)
|
||||
- Or: [🧲 Magnet Link](magnet:?xt=urn:btih:HASH...)
|
||||
- Never show raw magnet URIs without making them clickable
|
||||
|
||||
2. **Content URL**: Include the TorrentClaw content URL for browsing all seasons/episodes:
|
||||
- Format: [🔗 View all seasons on TorrentClaw](https://torrentclaw.com/shows/...)
|
||||
- This allows users to explore other seasons/episodes
|
||||
|
||||
3. **Presentation Format**: Use clear, readable formatting:
|
||||
- Group by episode/season for TV shows
|
||||
- Show quality, size, and seeder count prominently
|
||||
- Highlight torrents with active seeders
|
||||
- Warn if torrents have 0 seeders
|
||||
|
||||
4. **Example Format for TV Shows**:
|
||||
**Entrevías - Temporada 4**
|
||||
|
||||
**Episodio 1** (S04E01)
|
||||
- 720p HDTV • 879 MB • 6 seeders [📥 Download](magnet:?xt=...)
|
||||
|
||||
**Episodio 2** (S04E02)
|
||||
- 1080p WEB-DL • 2.5 GB • 0 seeders ⚠️ [📥 Download](magnet:?xt=...)
|
||||
- 720p HDTV • 976 MB • 1 seeder [📥 Download](magnet:?xt=...)
|
||||
|
||||
[🔗 View all seasons on TorrentClaw](URL)
|
||||
|
||||
5. **Helpful Information**:
|
||||
- Recommend torrents with more seeders
|
||||
- Suggest alternatives if requested season/episode has no seeders
|
||||
- Offer to search for different quality if user wants
|
||||
|
||||
Apply these practices to make results actionable and user-friendly.`,
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
);
|
||||
|
||||
server.prompt(
|
||||
"search_movie",
|
||||
"Search for a movie by title and get torrent download options",
|
||||
|
|
@ -12,7 +63,7 @@ export function registerPrompts(server: McpServer): void {
|
|||
role: "user",
|
||||
content: {
|
||||
type: "text",
|
||||
text: `Search for the movie "${title}" using search_content with type="movie". Present the results showing: title, year, ratings, and the top torrents sorted by quality score with their magnet links. If results are found, also call get_watch_providers with the content_id to check streaming availability.`,
|
||||
text: `Search for the movie "${title}" using search_content with type="movie". Present the results with clickable magnet links using markdown format [📥 Download](magnet:...), include the content URL for more details, and show quality/size/seeders clearly. If results are found, also call get_watch_providers with the content_id to check streaming availability.`,
|
||||
},
|
||||
},
|
||||
],
|
||||
|
|
@ -22,14 +73,23 @@ export function registerPrompts(server: McpServer): void {
|
|||
server.prompt(
|
||||
"search_show",
|
||||
"Search for a TV show by title and get torrent download options",
|
||||
{ title: z.string().describe("TV show title to search for") },
|
||||
({ title }) => ({
|
||||
{
|
||||
title: z.string().describe("TV show title to search for"),
|
||||
season: z.number().optional().describe("Specific season number"),
|
||||
},
|
||||
({ title, season }) => ({
|
||||
messages: [
|
||||
{
|
||||
role: "user",
|
||||
content: {
|
||||
type: "text",
|
||||
text: `Search for the TV show "${title}" using search_content with type="show". Present the results showing: title, year, ratings, and the top torrents sorted by quality score with their magnet links.`,
|
||||
text: `Search for the TV show "${title}" using search_content with type="show"${season ? ` and season=${season}` : ""}. Present results grouped by episode with:
|
||||
- Episode identifier (e.g., S04E01)
|
||||
- Quality, size, and seeder count
|
||||
- Clickable magnet links using markdown: [📥 Download](magnet:...)
|
||||
- Content URL for browsing all seasons: [🔗 View all seasons](URL)
|
||||
- Recommendations for torrents with most seeders
|
||||
- Warnings if torrents have 0 seeders`,
|
||||
},
|
||||
},
|
||||
],
|
||||
|
|
|
|||
137
src/resources/presentation-guide.ts
Normal file
137
src/resources/presentation-guide.ts
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||
|
||||
export function registerPresentationGuideResource(server: McpServer): void {
|
||||
server.resource(
|
||||
"presentation-guide",
|
||||
"torrentclaw://presentation-guide",
|
||||
{
|
||||
description:
|
||||
"Best practices for presenting torrent search results to users in a user-friendly, actionable format",
|
||||
mimeType: "text/markdown",
|
||||
},
|
||||
async (uri) => {
|
||||
const guide = `# TorrentClaw Results Presentation Guide
|
||||
|
||||
## Critical Requirements
|
||||
|
||||
When presenting torrent search results to users, you MUST follow these practices to make results actionable and user-friendly:
|
||||
|
||||
### 1. Clickable Magnet Links
|
||||
|
||||
**ALWAYS** make magnet links clickable using markdown format:
|
||||
|
||||
✅ **CORRECT**:
|
||||
- [📥 Download](magnet:?xt=urn:btih:41159dc60579839533e04796df0e96bfa4864cb4&...)
|
||||
- [🧲 Magnet](magnet:?xt=urn:btih:41159dc60579839533e04796df0e96bfa4864cb4&...)
|
||||
|
||||
❌ **INCORRECT**:
|
||||
- magnet:?xt=urn:btih:41159dc60579839533e04796df0e96bfa4864cb4 (not clickable)
|
||||
- Showing only the info hash without full magnet URI
|
||||
|
||||
### 2. Content URL for Browsing
|
||||
|
||||
**ALWAYS** include the TorrentClaw content URL so users can explore all seasons/episodes:
|
||||
|
||||
✅ **CORRECT**:
|
||||
[🔗 View all seasons and episodes on TorrentClaw](https://torrentclaw.com/shows/entrevias-2022-91260)
|
||||
|
||||
This allows users to:
|
||||
- Browse all available seasons
|
||||
- See all torrents for each episode
|
||||
- Explore different quality options
|
||||
|
||||
### 3. User-Friendly Presentation Format
|
||||
|
||||
**For TV Shows** (especially when searching by season):
|
||||
|
||||
\`\`\`markdown
|
||||
### Entrevías - Temporada 4
|
||||
|
||||
**Episodio 1** (S04E01)
|
||||
- 720p HDTV • 879 MB • 6 seeders • [📥 Download](magnet:?xt=urn:btih:...)
|
||||
|
||||
**Episodio 2** (S04E02)
|
||||
- 1080p WEB-DL • 2.5 GB • 0 seeders ⚠️ No active seeders • [📥 Download](magnet:?xt=urn:btih:...)
|
||||
- 720p HDTV • 976 MB • 1 seeder • [📥 Download](magnet:?xt=urn:btih:...)
|
||||
|
||||
**Episodio 3** (S04E03)
|
||||
- 720p HDTV • 795 MB • 2 seeders • [📥 Download](magnet:?xt=urn:btih:...)
|
||||
|
||||
[🔗 View all seasons on TorrentClaw](https://torrentclaw.com/shows/...)
|
||||
\`\`\`
|
||||
|
||||
**For Movies**:
|
||||
|
||||
\`\`\`markdown
|
||||
### Inception (2010)
|
||||
IMDb: 8.8 | TMDB: 8.4
|
||||
|
||||
**Available Torrents:**
|
||||
|
||||
1. **2160p BluRay** • 15.2 GB • 147 seeders • [📥 Download](magnet:?xt=...)
|
||||
2. **1080p BluRay** • 2.0 GB • 847 seeders ⭐ Recommended • [📥 Download](magnet:?xt=...)
|
||||
3. **720p WEB-DL** • 1.2 GB • 234 seeders • [📥 Download](magnet:?xt=...)
|
||||
|
||||
[🔗 View on TorrentClaw](https://torrentclaw.com/movies/...)
|
||||
\`\`\`
|
||||
|
||||
### 4. Helpful User Guidance
|
||||
|
||||
Provide context and recommendations:
|
||||
|
||||
- ✅ Recommend torrents with most seeders
|
||||
- ✅ Warn when torrents have 0 seeders: "⚠️ No active seeders"
|
||||
- ✅ Mark best option: "⭐ Recommended" (based on seeders + quality)
|
||||
- ✅ Suggest alternatives if requested season has no seeders
|
||||
- ✅ Offer to search different quality/season
|
||||
|
||||
### 5. What NOT to Do
|
||||
|
||||
❌ **Never** present results in plain text tables without clickable links
|
||||
❌ **Never** show truncated magnet links
|
||||
❌ **Never** omit the content URL
|
||||
❌ **Never** show info hashes without the full magnet URI
|
||||
❌ **Never** present results without indicating seeder count
|
||||
|
||||
### 6. Example of Good vs Bad Presentation
|
||||
|
||||
**❌ BAD** (what user reported as not practical):
|
||||
\`\`\`
|
||||
┌──────────┬───────────┬────────┬───────────┬──────────────────────────────────┐
|
||||
│ Episodio │ Calidad │ Tamaño │ Seeders │ Magnet │
|
||||
├──────────┼───────────┼────────┼───────────┼──────────────────────────────────┤
|
||||
│ S04E01 │ 720p HDTV │ 879 MB │ 6 seeders │ magnet:?xt=urn:btih:41159dc... │
|
||||
└──────────┴───────────┴────────┴───────────┴──────────────────────────────────┘
|
||||
\`\`\`
|
||||
|
||||
**✅ GOOD**:
|
||||
\`\`\`markdown
|
||||
**S04E01**
|
||||
720p HDTV • 879 MB • 6 seeders • [📥 Download](magnet:?xt=urn:btih:41159dc60579839533e04796df0e96bfa4864cb4&...)
|
||||
|
||||
[🔗 View all episodes on TorrentClaw](https://torrentclaw.com/shows/entrevias-2022-91260)
|
||||
\`\`\`
|
||||
|
||||
## Summary
|
||||
|
||||
The key is to make results **actionable**: users should be able to:
|
||||
1. Click magnet links to start downloading immediately
|
||||
2. Click content URL to explore more options
|
||||
3. Quickly identify which torrents are best (seeders)
|
||||
4. Understand warnings (no seeders)
|
||||
|
||||
**Remember**: You're not just displaying data, you're helping users take action.
|
||||
`;
|
||||
|
||||
return {
|
||||
contents: [
|
||||
{
|
||||
uri: uri.href,
|
||||
mimeType: "text/markdown",
|
||||
text: guide,
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
@ -10,7 +10,7 @@ export function registerSearchContent(
|
|||
): void {
|
||||
server.tool(
|
||||
"search_content",
|
||||
"Search for movies and TV shows by title, genre, year, rating, or quality. Returns matching content with metadata (title, year, genres, IMDb/TMDB ratings) and torrent download options (magnet links, quality, seeders, file size). This is the primary tool — use it first when a user asks to find, download, or learn about a movie or TV show. Results include a content_id needed by get_watch_providers and get_credits. For TV shows, you can filter by season/episode. Season/episode can also be auto-detected from the query (e.g. 'Bluey s01e05').",
|
||||
"Search for movies and TV shows by title, genre, year, rating, or quality. Returns matching content with metadata (title, year, genres, IMDb/TMDB ratings) and torrent download options (magnet links, quality, seeders, file size). This is the primary tool — use it first when a user asks to find, download, or learn about a movie or TV show. Results include a content_id needed by get_watch_providers and get_credits. For TV shows, you can filter by season/episode. Season/episode can also be auto-detected from the query (e.g. 'Bluey s01e05'). IMPORTANT: When presenting results to users, make magnet links clickable using markdown format [Download](magnet:?xt=...), include the contentUrl for browsing all seasons/episodes, and present the information in a user-friendly format rather than raw tables.",
|
||||
{
|
||||
query: z
|
||||
.string()
|
||||
|
|
@ -176,7 +176,11 @@ export function registerSearchContent(
|
|||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: formatSearchResults(data, { compact: params.compact }),
|
||||
text: formatSearchResults(data, {
|
||||
compact: params.compact,
|
||||
season: params.season,
|
||||
episode: params.episode,
|
||||
}),
|
||||
},
|
||||
],
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue