From lab
This skill should be used when adding a new service integration to `lab`, finishing a partial one, migrating a dispatch stub to the full directory layout, or wiring any of the standard surfaces — `lab-apis`, dispatch, CLI, MCP, API, health, docs — into the repo's service contract. Trigger phrases include: "add a service", "onboard Foo", "wire up dispatch for Foo", "finish the Foo integration", "migrate the Foo stub", "add Foo to lab".
npx claudepluginhub jmagar/lab --plugin labThis skill uses the workspace's default tool permissions.
This skill summarizes the current `lab` service contract. The source of truth is:
Guides Next.js Cache Components and Partial Prerendering (PPR) with cacheComponents enabled. Implements 'use cache', cacheLife(), cacheTag(), revalidateTag(), static/dynamic optimization, and cache debugging.
Migrates code, prompts, and API calls from Claude Sonnet 4.0/4.5 or Opus 4.1 to Opus 4.5, updating model strings on Anthropic, AWS, GCP, Azure platforms.
Facilitates interactive brainstorming sessions using diverse creative techniques and ideation methods. Activates when users say 'help me brainstorm' or 'help me ideate'.
This skill summarizes the current lab service contract. The source of truth is:
CLAUDE.mddocs/SERVICE_ONBOARDING.mddocs/DISPATCH.mddocs/OBSERVABILITY.mddocs/ERRORS.mddocs/SERIALIZATION.mdIf this skill disagrees with those docs, the docs win.
Bringing a service online means:
lab scaffold servicelab audit onboardinglab_admin only for read-only onboarding audit access, and only when LAB_ADMIN_ENABLED=1lab-apis owns the service logicdispatch/<service>/ is the shared semantic layerA service working on only one surface is not done.
For new onboarding work, the expected order is:
cargo test --all-features and the targeted smoke checksThis repo is verified as an all-features binary.
cargo build --all-featurescargo nextest run --manifest-path crates/lab/Cargo.toml --all-featuresBefore editing code, collect:
docs/upstream-api/The upstream spec is the contract. If it does not exist, create or refresh it first.
lab-apiscrates/lab-apis/src/<service>.rs
crates/lab-apis/src/<service>/client.rs
crates/lab-apis/src/<service>/types.rs
crates/lab-apis/src/<service>/error.rs
Rules:
mod.rsclient.rs owns request construction, response parsing, and business logiclab-apis does not read env or config filestypes.rsApiErrorServiceClient for health checkslabcrates/lab/src/dispatch/<service>.rs
crates/lab/src/dispatch/<service>/catalog.rs
crates/lab/src/dispatch/<service>/client.rs
crates/lab/src/dispatch/<service>/params.rs
crates/lab/src/dispatch/<service>/dispatch.rs
crates/lab/src/cli/<service>.rs
crates/lab/src/mcp/services/<service>.rs
crates/lab/src/api/services/<service>.rs
Registry and wiring:
crates/lab/src/cli.rscrates/lab/src/mcp/services.rs — pub mod <service>; module declarationcrates/lab/src/mcp/registry.rs — runtime tool registrationcrates/lab/src/api/services.rscrates/lab/src/api/router.rscrates/lab/src/api/state.rscrates/lab/src/tui/metadata.rsEvery onboarded service uses the directory-first dispatch layout above.
dispatch/<service>.rsResponsibilities only:
mod catalog; mod client; mod dispatch; mod params;ACTIONSclient_from_envdispatchdispatch_with_clientTypical shape:
mod catalog;
mod client;
mod dispatch;
mod params;
pub use catalog::ACTIONS;
#[allow(unused_imports)]
pub use client::{client_from_env, not_configured_error};
#[allow(unused_imports)]
pub use dispatch::{dispatch, dispatch_with_client};
The #[allow(unused_imports)] annotations are required. Not every compilation path (e.g. a narrow feature slice) exercises all re-exports, and clippy will fail the build without this annotation.
dispatch.rsMust expose both:
dispatch(action, params) for CLI and MCPdispatch_with_client(client, action, params) for APIdispatch(...) must handle:
helpschemadispatch_with_client(...) owns the service action routing.
catalog.rsACTIONS is the single source of truth.
Important:
helpschemaclient.rsOwns:
dispatch::helpers::env_non_emptynot_configured_error()Expose:
client_from_env()require_client()not_configured_error()The API handler must use not_configured_error() instead of duplicating an error string.
Choosing the right Auth variant — see references/patterns.md for the full table. Common mapping:
| Service auth style | Auth variant |
|---|---|
API key in a request header (e.g. X-Api-Key) | Auth::ApiKey { header: "X-Api-Key".into(), key } |
Bearer token in Authorization header | Auth::Bearer { token } |
| HTTP basic auth | Auth::Basic { username, password } |
| No auth (public endpoints, health probes) | Auth::None |
Using Auth::Bearer for a service that expects Auth::ApiKey (or vice versa) produces silent 401s that are easy to miss in tests. Confirm against the upstream API spec.
params.rsAll coercion from serde_json::Value to typed SDK requests lives here.
Do not inline multi-step param coercion in dispatch.rs.
Use the helpers from dispatch::helpers — do not write custom extraction when a helper already exists:
| Helper | Purpose |
|---|---|
require_str(params, "key") | Required string param — errors if absent |
optional_str(params, "key") | Optional string param — None if absent |
require_i64(params, "key") | Required integer param |
optional_u32(params, "key") | Optional unsigned 32-bit integer |
optional_u32_max(params, "key", max) | Optional u32 with upper bound |
body_from_params(params) | Serialize a sub-object as a request body |
object_without(params, &["key"]) | Clone params with named keys stripped |
CLI shims are thin:
clapdispatch::<service>::dispatch()lab-apis directlyThe MCP adapter is a forwarder:
pub use crate::dispatch::<service>::ACTIONS;
pub async fn dispatch(action: &str, params: Value) -> Result<Value, ToolError> {
crate::dispatch::<service>::dispatch(action, params).await
}
No business logic in mcp/services/<service>.rs.
The API handler is a thin adapter over handle_action(...).
It must:
AppStatehandle_action("<service>", "api", request_id, req, ACTIONS, ...)not_configured_error() when the client is absentdispatch_with_client(...)The API surface label is surface = "api".
Do not invent alternate wrappers or call require_client() in API handlers.
lab-apis uses ApiErrorApiErrorFrom<ServiceError> for ToolError lives only in crates/lab/src/dispatch/error.rs, feature-gatedimpl_tool_error_from! macro defined there — do not write the impl by hand:// In crates/lab/src/dispatch/error.rs:
impl_tool_error_from!(
"foo",
lab_apis::foo::error::FooError,
Api(api) => api.kind()
);
Stable shared kinds come from ApiError::kind().
Dispatch must emit structured logs for:
Required dispatch fields:
surfaceserviceactionelapsed_mskind on errorAPI also includes request_id.
HttpClient must emit:
request.startrequest.finishrequest.errorHealth probes must log operation = "health".
Never log params, secrets, headers, tokens, cookies, or secret env values.
Always update:
docs/coverage/<service>.mdUpdate these when applicable:
docs/README.mddocs/CONFIG.mddocs/SERVICES.mddocs/MCP.mddocs/CLI.mdTest order is mandatory: write the failing test before the implementation. This is not optional — see references/contracts.md.
Use wiremock for:
Every service entry-point should have at least these three tests, named by convention:
catalog_includes_<key>_actions — verifies the catalog compiles and contains the expected action nameshelp_lists_<primary_action> — smoke-tests the help action end to enddispatch_with_client_<describes_behavior> — one wiremock round-trip proving the happy path worksAlso cover:
When touched, cover:
For HTTP-backed services, a service is not done until all three surfaces have been smoke-tested live when possible:
mcportercurlAlso verify:
lab healthUse cargo nextest run --manifest-path crates/lab/Cargo.toml --all-features as the standard test runner.
Some services exist as stub dispatch files (~28 lines) — a flat .rs file that always returns not_implemented. When migrating to a full implementation:
.rs filedispatch/<service>/ directory with catalog.rs, client.rs, params.rs, dispatch.rsdispatch/<service>.rs entry-point with submodule declarations, re-exports, and unit testsThe stub registration in mcp/registry.rs and api/services.rs may already exist. Check before adding duplicate entries.
help / schema from ACTIONSnot_configured_error()require_client() instead of using AppStatedispatch/<service>/client.rsFrom<ServiceError> for ToolError added anywhere except dispatch/error.rs (use impl_tool_error_from! macro)pub mod <service> to api/services.rs but not to mcp/services.rs (or vice versa)From<ServiceError> for ToolError by hand instead of using the impl_tool_error_from! macroUse this order:
lab-apis filesIf docs/SERVICE_ONBOARDING.md changes, re-check this skill against:
not_configured_error() usage