pub struct ServerConfig {
pub server: ServerSection,
pub metadata: MetadataSection,
pub database: DatabaseSection,
pub backend: Option<BackendSection>,
pub code_mode: Option<CodeModeSection>,
pub tools: Vec<ToolDecl>,
pub config_slots: Vec<ConfigSlotDecl>,
pub prompts: Vec<PromptDecl>,
pub resources: Vec<ResourceDecl>,
pub shared_policy_store: Option<SharedPolicyStoreSection>,
}Expand description
Top-level pmcp-server-toolkit configuration parsed from a config.toml.
One struct parses the entire file in one shot (per D-13). All sub-sections
carry #[serde(deny_unknown_fields)] — a typo anywhere in the file is a
hard parse error.
§Entry points
Use ServerConfig::from_toml_strict_validated for production callers.
ServerConfig::from_toml is the no-validation variant for programmatic
merges; ServerConfig::validate runs the semantic checks separately.
§Examples
use pmcp_server_toolkit::config::ServerConfig;
let toml = r#"
[server]
name = "demo"
version = "0.1.0"
"#;
let cfg = ServerConfig::from_toml_strict_validated(toml)
.expect("valid minimum config");
assert_eq!(cfg.server.name, "demo");
assert_eq!(cfg.server.version, "0.1.0");Fields§
§server: ServerSection[server] — identity and version metadata.
metadata: MetadataSection[metadata] — admin-facing display defaults.
database: DatabaseSection[database] — backend connection + tables.
backend: Option<BackendSection>[backend] (optional, http feature) — OpenAPI/REST HTTP backend
declaration (base_url + [backend.auth] + [backend.http]).
Additive per the REF-01 superset invariant (D-06): a pure-SQL config
omits [backend] and this field parses to None. The whole section is
gated behind the http feature — a no-http build has no OpenAPI backend,
so exposing an unusable stub type would be misleading. See
BackendSection.
code_mode: Option<CodeModeSection>[code_mode] (optional) — code-mode policy and limits.
tools: Vec<ToolDecl>[[tools]] — declarative tool surface (TOML-defined handlers).
config_slots: Vec<ConfigSlotDecl>[[config_slots]] — declared config slots the TARGET environment must
fill (PKG-03). Additive per the REF-01 superset invariant: a config
omitting the block parses to an empty vec.
Deliberately NOT gated on the http feature — a SQL or workbook Shape A
server declares slots too, and gating it would make the field vanish in
the toolkit’s own default build.
prompts: Vec<PromptDecl>[[prompts]] — declarative prompt surface.
resources: Vec<ResourceDecl>[[resources]] — declarative resource surface.
[shared_policy_store] (optional) — AVP/Cedar shared-policy-store
declaration emitted by the reference SQL server (is_reference = true),
which provisions the policy store all sibling SQL servers attach to.
Additive per the REF-01 superset invariant (Plan 85-01); parsed
verbatim — the toolkit does not provision SSM at parse time.
Implementations§
Source§impl ServerConfig
impl ServerConfig
Sourcepub fn from_toml(toml_str: &str) -> Result<Self>
pub fn from_toml(toml_str: &str) -> Result<Self>
Parse ServerConfig from a TOML config string.
Performs strict parsing (#[serde(deny_unknown_fields)] on every
section, per D-13). Does not run semantic validation — callers
wanting required-field guarantees should use
Self::from_toml_strict_validated instead.
§Errors
Returns ToolkitError::Parse on syntax error or unknown field. A
mis-spelled key (e.g. auto_aprove_levels for auto_approve_levels)
produces a parse error here, not a silent default.
§Example
use pmcp_server_toolkit::config::ServerConfig;
let toml = r#"
[server]
id = "demo"
name = "Demo"
version = "0.1.0"
"#;
let cfg = ServerConfig::from_toml(toml).expect("parse");
assert_eq!(cfg.server.name, "Demo");Sourcepub fn from_toml_strict_validated(toml_str: &str) -> Result<Self>
pub fn from_toml_strict_validated(toml_str: &str) -> Result<Self>
Parse + validate. Per Phase 83 review R8 — guards against the
missing-required-value trap that the Default impls on sub-sections
would otherwise hide behind silent empty strings (e.g. a typo’d
[serever] header makes server.name default to "").
§Errors
Returns ToolkitError::Parse on TOML syntax / unknown-field errors,
or ToolkitError::Validation (wrapping
ConfigValidationError) on missing required values
(empty server.name, empty server.version, empty tool name, empty
table name).
§Example
use pmcp_server_toolkit::config::ServerConfig;
let toml = r#"
[server]
name = "demo"
version = "0.1.0"
"#;
let cfg = ServerConfig::from_toml_strict_validated(toml).expect("valid");Sourcepub fn validate(&self) -> Result<(), ConfigValidationError>
pub fn validate(&self) -> Result<(), ConfigValidationError>
Validate required-field semantics that #[serde(default)] would
otherwise mask. Per Phase 83 review R8.
Rules checked, in order:
server.nameis non-empty (trimmed).server.versionis non-empty (trimmed).- Every
[[tools]]entry has a non-emptyname. - No
[[tools]]entry mixes tool kinds (sql/path+method/script) — D-01 / T-90-02-04. - Every
[[database.tables]]entry has a non-emptyname. - Every
[[config_slots]]entry has a non-emptykeyANDname(PKG-03). The entry’skindneeds no rule here — it is the closedConfigSlotKindenum, so serde rejects an unknown discriminator at parse time, beforevalidate()is called. - When a
[backend]block is present (httpfeature), itsbase_urlis non-empty (trimmed) — GAP 3 / WR-02. Absent on no-http builds. - Every
[[tools.parameters]]declaration is well-formed (Phase 128 D2 / SC-2): no emptypattern, nominimum/maximumoutside the exactly-representablef64integer range, and the tool’s synthesizedinputSchemaCOMPILES as a Draft 2020-12 schema. The compile check requires theinput-validationfeature; on a build without it the check is skipped and atracing::warn!says so once, because an enforcement that is off must never read as on.
§Errors
Returns a ConfigValidationError variant identifying the
first rule violated. Iteration order matches struct field order.
Sourcepub fn lint(&self) -> Vec<ConfigWarning>
pub fn lint(&self) -> Vec<ConfigWarning>
Non-fatal configuration findings (Phase 128, D-07).
Returns, in [[tools]] then [[tools.parameters]] DECLARATION order:
- one
uncapped-stringfinding per BODY-position string parameter with nomax_length— the residual D-05 accepts, surfaced rather than refused; - one
declared-max-length-above-placeholder-capfinding per path- or query-position parameter whose declaredmax_lengthEXCEEDSpmcp::server::schema_validation::PLACEHOLDER_MAX_LENGTH, because such a parameter publishes a limit ininputSchemathat the always-on placeholder floor will not honour — so a refusal would name a rule the client was never told about;
and then one finding per ACTIVE [server.validation] opt-out.
Never returns an error and never refuses anything. A config with zero tools
and no active opt-out returns an empty Vec. Consumed by
cargo pmcp validate config and by the once-at-startup log.
Sourcepub fn lint_against_spec(&self, spec: &OpenApiSchema) -> Vec<ConfigWarning>
pub fn lint_against_spec(&self, spec: &OpenApiSchema) -> Vec<ConfigWarning>
The findings that need the operator’s OpenAPI document to be computable (Phase 128, D4(b) / T-128-36a).
Separate from Self::lint rather than folded into it, because lint
takes only &self and a config is meaningful with no spec at all — a
spec-less deployment is supported and must produce no findings from its own
absence.
Returns, in [[tools]] declaration order, one
CONFIGURED_TEMPLATE_NOT_IN_SPEC finding per single-call tool whose
(method, path) matches no operation the spec declares. Such a tool reaches
its endpoint and keeps the unconditional character floor and the always-on
length cap, but the spec’s declared pattern/maxLength narrowing for its
placeholders is silently not applied — the /users/{alias} versus
/users/{id} drift. That is an author error an operator can fix before
deploy, which is the whole reason this runs at config time.
An author-written query string on the configured path is stripped before
the lookup, because an OpenAPI path template never carries one — so
/content/{version}/CUI?string=x is matched as /content/{version}/CUI
rather than reported as drift.
§The bound on this guard, stated
It covers a template written in the CONFIG. A template a Code Mode script
COMPOSES at runtime is not visible here and cannot be, which is why the
runtime miss is additionally reported once per (method, template) pair by
crate::code_mode’s log_spec_lookup_miss. Neither signal is a refusal:
this returns findings, and never an error.
Tools with no path/method pair — SQL tools, script tools — are skipped:
they address no single spec operation.
Trait Implementations§
Source§impl Clone for ServerConfig
impl Clone for ServerConfig
Source§impl Debug for ServerConfig
impl Debug for ServerConfig
Source§impl Default for ServerConfig
impl Default for ServerConfig
Source§impl<'de> Deserialize<'de> for ServerConfig
impl<'de> Deserialize<'de> for ServerConfig
Source§fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where
__D: Deserializer<'de>,
fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where
__D: Deserializer<'de>,
Source§impl From<&ServerConfig> for StaticPromptHandler
impl From<&ServerConfig> for StaticPromptHandler
Source§fn from(cfg: &ServerConfig) -> Self
fn from(cfg: &ServerConfig) -> Self
Build a single StaticPromptHandler from a crate::config::ServerConfig.
Returns a handler for the FIRST [[prompts]] entry, or — if none are
declared — a no-op handler named "<no-prompts>" with an empty body.
Multi-prompt servers should use prompt_handlers_from_config instead.
§Example
use pmcp_server_toolkit::{ServerConfig, StaticPromptHandler};
let cfg = ServerConfig::default();
let _handler = StaticPromptHandler::from(&cfg);Source§impl From<&ServerConfig> for StaticResourceHandler
impl From<&ServerConfig> for StaticResourceHandler
Source§fn from(cfg: &ServerConfig) -> Self
fn from(cfg: &ServerConfig) -> Self
Build a StaticResourceHandler from a parsed crate::config::ServerConfig.
Each [[resources]] entry in config becomes one LoadedResource.
Resources with no content field default to an empty body — the
strict-parse path’s crate::config::ServerConfig::validate does not
flag empty resource bodies (operators may use the placeholder form
"loaded from path.md" as a stable URI handle), so this construction
follows suit. Resources WITH content_file semantics are out of scope
for the lifted shape (Lambda runtime constraint, mirroring
LoadedResource::from_config).
Insertion order matches the order of [[resources]] declarations,
satisfying Pattern D (deterministic list() output).
§Example
use pmcp_server_toolkit::{ServerConfig, StaticResourceHandler};
let cfg = ServerConfig::default();
let handler = StaticResourceHandler::from(&cfg);
assert_eq!(handler.len(), 0); // default config has no [[resources]]Source§impl PartialEq for ServerConfig
impl PartialEq for ServerConfig
Source§impl Serialize for ServerConfig
impl Serialize for ServerConfig
impl StructuralPartialEq for ServerConfig
Auto Trait Implementations§
impl Freeze for ServerConfig
impl RefUnwindSafe for ServerConfig
impl Send for ServerConfig
impl Sync for ServerConfig
impl Unpin for ServerConfig
impl UnsafeUnpin for ServerConfig
impl UnwindSafe for ServerConfig
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> DeserializeOwned for Twhere
T: for<'de> Deserialize<'de>,
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
Source§fn in_current_span(self) -> Instrumented<Self> ⓘ
fn in_current_span(self) -> Instrumented<Self> ⓘ
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more