embed mcp ui
This commit is contained in:

committed by
Geoff Seemueller

parent
22ef371c5b
commit
d79146b4d1
17
Cargo.lock
generated
17
Cargo.lock
generated
@@ -652,6 +652,16 @@ version = "0.3.17"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
|
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]]
|
[[package]]
|
||||||
name = "miniz_oxide"
|
name = "miniz_oxide"
|
||||||
version = "0.8.0"
|
version = "0.8.0"
|
||||||
@@ -737,6 +747,7 @@ dependencies = [
|
|||||||
"futures",
|
"futures",
|
||||||
"http",
|
"http",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
|
"mime_guess",
|
||||||
"rmcp",
|
"rmcp",
|
||||||
"rust-embed",
|
"rust-embed",
|
||||||
"serde",
|
"serde",
|
||||||
@@ -1485,6 +1496,12 @@ version = "1.17.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
|
checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicase"
|
||||||
|
version = "2.8.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-ident"
|
name = "unicode-ident"
|
||||||
version = "1.0.14"
|
version = "1.0.14"
|
||||||
|
@@ -33,3 +33,4 @@ anyhow = "1.0.97"
|
|||||||
base64 = "0.22.1"
|
base64 = "0.22.1"
|
||||||
fips204 = "0.4.6"
|
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",] }
|
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"
|
||||||
|
231
README.md
231
README.md
@@ -1,237 +1,10 @@
|
|||||||
# open-web-agent-rs
|
# 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
|
```text
|
||||||
```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
|
|
||||||
bun i
|
bun i
|
||||||
bun run build
|
|
||||||
bun dev
|
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<Child, String> {
|
|
||||||
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.
|
|
||||||
|
@@ -1,13 +1,49 @@
|
|||||||
|
use axum::response::Response;
|
||||||
use crate::handlers::{not_found::handle_not_found, ui::serve_ui};
|
use crate::handlers::{not_found::handle_not_found, ui::serve_ui};
|
||||||
use axum::routing::{get, Router};
|
use axum::routing::{get, Router};
|
||||||
|
use http::StatusCode;
|
||||||
use tower_http::trace::{self, TraceLayer};
|
use tower_http::trace::{self, TraceLayer};
|
||||||
use tracing::Level;
|
use tracing::Level;
|
||||||
|
|
||||||
use rmcp::transport::streamable_http_server::{
|
use rmcp::transport::streamable_http_server::{
|
||||||
StreamableHttpService, session::local::LocalSessionManager,
|
StreamableHttpService, session::local::LocalSessionManager,
|
||||||
};
|
};
|
||||||
|
use rust_embed::Embed;
|
||||||
use crate::agents::Agents;
|
use crate::agents::Agents;
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Embed)]
|
||||||
|
#[folder = "./node_modules/@modelcontextprotocol/inspector-client/dist"]
|
||||||
|
struct Asset;
|
||||||
|
|
||||||
|
pub struct StaticFile<T>(pub T);
|
||||||
|
|
||||||
|
impl<T> axum::response::IntoResponse for StaticFile<T>
|
||||||
|
where
|
||||||
|
T: Into<String>,
|
||||||
|
{
|
||||||
|
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 {
|
pub fn create_router() -> Router {
|
||||||
|
|
||||||
let mcp_service = StreamableHttpService::new(
|
let mcp_service = StreamableHttpService::new(
|
||||||
@@ -18,8 +54,10 @@ pub fn create_router() -> Router {
|
|||||||
|
|
||||||
Router::new()
|
Router::new()
|
||||||
.nest_service("/mcp", mcp_service)
|
.nest_service("/mcp", mcp_service)
|
||||||
.route("/", get(serve_ui))
|
|
||||||
.route("/health", get(health))
|
.route("/health", get(health))
|
||||||
|
.route("/", get(ui_index_handler))
|
||||||
|
.route("/index.html", get(ui_index_handler))
|
||||||
|
.route("/{*path}", get(static_handler))
|
||||||
.layer(
|
.layer(
|
||||||
TraceLayer::new_for_http()
|
TraceLayer::new_for_http()
|
||||||
.make_span_with(trace::DefaultMakeSpan::new().level(Level::INFO))
|
.make_span_with(trace::DefaultMakeSpan::new().level(Level::INFO))
|
||||||
|
Reference in New Issue
Block a user