{"slug": "ruby-vs-java-vs-typescript-my-experience-on-building-a-cowork-docx-plugin", "title": "Ruby vs. Java vs. TypeScript: my experience on building a Cowork DOCX plugin", "summary": "A developer built a Claude Cowork DOCX plugin in Ruby, Java, and TypeScript to compare the languages for processing zip files and XML. Java handled zip and XML support best in its runtime without issues, but TypeScript was ultimately chosen for future MCPB compatibility. The developer abandoned Ruby due to typing issues and library bugs, and switched from Node to Bun after discovering Cowork plugin does not support MCPB.", "body_md": "# Ruby vs. Java vs. TypeScript: my experience on building a Cowork DOCX plugin\n\nWe've built a Claude Cowork DOCX plugin in Ruby, Java, and TypeScript. Java is the winner for supporting zip files and XML in its runtime with no issues. However, TypeScript is chosen due to the possibility of MCPB support.\n\n- We've built a Claude Cowork DOCX plugin in Ruby, Java, and TypeScript.\n- Java is the winner for supporting zip files and XML in its runtime with no issues. However, TypeScript is chosen due to the possibility of future MCPB support without embedding the Node runtime.\n- Codex's plugin mechanism is lagging behind Claude's. It doesn't support an equivalent of\n`CLAUDE_PLUGIN_ROOT`\n\nand seems impossible to execute a binary inside the plugin. - Bun has been chosen for building a single-executable binary. One gap is that I cannot figure out how to make its source maps work with PostHog.\n\nRecently, we've implemented the same Cowork DOCX plugin 3 times. The first prototype was in Ruby. We reimplemented it in Java in order to make it a desktop app. Then, We reimplemented it in Typescripts (Node) in order to make it compatible with MCPB. Eventually, We've switched to Bun because Cowork plugin apparently doesn't support MCPB.\n\nSince now I have direct experience in implementing the same app in 3 different languages within a span of a month, I've decided to write about it.\n\nFor context, a DOCX file is actually a zip file with a bunch of XMLs and other files in it. Therefore, our code must process zip files and XMLs.\n\n[https://github.com/LegalRabbit-AI/legalrabbit-docx-claude-plugin](https://github.com/LegalRabbit-AI/legalrabbit-docx-claude-plugin?ref=tanin.nanakorn.com)\n\nIt works well with legal documents, and you can use it together with the\n\n[Claude for Legal](https://github.com/anthropics/claude-for-legal?ref=tanin.nanakorn.com)plugin.\n\nThe plugin is still in its nascent stage. I'd love for you to drop us an email at tanin@legalrabbit.ai, so we can help you and notify you when there's an update.\n\n## Ruby\n\nAt first, I built a prototype in Ruby because my colleague knew Ruby well, and we wanted it to a server-side application. I wrote a lot of Ruby back in 2010s. I felt Ruby was a beautiful language. Unfortunately, I don't feel that anymore.\n\nThe biggest issue is no typing.\n\nI worked at Stripe for 4.5 years, so I looked into integrating Sorbet; it was a bit too involved, so I didn't do it. I also looked at RBS but couldn't understand how to integrate it. Admittedly, I didn't spend too much time on this.\n\nHere were some common errors that I encountered:\n\n- Forgot\n`.each`\n\nin`node.children.each do |x|`\n\n. The error showed up as \"unexpected nil\" at a random place. - Forgot\n`.children`\n\nin`node.children[i]`\n\n. The error showed up as \"unexpected nil\" at a random place. - Using\n`assert_raises`\n\nin the test obscured a compilation error. The`assert_raises`\n\nwould just complain the error doesn't match the expected error. I had to comment out`assert_raises`\n\nto see the error stacktrace. - I used\n[ruby_llm-mcp](https://github.com/patvice/ruby_llm-mcp?ref=tanin.nanakorn.com)where the doc mentioned`tool.input_schema`\n\nbut the latest version has renamed it to`tool.params_schema`\n\n([issue](https://github.com/patvice/ruby_llm-mcp/issues/139?ref=tanin.nanakorn.com)). It took me a while to figure out since there was no typing hint to help me with it.\n\nIn terms of processing a zip file and XMLs, I used:\n\n[rubyzip](https://github.com/rubyzip/rubyzip?ref=tanin.nanakorn.com): it has[an obscure bug](https://github.com/rubyzip/rubyzip/issues/15?ref=tanin.nanakorn.com)where it produced a corrupted DOCX file. I've encountered this bug with a DOCX file from our customers. I can't share the confidential file to the rubyzip author. I tried to reproduce the file but failed to do so. Therefore, the bug cannot be solved.[nokogiri](https://github.com/sparklemotion/nokogiri?ref=tanin.nanakorn.com): it also has[a non-blocking bug](https://github.com/sparklemotion/nokogiri/discussions/3607?ref=tanin.nanakorn.com)where it doesn't format XML correctly. The bug is actually from the native library`libxml2`\n\n. Therefore, it's not exactly a bug for nokogiri to solve.\n\n## Java\n\nAfter some time, we've decided that we want a Claude Cowork plugin instead. A Claude plugin supports executing a binary locally, so I've chosen Java for it.\n\nI have my fair share of building a Java desktop application and know `jpackage`\n\nand alike very well (See: [my Java electron framework](https://github.com/tanin47/java-electron?ref=tanin.nanakorn.com)).\n\nWhat surprised me was that Java supported processing zip files and XML from their standard runtime. I didn't encounter any issue with the zip and XML library at all. It works as expected.\n\nThe final single-executable binary is about 88MB. It is 88MB because a JDK is embedded inside.\n\nI used AI to port the code. It took only 3 days to port thousands of lines of code.\n\n## TypeScript\n\nLater, I've discovered that Claude Desktop supports [MCPB](https://claude.com/docs/connectors/building/mcpb?ref=tanin.nanakorn.com). The MCPB provides a Node runtime. Therefore, our application would only contain our code. This means the size of the application would be ~1MB.\n\nWith TypeScript, I have to use the following libraries for processing zip files and XML:\n\n[fflate](https://github.com/101arrowz/fflate?ref=tanin.nanakorn.com)for processing zip files. It works as expected.[xmldom](https://github.com/xmldom/xmldom?ref=tanin.nanakorn.com)for processing XML. Because I need DOM API. It works as expected except that it doesn't support pretty-print XML. Its rationale is that pretty-print is out of scope because XML is used for transporting/serialization. Plus, there's no standard for pretty-printing XML. See[this issue](https://github.com/xmldom/xmldom/issues/44?ref=tanin.nanakorn.com).\n\nI used AI to port the code from Java to TypeScript. It only took 1.5 days to do.\n\nMCPB is great. However, to operate our DOCX MCP, we need an accompanying skill. Claude Desktop supports this through Claude plugin where both skills and MCPs can be packaged together. However, Claude plugin doesn't support MCPB :S\n\nClaude plugin only supports a single-executable binary. Node doesn't have this feature. Fortunately, Bun has, and it works as expected.\n\nThe only gap with Bun is that I cannot figure out how to upload the source map to PostHog because PostHog requires both the minimized JS file and the .js.map file.... but Bun only produces the binary and the .js.map file.\n\nThe final file size is ~70MB on Mac and ~120MB on Windows.\n\nIn the end, I've decided to stick with TypeScript because of the possibility of MCPB being supported in Claude/Codex plugin.\n\n### Thoughts on Codex\n\nI was looking at making a Codex plugin but Codex is behind Claude Desktop in terms of the plugin mechanism.\n\nClaude supports the environment variable: `CLAUDE_PLUGIN_ROOT`\n\n, and that enables me to execute the single-executable binary as the stdio MCP server.\n\nCodex doesn't have it. Their plugin structure has a folder for binaries but their doc doesn't mention how to execute the binary in that folder. I tried a couple possible paths but couldn't make it work.\n\n### Conclusion\n\nJava is the winner for me. However, as I mentioned earlier, I've decided to stick with TypeScript due to the possibility of future MCPB support without embedding the Node runtime.\n\nIgnoring the possibility of the embedded Node runtime, Java's strict typing makes it easier to code. The Java's built-in libraries for zip and XMLs work as expected.\n\nThe maturity of the Java runtimes on both Windows and Mac gives me more confident compared to Bun, which is quite new. To be fair, so far I haven't encountered any issue with Bun apart from being unable to upload source maps to PostHog. Our Bun-built Claude plugin seems to work well on both Mac and Windows.", "url": "https://wpnews.pro/news/ruby-vs-java-vs-typescript-my-experience-on-building-a-cowork-docx-plugin", "canonical_source": "https://tanin.nanakorn.com/ruby-java-typescrip-claude-docx-plugin/", "published_at": "2026-05-25 18:49:32+00:00", "updated_at": "2026-05-25 19:08:09.131143+00:00", "lang": "en", "topics": ["ai-tools", "ai-products", "ai-infrastructure", "generative-ai", "large-language-models"], "entities": ["Claude", "Cowork", "DOCX", "Ruby", "Java", "TypeScript", "Bun", "PostHog"], "alternates": {"html": "https://wpnews.pro/news/ruby-vs-java-vs-typescript-my-experience-on-building-a-cowork-docx-plugin", "markdown": "https://wpnews.pro/news/ruby-vs-java-vs-typescript-my-experience-on-building-a-cowork-docx-plugin.md", "text": "https://wpnews.pro/news/ruby-vs-java-vs-typescript-my-experience-on-building-a-cowork-docx-plugin.txt", "jsonld": "https://wpnews.pro/news/ruby-vs-java-vs-typescript-my-experience-on-building-a-cowork-docx-plugin.jsonld"}}