Skip to content

Extract contracts-core crate for reusable MCP action and error contracts #68

Description

@jmagar

Goal

Extract the generic parts of rtemplate-contracts into a reusable public contracts-core style crate for Rust MCP servers, while keeping rmcp-template's concrete service/action/config material in template-local crates.

This is the lowest-level shared contract issue. It should land before higher-level gateway/server/runtime extraction (#69), facade-builder composition (#70), crates.io publishing infrastructure (#71), release-please automation (#72), Code Mode (#74), auth (#75), traces (#76), and OpenAPI generation (#77) start depending on a shared vocabulary.

Current repo evidence

  • crates/rtemplate-contracts/src/lib.rs currently exports actions, config, env_registry, errors, and token_limit from one crate.
  • actions.rs mixes generic contract types (ActionTransport, ActionCost, ParamSpec, CliSpec, ActionSpec) with template-specific constants and behavior (READ_SCOPE = "example:read", ExampleAction, ACTION_SPECS, example remediation text, destructive confirmation policy).
  • config.rs is template/runtime specific: .example, RTEMPLATE_*, McpConfig, AuthConfig, home-directory discovery, dotenv loading, and TOML parsing do not belong in consumers that only need action/error contracts.
  • config.rs::load_dotenv() currently calls std::process::exit(1) on a symlinked .env; that may be fine for a binary startup path, but not for a public library dependency.
  • errors.rs contains reusable structured tool error concepts (ServiceErrorKind, ToolError, REST/MCP payload conversion), but currently imports actions::ActionValidationError and action_names(), coupling generic errors back to template actions.
  • token_limit.rs contains reusable response budget constants/helpers, but its MCP pagination behavior is actually enforced in the MCP adapter; keep the core crate narrowly about budgets/helpers unless paging types are explicitly extracted.
  • release/components.toml currently has one template component, so public crate release metadata needs Add crates.io publishing infrastructure for public reusable crates #71 before publishing.

Architecture decision

Create a new shared platform crate, tentatively rmcp-contracts-core, and leave rtemplate-contracts as the template/service-specific crate that depends on it.

Dependency direction:

rtemplate-api / rtemplate-cli / rtemplate-mcp / rtemplate-service / rtemplate-runtime
  -> rtemplate-contracts                 # template action table, config, env registry, scopes
    -> rmcp-contracts-core               # generic action metadata, error envelope, response budget
      -> serde / serde_json / thiserror  # minimal stable deps only

Do not move template config, auth config, dotenv loading, home-directory discovery, concrete scopes, ExampleAction, ACTION_SPECS, REST help text, plugin env mappings, or generated-service examples into the core crate.

Proposed core surface

  • action module:
    • ActionTransport, ActionCost, ParamSpec, CliFlagSpec, CliSpec, ActionSpec or owned/string-backed equivalents if the public API should not require 'static slices.
    • Generic action-name utilities that operate on caller-provided specs, not global ACTION_SPECS.
    • Generic validation primitives such as missing action, unknown action, missing field, wrong type, and transport-unavailable errors with caller-provided available action names.
  • error module:
    • Stable ToolError / ServiceErrorKind envelope with schema versioning, retryability, remediation, optional field / bad_value / expected_pattern / reason_kind.
    • Conversion helpers that accept tool/action names as parameters and do not import template action globals.
  • budget module:
    • MAX_RESPONSE_BYTES style default budget and plain text truncation helper, with the actual MCP paging cache remaining in the MCP/server crate unless separately extracted.
  • Optional env module only if it stays neutral:
    • Env-key descriptors and metadata, not RTEMPLATE_* loading or filesystem side effects.

Implementation sequence

  1. Add crates/rmcp-contracts-core to the workspace with minimal dependencies and publish = false until Add crates.io publishing infrastructure for public reusable crates #71 is ready.
  2. Move generic action metadata types and tests from rtemplate-contracts/src/actions.rs into the new crate.
  3. Refactor rtemplate-contracts::actions to define READ_SCOPE, WRITE_SCOPE, DENY_SCOPE, ExampleAction, ACTION_SPECS, scope satisfaction, destructive confirmation, and template-specific validation/remediation on top of core types.
  4. Move ToolError / ServiceErrorKind into core, then make from_action_validation a template-local adapter or a generic function that accepts an ActionCatalog/available-action list.
  5. Move budget constants/helpers only after confirming no MCP paging cache/state leaks into the core dependency graph.
  6. Leave Config, McpConfig, AuthConfig, ExampleConfig, default_data_dir, load_dotenv, and env_registry in rtemplate-contracts or split them into a separate template config crate later. Remove public-library process::exit from any shared crate path; return a typed error and let binaries decide whether to exit.
  7. Update direct imports in rtemplate-mcp, rtemplate-cli, rtemplate-api, rtemplate-service, and tests only after rtemplate-contracts re-exports or wraps the new core types.
  8. Add a consumer fixture crate/test that depends on rmcp-contracts-core without rtemplate-contracts, dirs, dotenvy, toml, auth, runtime, MCP transport, or template config.
  9. Hand release metadata to Add crates.io publishing infrastructure for public reusable crates #71: independent component entry, crate metadata checks, package dry-run, and versioning policy before publication.

Likely files/modules touched

  • Create: crates/rmcp-contracts-core/Cargo.toml
  • Create: crates/rmcp-contracts-core/src/lib.rs
  • Create: crates/rmcp-contracts-core/src/action.rs
  • Create: crates/rmcp-contracts-core/src/error.rs
  • Create: crates/rmcp-contracts-core/src/budget.rs
  • Modify: Cargo.toml workspace members
  • Modify: crates/rtemplate-contracts/Cargo.toml
  • Modify: crates/rtemplate-contracts/src/lib.rs
  • Modify: crates/rtemplate-contracts/src/actions.rs
  • Modify: crates/rtemplate-contracts/src/errors.rs
  • Modify: crates/rtemplate-contracts/src/token_limit.rs if budget helpers move or re-export
  • Modify as imports require: crates/rtemplate-mcp/src/schemas.rs, crates/rtemplate-mcp/src/rmcp_server.rs, crates/rtemplate-mcp/src/tools.rs, crates/rtemplate-cli/src/lib.rs, crates/rtemplate-api/src/api.rs, crates/rtemplate-service/src/lib.rs
  • Add tests: core unit tests plus an integration/fixture test proving standalone consumption

Non-goals

Risks / clarifications

  • ActionSpec currently uses &'static str and slices, which is convenient for generated constants but awkward for runtime/imported catalogs. Decide before extraction whether core exposes borrowed static specs, owned specs, or both.
  • Error helpers currently depend on action_names() for remediation. Core must not know template action globals.
  • ServiceErrorKind::retryable() currently marks validation retryable; validate whether that is intentional before freezing it publicly.
  • Moving token_limit without response paging may imply stronger guarantees than the core crate can enforce. Keep wording/API explicit.
  • Extracting config at the same time would drag in filesystem/env side effects and should be deferred.

Validation approach

  • cargo fmt --all --check
  • cargo check --workspace --all-features
  • cargo test -p rmcp-contracts-core
  • cargo test -p rtemplate-contracts
  • Focused integration tests that call ExampleAction::from_mcp_args, ExampleAction::from_rest, required_scope_for_action, and ToolError::to_mcp_payload through the existing template crate.
  • Consumer fixture check proving rmcp-contracts-core compiles independently with minimal dependencies.
  • cargo tree -p rmcp-contracts-core to confirm it does not pull dirs, dotenvy, toml, auth, gateway, runtime, CLI, or MCP transport crates.
  • cargo xtask check-version-sync after Add crates.io publishing infrastructure for public reusable crates #71 adds release metadata or if tracked version files change.

Relationship to related issues

Acceptance criteria

  • A new shared core crate exists with generic action metadata, validation primitives, structured error envelope, and response-budget helpers.
  • rtemplate-contracts keeps template-specific action tables, scopes, config, env loading, and service naming.
  • Generic consumers can use the core crate without dirs, dotenvy, toml, auth, gateway, runtime, CLI, or MCP transport dependencies.
  • No public shared library path calls std::process::exit.
  • Existing template CLI/MCP/REST behavior remains unchanged through rtemplate-contracts wrappers or re-exports.
  • Tests prove both standalone core consumption and current template behavior.
  • Release/publication handoff to Add crates.io publishing infrastructure for public reusable crates #71 is documented before any crates.io publish attempt.

Sources

  • /home/jmagar/workspace/rmcp-template/Cargo.toml:8 — current workspace crate list.
  • /home/jmagar/workspace/rmcp-template/crates/rtemplate-contracts/Cargo.toml:1 — current contracts crate metadata/dependencies and publish = false.
  • /home/jmagar/workspace/rmcp-template/crates/rtemplate-contracts/src/lib.rs:1 — current exported modules.
  • /home/jmagar/workspace/rmcp-template/crates/rtemplate-contracts/src/actions.rs:93 — template-specific scopes.
  • /home/jmagar/workspace/rmcp-template/crates/rtemplate-contracts/src/actions.rs:106 — reusable action metadata types begin.
  • /home/jmagar/workspace/rmcp-template/crates/rtemplate-contracts/src/actions.rs:213 — template-specific concrete action table.
  • /home/jmagar/workspace/rmcp-template/crates/rtemplate-contracts/src/config.rs:12 — template-specific service home/defaults.
  • /home/jmagar/workspace/rmcp-template/crates/rtemplate-contracts/src/config.rs:232 — dotenv loader with binary-style exit behavior.
  • /home/jmagar/workspace/rmcp-template/crates/rtemplate-contracts/src/errors.rs:49 — stable structured tool error envelope.
  • /home/jmagar/workspace/rmcp-template/crates/rtemplate-contracts/src/token_limit.rs:41 — response-size budget helper.
  • /home/jmagar/workspace/rmcp-template/release/components.toml:3 — current single release component.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions