@hasna/microservices 0.0.16 → 0.0.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (293) hide show
  1. package/README.md +143 -23
  2. package/bin/index.js +711 -13998
  3. package/bin/mcp.js +228 -10987
  4. package/dist/index.js +166 -10056
  5. package/package.json +25 -25
  6. package/microservices/microservice-ads/package.json +0 -28
  7. package/microservices/microservice-ads/src/cli/index.ts +0 -605
  8. package/microservices/microservice-ads/src/db/campaigns.ts +0 -797
  9. package/microservices/microservice-ads/src/db/database.ts +0 -91
  10. package/microservices/microservice-ads/src/db/migrations.ts +0 -60
  11. package/microservices/microservice-ads/src/index.ts +0 -39
  12. package/microservices/microservice-ads/src/mcp/index.ts +0 -480
  13. package/microservices/microservice-analytics/package.json +0 -28
  14. package/microservices/microservice-analytics/src/cli/index.ts +0 -373
  15. package/microservices/microservice-analytics/src/db/analytics.ts +0 -564
  16. package/microservices/microservice-analytics/src/db/database.ts +0 -91
  17. package/microservices/microservice-analytics/src/db/migrations.ts +0 -50
  18. package/microservices/microservice-analytics/src/index.ts +0 -37
  19. package/microservices/microservice-analytics/src/mcp/index.ts +0 -334
  20. package/microservices/microservice-assets/package.json +0 -28
  21. package/microservices/microservice-assets/src/cli/index.ts +0 -375
  22. package/microservices/microservice-assets/src/db/assets.ts +0 -370
  23. package/microservices/microservice-assets/src/db/database.ts +0 -91
  24. package/microservices/microservice-assets/src/db/migrations.ts +0 -51
  25. package/microservices/microservice-assets/src/index.ts +0 -32
  26. package/microservices/microservice-assets/src/mcp/index.ts +0 -346
  27. package/microservices/microservice-bookkeeping/package.json +0 -28
  28. package/microservices/microservice-bookkeeping/src/cli/index.ts +0 -386
  29. package/microservices/microservice-bookkeeping/src/db/bookkeeping.ts +0 -591
  30. package/microservices/microservice-bookkeeping/src/db/database.ts +0 -91
  31. package/microservices/microservice-bookkeeping/src/db/migrations.ts +0 -52
  32. package/microservices/microservice-bookkeeping/src/index.ts +0 -32
  33. package/microservices/microservice-bookkeeping/src/mcp/index.ts +0 -284
  34. package/microservices/microservice-calendar/package.json +0 -28
  35. package/microservices/microservice-calendar/src/cli/index.ts +0 -287
  36. package/microservices/microservice-calendar/src/db/calendar.ts +0 -328
  37. package/microservices/microservice-calendar/src/db/database.ts +0 -91
  38. package/microservices/microservice-calendar/src/db/migrations.ts +0 -47
  39. package/microservices/microservice-calendar/src/index.ts +0 -24
  40. package/microservices/microservice-calendar/src/mcp/index.ts +0 -226
  41. package/microservices/microservice-company/package.json +0 -28
  42. package/microservices/microservice-company/src/cli/index.ts +0 -1126
  43. package/microservices/microservice-company/src/db/company.ts +0 -854
  44. package/microservices/microservice-company/src/db/database.ts +0 -91
  45. package/microservices/microservice-company/src/db/migrations.ts +0 -214
  46. package/microservices/microservice-company/src/db/workflow-migrations.ts +0 -44
  47. package/microservices/microservice-company/src/index.ts +0 -60
  48. package/microservices/microservice-company/src/lib/audit.ts +0 -168
  49. package/microservices/microservice-company/src/lib/finance.ts +0 -299
  50. package/microservices/microservice-company/src/lib/settings.ts +0 -85
  51. package/microservices/microservice-company/src/lib/workflows.ts +0 -698
  52. package/microservices/microservice-company/src/mcp/index.ts +0 -991
  53. package/microservices/microservice-compliance/package.json +0 -28
  54. package/microservices/microservice-compliance/src/cli/index.ts +0 -467
  55. package/microservices/microservice-compliance/src/db/compliance.ts +0 -633
  56. package/microservices/microservice-compliance/src/db/database.ts +0 -91
  57. package/microservices/microservice-compliance/src/db/migrations.ts +0 -63
  58. package/microservices/microservice-compliance/src/index.ts +0 -46
  59. package/microservices/microservice-compliance/src/mcp/index.ts +0 -438
  60. package/microservices/microservice-contacts/package.json +0 -28
  61. package/microservices/microservice-contacts/src/cli/index.ts +0 -393
  62. package/microservices/microservice-contacts/src/db/companies.ts +0 -167
  63. package/microservices/microservice-contacts/src/db/contacts.ts +0 -249
  64. package/microservices/microservice-contacts/src/db/database.ts +0 -91
  65. package/microservices/microservice-contacts/src/db/migrations.ts +0 -71
  66. package/microservices/microservice-contacts/src/db/relationships.ts +0 -53
  67. package/microservices/microservice-contacts/src/index.ts +0 -42
  68. package/microservices/microservice-contacts/src/mcp/index.ts +0 -303
  69. package/microservices/microservice-contracts/package.json +0 -28
  70. package/microservices/microservice-contracts/src/cli/index.ts +0 -770
  71. package/microservices/microservice-contracts/src/db/contracts.ts +0 -925
  72. package/microservices/microservice-contracts/src/db/database.ts +0 -91
  73. package/microservices/microservice-contracts/src/db/migrations.ts +0 -141
  74. package/microservices/microservice-contracts/src/index.ts +0 -43
  75. package/microservices/microservice-contracts/src/mcp/index.ts +0 -617
  76. package/microservices/microservice-crm/package.json +0 -28
  77. package/microservices/microservice-crm/src/cli/index.ts +0 -396
  78. package/microservices/microservice-crm/src/db/database.ts +0 -91
  79. package/microservices/microservice-crm/src/db/migrations.ts +0 -66
  80. package/microservices/microservice-crm/src/db/pipeline.ts +0 -397
  81. package/microservices/microservice-crm/src/index.ts +0 -34
  82. package/microservices/microservice-crm/src/mcp/index.ts +0 -294
  83. package/microservices/microservice-documents/package.json +0 -28
  84. package/microservices/microservice-documents/src/cli/index.ts +0 -246
  85. package/microservices/microservice-documents/src/db/database.ts +0 -91
  86. package/microservices/microservice-documents/src/db/documents.ts +0 -316
  87. package/microservices/microservice-documents/src/db/migrations.ts +0 -49
  88. package/microservices/microservice-documents/src/index.ts +0 -24
  89. package/microservices/microservice-documents/src/mcp/index.ts +0 -202
  90. package/microservices/microservice-domains/package.json +0 -28
  91. package/microservices/microservice-domains/src/cli/index.ts +0 -1111
  92. package/microservices/microservice-domains/src/db/database.ts +0 -91
  93. package/microservices/microservice-domains/src/db/domains.ts +0 -1164
  94. package/microservices/microservice-domains/src/db/migrations.ts +0 -60
  95. package/microservices/microservice-domains/src/index.ts +0 -65
  96. package/microservices/microservice-domains/src/lib/brandsight.ts +0 -350
  97. package/microservices/microservice-domains/src/lib/godaddy.ts +0 -338
  98. package/microservices/microservice-domains/src/lib/namecheap.ts +0 -262
  99. package/microservices/microservice-domains/src/lib/registrar.ts +0 -355
  100. package/microservices/microservice-domains/src/mcp/index.ts +0 -781
  101. package/microservices/microservice-expenses/package.json +0 -28
  102. package/microservices/microservice-expenses/src/cli/index.ts +0 -267
  103. package/microservices/microservice-expenses/src/db/database.ts +0 -91
  104. package/microservices/microservice-expenses/src/db/expenses.ts +0 -345
  105. package/microservices/microservice-expenses/src/db/migrations.ts +0 -45
  106. package/microservices/microservice-expenses/src/index.ts +0 -25
  107. package/microservices/microservice-expenses/src/mcp/index.ts +0 -196
  108. package/microservices/microservice-habits/package.json +0 -28
  109. package/microservices/microservice-habits/src/cli/index.ts +0 -315
  110. package/microservices/microservice-habits/src/db/database.ts +0 -91
  111. package/microservices/microservice-habits/src/db/habits.ts +0 -451
  112. package/microservices/microservice-habits/src/db/migrations.ts +0 -46
  113. package/microservices/microservice-habits/src/index.ts +0 -31
  114. package/microservices/microservice-habits/src/mcp/index.ts +0 -313
  115. package/microservices/microservice-health/package.json +0 -28
  116. package/microservices/microservice-health/src/cli/index.ts +0 -484
  117. package/microservices/microservice-health/src/db/database.ts +0 -91
  118. package/microservices/microservice-health/src/db/health.ts +0 -708
  119. package/microservices/microservice-health/src/db/migrations.ts +0 -70
  120. package/microservices/microservice-health/src/index.ts +0 -63
  121. package/microservices/microservice-health/src/mcp/index.ts +0 -437
  122. package/microservices/microservice-hiring/package.json +0 -28
  123. package/microservices/microservice-hiring/src/cli/index.ts +0 -741
  124. package/microservices/microservice-hiring/src/db/database.ts +0 -91
  125. package/microservices/microservice-hiring/src/db/hiring.ts +0 -1085
  126. package/microservices/microservice-hiring/src/db/migrations.ts +0 -89
  127. package/microservices/microservice-hiring/src/index.ts +0 -80
  128. package/microservices/microservice-hiring/src/lib/scoring.ts +0 -206
  129. package/microservices/microservice-hiring/src/mcp/index.ts +0 -709
  130. package/microservices/microservice-inventory/package.json +0 -28
  131. package/microservices/microservice-inventory/src/cli/index.ts +0 -365
  132. package/microservices/microservice-inventory/src/db/database.ts +0 -91
  133. package/microservices/microservice-inventory/src/db/inventory.ts +0 -393
  134. package/microservices/microservice-inventory/src/db/migrations.ts +0 -54
  135. package/microservices/microservice-inventory/src/index.ts +0 -28
  136. package/microservices/microservice-inventory/src/mcp/index.ts +0 -250
  137. package/microservices/microservice-invoices/dashboard/index.html +0 -12
  138. package/microservices/microservice-invoices/dashboard/package.json +0 -29
  139. package/microservices/microservice-invoices/dashboard/tsconfig.json +0 -14
  140. package/microservices/microservice-invoices/dashboard/vite.config.ts +0 -15
  141. package/microservices/microservice-invoices/package.json +0 -31
  142. package/microservices/microservice-invoices/src/cli/index.ts +0 -308
  143. package/microservices/microservice-invoices/src/db/business.ts +0 -241
  144. package/microservices/microservice-invoices/src/db/clients.ts +0 -127
  145. package/microservices/microservice-invoices/src/db/database.ts +0 -91
  146. package/microservices/microservice-invoices/src/db/invoices.ts +0 -345
  147. package/microservices/microservice-invoices/src/db/migrations.ts +0 -184
  148. package/microservices/microservice-invoices/src/index.ts +0 -56
  149. package/microservices/microservice-invoices/src/mcp/index.ts +0 -242
  150. package/microservices/microservice-invoices/src/server/index.ts +0 -162
  151. package/microservices/microservice-leads/package.json +0 -28
  152. package/microservices/microservice-leads/src/cli/index.ts +0 -596
  153. package/microservices/microservice-leads/src/db/database.ts +0 -91
  154. package/microservices/microservice-leads/src/db/leads.ts +0 -520
  155. package/microservices/microservice-leads/src/db/lists.ts +0 -151
  156. package/microservices/microservice-leads/src/db/migrations.ts +0 -93
  157. package/microservices/microservice-leads/src/index.ts +0 -65
  158. package/microservices/microservice-leads/src/lib/enrichment.ts +0 -202
  159. package/microservices/microservice-leads/src/lib/scoring.ts +0 -134
  160. package/microservices/microservice-leads/src/mcp/index.ts +0 -533
  161. package/microservices/microservice-notes/package.json +0 -28
  162. package/microservices/microservice-notes/src/cli/index.ts +0 -63
  163. package/microservices/microservice-notes/src/db/database.ts +0 -91
  164. package/microservices/microservice-notes/src/db/migrations.ts +0 -40
  165. package/microservices/microservice-notes/src/db/notes.ts +0 -114
  166. package/microservices/microservice-notes/src/index.ts +0 -2
  167. package/microservices/microservice-notes/src/mcp/index.ts +0 -37
  168. package/microservices/microservice-notifications/package.json +0 -28
  169. package/microservices/microservice-notifications/src/cli/index.ts +0 -349
  170. package/microservices/microservice-notifications/src/db/database.ts +0 -91
  171. package/microservices/microservice-notifications/src/db/migrations.ts +0 -62
  172. package/microservices/microservice-notifications/src/db/notifications.ts +0 -509
  173. package/microservices/microservice-notifications/src/index.ts +0 -41
  174. package/microservices/microservice-notifications/src/mcp/index.ts +0 -422
  175. package/microservices/microservice-payments/package.json +0 -28
  176. package/microservices/microservice-payments/src/cli/index.ts +0 -609
  177. package/microservices/microservice-payments/src/db/database.ts +0 -91
  178. package/microservices/microservice-payments/src/db/migrations.ts +0 -81
  179. package/microservices/microservice-payments/src/db/payments.ts +0 -1204
  180. package/microservices/microservice-payments/src/index.ts +0 -51
  181. package/microservices/microservice-payments/src/mcp/index.ts +0 -683
  182. package/microservices/microservice-payroll/package.json +0 -28
  183. package/microservices/microservice-payroll/src/cli/index.ts +0 -643
  184. package/microservices/microservice-payroll/src/db/database.ts +0 -91
  185. package/microservices/microservice-payroll/src/db/migrations.ts +0 -95
  186. package/microservices/microservice-payroll/src/db/payroll.ts +0 -1377
  187. package/microservices/microservice-payroll/src/index.ts +0 -48
  188. package/microservices/microservice-payroll/src/mcp/index.ts +0 -666
  189. package/microservices/microservice-products/package.json +0 -28
  190. package/microservices/microservice-products/src/cli/index.ts +0 -416
  191. package/microservices/microservice-products/src/db/categories.ts +0 -154
  192. package/microservices/microservice-products/src/db/database.ts +0 -91
  193. package/microservices/microservice-products/src/db/migrations.ts +0 -58
  194. package/microservices/microservice-products/src/db/pricing-tiers.ts +0 -66
  195. package/microservices/microservice-products/src/db/products.ts +0 -452
  196. package/microservices/microservice-products/src/index.ts +0 -53
  197. package/microservices/microservice-products/src/mcp/index.ts +0 -453
  198. package/microservices/microservice-projects/package.json +0 -28
  199. package/microservices/microservice-projects/src/cli/index.ts +0 -480
  200. package/microservices/microservice-projects/src/db/database.ts +0 -91
  201. package/microservices/microservice-projects/src/db/migrations.ts +0 -65
  202. package/microservices/microservice-projects/src/db/projects.ts +0 -715
  203. package/microservices/microservice-projects/src/index.ts +0 -57
  204. package/microservices/microservice-projects/src/mcp/index.ts +0 -501
  205. package/microservices/microservice-proposals/package.json +0 -28
  206. package/microservices/microservice-proposals/src/cli/index.ts +0 -400
  207. package/microservices/microservice-proposals/src/db/database.ts +0 -91
  208. package/microservices/microservice-proposals/src/db/migrations.ts +0 -52
  209. package/microservices/microservice-proposals/src/db/proposals.ts +0 -532
  210. package/microservices/microservice-proposals/src/index.ts +0 -37
  211. package/microservices/microservice-proposals/src/mcp/index.ts +0 -375
  212. package/microservices/microservice-reading/package.json +0 -28
  213. package/microservices/microservice-reading/src/cli/index.ts +0 -464
  214. package/microservices/microservice-reading/src/db/database.ts +0 -91
  215. package/microservices/microservice-reading/src/db/migrations.ts +0 -59
  216. package/microservices/microservice-reading/src/db/reading.ts +0 -524
  217. package/microservices/microservice-reading/src/index.ts +0 -51
  218. package/microservices/microservice-reading/src/mcp/index.ts +0 -368
  219. package/microservices/microservice-shipping/package.json +0 -28
  220. package/microservices/microservice-shipping/src/cli/index.ts +0 -606
  221. package/microservices/microservice-shipping/src/db/database.ts +0 -91
  222. package/microservices/microservice-shipping/src/db/migrations.ts +0 -69
  223. package/microservices/microservice-shipping/src/db/shipping.ts +0 -1093
  224. package/microservices/microservice-shipping/src/index.ts +0 -53
  225. package/microservices/microservice-shipping/src/mcp/index.ts +0 -533
  226. package/microservices/microservice-social/package.json +0 -29
  227. package/microservices/microservice-social/src/cli/index.ts +0 -1583
  228. package/microservices/microservice-social/src/db/database.ts +0 -91
  229. package/microservices/microservice-social/src/db/migrations.ts +0 -160
  230. package/microservices/microservice-social/src/db/social.ts +0 -1076
  231. package/microservices/microservice-social/src/index.ts +0 -46
  232. package/microservices/microservice-social/src/lib/audience.ts +0 -353
  233. package/microservices/microservice-social/src/lib/content-ai.ts +0 -278
  234. package/microservices/microservice-social/src/lib/media.ts +0 -311
  235. package/microservices/microservice-social/src/lib/mentions.ts +0 -434
  236. package/microservices/microservice-social/src/lib/metrics-sync.ts +0 -264
  237. package/microservices/microservice-social/src/lib/publisher.ts +0 -377
  238. package/microservices/microservice-social/src/lib/scheduler.ts +0 -229
  239. package/microservices/microservice-social/src/lib/sentiment.ts +0 -256
  240. package/microservices/microservice-social/src/lib/threads.ts +0 -291
  241. package/microservices/microservice-social/src/mcp/index.ts +0 -1425
  242. package/microservices/microservice-social/src/server/index.ts +0 -441
  243. package/microservices/microservice-subscriptions/package.json +0 -28
  244. package/microservices/microservice-subscriptions/src/cli/index.ts +0 -715
  245. package/microservices/microservice-subscriptions/src/db/database.ts +0 -91
  246. package/microservices/microservice-subscriptions/src/db/migrations.ts +0 -125
  247. package/microservices/microservice-subscriptions/src/db/subscriptions.ts +0 -1256
  248. package/microservices/microservice-subscriptions/src/index.ts +0 -41
  249. package/microservices/microservice-subscriptions/src/mcp/index.ts +0 -631
  250. package/microservices/microservice-timesheets/package.json +0 -28
  251. package/microservices/microservice-timesheets/src/cli/index.ts +0 -373
  252. package/microservices/microservice-timesheets/src/db/database.ts +0 -91
  253. package/microservices/microservice-timesheets/src/db/locale.ts +0 -217
  254. package/microservices/microservice-timesheets/src/db/migrations.ts +0 -74
  255. package/microservices/microservice-timesheets/src/db/timesheets.ts +0 -447
  256. package/microservices/microservice-timesheets/src/index.ts +0 -44
  257. package/microservices/microservice-timesheets/src/mcp/index.ts +0 -269
  258. package/microservices/microservice-transcriber/package.json +0 -29
  259. package/microservices/microservice-transcriber/src/cli/index.ts +0 -1593
  260. package/microservices/microservice-transcriber/src/db/annotations.ts +0 -37
  261. package/microservices/microservice-transcriber/src/db/comments.ts +0 -166
  262. package/microservices/microservice-transcriber/src/db/database.ts +0 -91
  263. package/microservices/microservice-transcriber/src/db/migrations.ts +0 -118
  264. package/microservices/microservice-transcriber/src/db/proofread.ts +0 -119
  265. package/microservices/microservice-transcriber/src/db/transcripts.ts +0 -395
  266. package/microservices/microservice-transcriber/src/index.ts +0 -43
  267. package/microservices/microservice-transcriber/src/lib/config.ts +0 -77
  268. package/microservices/microservice-transcriber/src/lib/diff.ts +0 -91
  269. package/microservices/microservice-transcriber/src/lib/downloader.ts +0 -638
  270. package/microservices/microservice-transcriber/src/lib/feeds.ts +0 -62
  271. package/microservices/microservice-transcriber/src/lib/live.ts +0 -94
  272. package/microservices/microservice-transcriber/src/lib/notion.ts +0 -129
  273. package/microservices/microservice-transcriber/src/lib/proofread.ts +0 -296
  274. package/microservices/microservice-transcriber/src/lib/providers.ts +0 -713
  275. package/microservices/microservice-transcriber/src/lib/summarizer.ts +0 -147
  276. package/microservices/microservice-transcriber/src/lib/translator.ts +0 -75
  277. package/microservices/microservice-transcriber/src/lib/webhook.ts +0 -37
  278. package/microservices/microservice-transcriber/src/mcp/index.ts +0 -1330
  279. package/microservices/microservice-transcriber/src/server/index.ts +0 -199
  280. package/microservices/microservice-travel/package.json +0 -28
  281. package/microservices/microservice-travel/src/cli/index.ts +0 -505
  282. package/microservices/microservice-travel/src/db/database.ts +0 -91
  283. package/microservices/microservice-travel/src/db/migrations.ts +0 -77
  284. package/microservices/microservice-travel/src/db/travel.ts +0 -802
  285. package/microservices/microservice-travel/src/index.ts +0 -60
  286. package/microservices/microservice-travel/src/mcp/index.ts +0 -495
  287. package/microservices/microservice-wiki/package.json +0 -28
  288. package/microservices/microservice-wiki/src/cli/index.ts +0 -345
  289. package/microservices/microservice-wiki/src/db/database.ts +0 -91
  290. package/microservices/microservice-wiki/src/db/migrations.ts +0 -55
  291. package/microservices/microservice-wiki/src/db/wiki.ts +0 -395
  292. package/microservices/microservice-wiki/src/index.ts +0 -32
  293. package/microservices/microservice-wiki/src/mcp/index.ts +0 -344
@@ -1,1330 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
4
- import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
5
- import { z } from "zod";
6
- import {
7
- createTranscript,
8
- getTranscript,
9
- updateTranscript,
10
- deleteTranscript,
11
- listTranscripts,
12
- searchTranscripts,
13
- countTranscripts,
14
- renameSpeakers,
15
- findBySourceUrl,
16
- searchWithContext,
17
- addTags,
18
- removeTags,
19
- getTags,
20
- listAllTags,
21
- listTranscriptsByTag,
22
- type TranscriptProvider,
23
- type TranscriptStatus,
24
- type TranscriptSourceType,
25
- } from "../db/transcripts.js";
26
- import { prepareAudio, detectSourceType, getVideoInfo, downloadAudio, downloadVideo, createClip, isPlaylistUrl, getPlaylistUrls, fetchComments, type TrimOptions } from "../lib/downloader.js";
27
- import { listComments, getTopComments, searchComments, getCommentStats, importComments } from "../db/comments.js";
28
- import { getConfig, setConfig, resetConfig } from "../lib/config.js";
29
- import { summarizeText, extractHighlights, generateMeetingNotes, getDefaultSummaryProvider } from "../lib/summarizer.js";
30
- import { translateText } from "../lib/translator.js";
31
- import { fetchFeedEpisodes } from "../lib/feeds.js";
32
- import { createAnnotation, listAnnotations, deleteAnnotation } from "../db/annotations.js";
33
- import { wordDiff, diffStats, formatDiff } from "../lib/diff.js";
34
- import { transcribeFile, checkProviders, toSrt, toVtt, toAss, toMarkdown, segmentByChapters, formatWithConfidence } from "../lib/providers.js";
35
- import { proofreadTranscript, listIssues, applySuggestion, dismissIssue, getProofreadStats, exportAnnotated, type IssueType } from "../lib/proofread.js";
36
-
37
- const server = new McpServer({
38
- name: "microservice-transcriber",
39
- version: "0.0.1",
40
- });
41
-
42
- // ---------------------------------------------------------------------------
43
- // transcribe
44
- // ---------------------------------------------------------------------------
45
-
46
- server.registerTool(
47
- "transcribe",
48
- {
49
- title: "Transcribe Audio or Video",
50
- description:
51
- "Transcribe a local audio/video file or a URL (YouTube, Vimeo, Wistia, or any media URL). Uses ElevenLabs by default, or OpenAI Whisper.",
52
- inputSchema: {
53
- source: z.string().describe("File path or URL to transcribe"),
54
- provider: z
55
- .enum(["elevenlabs", "openai", "deepgram"])
56
- .optional()
57
- .describe("Transcription provider (default: elevenlabs)"),
58
- language: z
59
- .string()
60
- .optional()
61
- .describe("Language code e.g. 'en', 'fr'. Auto-detected if omitted."),
62
- title: z.string().optional().describe("Optional title for the transcript"),
63
- start: z.number().optional().describe("Start time in seconds — trim audio before transcribing"),
64
- end: z.number().optional().describe("End time in seconds — trim audio before transcribing"),
65
- diarize: z.boolean().optional().describe("Identify different speakers — ElevenLabs only"),
66
- vocab: z.array(z.string()).optional().describe("Custom vocabulary hints for accuracy (e.g. ['Karpathy', 'MicroGPT'])"),
67
- force: z.boolean().optional().describe("Re-transcribe even if URL already exists in DB"),
68
- comments: z.boolean().optional().describe("Also fetch and store YouTube/Vimeo comments"),
69
- },
70
- },
71
- async ({ source, provider = "elevenlabs", language, title, start, end, diarize, vocab, force, comments: fetchCommentsFlag }) => {
72
- // Duplicate detection
73
- if (!force) {
74
- const existing = findBySourceUrl(source);
75
- if (existing) {
76
- return { content: [{ type: "text", text: JSON.stringify({ duplicate: true, existing_id: existing.id, title: existing.title, message: "Already transcribed. Use force=true to re-transcribe." }, null, 2) }] };
77
- }
78
- }
79
-
80
- const providers = checkProviders();
81
-
82
- if (provider === "elevenlabs" && !providers.elevenlabs) {
83
- return { content: [{ type: "text", text: "ELEVENLABS_API_KEY is not set." }], isError: true };
84
- }
85
- if (provider === "openai" && !providers.openai) {
86
- return { content: [{ type: "text", text: "OPENAI_API_KEY is not set." }], isError: true };
87
- }
88
-
89
- const trim: TrimOptions | undefined =
90
- start !== undefined || end !== undefined ? { start, end } : undefined;
91
-
92
- const sourceType = detectSourceType(source);
93
- const record = createTranscript({
94
- source_url: source,
95
- source_type: sourceType,
96
- provider: provider as TranscriptProvider,
97
- language,
98
- title,
99
- });
100
-
101
- updateTranscript(record.id, { status: "processing" });
102
-
103
- let audio: Awaited<ReturnType<typeof prepareAudio>> | null = null;
104
- try {
105
- audio = await prepareAudio(source, trim);
106
-
107
- // Auto-title from video metadata if title not provided
108
- if (!title && audio.videoTitle) {
109
- updateTranscript(record.id, { title: audio.videoTitle });
110
- }
111
-
112
- const result = await transcribeFile(audio.filePath, {
113
- provider: provider as TranscriptProvider,
114
- language,
115
- diarize: diarize && provider === "elevenlabs",
116
- vocab: vocab && vocab.length > 0 ? vocab : undefined,
117
- });
118
-
119
- const chapterSegments = audio.chapters.length > 0 && result.metadata.words
120
- ? segmentByChapters(result.metadata.words, audio.chapters)
121
- : undefined;
122
-
123
- const updated = updateTranscript(record.id, {
124
- status: "completed",
125
- transcript_text: result.text,
126
- duration_seconds: result.duration_seconds ?? undefined,
127
- word_count: result.text.split(/\s+/).filter(Boolean).length,
128
- metadata: {
129
- ...result.metadata,
130
- ...(trim ? { trim_start: trim.start, trim_end: trim.end } : {}),
131
- ...(chapterSegments ? { chapters: chapterSegments } : {}),
132
- },
133
- });
134
-
135
- // Fetch comments if requested
136
- let commentCount = 0;
137
- if (fetchCommentsFlag && (sourceType === "youtube" || sourceType === "vimeo")) {
138
- try {
139
- const rawComments = await fetchComments(source);
140
- if (rawComments.length > 0) {
141
- const mapped = rawComments.map((c) => ({
142
- platform: sourceType,
143
- author: c.author,
144
- author_handle: c.author_id,
145
- comment_text: c.text,
146
- likes: c.like_count,
147
- reply_count: 0,
148
- is_reply: c.parent !== null,
149
- parent_comment_id: c.parent,
150
- published_at: c.timestamp ? new Date(c.timestamp * 1000).toISOString() : null,
151
- }));
152
- commentCount = importComments(record.id, mapped);
153
- }
154
- } catch {
155
- // Comment fetch is best-effort — don't fail the transcription
156
- }
157
- }
158
-
159
- const finalResult = { ...getTranscript(record.id), comments_imported: commentCount };
160
- return {
161
- content: [{ type: "text", text: JSON.stringify(finalResult, null, 2) }],
162
- };
163
- } catch (error) {
164
- const msg = error instanceof Error ? error.message : String(error);
165
- updateTranscript(record.id, { status: "failed", error_message: msg });
166
- return { content: [{ type: "text", text: `Transcription failed: ${msg}` }], isError: true };
167
- } finally {
168
- audio?.cleanup();
169
- }
170
- }
171
- );
172
-
173
- // ---------------------------------------------------------------------------
174
- // download_audio
175
- // ---------------------------------------------------------------------------
176
-
177
- server.registerTool(
178
- "download_audio",
179
- {
180
- title: "Download Audio",
181
- description: "Download audio from a URL (YouTube, Vimeo, Wistia, etc.) without transcribing. Saves to the audio library organized by platform.",
182
- inputSchema: {
183
- url: z.string().describe("Video/audio URL"),
184
- format: z.enum(["mp3", "m4a", "wav"]).optional().describe("Audio format (default: mp3)"),
185
- output_path: z.string().optional().describe("Override output file path"),
186
- start: z.number().optional().describe("Start time in seconds"),
187
- end: z.number().optional().describe("End time in seconds"),
188
- },
189
- },
190
- async ({ url, format, output_path, start, end }) => {
191
- try {
192
- const trim = start !== undefined || end !== undefined ? { start, end } : undefined;
193
- const result = await downloadAudio(url, { format: format as "mp3" | "m4a" | "wav" | undefined, outputPath: output_path, trim });
194
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
195
- } catch (error) {
196
- const msg = error instanceof Error ? error.message : String(error);
197
- return { content: [{ type: "text", text: `Download failed: ${msg}` }], isError: true };
198
- }
199
- }
200
- );
201
-
202
- // ---------------------------------------------------------------------------
203
- // batch_transcribe
204
- // ---------------------------------------------------------------------------
205
-
206
- server.registerTool(
207
- "batch_transcribe",
208
- {
209
- title: "Batch Transcribe",
210
- description: "Transcribe multiple sources sequentially. Each gets its own transcript record. Failures don't stop remaining items.",
211
- inputSchema: {
212
- sources: z.array(z.string()).describe("Array of file paths or URLs to transcribe"),
213
- provider: z.enum(["elevenlabs", "openai", "deepgram"]).optional().describe("Provider (default: elevenlabs)"),
214
- language: z.string().optional(),
215
- diarize: z.boolean().optional(),
216
- start: z.number().optional().describe("Start trim in seconds (applied to all sources)"),
217
- end: z.number().optional().describe("End trim in seconds (applied to all sources)"),
218
- },
219
- },
220
- async ({ sources, provider = "elevenlabs", language, diarize, start, end }) => {
221
- const available = checkProviders();
222
- if (provider === "elevenlabs" && !available.elevenlabs) {
223
- return { content: [{ type: "text", text: "ELEVENLABS_API_KEY is not set." }], isError: true };
224
- }
225
- if (provider === "openai" && !available.openai) {
226
- return { content: [{ type: "text", text: "OPENAI_API_KEY is not set." }], isError: true };
227
- }
228
-
229
- // Expand playlist URLs
230
- const expanded: string[] = [];
231
- for (const src of sources) {
232
- if (isPlaylistUrl(src)) {
233
- try {
234
- const videos = await getPlaylistUrls(src);
235
- expanded.push(...videos.map((v) => v.url));
236
- } catch { expanded.push(src); }
237
- } else {
238
- expanded.push(src);
239
- }
240
- }
241
-
242
- const trim = start !== undefined || end !== undefined ? { start, end } : undefined;
243
- const results: Array<{ source: string; id: string; success: boolean; error?: string }> = [];
244
-
245
- for (const source of expanded) {
246
- const sourceType = detectSourceType(source);
247
- const record = createTranscript({
248
- source_url: source,
249
- source_type: sourceType,
250
- provider: provider as TranscriptProvider,
251
- language,
252
- });
253
-
254
- updateTranscript(record.id, { status: "processing" });
255
-
256
- let audio: Awaited<ReturnType<typeof prepareAudio>> | null = null;
257
- try {
258
- audio = await prepareAudio(source, trim);
259
- if (audio.videoTitle) updateTranscript(record.id, { title: audio.videoTitle });
260
-
261
- const result = await transcribeFile(audio.filePath, {
262
- provider: provider as TranscriptProvider,
263
- language,
264
- diarize: diarize && provider === "elevenlabs",
265
- });
266
-
267
- const chapterSegments = audio.chapters.length > 0 && result.metadata.words
268
- ? segmentByChapters(result.metadata.words, audio.chapters)
269
- : undefined;
270
-
271
- updateTranscript(record.id, {
272
- status: "completed",
273
- transcript_text: result.text,
274
- duration_seconds: result.duration_seconds ?? undefined,
275
- word_count: result.text.split(/\s+/).filter(Boolean).length,
276
- metadata: {
277
- ...result.metadata,
278
- ...(trim ? { trim_start: trim.start, trim_end: trim.end } : {}),
279
- ...(chapterSegments ? { chapters: chapterSegments } : {}),
280
- },
281
- });
282
-
283
- results.push({ source, id: record.id, success: true });
284
- } catch (error) {
285
- const msg = error instanceof Error ? error.message : String(error);
286
- updateTranscript(record.id, { status: "failed", error_message: msg });
287
- results.push({ source, id: record.id, success: false, error: msg });
288
- } finally {
289
- audio?.cleanup();
290
- }
291
- }
292
-
293
- const succeeded = results.filter((r) => r.success).length;
294
- const failed = results.filter((r) => !r.success).length;
295
- return {
296
- content: [{ type: "text", text: JSON.stringify({ results, summary: { succeeded, failed, total: expanded.length } }, null, 2) }],
297
- };
298
- }
299
- );
300
-
301
- // ---------------------------------------------------------------------------
302
- // get_video_info
303
- // ---------------------------------------------------------------------------
304
-
305
- server.registerTool(
306
- "get_video_info",
307
- {
308
- title: "Get Video Info",
309
- description:
310
- "Fetch video metadata (title, duration, uploader, chapters, formats) from a URL without downloading or transcribing. Works with YouTube, Vimeo, Wistia, and any yt-dlp supported URL.",
311
- inputSchema: {
312
- url: z.string().describe("Video URL"),
313
- },
314
- },
315
- async ({ url }) => {
316
- try {
317
- const info = await getVideoInfo(url);
318
- return { content: [{ type: "text", text: JSON.stringify(info, null, 2) }] };
319
- } catch (error) {
320
- const msg = error instanceof Error ? error.message : String(error);
321
- return { content: [{ type: "text", text: `Failed to fetch video info: ${msg}` }], isError: true };
322
- }
323
- }
324
- );
325
-
326
- // ---------------------------------------------------------------------------
327
- // list_transcripts
328
- // ---------------------------------------------------------------------------
329
-
330
- server.registerTool(
331
- "list_transcripts",
332
- {
333
- title: "List Transcripts",
334
- description: "List transcripts with optional filters.",
335
- inputSchema: {
336
- status: z
337
- .enum(["pending", "processing", "completed", "failed"])
338
- .optional()
339
- .describe("Filter by status"),
340
- provider: z
341
- .enum(["elevenlabs", "openai", "deepgram"])
342
- .optional()
343
- .describe("Filter by provider"),
344
- source_type: z
345
- .enum(["file", "youtube", "vimeo", "wistia", "url"])
346
- .optional()
347
- .describe("Filter by source type"),
348
- limit: z.number().optional().describe("Max results (default 50)"),
349
- offset: z.number().optional().describe("Offset for pagination"),
350
- },
351
- },
352
- async ({ status, provider, source_type, limit, offset }) => {
353
- const results = listTranscripts({
354
- status: status as TranscriptStatus | undefined,
355
- provider: provider as TranscriptProvider | undefined,
356
- source_type: source_type as TranscriptSourceType | undefined,
357
- limit,
358
- offset,
359
- });
360
- return { content: [{ type: "text", text: JSON.stringify(results, null, 2) }] };
361
- }
362
- );
363
-
364
- // ---------------------------------------------------------------------------
365
- // get_transcript
366
- // ---------------------------------------------------------------------------
367
-
368
- server.registerTool(
369
- "get_transcript",
370
- {
371
- title: "Get Transcript",
372
- description: "Get a single transcript by ID, including full text and metadata.",
373
- inputSchema: {
374
- id: z.string().describe("Transcript ID"),
375
- },
376
- },
377
- async ({ id }) => {
378
- const t = getTranscript(id);
379
- if (!t) {
380
- return { content: [{ type: "text", text: `Transcript '${id}' not found.` }], isError: true };
381
- }
382
- return { content: [{ type: "text", text: JSON.stringify(t, null, 2) }] };
383
- }
384
- );
385
-
386
- // ---------------------------------------------------------------------------
387
- // search_transcripts
388
- // ---------------------------------------------------------------------------
389
-
390
- server.registerTool(
391
- "search_transcripts",
392
- {
393
- title: "Search Transcripts",
394
- description: "Full-text search across transcript text, titles, and source URLs. Use context param for excerpts with timestamps.",
395
- inputSchema: {
396
- query: z.string().describe("Search query"),
397
- context: z.number().optional().describe("Number of surrounding sentences to include (enables contextual search with timestamps)"),
398
- },
399
- },
400
- async ({ query, context }) => {
401
- if (context !== undefined) {
402
- const matches = searchWithContext(query, context);
403
- return { content: [{ type: "text", text: JSON.stringify(matches, null, 2) }] };
404
- }
405
- const results = searchTranscripts(query);
406
- return { content: [{ type: "text", text: JSON.stringify(results, null, 2) }] };
407
- }
408
- );
409
-
410
- // ---------------------------------------------------------------------------
411
- // retry_transcript
412
- // ---------------------------------------------------------------------------
413
-
414
- server.registerTool(
415
- "retry_transcript",
416
- {
417
- title: "Retry Transcript",
418
- description: "Retry a failed or pending transcription using its original source URL. Optionally switch providers.",
419
- inputSchema: {
420
- id: z.string().describe("Transcript ID to retry"),
421
- provider: z
422
- .enum(["elevenlabs", "openai", "deepgram"])
423
- .optional()
424
- .describe("Override provider (defaults to original)"),
425
- diarize: z.boolean().optional().describe("Identify speakers — ElevenLabs only"),
426
- },
427
- },
428
- async ({ id, provider: providerOverride, diarize }) => {
429
- const t = getTranscript(id);
430
- if (!t) {
431
- return { content: [{ type: "text", text: `Transcript '${id}' not found.` }], isError: true };
432
- }
433
- if (!t.source_url) {
434
- return { content: [{ type: "text", text: `Transcript '${id}' has no source URL to retry from.` }], isError: true };
435
- }
436
-
437
- const provider = (providerOverride ?? t.provider) as TranscriptProvider;
438
- const available = checkProviders();
439
- if (provider === "elevenlabs" && !available.elevenlabs) {
440
- return { content: [{ type: "text", text: "ELEVENLABS_API_KEY is not set." }], isError: true };
441
- }
442
- if (provider === "openai" && !available.openai) {
443
- return { content: [{ type: "text", text: "OPENAI_API_KEY is not set." }], isError: true };
444
- }
445
-
446
- updateTranscript(id, { status: "processing", error_message: null });
447
-
448
- let audio: Awaited<ReturnType<typeof prepareAudio>> | null = null;
449
- try {
450
- const trim = t.metadata?.trim_start !== undefined || t.metadata?.trim_end !== undefined
451
- ? { start: t.metadata.trim_start, end: t.metadata.trim_end }
452
- : undefined;
453
-
454
- audio = await prepareAudio(t.source_url, trim);
455
- const result = await transcribeFile(audio.filePath, {
456
- provider,
457
- language: t.language,
458
- diarize: diarize && provider === "elevenlabs",
459
- });
460
-
461
- const updated = updateTranscript(id, {
462
- status: "completed",
463
- transcript_text: result.text,
464
- duration_seconds: result.duration_seconds ?? undefined,
465
- word_count: result.text.split(/\s+/).filter(Boolean).length,
466
- metadata: {
467
- ...result.metadata,
468
- ...(trim ? { trim_start: trim.start, trim_end: trim.end } : {}),
469
- },
470
- });
471
-
472
- return { content: [{ type: "text", text: JSON.stringify(updated, null, 2) }] };
473
- } catch (error) {
474
- const msg = error instanceof Error ? error.message : String(error);
475
- updateTranscript(id, { status: "failed", error_message: msg });
476
- return { content: [{ type: "text", text: `Retry failed: ${msg}` }], isError: true };
477
- } finally {
478
- audio?.cleanup();
479
- }
480
- }
481
- );
482
-
483
- // ---------------------------------------------------------------------------
484
- // delete_transcript
485
- // ---------------------------------------------------------------------------
486
-
487
- server.registerTool(
488
- "delete_transcript",
489
- {
490
- title: "Delete Transcript",
491
- description: "Delete a transcript by ID.",
492
- inputSchema: {
493
- id: z.string().describe("Transcript ID"),
494
- },
495
- },
496
- async ({ id }) => {
497
- const deleted = deleteTranscript(id);
498
- if (!deleted) {
499
- return { content: [{ type: "text", text: `Transcript '${id}' not found.` }], isError: true };
500
- }
501
- return { content: [{ type: "text", text: JSON.stringify({ id, deleted: true }) }] };
502
- }
503
- );
504
-
505
- // ---------------------------------------------------------------------------
506
- // export_transcript
507
- // ---------------------------------------------------------------------------
508
-
509
- server.registerTool(
510
- "export_transcript",
511
- {
512
- title: "Export Transcript",
513
- description: "Export a completed transcript as plain text, SRT subtitles, or JSON.",
514
- inputSchema: {
515
- id: z.string().describe("Transcript ID"),
516
- format: z
517
- .enum(["txt", "srt", "vtt", "ass", "md", "json"])
518
- .optional()
519
- .describe("Export format: txt (default), srt, vtt, ass, json"),
520
- font_name: z.string().optional().describe("Font name for ASS format (default: Arial)"),
521
- font_size: z.number().optional().describe("Font size for ASS format (default: 20)"),
522
- color: z.string().optional().describe("Text color hex for ASS (default: FFFFFF = white)"),
523
- outline: z.number().optional().describe("Outline size for ASS (default: 2)"),
524
- shadow: z.number().optional().describe("Shadow size for ASS (default: 1)"),
525
- show_confidence: z.boolean().optional().describe("Flag low-confidence words with [?word?] markers (ElevenLabs only, txt format)"),
526
- confidence_threshold: z.number().optional().describe("Confidence threshold 0-1 (default 0.7)"),
527
- },
528
- },
529
- async ({ id, format = "txt", font_name, font_size, color, outline, shadow, show_confidence, confidence_threshold }) => {
530
- const t = getTranscript(id);
531
- if (!t) {
532
- return { content: [{ type: "text", text: `Transcript '${id}' not found.` }], isError: true };
533
- }
534
-
535
- if (t.status !== "completed" || !t.transcript_text) {
536
- return {
537
- content: [{ type: "text", text: `Transcript '${id}' is not completed (status: ${t.status}).` }],
538
- isError: true,
539
- };
540
- }
541
-
542
- let output: string;
543
- if (format === "json") {
544
- output = JSON.stringify(t, null, 2);
545
- } else if (format === "md") {
546
- output = toMarkdown(t);
547
- } else if (format === "srt" || format === "vtt" || format === "ass") {
548
- const words = t.metadata?.words ?? [];
549
- if (words.length === 0) {
550
- return {
551
- content: [{ type: "text", text: `No word-level timestamps available for ${format.toUpperCase()} export.` }],
552
- isError: true,
553
- };
554
- }
555
- if (format === "vtt") output = toVtt(words);
556
- else if (format === "ass") output = toAss(words, { fontName: font_name, fontSize: font_size, color, outline, shadow });
557
- else output = toSrt(words);
558
- } else {
559
- if (show_confidence && t.metadata?.words?.length) {
560
- output = formatWithConfidence(t.metadata.words, confidence_threshold ?? 0.7);
561
- } else {
562
- output = t.transcript_text;
563
- }
564
- }
565
-
566
- return { content: [{ type: "text", text: output }] };
567
- }
568
- );
569
-
570
- // ---------------------------------------------------------------------------
571
- // transcript_stats
572
- // ---------------------------------------------------------------------------
573
-
574
- server.registerTool(
575
- "transcript_stats",
576
- {
577
- title: "Transcript Stats",
578
- description: "Get transcript counts grouped by status and provider.",
579
- inputSchema: {},
580
- },
581
- async () => {
582
- const counts = countTranscripts();
583
- return { content: [{ type: "text", text: JSON.stringify(counts, null, 2) }] };
584
- }
585
- );
586
-
587
- // ---------------------------------------------------------------------------
588
- // check_providers
589
- // ---------------------------------------------------------------------------
590
-
591
- server.registerTool(
592
- "check_providers",
593
- {
594
- title: "Check Providers",
595
- description: "Check which transcription providers have API keys configured.",
596
- inputSchema: {},
597
- },
598
- async () => {
599
- const available = checkProviders();
600
- return { content: [{ type: "text", text: JSON.stringify(available, null, 2) }] };
601
- }
602
- );
603
-
604
- // ---------------------------------------------------------------------------
605
- // tag_transcript / list_tags
606
- // ---------------------------------------------------------------------------
607
-
608
- server.registerTool(
609
- "tag_transcript",
610
- {
611
- title: "Tag Transcript",
612
- description: "Add or remove tags on a transcript for organization.",
613
- inputSchema: {
614
- id: z.string().describe("Transcript ID"),
615
- add: z.array(z.string()).optional().describe("Tags to add"),
616
- remove: z.array(z.string()).optional().describe("Tags to remove"),
617
- },
618
- },
619
- async ({ id, add, remove }) => {
620
- if (add) addTags(id, add);
621
- if (remove) removeTags(id, remove);
622
- const tags = getTags(id);
623
- return { content: [{ type: "text", text: JSON.stringify({ id, tags }, null, 2) }] };
624
- }
625
- );
626
-
627
- server.registerTool(
628
- "list_tags",
629
- {
630
- title: "List Tags",
631
- description: "List all tags with transcript counts.",
632
- inputSchema: {},
633
- },
634
- async () => {
635
- const tags = listAllTags();
636
- return { content: [{ type: "text", text: JSON.stringify(tags, null, 2) }] };
637
- }
638
- );
639
-
640
- // ---------------------------------------------------------------------------
641
- // rename_speakers
642
- // ---------------------------------------------------------------------------
643
-
644
- server.registerTool(
645
- "rename_speakers",
646
- {
647
- title: "Rename Speakers",
648
- description: "Rename speaker labels in a diarized transcript. Replaces in text, words, and speaker segments.",
649
- inputSchema: {
650
- id: z.string().describe("Transcript ID"),
651
- mapping: z.record(z.string()).describe('Speaker name mapping, e.g. {"Speaker 1":"Andrej Karpathy","Speaker 2":"Sarah Guo"}'),
652
- },
653
- },
654
- async ({ id, mapping }) => {
655
- const updated = renameSpeakers(id, mapping);
656
- if (!updated) return { content: [{ type: "text", text: `Transcript '${id}' not found.` }], isError: true };
657
- return { content: [{ type: "text", text: JSON.stringify({ id, renamed: Object.keys(mapping).length }, null, 2) }] };
658
- }
659
- );
660
-
661
- // ---------------------------------------------------------------------------
662
- // translate_transcript
663
- // ---------------------------------------------------------------------------
664
-
665
- server.registerTool(
666
- "translate_transcript",
667
- {
668
- title: "Translate Transcript",
669
- description: "Translate a completed transcript to another language. Creates a new linked transcript record with source_transcript_id pointing to the original.",
670
- inputSchema: {
671
- id: z.string().describe("Source transcript ID"),
672
- to: z.string().describe("Target language code or name (e.g. 'fr', 'de', 'Spanish')"),
673
- provider: z.enum(["openai", "anthropic"]).optional().describe("AI provider (auto-detected from env)"),
674
- },
675
- },
676
- async ({ id, to, provider }) => {
677
- const t = getTranscript(id);
678
- if (!t) return { content: [{ type: "text", text: `Transcript '${id}' not found.` }], isError: true };
679
- if (t.status !== "completed" || !t.transcript_text) {
680
- return { content: [{ type: "text", text: `Transcript '${id}' is not completed.` }], isError: true };
681
- }
682
-
683
- const resolved = provider ?? getDefaultSummaryProvider();
684
- if (!resolved) {
685
- return { content: [{ type: "text", text: "No AI provider configured. Set OPENAI_API_KEY or ANTHROPIC_API_KEY." }], isError: true };
686
- }
687
-
688
- try {
689
- const translatedText = await translateText(t.transcript_text, to, resolved);
690
-
691
- const newRecord = createTranscript({
692
- source_url: t.source_url ?? `translated:${id}`,
693
- source_type: "translated",
694
- provider: t.provider,
695
- language: to,
696
- title: t.title ? `${t.title} [${to}]` : null,
697
- source_transcript_id: id,
698
- });
699
-
700
- updateTranscript(newRecord.id, {
701
- status: "completed",
702
- transcript_text: translatedText,
703
- word_count: translatedText.split(/\s+/).filter(Boolean).length,
704
- metadata: { model: resolved },
705
- });
706
-
707
- return { content: [{ type: "text", text: JSON.stringify(getTranscript(newRecord.id), null, 2) }] };
708
- } catch (error) {
709
- const msg = error instanceof Error ? error.message : String(error);
710
- return { content: [{ type: "text", text: `Translation failed: ${msg}` }], isError: true };
711
- }
712
- }
713
- );
714
-
715
- // ---------------------------------------------------------------------------
716
- // summarize_transcript
717
- // ---------------------------------------------------------------------------
718
-
719
- server.registerTool(
720
- "summarize_transcript",
721
- {
722
- title: "Summarize Transcript",
723
- description: "Generate a 3-5 sentence AI summary of a completed transcript. Stores summary in metadata.summary. Uses OpenAI gpt-4o-mini or Anthropic claude-haiku.",
724
- inputSchema: {
725
- id: z.string().describe("Transcript ID"),
726
- provider: z.enum(["openai", "anthropic"]).optional().describe("AI provider (auto-detected from env if omitted)"),
727
- },
728
- },
729
- async ({ id, provider }) => {
730
- const t = getTranscript(id);
731
- if (!t) return { content: [{ type: "text", text: `Transcript '${id}' not found.` }], isError: true };
732
- if (t.status !== "completed" || !t.transcript_text) {
733
- return { content: [{ type: "text", text: `Transcript '${id}' is not completed.` }], isError: true };
734
- }
735
-
736
- const resolved = provider ?? getDefaultSummaryProvider();
737
- if (!resolved) {
738
- return { content: [{ type: "text", text: "No AI provider configured. Set OPENAI_API_KEY or ANTHROPIC_API_KEY." }], isError: true };
739
- }
740
-
741
- try {
742
- const summary = await summarizeText(t.transcript_text, resolved);
743
- updateTranscript(id, { metadata: { ...t.metadata, summary } });
744
- return { content: [{ type: "text", text: JSON.stringify({ id, summary }, null, 2) }] };
745
- } catch (error) {
746
- const msg = error instanceof Error ? error.message : String(error);
747
- return { content: [{ type: "text", text: `Summarization failed: ${msg}` }], isError: true };
748
- }
749
- }
750
- );
751
-
752
- // ---------------------------------------------------------------------------
753
- // diff_transcripts
754
- // ---------------------------------------------------------------------------
755
-
756
- server.registerTool(
757
- "diff_transcripts",
758
- {
759
- title: "Diff Transcripts",
760
- description: "Compare two transcripts word-by-word. Returns similarity percentage and diff entries.",
761
- inputSchema: {
762
- id1: z.string().describe("First transcript ID"),
763
- id2: z.string().describe("Second transcript ID"),
764
- },
765
- },
766
- async ({ id1, id2 }) => {
767
- const t1 = getTranscript(id1);
768
- const t2 = getTranscript(id2);
769
- if (!t1) return { content: [{ type: "text", text: `Transcript '${id1}' not found.` }], isError: true };
770
- if (!t2) return { content: [{ type: "text", text: `Transcript '${id2}' not found.` }], isError: true };
771
- if (!t1.transcript_text || !t2.transcript_text) {
772
- return { content: [{ type: "text", text: "Both transcripts must be completed." }], isError: true };
773
- }
774
-
775
- const entries = wordDiff(t1.transcript_text, t2.transcript_text);
776
- const stats = diffStats(entries);
777
- return { content: [{ type: "text", text: JSON.stringify({ id1, id2, stats, formatted: formatDiff(entries).slice(0, 5000) }, null, 2) }] };
778
- }
779
- );
780
-
781
- // ---------------------------------------------------------------------------
782
- // create_clip
783
- // ---------------------------------------------------------------------------
784
-
785
- server.registerTool(
786
- "create_clip",
787
- {
788
- title: "Create Clip",
789
- description: "Extract a video clip with burned-in subtitles from a transcribed URL source.",
790
- inputSchema: {
791
- id: z.string().describe("Transcript ID"),
792
- start: z.number().describe("Start time in seconds"),
793
- end: z.number().describe("End time in seconds"),
794
- output_path: z.string().optional().describe("Output file path"),
795
- subtitles: z.boolean().optional().describe("Burn in subtitles (default: true)"),
796
- },
797
- },
798
- async ({ id, start, end, output_path, subtitles = true }) => {
799
- const t = getTranscript(id);
800
- if (!t) return { content: [{ type: "text", text: `Transcript '${id}' not found.` }], isError: true };
801
- if (!t.source_url || t.source_type === "file") {
802
- return { content: [{ type: "text", text: "Clip extraction requires a URL source." }], isError: true };
803
- }
804
-
805
- const outputPath = output_path ?? `/tmp/clip-${id.slice(0, 8)}.mp4`;
806
- let video: Awaited<ReturnType<typeof downloadVideo>> | null = null;
807
- let subsFile: string | null = null;
808
-
809
- try {
810
- video = await downloadVideo(t.source_url);
811
-
812
- if (subtitles && t.metadata?.words?.length) {
813
- const rangeWords = t.metadata.words.filter((w) => w.start >= start && w.end <= end);
814
- if (rangeWords.length > 0) {
815
- const offsetWords = rangeWords.map((w) => ({ ...w, start: w.start - start, end: w.end - start }));
816
- const assContent = toAss(offsetWords);
817
- subsFile = `/tmp/transcriber-clip-subs-${crypto.randomUUID()}.ass`;
818
- const { writeFileSync } = await import("node:fs");
819
- writeFileSync(subsFile, assContent, "utf8");
820
- }
821
- }
822
-
823
- await createClip({ videoPath: video.path, start, end, subtitlePath: subsFile ?? undefined, outputPath });
824
- return { content: [{ type: "text", text: JSON.stringify({ id, outputPath, start, end }, null, 2) }] };
825
- } catch (error) {
826
- return { content: [{ type: "text", text: `Clip failed: ${error instanceof Error ? error.message : error}` }], isError: true };
827
- } finally {
828
- video?.cleanup();
829
- if (subsFile) { try { const { unlinkSync } = await import("node:fs"); unlinkSync(subsFile); } catch {} }
830
- }
831
- }
832
- );
833
-
834
- // ---------------------------------------------------------------------------
835
- // meeting_notes
836
- // ---------------------------------------------------------------------------
837
-
838
- server.registerTool(
839
- "meeting_notes",
840
- {
841
- title: "Generate Meeting Notes",
842
- description: "Restructure a transcript into formatted meeting notes: attendees, agenda, key decisions, action items, summary.",
843
- inputSchema: {
844
- id: z.string().describe("Transcript ID"),
845
- provider: z.enum(["openai", "anthropic"]).optional(),
846
- },
847
- },
848
- async ({ id, provider }) => {
849
- const t = getTranscript(id);
850
- if (!t) return { content: [{ type: "text", text: `Transcript '${id}' not found.` }], isError: true };
851
- if (t.status !== "completed" || !t.transcript_text) {
852
- return { content: [{ type: "text", text: `Transcript '${id}' is not completed.` }], isError: true };
853
- }
854
- const resolved = provider ?? getDefaultSummaryProvider();
855
- if (!resolved) return { content: [{ type: "text", text: "No AI provider configured." }], isError: true };
856
- try {
857
- const notes = await generateMeetingNotes(t.transcript_text, resolved);
858
- updateTranscript(id, { metadata: { ...t.metadata, meeting_notes: notes } });
859
- return { content: [{ type: "text", text: notes }] };
860
- } catch (error) {
861
- return { content: [{ type: "text", text: `Failed: ${error instanceof Error ? error.message : error}` }], isError: true };
862
- }
863
- }
864
- );
865
-
866
- // ---------------------------------------------------------------------------
867
- // highlights_transcript
868
- // ---------------------------------------------------------------------------
869
-
870
- server.registerTool(
871
- "highlights_transcript",
872
- {
873
- title: "Extract Highlights",
874
- description: "Extract 5-10 key moments/quotes from a completed transcript using AI.",
875
- inputSchema: {
876
- id: z.string().describe("Transcript ID"),
877
- provider: z.enum(["openai", "anthropic"]).optional(),
878
- },
879
- },
880
- async ({ id, provider }) => {
881
- const t = getTranscript(id);
882
- if (!t) return { content: [{ type: "text", text: `Transcript '${id}' not found.` }], isError: true };
883
- if (t.status !== "completed" || !t.transcript_text) {
884
- return { content: [{ type: "text", text: `Transcript '${id}' is not completed.` }], isError: true };
885
- }
886
-
887
- const resolved = provider ?? getDefaultSummaryProvider();
888
- if (!resolved) return { content: [{ type: "text", text: "No AI provider configured." }], isError: true };
889
-
890
- try {
891
- const highlights = await extractHighlights(t.transcript_text, resolved);
892
- updateTranscript(id, { metadata: { ...t.metadata, highlights } });
893
- return { content: [{ type: "text", text: JSON.stringify({ id, highlights }, null, 2) }] };
894
- } catch (error) {
895
- return { content: [{ type: "text", text: `Highlights extraction failed: ${error instanceof Error ? error.message : error}` }], isError: true };
896
- }
897
- }
898
- );
899
-
900
- // ---------------------------------------------------------------------------
901
- // annotations
902
- // ---------------------------------------------------------------------------
903
-
904
- server.registerTool(
905
- "add_annotation",
906
- {
907
- title: "Add Annotation",
908
- description: "Add a timestamped annotation/bookmark to a transcript.",
909
- inputSchema: {
910
- transcript_id: z.string(), timestamp_sec: z.number(), note: z.string(),
911
- },
912
- },
913
- async ({ transcript_id, timestamp_sec, note }) => {
914
- const anno = createAnnotation(transcript_id, timestamp_sec, note);
915
- return { content: [{ type: "text", text: JSON.stringify(anno, null, 2) }] };
916
- }
917
- );
918
-
919
- server.registerTool(
920
- "list_annotations",
921
- {
922
- title: "List Annotations",
923
- description: "List all annotations for a transcript.",
924
- inputSchema: { transcript_id: z.string() },
925
- },
926
- async ({ transcript_id }) => {
927
- return { content: [{ type: "text", text: JSON.stringify(listAnnotations(transcript_id), null, 2) }] };
928
- }
929
- );
930
-
931
- server.registerTool(
932
- "delete_annotation",
933
- {
934
- title: "Delete Annotation",
935
- description: "Delete an annotation by ID.",
936
- inputSchema: { id: z.string() },
937
- },
938
- async ({ id }) => {
939
- const ok = deleteAnnotation(id);
940
- if (!ok) return { content: [{ type: "text", text: "Annotation not found." }], isError: true };
941
- return { content: [{ type: "text", text: JSON.stringify({ id, deleted: true }) }] };
942
- }
943
- );
944
-
945
- // ---------------------------------------------------------------------------
946
- // check_feeds
947
- // ---------------------------------------------------------------------------
948
-
949
- server.registerTool(
950
- "check_feeds",
951
- {
952
- title: "Check Podcast Feeds",
953
- description: "Check all registered RSS feeds for new episodes. Returns new episode URLs. Use with batch_transcribe to transcribe them.",
954
- inputSchema: {},
955
- },
956
- async () => {
957
- const cfg = getConfig();
958
- if (cfg.feeds.length === 0) return { content: [{ type: "text", text: "No feeds configured." }] };
959
-
960
- const allNew: Array<{ feed: string; episodes: Array<{ url: string; title: string | null }> }> = [];
961
- for (const feed of cfg.feeds) {
962
- try {
963
- const { episodes } = await fetchFeedEpisodes(feed.url);
964
- const newEps = episodes.filter((ep) => !findBySourceUrl(ep.url));
965
- if (newEps.length > 0) allNew.push({ feed: feed.title ?? feed.url, episodes: newEps.map((e) => ({ url: e.url, title: e.title })) });
966
- feed.lastChecked = new Date().toISOString();
967
- } catch {}
968
- }
969
- setConfig({ feeds: cfg.feeds });
970
- return { content: [{ type: "text", text: JSON.stringify(allNew, null, 2) }] };
971
- }
972
- );
973
-
974
- // ---------------------------------------------------------------------------
975
- // list_comments
976
- // ---------------------------------------------------------------------------
977
-
978
- server.registerTool(
979
- "list_comments",
980
- {
981
- title: "List Comments",
982
- description: "List comments for a transcript, optionally sorted by likes.",
983
- inputSchema: {
984
- transcript_id: z.string().describe("Transcript ID"),
985
- top: z.boolean().optional().describe("Sort by most liked"),
986
- limit: z.number().optional().describe("Max results (default 50)"),
987
- offset: z.number().optional().describe("Offset for pagination"),
988
- },
989
- },
990
- async ({ transcript_id, top, limit, offset }) => {
991
- const comments = listComments(transcript_id, { top, limit, offset });
992
- return { content: [{ type: "text", text: JSON.stringify(comments, null, 2) }] };
993
- }
994
- );
995
-
996
- // ---------------------------------------------------------------------------
997
- // top_comments
998
- // ---------------------------------------------------------------------------
999
-
1000
- server.registerTool(
1001
- "top_comments",
1002
- {
1003
- title: "Top Comments",
1004
- description: "Get the most liked comments for a transcript.",
1005
- inputSchema: {
1006
- transcript_id: z.string().describe("Transcript ID"),
1007
- limit: z.number().optional().describe("Number of top comments (default 10)"),
1008
- },
1009
- },
1010
- async ({ transcript_id, limit }) => {
1011
- const comments = getTopComments(transcript_id, limit);
1012
- return { content: [{ type: "text", text: JSON.stringify(comments, null, 2) }] };
1013
- }
1014
- );
1015
-
1016
- // ---------------------------------------------------------------------------
1017
- // search_comments
1018
- // ---------------------------------------------------------------------------
1019
-
1020
- server.registerTool(
1021
- "search_comments",
1022
- {
1023
- title: "Search Comments",
1024
- description: "Search comment text across all transcripts using LIKE matching.",
1025
- inputSchema: {
1026
- query: z.string().describe("Search query"),
1027
- },
1028
- },
1029
- async ({ query }) => {
1030
- const results = searchComments(query);
1031
- return { content: [{ type: "text", text: JSON.stringify(results, null, 2) }] };
1032
- }
1033
- );
1034
-
1035
- // ---------------------------------------------------------------------------
1036
- // comment_stats
1037
- // ---------------------------------------------------------------------------
1038
-
1039
- server.registerTool(
1040
- "comment_stats",
1041
- {
1042
- title: "Comment Stats",
1043
- description: "Get comment statistics for a transcript: total, replies, unique authors, avg likes, top commenter.",
1044
- inputSchema: {
1045
- transcript_id: z.string().describe("Transcript ID"),
1046
- },
1047
- },
1048
- async ({ transcript_id }) => {
1049
- const stats = getCommentStats(transcript_id);
1050
- return { content: [{ type: "text", text: JSON.stringify(stats, null, 2) }] };
1051
- }
1052
- );
1053
-
1054
- // ---------------------------------------------------------------------------
1055
- // get_config / set_config
1056
- // ---------------------------------------------------------------------------
1057
-
1058
- server.registerTool(
1059
- "get_config",
1060
- {
1061
- title: "Get Config",
1062
- description: "Get current transcriber configuration defaults.",
1063
- inputSchema: {},
1064
- },
1065
- async () => {
1066
- const cfg = getConfig();
1067
- return { content: [{ type: "text", text: JSON.stringify(cfg, null, 2) }] };
1068
- }
1069
- );
1070
-
1071
- server.registerTool(
1072
- "set_config",
1073
- {
1074
- title: "Set Config",
1075
- description: "Update transcriber configuration defaults.",
1076
- inputSchema: {
1077
- defaultProvider: z.enum(["elevenlabs", "openai", "deepgram"]).optional(),
1078
- defaultLanguage: z.string().optional(),
1079
- defaultFormat: z.enum(["txt", "srt", "vtt", "json"]).optional(),
1080
- diarize: z.boolean().optional(),
1081
- },
1082
- },
1083
- async (updates) => {
1084
- const cfg = setConfig(updates);
1085
- return { content: [{ type: "text", text: JSON.stringify(cfg, null, 2) }] };
1086
- }
1087
- );
1088
-
1089
- server.registerTool(
1090
- "reset_config",
1091
- {
1092
- title: "Reset Config",
1093
- description: "Reset all transcriber configuration to defaults.",
1094
- inputSchema: {},
1095
- },
1096
- async () => {
1097
- const cfg = resetConfig();
1098
- return { content: [{ type: "text", text: JSON.stringify(cfg, null, 2) }] };
1099
- }
1100
- );
1101
-
1102
- // ---------------------------------------------------------------------------
1103
- // proofread_transcript
1104
- // ---------------------------------------------------------------------------
1105
-
1106
- server.registerTool(
1107
- "proofread_transcript",
1108
- {
1109
- title: "Proofread Transcript",
1110
- description: "Run AI-powered spellcheck/proofread on a transcript. Finds spelling, grammar, punctuation, and clarity issues. Non-destructive: stores issues in DB without modifying transcript text.",
1111
- inputSchema: {
1112
- id: z.string().describe("Transcript ID"),
1113
- types: z.array(z.enum(["spelling", "grammar", "punctuation", "clarity"])).optional().describe("Issue types to check (default: all)"),
1114
- confidence_threshold: z.number().optional().describe("Minimum confidence 0-1 (default: 0.7)"),
1115
- provider: z.enum(["openai", "anthropic"]).optional().describe("AI provider (auto-detected from env)"),
1116
- },
1117
- },
1118
- async ({ id, types, confidence_threshold, provider }) => {
1119
- try {
1120
- const issues = await proofreadTranscript(id, {
1121
- types: types as IssueType[] | undefined,
1122
- confidence_threshold,
1123
- provider: provider as "openai" | "anthropic" | undefined,
1124
- });
1125
- return { content: [{ type: "text", text: JSON.stringify(issues, null, 2) }] };
1126
- } catch (error) {
1127
- return { content: [{ type: "text", text: `Proofread failed: ${error instanceof Error ? error.message : error}` }], isError: true };
1128
- }
1129
- }
1130
- );
1131
-
1132
- // ---------------------------------------------------------------------------
1133
- // list_proofread_issues
1134
- // ---------------------------------------------------------------------------
1135
-
1136
- server.registerTool(
1137
- "list_proofread_issues",
1138
- {
1139
- title: "List Proofread Issues",
1140
- description: "List proofread issues for a transcript with optional filters.",
1141
- inputSchema: {
1142
- transcript_id: z.string().describe("Transcript ID"),
1143
- issue_type: z.enum(["spelling", "grammar", "punctuation", "clarity"]).optional().describe("Filter by issue type"),
1144
- status: z.enum(["pending", "applied", "dismissed"]).optional().describe("Filter by status"),
1145
- },
1146
- },
1147
- async ({ transcript_id, issue_type, status }) => {
1148
- const issues = listIssues(transcript_id, {
1149
- issue_type: issue_type as IssueType | undefined,
1150
- status: status as "pending" | "applied" | "dismissed" | undefined,
1151
- });
1152
- return { content: [{ type: "text", text: JSON.stringify(issues, null, 2) }] };
1153
- }
1154
- );
1155
-
1156
- // ---------------------------------------------------------------------------
1157
- // apply_suggestion
1158
- // ---------------------------------------------------------------------------
1159
-
1160
- server.registerTool(
1161
- "apply_suggestion",
1162
- {
1163
- title: "Apply Proofread Suggestion",
1164
- description: "Apply a proofread suggestion to the transcript text. Replaces the original text with the suggestion and marks the issue as applied.",
1165
- inputSchema: {
1166
- issue_id: z.string().describe("Proofread issue ID"),
1167
- },
1168
- },
1169
- async ({ issue_id }) => {
1170
- const result = applySuggestion(issue_id);
1171
- if (!result) return { content: [{ type: "text", text: `Issue '${issue_id}' not found.` }], isError: true };
1172
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1173
- }
1174
- );
1175
-
1176
- // ---------------------------------------------------------------------------
1177
- // dismiss_issue
1178
- // ---------------------------------------------------------------------------
1179
-
1180
- server.registerTool(
1181
- "dismiss_issue",
1182
- {
1183
- title: "Dismiss Proofread Issue",
1184
- description: "Dismiss a proofread issue without modifying the transcript text.",
1185
- inputSchema: {
1186
- issue_id: z.string().describe("Proofread issue ID"),
1187
- },
1188
- },
1189
- async ({ issue_id }) => {
1190
- const result = dismissIssue(issue_id);
1191
- if (!result) return { content: [{ type: "text", text: `Issue '${issue_id}' not found.` }], isError: true };
1192
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1193
- }
1194
- );
1195
-
1196
- // ---------------------------------------------------------------------------
1197
- // proofread_stats
1198
- // ---------------------------------------------------------------------------
1199
-
1200
- server.registerTool(
1201
- "proofread_stats",
1202
- {
1203
- title: "Proofread Stats",
1204
- description: "Get proofread issue statistics for a transcript: total, by type, pending/applied/dismissed counts.",
1205
- inputSchema: {
1206
- transcript_id: z.string().describe("Transcript ID"),
1207
- },
1208
- },
1209
- async ({ transcript_id }) => {
1210
- const stats = getProofreadStats(transcript_id);
1211
- return { content: [{ type: "text", text: JSON.stringify(stats, null, 2) }] };
1212
- }
1213
- );
1214
-
1215
- // ---------------------------------------------------------------------------
1216
- // export_annotated
1217
- // ---------------------------------------------------------------------------
1218
-
1219
- server.registerTool(
1220
- "export_annotated",
1221
- {
1222
- title: "Export Annotated Transcript",
1223
- description: "Export transcript text with inline proofread annotations showing pending issues as [TYPE: \"original\" -> \"suggestion\"] markers.",
1224
- inputSchema: {
1225
- transcript_id: z.string().describe("Transcript ID"),
1226
- },
1227
- },
1228
- async ({ transcript_id }) => {
1229
- try {
1230
- const text = exportAnnotated(transcript_id);
1231
- return { content: [{ type: "text", text }] };
1232
- } catch (error) {
1233
- return { content: [{ type: "text", text: `Export failed: ${error instanceof Error ? error.message : error}` }], isError: true };
1234
- }
1235
- }
1236
- );
1237
-
1238
- // ---------------------------------------------------------------------------
1239
- // search_tools / describe_tools
1240
- // ---------------------------------------------------------------------------
1241
-
1242
- server.registerTool(
1243
- "search_tools",
1244
- {
1245
- title: "Search Tools",
1246
- description: "List tool names, optionally filtered by keyword.",
1247
- inputSchema: { query: z.string().optional() },
1248
- },
1249
- async ({ query }) => {
1250
- const all = [
1251
- "transcribe",
1252
- "batch_transcribe",
1253
- "download_audio",
1254
- "get_video_info",
1255
- "list_transcripts",
1256
- "get_transcript",
1257
- "search_transcripts",
1258
- "retry_transcript",
1259
- "delete_transcript",
1260
- "export_transcript",
1261
- "transcript_stats",
1262
- "check_providers",
1263
- "translate_transcript",
1264
- "summarize_transcript",
1265
- "get_config",
1266
- "set_config",
1267
- "reset_config",
1268
- "list_comments",
1269
- "top_comments",
1270
- "search_comments",
1271
- "comment_stats",
1272
- "proofread_transcript",
1273
- "list_proofread_issues",
1274
- "apply_suggestion",
1275
- "dismiss_issue",
1276
- "proofread_stats",
1277
- "export_annotated",
1278
- "search_tools",
1279
- "describe_tools",
1280
- ];
1281
- const matches = query ? all.filter((n) => n.includes(query.toLowerCase())) : all;
1282
- return { content: [{ type: "text" as const, text: matches.join(", ") }] };
1283
- }
1284
- );
1285
-
1286
- server.registerTool(
1287
- "describe_tools",
1288
- {
1289
- title: "Describe Tools",
1290
- description: "Get full descriptions for specific tools.",
1291
- inputSchema: { names: z.array(z.string()) },
1292
- },
1293
- async ({ names }) => {
1294
- const descriptions: Record<string, string> = {
1295
- transcribe: "Transcribe a file or URL. Params: source, provider? (elevenlabs|openai), language?, title?, start?, end?",
1296
- get_video_info: "Fetch video metadata without downloading. Params: url",
1297
- list_transcripts: "List transcripts. Params: status?, provider?, source_type?, limit?, offset?",
1298
- get_transcript: "Get a transcript by ID. Params: id",
1299
- search_transcripts: "Full-text search. Params: query",
1300
- retry_transcript: "Retry a failed transcript. Params: id, provider?, diarize?",
1301
- delete_transcript: "Delete a transcript. Params: id",
1302
- export_transcript: "Export as txt/srt/json. Params: id, format?",
1303
- transcript_stats: "Counts by status and provider.",
1304
- check_providers: "Check which API keys are configured.",
1305
- proofread_transcript: "AI spellcheck/proofread. Params: id, types?, confidence_threshold?, provider?",
1306
- list_proofread_issues: "List proofread issues. Params: transcript_id, issue_type?, status?",
1307
- apply_suggestion: "Apply a proofread suggestion. Params: issue_id",
1308
- dismiss_issue: "Dismiss a proofread issue. Params: issue_id",
1309
- proofread_stats: "Proofread stats. Params: transcript_id",
1310
- export_annotated: "Export with inline annotations. Params: transcript_id",
1311
- };
1312
- const result = names.map((n) => `${n}: ${descriptions[n] || "See tool schema"}`).join("\n");
1313
- return { content: [{ type: "text" as const, text: result }] };
1314
- }
1315
- );
1316
-
1317
- // ---------------------------------------------------------------------------
1318
- // Start
1319
- // ---------------------------------------------------------------------------
1320
-
1321
- async function main() {
1322
- const transport = new StdioServerTransport();
1323
- await server.connect(transport);
1324
- console.error("Transcriber MCP server running on stdio");
1325
- }
1326
-
1327
- main().catch((error) => {
1328
- console.error("Fatal error:", error);
1329
- process.exit(1);
1330
- });