icarus_canister/
endpoints.rs

1//! Canister endpoints for tool metadata discovery
2//!
3//! In the clean architecture, canisters don't handle MCP protocol.
4//! They only provide metadata for bridge discovery.
5
6use crate::state::STATE;
7use candid::{CandidType, Deserialize, Principal};
8use icarus_core::protocol::{IcarusMetadata, ToolMetadata};
9
10/// Query canister metadata for tool discovery
11pub fn icarus_metadata() -> IcarusMetadata {
12    STATE.with(|s| {
13        let state = s.borrow();
14        if let Some(state) = state.as_ref() {
15            IcarusMetadata {
16                version: state.config.get().version.clone(),
17                canister_id: ic_cdk::id(),
18                tools: state
19                    .tools
20                    .iter()
21                    .map(|(name, tool_state)| {
22                        ToolMetadata {
23                            name: name.clone(),
24                            candid_method: name.clone(), // Method name matches tool name
25                            is_query: tool_state.is_query,
26                            description: format!("{} tool", name), // TODO: Get from tool definition
27                            parameters: vec![],                    // TODO: Get from tool definition
28                        }
29                    })
30                    .collect(),
31            }
32        } else {
33            IcarusMetadata {
34                version: "1.0.0".to_string(),
35                canister_id: ic_cdk::id(),
36                tools: vec![],
37            }
38        }
39    })
40}
41
42/// HTTP request type for canister HTTP gateway
43#[derive(CandidType, Deserialize)]
44pub struct HttpRequest {
45    pub method: String,
46    pub url: String,
47    pub headers: Vec<(String, String)>,
48    pub body: Vec<u8>,
49}
50
51/// HTTP response type for canister HTTP gateway
52#[derive(CandidType)]
53pub struct HttpResponse {
54    pub status_code: u16,
55    pub headers: Vec<(String, String)>,
56    pub body: Vec<u8>,
57}
58
59/// Handle HTTP requests from the IC HTTP gateway
60pub fn http_request(_req: HttpRequest) -> HttpResponse {
61    let metadata = icarus_metadata();
62
63    // Generate HTML showing available tools
64    let tools_html: String = metadata
65        .tools
66        .iter()
67        .map(|tool| {
68            format!(
69                r#"<div class="tool">
70                <strong>{}</strong> ({})
71                <br><small>{}</small>
72            </div>"#,
73                tool.name,
74                if tool.is_query { "query" } else { "update" },
75                tool.description
76            )
77        })
78        .collect::<Vec<_>>()
79        .join("\n");
80
81    let html = format!(
82        r#"<!DOCTYPE html>
83<html>
84<head>
85    <title>Icarus Canister</title>
86    <style>
87        body {{
88            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
89            max-width: 800px;
90            margin: 50px auto;
91            padding: 20px;
92            background: #f5f5f5;
93        }}
94        .container {{
95            background: white;
96            padding: 30px;
97            border-radius: 10px;
98            box-shadow: 0 2px 10px rgba(0,0,0,0.1);
99        }}
100        h1 {{
101            color: #333;
102            margin-bottom: 10px;
103        }}
104        .info {{
105            background: #e7f3ff;
106            padding: 15px;
107            border-radius: 5px;
108            border-left: 4px solid #2196F3;
109            margin: 20px 0;
110        }}
111        code {{
112            background: #f0f0f0;
113            padding: 2px 5px;
114            border-radius: 3px;
115            font-family: "Courier New", monospace;
116        }}
117        .tool {{
118            margin: 10px 0;
119            padding: 10px;
120            background: #f9f9f9;
121            border-radius: 5px;
122        }}
123    </style>
124</head>
125<body>
126    <div class="container">
127        <h1>🚀 Icarus Canister</h1>
128        <p>This is a standard ICP canister built with Icarus SDK.</p>
129        
130        <div class="info">
131            <strong>Canister ID:</strong> <code>{}</code><br>
132            <strong>Version:</strong> <code>{}</code><br>
133            <strong>Status:</strong> <span style="color: green;">✓ Running</span>
134        </div>
135        
136        <h2>Available Tools</h2>
137        {}
138        
139        <h2>Connect with Claude Desktop</h2>
140        <p>To use this canister with Claude Desktop, run:</p>
141        <code>icarus bridge start --canister-id {}</code>
142        
143        <hr style="margin-top: 40px; border: none; border-top: 1px solid #eee;">
144        <p style="text-align: center; color: #666; font-size: 14px;">
145            Powered by <a href="https://icarus.dev" style="color: #2196F3;">Icarus SDK</a>
146        </p>
147    </div>
148</body>
149</html>"#,
150        ic_cdk::id().to_text(),
151        metadata.version,
152        tools_html,
153        ic_cdk::id().to_text()
154    );
155
156    HttpResponse {
157        status_code: 200,
158        headers: vec![
159            (
160                "Content-Type".to_string(),
161                "text/html; charset=UTF-8".to_string(),
162            ),
163            ("Cache-Control".to_string(), "no-cache".to_string()),
164        ],
165        body: html.into_bytes(),
166    }
167}
168
169/// Get the current owner of the canister
170pub fn get_owner() -> Principal {
171    crate::state::get_owner()
172}