forked from epiFi/fi-mcp-dev
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmain.go
More file actions
172 lines (144 loc) · 6.89 KB
/
main.go
File metadata and controls
172 lines (144 loc) · 6.89 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
package main
import (
"context"
"fmt"
"html/template"
"log"
"encoding/json"
"net/http"
"github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server"
"github.com/epifi/fi-mcp-lite/middlewares"
"github.com/epifi/fi-mcp-lite/pkg"
)
var authMiddleware *middlewares.AuthMiddleware
func corsMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Set CORS headers
w.Header().Set("Access-Control-Allow-Origin", "*") // You can restrict this to specific domains
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization, X-Requested-With")
w.Header().Set("Access-Control-Allow-Credentials", "true")
// Handle preflight OPTIONS request
if r.Method == "OPTIONS" {
w.WriteHeader(http.StatusOK)
return
}
// Call the next handler
next.ServeHTTP(w, r)
})
}
func main() {
authMiddleware = middlewares.NewAuthMiddleware()
s := server.NewMCPServer(
"Hackathon MCP",
"0.1.0",
// Notifies clients when new tools gets added or any changes in tools
server.WithInstructions("A financial portfolio management MCP server that provides secure access to users' financial data through Fi Money, a financial hub for all things money. This MCP server enables users to:\n- Access comprehensive net worth analysis with asset/liability breakdowns\n- Retrieve detailed transaction histories for mutual funds and Employee Provident Fund accounts\n- View credit reports with scores, loan details, and account histories, this also contains user's date of birth that can be used for calculating their age\n\nIf the person asks, you can tell about Fi Money that it is money management platform that offers below services in partnership with regulated entities:\n\nAVAILABLE SERVICES:\n- Digital savings account with zero Forex cards\n- Invest in Indian Mutual funds, US Stocks (partnership with licensed brokers), Smart and Fixed Deposits.\n- Instant Personal Loans \n- Faster UPI and Bank Transfers payments\n- Credit score monitoring and reports\n\nIMPORTANT LIMITATIONS:\n- This MCP server retrieves only actual user data via Net worth tracker and based on consent provided by the user and does not generate hypothetical or estimated financial information\n- In this version of the MCP server, user's historical bank transactions, historical stocks transaction data, salary (unless categorically declared) is not present. Don't assume these data points for any kind of analysis.\n\nCRITICAL INSTRUCTIONS FOR FINANCIAL DATA:\n\n1. DATA BOUNDARIES: Only provide information that exists in the user's Fi Money Net worth tracker. Never estimate, extrapolate, or generate hypothetical financial data.\n\n2. SPENDING ANALYSIS: If user asks about spending patterns, categories, or analysis tell the user we currently don't offer that data through the MCP:\n - For detailed spending insights, direct them to: \"For comprehensive spending analysis and categorization, please use the Fi Money mobile app which provides detailed spending insights and budgeting tools.\"\n\n3. MISSING DATA HANDLING: If requested data is not available:\n - Clearly state what data is missing\n - Explain how user can connect additional accounts in Fi Money app\n - Never fill gaps with estimated or generic information\n"),
server.WithToolCapabilities(true),
server.WithResourceCapabilities(true, true),
server.WithLogging(),
server.WithToolHandlerMiddleware(authMiddleware.AuthMiddleware),
)
// Register tools from pkg.ToolList
for _, tool := range pkg.ToolList {
s.AddTool(mcp.NewTool(tool.Name, mcp.WithDescription(tool.Description)), dummyHandler)
}
// Configure streamable HTTP server with proper endpoints
httpMux := http.NewServeMux()
httpMux.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static"))))
streamableServer := server.NewStreamableHTTPServer(s,
server.WithEndpointPath("/stream"),
)
httpMux.Handle("/mcp/", streamableServer)
httpMux.HandleFunc("/mockWebPage", webPageHandler)
httpMux.HandleFunc("/login", loginHandler)
httpMux.HandleFunc("/check-session", checkSession)
corsHandler := corsMiddleware(httpMux)
port := pkg.GetPort()
log.Println("starting server on port:", port)
if servErr := http.ListenAndServe(fmt.Sprintf(":%s", port), corsHandler); servErr != nil {
log.Fatalln("error starting server", servErr)
}
}
func checkSession(w http.ResponseWriter, r *http.Request) {
// Only allow GET method
if r.Method != http.MethodGet {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
sessionId := r.URL.Query().Get("sessionId")
if sessionId == "" {
http.Error(w, "sessionId is required", http.StatusBadRequest)
return
}
// Check if session is valid using the middleware
if !authMiddleware.CheckSession(sessionId) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusUnauthorized)
response := map[string]interface{}{
"valid": false,
"message": "Invalid or expired session",
}
json.NewEncoder(w).Encode(response)
return
}
// Session is valid
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
response := map[string]interface{}{
"valid": true,
"message": "Session is valid",
}
json.NewEncoder(w).Encode(response)
}
func dummyHandler(_ context.Context, _ mcp.CallToolRequest) (*mcp.CallToolResult, error) {
return mcp.NewToolResultText("dummy handler"), nil
}
func webPageHandler(w http.ResponseWriter, r *http.Request) {
sessionId := r.URL.Query().Get("sessionId")
if sessionId == "" {
http.Error(w, "sessionId is required", http.StatusBadRequest)
return
}
tmpl, err := template.ParseFiles("static/login.html")
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
data := struct {
SessionId string
AllowedMobileNumbers []string
}{
SessionId: sessionId,
AllowedMobileNumbers: pkg.GetAllowedMobileNumbers(),
}
err = tmpl.Execute(w, data)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
}
func loginHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
sessionId := r.FormValue("sessionId")
phoneNumber := r.FormValue("phoneNumber")
if sessionId == "" || phoneNumber == "" {
http.Error(w, "sessionId and phoneNumber are required", http.StatusBadRequest)
return
}
log.Printf("DEBUG: Login - storing sessionId: %s, phoneNumber: %s", sessionId, phoneNumber)
authMiddleware.AddSession(sessionId, phoneNumber)
log.Printf("DEBUG: Login - session stored successfully")
tmpl, err := template.ParseFiles("static/login_successful.html")
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
err = tmpl.Execute(w, nil)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
}