Foundation Servers

Foundation servers provide core capabilities that domain servers build upon. They embody the DRY principle—write common functionality once, use it everywhere.

What Makes a Good Foundation?

Foundation servers should be:

┌─────────────────────────────────────────────────────────────────────────┐
│                    Foundation Server Characteristics                    │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  ✓ STABLE: APIs rarely change (breaking changes affect all domains)     │
│  ✓ GENERIC: No business-specific logic                                  │
│  ✓ COMPOSABLE: Easy to combine with other foundations                   │
│  ✓ WELL-TESTED: Heavily tested since bugs affect everyone               │
│  ✓ DOCUMENTED: Clear contracts for domain developers                    │
│                                                                         │
│  Good Foundation Candidates:       Bad Foundation Candidates:           │
│  ═══════════════════════════       ═══════════════════════════          │
│  • Authentication/Authorization    • Business rules                     │
│  • Database connectivity           • Domain calculations                │
│  • File system access              • Department-specific logic          │
│  • HTTP client operations          • UI/presentation code               │
│  • Caching infrastructure          • Company-specific policies          │
│  • Logging and metrics                                                  │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

Common Foundation Patterns

1. Authentication Foundation

Most enterprise servers need authentication. Build it once:

#![allow(unused)]
fn main() {
use pmcp::{Result, Server};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::sync::Arc;

/// User information returned by authentication
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AuthenticatedUser {
    pub id: String,
    pub email: String,
    pub roles: Vec<String>,
    pub department: String,
}

/// Authentication foundation providing user validation and info retrieval
pub struct AuthFoundation {
    // In production: connection to identity provider (Okta, Auth0, etc.)
    user_cache: Arc<tokio::sync::RwLock<std::collections::HashMap<String, AuthenticatedUser>>>,
}

impl AuthFoundation {
    pub fn new() -> Self {
        Self {
            user_cache: Arc::new(tokio::sync::RwLock::new(std::collections::HashMap::new())),
        }
    }

    /// Validate a token and return user information
    pub async fn validate_token(&self, token: &str) -> Result<AuthenticatedUser> {
        // In production: validate JWT, check with IdP, etc.
        // This is the SINGLE place where token validation logic lives

        if token.starts_with("valid_") {
            let user_id = token.strip_prefix("valid_").unwrap_or("unknown");
            Ok(AuthenticatedUser {
                id: user_id.to_string(),
                email: format!("{}@company.com", user_id),
                roles: vec!["employee".to_string()],
                department: "engineering".to_string(),
            })
        } else {
            Err(pmcp::Error::protocol(
                pmcp::ErrorCode::INVALID_PARAMS,
                "Invalid authentication token",
            ))
        }
    }

    /// Check if user has required role
    pub fn has_role(&self, user: &AuthenticatedUser, required_role: &str) -> bool {
        user.roles.iter().any(|r| r == required_role || r == "admin")
    }

    /// Create middleware that validates tokens on every request
    pub fn create_middleware(&self) -> AuthMiddleware {
        AuthMiddleware {
            foundation: self.clone(),
        }
    }
}

impl Clone for AuthFoundation {
    fn clone(&self) -> Self {
        Self {
            user_cache: self.user_cache.clone(),
        }
    }
}

/// Middleware that validates auth tokens on requests
pub struct AuthMiddleware {
    foundation: AuthFoundation,
}
}

2. Database Foundation

Centralize database access patterns:

#![allow(unused)]
fn main() {
use async_trait::async_trait;
use pmcp::server::dynamic_resources::{DynamicResourceProvider, RequestContext, UriParams};
use pmcp::types::{Content, ReadResourceResult, ResourceTemplate};

/// Database foundation providing query capabilities
pub struct DatabaseFoundation {
    connection_string: String,
    // In production: connection pool (sqlx, diesel, etc.)
}

impl DatabaseFoundation {
    pub fn new(connection_string: impl Into<String>) -> Self {
        Self {
            connection_string: connection_string.into(),
        }
    }

    /// Execute a read-only query
    pub async fn query(&self, sql: &str, params: &[&str]) -> Result<Vec<serde_json::Value>> {
        // Single place for:
        // - Query validation
        // - SQL injection prevention
        // - Connection pooling
        // - Query logging
        // - Performance metrics

        tracing::info!(sql = %sql, "Executing query");

        // Simulated response
        Ok(vec![serde_json::json!({
            "id": 1,
            "result": "sample data"
        })])
    }

    /// Create a dynamic resource provider for database tables
    pub fn create_table_provider(&self, allowed_tables: Vec<String>) -> TableResourceProvider {
        TableResourceProvider {
            foundation: self.clone(),
            allowed_tables,
        }
    }
}

impl Clone for DatabaseFoundation {
    fn clone(&self) -> Self {
        Self {
            connection_string: self.connection_string.clone(),
        }
    }
}

/// Dynamic resource provider for database tables
///
/// Provides resources like:
/// - tables://{table}/schema - Table schema
/// - tables://{table}/sample - Sample rows
/// - tables://{table}/count - Row count
pub struct TableResourceProvider {
    foundation: DatabaseFoundation,
    allowed_tables: Vec<String>,
}

#[async_trait]
impl DynamicResourceProvider for TableResourceProvider {
    fn templates(&self) -> Vec<ResourceTemplate> {
        vec![
            ResourceTemplate {
                uri_template: "tables://{table}/schema".to_string(),
                name: "Table Schema".to_string(),
                description: Some("Schema definition for a database table".to_string()),
                mime_type: Some("application/json".to_string()),
            },
            ResourceTemplate {
                uri_template: "tables://{table}/sample".to_string(),
                name: "Sample Rows".to_string(),
                description: Some("Sample rows from the table (first 10)".to_string()),
                mime_type: Some("application/json".to_string()),
            },
            ResourceTemplate {
                uri_template: "tables://{table}/count".to_string(),
                name: "Row Count".to_string(),
                description: Some("Number of rows in the table".to_string()),
                mime_type: Some("application/json".to_string()),
            },
        ]
    }

    async fn fetch(
        &self,
        uri: &str,
        params: UriParams,
        _context: RequestContext,
    ) -> Result<ReadResourceResult> {
        let table = params.get("table").ok_or_else(|| {
            pmcp::Error::protocol(pmcp::ErrorCode::INVALID_PARAMS, "Missing table name")
        })?;

        // Validate table is in allowed list (security!)
        if !self.allowed_tables.contains(&table.to_string()) {
            return Err(pmcp::Error::protocol(
                pmcp::ErrorCode::INVALID_PARAMS,
                format!("Table '{}' not accessible", table),
            ));
        }

        let content = if uri.contains("/schema") {
            let schema = self.foundation
                .query(
                    "SELECT column_name, data_type FROM information_schema.columns WHERE table_name = $1",
                    &[table],
                )
                .await?;
            Content::Text {
                text: serde_json::to_string_pretty(&schema)?,
            }
        } else if uri.contains("/sample") {
            let sample = self.foundation
                .query(&format!("SELECT * FROM {} LIMIT 10", table), &[])
                .await?;
            Content::Text {
                text: serde_json::to_string_pretty(&sample)?,
            }
        } else if uri.contains("/count") {
            let count = self.foundation
                .query(&format!("SELECT COUNT(*) as count FROM {}", table), &[])
                .await?;
            Content::Text {
                text: serde_json::to_string_pretty(&count)?,
            }
        } else {
            return Err(pmcp::Error::protocol(
                pmcp::ErrorCode::INVALID_PARAMS,
                "Unknown resource type",
            ));
        };

        Ok(ReadResourceResult {
            contents: vec![content],
        })
    }

    fn priority(&self) -> i32 {
        50
    }
}
}

3. File System Foundation

Secure, audited file access:

#![allow(unused)]
fn main() {
use std::path::{Path, PathBuf};

/// File system foundation with security controls
pub struct FileSystemFoundation {
    base_path: PathBuf,
    allowed_extensions: Vec<String>,
    max_file_size: usize,
}

impl FileSystemFoundation {
    pub fn new(base_path: impl Into<PathBuf>) -> Self {
        Self {
            base_path: base_path.into(),
            allowed_extensions: vec![
                "txt".to_string(),
                "json".to_string(),
                "csv".to_string(),
                "md".to_string(),
            ],
            max_file_size: 10 * 1024 * 1024, // 10 MB
        }
    }

    /// Safely resolve a path, preventing directory traversal attacks
    fn safe_path(&self, relative_path: &str) -> Result<PathBuf> {
        let path = self.base_path.join(relative_path);
        let canonical = path.canonicalize().map_err(|_| {
            pmcp::Error::protocol(pmcp::ErrorCode::INVALID_PARAMS, "Path not found")
        })?;

        // Prevent directory traversal (../../../etc/passwd)
        if !canonical.starts_with(&self.base_path) {
            return Err(pmcp::Error::protocol(
                pmcp::ErrorCode::INVALID_PARAMS,
                "Path traversal not allowed",
            ));
        }

        // Check extension
        if let Some(ext) = canonical.extension() {
            let ext_str = ext.to_string_lossy().to_lowercase();
            if !self.allowed_extensions.contains(&ext_str) {
                return Err(pmcp::Error::protocol(
                    pmcp::ErrorCode::INVALID_PARAMS,
                    format!("File extension '{}' not allowed", ext_str),
                ));
            }
        }

        Ok(canonical)
    }

    /// Read a file with security checks
    pub async fn read_file(&self, relative_path: &str) -> Result<String> {
        let path = self.safe_path(relative_path)?;

        // Check file size
        let metadata = tokio::fs::metadata(&path).await.map_err(|e| {
            pmcp::Error::protocol(pmcp::ErrorCode::INTERNAL_ERROR, e.to_string())
        })?;

        if metadata.len() > self.max_file_size as u64 {
            return Err(pmcp::Error::protocol(
                pmcp::ErrorCode::INVALID_PARAMS,
                format!("File exceeds maximum size of {} bytes", self.max_file_size),
            ));
        }

        // Audit log
        tracing::info!(path = %path.display(), "File read access");

        tokio::fs::read_to_string(&path).await.map_err(|e| {
            pmcp::Error::protocol(pmcp::ErrorCode::INTERNAL_ERROR, e.to_string())
        })
    }

    /// List files in a directory
    pub async fn list_files(&self, relative_path: &str) -> Result<Vec<String>> {
        let path = self.safe_path(relative_path)?;

        let mut entries = tokio::fs::read_dir(&path).await.map_err(|e| {
            pmcp::Error::protocol(pmcp::ErrorCode::INTERNAL_ERROR, e.to_string())
        })?;

        let mut files = Vec::new();
        while let Some(entry) = entries.next_entry().await.map_err(|e| {
            pmcp::Error::protocol(pmcp::ErrorCode::INTERNAL_ERROR, e.to_string())
        })? {
            files.push(entry.file_name().to_string_lossy().to_string());
        }

        Ok(files)
    }
}
}

Composing Foundations

Domain servers compose multiple foundations:

#![allow(unused)]
fn main() {
use pmcp::Server;
use std::sync::Arc;

/// A domain server that composes multiple foundations
pub async fn create_finance_server(
    auth: Arc<AuthFoundation>,
    db: Arc<DatabaseFoundation>,
    fs: Arc<FileSystemFoundation>,
) -> Result<Server> {
    // Create typed tools that use foundations
    let auth_clone = auth.clone();
    let db_clone = db.clone();

    Server::builder()
        .name("finance-server")
        .version("1.0.0")
        // Tool using auth + database foundations
        .tool_typed("get_expense_report", move |input: ExpenseReportInput, extra| {
            let auth = auth_clone.clone();
            let db = db_clone.clone();
            Box::pin(async move {
                // Use auth foundation
                let user = auth.validate_token(&input.token).await?;

                // Check permissions
                if !auth.has_role(&user, "finance_viewer") {
                    return Err(pmcp::Error::protocol(
                        pmcp::ErrorCode::INVALID_PARAMS,
                        "Insufficient permissions",
                    ));
                }

                // Use database foundation
                let expenses = db.query(
                    "SELECT * FROM expenses WHERE user_id = $1 AND month = $2",
                    &[&user.id, &input.month],
                ).await?;

                Ok(serde_json::json!({
                    "user": user.email,
                    "month": input.month,
                    "expenses": expenses
                }))
            })
        })
        // Add file resources using filesystem foundation
        .resources(
            pmcp::server::simple_resources::ResourceCollection::new()
                .add_dynamic_provider(Arc::new(
                    fs.create_resource_provider("reports://")
                ))
        )
        .build()
}

#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
struct ExpenseReportInput {
    token: String,
    month: String,
}
}

Foundation Versioning

When foundations evolve, version them carefully:

#![allow(unused)]
fn main() {
/// Foundation trait with version information
pub trait Foundation: Send + Sync {
    /// Foundation version for compatibility checking
    fn version(&self) -> &str;

    /// Minimum compatible version
    fn min_compatible_version(&self) -> &str;
}

impl Foundation for AuthFoundation {
    fn version(&self) -> &str {
        "2.0.0"
    }

    fn min_compatible_version(&self) -> &str {
        "1.5.0"  // Backwards compatible with 1.5+
    }
}

/// Check foundation compatibility before composing
fn check_compatibility(foundation: &dyn Foundation, required_version: &str) -> Result<()> {
    let version = semver::Version::parse(foundation.version())?;
    let required = semver::Version::parse(required_version)?;

    if version < required {
        return Err(pmcp::Error::protocol(
            pmcp::ErrorCode::INTERNAL_ERROR,
            format!(
                "Foundation version {} is below required version {}",
                version, required
            ),
        ));
    }

    Ok(())
}
}

Testing Foundations

Foundations need thorough testing since bugs affect all consumers:

#![allow(unused)]
fn main() {
#[cfg(test)]
mod tests {
    use super::*;

    #[tokio::test]
    async fn auth_foundation_validates_tokens() {
        let auth = AuthFoundation::new();

        // Valid token
        let user = auth.validate_token("valid_user123").await.unwrap();
        assert_eq!(user.id, "user123");

        // Invalid token
        let result = auth.validate_token("invalid_token").await;
        assert!(result.is_err());
    }

    #[tokio::test]
    async fn auth_foundation_checks_roles() {
        let auth = AuthFoundation::new();
        let user = auth.validate_token("valid_admin").await.unwrap();

        assert!(auth.has_role(&user, "employee"));
        assert!(!auth.has_role(&user, "super_admin"));
    }

    #[tokio::test]
    async fn filesystem_prevents_traversal() {
        let fs = FileSystemFoundation::new("/data");

        // Attempting path traversal should fail
        let result = fs.read_file("../../../etc/passwd").await;
        assert!(result.is_err());

        // Valid path should work
        let result = fs.read_file("reports/q1.txt").await;
        // Depends on actual file existence
    }

    #[tokio::test]
    async fn database_validates_tables() {
        let db = DatabaseFoundation::new("postgres://localhost/test");
        let provider = db.create_table_provider(vec!["users".to_string(), "orders".to_string()]);

        // Allowed table
        let templates = provider.templates();
        assert_eq!(templates.len(), 3);

        // Verify URI template format
        assert!(templates[0].uri_template.contains("{table}"));
    }
}
}

Summary

Foundation TypeProvidesUsed By
AuthenticationToken validation, user info, rolesAll domain servers
DatabaseConnection pooling, query execution, resource providersServers needing data access
File SystemSafe file access, directory listingServers handling documents
HTTP ClientExternal API calls, retry logicIntegration servers
CacheIn-memory and distributed cachingPerformance-critical servers

Building good foundations takes time upfront but pays dividends as your MCP server ecosystem grows. Every domain server benefits from the shared, well-tested, consistently-behaved foundation layer.


Continue to Domain Servers