transistor-mcp
Advanced tools
@@ -186,3 +186,6 @@ import axios from "axios"; | ||
| } | ||
| // Delete by show_id + email | ||
| // Delete by show_id + email — both are required for this branch. | ||
| if (!args.show_id || !args.email) { | ||
| throw new Error("delete_subscriber requires either subscriber_id, or both show_id and email."); | ||
| } | ||
| const response = await this.api.delete("/v1/subscribers", { | ||
@@ -189,0 +192,0 @@ params: { show_id: args.show_id, email: args.email }, |
+21
-3
@@ -11,5 +11,18 @@ /** | ||
| /** | ||
| * Verify that y/m/d form a real calendar date (e.g. reject 2025-13-40 or | ||
| * 2025-02-30). Returns true only when the numbers round-trip through Date. | ||
| */ | ||
| function isRealDate(year, month, day) { | ||
| if (month < 1 || month > 12 || day < 1 || day > 31) | ||
| return false; | ||
| const d = new Date(Date.UTC(year, month - 1, day)); | ||
| return (d.getUTCFullYear() === year && | ||
| d.getUTCMonth() === month - 1 && | ||
| d.getUTCDate() === day); | ||
| } | ||
| /** | ||
| * Convert a date string to Transistor's dd-mm-yyyy format. | ||
| * Accepts either ISO (yyyy-mm-dd) or already-correct (dd-mm-yyyy). | ||
| * Returns undefined for undefined input. | ||
| * Throws on a syntactically-ISO string that is not a real calendar date. | ||
| */ | ||
@@ -20,7 +33,12 @@ export function toTransistorDate(date) { | ||
| if (ISO_DATE_RE.test(date)) { | ||
| const [year, month, day] = date.split("-"); | ||
| return `${day}-${month}-${year}`; | ||
| const [year, month, day] = date.split("-").map(Number); | ||
| if (!isRealDate(year, month, day)) { | ||
| throw new Error(`Invalid date "${date}": expected a real calendar date in yyyy-mm-dd format.`); | ||
| } | ||
| const [y, m, d] = date.split("-"); | ||
| return `${d}-${m}-${y}`; | ||
| } | ||
| if (TRANSISTOR_DATE_RE.test(date)) { | ||
| // Already in Transistor format | ||
| // Already in Transistor dd-mm-yyyy format (ambiguous: dd-mm vs mm-dd is | ||
| // not validated here — passed through and resolved server-side). | ||
| return date; | ||
@@ -27,0 +45,0 @@ } |
@@ -6,2 +6,14 @@ import { McpError, ErrorCode } from "@modelcontextprotocol/sdk/types.js"; | ||
| /** | ||
| * Extract a concise, human-readable message from an unknown thrown value, | ||
| * preferring the Transistor API's status/message when it is an Axios error. | ||
| */ | ||
| function errorMessage(e) { | ||
| if (axios.isAxiosError(e)) { | ||
| const status = e.response?.status; | ||
| const apiMsg = e.response?.data?.error ?? e.message; | ||
| return status ? `HTTP ${status}: ${apiMsg}` : apiMsg; | ||
| } | ||
| return e instanceof Error ? e.message : String(e); | ||
| } | ||
| /** | ||
| * Strip heavy fields from episode responses to reduce token usage. | ||
@@ -992,4 +1004,7 @@ * Removes HTML descriptions, embed codes, and formatted variants | ||
| if (days.length >= 14) { | ||
| const lastWeek = days.slice(-7); | ||
| const prevWeek = days.slice(-14, -7); | ||
| // Sort chronologically before slicing — the API does not guarantee | ||
| // ordering, and ISO yyyy-mm-dd strings sort lexicographically by date. | ||
| const byDate = [...days].sort((a, b) => a.date.localeCompare(b.date)); | ||
| const lastWeek = byDate.slice(-7); | ||
| const prevWeek = byDate.slice(-14, -7); | ||
| const lastTotal = lastWeek.reduce((s, d) => s + d.downloads, 0); | ||
@@ -1025,3 +1040,5 @@ const prevTotal = prevWeek.reduce((s, d) => s + d.downloads, 0); | ||
| const results = await Promise.all(args.episode_ids.map(async (episode_id) => { | ||
| // Fetch analytics and episode metadata in parallel | ||
| // Fetch analytics and episode metadata in parallel. Capture failures | ||
| // per-episode so one bad episode does not abort the whole comparison, | ||
| // but surface the error instead of silently reporting zero downloads. | ||
| const [analyticsData, episodeData] = await Promise.all([ | ||
@@ -1035,3 +1052,3 @@ this.apiClient | ||
| }) | ||
| .catch(() => null), | ||
| .catch((e) => ({ __error: errorMessage(e) })), | ||
| this.apiClient | ||
@@ -1041,5 +1058,8 @@ .getEpisode({ episode_id }) | ||
| ]); | ||
| const days = analyticsData | ||
| ? this.extractDownloads(analyticsData) | ||
| : []; | ||
| const analyticsError = analyticsData && typeof analyticsData === "object" && "__error" in analyticsData | ||
| ? analyticsData.__error | ||
| : null; | ||
| const days = analyticsError | ||
| ? [] | ||
| : this.extractDownloads(analyticsData); | ||
| const total = days.reduce((s, d) => s + d.downloads, 0); | ||
@@ -1068,2 +1088,3 @@ const avg = days.length > 0 ? total / days.length : 0; | ||
| days_since_publish: daysSincePublish, | ||
| ...(analyticsError ? { error: analyticsError } : {}), | ||
| }; | ||
@@ -1070,0 +1091,0 @@ })); |
+5
-4
| { | ||
| "name": "transistor-mcp", | ||
| "version": "0.2.0", | ||
| "description": "MCP server for the Transistor.fm podcast hosting API — manage shows, episodes, analytics, download summaries, transcripts, subscribers, and webhooks.", | ||
| "version": "0.3.0", | ||
| "description": "MCP server for the Transistor.fm podcast hosting API \u2014 manage shows, episodes, analytics, download summaries, transcripts, subscribers, and webhooks.", | ||
| "type": "module", | ||
@@ -45,4 +45,5 @@ "mcpName": "io.github.conorbronsdon/transistor-mcp", | ||
| "dependencies": { | ||
| "@modelcontextprotocol/sdk": "0.6.0", | ||
| "axios": "^1.7.9" | ||
| "@modelcontextprotocol/sdk": "1.25.2", | ||
| "axios": "^1.13.5", | ||
| "form-data": "^4.0.4" | ||
| }, | ||
@@ -49,0 +50,0 @@ "devDependencies": { |
+5
-1
@@ -21,4 +21,8 @@ <div align="center"> | ||
| > **Fork notice:** This is a maintained fork of [gxjansen/Transistor-MCP](https://github.com/gxjansen/Transistor-MCP) by [Guido X Jansen](https://github.com/gxjansen), who built the original server. This fork adds full API parity (all documented params, response trimming, search), the `get_download_summary` and `compare_episodes` tools, ISO date handling, and transcript support. The MIT license and original copyright are preserved in [LICENSE](LICENSE). | ||
| > **Fork notice:** The original server was built by [Guido X Jansen](https://github.com/gxjansen) ([gxjansen/Transistor-MCP](https://github.com/gxjansen/Transistor-MCP)). The full-API-parity pass (all documented params, response trimming, search), the `get_download_summary` and `compare_episodes` analytics tools, ISO date handling, and transcript support were contributed here and have since been merged upstream. This repository is a packaged, npm-published build (`transistor-mcp`) kept in sync with upstream. The MIT license and original copyright are preserved in [LICENSE](LICENSE). | ||
| <a href="https://glama.ai/mcp/servers/conorbronsdon/Transistor-MCP"> | ||
| <img width="380" height="200" src="https://glama.ai/mcp/servers/conorbronsdon/Transistor-MCP/badge" alt="Transistor-MCP MCP server" /> | ||
| </a> | ||
| ## About | ||
@@ -25,0 +29,0 @@ |
85404
2.43%1562
2.76%548
0.74%3
50%+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
- Removed
- Removed
Updated