[{"data":1,"prerenderedAt":3747},["ShallowReactive",2],{"navigation":3,"/blog/software-development/one-mcp-server-two-auth-stories-claude-code-vs-claude-desktop-post":754,"/blog/software-development/one-mcp-server-two-auth-stories-claude-code-vs-claude-desktop-surround":1550,"/blog/software-development/one-mcp-server-two-auth-stories-claude-code-vs-claude-desktop-related":1555},[4,70,207,324,741],{"title":5,"path":6,"stem":7,"children":8,"page":69},"Case Study","/blog/case-study","blog/case-study",[9,13,17,21,25,29,33,37,41,45,49,53,57,61,65],{"title":10,"path":11,"stem":12},"Ambistream – Multi-Layer Streaming Platform","/blog/case-study/ambistream-building-a-multi-layer-streaming-platform-from-a-spark-of-an-idea","blog/case-study/ambistream-building-a-multi-layer-streaming-platform-from-a-spark-of-an-idea",{"title":14,"path":15,"stem":16},"Custom Chromecast & AirPlay Casting App","/blog/case-study/chromecast-airplay-casting-app-case-study","blog/case-study/chromecast-airplay-casting-app-case-study",{"title":18,"path":19,"stem":20},"Direct Music Licensing Platform Case Study","/blog/case-study/dlm-music-catalog-case-study","blog/case-study/dlm-music-catalog-case-study",{"title":22,"path":23,"stem":24},"Assembly Instructions App - Galeco Mobile App","/blog/case-study/galeco-mobile-app-case-study","blog/case-study/galeco-mobile-app-case-study",{"title":26,"path":27,"stem":28},"MemoSonic: Building an Audio Memory Game","/blog/case-study/how-we-built-memosonic-accessible-audio-memory-game-flutter","blog/case-study/how-we-built-memosonic-accessible-audio-memory-game-flutter",{"title":30,"path":31,"stem":32},"Loyalty Program App for Shopping Mall","/blog/case-study/loyalty-program-application-case-study","blog/case-study/loyalty-program-application-case-study",{"title":34,"path":35,"stem":36},"Mobile Application for Music Catalogs","/blog/case-study/mobile-app-for-music-catalog","blog/case-study/mobile-app-for-music-catalog",{"title":38,"path":39,"stem":40},"Universal Music Data Parser for 20+ Platforms","/blog/case-study/musicdata-lab-universal-music-data-parser-case-study","blog/case-study/musicdata-lab-universal-music-data-parser-case-study",{"title":42,"path":43,"stem":44},"Panther ML/AI Pricing Recommendation Tool","/blog/case-study/panther-pricing-recommendation-tool-case-study","blog/case-study/panther-pricing-recommendation-tool-case-study",{"title":46,"path":47,"stem":48},"4D Grupa Roofing Wholesalers Platform","/blog/case-study/roofing-wholesalers-website-case-study","blog/case-study/roofing-wholesalers-website-case-study",{"title":50,"path":51,"stem":52},"Talent Alpha HR Frontend Platform","/blog/case-study/talent-alpha-hr-platform-case-study","blog/case-study/talent-alpha-hr-platform-case-study",{"title":54,"path":55,"stem":56},"Ticketing & Events Platform Development","/blog/case-study/ticketing-events-platform-case-study","blog/case-study/ticketing-events-platform-case-study",{"title":58,"path":59,"stem":60},"Turn Fans into Superfans — Roadie.co","/blog/case-study/turn-fans-into-superfans-roadie-co","blog/case-study/turn-fans-into-superfans-roadie-co",{"title":62,"path":63,"stem":64},"Walkative 2.0 Global Booking Engine","/blog/case-study/walkative-2-booking-platform-case-study","blog/case-study/walkative-2-booking-platform-case-study",{"title":66,"path":67,"stem":68},"Why Merch Is the New Royalty Check, and How Tech Is Closing the Gap","/blog/case-study/why-merch-is-the-new-royalty-check","blog/case-study/why-merch-is-the-new-royalty-check",false,{"title":71,"path":72,"stem":73,"children":74,"page":69},"Music Data","/blog/music-data","blog/music-data",[75,79,83,87,91,95,99,103,107,111,115,119,123,127,131,135,139,143,147,151,155,159,163,167,171,175,179,183,187,191,195,199,203],{"title":76,"path":77,"stem":78},"13 Distributors, 5 File Formats, Zero Standards -The Reality of Music Royalty Data","/blog/music-data/13-distributors-5-file-formats-zero-standards-the-reality-of-music-royalty-data","blog/music-data/13-distributors-5-file-formats-zero-standards-the-reality-of-music-royalty-data",{"title":80,"path":81,"stem":82},"830 Ways to Say Spotify - Normalizing Music Streaming Data","/blog/music-data/830-ways-to-say-spotify-normalizing-music-streaming-data","blog/music-data/830-ways-to-say-spotify-normalizing-music-streaming-data",{"title":84,"path":85,"stem":86},"Why Music Companies Need AI-Powered Analytics (And How We Built One)","/blog/music-data/ai-powered-analytics-dashboard-django-clickhouse-ollama","blog/music-data/ai-powered-analytics-dashboard-django-clickhouse-ollama",{"title":88,"path":89,"stem":90},"AI Rehearsal: Spaced Repetition for Your Musical Ideas","/blog/music-data/ai-rehearsal-spaced-repetition-for-musical-ideas","blog/music-data/ai-rehearsal-spaced-repetition-for-musical-ideas",{"title":92,"path":93,"stem":94},"Audio Project Organization Is a Mess — Here's Why","/blog/music-data/audio-project-organization-mess","blog/music-data/audio-project-organization-mess",{"title":96,"path":97,"stem":98},"Why Audio Search Is Still Broken and How to Fix It with Embeddings","/blog/music-data/audio-search-broken-fix-with-embeddings","blog/music-data/audio-search-broken-fix-with-embeddings",{"title":100,"path":101,"stem":102},"AI Song Structure Analysis: Intro, Verse, Chorus","/blog/music-data/automatic-song-structure-analysis-how-ai-detects-intro-verse-chorus","blog/music-data/automatic-song-structure-analysis-how-ai-detects-intro-verse-chorus",{"title":104,"path":105,"stem":106},"The Broken Feedback Loop in Music Collaboration","/blog/music-data/broken-feedback-loop-music-collaboration","blog/music-data/broken-feedback-loop-music-collaboration",{"title":108,"path":109,"stem":110},"Building a Claude Skill for DDEX Validation: Automate Music Metadata Checks with AI","/blog/music-data/building-a-claude-skill-for-ddex-validation-music-metadata","blog/music-data/building-a-claude-skill-for-ddex-validation-music-metadata",{"title":112,"path":113,"stem":114},"Building a Custom Music Delivery Platform on the Revelator API","/blog/music-data/building-a-custom-music-delivery-platform-on-the-revelator-api","blog/music-data/building-a-custom-music-delivery-platform-on-the-revelator-api",{"title":116,"path":117,"stem":118},"Building a Suno AI Remix App with Nuxt & Firebase","/blog/music-data/building-a-suno-remix-app-with-nuxt-and-firebase","blog/music-data/building-a-suno-remix-app-with-nuxt-and-firebase",{"title":120,"path":121,"stem":122},"C2PA & DDEX: Authenticity Meets Rights in the Age of AI Music","/blog/music-data/c2pa-and-ddex-authenticity-meets-rights-in-the-age-of-ai-music","blog/music-data/c2pa-and-ddex-authenticity-meets-rights-in-the-age-of-ai-music",{"title":124,"path":125,"stem":126},"C2PA in Music: A Claude MCP for Reading Content Provenance","/blog/music-data/c2pa-in-music-mcp","blog/music-data/c2pa-in-music-mcp",{"title":128,"path":129,"stem":130},"Data Modeling in MongoDB Using Design Patterns","/blog/music-data/data-modeling-in-mongodb-with-the-usage-of-design-patterns","blog/music-data/data-modeling-in-mongodb-with-the-usage-of-design-patterns",{"title":132,"path":133,"stem":134},"Office Hours with MusicTech Lab's DDEX Expert","/blog/music-data/ddex-office-hours-musictech","blog/music-data/ddex-office-hours-musictech",{"title":136,"path":137,"stem":138},"DDEX Open Source Projects Review","/blog/music-data/ddex-open-source-projects-review","blog/music-data/ddex-open-source-projects-review",{"title":140,"path":141,"stem":142},"Extracting Data from Ableton .als and .asd Files","/blog/music-data/extracting-data-from-ableton-als-asd-files","blog/music-data/extracting-data-from-ableton-als-asd-files",{"title":144,"path":145,"stem":146},"Maciej Dulski on Sound Connections Podcast","/blog/music-data/from-startups-to-musictech-maciej-dulski-on-sound-connections-podcast","blog/music-data/from-startups-to-musictech-maciej-dulski-on-sound-connections-podcast",{"title":148,"path":149,"stem":150},"How to Transcribe Video to Text Using OpenAI Whisper","/blog/music-data/how-to-transcribe-video-to-text-using-whisper","blog/music-data/how-to-transcribe-video-to-text-using-whisper",{"title":152,"path":153,"stem":154},"Epidemic Sound MCP with Claude for Devs","/blog/music-data/how-to-use-epidemic-sound-mcp-with-claude","blog/music-data/how-to-use-epidemic-sound-mcp-with-claude",{"title":156,"path":157,"stem":158},"Hybrid Database Model in Django for Speed","/blog/music-data/hybrid-database-model-in-django-as-a-performance-booster","blog/music-data/hybrid-database-model-in-django-as-a-performance-booster",{"title":160,"path":161,"stem":162},"Introduction to generating DDEX file using Python","/blog/music-data/introduction-to-generating-ddex-file-using-python","blog/music-data/introduction-to-generating-ddex-file-using-python",{"title":164,"path":165,"stem":166},"Maintaining Music Tech Tools: The SLA Dilemma for Small Teams","/blog/music-data/maintaining-music-tech-tools-the-sla-dilemma-for-small-teams","blog/music-data/maintaining-music-tech-tools-the-sla-dilemma-for-small-teams",{"title":168,"path":169,"stem":170},"Querying Bandcamp Revenue Reports with Natural Language — Meet mtl-bandcamp-mcp","/blog/music-data/mtl-bandcamp-mcp-open-source-revenue-dashboard","blog/music-data/mtl-bandcamp-mcp-open-source-revenue-dashboard",{"title":172,"path":173,"stem":174},"mtl-metadata-mcp: Open Source Audio Metadata Embedding for Claude Code","/blog/music-data/mtl-metadata-mcp-open-source-audio-metadata-embedding","blog/music-data/mtl-metadata-mcp-open-source-audio-metadata-embedding",{"title":176,"path":177,"stem":178},"MusicTech Resources for Builders","/blog/music-data/musictech-resources-curated-insights-for-the-musictech-builders","blog/music-data/musictech-resources-curated-insights-for-the-musictech-builders",{"title":180,"path":181,"stem":182},"Poland's Creative Tech and MusicTech Rise","/blog/music-data/polands-creative-tech-sector-is-on-the-rise-and-musictech-is-part-of-it","blog/music-data/polands-creative-tech-sector-is-on-the-rise-and-musictech-is-part-of-it",{"title":184,"path":185,"stem":186},"Batch ISRC Enrichment That Turns Messy Catalogs Into Clean Data","/blog/music-data/scout-isrc-metadata-enrichment-spotify-musicbrainz","blog/music-data/scout-isrc-metadata-enrichment-spotify-musicbrainz",{"title":188,"path":189,"stem":190},"Music Self-Publishing: The Emuze.me Story","/blog/music-data/self-publishing-in-the-music-industry-a-tale-of-emuze-me","blog/music-data/self-publishing-in-the-music-industry-a-tale-of-emuze-me",{"title":192,"path":193,"stem":194},"Understanding the API First Approach","/blog/music-data/understanding-the-api-first-approach","blog/music-data/understanding-the-api-first-approach",{"title":196,"path":197,"stem":198},"The Voice Memo Graveyard Problem","/blog/music-data/voice-memo-graveyard-problem","blog/music-data/voice-memo-graveyard-problem",{"title":200,"path":201,"stem":202},"Which Database for Music Data? Redshift vs BigQuery vs ClickHouse and When to Use Each","/blog/music-data/which-database-for-music-data-redshift-vs-bigquery-vs-clickhouse","blog/music-data/which-database-for-music-data-redshift-vs-bigquery-vs-clickhouse",{"title":204,"path":205,"stem":206},"Why we decided to use wavesurfer.js","/blog/music-data/why-we-decided-to-use-wavesurfer","blog/music-data/why-we-decided-to-use-wavesurfer",{"title":208,"path":209,"stem":210,"children":211,"page":69},"Newsletter","/blog/newsletter","blog/newsletter",[212,216,220,224,228,232,236,240,244,248,252,256,260,264,268,272,276,280,284,288,292,296,300,304,308,312,316,320],{"title":213,"path":214,"stem":215},"Music Industry Tech Openings (April 2024 Update)","/blog/newsletter/music-industry-tech-openings-april-2024-update","blog/newsletter/music-industry-tech-openings-april-2024-update",{"title":217,"path":218,"stem":219},"Music Industry Tech Openings (April 2025 Update)","/blog/newsletter/music-industry-tech-openings-april-2025-update","blog/newsletter/music-industry-tech-openings-april-2025-update",{"title":221,"path":222,"stem":223},"Music Industry Tech Openings (August 2024 Update)","/blog/newsletter/music-industry-tech-openings-august-2024-update","blog/newsletter/music-industry-tech-openings-august-2024-update",{"title":225,"path":226,"stem":227},"Music Industry Tech Openings (December 2024 Update)","/blog/newsletter/music-industry-tech-openings-december-2024-update","blog/newsletter/music-industry-tech-openings-december-2024-update",{"title":229,"path":230,"stem":231},"Music Industry Tech Openings (February 2025 Update)","/blog/newsletter/music-industry-tech-openings-february-2025-update","blog/newsletter/music-industry-tech-openings-february-2025-update",{"title":233,"path":234,"stem":235},"Music Industry Tech Openings (January 2025 Update)","/blog/newsletter/music-industry-tech-openings-january-2025-update","blog/newsletter/music-industry-tech-openings-january-2025-update",{"title":237,"path":238,"stem":239},"Music Industry Tech Openings (July 2024 Update)","/blog/newsletter/music-industry-tech-openings-july-2024-update","blog/newsletter/music-industry-tech-openings-july-2024-update",{"title":241,"path":242,"stem":243},"Music Industry Tech Openings (June 2024 Update)","/blog/newsletter/music-industry-tech-openings-june-2024-update","blog/newsletter/music-industry-tech-openings-june-2024-update",{"title":245,"path":246,"stem":247},"Music Industry Tech Openings (March 2025 Update)","/blog/newsletter/music-industry-tech-openings-march-2025-update","blog/newsletter/music-industry-tech-openings-march-2025-update",{"title":249,"path":250,"stem":251},"Music Industry Tech Openings (May 2024 Update)","/blog/newsletter/music-industry-tech-openings-may-2024-update","blog/newsletter/music-industry-tech-openings-may-2024-update",{"title":253,"path":254,"stem":255},"Music Industry Tech Openings (May 2025 Update)","/blog/newsletter/music-industry-tech-openings-may-2025","blog/newsletter/music-industry-tech-openings-may-2025",{"title":257,"path":258,"stem":259},"Music Industry Tech Openings (November 2024 Update)","/blog/newsletter/music-industry-tech-openings-november-2024-update","blog/newsletter/music-industry-tech-openings-november-2024-update",{"title":261,"path":262,"stem":263},"Music Industry Tech Openings (October 2024 Update)","/blog/newsletter/music-industry-tech-openings-october-2024-update","blog/newsletter/music-industry-tech-openings-october-2024-update",{"title":265,"path":266,"stem":267},"Music Industry Tech Openings (September 2024 Update)","/blog/newsletter/music-industry-tech-openings-september-2024-update","blog/newsletter/music-industry-tech-openings-september-2024-update",{"title":269,"path":270,"stem":271},"MusicTech Insights #1 by Maciej Dulski","/blog/newsletter/musictech-insights-1-curated-by-maciej-dulski","blog/newsletter/musictech-insights-1-curated-by-maciej-dulski",{"title":273,"path":274,"stem":275},"Provenance, Not Detection, Is the Durable Answer","/blog/newsletter/musictech-insights-10-curated-by-jim-anderson","blog/newsletter/musictech-insights-10-curated-by-jim-anderson",{"title":277,"path":278,"stem":279},"What CMOs can teach us about innovation in uncertain times","/blog/newsletter/musictech-insights-10-curated-by-joanna-kurkowska","blog/newsletter/musictech-insights-10-curated-by-joanna-kurkowska",{"title":281,"path":282,"stem":283},"How AI is Automating the Music Industry Back Office","/blog/newsletter/musictech-insights-12-curated-by-benjamin-james","blog/newsletter/musictech-insights-12-curated-by-benjamin-james",{"title":285,"path":286,"stem":287},"Feeling the MusicTech Momentum","/blog/newsletter/musictech-insights-2-curated-by-maciej-dulski","blog/newsletter/musictech-insights-2-curated-by-maciej-dulski",{"title":289,"path":290,"stem":291},"AI in Music: Hype, Hope, and a Human Touch","/blog/newsletter/musictech-insights-3-curated-by-drew-thurlow","blog/newsletter/musictech-insights-3-curated-by-drew-thurlow",{"title":293,"path":294,"stem":295},"The Music Metadata Conundrum","/blog/newsletter/musictech-insights-4-curated-by-amanda-schupf","blog/newsletter/musictech-insights-4-curated-by-amanda-schupf",{"title":297,"path":298,"stem":299},"7 Rounds in the First 10 Days of November 2025","/blog/newsletter/musictech-insights-5-curated-by-maciej-dulski","blog/newsletter/musictech-insights-5-curated-by-maciej-dulski",{"title":301,"path":302,"stem":303},"The End of an Era: It's All About to Crash","/blog/newsletter/musictech-insights-6-curated-by-sigurdur-arnason","blog/newsletter/musictech-insights-6-curated-by-sigurdur-arnason",{"title":305,"path":306,"stem":307},"Low-Code Magic Won't Solve MusicTech Reality","/blog/newsletter/musictech-insights-7-curated-by-mariusz-smenzyk","blog/newsletter/musictech-insights-7-curated-by-mariusz-smenzyk",{"title":309,"path":310,"stem":311},"The New Economics of Game Music","/blog/newsletter/musictech-insights-8-curated-by-kenny-vaughan","blog/newsletter/musictech-insights-8-curated-by-kenny-vaughan",{"title":313,"path":314,"stem":315},"Music Business Meets Direct-to-Fan","/blog/newsletter/musictech-insights-9-curated-by-yaw-asamani","blog/newsletter/musictech-insights-9-curated-by-yaw-asamani",{"title":317,"path":318,"stem":319},"Victoria Blinova: Human in the loop","/blog/newsletter/musictech-insights-mudder-fracas-podcast-005","blog/newsletter/musictech-insights-mudder-fracas-podcast-005",{"title":321,"path":322,"stem":323},"Who Actually Owns Fenty? (It's Not Roc Nation)","/blog/newsletter/musictech-insights-mudder-fracas-who-actually-owns-fenty","blog/newsletter/musictech-insights-mudder-fracas-who-actually-owns-fenty",{"title":325,"path":326,"stem":327,"children":328,"page":69},"Software Development","/blog/software-development","blog/software-development",[329,333,337,341,345,349,353,357,361,365,369,373,377,381,385,389,393,397,401,405,409,413,417,421,425,429,433,437,441,445,449,453,457,461,465,469,473,477,481,485,489,493,497,501,505,509,513,517,521,525,529,533,537,541,545,549,553,557,561,565,569,573,577,581,585,589,593,597,601,605,609,613,617,621,625,629,633,637,641,645,649,653,657,661,665,669,673,677,681,685,689,693,697,701,705,709,713,717,721,725,729,733,737],{"title":330,"path":331,"stem":332},"Benefits of Outsourcing Software Development","/blog/software-development/10-benefits-of-outsourcing-software-development-services","blog/software-development/10-benefits-of-outsourcing-software-development-services",{"title":334,"path":335,"stem":336},"10 Steps to Find the Best MVP Developers","/blog/software-development/10-steps-to-find-the-best-mvp-developers-for-your-startup-idea","blog/software-development/10-steps-to-find-the-best-mvp-developers-for-your-startup-idea",{"title":338,"path":339,"stem":340},"1,200 Looms Later: How Async Video Became My Development Superpower","/blog/software-development/1200-looms-how-async-video-became-our-development-superpower","blog/software-development/1200-looms-how-async-video-became-our-development-superpower",{"title":342,"path":343,"stem":344},"Communication Strategy in Outsourcing Projects","/blog/software-development/5-steps-to-implement-an-effective-communication-strategy-in-outsourcing-software-development-project","blog/software-development/5-steps-to-implement-an-effective-communication-strategy-in-outsourcing-software-development-project",{"title":346,"path":347,"stem":348},"7 Best Practices for Outsourcing Software Development","/blog/software-development/7-best-practices-for-outsourcing-software-development","blog/software-development/7-best-practices-for-outsourcing-software-development",{"title":350,"path":351,"stem":352},"9 Reasons Why Saleor.io Is Best for eCommerce","/blog/software-development/9-reasons-why-the-saleor-io-platform-is-the-best-choice-for-your-ecommerce-website","blog/software-development/9-reasons-why-the-saleor-io-platform-is-the-best-choice-for-your-ecommerce-website",{"title":354,"path":355,"stem":356},"A Look at Bravelab.io’s Clutch 2021 Year In Review","/blog/software-development/a-look-at-bravelab-ios-clutch-2021-year-in-review","blog/software-development/a-look-at-bravelab-ios-clutch-2021-year-in-review",{"title":358,"path":359,"stem":360},"A quick introduction to profit sharing implementation","/blog/software-development/a-quick-introduction-to-profit-sharing-implementation","blog/software-development/a-quick-introduction-to-profit-sharing-implementation",{"title":362,"path":363,"stem":364},"AI Audio Similarity Search: The Future of Sound Library Discovery","/blog/software-development/ai-audio-similarity-search-for-sound-libraries","blog/software-development/ai-audio-similarity-search-for-sound-libraries",{"title":366,"path":367,"stem":368},"Automate Repetitive Tasks for Better Results","/blog/software-development/automate-repetitive-tasks-to-improve-your-business-performance","blog/software-development/automate-repetitive-tasks-to-improve-your-business-performance",{"title":370,"path":371,"stem":372},"Automating Success: The Art of Unified Documentation","/blog/software-development/automating-success-the-art-of-unified-documentation","blog/software-development/automating-success-the-art-of-unified-documentation",{"title":374,"path":375,"stem":376},"Brave 3.0 Website Redesign, Part 2: Solution","/blog/software-development/brave-3-0-how-we-conducted-website-redesign-part-2-solution","blog/software-development/brave-3-0-how-we-conducted-website-redesign-part-2-solution",{"title":378,"path":379,"stem":380},"Brave 3.0, Part 4: Tech Stack and Recap","/blog/software-development/brave-3-0-part-4-technologies-behind-and-final-series-recap","blog/software-development/brave-3-0-part-4-technologies-behind-and-final-series-recap",{"title":382,"path":383,"stem":384},"Brave 3.0 – redesign process part 1. The Challenge","/blog/software-development/brave-3-0-redesign-process-part-1-challenge","blog/software-development/brave-3-0-redesign-process-part-1-challenge",{"title":386,"path":387,"stem":388},"Brave 3.0 – redesign process, part 3. Lesson learned","/blog/software-development/brave-3-0-redesign-process-part-3-lesson-learned","blog/software-development/brave-3-0-redesign-process-part-3-lesson-learned",{"title":390,"path":391,"stem":392},"Bravelab.io: Top Software Developer by Clutch","/blog/software-development/bravelab-io-is-recognized-as-a-top-custom-software-developer-by-clutch","blog/software-development/bravelab-io-is-recognized-as-a-top-custom-software-developer-by-clutch",{"title":394,"path":395,"stem":396},"Bravelab.io: Top Developer in Poland by Clutch","/blog/software-development/bravelab-io-named-top-software-developer-in-poland-by-clutch","blog/software-development/bravelab-io-named-top-software-developer-in-poland-by-clutch",{"title":398,"path":399,"stem":400},"MusicTech Lab Partners with LALAL.AI","/blog/software-development/bravelab-partners-with-the-audio-lalal-ai","blog/software-development/bravelab-partners-with-the-audio-lalal-ai",{"title":402,"path":403,"stem":404},"MusicTech Lab Partners with The Audio Programmer","/blog/software-development/bravelab-partners-with-the-audio-programmer","blog/software-development/bravelab-partners-with-the-audio-programmer",{"title":406,"path":407,"stem":408},"Bravelab's team about productivity","/blog/software-development/bravelabs-team-about-productivity","blog/software-development/bravelabs-team-about-productivity",{"title":410,"path":411,"stem":412},"Braveloper","/blog/software-development/braveloper","blog/software-development/braveloper",{"title":414,"path":415,"stem":416},"Bravely App: Boost Productivity with Django","/blog/software-development/bravely-app-how-to-be-more-productive-with-django-quick","blog/software-development/bravely-app-how-to-be-more-productive-with-django-quick",{"title":418,"path":419,"stem":420},"DIY MIDI Controller for Ableton with Arduino","/blog/software-development/building-a-diy-midi-controller-for-ableton-live-with-arduino","blog/software-development/building-a-diy-midi-controller-for-ableton-live-with-arduino",{"title":422,"path":423,"stem":424},"C2PA in Ableton: Making AI Music Provenance Visible Inside Your DAW","/blog/software-development/c2pa-in-ableton-max-for-live","blog/software-development/c2pa-in-ableton-max-for-live",{"title":426,"path":427,"stem":428},"Change Detection mechanism in Angular","/blog/software-development/change-detection-mechanism-in-angular","blog/software-development/change-detection-mechanism-in-angular",{"title":430,"path":431,"stem":432},"Communication Channels in Remote Work","/blog/software-development/comparison-of-the-communication-channels-in-remote-work","blog/software-development/comparison-of-the-communication-channels-in-remote-work",{"title":434,"path":435,"stem":436},"Connecting Your Max for Live Device to a Cloud API","/blog/software-development/connecting-your-max-for-live-device-to-a-cloud-api","blog/software-development/connecting-your-max-for-live-device-to-a-cloud-api",{"title":438,"path":439,"stem":440},"From Voice Memo to Studio: The Cross-Platform Problem for Creators","/blog/software-development/cross-platform-problem-for-creators","blog/software-development/cross-platform-problem-for-creators",{"title":442,"path":443,"stem":444},"Cultural transformation through the pandemic era","/blog/software-development/cultural-transformation-through-the-pandemic-era","blog/software-development/cultural-transformation-through-the-pandemic-era",{"title":446,"path":447,"stem":448},"D-Commerce Decoded: Cutting Through the Hype","/blog/software-development/d-commerce-decoded-cutting-through-the-hype","blog/software-development/d-commerce-decoded-cutting-through-the-hype",{"title":450,"path":451,"stem":452},"Dev Meeting 002: Intro to DDD","/blog/software-development/dev-meeting-002-introduction-to-domain-driven-design-ddd","blog/software-development/dev-meeting-002-introduction-to-domain-driven-design-ddd",{"title":454,"path":455,"stem":456},"Dev Meeting 003: Web3 Primer","/blog/software-development/dev-meeting-003-web3-primer","blog/software-development/dev-meeting-003-web3-primer",{"title":458,"path":459,"stem":460},"Dev Meeting 004: Introduction to Event Storming","/blog/software-development/dev-meeting-004-introduction-to-event-storming","blog/software-development/dev-meeting-004-introduction-to-event-storming",{"title":462,"path":463,"stem":464},"Dev Meeting 001: Kubernetes is a Framework","/blog/software-development/dev-meeting-kubernetes-is-a-framework","blog/software-development/dev-meeting-kubernetes-is-a-framework",{"title":466,"path":467,"stem":468},"Did You Know? 10 Developer Tips from Real Codebases","/blog/software-development/did-you-know-dev-tips-part-1","blog/software-development/did-you-know-dev-tips-part-1",{"title":470,"path":471,"stem":472},"10 Surprising MusicTech Facts (Part 2)","/blog/software-development/did-you-know-musictech-facts-part-2","blog/software-development/did-you-know-musictech-facts-part-2",{"title":474,"path":475,"stem":476},"Django-cms and GraphQL","/blog/software-development/django-cms-and-graphql","blog/software-development/django-cms-and-graphql",{"title":478,"path":479,"stem":480},"Does Zappa make it super easy?","/blog/software-development/does-zappa-make-it-super-easy","blog/software-development/does-zappa-make-it-super-easy",{"title":482,"path":483,"stem":484},"Establishing cooperation between Netlify and Bravelab","/blog/software-development/establishing-cooperation-between-netlify-and-bravelab","blog/software-development/establishing-cooperation-between-netlify-and-bravelab",{"title":486,"path":487,"stem":488},"Export Ableton Locators to JSON via Max for Live","/blog/software-development/exporting-ableton-live-locators-to-json-with-max-for-live","blog/software-development/exporting-ableton-live-locators-to-json-with-max-for-live",{"title":490,"path":491,"stem":492},"IT Outsourcing: Success and Failure Factors","/blog/software-development/factors-that-contribute-to-the-success-or-failure-of-an-it-outsourcing-project","blog/software-development/factors-that-contribute-to-the-success-or-failure-of-an-it-outsourcing-project",{"title":494,"path":495,"stem":496},"Flutter 2022 Strategy: Analyzing the Roadmap","/blog/software-development/flutter-strategy-for-2022-analyzing-the-new-flutter-roadmap","blog/software-development/flutter-strategy-for-2022-analyzing-the-new-flutter-roadmap",{"title":498,"path":499,"stem":500},"Git Better #1 — Commit Message Convention","/blog/software-development/git-better-1-see-more-with-a-commit-message-convention","blog/software-development/git-better-1-see-more-with-a-commit-message-convention",{"title":502,"path":503,"stem":504},"Hasura in action. How to use it with Django","/blog/software-development/hasura-in-action","blog/software-development/hasura-in-action",{"title":506,"path":507,"stem":508},"Holacracy why and where we are","/blog/software-development/holacracy-why-and-where-we-are","blog/software-development/holacracy-why-and-where-we-are",{"title":510,"path":511,"stem":512},"How does JavaScript work","/blog/software-development/how-does-javascript-work","blog/software-development/how-does-javascript-work",{"title":514,"path":515,"stem":516},"How important is good UX/UI design?","/blog/software-development/how-important-is-good-ux-ui-design","blog/software-development/how-important-is-good-ux-ui-design",{"title":518,"path":519,"stem":520},"How repetitive tasks impact your business","/blog/software-development/how-repetitive-tasks-impact-your-business","blog/software-development/how-repetitive-tasks-impact-your-business",{"title":522,"path":523,"stem":524},"Becoming a Vue.js Dev: Do Paid Trials Work?","/blog/software-development/how-to-become-a-vue-js-developer-and-whether-paid-trials-in-it-work-out","blog/software-development/how-to-become-a-vue-js-developer-and-whether-paid-trials-in-it-work-out",{"title":526,"path":527,"stem":528},"How to Build an MVP in 6 Steps","/blog/software-development/how-to-build-a-minimum-viable-product-mvp-in-6-steps","blog/software-development/how-to-build-a-minimum-viable-product-mvp-in-6-steps",{"title":530,"path":531,"stem":532},"How to conduct workshops for creative industry?","/blog/software-development/how-to-conduct-workshops-for-creative-industry","blog/software-development/how-to-conduct-workshops-for-creative-industry",{"title":534,"path":535,"stem":536},"How to easily create form in Angular","/blog/software-development/how-to-easily-create-form-in-angular","blog/software-development/how-to-easily-create-form-in-angular",{"title":538,"path":539,"stem":540},"How to export orders in Saleor.io to XLSX file","/blog/software-development/how-to-export-orders-in-saleor-io-to-xlsx-file","blog/software-development/how-to-export-orders-in-saleor-io-to-xlsx-file",{"title":542,"path":543,"stem":544},"Handling High Loads on E-Commerce Platforms","/blog/software-development/how-to-handle-high-loads-on-e-commerce-platform-with-ease","blog/software-development/how-to-handle-high-loads-on-e-commerce-platform-with-ease",{"title":546,"path":547,"stem":548},"How to launch Saleor.io shop instance within 40h","/blog/software-development/how-to-launch-saleor-io-shop-instance-within-40h","blog/software-development/how-to-launch-saleor-io-shop-instance-within-40h",{"title":550,"path":551,"stem":552},"First Steps to Build a Business Relationship","/blog/software-development/how-to-make-the-first-step-to-establish-a-business-relationship","blog/software-development/how-to-make-the-first-step-to-establish-a-business-relationship",{"title":554,"path":555,"stem":556},"Multi-Tenant Apps with Django and Saleor.io","/blog/software-development/how-to-manage-tenants-in-the-multitenant-app-based-on-django-tenants-and-saleor-io-platform","blog/software-development/how-to-manage-tenants-in-the-multitenant-app-based-on-django-tenants-and-saleor-io-platform",{"title":558,"path":559,"stem":560},"Notion Backup Tool Built in 3 Days with Python","/blog/software-development/how-we-built-a-notion-backup-tool-in-3-days-with-pythonvue-and-why","blog/software-development/how-we-built-a-notion-backup-tool-in-3-days-with-pythonvue-and-why",{"title":562,"path":563,"stem":564},"Important new features in Python 3.8","/blog/software-development/important-new-features-in-python-3-8","blog/software-development/important-new-features-in-python-3-8",{"title":566,"path":567,"stem":568},"Installing Proxmox on dedicated server from OVH","/blog/software-development/installing-proxmox-on-dedicated-server-from-ovh","blog/software-development/installing-proxmox-on-dedicated-server-from-ovh",{"title":570,"path":571,"stem":572},"Integrating SignNow E-Signatures into Your Django Application","/blog/software-development/integrating-signnow-e-signatures-into-your-django-application","blog/software-development/integrating-signnow-e-signatures-into-your-django-application",{"title":574,"path":575,"stem":576},"Tempus Metronome and GetSongBPM API","/blog/software-development/integrating-tempus-metronome-with-the-getsongbpm-api-what-bpm-really-means-and-how-to-use-it","blog/software-development/integrating-tempus-metronome-with-the-getsongbpm-api-what-bpm-really-means-and-how-to-use-it",{"title":578,"path":579,"stem":580},"Introducing MusicTech Poland","/blog/software-development/introducing-musictech-poland","blog/software-development/introducing-musictech-poland",{"title":582,"path":583,"stem":584},"Vue.js as a Frontend for Saleor.io","/blog/software-development/is-it-possible-to-use-vue-js-as-a-frontend-for-saleor-io-platform","blog/software-development/is-it-possible-to-use-vue-js-as-a-frontend-for-saleor-io-platform",{"title":586,"path":587,"stem":588},"Is your business ready for the cashless era?","/blog/software-development/is-your-business-ready-for-the-cashless-era","blog/software-development/is-your-business-ready-for-the-cashless-era",{"title":590,"path":591,"stem":592},"Is your face ready to buy?","/blog/software-development/is-your-face-ready-to-buy","blog/software-development/is-your-face-ready-to-buy",{"title":594,"path":595,"stem":596},"JS Frameworks: Trends and Opportunities","/blog/software-development/javascript-trending-frameworks-and-market-opportunities","blog/software-development/javascript-trending-frameworks-and-market-opportunities",{"title":598,"path":599,"stem":600},"Kanban Board: Boost Your Team Productivity","/blog/software-development/kanban-board-methodology-hack-your-companys-productivity","blog/software-development/kanban-board-methodology-hack-your-companys-productivity",{"title":602,"path":603,"stem":604},"Codemagic MCP Server: Trigger Mobile CI Builds from Claude Code","/blog/software-development/mcp-codemagic-trigger-ci-builds-from-claude-code","blog/software-development/mcp-codemagic-trigger-ci-builds-from-claude-code",{"title":606,"path":607,"stem":608},"Verified Human Cert MCP Server: Prove Your Music Is Human-Made, Right from the Terminal","/blog/software-development/mcp-verified-human-cert-open-source","blog/software-development/mcp-verified-human-cert-open-source",{"title":610,"path":611,"stem":612},"Migrating from TravisCI to Github Actions","/blog/software-development/migrating-from-travisci-to-github-actions","blog/software-development/migrating-from-travisci-to-github-actions",{"title":614,"path":615,"stem":616},"MusicTech Lab: Top Software Developer by Clutch","/blog/software-development/musictechlab-is-recognized-as-a-top-custom-software-developer-by-clutch","blog/software-development/musictechlab-is-recognized-as-a-top-custom-software-developer-by-clutch",{"title":618,"path":619,"stem":620},"MusicTech Lab x Verified Human: Building a Trust Layer for Human-Made Music","/blog/software-development/musictechlab_blog_verified_human_partnership","blog/software-development/musictechlab_blog_verified_human_partnership",{"title":622,"path":623,"stem":624},"MusicXML: Standard for Music Notation","/blog/software-development/musicxml-standard-for-music-notation-and-education","blog/software-development/musicxml-standard-for-music-notation-and-education",{"title":626,"path":627,"stem":628},"One MCP Server, Two Auth Stories: Claude Code vs. Claude Desktop","/blog/software-development/one-mcp-server-two-auth-stories-claude-code-vs-claude-desktop","blog/software-development/one-mcp-server-two-auth-stories-claude-code-vs-claude-desktop",{"title":630,"path":631,"stem":632},"Only a few books but dozens of ideas","/blog/software-development/only-a-few-books-but-dozens-of-ideas","blog/software-development/only-a-few-books-but-dozens-of-ideas",{"title":634,"path":635,"stem":636},"Overdue Invoices and Issue Tracker Integration","/blog/software-development/overdue-invoices-integration-with-the-issue-tracking-system","blog/software-development/overdue-invoices-integration-with-the-issue-tracking-system",{"title":638,"path":639,"stem":640},"Performing SAML SSO using JWT in Django","/blog/software-development/performing-saml-sso-using-jwt-in-django","blog/software-development/performing-saml-sso-using-jwt-in-django",{"title":642,"path":643,"stem":644},"Progressive Web Apps for Mobile Development","/blog/software-development/progressive-web-apps-a-new-way-of-creating-mobile-application","blog/software-development/progressive-web-apps-a-new-way-of-creating-mobile-application",{"title":646,"path":647,"stem":648},"Recruitment System: Gmail, Jira, and CRM","/blog/software-development/recruitment-system-integrating-gmail-bravely-jira-slack-and-copper-crm","blog/software-development/recruitment-system-integrating-gmail-bravely-jira-slack-and-copper-crm",{"title":650,"path":651,"stem":652},"Scratch Me: Chrome Extension for Leads","/blog/software-development/scratch-me-a-simple-chrome-extension-which-will-increase-your-productivity","blog/software-development/scratch-me-a-simple-chrome-extension-which-will-increase-your-productivity",{"title":654,"path":655,"stem":656},"Scratch Me – integration with the Copper CRM","/blog/software-development/scratch-me-integration-with-the-copper-crm","blog/software-development/scratch-me-integration-with-the-copper-crm",{"title":658,"path":659,"stem":660},"SignNow MCP Server: E-Signatures Straight from Claude Code","/blog/software-development/signnow-mcp-server-e-signatures-from-claude-code","blog/software-development/signnow-mcp-server-e-signatures-from-claude-code",{"title":662,"path":663,"stem":664},"Music Industry Tech Openings (March 2024 Update)","/blog/software-development/technical-job-opportunities-in-the-music-industry","blog/software-development/technical-job-opportunities-in-the-music-industry",{"title":666,"path":667,"stem":668},"Thanks app – a Management 3.0 solution","/blog/software-development/thanks-app-a-management-3-0-solution","blog/software-development/thanks-app-a-management-3-0-solution",{"title":670,"path":671,"stem":672},"Colonial Pipeline Case: 7 Security Reminders","/blog/software-development/the-case-of-colonial-pipeline-and-7-security-reminders","blog/software-development/the-case-of-colonial-pipeline-and-7-security-reminders",{"title":674,"path":675,"stem":676},"The Evolution and Future of E-commerce Platforms","/blog/software-development/the-evolution-and-future-of-e-commerce-platforms","blog/software-development/the-evolution-and-future-of-e-commerce-platforms",{"title":678,"path":679,"stem":680},"The Gender Gap in the Tech Industry","/blog/software-development/the-gender-gap-in-the-tech-industry","blog/software-development/the-gender-gap-in-the-tech-industry",{"title":682,"path":683,"stem":684},"First Attempt to Implement 4DX at Bravelab.io","/blog/software-development/the-very-first-attempt-to-implement-4dx-in-bravelab-io","blog/software-development/the-very-first-attempt-to-implement-4dx-in-bravelab-io",{"title":686,"path":687,"stem":688},"The WTF Scale: IT Project Complexity","/blog/software-development/the-wtf-programming-scale-measuring-it-project-complexity","blog/software-development/the-wtf-programming-scale-measuring-it-project-complexity",{"title":690,"path":691,"stem":692},"Top 10 articles through the eyes of our developers","/blog/software-development/top-10-articles-through-the-eyes-of-our-developers","blog/software-development/top-10-articles-through-the-eyes-of-our-developers",{"title":694,"path":695,"stem":696},"Top 6 apps made with Flutter","/blog/software-development/top-6-apps-made-with-flutter","blog/software-development/top-6-apps-made-with-flutter",{"title":698,"path":699,"stem":700},"Uber 101: How Uber Made It to the Top","/blog/software-development/uber-101-how-this-ride-sharing-behemoth-made-it-to-the-top","blog/software-development/uber-101-how-this-ride-sharing-behemoth-made-it-to-the-top",{"title":702,"path":703,"stem":704},"MusicTech Lab Partners with Music Glue","/blog/software-development/unifying-artists-and-audiences-exploring-music-glue","blog/software-development/unifying-artists-and-audiences-exploring-music-glue",{"title":706,"path":707,"stem":708},"Why AI Will Defeat Traditional HR","/blog/software-development/warning-why-artificial-intelligence-will-defeat-traditional-hr","blog/software-development/warning-why-artificial-intelligence-will-defeat-traditional-hr",{"title":710,"path":711,"stem":712},"What is a Discovery Document?","/blog/software-development/what-is-discovery-document","blog/software-development/what-is-discovery-document",{"title":714,"path":715,"stem":716},"What is Flutter, and Why is it Worth Considering?","/blog/software-development/what-is-flutter-and-why-is-it-worth-considering","blog/software-development/what-is-flutter-and-why-is-it-worth-considering",{"title":718,"path":719,"stem":720},"What is a Watermarked Song?","/blog/software-development/what-is-watermarked-song","blog/software-development/what-is-watermarked-song",{"title":722,"path":723,"stem":724},"Choosing a Frontend Framework for the Web","/blog/software-development/which-framework-should-you-choose-for-the-frontend-web-platform-development","blog/software-development/which-framework-should-you-choose-for-the-frontend-web-platform-development",{"title":726,"path":727,"stem":728},"Why DAWs Are the Wrong Tool for Starting a Song","/blog/software-development/why-daws-wrong-tool-for-starting-song","blog/software-development/why-daws-wrong-tool-for-starting-song",{"title":730,"path":731,"stem":732},"Why the Programming World Loves Python","/blog/software-development/why-the-programming-world-loves-python","blog/software-development/why-the-programming-world-loves-python",{"title":734,"path":735,"stem":736},"Why We Don't Build Chat From Scratch (And Neither Should You)","/blog/software-development/why-we-dont-build-chat-from-scratch","blog/software-development/why-we-dont-build-chat-from-scratch",{"title":738,"path":739,"stem":740},"Why we use Sanity.io","/blog/software-development/why-we-use-sanity-io","blog/software-development/why-we-use-sanity-io",{"title":742,"path":743,"stem":744,"children":745,"page":69},"Sportstech","/blog/sportstech","blog/sportstech",[746,750],{"title":747,"path":748,"stem":749},"BeatBuddy Replay: Video Analysis App Challenges","/blog/sportstech/beatbuddy-replay-video-analysis-app-for-swimmers-flutter","blog/sportstech/beatbuddy-replay-video-analysis-app-for-swimmers-flutter",{"title":751,"path":752,"stem":753},"How to Create a Watch Face App for Garmin Watch","/blog/sportstech/how-to-create-watch-face-app-for-garmin-watch","blog/sportstech/how-to-create-watch-face-app-for-garmin-watch",{"id":755,"title":626,"authors":756,"badge":762,"body":765,"category":1508,"client":1509,"date":1510,"description":1511,"extension":1512,"faq":1513,"featured":69,"featuredOrder":1509,"hidden":69,"image":1523,"keyTakeaways":1525,"meta":1539,"navigation":1015,"path":627,"seo":1540,"status":1509,"stem":628,"tags":1543,"teaser":1509,"__hash__":1549},"posts/blog/software-development/one-mcp-server-two-auth-stories-claude-code-vs-claude-desktop.md",[757],{"name":758,"to":759,"avatar":760},"Mariusz Smenzyk","https://www.linkedin.com/in/mariusz-smenzyk/",{"src":761},"/images/people/mariusz-smenzyk2.webp",{"label":763,"color":764},"Tutorial","#7c3aed",{"type":766,"value":767,"toc":1500},"minimark",[768,772,775,780,783,786,811,817,821,837,840,900,911,922,925,929,940,943,949,952,956,982,1083,1089,1096,1349,1363,1366,1372,1383,1387,1458,1461,1464,1470,1477,1483,1486,1490,1493,1496],[769,770,771],"p",{},"We built a small internal tool: an MCP server so Claude Code could search and manage MusicTech Lab's own resources directory, startups, investors, open source projects, straight from the terminal. It worked well enough that a colleague asked the obvious question: \"can I use this too?\"",[769,773,774],{},"That single request turned into a much bigger project than expected, not because sharing a tool is hard, but because \"sharing it with Claude Code\" and \"sharing it with Claude Desktop\" turned out to be two almost unrelated engineering problems.",[776,777,779],"h2",{"id":778},"from-one-laptop-to-a-shared-server","From One Laptop to a Shared Server",[769,781,782],{},"The original setup was about as simple as MCP servers get: a local process talking over stdio, one person, one machine, a JWT cached in a file on disk. Claude Code would spawn it, log in once, and reuse that session indefinitely.",[769,784,785],{},"Sharing it meant something different: a real remote deployment other people could point their own Claude client at, without installing Python or Poetry locally.",[787,788,795,801,806],"div",{"className":789},[790,791,792,793,794],"grid","grid-cols-1","md:grid-cols-3","gap-4","my-8",[796,797],"spotlight-card",{"description":798,"icon":799,"title":800},"Swapped the stdio transport for MCP's streamable-http mode, so the server can run as a long-lived process behind nginx.","i-lucide-server","Streamable HTTP",[796,802],{"description":803,"icon":804,"title":805},"Each connected client gets its own in-memory login state, keyed to its MCP session, not a single shared file.","i-lucide-users","Per-connection sessions",[796,807],{"description":808,"icon":809,"title":810},"Deployed in isolation from any other production infrastructure, no shared ports or configs to risk.","i-lucide-globe","Its own droplet",[812,813,814],"warning",{},[769,815,816],{},"The naive version of this would have kept the single shared token file as-is. Two people connecting to the same remote server would then silently share one login, logging in as one person could log the other one out mid-session. Session isolation isn't an optimization here, it's the difference between \"works for one person\" and \"works for two people safely.\"",[776,818,820],{"id":819},"building-a-real-cicd-pipeline","Building a Real CI/CD Pipeline",[769,822,823,824,828,829,832,833,836],{},"Once the server lived on its own droplet, updating it by hand (rebuild locally, ",[825,826,827],"code",{},"rsync",", SSH in, restart) stopped being sustainable. So it got a proper GitHub Actions pipeline: lint and test on every pull request, then on push to ",[825,830,831],{},"main",", build a Docker image, push it to GitHub Container Registry, and deploy it to the droplet over SSH as a dedicated, non-root ",[825,834,835],{},"deploy"," user.",[769,838,839],{},"The pipeline caught a bug worth calling out, because it's the kind that stays invisible for months if you don't go looking for it.",[841,842,848],"pre",{"className":843,"code":844,"filename":845,"language":846,"meta":847,"style":847},"language-yaml shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","services:\n  mcp-server:\n    image: ghcr.io/musictechlab/mtl-website-mcp:latest\n    build: .   # looked like a harmless local-dev convenience\n","docker-compose.yaml, before","yaml","",[825,849,850,863,871,884],{"__ignoreMap":847},[851,852,855,859],"span",{"class":853,"line":854},"line",1,[851,856,858],{"class":857},"swJcz","services",[851,860,862],{"class":861},"sMK4o",":\n",[851,864,866,869],{"class":853,"line":865},2,[851,867,868],{"class":857},"  mcp-server",[851,870,862],{"class":861},[851,872,874,877,880],{"class":853,"line":873},3,[851,875,876],{"class":857},"    image",[851,878,879],{"class":861},":",[851,881,883],{"class":882},"sfazB"," ghcr.io/musictechlab/mtl-website-mcp:latest\n",[851,885,887,890,892,896],{"class":853,"line":886},4,[851,888,889],{"class":857},"    build",[851,891,879],{"class":861},[851,893,895],{"class":894},"sbssI"," .",[851,897,899],{"class":898},"sHwdD","   # looked like a harmless local-dev convenience\n",[769,901,902,903,906,907,910],{},"Keeping ",[825,904,905],{},"build:"," alongside ",[825,908,909],{},"image:"," meant that if the registry pull ever failed (a stale credential, a network blip), Docker Compose silently fell back to rebuilding from whatever source happened to already be sitting on the server, stale code, no error, no failed deploy. The GitHub Actions run would show green. The droplet just wouldn't be running what was actually merged.",[912,913,914],"tip",{},[769,915,916,917,921],{},"The fix was to split the deploy-only compose file so it has ",[918,919,920],"strong",{},"no build fallback at all",". If the pull fails, the deploy fails loudly, which is exactly what you want from a \"successful\" deploy that shipped nothing.",[769,923,924],{},"The second lesson came from looking sideways instead of inward: two sibling MusicTechLab repos already had CI/CD pipelines solving the exact same registry-auth problem, using a fresh, job-scoped login on every deploy instead of a long-lived credential sitting on the server. Copying a proven internal pattern beat inventing a new one.",[776,926,928],{"id":927},"the-wall-claude-desktop-doesnt-do-static-tokens","The Wall: Claude Desktop Doesn't Do Static Tokens",[769,930,931,932,935,936,939],{},"With the shared server and CI/CD in place, Claude Code worked great: ",[825,933,934],{},"claude mcp add --transport http"," with a ",[825,937,938],{},"--header \"Authorization: Bearer ...\""," flag, done.",[769,941,942],{},"Claude Desktop's \"Add custom connector\" flow had a different idea entirely. It always performs OAuth 2.0 dynamic client registration against the server's origin before it will connect, no exceptions, no fallback for a static header. The first attempt to connect failed immediately:",[944,945,946],"blockquote",{},[769,947,948],{},"Couldn't register with MTL Website's sign-in service. You can try again, or add an OAuth Client ID in the connector settings.",[769,950,951],{},"There was no sign-in service to register with. There was no OAuth at all.",[776,953,955],{"id":954},"building-the-oauth-layer","Building the OAuth Layer",[769,957,958,959,962,963,966,967,970,971,970,974,977,978,981],{},"The MCP Python SDK turned out to already ship the hard part: an ",[825,960,961],{},"OAuthAuthorizationServerProvider"," protocol that, once implemented, gets ",[825,964,965],{},"FastMCP"," to automatically mount ",[825,968,969],{},"/.well-known/oauth-authorization-server",", ",[825,972,973],{},"/authorize",[825,975,976],{},"/token",", and ",[825,979,980],{},"/register",".",[841,983,987],{"className":984,"code":985,"language":986,"meta":847,"style":847},"language-mermaid shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","sequenceDiagram\n    participant D as Claude Desktop\n    participant S as MCP Server\n    participant B as mtl-website backend\n\n    D->>S: POST /register (dynamic client registration)\n    S-->>D: client_id\n    D->>S: GET /authorize (PKCE challenge, state)\n    S-->>D: redirect to /login\n    D->>S: POST /login (staff email + password)\n    S->>B: validate credentials\n    B-->>S: JWT (access + refresh)\n    S-->>D: redirect with authorization code\n    D->>S: POST /token (code + PKCE verifier)\n    S-->>D: access token\n    D->>S: POST /mcp (Authorization: Bearer access token)\n","mermaid",[825,988,989,995,1000,1005,1010,1017,1023,1029,1035,1041,1047,1053,1059,1065,1071,1077],{"__ignoreMap":847},[851,990,991],{"class":853,"line":854},[851,992,994],{"class":993},"sTEyZ","sequenceDiagram\n",[851,996,997],{"class":853,"line":865},[851,998,999],{"class":993},"    participant D as Claude Desktop\n",[851,1001,1002],{"class":853,"line":873},[851,1003,1004],{"class":993},"    participant S as MCP Server\n",[851,1006,1007],{"class":853,"line":886},[851,1008,1009],{"class":993},"    participant B as mtl-website backend\n",[851,1011,1013],{"class":853,"line":1012},5,[851,1014,1016],{"emptyLinePlaceholder":1015},true,"\n",[851,1018,1020],{"class":853,"line":1019},6,[851,1021,1022],{"class":993},"    D->>S: POST /register (dynamic client registration)\n",[851,1024,1026],{"class":853,"line":1025},7,[851,1027,1028],{"class":993},"    S-->>D: client_id\n",[851,1030,1032],{"class":853,"line":1031},8,[851,1033,1034],{"class":993},"    D->>S: GET /authorize (PKCE challenge, state)\n",[851,1036,1038],{"class":853,"line":1037},9,[851,1039,1040],{"class":993},"    S-->>D: redirect to /login\n",[851,1042,1044],{"class":853,"line":1043},10,[851,1045,1046],{"class":993},"    D->>S: POST /login (staff email + password)\n",[851,1048,1050],{"class":853,"line":1049},11,[851,1051,1052],{"class":993},"    S->>B: validate credentials\n",[851,1054,1056],{"class":853,"line":1055},12,[851,1057,1058],{"class":993},"    B-->>S: JWT (access + refresh)\n",[851,1060,1062],{"class":853,"line":1061},13,[851,1063,1064],{"class":993},"    S-->>D: redirect with authorization code\n",[851,1066,1068],{"class":853,"line":1067},14,[851,1069,1070],{"class":993},"    D->>S: POST /token (code + PKCE verifier)\n",[851,1072,1074],{"class":853,"line":1073},15,[851,1075,1076],{"class":993},"    S-->>D: access token\n",[851,1078,1080],{"class":853,"line":1079},16,[851,1081,1082],{"class":993},"    D->>S: POST /mcp (Authorization: Bearer access token)\n",[769,1084,1085,1086,1088],{},"The design decision that mattered most: ",[825,1087,973],{}," redirects to a real staff login page, not an auto-approve stub. Since a signed-in connection also gets write access to production data, gating it behind actual credentials, not just \"anyone who finds the URL\", was worth the extra page.",[769,1090,1091],{},[1092,1093],"img",{"alt":1094,"src":1095},"The staff sign-in page a Claude Desktop connection redirects to","/images/blog/musictechlab_blog_one-mcp-server-two-auth-stories_inline_1.webp",[841,1097,1102],{"className":1098,"code":1099,"filename":1100,"language":1101,"meta":847,"style":847},"language-python shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","async def load_access_token(self, token: str) -> AccessToken | None:\n    # Legacy static token: keeps Claude Code's existing setup working, unchanged.\n    if self.legacy_token and secrets.compare_digest(token, self.legacy_token):\n        return AccessToken(token=token, client_id=\"legacy\", scopes=[\"mcp\"], expires_at=None)\n\n    access_token = self._access_tokens.get(token)\n    if access_token is None or (access_token.expires_at and access_token.expires_at \u003C time.time()):\n        return None\n    return access_token\n","oauth.py","python",[825,1103,1104,1152,1157,1198,1254,1258,1284,1334,1341],{"__ignoreMap":847},[851,1105,1106,1110,1113,1117,1120,1124,1127,1131,1133,1137,1140,1143,1146,1149],{"class":853,"line":854},[851,1107,1109],{"class":1108},"spNyl","async",[851,1111,1112],{"class":1108}," def",[851,1114,1116],{"class":1115},"s2Zo4"," load_access_token",[851,1118,1119],{"class":861},"(",[851,1121,1123],{"class":1122},"s5tWE","self",[851,1125,1126],{"class":861},",",[851,1128,1130],{"class":1129},"sHdIc"," token",[851,1132,879],{"class":861},[851,1134,1136],{"class":1135},"sBMFI"," str",[851,1138,1139],{"class":861},")",[851,1141,1142],{"class":861}," ->",[851,1144,1145],{"class":993}," AccessToken ",[851,1147,1148],{"class":861},"|",[851,1150,1151],{"class":861}," None:\n",[851,1153,1154],{"class":853,"line":865},[851,1155,1156],{"class":898},"    # Legacy static token: keeps Claude Code's existing setup working, unchanged.\n",[851,1158,1159,1163,1166,1168,1171,1174,1177,1179,1182,1184,1187,1189,1191,1193,1195],{"class":853,"line":873},[851,1160,1162],{"class":1161},"s7zQu","    if",[851,1164,1165],{"class":993}," self",[851,1167,981],{"class":861},[851,1169,1170],{"class":857},"legacy_token",[851,1172,1173],{"class":861}," and",[851,1175,1176],{"class":993}," secrets",[851,1178,981],{"class":861},[851,1180,1181],{"class":1115},"compare_digest",[851,1183,1119],{"class":861},[851,1185,1186],{"class":1115},"token",[851,1188,1126],{"class":861},[851,1190,1165],{"class":993},[851,1192,981],{"class":861},[851,1194,1170],{"class":857},[851,1196,1197],{"class":861},"):\n",[851,1199,1200,1203,1206,1208,1210,1213,1215,1217,1220,1222,1225,1228,1230,1232,1235,1238,1240,1243,1245,1248,1251],{"class":853,"line":886},[851,1201,1202],{"class":1161},"        return",[851,1204,1205],{"class":1115}," AccessToken",[851,1207,1119],{"class":861},[851,1209,1186],{"class":1129},[851,1211,1212],{"class":861},"=",[851,1214,1186],{"class":1115},[851,1216,1126],{"class":861},[851,1218,1219],{"class":1129}," client_id",[851,1221,1212],{"class":861},[851,1223,1224],{"class":861},"\"",[851,1226,1227],{"class":882},"legacy",[851,1229,1224],{"class":861},[851,1231,1126],{"class":861},[851,1233,1234],{"class":1129}," scopes",[851,1236,1237],{"class":861},"=[",[851,1239,1224],{"class":861},[851,1241,1242],{"class":882},"mcp",[851,1244,1224],{"class":861},[851,1246,1247],{"class":861},"],",[851,1249,1250],{"class":1129}," expires_at",[851,1252,1253],{"class":861},"=None)\n",[851,1255,1256],{"class":853,"line":1012},[851,1257,1016],{"emptyLinePlaceholder":1015},[851,1259,1260,1263,1265,1267,1269,1272,1274,1277,1279,1281],{"class":853,"line":1019},[851,1261,1262],{"class":993},"    access_token ",[851,1264,1212],{"class":861},[851,1266,1165],{"class":993},[851,1268,981],{"class":861},[851,1270,1271],{"class":857},"_access_tokens",[851,1273,981],{"class":861},[851,1275,1276],{"class":1115},"get",[851,1278,1119],{"class":861},[851,1280,1186],{"class":1115},[851,1282,1283],{"class":861},")\n",[851,1285,1286,1288,1291,1294,1297,1300,1303,1306,1308,1311,1313,1316,1318,1320,1323,1326,1328,1331],{"class":853,"line":1025},[851,1287,1162],{"class":1161},[851,1289,1290],{"class":993}," access_token ",[851,1292,1293],{"class":861},"is",[851,1295,1296],{"class":861}," None",[851,1298,1299],{"class":861}," or",[851,1301,1302],{"class":861}," (",[851,1304,1305],{"class":993},"access_token",[851,1307,981],{"class":861},[851,1309,1310],{"class":857},"expires_at",[851,1312,1173],{"class":861},[851,1314,1315],{"class":993}," access_token",[851,1317,981],{"class":861},[851,1319,1310],{"class":857},[851,1321,1322],{"class":861}," \u003C",[851,1324,1325],{"class":993}," time",[851,1327,981],{"class":861},[851,1329,1330],{"class":1115},"time",[851,1332,1333],{"class":861},"()):\n",[851,1335,1336,1338],{"class":853,"line":1031},[851,1337,1202],{"class":1161},[851,1339,1340],{"class":861}," None\n",[851,1342,1343,1346],{"class":853,"line":1037},[851,1344,1345],{"class":1161},"    return",[851,1347,1348],{"class":993}," access_token\n",[1350,1351,1352],"note",{},[769,1353,1354,1355,1358,1359,1362],{},"PKCE verification (comparing the client's ",[825,1356,1357],{},"code_verifier"," against the ",[825,1360,1361],{},"code_challenge"," stored on the authorization code) is handled entirely by the SDK. The provider only needs to store the challenge; it never has to hash or compare anything itself.",[769,1364,1365],{},"Once the token exchange succeeds, Claude Desktop's own callback page confirms it before handing control back to the app:",[769,1367,1368],{},[1092,1369],{"alt":1370,"src":1371},"The success screen Claude Desktop shows once the OAuth exchange completes","/images/blog/musictechlab_blog_one-mcp-server-two-auth-stories_inline_2.webp",[769,1373,1374,1375,1377,1378,1382],{},"A nice side effect of gating ",[825,1376,973],{}," behind a real login: the resulting session already carries the backend JWT obtained during that login. Desktop users get read ",[1379,1380,1381],"em",{},"and"," write access immediately after signing in, no separate login step inside the chat, while Claude Code's static-token path is completely unaffected and still calls the login tool manually, exactly as before.",[776,1384,1386],{"id":1385},"two-clients-one-server","Two Clients, One Server",[1388,1389,1390,1405],"table",{},[1391,1392,1393],"thead",{},[1394,1395,1396,1399,1402],"tr",{},[1397,1398],"th",{},[1397,1400,1401],{},"Claude Code",[1397,1403,1404],{},"Claude Desktop",[1406,1407,1408,1422,1433,1448],"tbody",{},[1394,1409,1410,1414,1419],{},[1411,1412,1413],"td",{},"Setup",[1411,1415,1416],{},[825,1417,1418],{},"claude mcp add --header \"Bearer ...\"",[1411,1420,1421],{},"Settings → Connectors → Add custom connector",[1394,1423,1424,1427,1430],{},[1411,1425,1426],{},"Auth mechanism",[1411,1428,1429],{},"Static shared token",[1411,1431,1432],{},"OAuth 2.1, dynamic client registration, PKCE",[1394,1434,1435,1438,1445],{},[1411,1436,1437],{},"Write access",[1411,1439,1440,1441,1444],{},"Manual ",[825,1442,1443],{},"mtl_website_login"," call",[1411,1446,1447],{},"Automatic, seeded from the OAuth login",[1394,1449,1450,1453,1456],{},[1411,1451,1452],{},"Session isolation",[1411,1454,1455],{},"Per-connection, in-memory",[1411,1457,1455],{},[769,1459,1460],{},"Neither path is a workaround for the other. They're both first-class ways into the same server, chosen by whichever client is asking.",[769,1462,1463],{},"This is what it looks like in practice: Claude Desktop searching real production data, no separate login tool call anywhere in sight, because the OAuth sign-in already handled it.",[769,1465,1466],{},[1092,1467],{"alt":1468,"src":1469},"Claude Desktop querying Series B investments in production, no separate login step required","/images/blog/musictechlab_blog_one-mcp-server-two-auth-stories_inline_3.webp",[769,1471,1472,1473,1476],{},"What happened next wasn't something we built at all: the MCP search matched loosely, pulling in some Series A and D rounds alongside the real Series B deals. Claude noticed on its own, and rather than accept the noisy results, it spun up six parallel agents to scan the underlying investment records directly and filter for the true ",[825,1474,1475],{},"round_type == \"Series B\""," rows.",[769,1478,1479],{},[1092,1480],{"alt":1481,"src":1482},"Claude Desktop spinning up six parallel agents to verify Series B investment data directly, correcting for loose MCP search matching","/images/blog/musictechlab_blog_one-mcp-server-two-auth-stories_inline_4.webp",[769,1484,1485],{},"That's the part worth sitting with: the MCP server just exposes data, search included, warts and all. The judgment about whether that data is trustworthy, and the initiative to go verify it, comes from the model on the other end of the connection.",[776,1487,1489],{"id":1488},"what-this-is-actually-about","What This Is Actually About",[769,1491,1492],{},"\"Add authentication\" sounds like one task. In practice it was three different problems stacked on top of each other: isolating sessions so a shared server doesn't leak one user's login into another's, building a deploy pipeline that fails loudly instead of quietly serving stale code, and implementing a real OAuth authorization server because one specific client simply doesn't support anything less.",[769,1494,1495],{},"If you're building an MCP server and only need it to work from Claude Code, a static bearer token is genuinely enough, don't build more than that. The moment Claude Desktop needs to connect, budget for OAuth from the start; there's no smaller version of that requirement to reach for instead.",[1497,1498,1499],"style",{},"html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .s5tWE, html code.shiki .s5tWE{--shiki-light:#E53935;--shiki-light-font-style:italic;--shiki-default:#F07178;--shiki-default-font-style:italic;--shiki-dark:#F07178;--shiki-dark-font-style:italic}html pre.shiki code .sHdIc, html code.shiki .sHdIc{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#EEFFFF;--shiki-default-font-style:italic;--shiki-dark:#BABED8;--shiki-dark-font-style:italic}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}",{"title":847,"searchDepth":865,"depth":865,"links":1501},[1502,1503,1504,1505,1506,1507],{"id":778,"depth":865,"text":779},{"id":819,"depth":865,"text":820},{"id":927,"depth":865,"text":928},{"id":954,"depth":865,"text":955},{"id":1385,"depth":865,"text":1386},{"id":1488,"depth":865,"text":1489},"software-development",null,"2026-07-13T00:00:00.000Z","Why adding Claude Desktop support to an internal MCP server meant building real OAuth 2.1, even though Claude Code was happy with a static token.","md",[1514,1517,1520],{"question":1515,"answer":1516},"Does Claude Code support OAuth for remote MCP servers?","Yes, but it doesn't require it. `claude mcp add --transport http` also accepts a plain --header flag, so a static bearer token is enough. Claude Desktop's custom connector flow has no equivalent shortcut.",{"question":1518,"answer":1519},"Why can't Claude Desktop just use a bearer token like Claude Code?","Claude Desktop's connector flow always performs OAuth 2.0 dynamic client registration (RFC 7591) against the server's origin before it will connect at all. There's no configuration option to skip this and supply a static header instead.",{"question":1521,"answer":1522},"Do I need to implement PKCE myself?","No. The MCP Python SDK's OAuthAuthorizationServerProvider handles PKCE verification internally, you only need to store the code_challenge on the authorization code you issue; the SDK compares it against the client's code_verifier automatically.",{"src":1524},"/images/blog/musictechlab_blog_one-mcp-server-two-auth-stories.webp",{"enabled":1015,"items":1526},[1527,1530,1533,1536],{"text":1528,"icon":1529},"Claude Code accepts a static bearer token for remote MCP servers. Claude Desktop does not.","i-lucide-key",{"text":1531,"icon":1532},"Claude Desktop's connector flow always performs OAuth dynamic client registration before connecting, with no fallback.","i-lucide-shield",{"text":1534,"icon":1535},"The MCP Python SDK ships a full OAuth authorization server implementation, PKCE included, you just supply the storage and the login step.","i-lucide-package",{"text":1537,"icon":1538},"A silent CI/CD bug, a failed registry pull quietly falling back to a stale local build, was more dangerous than the OAuth work itself.","i-lucide-bug",{},{"title":1541,"description":1542},"Adding OAuth to an MCP Server for Claude Desktop","How Claude Desktop's OAuth-only connector flow forced a redesign of an internal Claude Code MCP server, and what we learned building it.",[1544,1401,1404,1545,1546,1547,1548],"MCP","OAuth","CI/CD","DevOps","AI tooling","2OphHdpDxIYQx10zTQbAEWuAth2D9sf0E-wsx8lzQnQ",[1551,1553],{"title":622,"path":623,"stem":624,"description":1552,"children":-1},"What is MusicXML, how does it work, and what are its applications in music and education? A guide to the open standard for sheet music exchange.",{"title":630,"path":631,"stem":632,"description":1554,"children":-1},"Key takeaways from Bill Gates' recommended book list, including Getting Things Done, Deep Work, and practical ideas for time management and productivity.",[1556,2588,3412,3435],{"id":1557,"title":422,"authors":1558,"badge":1562,"body":1564,"category":1508,"client":1509,"date":2543,"description":2544,"extension":1512,"faq":2545,"featured":69,"featuredOrder":1509,"hidden":69,"image":2561,"keyTakeaways":2563,"meta":2576,"navigation":1015,"path":423,"seo":2577,"status":1509,"stem":424,"tags":2580,"teaser":1509,"__hash__":2587,"score":873},"posts/blog/software-development/c2pa-in-ableton-max-for-live.md",[1559],{"name":1560,"to":759,"avatar":1561},"Mariusz Smenżyk",{"src":761},{"label":1563,"color":764},"Open Source",{"type":766,"value":1565,"toc":2530},[1566,1574,1585,1589,1592,1599,1602,1609,1616,1620,1623,1634,1638,1819,1822,1869,1873,1981,1988,1992,1995,2032,2038,2042,2045,2307,2318,2322,2331,2338,2342,2345,2438,2446,2450,2459,2462,2498,2502,2510,2513,2517,2520,2527],[769,1567,1568,1569,1573],{},"In May 2026 we shipped ",[1570,1571,1572],"a",{"href":125},"a Claude MCP for reading C2PA manifests in music files",". This post is the follow-up: the same reader, now inside Ableton Live as an open-source Max for Live device.",[769,1575,1576,1577,1580,1581,1584],{},"This is the fourth article in our ",[1570,1578,1579],{"href":487},"Max for Live series",". It builds directly on the ",[1570,1582,1583],{"href":435},"M4L → FastAPI pattern"," we wrote about in January 2026, with one change: the API runs on your laptop, not in the cloud.",[776,1586,1588],{"id":1587},"the-problem","The problem",[769,1590,1591],{},"Google Lyria signs every MP3 it generates with a C2PA manifest. The manifest records who made the file, what model produced it, whether it is AI-generated, what watermarks were applied (Lyria adds SynthID), and who signed the claim. The data is there. Producers cannot see it.",[769,1593,1594,1595,1598],{},"You drop a Lyria stem onto an audio track. Ableton shows you the waveform. It does not show you that the file is AI-generated, who signed it, or what the manifest says about the source. To find out, you have to leave the DAW, run ",[825,1596,1597],{},"c2patool"," on the file, and read raw JSON.",[769,1600,1601],{},"Andrew Melchior — Massive Attack's CTO, advising the UK DCMS on AI and the Copyright Act — framed the bigger gap in a reply on LinkedIn to our MCP announcement:",[1350,1603,1604],{},[769,1605,1606],{},[918,1607,1608],{},"C2PA now tells you a machine generated this track. It doesn't tell you whose work trained the machine.",[769,1610,1611,1612,1615],{},"Training-corpus provenance is the hard problem. This article is about the easier half — making the ",[1379,1613,1614],{},"output"," manifest visible at the point a producer is actually working.",[776,1617,1619],{"id":1618},"the-fix","The fix",[769,1621,1622],{},"A Max for Live device. Click a clip → see the manifest summary. That is the whole product.",[769,1624,1625,1626,1629,1630,1633],{},"Under the hood, the device borrows a pattern we already shipped: a Max for Live ",[825,1627,1628],{},"js"," object reads the Live Object Model, then routes the work to a Node for Max HTTP client. We wrote about ",[1570,1631,1632],{"href":435},"this exact shape in January 2026",". The only change here is where the HTTP server lives.",[776,1635,1637],{"id":1636},"architecture-in-one-diagram","Architecture in one diagram",[841,1639,1641],{"className":984,"code":1640,"language":986,"meta":847,"style":847},"flowchart TB\n    subgraph Ableton[\"Ableton Live\"]\n        device[\"MTL_C2PA_Ableton_PoC.amxd\"]\n        livepi[\"LiveAPI observer\u003Cbr/>(detail_clip)\"]\n        js[\"c2pa_reader.js\"]\n        node[\"c2pa_node.js\u003Cbr/>(Node for Max)\"]\n        ui[\"UI textedit\u003Cbr/>summary display\"]\n        device --> livepi --> js --> node --> ui\n    end\n\n    subgraph LocalServer[\"mtl-c2pa-http (127.0.0.1:8765)\"]\n        fastapi[\"FastAPI app\"]\n        summary[\"/summary\"]\n        verify[\"/verify\"]\n        scan[\"/scan\"]\n        info[\"/info\"]\n        fastapi --> summary\n        fastapi --> verify\n        fastapi --> scan\n        fastapi --> info\n    end\n\n    subgraph PythonPkg[\"mtl_c2pa_server (in this repo)\"]\n        c2pamod[\"c2pa.py (parser)\"]\n        reader[\"c2pa-python Reader\u003Cbr/>(Rust binding)\"]\n        c2pamod --> reader\n    end\n\n    node -->|POST /summary| fastapi\n    fastapi -->|import| c2pamod\n\n    launchd[\"launchd plist\u003Cbr/>auto-start on login\"]\n    launchd -.->|spawn| fastapi\n",[825,1642,1643,1648,1653,1658,1663,1668,1673,1678,1683,1688,1692,1697,1702,1707,1712,1717,1722,1728,1734,1740,1746,1751,1756,1762,1768,1774,1780,1785,1790,1796,1802,1807,1813],{"__ignoreMap":847},[851,1644,1645],{"class":853,"line":854},[851,1646,1647],{"class":993},"flowchart TB\n",[851,1649,1650],{"class":853,"line":865},[851,1651,1652],{"class":993},"    subgraph Ableton[\"Ableton Live\"]\n",[851,1654,1655],{"class":853,"line":873},[851,1656,1657],{"class":993},"        device[\"MTL_C2PA_Ableton_PoC.amxd\"]\n",[851,1659,1660],{"class":853,"line":886},[851,1661,1662],{"class":993},"        livepi[\"LiveAPI observer\u003Cbr/>(detail_clip)\"]\n",[851,1664,1665],{"class":853,"line":1012},[851,1666,1667],{"class":993},"        js[\"c2pa_reader.js\"]\n",[851,1669,1670],{"class":853,"line":1019},[851,1671,1672],{"class":993},"        node[\"c2pa_node.js\u003Cbr/>(Node for Max)\"]\n",[851,1674,1675],{"class":853,"line":1025},[851,1676,1677],{"class":993},"        ui[\"UI textedit\u003Cbr/>summary display\"]\n",[851,1679,1680],{"class":853,"line":1031},[851,1681,1682],{"class":993},"        device --> livepi --> js --> node --> ui\n",[851,1684,1685],{"class":853,"line":1037},[851,1686,1687],{"class":993},"    end\n",[851,1689,1690],{"class":853,"line":1043},[851,1691,1016],{"emptyLinePlaceholder":1015},[851,1693,1694],{"class":853,"line":1049},[851,1695,1696],{"class":993},"    subgraph LocalServer[\"mtl-c2pa-http (127.0.0.1:8765)\"]\n",[851,1698,1699],{"class":853,"line":1055},[851,1700,1701],{"class":993},"        fastapi[\"FastAPI app\"]\n",[851,1703,1704],{"class":853,"line":1061},[851,1705,1706],{"class":993},"        summary[\"/summary\"]\n",[851,1708,1709],{"class":853,"line":1067},[851,1710,1711],{"class":993},"        verify[\"/verify\"]\n",[851,1713,1714],{"class":853,"line":1073},[851,1715,1716],{"class":993},"        scan[\"/scan\"]\n",[851,1718,1719],{"class":853,"line":1079},[851,1720,1721],{"class":993},"        info[\"/info\"]\n",[851,1723,1725],{"class":853,"line":1724},17,[851,1726,1727],{"class":993},"        fastapi --> summary\n",[851,1729,1731],{"class":853,"line":1730},18,[851,1732,1733],{"class":993},"        fastapi --> verify\n",[851,1735,1737],{"class":853,"line":1736},19,[851,1738,1739],{"class":993},"        fastapi --> scan\n",[851,1741,1743],{"class":853,"line":1742},20,[851,1744,1745],{"class":993},"        fastapi --> info\n",[851,1747,1749],{"class":853,"line":1748},21,[851,1750,1687],{"class":993},[851,1752,1754],{"class":853,"line":1753},22,[851,1755,1016],{"emptyLinePlaceholder":1015},[851,1757,1759],{"class":853,"line":1758},23,[851,1760,1761],{"class":993},"    subgraph PythonPkg[\"mtl_c2pa_server (in this repo)\"]\n",[851,1763,1765],{"class":853,"line":1764},24,[851,1766,1767],{"class":993},"        c2pamod[\"c2pa.py (parser)\"]\n",[851,1769,1771],{"class":853,"line":1770},25,[851,1772,1773],{"class":993},"        reader[\"c2pa-python Reader\u003Cbr/>(Rust binding)\"]\n",[851,1775,1777],{"class":853,"line":1776},26,[851,1778,1779],{"class":993},"        c2pamod --> reader\n",[851,1781,1783],{"class":853,"line":1782},27,[851,1784,1687],{"class":993},[851,1786,1788],{"class":853,"line":1787},28,[851,1789,1016],{"emptyLinePlaceholder":1015},[851,1791,1793],{"class":853,"line":1792},29,[851,1794,1795],{"class":993},"    node -->|POST /summary| fastapi\n",[851,1797,1799],{"class":853,"line":1798},30,[851,1800,1801],{"class":993},"    fastapi -->|import| c2pamod\n",[851,1803,1805],{"class":853,"line":1804},31,[851,1806,1016],{"emptyLinePlaceholder":1015},[851,1808,1810],{"class":853,"line":1809},32,[851,1811,1812],{"class":993},"    launchd[\"launchd plist\u003Cbr/>auto-start on login\"]\n",[851,1814,1816],{"class":853,"line":1815},33,[851,1817,1818],{"class":993},"    launchd -.->|spawn| fastapi\n",[769,1820,1821],{},"Three sentences:",[1823,1824,1825,1839,1849],"ol",{},[1826,1827,1828,1829,1831,1832,1835,1836,981],"li",{},"A LiveAPI observer in the ",[825,1830,1628],{}," object watches ",[825,1833,1834],{},"live_set view detail_clip",". When the selection changes, it pulls the clip's ",[825,1837,1838],{},"file_path",[1826,1840,1841,1842,1845,1846,981],{},"The path flows into a Node for Max script, which ",[825,1843,1844],{},"POST","s it to ",[825,1847,1848],{},"http://127.0.0.1:8765/summary",[1826,1850,1851,1852,1855,1856,1865,1866,981],{},"The local FastAPI server is shipped in this same repo as the device — a small Python package (",[825,1853,1854],{},"mtl_c2pa_server",") that wraps the ",[1570,1857,1861,1862],{"href":1858,"rel":1859},"https://github.com/contentauth/c2pa-python",[1860],"nofollow","official ",[825,1863,1864],{},"c2pa-python"," Rust binding. One clone, one ",[825,1867,1868],{},"poetry install",[776,1870,1872],{"id":1871},"selection-to-display-end-to-end","Selection-to-display, end to end",[841,1874,1876],{"className":984,"code":1875,"language":986,"meta":847,"style":847},"sequenceDiagram\n    actor User\n    participant Live as Ableton Live\n    participant Device as M4L Device\n    participant Reader as c2pa_reader.js\n    participant Node as c2pa_node.js\n    participant HTTP as mtl-c2pa-http :8765\n    participant Lib as c2pa-python\n\n    User->>Live: click audio clip\n    Live->>Device: detail_clip changed\n    Device->>Reader: observer fires\n    Reader->>Live: get detail_clip.file_path\n    Live-->>Reader: /path/to/lyria.mp3\n    Reader->>Node: outlet \"fetch\" path\n    Node->>HTTP: POST /summary {path}\n    HTTP->>Lib: Reader(mime, stream).json()\n    Lib-->>HTTP: manifest store\n    HTTP-->>Node: summary JSON\n    Node->>Device: outlet \"result\" json\n    Device-->>User: display summary\n",[825,1877,1878,1882,1887,1892,1897,1902,1907,1912,1917,1921,1926,1931,1936,1941,1946,1951,1956,1961,1966,1971,1976],{"__ignoreMap":847},[851,1879,1880],{"class":853,"line":854},[851,1881,994],{"class":993},[851,1883,1884],{"class":853,"line":865},[851,1885,1886],{"class":993},"    actor User\n",[851,1888,1889],{"class":853,"line":873},[851,1890,1891],{"class":993},"    participant Live as Ableton Live\n",[851,1893,1894],{"class":853,"line":886},[851,1895,1896],{"class":993},"    participant Device as M4L Device\n",[851,1898,1899],{"class":853,"line":1012},[851,1900,1901],{"class":993},"    participant Reader as c2pa_reader.js\n",[851,1903,1904],{"class":853,"line":1019},[851,1905,1906],{"class":993},"    participant Node as c2pa_node.js\n",[851,1908,1909],{"class":853,"line":1025},[851,1910,1911],{"class":993},"    participant HTTP as mtl-c2pa-http :8765\n",[851,1913,1914],{"class":853,"line":1031},[851,1915,1916],{"class":993},"    participant Lib as c2pa-python\n",[851,1918,1919],{"class":853,"line":1037},[851,1920,1016],{"emptyLinePlaceholder":1015},[851,1922,1923],{"class":853,"line":1043},[851,1924,1925],{"class":993},"    User->>Live: click audio clip\n",[851,1927,1928],{"class":853,"line":1049},[851,1929,1930],{"class":993},"    Live->>Device: detail_clip changed\n",[851,1932,1933],{"class":853,"line":1055},[851,1934,1935],{"class":993},"    Device->>Reader: observer fires\n",[851,1937,1938],{"class":853,"line":1061},[851,1939,1940],{"class":993},"    Reader->>Live: get detail_clip.file_path\n",[851,1942,1943],{"class":853,"line":1067},[851,1944,1945],{"class":993},"    Live-->>Reader: /path/to/lyria.mp3\n",[851,1947,1948],{"class":853,"line":1073},[851,1949,1950],{"class":993},"    Reader->>Node: outlet \"fetch\" path\n",[851,1952,1953],{"class":853,"line":1079},[851,1954,1955],{"class":993},"    Node->>HTTP: POST /summary {path}\n",[851,1957,1958],{"class":853,"line":1724},[851,1959,1960],{"class":993},"    HTTP->>Lib: Reader(mime, stream).json()\n",[851,1962,1963],{"class":853,"line":1730},[851,1964,1965],{"class":993},"    Lib-->>HTTP: manifest store\n",[851,1967,1968],{"class":853,"line":1736},[851,1969,1970],{"class":993},"    HTTP-->>Node: summary JSON\n",[851,1972,1973],{"class":853,"line":1742},[851,1974,1975],{"class":993},"    Node->>Device: outlet \"result\" json\n",[851,1977,1978],{"class":853,"line":1748},[851,1979,1980],{"class":993},"    Device-->>User: display summary\n",[769,1982,1983,1984,1987],{},"The manual \"Refresh\" button short-circuits the observer and triggers the same ",[825,1985,1986],{},"POST /summary"," call. Same pipeline, different trigger source.",[776,1989,1991],{"id":1990},"why-local-not-cloud-not-cli","Why local, not cloud, not CLI",[769,1993,1994],{},"We considered three options. Local won.",[787,1996,1998,2010,2022],{"className":1997},[790,791,792,793,794],[796,1999,2003],{"description":2000,"icon":2001,"title":2002},"Simplest, but Python startup costs ~300 ms. On every clip selection. You feel it.","i-lucide-zap","A CLI shell-out per click",[769,2004,2005,2006,2009],{},"We use ",[1570,2007,1864],{"href":1858,"rel":2008},[1860],", which wraps the Rust binding. The Python interpreter cold-start is the bottleneck, not the C2PA read itself.",[796,2011,2015],{"description":2012,"icon":2013,"title":2014},"Right for generation, wrong for reading. You'd upload audio just to inspect a local file.","i-lucide-cloud","Cloud Run",[769,2016,2017,2018,2021],{},"Cloud is what our ",[1570,2019,2020],{"href":435},"reference M4L → API article"," uses — and for storing generation events with an audit log, it is the right answer. For reading a manifest already in your file system, it isn't.",[796,2023,2026],{"description":2024,"icon":799,"title":2025},"Keeps c2pa-python warm. Loopback only. Reuses the MCP parser without changes.","Local FastAPI server",[769,2027,2028,2029,2031],{},"The HTTP layer is ~80 lines wrapping ",[825,2030,1864],{}," directly. Self-contained in this repo — one clone, one install, no separate dependency on the sibling MCP server.",[769,2033,2034,2035,2037],{},"A persistent local FastAPI server keeps ",[825,2036,1864],{}," warm in memory, runs on loopback only (no external attack surface), and reuses the existing MCP parser without changes.",[776,2039,2041],{"id":2040},"what-you-see","What you see",[769,2043,2044],{},"For a Lyria-signed MP3, the device shows the same shape the MCP produces:",[841,2046,2051],{"className":2047,"code":2048,"filename":2049,"language":2050,"meta":847,"style":847},"language-json shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","{\n  \"file\": \"/Users/you/Music/Sovereign_Ascent.mp3\",\n  \"generator\": {\"name\": \"Google C2PA Core Generator Library\"},\n  \"is_ai_generated\": true,\n  \"actions\": [\n    {\"action\": \"c2pa.created\", \"description\": \"Created by Google Generative AI.\"},\n    {\"action\": \"c2pa.edited\", \"description\": \"Applied imperceptible SynthID watermark.\"}\n  ],\n  \"watermarks\": [\n    {\"description\": \"Applied imperceptible SynthID watermark.\"}\n  ],\n  \"signature_issuer\": \"Google LLC\",\n  \"validation\": \"valid\"\n}\n","c2pa_summary output in Ableton","json",[825,2052,2053,2058,2081,2114,2128,2142,2183,2222,2227,2240,2260,2264,2284,2303],{"__ignoreMap":847},[851,2054,2055],{"class":853,"line":854},[851,2056,2057],{"class":861},"{\n",[851,2059,2060,2063,2066,2068,2070,2073,2076,2078],{"class":853,"line":865},[851,2061,2062],{"class":861},"  \"",[851,2064,2065],{"class":1108},"file",[851,2067,1224],{"class":861},[851,2069,879],{"class":861},[851,2071,2072],{"class":861}," \"",[851,2074,2075],{"class":882},"/Users/you/Music/Sovereign_Ascent.mp3",[851,2077,1224],{"class":861},[851,2079,2080],{"class":861},",\n",[851,2082,2083,2085,2088,2090,2092,2095,2097,2100,2102,2104,2106,2109,2111],{"class":853,"line":873},[851,2084,2062],{"class":861},[851,2086,2087],{"class":1108},"generator",[851,2089,1224],{"class":861},[851,2091,879],{"class":861},[851,2093,2094],{"class":861}," {",[851,2096,1224],{"class":861},[851,2098,2099],{"class":1135},"name",[851,2101,1224],{"class":861},[851,2103,879],{"class":861},[851,2105,2072],{"class":861},[851,2107,2108],{"class":882},"Google C2PA Core Generator Library",[851,2110,1224],{"class":861},[851,2112,2113],{"class":861},"},\n",[851,2115,2116,2118,2121,2123,2125],{"class":853,"line":886},[851,2117,2062],{"class":861},[851,2119,2120],{"class":1108},"is_ai_generated",[851,2122,1224],{"class":861},[851,2124,879],{"class":861},[851,2126,2127],{"class":861}," true,\n",[851,2129,2130,2132,2135,2137,2139],{"class":853,"line":1012},[851,2131,2062],{"class":861},[851,2133,2134],{"class":1108},"actions",[851,2136,1224],{"class":861},[851,2138,879],{"class":861},[851,2140,2141],{"class":861}," [\n",[851,2143,2144,2147,2149,2152,2154,2156,2158,2161,2163,2165,2167,2170,2172,2174,2176,2179,2181],{"class":853,"line":1019},[851,2145,2146],{"class":861},"    {",[851,2148,1224],{"class":861},[851,2150,2151],{"class":1135},"action",[851,2153,1224],{"class":861},[851,2155,879],{"class":861},[851,2157,2072],{"class":861},[851,2159,2160],{"class":882},"c2pa.created",[851,2162,1224],{"class":861},[851,2164,1126],{"class":861},[851,2166,2072],{"class":861},[851,2168,2169],{"class":1135},"description",[851,2171,1224],{"class":861},[851,2173,879],{"class":861},[851,2175,2072],{"class":861},[851,2177,2178],{"class":882},"Created by Google Generative AI.",[851,2180,1224],{"class":861},[851,2182,2113],{"class":861},[851,2184,2185,2187,2189,2191,2193,2195,2197,2200,2202,2204,2206,2208,2210,2212,2214,2217,2219],{"class":853,"line":1025},[851,2186,2146],{"class":861},[851,2188,1224],{"class":861},[851,2190,2151],{"class":1135},[851,2192,1224],{"class":861},[851,2194,879],{"class":861},[851,2196,2072],{"class":861},[851,2198,2199],{"class":882},"c2pa.edited",[851,2201,1224],{"class":861},[851,2203,1126],{"class":861},[851,2205,2072],{"class":861},[851,2207,2169],{"class":1135},[851,2209,1224],{"class":861},[851,2211,879],{"class":861},[851,2213,2072],{"class":861},[851,2215,2216],{"class":882},"Applied imperceptible SynthID watermark.",[851,2218,1224],{"class":861},[851,2220,2221],{"class":861},"}\n",[851,2223,2224],{"class":853,"line":1031},[851,2225,2226],{"class":861},"  ],\n",[851,2228,2229,2231,2234,2236,2238],{"class":853,"line":1037},[851,2230,2062],{"class":861},[851,2232,2233],{"class":1108},"watermarks",[851,2235,1224],{"class":861},[851,2237,879],{"class":861},[851,2239,2141],{"class":861},[851,2241,2242,2244,2246,2248,2250,2252,2254,2256,2258],{"class":853,"line":1043},[851,2243,2146],{"class":861},[851,2245,1224],{"class":861},[851,2247,2169],{"class":1135},[851,2249,1224],{"class":861},[851,2251,879],{"class":861},[851,2253,2072],{"class":861},[851,2255,2216],{"class":882},[851,2257,1224],{"class":861},[851,2259,2221],{"class":861},[851,2261,2262],{"class":853,"line":1049},[851,2263,2226],{"class":861},[851,2265,2266,2268,2271,2273,2275,2277,2280,2282],{"class":853,"line":1055},[851,2267,2062],{"class":861},[851,2269,2270],{"class":1108},"signature_issuer",[851,2272,1224],{"class":861},[851,2274,879],{"class":861},[851,2276,2072],{"class":861},[851,2278,2279],{"class":882},"Google LLC",[851,2281,1224],{"class":861},[851,2283,2080],{"class":861},[851,2285,2286,2288,2291,2293,2295,2297,2300],{"class":853,"line":1061},[851,2287,2062],{"class":861},[851,2289,2290],{"class":1108},"validation",[851,2292,1224],{"class":861},[851,2294,879],{"class":861},[851,2296,2072],{"class":861},[851,2298,2299],{"class":882},"valid",[851,2301,2302],{"class":861},"\"\n",[851,2304,2305],{"class":853,"line":1067},[851,2306,2221],{"class":861},[769,2308,2309,2310,2313,2314,2317],{},"For an unsigned audio clip you get ",[825,2311,2312],{},"{\"error\": \"No C2PA manifest found\"}",". For a MIDI clip, ",[825,2315,2316],{},"{\"info\": \"MIDI clip — no C2PA manifest applicable\"}",". The Refresh button re-runs the lookup manually.",[776,2319,2321],{"id":2320},"what-this-doesnt-solve","What this doesn't solve",[812,2323,2324],{},[769,2325,2326,2327,2330],{},"This is read-side only. The device tells you the C2PA truth that is ",[1379,2328,2329],{},"already in the file",". It does not sign anything. It does not tell you what corpus trained the model. Andrew's point still stands.",[769,2332,2333,2334,2337],{},"The C2PA community is working on the harder problem. There is an active conversation in the C2PA group about capturing provenance ",[1379,2335,2336],{},"during"," DAW work — signing the project at bounce time, attributing the samples and MIDI sources that went in. That is the generation side. We would like to help build it next.",[776,2339,2341],{"id":2340},"install-and-try-it","Install and try it",[769,2343,2344],{},"You need Ableton Live with Max for Live (Live Suite, or Standard plus the M4L add-on), and macOS for the auto-start script.",[841,2346,2351],{"className":2347,"code":2348,"filename":2349,"language":2350,"meta":847,"style":847},"language-bash shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","# 1. Clone and install (one-time)\ngit clone https://github.com/musictechlab/mtl-c2pa-ableton.git\ncd mtl-c2pa-ableton && poetry install\n\n# 2. Auto-start on login (macOS)\nbash install/install.sh\n\n# 3. Verify\ncurl http://127.0.0.1:8765/health\n# {\"status\":\"ok\"}\n\n# 4. Drop the device on a track\n# Drag device/MTL_C2PA_Ableton_PoC.amxd onto any audio track in Live.\n","setup.sh","bash",[825,2352,2353,2358,2369,2386,2390,2395,2402,2406,2411,2419,2424,2428,2433],{"__ignoreMap":847},[851,2354,2355],{"class":853,"line":854},[851,2356,2357],{"class":898},"# 1. Clone and install (one-time)\n",[851,2359,2360,2363,2366],{"class":853,"line":865},[851,2361,2362],{"class":1135},"git",[851,2364,2365],{"class":882}," clone",[851,2367,2368],{"class":882}," https://github.com/musictechlab/mtl-c2pa-ableton.git\n",[851,2370,2371,2374,2377,2380,2383],{"class":853,"line":873},[851,2372,2373],{"class":1115},"cd",[851,2375,2376],{"class":882}," mtl-c2pa-ableton",[851,2378,2379],{"class":861}," &&",[851,2381,2382],{"class":1135}," poetry",[851,2384,2385],{"class":882}," install\n",[851,2387,2388],{"class":853,"line":886},[851,2389,1016],{"emptyLinePlaceholder":1015},[851,2391,2392],{"class":853,"line":1012},[851,2393,2394],{"class":898},"# 2. Auto-start on login (macOS)\n",[851,2396,2397,2399],{"class":853,"line":1019},[851,2398,2350],{"class":1135},[851,2400,2401],{"class":882}," install/install.sh\n",[851,2403,2404],{"class":853,"line":1025},[851,2405,1016],{"emptyLinePlaceholder":1015},[851,2407,2408],{"class":853,"line":1031},[851,2409,2410],{"class":898},"# 3. Verify\n",[851,2412,2413,2416],{"class":853,"line":1037},[851,2414,2415],{"class":1135},"curl",[851,2417,2418],{"class":882}," http://127.0.0.1:8765/health\n",[851,2420,2421],{"class":853,"line":1043},[851,2422,2423],{"class":898},"# {\"status\":\"ok\"}\n",[851,2425,2426],{"class":853,"line":1049},[851,2427,1016],{"emptyLinePlaceholder":1015},[851,2429,2430],{"class":853,"line":1055},[851,2431,2432],{"class":898},"# 4. Drop the device on a track\n",[851,2434,2435],{"class":853,"line":1061},[851,2436,2437],{"class":898},"# Drag device/MTL_C2PA_Ableton_PoC.amxd onto any audio track in Live.\n",[769,2439,2440,2441,981],{},"That's it. Click a Lyria clip — see the manifest. Full setup detail in the ",[1570,2442,2445],{"href":2443,"rel":2444},"https://github.com/musictechlab/mtl-c2pa-ableton",[1860],"repo README",[776,2447,2449],{"id":2448},"roadmap","Roadmap",[769,2451,2452,2453,2458],{},"Generation-side device next. The plan is a Max for Live effect that signs the project at bounce time and emits a C2PA manifest describing the session's ingredients — samples, MIDI sources, plugin chain. We would like input from the ",[1570,2454,2457],{"href":2455,"rel":2456},"https://c2pa.org/community/",[1860],"C2PA community"," before settling on the assertion shape.",[769,2460,2461],{},"If you are interested in the broader open-source MCP family we have shipped:",[787,2463,2466,2474,2482,2490],{"className":2464},[790,791,2465,793,794],"md:grid-cols-2",[796,2467,2471],{"description":2468,"icon":2469,"title":2470,"to":173},"Read and write ID3, FLAC, and Vorbis tags from Claude — siblings on the metadata layer.","i-lucide-tag","mtl-metadata-mcp",[769,2472,2473],{},"ISRCs, artist, album, year — the rights-and-identifier layer that complements C2PA's provenance layer.",[796,2475,2479],{"description":2476,"icon":2477,"title":2478,"to":607},"Complementary provenance: VHC says a human made this; C2PA says how it was made.","i-lucide-user-check","Verified Human Cert MCP",[769,2480,2481],{},"Together they answer the two questions about an AI-suspect track: was it made by a human, and what does the file declare about its origin?",[796,2483,2487],{"description":2484,"icon":2485,"title":2486,"to":169},"Natural-language queries over Bandcamp revenue CSVs from Claude.","i-lucide-bar-chart-3","mtl-bandcamp-mcp",[769,2488,2489],{},"The same MCP-server pattern, different data source. Wraps the official Bandcamp Sales Report exports.",[796,2491,2495],{"description":2492,"icon":2493,"title":2494,"to":141},"Adjacent metadata extraction — going below the LiveAPI layer.","i-lucide-file-search","Inside .als and .asd files",[769,2496,2497],{},"For when you need to read an Ableton project without opening Ableton — same Max for Live series, different angle.",[776,2499,2501],{"id":2500},"try-it-break-it-send-feedback","Try it, break it, send feedback",[769,2503,2504,2505,2509],{},"The device is MIT-licensed. Repo: ",[1570,2506,2508],{"href":2443,"rel":2507},[1860],"musictechlab/mtl-c2pa-ableton",". Issues and PRs welcome. If you build something on top of the local HTTP server (a Logic plugin, a REAPER script, a standalone viewer), tell us — same pattern works for any DAW that can shell out to localhost.",[2511,2512],"hr",{},[776,2514,2516],{"id":2515},"need-help-integrating-c2pa-into-your-music-workflow","Need help integrating C2PA into your music workflow?",[769,2518,2519],{},"Adding provenance to your distribution pipeline, AI music platform, DAW plugin, or rights workflow? We have been there.",[769,2521,2522,2526],{},[1570,2523,2525],{"href":2524},"/contact","Let's talk"," — no sales pitch, just honest engineering advice.",[1497,2528,2529],{},"html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}",{"title":847,"searchDepth":865,"depth":865,"links":2531},[2532,2533,2534,2535,2536,2537,2538,2539,2540,2541,2542],{"id":1587,"depth":865,"text":1588},{"id":1618,"depth":865,"text":1619},{"id":1636,"depth":865,"text":1637},{"id":1871,"depth":865,"text":1872},{"id":1990,"depth":865,"text":1991},{"id":2040,"depth":865,"text":2041},{"id":2320,"depth":865,"text":2321},{"id":2340,"depth":865,"text":2341},{"id":2448,"depth":865,"text":2449},{"id":2500,"depth":865,"text":2501},{"id":2515,"depth":865,"text":2516},"2026-05-29T00:00:00.000Z","In May 2026 we shipped an MCP for reading C2PA manifests in music. This post is the follow-up: the same reader, now inside Ableton Live as an open-source Max for Live device.",[2546,2549,2552,2555,2558],{"question":2547,"answer":2548},"Why a local HTTP server instead of a CLI shell-out or a cloud API?","Python startup takes about 300 ms, which you feel on every clip click. A persistent local server keeps c2pa-python warm in memory. Cloud would mean uploading audio to a server just to inspect a file the user already has on disk — wrong shape for reading, right shape for generation.",{"question":2550,"answer":2551},"Does the device work without the Max for Live add-on?","No. You need Ableton Live Suite (which includes Max for Live), or Ableton Live Standard with the Max for Live add-on. The device is a .amxd file — it cannot run as a stock VST or AU plugin.",{"question":2553,"answer":2554},"Does the device upload my audio anywhere?","No. The FastAPI server binds to 127.0.0.1 only — loopback. No external network access. The Node for Max HTTP client only ever talks to your own machine. You can verify with `lsof -i :8765` while the server runs.",{"question":2556,"answer":2557},"What happens with MIDI clips or recorded audio that has no source file?","The device returns a structured info message: MIDI clip — no C2PA manifest applicable, or audio clip has no file path (recorded in session?). It never crashes the device, never blocks Live.",{"question":2559,"answer":2560},"Will this device tell me whose work trained the model that generated my Lyria stem?","No, and that is the next problem the C2PA community is working on. This device surfaces the output manifest — what Google Lyria declared about the file. Training-corpus attribution requires upstream provenance on the training data itself, which almost no major dataset is C2PA-signed today.",{"src":2562},"/images/blog/musictechlab_blog_c2pa-in-ableton-hero.webp",{"enabled":1015,"items":2564},[2565,2568,2571,2573],{"text":2566,"icon":2567},"Google Lyria signs every generated MP3 with a C2PA manifest, but Ableton has no way to display that information today.","i-lucide-eye-off",{"text":2569,"icon":2570},"A Max for Live device + local FastAPI server makes the manifest visible the moment you click a clip.","i-lucide-mouse-pointer-click",{"text":2572,"icon":1535},"All in one repo — Python HTTP server + Max for Live device. One clone, one install, no cloud round-trip.",{"text":2574,"icon":2575},"Read-side only — the harder problem (signing the DAW project, attributing the training corpus) is next.","i-lucide-arrow-right",{},{"title":2578,"description":2579},"C2PA in Ableton: Open-Source Max for Live","Open-source Max for Live device that displays C2PA provenance manifests for the selected audio clip in Ableton Live. Read Lyria signatures inside your DAW.",[2581,2582,2583,2584,2585,1544,2586],"C2PA","Ableton","max-for-live","provenance","AI","open-source","bHaegB7u78Z6SHCMVTmTpBO76170wurgB4enSfRTMbs",{"id":2589,"title":602,"authors":2590,"badge":2593,"body":2594,"category":1508,"client":1509,"date":3371,"description":3372,"extension":1512,"faq":3373,"featured":1015,"featuredOrder":3386,"hidden":69,"image":3387,"keyTakeaways":3390,"meta":3400,"navigation":1015,"path":603,"seo":3401,"status":1509,"stem":604,"tags":3404,"teaser":1509,"__hash__":3411,"score":865},"posts/blog/software-development/mcp-codemagic-trigger-ci-builds-from-claude-code.md",[2591],{"name":1560,"to":759,"avatar":2592},{"src":761},{"label":1563,"color":764},{"type":766,"value":2595,"toc":3361},[2596,2611,2614,2623,2627,2636,2639,2643,2646,2720,2723,2727,2730,2736,2739,2765,2769,2782,2921,2928,3143,3146,3155,3159,3162,3205,3208,3250,3253,3257,3268,3282,3300,3304,3307,3318,3323,3327,3330,3355,3358],[769,2597,2598,2599,2604,2605,2610],{},"During development of ",[1570,2600,2603],{"href":2601,"rel":2602},"https://vimoswim.com",[1860],"VimoSwim",", every iOS and Android release runs through ",[1570,2606,2609],{"href":2607,"rel":2608},"https://codemagic.io",[1860],"Codemagic",", our CI/CD platform of choice for Flutter. The pipeline itself is solid, but checking on a build still meant leaving the terminal, opening the dashboard, and navigating through the project to find a status that a single API call could have returned.",[769,2612,2613],{},"So we asked the obvious question: what if Claude could orchestrate the build directly, triggering a release, polling its status, and cancelling a misconfigured run from the same environment where we already write the code?",[769,2615,2616,2617,2622],{},"Because no Codemagic MCP server existed, we built one. Today we are open-sourcing ",[1570,2618,2621],{"href":2619,"rel":2620},"https://github.com/musictechlab/mcp-codemagic",[1860],"mcp-codemagic",", a Model Context Protocol server that brings Codemagic CI/CD directly into Claude Code.",[776,2624,2626],{"id":2625},"what-is-mcp","What is MCP?",[769,2628,2629,2630,2635],{},"The ",[1570,2631,2634],{"href":2632,"rel":2633},"https://modelcontextprotocol.io/",[1860],"Model Context Protocol"," (MCP) is an open standard that lets AI assistants like Claude interact with external tools and services. Think of it as a plugin system: you register an MCP server, and Claude gains new abilities.",[769,2637,2638],{},"In our case, those abilities are CI/CD builds.",[776,2640,2642],{"id":2641},"what-the-server-does","What the server does",[769,2644,2645],{},"mcp-codemagic exposes six tools that cover the full Codemagic build lifecycle:",[1388,2647,2648,2658],{},[1391,2649,2650],{},[1394,2651,2652,2655],{},[1397,2653,2654],{},"Tool",[1397,2656,2657],{},"What it does",[1406,2659,2660,2670,2680,2690,2700,2710],{},[1394,2661,2662,2667],{},[1411,2663,2664],{},[825,2665,2666],{},"codemagic_list_apps",[1411,2668,2669],{},"List every connected app and its workflow ids",[1394,2671,2672,2677],{},[1411,2673,2674],{},[825,2675,2676],{},"codemagic_get_app",[1411,2678,2679],{},"Get one app's repository, branches, and workflows",[1394,2681,2682,2687],{},[1411,2683,2684],{},[825,2685,2686],{},"codemagic_start_build",[1411,2688,2689],{},"Trigger a build for an app and workflow on a branch or tag",[1394,2691,2692,2697],{},[1411,2693,2694],{},[825,2695,2696],{},"codemagic_get_build",[1411,2698,2699],{},"Get the status and details of a build",[1394,2701,2702,2707],{},[1411,2703,2704],{},[825,2705,2706],{},"codemagic_list_builds",[1411,2708,2709],{},"List builds, filtered by app, workflow, branch, or status",[1394,2711,2712,2717],{},[1411,2713,2714],{},[825,2715,2716],{},"codemagic_cancel_build",[1411,2718,2719],{},"Cancel a running or queued build",[769,2721,2722],{},"That is the entire surface area: discover, trigger, watch, and cancel, which is precisely enough to drive a mobile release without ever touching the dashboard.",[776,2724,2726],{"id":2725},"how-it-looks-in-practice","How it looks in practice",[769,2728,2729],{},"You talk to it in plain language. Ask for the latest build of an app and you get a clean, readable summary instead of raw JSON:",[769,2731,2732],{},[1092,2733],{"alt":2734,"src":2735},"Recent Codemagic build status rendered in Claude Code","/images/blog/musictechlab_blog_mcp_codemagic_build_status.webp",[769,2737,2738],{},"A few prompts we actually use:",[2740,2741,2742,2745,2755,2762],"ul",{},[1826,2743,2744],{},"\"List my Codemagic apps and their workflows.\"",[1826,2746,2747,2748,2751,2752,2754],{},"\"Start the ",[825,2749,2750],{},"ios-release"," workflow for vimoswim-coach on ",[825,2753,831],{},".\"",[1826,2756,2757,2758,2761],{},"\"What's the status of build ",[825,2759,2760],{},"6a28f92c...","?\"",[1826,2763,2764],{},"\"Cancel that build, it's pointing at the wrong branch.\"",[776,2766,2768],{"id":2767},"how-it-wraps-the-api","How it wraps the API",[769,2770,2771,2772,2777,2778,2781],{},"The whole thing is a thin wrapper around the ",[1570,2773,2776],{"href":2774,"rel":2775},"https://docs.codemagic.io/rest-api/overview/",[1860],"Codemagic REST API",". Authentication is a single token sent in the ",[825,2779,2780],{},"x-auth-token"," header, read from an environment variable so it never lands in code or git history.",[841,2783,2786],{"className":1098,"code":2784,"filename":2785,"language":1101,"meta":847,"style":847},"DEFAULT_BASE_URL = \"https://api.codemagic.io\"\nTOKEN_ENV = \"CODEMAGIC_API_KEY\"\n\n\ndef _headers() -> dict[str, str]:\n    return {\n        \"x-auth-token\": _token(),\n        \"Accept\": \"application/json\",\n        \"Content-Type\": \"application/json\",\n    }\n","codemagic.py",[825,2787,2788,2802,2816,2820,2824,2853,2860,2877,2897,2916],{"__ignoreMap":847},[851,2789,2790,2793,2795,2797,2800],{"class":853,"line":854},[851,2791,2792],{"class":993},"DEFAULT_BASE_URL ",[851,2794,1212],{"class":861},[851,2796,2072],{"class":861},[851,2798,2799],{"class":882},"https://api.codemagic.io",[851,2801,2302],{"class":861},[851,2803,2804,2807,2809,2811,2814],{"class":853,"line":865},[851,2805,2806],{"class":993},"TOKEN_ENV ",[851,2808,1212],{"class":861},[851,2810,2072],{"class":861},[851,2812,2813],{"class":882},"CODEMAGIC_API_KEY",[851,2815,2302],{"class":861},[851,2817,2818],{"class":853,"line":873},[851,2819,1016],{"emptyLinePlaceholder":1015},[851,2821,2822],{"class":853,"line":886},[851,2823,1016],{"emptyLinePlaceholder":1015},[851,2825,2826,2829,2832,2835,2837,2840,2843,2846,2848,2850],{"class":853,"line":1012},[851,2827,2828],{"class":1108},"def",[851,2830,2831],{"class":1115}," _headers",[851,2833,2834],{"class":861},"()",[851,2836,1142],{"class":861},[851,2838,2839],{"class":993}," dict",[851,2841,2842],{"class":861},"[",[851,2844,2845],{"class":1135},"str",[851,2847,1126],{"class":861},[851,2849,1136],{"class":1135},[851,2851,2852],{"class":861},"]:\n",[851,2854,2855,2857],{"class":853,"line":1019},[851,2856,1345],{"class":1161},[851,2858,2859],{"class":861}," {\n",[851,2861,2862,2865,2867,2869,2871,2874],{"class":853,"line":1025},[851,2863,2864],{"class":861},"        \"",[851,2866,2780],{"class":882},[851,2868,1224],{"class":861},[851,2870,879],{"class":861},[851,2872,2873],{"class":1115}," _token",[851,2875,2876],{"class":861},"(),\n",[851,2878,2879,2881,2884,2886,2888,2890,2893,2895],{"class":853,"line":1031},[851,2880,2864],{"class":861},[851,2882,2883],{"class":882},"Accept",[851,2885,1224],{"class":861},[851,2887,879],{"class":861},[851,2889,2072],{"class":861},[851,2891,2892],{"class":882},"application/json",[851,2894,1224],{"class":861},[851,2896,2080],{"class":861},[851,2898,2899,2901,2904,2906,2908,2910,2912,2914],{"class":853,"line":1037},[851,2900,2864],{"class":861},[851,2902,2903],{"class":882},"Content-Type",[851,2905,1224],{"class":861},[851,2907,879],{"class":861},[851,2909,2072],{"class":861},[851,2911,2892],{"class":882},[851,2913,1224],{"class":861},[851,2915,2080],{"class":861},[851,2917,2918],{"class":853,"line":1043},[851,2919,2920],{"class":861},"    }\n",[769,2922,2923,2924,2927],{},"Triggering a build is a ",[825,2925,2926],{},"POST /builds"," with the app id, the workflow id, and a branch or tag. We validate that you pass exactly one of branch or tag before the request ever leaves your machine:",[841,2929,2931],{"className":1098,"code":2930,"filename":2785,"language":1101,"meta":847,"style":847},"def start_build(app_id, workflow_id, *, branch=None, tag=None):\n    if bool(branch) == bool(tag):\n        raise CodemagicError(\"Provide exactly one of 'branch' or 'tag'.\")\n    payload = {\"appId\": app_id, \"workflowId\": workflow_id}\n    if branch:\n        payload[\"branch\"] = branch\n    if tag:\n        payload[\"tag\"] = tag\n    return _request(\"POST\", \"/builds\", json=payload)\n",[825,2932,2933,2969,2994,3013,3049,3058,3080,3088,3107],{"__ignoreMap":847},[851,2934,2935,2937,2940,2942,2945,2947,2950,2952,2955,2957,2960,2963,2966],{"class":853,"line":854},[851,2936,2828],{"class":1108},[851,2938,2939],{"class":1115}," start_build",[851,2941,1119],{"class":861},[851,2943,2944],{"class":1129},"app_id",[851,2946,1126],{"class":861},[851,2948,2949],{"class":1129}," workflow_id",[851,2951,1126],{"class":861},[851,2953,2954],{"class":861}," *",[851,2956,970],{"class":993},[851,2958,2959],{"class":1129},"branch",[851,2961,2962],{"class":861},"=None,",[851,2964,2965],{"class":1129}," tag",[851,2967,2968],{"class":861},"=None):\n",[851,2970,2971,2973,2976,2978,2980,2982,2985,2987,2989,2992],{"class":853,"line":865},[851,2972,1162],{"class":1161},[851,2974,2975],{"class":1135}," bool",[851,2977,1119],{"class":861},[851,2979,2959],{"class":1115},[851,2981,1139],{"class":861},[851,2983,2984],{"class":861}," ==",[851,2986,2975],{"class":1135},[851,2988,1119],{"class":861},[851,2990,2991],{"class":1115},"tag",[851,2993,1197],{"class":861},[851,2995,2996,2999,3002,3004,3006,3009,3011],{"class":853,"line":873},[851,2997,2998],{"class":1161},"        raise",[851,3000,3001],{"class":1115}," CodemagicError",[851,3003,1119],{"class":861},[851,3005,1224],{"class":861},[851,3007,3008],{"class":882},"Provide exactly one of 'branch' or 'tag'.",[851,3010,1224],{"class":861},[851,3012,1283],{"class":861},[851,3014,3015,3018,3020,3022,3024,3027,3029,3031,3034,3036,3038,3041,3043,3045,3047],{"class":853,"line":886},[851,3016,3017],{"class":993},"    payload ",[851,3019,1212],{"class":861},[851,3021,2094],{"class":861},[851,3023,1224],{"class":861},[851,3025,3026],{"class":882},"appId",[851,3028,1224],{"class":861},[851,3030,879],{"class":861},[851,3032,3033],{"class":993}," app_id",[851,3035,1126],{"class":861},[851,3037,2072],{"class":861},[851,3039,3040],{"class":882},"workflowId",[851,3042,1224],{"class":861},[851,3044,879],{"class":861},[851,3046,2949],{"class":993},[851,3048,2221],{"class":861},[851,3050,3051,3053,3056],{"class":853,"line":1012},[851,3052,1162],{"class":1161},[851,3054,3055],{"class":993}," branch",[851,3057,862],{"class":861},[851,3059,3060,3063,3065,3067,3069,3071,3074,3077],{"class":853,"line":1019},[851,3061,3062],{"class":993},"        payload",[851,3064,2842],{"class":861},[851,3066,1224],{"class":861},[851,3068,2959],{"class":882},[851,3070,1224],{"class":861},[851,3072,3073],{"class":861},"]",[851,3075,3076],{"class":861}," =",[851,3078,3079],{"class":993}," branch\n",[851,3081,3082,3084,3086],{"class":853,"line":1025},[851,3083,1162],{"class":1161},[851,3085,2965],{"class":993},[851,3087,862],{"class":861},[851,3089,3090,3092,3094,3096,3098,3100,3102,3104],{"class":853,"line":1031},[851,3091,3062],{"class":993},[851,3093,2842],{"class":861},[851,3095,1224],{"class":861},[851,3097,2991],{"class":882},[851,3099,1224],{"class":861},[851,3101,3073],{"class":861},[851,3103,3076],{"class":861},[851,3105,3106],{"class":993}," tag\n",[851,3108,3109,3111,3114,3116,3118,3120,3122,3124,3126,3129,3131,3133,3136,3138,3141],{"class":853,"line":1037},[851,3110,1345],{"class":1161},[851,3112,3113],{"class":1115}," _request",[851,3115,1119],{"class":861},[851,3117,1224],{"class":861},[851,3119,1844],{"class":882},[851,3121,1224],{"class":861},[851,3123,1126],{"class":861},[851,3125,2072],{"class":861},[851,3127,3128],{"class":882},"/builds",[851,3130,1224],{"class":861},[851,3132,1126],{"class":861},[851,3134,3135],{"class":1129}," json",[851,3137,1212],{"class":861},[851,3139,3140],{"class":1115},"payload",[851,3142,1283],{"class":861},[769,3144,3145],{},"Errors come back as JSON instead of raising, so Claude can read a failed status code and explain it to you rather than crashing the tool call.",[912,3147,3148],{},[769,3149,3150,3151,3154],{},"The token lives under Codemagic, in User settings or Team settings, then Integrations, then Codemagic API. Keep it in a local ",[825,3152,3153],{},".env"," file. It controls real builds, so treat it like a password.",[776,3156,3158],{"id":3157},"setting-it-up","Setting it up",[769,3160,3161],{},"It is a small Python project managed with Poetry.",[841,3163,3166],{"className":2347,"code":3164,"filename":3165,"language":2350,"meta":847,"style":847},"git clone https://github.com/musictechlab/mcp-codemagic.git\ncd mcp-codemagic\npoetry install\ncp .env.example .env   # then add your CODEMAGIC_API_KEY\n","terminal",[825,3167,3168,3177,3184,3191],{"__ignoreMap":847},[851,3169,3170,3172,3174],{"class":853,"line":854},[851,3171,2362],{"class":1135},[851,3173,2365],{"class":882},[851,3175,3176],{"class":882}," https://github.com/musictechlab/mcp-codemagic.git\n",[851,3178,3179,3181],{"class":853,"line":865},[851,3180,2373],{"class":1115},[851,3182,3183],{"class":882}," mcp-codemagic\n",[851,3185,3186,3189],{"class":853,"line":873},[851,3187,3188],{"class":1135},"poetry",[851,3190,2385],{"class":882},[851,3192,3193,3196,3199,3202],{"class":853,"line":886},[851,3194,3195],{"class":1135},"cp",[851,3197,3198],{"class":882}," .env.example",[851,3200,3201],{"class":882}," .env",[851,3203,3204],{"class":898},"   # then add your CODEMAGIC_API_KEY\n",[769,3206,3207],{},"Register it with Claude Code in one command:",[841,3209,3211],{"className":2347,"code":3210,"filename":3165,"language":2350,"meta":847,"style":847},"claude mcp add codemagic -- poetry --directory /path/to/mcp-codemagic run python -m mcp_codemagic\n",[825,3212,3213],{"__ignoreMap":847},[851,3214,3215,3218,3221,3224,3227,3230,3232,3235,3238,3241,3244,3247],{"class":853,"line":854},[851,3216,3217],{"class":1135},"claude",[851,3219,3220],{"class":882}," mcp",[851,3222,3223],{"class":882}," add",[851,3225,3226],{"class":882}," codemagic",[851,3228,3229],{"class":882}," --",[851,3231,2382],{"class":882},[851,3233,3234],{"class":882}," --directory",[851,3236,3237],{"class":882}," /path/to/mcp-codemagic",[851,3239,3240],{"class":882}," run",[851,3242,3243],{"class":882}," python",[851,3245,3246],{"class":882}," -m",[851,3248,3249],{"class":882}," mcp_codemagic\n",[769,3251,3252],{},"That is it. Claude now has six new tools and can talk to your Codemagic account.",[776,3254,3256],{"id":3255},"testing-the-dangerous-part","Testing the dangerous part",[769,3258,3259,3260,3263,3264,3267],{},"Two of the tools mutate real infrastructure: ",[825,3261,3262],{},"start_build"," and ",[825,3265,3266],{},"cancel_build",". Unit tests with mocked HTTP cover the request construction, but they cannot confirm that the write path actually behaves against the live API, and we were not willing to burn CI minutes on a full release just to find out.",[769,3269,3270,3271,3274,3275,3277,3278,3281],{},"The solution was a trigger-and-cancel test: start a genuine build on a branch, confirm it registers as ",[825,3272,3273],{},"queued",", then immediately cancel it. The build transitioned from ",[825,3276,3273],{}," to ",[825,3279,3280],{},"canceled"," within roughly three seconds, before the build machine had even been provisioned, which verified both write tools at a near-zero cost.",[1350,3283,3284],{},[769,3285,3286,3287,970,3290,970,3293,970,3296,3299],{},"We deliberately kept the read tools and the write tools separate in our validation. Read tools (",[825,3288,3289],{},"list_apps",[825,3291,3292],{},"get_app",[825,3294,3295],{},"get_build",[825,3297,3298],{},"list_builds",") are safe to run anywhere. Write tools touch real builds, so we gate them behind explicit intent.",[776,3301,3303],{"id":3302},"why-we-built-this","Why we built this",[769,3305,3306],{},"The honest answer is that it eliminates friction from an operation we perform constantly. When a release is compiling, the recurring question of \"is it done yet?\" should not demand a context switch into a browser tab and several clicks through a dashboard.",[769,3308,3309,3310,3313,3314,3317],{},"It also continues a pattern we keep returning to: wrapping the services we already pay for in MCP, so that the assistant we already use can operate them directly. We applied the same approach to ",[1570,3311,3312],{"href":659},"SignNow e-signatures"," and to the ",[1570,3315,3316],{"href":607},"Verified Human Cert registry",". Codemagic is the identical idea, redirected at our mobile release pipeline.",[812,3319,3320],{},[769,3321,3322],{},"This server can trigger and cancel real builds. Be careful which tools you expose in shared or automated environments, and review who has access to the API token.",[776,3324,3326],{"id":3325},"try-it","Try it",[769,3328,3329],{},"mcp-codemagic is open source under the MIT license and ready to use today.",[787,3331,3333,3344],{"className":3332},[790,791,2465,793,794],[796,3334,3338],{"description":3335,"icon":3336,"title":3337},"Clone the repo, install with Poetry, and register it with Claude Code in minutes.","i-lucide-github","Get the code",[769,3339,3340],{},[1570,3341,3343],{"href":2619,"rel":3342},[1860],"github.com/musictechlab/mcp-codemagic",[796,3345,3349],{"description":3346,"icon":3347,"title":3348},"We build mobile, music, and AI tooling for teams that ship.","i-lucide-rocket","Build something with us",[769,3350,3351],{},[1570,3352,2525],{"href":3353,"rel":3354},"https://musictechlab.io/contact",[1860],[769,3356,3357],{},"If you run Flutter apps on Codemagic and you live in your terminal, give it a spin. Issues and pull requests are welcome.",[1497,3359,3360],{},"html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sHdIc, html code.shiki .sHdIc{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#EEFFFF;--shiki-default-font-style:italic;--shiki-dark:#BABED8;--shiki-dark-font-style:italic}html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}",{"title":847,"searchDepth":865,"depth":865,"links":3362},[3363,3364,3365,3366,3367,3368,3369,3370],{"id":2625,"depth":865,"text":2626},{"id":2641,"depth":865,"text":2642},{"id":2725,"depth":865,"text":2726},{"id":2767,"depth":865,"text":2768},{"id":3157,"depth":865,"text":3158},{"id":3255,"depth":865,"text":3256},{"id":3302,"depth":865,"text":3303},{"id":3325,"depth":865,"text":3326},"2026-06-10T00:00:00.000Z","We open-sourced mcp-codemagic, an MCP server that brings Codemagic CI/CD into Claude Code. List apps, trigger builds, check status, and cancel runs without leaving your terminal.",[3374,3377,3380,3383],{"question":3375,"answer":3376},"What is the Codemagic MCP server?","It is an open-source Model Context Protocol server that connects the Codemagic CI/CD REST API to Claude Code and other MCP-compatible clients. It lets you list apps, trigger builds, check build status, and cancel runs directly from your terminal.",{"question":3378,"answer":3379},"Is this an official Codemagic product?","No. This is a community project built by MusicTech Lab. It is not affiliated with or endorsed by Codemagic or Nevercode.",{"question":3381,"answer":3382},"What tools does the server provide?","Six tools: codemagic_list_apps, codemagic_get_app, codemagic_start_build, codemagic_get_build, codemagic_list_builds, and codemagic_cancel_build.",{"question":3384,"answer":3385},"Can I use this with clients other than Claude Code?","Yes. Any MCP-compatible client can use this server. Claude Code is the primary client we built it for, but the MCP protocol is open and supported by a growing number of tools.",34,{"src":3388,"credit":3389},"/images/blog/musictechlab_blog_mcp_codemagic.webp","Photo by [Fotis Fotopoulos](https://unsplash.com/@ffstop) on [Unsplash](https://unsplash.com)",{"enabled":1015,"items":3391},[3392,3395,3398],{"text":3393,"icon":3394},"mcp-codemagic exposes six tools covering the full Codemagic build lifecycle.","i-lucide-terminal",{"text":3396,"icon":3397},"Natural language replaces REST calls: trigger, watch, and cancel mobile builds from the terminal.","i-lucide-sparkles",{"text":3399,"icon":1535},"It wraps the Codemagic REST API with x-auth-token, in about 150 lines of Python.",{},{"title":3402,"description":3403},"Codemagic MCP Server for Claude Code","Open-source MCP server for Codemagic CI/CD. Trigger builds, check status, and cancel runs from Claude Code or any MCP client.",[1242,2586,3405,3406,3407,3408,3409,3410],"codemagic","ci-cd","claude-code","flutter","mobile","developer-tools","UeQUV1v_lNykRPZ6kwYbyauDL3QwQk5VkOHtIMMvTAY",{"id":3413,"title":438,"authors":3414,"badge":1509,"body":3417,"category":1508,"client":1509,"date":3421,"description":3422,"extension":1512,"faq":1509,"featured":69,"featuredOrder":1509,"hidden":69,"image":3423,"keyTakeaways":1509,"meta":3425,"navigation":1015,"path":439,"seo":3426,"status":3428,"stem":440,"tags":3429,"teaser":3433,"__hash__":3434,"score":865},"posts/blog/software-development/cross-platform-problem-for-creators.md",[3415],{"name":758,"to":759,"avatar":3416},{"src":761},{"type":766,"value":3418,"toc":3419},[],{"title":847,"searchDepth":865,"depth":865,"links":3420},[],"2026-05-22T00:00:00.000Z","Ideas happen anywhere — rehearsal room, commute, studio. But syncing between devices stays manual and error-prone, with no offline or conflict handling.",{"src":3424},"/images/blog/musictechlab_blog_cross-platform-creators.webp",{},{"title":3427,"description":3422},"Cross-Platform Problem for Music Creators","upcoming",[3430,3409,3431,3432],"music-tech","cross-platform","sync","We'll explore the gap between capturing an idea on your phone and working on it in the studio — and why no tool has solved this properly yet.","RK00VMRUDYxATUvP1X2AYxn6c2hE1-pXm4Bm0lXycU8",{"id":3436,"title":618,"authors":3437,"badge":3443,"body":3445,"category":1508,"client":1509,"date":3721,"description":3722,"extension":1512,"faq":1509,"featured":1015,"featuredOrder":1509,"hidden":69,"image":3723,"keyTakeaways":3726,"meta":3736,"navigation":1015,"path":619,"seo":3737,"status":1509,"stem":620,"tags":3740,"teaser":1509,"__hash__":3746,"score":865},"posts/blog/software-development/musictechlab_blog_verified_human_partnership.md",[3438],{"name":3439,"to":3440,"avatar":3441},"Maciej Dulski","https://www.linkedin.com/in/maciej-dulski/",{"src":3442},"/images/cdn-migrated/maciej-dulski-400x400.webp",{"label":3444,"color":764},"Partnership",{"type":766,"value":3446,"toc":3703},[3447,3450,3453,3457,3460,3463,3466,3470,3473,3476,3481,3484,3488,3491,3495,3498,3502,3508,3511,3514,3517,3520,3524,3527,3538,3542,3545,3556,3560,3563,3569,3572,3583,3586,3590,3601,3604,3607,3618,3621,3625,3636,3639,3643,3646,3660,3664,3668,3671,3674,3677,3681],[769,3448,3449],{},"The music industry is entering a phase where the question is no longer how music is made, but whether we can still trust what we hear.",[769,3451,3452],{},"At MusicTech Lab, we build software for the future of the music industry. That future must still respect the people who create it. This is why we are partnering with Verified Human — an initiative focused on establishing verifiable standards for human-made music in the age of AI.",[776,3454,3456],{"id":3455},"why-this-partnership-matters","Why this partnership matters",[769,3458,3459],{},"As generative AI tools reshape production workflows, questions around authorship, ownership, and attribution are becoming structural rather than theoretical.",[769,3461,3462],{},"The core challenge is no longer detection. It is provenance.",[769,3464,3465],{},"This collaboration focuses on restoring that missing layer — not by detecting AI usage, but by verifying human creation through structured, evidence-based certification.",[776,3467,3469],{"id":3468},"our-role-in-the-project","Our role in the project",[769,3471,3472],{},"MusicTech Lab acts as a technical advisor and audit partner to Verified Human.",[769,3474,3475],{},"Our involvement spans three areas:",[3477,3478,3480],"h3",{"id":3479},"technical-review","Technical review",[769,3482,3483],{},"We are conducting a full technical and security review of the certification platform, focusing on architecture, API design, and system reliability.",[3477,3485,3487],{"id":3486},"product-and-architecture-advisory","Product and architecture advisory",[769,3489,3490],{},"We support the ongoing development of the platform’s infrastructure, including scalability, developer experience, and integration patterns.",[3477,3492,3494],{"id":3493},"ecosystem-and-go-to-market-collaboration","Ecosystem and go-to-market collaboration",[769,3496,3497],{},"We collaborate on ecosystem development and industry adoption of “human-made” music as a verifiable standard.",[776,3499,3501],{"id":3500},"how-verified-human-works","How Verified Human works",[769,3503,3504],{},[1092,3505],{"alt":3506,"src":3507},"Verified Human Registry API interface","/images/blog/musictechlab_verified_human_screenshot_01.webp",[769,3509,3510],{},"Verified Human is not an AI detection system.",[769,3512,3513],{},"Detection asks what something is not. Certification proves what something is.",[769,3515,3516],{},"The system is an evidence-based framework (USPTO Serial #99666944) that verifies whether recorded audio was produced by human professionals.",[769,3518,3519],{},"It operates across two certification tracks:",[3477,3521,3523],{"id":3522},"track-a-music-recordings","Track A — Music & Recordings",[769,3525,3526],{},"A tiered evidence model:",[2740,3528,3529,3532,3535],{},[1826,3530,3531],{},"Fully Verified: session files and professional production evidence provided",[1826,3533,3534],{},"Verified: partial production documentation and validation",[1826,3536,3537],{},"Historically Attested: recordings created before the era of generative AI",[3477,3539,3541],{"id":3540},"track-b-podcasts-spoken-word","Track B — Podcasts & Spoken Word",[769,3543,3544],{},"Verification of whether content is:",[2740,3546,3547,3550,3553],{},[1826,3548,3549],{},"human-voiced",[1826,3551,3552],{},"human-scripted",[1826,3554,3555],{},"human-produced",[776,3557,3559],{"id":3558},"api-and-developer-integration","API and developer integration",[769,3561,3562],{},"The Verified Human registry exposes a public API designed for real-time verification using ISRC codes, artist names, or certificate identifiers.",[769,3564,3565],{},[1092,3566],{"alt":3567,"src":3568},"Certificate Details","/images/blog/musictechlab_verified_human_screenshot_02.webp",[769,3570,3571],{},"This enables:",[2740,3573,3574,3577,3580],{},[1826,3575,3576],{},"streaming platforms",[1826,3578,3579],{},"rights organizations",[1826,3581,3582],{},"catalog systems",[769,3584,3585],{},"to verify certification status directly during ingestion or playback.",[776,3587,3589],{"id":3588},"mcp-based-ai-integration","MCP-based AI integration",[769,3591,3592,3593,3596],{},"A key technical component of the ecosystem is the MCP server:",[3594,3595],"br",{},[1570,3597,3600],{"href":3598,"rel":3599},"https://github.com/musictechlab/mcp-verifiedhumancert",[1860],"mcp-verifiedhumancert",[769,3602,3603],{},"Built on the Model Context Protocol, it allows AI systems to query the Verified Human registry as a native tool inside LLM-based workflows.",[769,3605,3606],{},"It supports:",[2740,3608,3609,3612,3615],{},[1826,3610,3611],{},"ISRC-based verification",[1826,3613,3614],{},"certificate lookup",[1826,3616,3617],{},"artist + track resolution",[769,3619,3620],{},"This enables multi-agent workflows where metadata extraction and certification verification happen in a single automated pipeline.",[776,3622,3624],{"id":3623},"multi-agent-workflow-example","Multi-agent workflow example",[1823,3626,3627,3630,3633],{},[1826,3628,3629],{},"One agent extracts ISRC from an audio file",[1826,3631,3632],{},"Another agent queries the Verified Human registry",[1826,3634,3635],{},"An orchestrator (e.g. Claude) combines the results",[769,3637,3638],{},"This removes the need for custom integration layers between metadata systems and certification APIs.",[776,3640,3642],{"id":3641},"implementation-support","Implementation support",[769,3644,3645],{},"MusicTech Lab supports implementation across the ecosystem:",[2740,3647,3648,3651,3654,3657],{},[1826,3649,3650],{},"DSPs: batch ISRC verification during catalog ingestion",[1826,3652,3653],{},"Search systems: filtering for verified human-made content",[1826,3655,3656],{},"Podcast networks: RSS-based certification metadata integration",[1826,3658,3659],{},"Developers: embedding MCP-based verification into AI workflows",[3661,3662],"testimonials",{":items":3663},"[{\"quote\":\"Detection asks whether something is fake. Certification proves something is real. Real artists deserve a system that names them, not a model that erases them. That is what we are building, and that is why we wanted MusicTech Lab in the room from day one.\",\"name\":\"Jim Anderson\",\"description\":\"Founder and CEO @ Verified Human\",\"avatar\":\"/images/blog/jim-anderson-400-400.webp\"}]",[776,3665,3667],{"id":3666},"a-new-industry-standard","A new industry standard",[769,3669,3670],{},"This collaboration is not just a technical integration.",[769,3672,3673],{},"It is an attempt to define a trust layer for music in the AI era — where “human-made” becomes a verifiable, machine-readable attribute rather than a subjective claim.",[769,3675,3676],{},"By combining Verified Human’s vision with MusicTech Lab’s technical expertise and independent review, we are working toward infrastructure that keeps the human element at the center of the music industry.",[776,3678,3680],{"id":3679},"resources","Resources",[2740,3682,3683,3690,3697],{},[1826,3684,3685,3686],{},"Official website: ",[1570,3687,3688],{"href":3688,"rel":3689},"https://verifiedhumancert.com/",[1860],[1826,3691,3692,3693],{},"Developer API: ",[1570,3694,3695],{"href":3695,"rel":3696},"https://verifiedhumancert.com/developers",[1860],[1826,3698,3699,3700],{},"MCP server (open-source): ",[1570,3701,3598],{"href":3598,"rel":3702},[1860],{"title":847,"searchDepth":865,"depth":865,"links":3704},[3705,3706,3711,3715,3716,3717,3718,3719,3720],{"id":3455,"depth":865,"text":3456},{"id":3468,"depth":865,"text":3469,"children":3707},[3708,3709,3710],{"id":3479,"depth":873,"text":3480},{"id":3486,"depth":873,"text":3487},{"id":3493,"depth":873,"text":3494},{"id":3500,"depth":865,"text":3501,"children":3712},[3713,3714],{"id":3522,"depth":873,"text":3523},{"id":3540,"depth":873,"text":3541},{"id":3558,"depth":865,"text":3559},{"id":3588,"depth":865,"text":3589},{"id":3623,"depth":865,"text":3624},{"id":3641,"depth":865,"text":3642},{"id":3666,"depth":865,"text":3667},{"id":3679,"depth":865,"text":3680},"2026-05-13T00:00:00.000Z","MusicTech Lab partners with Verified Human to build infrastructure for authentic, human-made music in the age of AI. Technical review, product development, and MCP-based AI integration.",{"src":3724,"credit":3725},"/images/blog/musictechlab_partnership_verified_human_1.webp","MusicTech Lab",{"enabled":1015,"items":3727},[3728,3731,3734],{"text":3729,"icon":3730},"MusicTech Lab joins Verified Human as technical review and advisory partner.","i-lucide-shield-check",{"text":3732,"icon":3733},"The collaboration focuses on trust infrastructure for human-made music in the AI era.","i-lucide-link",{"text":3735,"icon":3394},"MCP-based tooling enables AI-native verification of music certification status.",{},{"title":3738,"description":3739},"MusicTech Lab × Verified Human Partnership","MusicTech Lab partners with Verified Human to review, build, and scale infrastructure for certifying human-made music in the age of AI.",[3430,3741,3742,1242,3743,3744,3745],"verified-human","ai-music","music-authenticity","music-industry","ai-governance","7Tfnkn-aKDkKCiT9RNQQK6JBKKN11kp534rIJW0YB84",1783967817865]