{"_id":"ytsubs","_rev":"7-3d8b038130bb39e3e8ed84150d580742","name":"ytsubs","dist-tags":{"latest":"0.3.0"},"versions":{"0.0.1":{"name":"ytsubs","version":"0.0.1","keywords":["youtube","subtitles","captions","transcript"],"author":{"name":"bguiz"},"license":"MIT","_id":"ytsubs@0.0.1","maintainers":[{"name":"bguiz","email":"npmjs@bguiz.com"}],"homepage":"https://github.com/bguiz/yt-subs#readme","bugs":{"url":"https://github.com/bguiz/yt-subs/issues"},"bin":{"ytsubs":"yt-subs.js"},"dist":{"shasum":"89f7274d79c47672087bd4fcc0dee53a4eb2e417","tarball":"https://registry.npmjs.org/ytsubs/-/ytsubs-0.0.1.tgz","fileCount":4,"integrity":"sha512-gWWR3eH5Mpu8R4jDhAT1FaANQ92lrqrGA9D12Ev2PG23xmH/gUdFBdfvjLr3j/EGLpHmsc5gSspbsvT4asx1jQ==","signatures":[{"sig":"MEUCIQCc/xWeGs27UurR2nelyW2IPQMgXjJ70HEP/21bP4IqTwIgAtGC+kv2JzN95pECFTKHKH05GOc3cAQv2tF7vd6YrBY=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":10164},"main":"yt-subs-sdk.js","type":"module","gitHead":"de11326edc3ec7f48ea03b79d2fceaef83700fba","scripts":{"test":"npm run test:unit && npm run test:e2e","coverage":"node --test --experimental-test-coverage --test-coverage-include=yt-subs-sdk.js yt-subs.*.unit.test.js","test:e2e":"node --test yt-subs.*.e2e.test.js","test:unit":"node --test yt-subs.*.unit.test.js","list-files":"npm pack && tar -xvzf *.tgz && rm -rf package *.tgz"},"_npmUser":{"name":"bguiz","email":"npmjs@bguiz.com"},"repository":{"url":"git+https://github.com/bguiz/yt-subs.git","type":"git"},"_npmVersion":"11.11.0","description":"downloads transcript files from youtube videos","directories":{},"_nodeVersion":"24.14.1","dependencies":{"youtube-transcript-plus":"2.0.0"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/ytsubs_0.0.1_1776160755948_0.0014444329521556831","host":"s3://npm-registry-packages-npm-production"}},"0.1.0":{"name":"ytsubs","version":"0.1.0","keywords":["youtube","subtitles","captions","transcript"],"author":{"name":"bguiz"},"license":"MIT","_id":"ytsubs@0.1.0","maintainers":[{"name":"bguiz","email":"npmjs@bguiz.com"}],"homepage":"https://github.com/bguiz/yt-subs#readme","bugs":{"url":"https://github.com/bguiz/yt-subs/issues"},"bin":{"ytsubs":"yt-subs-cli.js","ytsubs-mcp":"yt-subs-mcp.js"},"dist":{"shasum":"8f1ed3342fdc08590314329d6039b61086c1ecbf","tarball":"https://registry.npmjs.org/ytsubs/-/ytsubs-0.1.0.tgz","fileCount":5,"integrity":"sha512-9dSMz8JVj31hwY8OogonMX0VfpEL9+eDqDlMzLhHRn8MCvhGj9h5rf7YsSRG+Ow1/82EG45A9zIB9RNdcYw5Uw==","signatures":[{"sig":"MEYCIQDLiJd5LFQI0QWxIrDp2B3xQOyL4pNw3fdcV8LcGOE9cAIhANTh/kkIsL1gfKCvOt5r7C6OvPF7/KWHCVSQzWc59VYZ","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":16106},"main":"yt-subs-sdk.js","type":"module","gitHead":"e7da8742bc088fa91aa17bbccd84553bc792f0a1","scripts":{"cli":"node yt-subs-cli.js","test":"npm run test:unit && npm run test:e2e","coverage":"node --test --experimental-test-coverage --test-coverage-include=\"yt-subs-*.js\" yt-subs.*.unit.test.js","mcp:http":"node yt-subs-mcp.js http","test:e2e":"node --test yt-subs.*.e2e.test.js","mcp:stdio":"node yt-subs-mcp.js stdio","test:unit":"node --test yt-subs.*.unit.test.js","list-files":"npm pack && tar -xvzf *.tgz && rm -rf package *.tgz"},"_npmUser":{"name":"bguiz","email":"npmjs@bguiz.com"},"repository":{"url":"git+https://github.com/bguiz/yt-subs.git","type":"git"},"_npmVersion":"11.11.0","description":"downloads transcript files from youtube videos","directories":{},"_nodeVersion":"24.14.1","dependencies":{"youtube-transcript-plus":"2.0.0","@modelcontextprotocol/sdk":"1.29.0"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/ytsubs_0.1.0_1776178354921_0.8330214839872243","host":"s3://npm-registry-packages-npm-production"}},"0.1.1":{"name":"ytsubs","version":"0.1.1","keywords":["youtube","subtitles","captions","transcript"],"author":{"name":"bguiz"},"license":"MIT","_id":"ytsubs@0.1.1","maintainers":[{"name":"bguiz","email":"npmjs@bguiz.com"}],"homepage":"https://github.com/bguiz/yt-subs#readme","bugs":{"url":"https://github.com/bguiz/yt-subs/issues"},"bin":{"ytsubs":"yt-subs-cli.js","ytsubs-mcp":"yt-subs-mcp.js"},"dist":{"shasum":"72ade1cd289865e73aa87b05d75ec7d779988831","tarball":"https://registry.npmjs.org/ytsubs/-/ytsubs-0.1.1.tgz","fileCount":5,"integrity":"sha512-GtHqsb2BlncCDKXpulBKKUQwcM08zHrNXo2UaCsxlJIp3SdWLqwKpH4GDtCTPNqx1fb+XGfUNhvbe3bJXSDQKQ==","signatures":[{"sig":"MEUCIB/ai7DDtKjYCLAUHOFOujIrYriejadGf8DvQaXHpPhoAiEAy1AZhL8RijOA96wu7Y6lRyM5V0q0YHgFdAsKrv9TCnM=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":16351},"main":"yt-subs-sdk.js","type":"module","gitHead":"8f8fe73d954ad22babe11e45a73829e89d05fcad","scripts":{"cli":"node yt-subs-cli.js","test":"npm run test:unit && npm run test:e2e","coverage":"node --test --experimental-test-coverage --test-coverage-include=\"yt-subs-*.js\" yt-subs.*.unit.test.js","mcp:http":"node yt-subs-mcp.js http","test:e2e":"node --test yt-subs.*.e2e.test.js","mcp:stdio":"node yt-subs-mcp.js stdio","test:unit":"node --test yt-subs.*.unit.test.js","list-files":"npm pack && tar -xvzf *.tgz && rm -rf package *.tgz"},"_npmUser":{"name":"bguiz","email":"npmjs@bguiz.com"},"repository":{"url":"git+https://github.com/bguiz/yt-subs.git","type":"git"},"_npmVersion":"11.11.0","description":"downloads transcript files from youtube videos","directories":{},"_nodeVersion":"24.14.1","dependencies":{"youtube-transcript-plus":"2.0.0","@modelcontextprotocol/sdk":"1.29.0"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/ytsubs_0.1.1_1776578348268_0.6370606022334786","host":"s3://npm-registry-packages-npm-production"}},"0.1.2":{"name":"ytsubs","version":"0.1.2","keywords":["youtube","subtitles","captions","transcript"],"author":{"name":"bguiz"},"license":"MIT","_id":"ytsubs@0.1.2","maintainers":[{"name":"bguiz","email":"npmjs@bguiz.com"}],"homepage":"https://github.com/bguiz/yt-subs#readme","bugs":{"url":"https://github.com/bguiz/yt-subs/issues"},"bin":{"ytsubs":"yt-subs-cli.js","ytsubs-mcp":"yt-subs-mcp.js"},"dist":{"shasum":"8e1862069aa521fc2c447e828d9bf286921d4e7e","tarball":"https://registry.npmjs.org/ytsubs/-/ytsubs-0.1.2.tgz","fileCount":5,"integrity":"sha512-DpYRaFbBIsdzsYXrw+507jm5o1GR2WNKFU2r7G5Dqn59W/3irAuwwqTSzEHGPkAZMV75aevDjSCvqfXnA+afVw==","signatures":[{"sig":"MEUCICldjVrWCLNcM2/Otce00SBHkrIzoE5jVi3B4sWH2qH4AiEAwzvGnTPxDZOvfo43DaoGXz2J0/xynpE+2lCOeLgg38Y=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":16393},"main":"yt-subs-sdk.js","type":"module","gitHead":"472659e397d6dbf9e740e7cfd27d77eb4f22884f","scripts":{"cli":"node yt-subs-cli.js","test":"npm run test:unit && npm run test:e2e","coverage":"node --test --experimental-test-coverage --test-coverage-include=\"yt-subs-*.js\" yt-subs.*.unit.test.js","mcp:http":"node yt-subs-mcp.js http","test:e2e":"node --test yt-subs.*.e2e.test.js","mcp:stdio":"node yt-subs-mcp.js stdio","test:unit":"node --test yt-subs.*.unit.test.js","list-files":"npm pack && tar -xvzf *.tgz && rm -rf package *.tgz"},"_npmUser":{"name":"bguiz","email":"npmjs@bguiz.com"},"repository":{"url":"git+https://github.com/bguiz/yt-subs.git","type":"git"},"_npmVersion":"11.11.0","description":"downloads transcript files from youtube videos","directories":{},"_nodeVersion":"24.14.1","dependencies":{"youtube-transcript-plus":"2.0.0","@modelcontextprotocol/sdk":"1.29.0"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/ytsubs_0.1.2_1776603947535_0.5420191465660886","host":"s3://npm-registry-packages-npm-production"}},"0.1.3":{"name":"ytsubs","version":"0.1.3","keywords":["youtube","subtitles","captions","transcript"],"author":{"name":"bguiz"},"license":"MIT","_id":"ytsubs@0.1.3","maintainers":[{"name":"bguiz","email":"npmjs@bguiz.com"}],"homepage":"https://github.com/bguiz/yt-subs#readme","bugs":{"url":"https://github.com/bguiz/yt-subs/issues"},"bin":{"ytsubs":"yt-subs-cli.js","ytsubs-mcp":"yt-subs-mcp.js"},"dist":{"shasum":"9b7039bda6dd2454853a0eada0f70a56ac76ccb1","tarball":"https://registry.npmjs.org/ytsubs/-/ytsubs-0.1.3.tgz","fileCount":5,"integrity":"sha512-3YiQqrYhZSwpK/9M7MMJmpmxlRwu0ztdZ3LT/PCofAdWf2C6WWNnBFQRK+dEyvhFUqAv7L/5RrFnwce4ywszPA==","signatures":[{"sig":"MEUCIQCNHjvM+AaCm8ywDGLnVVZIhxyKkVOueo2az68xVolo9gIgHkk54NxsRhmK9j5gyVN2xxOxu/LWJgCHR08/1hAiKK8=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":16683},"main":"yt-subs-sdk.js","type":"module","gitHead":"a8ee1cf6aba524f811dec554ea33d39a2aa25ad8","scripts":{"cli":"node yt-subs-cli.js","test":"npm run test:unit && npm run test:e2e","coverage":"node --test --experimental-test-coverage --test-coverage-include=\"yt-subs-*.js\" yt-subs.*.unit.test.js","mcp:http":"node yt-subs-mcp.js http","test:e2e":"node --test yt-subs.*.e2e.test.js","mcp:stdio":"node yt-subs-mcp.js stdio","test:unit":"node --test yt-subs.*.unit.test.js","list-files":"npm pack && tar -xvzf *.tgz && rm -rf package *.tgz"},"_npmUser":{"name":"bguiz","email":"npmjs@bguiz.com"},"repository":{"url":"git+https://github.com/bguiz/yt-subs.git","type":"git"},"_npmVersion":"11.11.0","description":"downloads transcript files from youtube videos","directories":{},"_nodeVersion":"24.14.1","dependencies":{"youtube-transcript-plus":"2.0.0","@modelcontextprotocol/sdk":"1.29.0"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/ytsubs_0.1.3_1776679147849_0.3983421069564552","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"ytsubs","version":"0.2.0","keywords":["youtube","subtitles","captions","transcript"],"author":{"name":"bguiz"},"license":"MIT","_id":"ytsubs@0.2.0","maintainers":[{"name":"bguiz","email":"npmjs@bguiz.com"}],"homepage":"https://github.com/bguiz/yt-subs#readme","bugs":{"url":"https://github.com/bguiz/yt-subs/issues"},"bin":{"ytsubs":"yt-subs-cli.js","ytsubs-mcp":"yt-subs-mcp.js"},"dist":{"shasum":"40faf510069e905b44b9543eb6b4292fd2ad14aa","tarball":"https://registry.npmjs.org/ytsubs/-/ytsubs-0.2.0.tgz","fileCount":5,"integrity":"sha512-oZCW/KuFwdSHXq6ka47KuUKnHO79acI//51SDiCr/hyBia6ozoAuDkHCFEqa/OQQ7d5VvUcpejVikvEcNQxDog==","signatures":[{"sig":"MEUCIGenpE41k911uVwPdcHT/X0ZfqBRbRFZS4rfGcBVHdsKAiEA0pv+ceAXQb2m/NkZKXMBCv7azOTLH3HApqzOPz7yMpw=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":17246},"main":"yt-subs-sdk.js","type":"module","gitHead":"efe742736ff90720d2a88bb7d1197369ee7c2f94","scripts":{"cli":"node yt-subs-cli.js","test":"npm run test:unit && npm run test:e2e","coverage":"node --test --experimental-test-coverage --test-coverage-include=\"yt-subs-*.js\" yt-subs.*.unit.test.js","mcp:http":"node yt-subs-mcp.js http","test:e2e":"node --test yt-subs.*.e2e.test.js","mcp:stdio":"node yt-subs-mcp.js stdio","test:unit":"node --test yt-subs.*.unit.test.js","list-files":"npm pack && tar -xvzf *.tgz && rm -rf package *.tgz"},"_npmUser":{"name":"bguiz","email":"npmjs@bguiz.com"},"repository":{"url":"git+https://github.com/bguiz/yt-subs.git","type":"git"},"_npmVersion":"11.11.0","description":"downloads transcript files from youtube videos","directories":{},"_nodeVersion":"24.14.1","dependencies":{"youtube-transcript-plus":"2.0.0","@modelcontextprotocol/sdk":"1.29.0"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/ytsubs_0.2.0_1776847879013_0.8913595898806206","host":"s3://npm-registry-packages-npm-production"}},"0.3.0":{"name":"ytsubs","version":"0.3.0","description":"downloads transcript files from youtube videos","type":"module","engines":{"node":">=22"},"bin":{"ytsubs":"src/yt-subs-cli.js","ytsubs-mcp":"src/yt-subs-mcp.js","ytsubs-server":"src/yt-subs-server.js","ytsubs-docs-openapi":"src/yt-subs-docs-openapi.js"},"main":"src/yt-subs-sdk.js","exports":{".":"./src/yt-subs-sdk.js","./cli":"./src/yt-subs-cli.js","./mcp":"./src/yt-subs-mcp.js","./server":"./src/yt-subs-server.js","./docs-openapi":"./src/yt-subs-docs-openapi.js"},"license":"MIT","author":{"name":"bguiz"},"keywords":["youtube","subtitles","captions","transcript"],"homepage":"https://github.com/bguiz/yt-subs#readme","bugs":{"url":"https://github.com/bguiz/yt-subs/issues"},"repository":{"type":"git","url":"git+https://github.com/bguiz/yt-subs.git"},"scripts":{"cli":"node src/yt-subs-cli.js","mcp:stdio":"node src/yt-subs-mcp.js stdio","mcp:http":"npm run api:http","mcp:http-fixed":"npm run api:http-fixed","api:http":"node src/yt-subs-server.js","api:http-fixed":"YTSUBS_HOST=127.0.0.1 YTSUBS_PORT=3456 node src/yt-subs-server.js","test":"npm run test:unit && npm run test:e2e","test:unit":"node --test src/yt-subs.*.unit.test.js","test:e2e":"node --test src/yt-subs.*.e2e.test.js","test:e2e-extended":"node --test src/yt-subs.*.e2e.test.js src/yt-subs.*.e2e.extended.test.js","docs:api":"node src/yt-subs-docs-openapi.js","coverage":"node --test --experimental-test-coverage --test-coverage-include=\"src/yt-subs-*.js\" src/yt-subs.*.unit.test.js","lint:comment":"eslint .","lint:code":"biome lint .","format:check":"biome format .","format:write":"biome format --write .","coverage:lcov":"mkdir -p dist/coverage && node --test --experimental-test-coverage --test-coverage-include=\"src/yt-subs-*.js\" --test-reporter=spec --test-reporter-destination=stdout --test-reporter=lcov --test-reporter-destination=dist/coverage/coverage.lcov src/yt-subs.*.unit.test.js","check:prepush":"npm run lint:comment && npm run lint:code && npm run format:check && npm run coverage","list-files":"npm pack && tar -xvzf *.tgz && rm -rf package *.tgz","prepare":"husky"},"dependencies":{"@modelcontextprotocol/sdk":"1.29.0","youtube-transcript-plus":"2.0.0"},"devDependencies":{"@biomejs/biome":"2.4.12","eslint":"10.2.1","eslint-plugin-jsdoc":"62.9.0","husky":"9.1.7"},"gitHead":"ba326360e4a43d1e8bfe19381c0cdfacaa2f6439","_id":"ytsubs@0.3.0","_nodeVersion":"24.14.1","_npmVersion":"11.11.0","dist":{"integrity":"sha512-se7l5o585PoG/g2mMPwUF5b83yloBW1ip0TPqsXrTsKDcFJILQRh1cMyoR6mJU8W1pvJvvbV1R2hOzQXEwrz/g==","shasum":"0292a1a33506e50925492f86c33592573896ca44","tarball":"https://registry.npmjs.org/ytsubs/-/ytsubs-0.3.0.tgz","fileCount":7,"unpackedSize":42617,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQCzm6XetXhz4lJa7Uo24zXrafdwGwmhiblRrzSdoLxCzgIgQB/UG4yzrSSi/7rbN7xEdzo0NrNeGYScta/xEhcQS+A="}]},"_npmUser":{"name":"bguiz","email":"npmjs@bguiz.com"},"directories":{},"maintainers":[{"name":"bguiz","email":"npmjs@bguiz.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/ytsubs_0.3.0_1777219881567_0.7971606258657933"},"_hasShrinkwrap":false}},"time":{"created":"2026-04-14T09:59:15.947Z","modified":"2026-04-26T16:11:21.862Z","0.0.1":"2026-04-14T09:59:16.080Z","0.1.0":"2026-04-14T14:52:35.171Z","0.1.1":"2026-04-19T05:59:08.400Z","0.1.2":"2026-04-19T13:05:47.682Z","0.1.3":"2026-04-20T09:59:08.035Z","0.2.0":"2026-04-22T08:51:19.148Z","0.3.0":"2026-04-26T16:11:21.756Z"},"bugs":{"url":"https://github.com/bguiz/yt-subs/issues"},"author":{"name":"bguiz"},"license":"MIT","homepage":"https://github.com/bguiz/yt-subs#readme","keywords":["youtube","subtitles","captions","transcript"],"repository":{"type":"git","url":"git+https://github.com/bguiz/yt-subs.git"},"description":"downloads transcript files from youtube videos","maintainers":[{"name":"bguiz","email":"npmjs@bguiz.com"}],"readme":"# ytsubs\n\nExtracts the transcript/ subtitles/ captions of Youtube videos.\n\n## Installation\n\n```shell\nnpm install --global ytsubs\n```\n\nNote: For CLI usage only, no installation is required.\n\nNote: Node.Js v22+ recommended.\n\n## Usage\n\n### CLI usage\n\nRun `npx -y ytsubs` followed by either a Youtube URL, or an 11-character ID.\n\nFor example, all of the following will extract from the same Youtube video.\n\n```shell\nnpx -y ytsubs \"https://www.youtube.com/watch?v=dQw4w9WgXcQ\"\n\nnpx -y ytsubs \"youtu.be/dQw4w9WgXcQ\"\n\nnpx -y ytsubs dQw4w9WgXcQ\n```\n\nThere aren't any options, just one CLI argument to identify which video.\nIf you would like to specify options, use the SDK programmatically instead.\n\n### SDK usage\n\nIn your project, install `ytsubs`:\n\n```shell\nnpm install ytsubs\n```\n\nImport the following methods from the SDK:\n\n```js\nimport {\n    extractFromVideo,\n    outputTextOnly,\n    outputAsMarkdown,\n} from 'ytsubs';\n```\n\nOptionally, create an options object to override defaults:\n\n```js\nconst options = {\n    cache: false,    // default: true  - caches responses in `.yt-subs-cache` under home directory\n    retry: false,    // default: true  - retries with exponential backoff on transient failure\n    language: 'es',  // default: 'en'  - any two-letter BCP-47 language code\n    textType: 'srt', // default: 'text' - can be 'text', 'srt', or 'vtt'\n    timeout: 30e3,  // default: none  - abort after this many milliseconds\n};\n```\n\n**To extract the transcript**:\n\n```js\nconst result = await extractFromVideo({\n    videoUrl, // youtube URL or video ID\n    options, // optional\n});\n```\n\nThe result object will contain the following fields:\n- `videoUrl`: The video URL or ID that was passed in\n- `title`: Video title\n- `metadata`: Miscellaneous info (video ID, thumbnail URLs, etc.)\n- `description`: Video description\n- `text`: Video transcript (as text, SRT, or VTT)\n\n**To convert the transcript to markdown format**:\n\n```js\nconst markdown = outputAsMarkdown(result);\n```\n\n**To convert the transcript to text format**:\n\n```js\nconst text = outputTextOnly(result);\n```\n\n### Generative AI agent-skill usage\n\nThis module comes with its own agent-skill, which complies with\nthe [agent skills specification](https://agentskills.io/specification).\n\nTo use it, you need to place a copy where your generative AI harness\n(e.g. Claude Code, Kimi-CLI) is able to find it:\n\n```shell\nnpx skills add bguiz/ytsubs --skill youtube-transcript-extract\n```\n\nTo invoke it explicitly within your harness use a *command*, e.g.\n\n```text\n/youtube-transcript-extract youtu.be/dQw4w9WgXcQ\n```\n\nYou can also use natural language to invoke it within your harness, e.g.\n\n```text\ndownload subtitles of youtu.be/dQw4w9WgXcQ and save to subtitles.txt\n```\n\nRead the skill file to see how it works:\n[`./.agents/skills/youtube-transcript-extract/SKILL.md`](.agents/skills/youtube-transcript-extract/SKILL.md)\n\n### MCP server usage\n\nThis module comes with its own MCP server, which complies with\nthe [Model Context Protocol specification](https://modelcontextprotocol.io/specification/2025-11-25)\n(dated 2025/11/25).\nIt supports both **stdio** and **streamable HTTP** transports.\n\n**Terminal**:\nTo run the MCP server in a terminal, enter the following command:\n\n```shell\nnpx -y -p ytsubs ytsubs-mcp stdio # for stdio transport\nnpx -y -p ytsubs ytsubs-mcp http # for streamable HTTP transport\n```\n\n**Environment variables** (HTTP transport only):\n\n| Variable | Default | Description |\n|---|---|---|\n| `YTSUBS_PORT` | `0` | Port to listen on. `0` assigns an ephemeral OS port. |\n| `YTSUBS_HOST` | `127.0.0.1` | Interface to bind. Use `0.0.0.0` for all interfaces. |\n\nExample — bind to a fixed port on all interfaces:\n\n```shell\nYTSUBS_HOST=0.0.0.0 YTSUBS_PORT=3456 npx -y -p ytsubs ytsubs-mcp http\n```\n\n**Inspector**:\nTo connect using the MCP inspector tool:\n\n```shell\nMCP_AUTO_OPEN_ENABLED=false DANGEROUSLY_OMIT_AUTH=true npx @modelcontextprotocol/inspector $( which node ) $PWD/yt-subs-mcp.js\n```\n\nRun the above command to start an MCP client that is connected to\nthe MCP server over stdio.\nYou should output similar to:\n\n```text\n🚀 MCP Inspector is up and running at:\n   http://localhost:6274\n```\n\nVisit that URL in a browser, press the \"connect\" button,\nthen press the \"list tools\" button,\nthen press the \"youtube-transcript-extract\" button,\nand finally, press the \"run tool\" button.\n\nIn the \"history\" pane, you should see a new invocation of \"tools/call\".\nExpand the view from this using the triangular icon,\nand yopu will be able to see both the request and response in full.\n\n**Programmatically**:\nTo run and connect programmatically from Node.JS:\n\nImport MCP modules.\n\n```js\nimport { Client } from '@modelcontextprotocol/sdk/client/index.js';\nimport { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';\n```\n\nFor MCP over *stdio*, initialise a `transport` object like this (simple):\n\n```js\nconst transport = new StdioClientTransport({\n    command: 'ytsubs-mcp',\n    args: ['stdio'],\n    stderr: 'pipe',\n});\n```\n\nFor MCP over *streamable HTTP*, initialise a `transport` object like this (complex):\n\n```js\nserverProcess = spawn(\n    'ytsubs-mcp',\n    ['http'], {\n        stdio: ['ignore', 'ignore', 'pipe'],\n    },\n);\n\nlet port;\ntry {\n    port = await new Promise((resolve, reject) => {\n        let stderr = '';\n        serverProcess.stderr.on('data', (chunk) => {\n            stderr += chunk.toString();\n            const match = stderr.match(/LISTEN (\\d+)/);\n            if (match) {\n                resolve(parseInt(match[1], 10));\n            }\n        });\n        serverProcess.on('error', reject);\n        serverProcess.on('close', (code) => {\n            reject(new Error(`server exited (code ${code}) before announcing port`));\n        });\n    });\n} catch (err) {\n    if (err.code === 'ENOENT') {\n        assert.fail('ytsubs-mcp not found on PATH — run in the project directory: npm link');\n    }\n    throw err;\n}\n\nconst transport = new StreamableHTTPClientTransport(\n    new URL(`http://127.0.0.1:${port}/mcp`),\n);\n```\n\nThen create an MCP client that connects to the `transport`.\n\n```js\nclient = new Client({ name: 'test-client', version: '0.0.1' });\nawait client.connect(transport);\n\n// interact with MCP server using client\nawait client.listTools();\nawait client.callTool({\n    name: 'youtube-transcript-extract',\n    arguments: { videoUrl: VIDEO_URL },\n});\n```\n\n**Docker**:\n\nBuild the image:\n\n```shell\ndocker build -t ytsubs .\n```\n\nRun the MCP HTTP server in a container, with a persistent cache volume and port binding:\n\n```shell\ndocker run --rm \\\n  -v ~/.yt-subs-cache:/root/.yt-subs-cache \\\n  -p 3456:3456 \\\n  ytsubs\n```\n\nThe server listens on `http://localhost:3456/mcp` (bound to all interfaces inside the container via `YTSUBS_HOST=0.0.0.0`).\n\n### OpenAPI spec\n\nAn [OpenAPI 3.1.0](https://spec.openapis.org/oas/v3.1.0) spec for the MCP HTTP server is provided in [`openapi.yaml`](./openapi.yaml).\n\nTo browse it interactively using [Swagger UI](https://swagger.io/tools/swagger-ui/)\n(start the MCP HTTP server first, then run):\n\n```shell\nnpm run docs:api\n```\n\nThen open `http://127.0.0.1:8080/` in a browser.\nSet the port with `YTSUBS_DOCS_PORT` (default `8080`).\n\n## Contributing\n\nYour contributions are welcome!\n\n### Development tooling\n\nThe following commands are available for local development.\n\n| Command | Tool | What it does |\n|---|---|---|\n| `npm run lint:comment` | `eslint`, `eslint-plugin-jsdoc` | Checks JSDoc comment structure |\n| `npm run lint:code` | `biome` | Validates code lint rules |\n| `npm run format:check` | `biome` | Validate formatting |\n| `npm run format:write` | `biome` | Same as `format:check`, then overwrites files in place |\n| `npm run test` | `node` | Runs all tests |\n| `npm run test:unit` | `node` | Runs only unit tests |\n| `npm run test:e2e` | `node` | Runs only e2e tests |\n| `npm run test:e2e-extended` | `node` | Runs e2e tests, which are not intended for CI |\n| `npm run docs:api` | `node` | Serves OpenAPI spec UI at `http://127.0.0.1:8080/` |\n| `npm run coverage` | `node` | Same as `test`, and adds line/branch/function code coverage report |\n| `npm run coverage:lcov` | `node` | Same as `coverage`, also writes `coverage.lcov`, intended for upload to Codecov |\n\n\nA subset of these checks also run automatically as a **pre-push git hook** (via Husky).\nTo bypass temporarily (not recommended): `git push --no-verify`.\nTo invoke them manually:\n\n```shell\nnpm run check:prepush\n```\n\n### Submitting an update\n\nBase set up:\n\n[Fork this repo](https://github.com/bguiz/ytsubs/fork) on Github.\n\n```shell\ngit clone git@github.com:${YOUR_GITHUB_USERNAME}/ytsubs.git\ncd ytsubs\nnpm install\nnpm link # needed to test `npx` equivalent, used in e2e tests\nytsubs # test that npm link is active\n```\n\nCreate a new branch prefixed with `feat/`, `fix/`, `docs/`, `refactor/`, or `test/`.\n\n```shell\ngit fetch origin main:main\ngit checkout main\n\ngit checkout -b feat/my-new-feature # for features\ngit checkout -b fix/my-bug-fix # for bugs\ngit checkout -b docs/my-docs-update # for documentation\ngit checkout -b refactor/my-code-quality-improvement # for refactors\ngit checkout -b test/my-new-test # for refactors\n```\n\nMake your changes, then ensure that:\n- there are no regressions, and\n- that code coverage has not worsened\n\n```shell\nnpm run test # run both unit tests and end-to-end tests\n\nnpm run coverage # run unit tests and measure code coverage\n```\n\nPush your git branch to the github remote of your fork:\n\n```shell\ngit push origin ${YOUR_BRANCH_NAME}\n```\n\nThen [submit a Github PR](https://github.com/bguiz/ytsubs/pulls)\nbased on the branch that you have just pushed.\n\nWhen you `git push` your branch associated with a PR,\nthis project will kick off a Github CI workflow,\nwhich you can find in [`.github/workflows/ci.yml`](./.github/workflows/ci.yml).\n\n### Submitting a request\n\n[Submit a Github issue](https://github.com/bguiz/ytsubs/issues).\n\n## Author\n\n[Brendan Graetz](https://bguiz.com/)\n\n## Licence\n\nMIT\n","readmeFilename":"README.md"}