From d79146b4d1ae09dc5da8834b57f7cf0fa944c205 Mon Sep 17 00:00:00 2001 From: geoffsee <> Date: Thu, 5 Jun 2025 14:29:08 -0400 Subject: [PATCH] embed mcp ui --- Cargo.lock | 17 ++++ Cargo.toml | 1 + README.md | 231 +------------------------------------------------- src/routes.rs | 40 ++++++++- 4 files changed, 59 insertions(+), 230 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2ca7fac..b3992fb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -652,6 +652,16 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +[[package]] +name = "mime_guess" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" +dependencies = [ + "mime", + "unicase", +] + [[package]] name = "miniz_oxide" version = "0.8.0" @@ -737,6 +747,7 @@ dependencies = [ "futures", "http", "lazy_static", + "mime_guess", "rmcp", "rust-embed", "serde", @@ -1485,6 +1496,12 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +[[package]] +name = "unicase" +version = "2.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" + [[package]] name = "unicode-ident" version = "1.0.14" diff --git a/Cargo.toml b/Cargo.toml index d31dbcb..0e6efc7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,3 +33,4 @@ anyhow = "1.0.97" base64 = "0.22.1" fips204 = "0.4.6" rmcp = { git = "https://github.com/modelcontextprotocol/rust-sdk", branch = "main", features = ["server", "transport-streamable-http-server", "transport-sse-server", "transport-io",] } +mime_guess = "2.0.5" diff --git a/README.md b/README.md index d91e598..f11a274 100644 --- a/README.md +++ b/README.md @@ -1,237 +1,10 @@ # open-web-agent-rs -Remote genaiscript host for integration into conversational AI applications. -> This application is actively being ported, expect breaking changes. +Server is being converted to MCP. Things are probably broken. -## Quickstart -```bash -git clone https://github.com/seemueller-io/open-web-agent-rs.git -sed -i '' '/^OPENAI_API_KEY=/d' .env; echo 'OPENAI_API_KEY=your-own-api-key' >> .env +```text bun i -bun run build bun dev -docker compose start searxng ``` -```typescript -#!/usr/bin/env deno -A -const API_ROOT = "http://localhost:3006"; - -const sid = crypto.randomUUID(); -// -------------------- 1. Create the agent -------------------- -const createAgentBody = { - id: sid, - resource: "web-search", - parent: sid, - payload: { input: "Who won the 2024 election in the US?" }, -}; - -const createRes = await fetch(`${API_ROOT}/api/agents`, { - method: "POST", - headers: { "content-type": "application/json" }, - body: JSON.stringify(createAgentBody), -}); - - -const raw = await createRes.text(); -console.log({raw}); -const {stream_url: streamId} = JSON.parse(raw); - -console.log("Agent created with streamId:", streamId); - -// -------------------- 2. Listen to the SSE stream -------------------- -const streamUrl = `${API_ROOT}${streamId}`; -const es = new EventSource(streamUrl); - - -es.onopen = (e) => { - console.log("connected", e); -}; - -es.onmessage = (e) => { - console.log("⟶", e.data); -}; - -es.onerror = (e) => { - console.error("SSE error:", e); - es.close(); -}; -``` - -### Disclaimer -This has not undergone a formal security assessment. You should do your own evaluation before using this. - -### Features not included in this fork -- Capabilities API: Reports available agents via HTTP (useful for dynamic intent mapping) - -### Planned Features -- Embed Model Context Protocol for client connectivity - -## Documentation - -Comprehensive documentation is available in the [docs](./docs) directory: - -- [Installation Guide](./docs/installation.md) - How to install and set up the project -- [Configuration Guide](./docs/configuration.md) - Environment variables and configuration options -- [API Documentation](./docs/api.md) - API endpoints and usage examples -- [Authentication](./docs/tokens.md) - Authentication system documentation -- [Agents Guide](./docs/agents.md) - How to create and use agents -- [Input Documentation](./docs/input.md) - How input works for agents -- [Stream Data Format](./docs/streams.md) - How stream data is formatted for clients - - -### Setup -See [Installation](./docs/installation.md) - -### How it works -1. A chat client specifies the URL to this host in their environment. -2. They send a request with their credentials to create a stream resource - -## Adding New Agents - -This project allows you to create and integrate new agents that can perform various tasks. Here's how to add a new agent: - -### 1. Create a GenAIScript File - -Create a new `.genai.mts` file in the `packages/genaiscript/genaisrc/` directory. This file will contain the agent's logic. - -Example structure of a GenAIScript file: - -```typescript -import {SomeClient} from "@agentic/some-package"; -import "./tools/some-tool.genai.mjs" - -script({ - title: "your_agent_name", - maxTokens: 8192, - cache: false, - tools: ["tool-name"], -}); - -def("USER_INPUT", env.vars.user_input); - -$`You are an assistant that performs a specific task. -- Instruction 1 -- Instruction 2 -- Instruction 3` -``` - -### 2. Create a Rust Agent Function - -Create a new Rust file in the `src/agents/` directory or add a function to an existing file. This function will be a wrapper that calls the GenAIScript file. - -Example agent function: - -```rust -use tokio::process::Child; -use tracing; - -use crate::utils::utils::run_agent; - -pub async fn agent(stream_id: &str, input: &str) -> Result { - run_agent(stream_id, input, "./packages/genaiscript/genaisrc/your-agent.genai.mts").await -} -``` - -### 3. Register the Agent in the Module - -Add your agent to the `src/agents/mod.rs` file: - -```rust -pub(crate) mod your_module; -``` - -### 4. Register the Agent in the Webhook Handler - -Add your agent to the match statement in the `use_agent` function in `src/handlers/agents.rs`: - -``` -// In the use_agent function -let cmd = match resource.as_str() { - "web-search" => crate::agents::search::agent(agent_id.as_str(), &*input).await, - "news-search" => crate::agents::news::agent(agent_id.as_str(), &*input).await, - // Add your agent here - "your-resource-name" => crate::agents::your_module::agent(agent_id.as_str(), &*input).await, - _ => { - tracing::error!("Unsupported resource type: {}", resource); - return StatusCode::BAD_REQUEST.into_response(); - } -}; -``` - -### 5. Configure Environment Variables - -If your agent requires specific API keys or configuration, add them to the `ShimBinding` struct in `src/utils/utils.rs`. - - -### Fast Agent Development Workflow -1. Create script: create a new genaiscript script in `packages/genaiscript/genaisrc` -2. Setup a development executor: Map a package script in `package.json` to the script in step 1 following the existing examples -3. Iterate until agent is functional. -4. Follow the guide on adding a new agent to integrate it into the rust server. - -## License - -This project is licensed under the [MIT License](LICENSE) - -## FAQ -> Q: Why Rust? -> A: Stronger primitives for STDIO and process management. - - -Development History (Nov 2024 – May 2025) ---- - -#### May 2025 - -* **Sanitize codebase and cleanup** *(2025-05-23)* - -#### April 2025 - -* **Replace Perigon integration with SearxNG** *(2025-04-16)* -* **Enable authentication for SearxNG search** *(2025-04-04)* -* **Temporarily remove SearxNG password** *(2025-04-04)* -* **Deploy SearxNG search functionality** *(2025-04-01)* - -#### March 2025 - -* **Deploy updated search functionality using SearxNG** *(2025-03-31)* -* **Resolve dependency issues and update Docker configuration** *(2025-03-31)* -* **Implement cryptocurrency market data fetching and quoting functionality** *(2025-03-20)* -* **Update AI model configuration** *(2025-03-20)* -* **Fix model provider issue** *(2025-03-18)* -* **Deploy configuration with auto-scaling capabilities (scales to zero)** *(2025-03-17)* - -#### February 2025 - -* **Add image generation endpoint** *(2025-02-05)* - -#### January 2025 - -* **Containerize application with Docker and deploy successfully** *(2025-01-27)* -* **Implement request call-count tracking and integrate tracing (tower-http)** *(2025-01-21)* -* **Disable caching mechanism** *(2025-01-16)* -* **Update deployment configuration to use GPT-4o-mini model** *(2025-01-15)* -* **Switch AI model provider back to OpenAI** *(2025-01-14)* - -#### December 2024 - -* **Refactor database handling and web scraping logic** *(2024-12-30)* -* **Implement robust error handling and retry logic for webhooks** *(2024-12-29)* -* **Add sled database for persistent webhook handling** *(2024-12-28)* -* **Enhance scraping modules and build configurations** *(2024-12-28)* -* **Finalize URL reader implementation** *(2024-12-27)* -* **Upgrade news fetching mechanism and set specific search query provider** *(2024-12-21, 2024-12-18)* -* **Improve news search functionality (date filtering, formatting, error handling)** *(2024-12-16 to 2024-12-18)* -* **Add Perigon integration for news search** *(2024-12-16)* -* **Enhance VM resources and refine search result formatting** *(2024-12-16)* -* **Add stream activity tracking with reconnection handling** *(2024-12-15)* -* **Simplify AI search scripts and improve dependency management** *(2024-12-10)* -* **Update API keys and model configurations for better search reliability** *(2024-12-07, 2024-12-02)* - -#### November 2024 - -* **Refactor project structure, enhance logging, and initial UI responses** *(2024-11-28)* ---- -Note: Original commit history may be available by request. diff --git a/src/routes.rs b/src/routes.rs index 300ceb2..67b7cd4 100644 --- a/src/routes.rs +++ b/src/routes.rs @@ -1,13 +1,49 @@ +use axum::response::Response; use crate::handlers::{not_found::handle_not_found, ui::serve_ui}; use axum::routing::{get, Router}; +use http::StatusCode; use tower_http::trace::{self, TraceLayer}; use tracing::Level; use rmcp::transport::streamable_http_server::{ StreamableHttpService, session::local::LocalSessionManager, }; +use rust_embed::Embed; use crate::agents::Agents; + +#[derive(Embed)] +#[folder = "./node_modules/@modelcontextprotocol/inspector-client/dist"] +struct Asset; + +pub struct StaticFile(pub T); + +impl axum::response::IntoResponse for StaticFile +where + T: Into, +{ + fn into_response(self) -> Response { + let path = self.0.into(); + + match Asset::get(path.as_str()) { + Some(content) => { + let mime = mime_guess::from_path(path).first_or_octet_stream(); + ([(http::header::CONTENT_TYPE, mime.as_ref())], content.data).into_response() + } + None => (StatusCode::NOT_FOUND, "404 Not Found").into_response(), + } + } +} + +async fn ui_index_handler() -> impl axum::response::IntoResponse { + StaticFile("index.html") +} + +async fn static_handler(uri: http::Uri) -> impl axum::response::IntoResponse { + let path = uri.path().trim_start_matches("/").to_string(); + StaticFile(path) +} + pub fn create_router() -> Router { let mcp_service = StreamableHttpService::new( @@ -18,8 +54,10 @@ pub fn create_router() -> Router { Router::new() .nest_service("/mcp", mcp_service) - .route("/", get(serve_ui)) .route("/health", get(health)) + .route("/", get(ui_index_handler)) + .route("/index.html", get(ui_index_handler)) + .route("/{*path}", get(static_handler)) .layer( TraceLayer::new_for_http() .make_span_with(trace::DefaultMakeSpan::new().level(Level::INFO))