Deploy Applets with Configuration
When building MCP servers or other applets that need to connect to external services, you often require configuration values like API endpoints, hostnames, ports, or other settings that shouldn't be hardcoded into your applet. WeilChain provides a secure configuration system that allows you to deploy applets with custom configurations while keeping sensitive information separate from your code.
Why Use External Configuration?
Hardcoding configuration values in your applet has several drawbacks:
- Security Concerns: Sensitive information like API keys or internal hostnames become visible in the applet code
- Flexibility: Different deployments might need different configurations (development vs production)
- Reusability: Other users can't easily adapt your applet for their own infrastructure
- Maintenance: Changing configuration requires recompiling and redeploying the entire applet
WeilChain's configuration system solves these problems by allowing you to specify configuration externally at deployment time.
Defining Configuration Structure
First, define your configuration structure in your WIDL file using a record
type. This creates a structured schema for your configuration values.
Example: Postgres Service Configuration
record PostgresConfig {
host: string,
port: string
}
@mcp
interface Postgres {
config -> PostgresConfig;
// This returns the schema of the database with name given by argument `db_name`.
query func schema(db_name: string) -> result<string, string>;
// This runs a query provided in argument `query_str` on the database with name given by argument `db_name`.
query func run_query(db_name: string, query_str: string) -> result<list<string>, string>;
// This executes the statement provided in argument `statement` potentially mutating the rows of the database with name given by argument `db_name`.
query func execute(db_name: string, statement: string) -> result<u64, string>
}
Configuration Schema Definition
The record
type defines the structure of your configuration:
- Field Types: Use appropriate WIDL types (
string
for now) - Required Fields: All fields in a record are required by default
- Naming: Use clear, descriptive field names that indicate their purpose
Creating Configuration Files
Create a YAML configuration file that matches your defined schema. This file contains the actual values for your deployment.
Example Configuration File
host: "1.2.3.4"
port: "1234"
Deploying with Configuration
Use the enhanced deploy command to include your configuration file:
deploy -f path/to/applet.wasm -p path/to/applet.widl -c path/to/config.yaml
Deploy Command Parameters
-f
: Path to your compiled WASM applet-p
: Path to your WIDL interface definition-c
: Path to your YAML configuration file
Deployment Process
When you deploy with configuration:
- Validation: The deploy tool validates your configuration against the WIDL schema
- Encryption: Configuration values are securely encrypted before storage
- Registration: The applet is registered with its associated configuration
- Access Control: Only your applet instance can access its configuration
Implementing Configuration in Rust
Applet State Setup
Import the necessary types and set up your applet state to use the configuration system:
use serde::{Deserialize, Serialize};
use weil_macros::{constructor, mutate, query, smart_contract, WeilType};
use weil_rs::config::Secrets;
// Your configuration structure (generated from WIDL)
#[derive(Serialize, Deserialize, Clone)]
pub struct PostgresConfig {
pub host: String,
pub port: String,
}
#[derive(Serialize, Deserialize, WeilType)]
pub struct PostgresContractState {
secrets: Secrets<PostgresConfig>,
}
Constructor Implementation
Initialize your applet state with the configuration system:
#[smart_contract]
impl Postgres for PostgresContractState {
#[constructor]
fn new() -> Result<Self, String>
where
Self: Sized,
{
Ok(PostgresContractState {
secrets: Secrets::new(),
})
}
// Implementation methods...
}
Using Configuration Values
Access configuration values through the secrets
field in your applet methods:
impl PostgresContractState {
fn url(&self, endpoint: &str) -> String {
format!(
"http://{}:{}/{}",
self.secrets.config().host,
self.secrets.config().port,
endpoint
)
}
async fn make_api_call(&self, endpoint: &str) -> Result<String, String> {
let url = self.url(endpoint);
// Use the constructed URL for your API call
todo!("Implement request to postgres url {}", url)
}
}
The configuration system enables you to build flexible, secure, and reusable applets that can adapt to different deployment environments while keeping sensitive information protected.