Skip to content
This repository was archived by the owner on Sep 10, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 0 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -121,11 +121,6 @@ jobs:
else
echo "::notice::Coverage ${COVERAGE}% meets the 80% requirement"
fi

- name: Test build templates
run: |
# Test that the embedded templates are valid
cargo build --release

security:
name: Security Audit
Expand Down
54 changes: 27 additions & 27 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,50 +18,50 @@ FTL is an open source framework and tool platform for AI agents. It builds on th

## Why?

MCP is minimal. Tools are straightforward. Writing and running them should be too.

<details>
<summary><strong>⤵ Sub-millisecond cold starts on global edge compute + Flexible deployment targets</strong></summary>
Tools should be more than proxies for REST APIs that were meant for a totally different consumer.

FTL tools run on any host compatible with Spin/[Wasmtime](https://github.com/bytecodealliance/wasmtime), including your development machine.

When it's time to deploy, the FTL Platform aims to be the best surface for running and managing high performance remote tools. It handles the plumbing and exposes MCP-compliant servers that are distributed enough to be used by agents operating anywhere, including real-time voice and multimodal systems.

Latency and compute overhead for remote tool calls should not be something you have to design around. Instant, sandboxed, globally distributed high performance compute should be available to your agent as a resource. This enables powerful patterns for crafting optimal agent interactions and tool responses beyond just proxying to third party APIs.

- FTL tools run as individually sandboxed components on [Fermyon Wasm Functions](https://www.fermyon.com/wasm-functions) and [Akamai](https://www.akamai.com/why-akamai/global-infrastructure)'s globally distributed edge cloud.
- Workers automatically scale horizontally to meet demand, can cold start in < 1ms, and scale down to zero.
- The FTL [gateway components](#architecture) handle MCP server implementation, auth, tool argument validation, and tool component routing.
- Tool calls are automatically routed to a worker running on most optimal Akamai edge PoP. Ensure your users have the best possible experience when using agents that access their applications and content, regardless of physical location.
- High performance programming patterns with low-level features like [SIMD](https://github.com/WebAssembly/spec/blob/main/proposals/simd/SIMD.md) are available via languages like Rust and C to unlock SOTA compute capabilities for real-time agents.

The FTL Platform is just one possible deployment target. It is currently in early alpha and free with limited capacity. Opt in via the `ftl login` command, which enables `ftl deploy`.
</details>

<details>
<summary><strong>⤵ Secure by design</strong></summary>

- Tools run as individual WebAssembly components to provide sandboxed tool executions on a provably airtight [security model](https://webassembly.org/docs/security/).
- MCP endpoints are secured by [protocol-compliant authorization](https://modelcontextprotocol.io/specification/2025-06-18/basic/authorization).
- MCP endpoints are secured by configurable [protocol-compliant authorization](https://modelcontextprotocol.io/specification/2025-06-18/basic/authorization).
- Plug in your own OIDC provider via simple configuration, or use FTL's by default.
- Allowed outbound network calls are configurable by host per tool. This is especially important when you include tools you didn't author in your MCP server, which you can do with OCI registry references (see below.).
- Allowed outbound network calls are configurable per tool. This is especially useful when including third party tool components in your MCP server (see below).
</details>

<details>
<summary><strong>⤵ Distribute and compose tools like tiny docker images</strong></summary>

- Tools are compiled to self-contained Wasm binaries that are often < 1MB.
- Tools can be pushed and pulled directly from [OCI](https://opencontainers.org/)-compliant registries like Docker Hub, GitHub Container Registry, Amazon Elastic Container Registry, and more.
- Mix and match individual tools in your MCP server by registry URI. Allowed outbound network calls are configurable per tool.
- Tool binary size and performance are influenced by the tool's individual source language.
</details>

<details>
<summary><strong>⤵ Write tools in the best source language for the job</strong></summary>

* Combine tools written in different source languages within one MCP server.
* Use Rust, TypeScript, Python, Go, C, and [more](https://component-model.bytecodealliance.org/language-support.html).
* High performance features like [SIMD](https://github.com/WebAssembly/spec/blob/main/proposals/simd/SIMD.md) are available via languages like Rust and C.
- Combine tools written in different source languages within one MCP server.
- Use Rust, TypeScript, Python, Go, C, and [more](https://component-model.bytecodealliance.org/language-support.html).
- High performance features like [SIMD](https://github.com/WebAssembly/spec/blob/main/proposals/simd/SIMD.md) are available via languages like Rust and C.
</details>

<details>
<summary><strong>⤵ Distribute and compose tools like tiny docker images</strong></summary>
<summary><strong>⤵ Deploy on the FTL Platform</strong></summary>

The FTL Platform aims to be an excellent choice for running and managing remote tools. It handles the plumbing and exposes MCP-compliant servers that are fast and distributed enough to be used by agents operating anywhere, including real-time voice and multimodal systems.

Latency and compute overhead for remote tool calls should not be something you have to design around. Instant, sandboxed, globally distributed high performance compute should be available to your agent as a resource. This enables powerful patterns for crafting optimal agent interactions and tool responses beyond just proxying to third party APIs.

* Tools are compiled to self-contained Wasm binaries that are often < 1MB.
* Tools can be pushed and pulled directly from [OCI](https://opencontainers.org/)-compliant registries like Docker Hub, GitHub Container Registry, Amazon Elastic Container Registry, and more.
* Mix and match individual tools in your MCP server by registry URI.
* Tool binary size and performance are influenced by the tool's individual source language.
- FTL tools run as individually sandboxed components on [Fermyon Wasm Functions](https://www.fermyon.com/wasm-functions) and [Akamai](https://www.akamai.com/why-akamai/global-infrastructure)'s globally distributed edge cloud.
- Workers automatically scale horizontally to meet demand, can cold start in < 1ms, and scale down to zero.
- The FTL [gateway components](#architecture) handle MCP server implementation, auth, tool argument validation, and tool component routing.
- Tool calls are automatically routed to a worker running on most optimal Akamai edge PoP, enabling consistently low latency across geographic regions.
- High performance programming patterns with low-level features like [SIMD](https://github.com/WebAssembly/spec/blob/main/proposals/simd/SIMD.md) are available via languages like Rust and C to unlock SOTA compute capabilities for real-time agents.

The FTL Platform is just one possible deployment target. It is currently in early alpha and free with limited capacity. Opt in via the `ftl login` command, which enables `ftl deploy`.
</details>

## Quick Start
Expand Down
58 changes: 6 additions & 52 deletions backend-openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -147,22 +147,6 @@
"responses": {
"200": {
"description": "List of applications",
"headers": {
"X-Cache": {
"schema": {
"type": "string",
"enum": [
"HIT",
"MISS"
]
}
},
"X-Cached-At": {
"schema": {
"type": "string"
}
}
},
"content": {
"application/json": {
"schema": {
Expand Down Expand Up @@ -229,22 +213,6 @@
"responses": {
"200": {
"description": "Application status retrieved successfully",
"headers": {
"X-Cache": {
"schema": {
"type": "string",
"enum": [
"HIT",
"MISS"
]
}
},
"X-Cached-At": {
"schema": {
"type": "string"
}
}
},
"content": {
"application/json": {
"schema": {
Expand Down Expand Up @@ -470,17 +438,7 @@
},
"responses": {
"200": {
"description": "Repository already exists",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateEcrRepositoryResponse"
}
}
}
},
"201": {
"description": "Repository created successfully",
"description": "Repository created successfully or already exists",
"content": {
"application/json": {
"schema": {
Expand Down Expand Up @@ -588,7 +546,7 @@
"description": "Application name",
"type": "string",
"minLength": 1,
"maxLength": 50,
"maxLength": 200,
"pattern": "^[a-zA-Z0-9-_]+$"
},
"tools": {
Expand Down Expand Up @@ -632,9 +590,6 @@
"variables": {
"description": "Environment variables as key-value pairs",
"type": "object",
"propertyNames": {
"type": "string"
},
"additionalProperties": {
"type": "string"
}
Expand Down Expand Up @@ -707,8 +662,8 @@
"description": "Standard error response format",
"type": "object",
"properties": {
"error": {
"description": "Human-readable error message",
"message": {
"description": "Error message",
"type": "string"
},
"code": {
Expand Down Expand Up @@ -739,8 +694,7 @@
}
},
"required": [
"error",
"code"
"message"
],
"additionalProperties": false
},
Expand Down Expand Up @@ -1078,7 +1032,7 @@
"platform": {
"description": "Deployment platform",
"type": "string",
"enum": ["Fermyon"]
"const": "Fermyon"
},
"createdAt": {
"description": "ISO 8601 timestamp",
Expand Down
40 changes: 38 additions & 2 deletions src/deps.rs
Original file line number Diff line number Diff line change
Expand Up @@ -258,31 +258,55 @@ impl CommandExecutor for RealCommandExecutor {
/// Production API client wrapper
pub struct RealFtlApiClient {
client: ApiClient,
auth_token: Option<String>,
}

impl RealFtlApiClient {
#[allow(dead_code)]
pub const fn new(client: ApiClient) -> Self {
Self { client }
Self {
client,
auth_token: None,
}
}

pub const fn new_with_auth(client: ApiClient, auth_token: String) -> Self {
Self {
client,
auth_token: Some(auth_token),
}
}
}

#[async_trait]
impl FtlApiClient for RealFtlApiClient {
async fn get_ecr_credentials(&self) -> Result<types::GetEcrCredentialsResponse> {
let auth = self
.auth_token
.as_ref()
.ok_or_else(|| anyhow::anyhow!("No authentication token available"))?;

self.client
.get_ecr_credentials()
.authorization(format!("Bearer {auth}"))
.send()
.await
.map(progenitor_client::ResponseValue::into_inner)
.map_err(|e| anyhow::anyhow!("Failed to get ECR credentials: {}", e))
.map_err(|e| anyhow::anyhow!("{}", e))
}

async fn create_ecr_repository(
&self,
request: &types::CreateEcrRepositoryRequest,
) -> Result<types::CreateEcrRepositoryResponse> {
let auth = self
.auth_token
.as_ref()
.ok_or_else(|| anyhow::anyhow!("No authentication token available"))?;

self.client
.create_ecr_repository()
.authorization(format!("Bearer {auth}"))
.body(request)
.send()
.await
Expand All @@ -291,8 +315,14 @@ impl FtlApiClient for RealFtlApiClient {
}

async fn get_deployment_status(&self, deployment_id: &str) -> Result<types::DeploymentStatus> {
let auth = self
.auth_token
.as_ref()
.ok_or_else(|| anyhow::anyhow!("No authentication token available"))?;

self.client
.get_deployment_status()
.authorization(format!("Bearer {auth}"))
.deployment_id(deployment_id)
.send()
.await
Expand All @@ -304,8 +334,14 @@ impl FtlApiClient for RealFtlApiClient {
&self,
request: &types::DeploymentRequest,
) -> Result<types::DeploymentResponse> {
let auth = self
.auth_token
.as_ref()
.ok_or_else(|| anyhow::anyhow!("No authentication token available"))?;

self.client
.deploy_app()
.authorization(format!("Bearer {auth}"))
.body(request)
.send()
.await
Expand Down
26 changes: 23 additions & 3 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ mod deps;
mod language;
mod ui;

use deps::CredentialsProvider;

// Implementations for up command dependencies
struct RealFileWatcher;

Expand Down Expand Up @@ -775,14 +777,32 @@ async fn main() -> Result<()> {
Command::Deploy => {
// Create dependencies
let ui = Arc::new(ui::RealUserInterface);

// Get credentials first to create authenticated API client
let credentials_provider = deps::RealCredentialsProvider;
let Ok(credentials) = credentials_provider.get_or_refresh_credentials().await else {
return Err(anyhow::anyhow!(
"Not logged in to FTL. Run 'ftl login' first."
));
};

// Create API client with authentication
let api_client_config = api_client::ApiConfig {
base_url: "https://fqwe5s59ob.execute-api.us-east-1.amazonaws.com".to_string(),
auth_token: Some(credentials.access_token.clone()),
timeout: std::time::Duration::from_secs(30),
};
let api_client = api_client::create_client(api_client_config)?;

let deps = Arc::new(commands::deploy::DeployDependencies {
file_system: Arc::new(deps::RealFileSystem),
command_executor: Arc::new(deps::RealCommandExecutor),
ui: ui.clone(),
credentials_provider: Arc::new(deps::RealCredentialsProvider),
api_client: Arc::new(deps::RealFtlApiClient::new(crate::api_client::Client::new(
"https://fqwe5s59ob.execute-api.us-east-1.amazonaws.com",
))),
api_client: Arc::new(deps::RealFtlApiClient::new_with_auth(
api_client,
credentials.access_token,
)),
clock: Arc::new(deps::RealClock),
async_runtime: Arc::new(deps::RealAsyncRuntime),
build_executor: Arc::new(deps::RealBuildExecutor),
Expand Down
2 changes: 1 addition & 1 deletion src/test_helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,7 @@ pub fn test_deployment_status(
status,
deployment_url: Some("https://test-app.example.com".to_string()),
image_url: "test-image:latest".to_string(),
platform: types::DeploymentStatusDeploymentPlatform::Fermyon,
platform: "Fermyon".to_string(),
created_at: chrono::Utc::now(),
updated_at: chrono::Utc::now(),
completed_at: None,
Expand Down
Loading