fodda-mcp 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +124 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +434 -0
- package/dist/index.js.map +1 -0
- package/dist/scripts/simulate_gemini.d.ts +2 -0
- package/dist/scripts/simulate_gemini.d.ts.map +1 -0
- package/dist/scripts/simulate_gemini.js +100 -0
- package/dist/scripts/simulate_gemini.js.map +1 -0
- package/dist/scripts/verify_registry.d.ts +2 -0
- package/dist/scripts/verify_registry.d.ts.map +1 -0
- package/dist/scripts/verify_registry.js +77 -0
- package/dist/scripts/verify_registry.js.map +1 -0
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +332 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/tools.d.ts +12 -0
- package/dist/src/tools.d.ts.map +1 -0
- package/dist/src/tools.js +104 -0
- package/dist/src/tools.js.map +1 -0
- package/dist/src/types.d.ts +44 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.js +2 -0
- package/dist/src/types.js.map +1 -0
- package/dist/src/verify.d.ts +2 -0
- package/dist/src/verify.d.ts.map +1 -0
- package/dist/src/verify.js +47 -0
- package/dist/src/verify.js.map +1 -0
- package/dist/src/verify_hmac.d.ts +2 -0
- package/dist/src/verify_hmac.d.ts.map +1 -0
- package/dist/src/verify_hmac.js +106 -0
- package/dist/src/verify_hmac.js.map +1 -0
- package/dist/src/verify_tools_endpoint.d.ts +2 -0
- package/dist/src/verify_tools_endpoint.d.ts.map +1 -0
- package/dist/src/verify_tools_endpoint.js +40 -0
- package/dist/src/verify_tools_endpoint.js.map +1 -0
- package/dist/tools.d.ts +12 -0
- package/dist/tools.d.ts.map +1 -0
- package/dist/tools.js +191 -0
- package/dist/tools.js.map +1 -0
- package/dist/types.d.ts +44 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/verify.d.ts +2 -0
- package/dist/verify.d.ts.map +1 -0
- package/dist/verify.js +47 -0
- package/dist/verify.js.map +1 -0
- package/dist/verify_hmac.d.ts +2 -0
- package/dist/verify_hmac.d.ts.map +1 -0
- package/dist/verify_hmac.js +106 -0
- package/dist/verify_hmac.js.map +1 -0
- package/dist/verify_tools_endpoint.d.ts +2 -0
- package/dist/verify_tools_endpoint.d.ts.map +1 -0
- package/dist/verify_tools_endpoint.js +39 -0
- package/dist/verify_tools_endpoint.js.map +1 -0
- package/package.json +48 -0
- package/server.json +49 -0
package/README.md
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# Fodda MCP Server
|
|
2
|
+
|
|
3
|
+
**Expert-curated knowledge graphs for AI agents** — PSFK Retail, Beauty, Sports and partner datasets via the Model Context Protocol.
|
|
4
|
+
|
|
5
|
+
[](https://registry.modelcontextprotocol.io)
|
|
6
|
+
[](./CHANGELOG.md)
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Quick Start
|
|
11
|
+
|
|
12
|
+
### Claude Code
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
claude mcp add --transport sse fodda https://mcp.fodda.ai/sse \
|
|
16
|
+
--header "Authorization: Bearer YOUR_API_KEY"
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
### Gemini CLI
|
|
20
|
+
|
|
21
|
+
```json
|
|
22
|
+
{
|
|
23
|
+
"tools": [{
|
|
24
|
+
"type": "mcp",
|
|
25
|
+
"name": "fodda",
|
|
26
|
+
"url": "https://mcp.fodda.ai/sse",
|
|
27
|
+
"headers": { "Authorization": "Bearer YOUR_API_KEY" }
|
|
28
|
+
}]
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### Generic SSE Client
|
|
33
|
+
|
|
34
|
+
Connect to `https://mcp.fodda.ai/sse` with an `Authorization: Bearer YOUR_API_KEY` header.
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Available Tools
|
|
39
|
+
|
|
40
|
+
| Tool | Description | Deterministic |
|
|
41
|
+
|------|-------------|:---:|
|
|
42
|
+
| `search_graph` | Hybrid keyword + semantic search on a knowledge graph | ❌ |
|
|
43
|
+
| `get_neighbors` | Traverse from seed nodes to discover related concepts | ✅ |
|
|
44
|
+
| `get_evidence` | Source signals, articles, and provenance for a node | ✅ |
|
|
45
|
+
| `get_node` | Retrieve metadata for a single node by ID | ✅ |
|
|
46
|
+
| `get_label_values` | Discover valid values for a node label/category | ✅ |
|
|
47
|
+
| `psfk_overview` | Structured macro overview across industries and sectors | ❌ |
|
|
48
|
+
|
|
49
|
+
All tools require `userId` and — except `psfk_overview` — a `graphId`.
|
|
50
|
+
|
|
51
|
+
### Discovery Endpoints
|
|
52
|
+
|
|
53
|
+
| Endpoint | Description |
|
|
54
|
+
|----------|-------------|
|
|
55
|
+
| `GET /mcp/tools` | Full tool schemas, versions, and capabilities |
|
|
56
|
+
| `GET /health` | Health check (`{ "status": "ok" }`) |
|
|
57
|
+
| `GET /.well-known/mcp.json` | MCP server auto-discovery manifest |
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## Authentication
|
|
62
|
+
|
|
63
|
+
Pass your Fodda API key as a Bearer token:
|
|
64
|
+
|
|
65
|
+
```
|
|
66
|
+
Authorization: Bearer fk_live_...
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
In MCP request `_meta`:
|
|
70
|
+
```json
|
|
71
|
+
{ "_meta": { "authorization": "Bearer fk_live_..." } }
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## Configuration
|
|
77
|
+
|
|
78
|
+
| Variable | Description | Default |
|
|
79
|
+
|----------|-------------|---------|
|
|
80
|
+
| `PORT` | SSE server port (omit for stdio mode) | — |
|
|
81
|
+
| `FODDA_API_URL` | Upstream API base URL | `https://api.fodda.ai` |
|
|
82
|
+
| `FODDA_MCP_SECRET` | HMAC signing secret | — |
|
|
83
|
+
| `NODE_ENV` | Environment (`development` / `production`) | `production` |
|
|
84
|
+
| `INTERNAL_TEST_KEYS` | Comma-separated keys for simulation mode | — |
|
|
85
|
+
| `RATE_LIMIT_RPM` | Requests per minute per API key | `60` |
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## Build & Run
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
npm install
|
|
93
|
+
npm run build
|
|
94
|
+
|
|
95
|
+
# Stdio mode
|
|
96
|
+
npm start
|
|
97
|
+
|
|
98
|
+
# SSE mode
|
|
99
|
+
PORT=8080 npm start
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Self-Hosting
|
|
103
|
+
|
|
104
|
+
- **Docker**: `docker build -t fodda-mcp . && docker run -p 8080:8080 -e PORT=8080 fodda-mcp`
|
|
105
|
+
- **Cloud Run**: `./deploy_cloud_run.sh`
|
|
106
|
+
- **Kubernetes**: See [`deployment/k8s/`](./deployment/k8s/)
|
|
107
|
+
- **Terraform**: See [`deployment/terraform/`](./deployment/terraform/)
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## MCP Registry
|
|
112
|
+
|
|
113
|
+
This server is published to the [Official MCP Registry](https://registry.modelcontextprotocol.io) as `ai.fodda/mcp-server`.
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
# Verify listing
|
|
117
|
+
curl "https://registry.modelcontextprotocol.io/v0.1/servers?search=ai.fodda/mcp-server"
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## License
|
|
123
|
+
|
|
124
|
+
Proprietary — [fodda.ai](https://www.fodda.ai)
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,434 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import crypto from "crypto";
|
|
5
|
+
import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
6
|
+
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
|
|
7
|
+
import express from "express";
|
|
8
|
+
import axios from "axios";
|
|
9
|
+
import dotenv from "dotenv";
|
|
10
|
+
import { TOOLS, MCP_SERVER_VERSION } from "./tools.js";
|
|
11
|
+
dotenv.config();
|
|
12
|
+
const API_BASE_URL = process.env.FODDA_API_URL || "https://api.fodda.ai";
|
|
13
|
+
const IS_DEV = process.env.NODE_ENV === "development";
|
|
14
|
+
const DUMMY_API_KEY = "dummy-test-key";
|
|
15
|
+
const DUMMY_USER_ID = "dummy-test-user";
|
|
16
|
+
const server = new Server({
|
|
17
|
+
name: "fodda-mcp",
|
|
18
|
+
version: MCP_SERVER_VERSION,
|
|
19
|
+
}, {
|
|
20
|
+
capabilities: {
|
|
21
|
+
tools: {},
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
/**
|
|
25
|
+
* List available tools.
|
|
26
|
+
*/
|
|
27
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
28
|
+
return {
|
|
29
|
+
tools: TOOLS,
|
|
30
|
+
};
|
|
31
|
+
});
|
|
32
|
+
/**
|
|
33
|
+
* Handle tool calls.
|
|
34
|
+
*/
|
|
35
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
36
|
+
const { name, arguments: args } = request.params;
|
|
37
|
+
const graphId = args?.graphId;
|
|
38
|
+
let userId = args?.userId;
|
|
39
|
+
// Validate graphId for tools that require it
|
|
40
|
+
if (name !== "psfk_overview" && !graphId) {
|
|
41
|
+
throw new Error("graphId is required for all Fodda tools except psfk_overview.");
|
|
42
|
+
}
|
|
43
|
+
// Extract Bearer Token from _meta
|
|
44
|
+
const authHeader = request.params._meta?.authorization || "";
|
|
45
|
+
let apiKey = typeof authHeader === 'string' && authHeader.startsWith("Bearer ") ? authHeader.substring(7) : null;
|
|
46
|
+
// Strict Enforcement / Dev Mode Fallback
|
|
47
|
+
if (!apiKey) {
|
|
48
|
+
if (IS_DEV) {
|
|
49
|
+
apiKey = DUMMY_API_KEY;
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
return {
|
|
53
|
+
isError: true,
|
|
54
|
+
content: [{ type: "text", text: "Error: No API Key provided. Please include a Bearer token in the 'authorization' field of the MCP request _meta." }],
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (!userId) {
|
|
59
|
+
if (IS_DEV) {
|
|
60
|
+
userId = DUMMY_USER_ID;
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
return {
|
|
64
|
+
isError: true,
|
|
65
|
+
content: [{ type: "text", text: "Error: No userId provided. This field is required for tracking and billing." }],
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
// Enterprise Readiness: Simulated Gemini Tool Invocation Mode
|
|
70
|
+
// strictly enforced production constraints
|
|
71
|
+
const meta = request.params._meta || {};
|
|
72
|
+
const testMode = meta.test_mode;
|
|
73
|
+
if (testMode) {
|
|
74
|
+
// 1. Strict Value Check
|
|
75
|
+
if (testMode !== "gemini_echo") {
|
|
76
|
+
// Silently ignore unknown test modes or throw error?
|
|
77
|
+
// "Reject unknown test modes" -> Throwing error is safer to signal invalid usage
|
|
78
|
+
return {
|
|
79
|
+
isError: true,
|
|
80
|
+
content: [{ type: "text", text: `Error: Invalid test_mode '${testMode}'. Only 'gemini_echo' is supported.` }]
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
// 2. Production Guardrails
|
|
84
|
+
// Simulation is ignored (treated as normal unauthorized/error if it falls through, or just explicitly blocked here)
|
|
85
|
+
// unless ENV != production OR API key has role internal_testing.
|
|
86
|
+
// We assume 'apiKey' variable holds the extracted key.
|
|
87
|
+
// Since we can't check roles dynamically without a DB call, we'll use a placeholder logic or ENV var for now.
|
|
88
|
+
const isInternalKey = process.env.INTERNAL_TEST_KEYS?.split(',').includes(apiKey || "");
|
|
89
|
+
const isProduction = process.env.NODE_ENV === "production";
|
|
90
|
+
if (isProduction && !isInternalKey) {
|
|
91
|
+
// In production, matching keys are required to use simulation.
|
|
92
|
+
// If not internal key, we pretend simulation doesn't exist or return error?
|
|
93
|
+
// "Be ignored unless..." -> Proceed to normal execution path (which handles auth/tool call normally)
|
|
94
|
+
// But wait, if we proceed, it tries to execute the tool for real.
|
|
95
|
+
// If the intention is to SAFEGUARD against accidental real execution, we should probably BLOCK if test_mode is present but unauthorized.
|
|
96
|
+
// However, "Be ignored" implies falling back to normal behavior OR just doing nothing.
|
|
97
|
+
// Safest Enterprise approach: If you ASK for test_mode but aren't allowed, FAIL. Don't fall back to real execution which might cost money.
|
|
98
|
+
return {
|
|
99
|
+
isError: true,
|
|
100
|
+
content: [{ type: "text", text: "Error: Simulation mode not permitted in production without internal privileges." }]
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
// 3. Structured Logging
|
|
104
|
+
console.error(JSON.stringify({
|
|
105
|
+
event: "mcp.tool_call.simulation",
|
|
106
|
+
simulation_mode: true,
|
|
107
|
+
simulation_type: "gemini_echo",
|
|
108
|
+
tool: name,
|
|
109
|
+
graphId,
|
|
110
|
+
userId
|
|
111
|
+
}));
|
|
112
|
+
// 4. Gemini Echo Response
|
|
113
|
+
return {
|
|
114
|
+
content: [{
|
|
115
|
+
type: "text",
|
|
116
|
+
text: JSON.stringify({
|
|
117
|
+
tool_calls: [{
|
|
118
|
+
name: name,
|
|
119
|
+
arguments: args
|
|
120
|
+
}]
|
|
121
|
+
}, null, 2)
|
|
122
|
+
}]
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
const timestamp = Date.now().toString();
|
|
126
|
+
const headers = {
|
|
127
|
+
"X-API-Key": apiKey,
|
|
128
|
+
"X-User-Id": userId,
|
|
129
|
+
"X-Fodda-Timestamp": timestamp,
|
|
130
|
+
"Content-Type": "application/json",
|
|
131
|
+
};
|
|
132
|
+
// Helper to sign payload
|
|
133
|
+
const signRequest = (body) => {
|
|
134
|
+
const secret = process.env.FODDA_MCP_SECRET;
|
|
135
|
+
if (secret) {
|
|
136
|
+
const payload = timestamp + "." + JSON.stringify(body);
|
|
137
|
+
const signature = crypto.createHmac("sha256", secret).update(payload).digest("hex");
|
|
138
|
+
headers["X-Fodda-Signature"] = signature;
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
const startTime = Date.now();
|
|
142
|
+
try {
|
|
143
|
+
let response;
|
|
144
|
+
switch (name) {
|
|
145
|
+
case "search_graph": {
|
|
146
|
+
const limit = Math.min(Number(args?.limit) || 25, 50); // Defense in Depth: Cap results
|
|
147
|
+
const body = {
|
|
148
|
+
query: args?.query,
|
|
149
|
+
limit: limit,
|
|
150
|
+
use_semantic: args?.use_semantic !== false,
|
|
151
|
+
};
|
|
152
|
+
signRequest(body);
|
|
153
|
+
response = await axios.post(`${API_BASE_URL}/v1/graphs/${graphId}/search`, body, { headers });
|
|
154
|
+
break;
|
|
155
|
+
}
|
|
156
|
+
case "get_neighbors": {
|
|
157
|
+
const depth = Math.min(Number(args?.depth) || 1, 2); // Defense in Depth: Cap traversal depth
|
|
158
|
+
const limit = Math.min(Number(args?.limit) || 50, 50); // Defense in Depth: Cap results
|
|
159
|
+
const body = {
|
|
160
|
+
seed_node_ids: args?.seed_node_ids,
|
|
161
|
+
relationship_types: args?.relationship_types,
|
|
162
|
+
depth: depth,
|
|
163
|
+
limit: limit,
|
|
164
|
+
};
|
|
165
|
+
signRequest(body);
|
|
166
|
+
response = await axios.post(`${API_BASE_URL}/v1/graphs/${graphId}/neighbors`, body, { headers });
|
|
167
|
+
break;
|
|
168
|
+
}
|
|
169
|
+
case "get_evidence": {
|
|
170
|
+
const top_k = Math.min(Number(args?.top_k) || 5, 10); // Defense in Depth: Cap evidence sources
|
|
171
|
+
const body = {
|
|
172
|
+
for_node_id: args?.for_node_id,
|
|
173
|
+
top_k: top_k,
|
|
174
|
+
};
|
|
175
|
+
signRequest(body);
|
|
176
|
+
response = await axios.post(`${API_BASE_URL}/v1/graphs/${graphId}/evidence`, body, { headers });
|
|
177
|
+
break;
|
|
178
|
+
}
|
|
179
|
+
case "get_node": {
|
|
180
|
+
const getNodePath = `/v1/graphs/${graphId}/nodes/${args?.nodeId}`;
|
|
181
|
+
const getNodeSecret = process.env.FODDA_MCP_SECRET;
|
|
182
|
+
if (getNodeSecret) {
|
|
183
|
+
const payload = timestamp + "." + getNodePath;
|
|
184
|
+
const signature = crypto.createHmac("sha256", getNodeSecret).update(payload).digest("hex");
|
|
185
|
+
headers["X-Fodda-Signature"] = signature;
|
|
186
|
+
}
|
|
187
|
+
response = await axios.get(`${API_BASE_URL}${getNodePath}`, { headers });
|
|
188
|
+
break;
|
|
189
|
+
}
|
|
190
|
+
case "get_label_values": {
|
|
191
|
+
const getLabelPath = `/v1/graphs/${graphId}/labels/${args?.label}/values`;
|
|
192
|
+
const getLabelSecret = process.env.FODDA_MCP_SECRET;
|
|
193
|
+
if (getLabelSecret) {
|
|
194
|
+
const payload = timestamp + "." + getLabelPath;
|
|
195
|
+
const signature = crypto.createHmac("sha256", getLabelSecret).update(payload).digest("hex");
|
|
196
|
+
headers["X-Fodda-Signature"] = signature;
|
|
197
|
+
}
|
|
198
|
+
response = await axios.get(`${API_BASE_URL}${getLabelPath}`, { headers });
|
|
199
|
+
break;
|
|
200
|
+
}
|
|
201
|
+
case "psfk_overview": {
|
|
202
|
+
// Logic: At least one of industry or sector is required
|
|
203
|
+
const industry = args?.industry;
|
|
204
|
+
const sector = args?.sector;
|
|
205
|
+
if (!industry && !sector) {
|
|
206
|
+
throw new Error("At least one of 'industry' or 'sector' must be provided for psfk_overview.");
|
|
207
|
+
}
|
|
208
|
+
const body = {
|
|
209
|
+
industry,
|
|
210
|
+
sector,
|
|
211
|
+
region: args?.region,
|
|
212
|
+
timeframe: args?.timeframe,
|
|
213
|
+
};
|
|
214
|
+
signRequest(body);
|
|
215
|
+
response = await axios.post(`${API_BASE_URL}/v1/psfk/overview`, body, { headers });
|
|
216
|
+
break;
|
|
217
|
+
}
|
|
218
|
+
default:
|
|
219
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
220
|
+
}
|
|
221
|
+
const durationMs = Date.now() - startTime;
|
|
222
|
+
const usage = response.data.usage || { total_billable_units: 0 };
|
|
223
|
+
// Structured Audit Log for Enterprise Traceability (Refined for Security Compliance)
|
|
224
|
+
console.error(JSON.stringify({
|
|
225
|
+
event: "mcp.tool_call",
|
|
226
|
+
tool: name,
|
|
227
|
+
graphId,
|
|
228
|
+
userId, // Corresponds to tenant/user identity
|
|
229
|
+
status: response.status,
|
|
230
|
+
durationMs,
|
|
231
|
+
billable_units: usage.total_billable_units,
|
|
232
|
+
deterministic: false,
|
|
233
|
+
layer: "mcp_proxy"
|
|
234
|
+
}));
|
|
235
|
+
return {
|
|
236
|
+
content: [{ type: "text", text: JSON.stringify(response.data, null, 2) }]
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
catch (error) {
|
|
240
|
+
const durationMs = Date.now() - startTime;
|
|
241
|
+
console.error(JSON.stringify({
|
|
242
|
+
event: "mcp.tool_error",
|
|
243
|
+
tool: name,
|
|
244
|
+
graphId,
|
|
245
|
+
userId,
|
|
246
|
+
status: error.response?.status || 500,
|
|
247
|
+
durationMs,
|
|
248
|
+
error: error.response?.data?.message || error.message
|
|
249
|
+
}));
|
|
250
|
+
return {
|
|
251
|
+
isError: true,
|
|
252
|
+
content: [{ type: "text", text: `Error calling Fodda API: ${error.response?.data?.message || error.message}` }],
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
/**
|
|
257
|
+
* Start the server.
|
|
258
|
+
*/
|
|
259
|
+
async function main() {
|
|
260
|
+
if (process.env.PORT) {
|
|
261
|
+
const app = express();
|
|
262
|
+
const port = parseInt(process.env.PORT) || 8080;
|
|
263
|
+
const transports = new Map();
|
|
264
|
+
// Security: HMAC Signature Verification Middleware
|
|
265
|
+
const verifySignature = (req, res, next) => {
|
|
266
|
+
// Skip verification for public/health endpoints
|
|
267
|
+
if ((req.path === "/mcp/tools" || req.path === "/health" || req.path === "/.well-known/mcp.json") && req.method === "GET") {
|
|
268
|
+
return next();
|
|
269
|
+
}
|
|
270
|
+
const signature = req.headers["x-fodda-signature"];
|
|
271
|
+
const secret = process.env.FODDA_MCP_SECRET;
|
|
272
|
+
if (!secret) {
|
|
273
|
+
console.error("CRITICAL: FODDA_MCP_SECRET not set in environment.");
|
|
274
|
+
return res.status(500).json({ error: "Server misconfiguration" });
|
|
275
|
+
}
|
|
276
|
+
if (!signature || typeof signature !== 'string') {
|
|
277
|
+
return res.status(401).json({ error: "Missing or invalid signature" });
|
|
278
|
+
}
|
|
279
|
+
const payload = JSON.stringify(req.body);
|
|
280
|
+
const expectedSignature = crypto
|
|
281
|
+
.createHmac("sha256", secret)
|
|
282
|
+
.update(payload)
|
|
283
|
+
.digest("hex");
|
|
284
|
+
// Timing-safe comparison
|
|
285
|
+
const trusted = Buffer.from(expectedSignature, 'ascii');
|
|
286
|
+
const untrusted = Buffer.from(signature, 'ascii');
|
|
287
|
+
if (trusted.length !== untrusted.length || !crypto.timingSafeEqual(trusted, untrusted)) {
|
|
288
|
+
console.error("❌ Invalid Signature:", signature, "Expected:", expectedSignature);
|
|
289
|
+
return res.status(401).json({ error: "Invalid signature" });
|
|
290
|
+
}
|
|
291
|
+
next();
|
|
292
|
+
};
|
|
293
|
+
// --- Per-Key Rate Limiting ---
|
|
294
|
+
const RATE_LIMIT_WINDOW_MS = 60_000; // 1 minute
|
|
295
|
+
const RATE_LIMIT_MAX = parseInt(process.env.RATE_LIMIT_RPM || "60", 10);
|
|
296
|
+
const rateLimitMap = new Map();
|
|
297
|
+
// Clean up expired entries every 5 minutes
|
|
298
|
+
setInterval(() => {
|
|
299
|
+
const now = Date.now();
|
|
300
|
+
for (const [key, entry] of rateLimitMap) {
|
|
301
|
+
if (now - entry.windowStart > RATE_LIMIT_WINDOW_MS * 2) {
|
|
302
|
+
rateLimitMap.delete(key);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}, 5 * 60_000);
|
|
306
|
+
const rateLimiter = (req, res, next) => {
|
|
307
|
+
// Skip rate limiting for public/health endpoints
|
|
308
|
+
if ((req.path === "/mcp/tools" || req.path === "/health" || req.path === "/.well-known/mcp.json") && req.method === "GET") {
|
|
309
|
+
return next();
|
|
310
|
+
}
|
|
311
|
+
// Extract key from API key header or fallback to IP
|
|
312
|
+
const apiKey = req.headers["x-api-key"] || req.ip || "unknown";
|
|
313
|
+
const now = Date.now();
|
|
314
|
+
const entry = rateLimitMap.get(apiKey);
|
|
315
|
+
if (!entry || now - entry.windowStart > RATE_LIMIT_WINDOW_MS) {
|
|
316
|
+
// New window
|
|
317
|
+
rateLimitMap.set(apiKey, { count: 1, windowStart: now });
|
|
318
|
+
}
|
|
319
|
+
else {
|
|
320
|
+
entry.count++;
|
|
321
|
+
if (entry.count > RATE_LIMIT_MAX) {
|
|
322
|
+
const retryAfter = Math.ceil((entry.windowStart + RATE_LIMIT_WINDOW_MS - now) / 1000);
|
|
323
|
+
console.error(JSON.stringify({
|
|
324
|
+
event: "mcp.rate_limit",
|
|
325
|
+
apiKey: apiKey.substring(0, 8) + "...",
|
|
326
|
+
count: entry.count,
|
|
327
|
+
limit: RATE_LIMIT_MAX,
|
|
328
|
+
}));
|
|
329
|
+
res.set("Retry-After", String(retryAfter));
|
|
330
|
+
return res.status(429).json({
|
|
331
|
+
error: "Rate limit exceeded",
|
|
332
|
+
limit: RATE_LIMIT_MAX,
|
|
333
|
+
window: "60s",
|
|
334
|
+
retry_after: retryAfter,
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
// Set rate limit headers
|
|
339
|
+
const current = rateLimitMap.get(apiKey);
|
|
340
|
+
res.set("X-RateLimit-Limit", String(RATE_LIMIT_MAX));
|
|
341
|
+
res.set("X-RateLimit-Remaining", String(Math.max(0, RATE_LIMIT_MAX - current.count)));
|
|
342
|
+
res.set("X-RateLimit-Reset", String(Math.ceil((current.windowStart + RATE_LIMIT_WINDOW_MS) / 1000)));
|
|
343
|
+
next();
|
|
344
|
+
};
|
|
345
|
+
// Parse JSON bodies with size limit (required for signature verification)
|
|
346
|
+
app.use(express.json({ limit: '1mb' }));
|
|
347
|
+
// Rate limiting (before HMAC to reject early)
|
|
348
|
+
app.use(rateLimiter);
|
|
349
|
+
// HMAC Signature Verification
|
|
350
|
+
app.use(verifySignature);
|
|
351
|
+
app.get("/sse", async (req, res) => {
|
|
352
|
+
const sessionId = crypto.randomUUID();
|
|
353
|
+
console.error(`New SSE connection established (session: ${sessionId})`);
|
|
354
|
+
const transport = new SSEServerTransport("/messages", res);
|
|
355
|
+
transports.set(sessionId, transport);
|
|
356
|
+
// Clean up on disconnect
|
|
357
|
+
res.on("close", () => {
|
|
358
|
+
transports.delete(sessionId);
|
|
359
|
+
console.error(`SSE session ${sessionId} disconnected (${transports.size} active)`);
|
|
360
|
+
});
|
|
361
|
+
await server.connect(transport);
|
|
362
|
+
});
|
|
363
|
+
app.post("/messages", async (req, res) => {
|
|
364
|
+
// Find the appropriate transport by checking the sessionId query param
|
|
365
|
+
const sessionId = req.query.sessionId;
|
|
366
|
+
if (sessionId && transports.has(sessionId)) {
|
|
367
|
+
await transports.get(sessionId).handlePostMessage(req, res);
|
|
368
|
+
}
|
|
369
|
+
else if (transports.size === 1) {
|
|
370
|
+
// Fallback: single-client mode
|
|
371
|
+
const [transport] = transports.values();
|
|
372
|
+
await transport.handlePostMessage(req, res);
|
|
373
|
+
}
|
|
374
|
+
else if (transports.size === 0) {
|
|
375
|
+
res.status(400).send("No SSE connections established");
|
|
376
|
+
}
|
|
377
|
+
else {
|
|
378
|
+
res.status(400).send("Multiple SSE sessions active. Specify sessionId query parameter.");
|
|
379
|
+
}
|
|
380
|
+
});
|
|
381
|
+
// Health check for Cloud Run liveness/readiness probes
|
|
382
|
+
app.get("/health", (req, res) => {
|
|
383
|
+
res.json({ status: "ok", version: MCP_SERVER_VERSION });
|
|
384
|
+
});
|
|
385
|
+
// Enterprise Transparency: Tool Capability Registry
|
|
386
|
+
app.get("/mcp/tools", (req, res) => {
|
|
387
|
+
res.json({
|
|
388
|
+
tools: TOOLS,
|
|
389
|
+
count: TOOLS.length,
|
|
390
|
+
version: MCP_SERVER_VERSION
|
|
391
|
+
});
|
|
392
|
+
});
|
|
393
|
+
// MCP Server Discovery (emerging .well-known standard)
|
|
394
|
+
app.get("/.well-known/mcp.json", (req, res) => {
|
|
395
|
+
res.json({
|
|
396
|
+
name: "ai.fodda/mcp-server",
|
|
397
|
+
title: "Fodda Knowledge Graphs",
|
|
398
|
+
description: "Expert-curated knowledge graphs for AI agents — PSFK Retail, Beauty, Sports and more.",
|
|
399
|
+
version: MCP_SERVER_VERSION,
|
|
400
|
+
transport: {
|
|
401
|
+
type: "sse",
|
|
402
|
+
url: `${req.protocol}://${req.get("host")}/sse`
|
|
403
|
+
},
|
|
404
|
+
tools_endpoint: `${req.protocol}://${req.get("host")}/mcp/tools`,
|
|
405
|
+
health_endpoint: `${req.protocol}://${req.get("host")}/health`
|
|
406
|
+
});
|
|
407
|
+
});
|
|
408
|
+
const httpServer = app.listen(port, () => {
|
|
409
|
+
console.error(`Fodda MCP server running on SSE at http://localhost:${port}/sse`);
|
|
410
|
+
});
|
|
411
|
+
// Graceful shutdown for Cloud Run
|
|
412
|
+
const shutdown = () => {
|
|
413
|
+
console.error("Shutting down gracefully...");
|
|
414
|
+
httpServer.close(() => {
|
|
415
|
+
console.error(`Closed ${transports.size} active SSE connections.`);
|
|
416
|
+
process.exit(0);
|
|
417
|
+
});
|
|
418
|
+
// Force exit after 10s if connections don't close
|
|
419
|
+
setTimeout(() => process.exit(1), 10000);
|
|
420
|
+
};
|
|
421
|
+
process.on("SIGTERM", shutdown);
|
|
422
|
+
process.on("SIGINT", shutdown);
|
|
423
|
+
}
|
|
424
|
+
else {
|
|
425
|
+
const transport = new StdioServerTransport();
|
|
426
|
+
await server.connect(transport);
|
|
427
|
+
console.error("Fodda MCP server running on stdio");
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
main().catch((error) => {
|
|
431
|
+
console.error("Server error:", error);
|
|
432
|
+
process.exit(1);
|
|
433
|
+
});
|
|
434
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"simulate_gemini.d.ts","sourceRoot":"","sources":["../../scripts/simulate_gemini.ts"],"names":[],"mappings":""}
|