Publishing to Crates.io
Publishing your pforge crates to crates.io makes them available to the Rust ecosystem and allows users to install your MCP servers with a simple cargo install
command. This chapter covers the complete publishing workflow based on pforge’s real-world experience publishing five interconnected crates.
Why Publish to Crates.io?
Publishing to crates.io provides several benefits:
- Easy Installation: Users can install with
cargo install pforge-cli
instead of building from source - Dependency Management: Other crates can depend on your published crates with automatic version resolution
- Discoverability: Your crates appear in searches on crates.io and docs.rs
- Documentation: Automatic documentation generation and hosting on docs.rs
- Versioning: Semantic versioning guarantees compatibility and upgrade paths
- Trust: Published crates undergo community review and validation
The pforge Publishing Story
pforge consists of five published crates that work together:
Crate | Purpose | Dependencies |
---|---|---|
pforge-config | Configuration parsing and validation | None (foundation) |
pforge-macro | Procedural macros | None (independent) |
pforge-runtime | Core runtime and handler registry | config |
pforge-codegen | Code generation from YAML to Rust | config |
pforge-cli | Command-line interface and templates | config, runtime, codegen |
This dependency chain means publishing order matters critically. You must publish foundation crates before crates that depend on them.
Publishing Challenges We Encountered
When publishing pforge, we hit several real-world issues:
1. Rate Limiting
crates.io rate-limits new crate publications to prevent spam. Publishing five crates in rapid succession triggered:
error: failed to publish to crates.io
Caused by:
the remote server responded with an error: too many crates published too quickly
Solution: Wait 10-15 minutes between publications, or publish over multiple days.
2. Missing Metadata
First publication attempt failed with:
error: missing required metadata fields:
- description
- keywords
- categories
- license
Solution: Add comprehensive metadata to Cargo.toml
workspace section (covered in Chapter 17-01).
3. Template Files Not Included
The CLI crate initially failed to include template files needed for pforge new
:
error: templates not found after installation
Solution: Add include
field to Cargo.toml
:
include = [
"src/**/*",
"templates/**/*",
"Cargo.toml",
]
4. Version Specification Conflicts
Publishing pforge-runtime
failed because it depended on pforge-config = { path = "../pforge-config" }
without a version:
error: all dependencies must have version numbers for published crates
Solution: Use workspace dependencies with explicit versions (covered in Chapter 17-02).
5. Documentation Links Broken
docs.rs generation failed because README links used repository-relative paths:
warning: documentation link failed to resolve
Solution: Use absolute URLs in documentation or test with cargo doc --no-deps
.
The Publishing Workflow
Based on these experiences, here’s the proven workflow:
1. Prepare All Crates (Chapter 17-01)
- Add required metadata
- Configure workspace inheritance
- Set up
include
fields - Write comprehensive README files
2. Manage Versions (Chapter 17-02)
- Follow semantic versioning
- Update all internal dependencies
- Create version tags
- Update CHANGELOG
3. Write Documentation (Chapter 17-03)
- Add crate-level docs (
lib.rs
) - Document all public APIs
- Create examples
- Test documentation builds
4. Publish in Order (Chapter 17-04)
- Test with
cargo publish --dry-run
- Publish foundation crates first
- Wait for crates.io processing
- Verify each publication
- Continue up dependency chain
5. Post-Publication
- Test installation from crates.io
- Verify docs.rs generation
- Announce the release
- Monitor for issues
The Dependency Chain
Understanding the dependency chain is crucial for successful publication:
pforge-config (no deps) ←─────┐
│
pforge-macro (no deps) │
│
pforge-runtime (depends) ─────┘
↑
│
pforge-codegen (depends)
↑
│
pforge-cli (depends on runtime + codegen)
Critical Rule: Never publish a crate before its dependencies are available on crates.io.
Publishing Order for pforge
The exact order we used:
- Day 1:
pforge-config
andpforge-macro
(independent, can be parallel) - Day 1 (after 15 min):
pforge-runtime
(depends on config) - Day 2:
pforge-codegen
(depends on config) - Day 2 (after 15 min):
pforge-cli
(depends on all three)
We spread publications across two days to avoid rate limiting and allow time for verification between steps.
Verification Steps
After each publication:
1. Check crates.io
Visit https://crates.io/crates/pforge-config
and verify:
- Version number is correct
- Description and keywords appear
- License is displayed
- Repository link works
2. Check docs.rs
Visit https://docs.rs/pforge-config
and verify:
- Documentation builds successfully
- All modules are documented
- Examples render correctly
- Links work
3. Test Installation
On a clean machine or Docker container:
cargo install pforge-cli
pforge --version
pforge new test-project
This ensures the published crate actually works for end users.
Rollback and Fixes
Important: crates.io is append-only. You cannot:
- Delete published versions
- Modify published crate contents
- Unpublish a version (only yank it)
If you publish with a bug:
Option 1: Yank the Version
cargo yank --version 0.1.0
This prevents new projects from using the version but doesn’t break existing users.
Option 2: Publish a Patch
# Fix the bug
# Bump version to 0.1.1
cargo publish
The new version becomes the default, but the old version remains accessible.
Pre-Publication Checklist
Before publishing ANY crate, verify:
-
All tests pass:
cargo test --all
-
Quality gates pass:
make quality-gate
-
Documentation builds:
cargo doc --no-deps
-
Dry run succeeds:
cargo publish --dry-run
- Dependencies are published (for non-foundation crates)
- Version numbers are correct
- CHANGELOG is updated
- Git tags are created
- README is comprehensive
- Examples work
Publishing Tools
Helpful tools for the publishing process:
# Check what will be included in the package
cargo package --list
# Create a .crate file without publishing
cargo package
# Inspect the .crate file
tar -tzf target/package/pforge-config-0.1.0.crate
# Dry run (doesn't actually publish)
cargo publish --dry-run
# Publish with dirty git tree (use cautiously)
cargo publish --allow-dirty
Common Pitfalls
1. Publishing Without Testing
Problem: Rushing to publish without thorough testing.
Solution: Always run the pre-publication checklist. We found bugs in pforge-cli
template handling only after attempting publication.
2. Incorrect Version Dependencies
Problem: Internal dependencies using path
without version
.
Solution: Use workspace dependencies with explicit versions:
pforge-config = { workspace = true }
3. Missing Files
Problem: Source files or resources not included in package.
Solution: Use include
field or check with cargo package --list
.
4. Platform-Specific Code
Problem: Code that only works on Linux but no platform guards.
Solution: Add #[cfg(...)]
attributes and test on all platforms before publishing.
5. Large Crate Size
Problem: Accidentally including test data or build artifacts.
Solution: Use .cargo-ignore
(similar to .gitignore
but for cargo packages).
Multi-Crate Workspace Tips
For workspaces like pforge with multiple publishable crates:
1. Shared Metadata
Define common metadata in [workspace.package]
:
[workspace.package]
version = "0.1.0"
edition = "2021"
license = "MIT"
authors = ["Pragmatic AI Labs"]
repository = "https://github.com/paiml/pforge"
Each crate inherits with:
[package]
name = "pforge-config"
version.workspace = true
edition.workspace = true
license.workspace = true
2. Shared Dependencies
Define versions once in [workspace.dependencies]
:
[workspace.dependencies]
serde = { version = "1.0", features = ["derive"] }
pforge-config = { path = "crates/pforge-config", version = "0.1.0" }
Crates use with:
[dependencies]
serde = { workspace = true }
pforge-config = { workspace = true }
3. Version Bumping Script
Create a script to bump all versions simultaneously:
#!/bin/bash
NEW_VERSION=$1
sed -i "s/^version = .*/version = \"$NEW_VERSION\"/" Cargo.toml
for crate in crates/*/Cargo.toml; do
# Versions are inherited, so this updates workspace version
echo "Updated $crate"
done
cargo update -w
Documentation Best Practices
Good documentation drives adoption:
1. Crate-Level Documentation
Add to lib.rs
:
//! # pforge-config
//!
//! Configuration parsing and validation for pforge MCP servers.
//!
//! This crate provides the core configuration types and parsing logic
//! used by the pforge framework.
//!
//! ## Example
//!
//! ```rust
//! use pforge_config::ForgeConfig;
//!
//! let yaml = r#"
//! forge:
//! name: my-server
//! version: 0.1.0
//! "#;
//!
//! let config = ForgeConfig::from_yaml(yaml)?;
//! assert_eq!(config.name, "my-server");
//! ```
2. Module Documentation
Document each public module:
/// Tool definition types and validation.
///
/// This module contains the [`ToolDef`] enum and related types
/// for defining MCP tools declaratively.
pub mod tools;
3. Examples Directory
Add runnable examples in examples/
:
crates/pforge-config/
├── examples/
│ ├── basic_config.rs
│ ├── validation.rs
│ └── advanced_features.rs
Users can run them with:
cargo run --example basic_config
Chapter Summary
Publishing to crates.io requires careful preparation, strict ordering, and attention to detail. The key lessons from pforge’s publishing experience:
- Metadata is mandatory: Description, keywords, categories, license
- Order matters: Publish dependencies before dependents
- Rate limits exist: Space out publications by 10-15 minutes
- Include everything: Templates, resources, documentation
- Test thoroughly: Dry runs, package inspection, clean installs
- Document well: Users rely on docs.rs
- Version carefully: Semantic versioning is a contract
- No rollbacks: You can’t unpublish, only yank and patch
The next four chapters dive deep into each phase of the publishing process.
Next: Preparing Your Crate