@iflow-mcp/apple-rag-mcp 4.6.2
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/.github/workflows/release.yml +62 -0
- package/.releaserc.json +38 -0
- package/CHANGELOG.md +161 -0
- package/README.md +114 -0
- package/README.zh-CN.md +119 -0
- package/apple-rag-mcp_process.log +8 -0
- package/biome.json +59 -0
- package/dist/src/auth/auth-middleware.d.ts +26 -0
- package/dist/src/auth/auth-middleware.d.ts.map +1 -0
- package/dist/src/auth/auth-middleware.js +77 -0
- package/dist/src/auth/auth-middleware.js.map +1 -0
- package/dist/src/auth/token-validator.d.ts +22 -0
- package/dist/src/auth/token-validator.d.ts.map +1 -0
- package/dist/src/auth/token-validator.js +64 -0
- package/dist/src/auth/token-validator.js.map +1 -0
- package/dist/src/mcp/formatters/response-formatter.d.ts +26 -0
- package/dist/src/mcp/formatters/response-formatter.d.ts.map +1 -0
- package/dist/src/mcp/formatters/response-formatter.js +119 -0
- package/dist/src/mcp/formatters/response-formatter.js.map +1 -0
- package/dist/src/mcp/manifest.d.ts +48 -0
- package/dist/src/mcp/manifest.d.ts.map +1 -0
- package/dist/src/mcp/manifest.js +46 -0
- package/dist/src/mcp/manifest.js.map +1 -0
- package/dist/src/mcp/middleware/request-validator.d.ts +48 -0
- package/dist/src/mcp/middleware/request-validator.d.ts.map +1 -0
- package/dist/src/mcp/middleware/request-validator.js +102 -0
- package/dist/src/mcp/middleware/request-validator.js.map +1 -0
- package/dist/src/mcp/protocol-handler.d.ts +70 -0
- package/dist/src/mcp/protocol-handler.d.ts.map +1 -0
- package/dist/src/mcp/protocol-handler.js +285 -0
- package/dist/src/mcp/protocol-handler.js.map +1 -0
- package/dist/src/mcp/tools/fetch-tool.d.ts +18 -0
- package/dist/src/mcp/tools/fetch-tool.d.ts.map +1 -0
- package/dist/src/mcp/tools/fetch-tool.js +76 -0
- package/dist/src/mcp/tools/fetch-tool.js.map +1 -0
- package/dist/src/mcp/tools/search-tool.d.ts +20 -0
- package/dist/src/mcp/tools/search-tool.d.ts.map +1 -0
- package/dist/src/mcp/tools/search-tool.js +86 -0
- package/dist/src/mcp/tools/search-tool.js.map +1 -0
- package/dist/src/services/database.d.ts +37 -0
- package/dist/src/services/database.d.ts.map +1 -0
- package/dist/src/services/database.js +166 -0
- package/dist/src/services/database.js.map +1 -0
- package/dist/src/services/deepinfra-base.d.ts +22 -0
- package/dist/src/services/deepinfra-base.d.ts.map +1 -0
- package/dist/src/services/deepinfra-base.js +55 -0
- package/dist/src/services/deepinfra-base.js.map +1 -0
- package/dist/src/services/embedding.d.ts +44 -0
- package/dist/src/services/embedding.d.ts.map +1 -0
- package/dist/src/services/embedding.js +61 -0
- package/dist/src/services/embedding.js.map +1 -0
- package/dist/src/services/index.d.ts +10 -0
- package/dist/src/services/index.d.ts.map +1 -0
- package/dist/src/services/index.js +52 -0
- package/dist/src/services/index.js.map +1 -0
- package/dist/src/services/ip-authentication.d.ts +12 -0
- package/dist/src/services/ip-authentication.d.ts.map +1 -0
- package/dist/src/services/ip-authentication.js +39 -0
- package/dist/src/services/ip-authentication.js.map +1 -0
- package/dist/src/services/rag.d.ts +35 -0
- package/dist/src/services/rag.d.ts.map +1 -0
- package/dist/src/services/rag.js +106 -0
- package/dist/src/services/rag.js.map +1 -0
- package/dist/src/services/rate-limit.d.ts +27 -0
- package/dist/src/services/rate-limit.d.ts.map +1 -0
- package/dist/src/services/rate-limit.js +91 -0
- package/dist/src/services/rate-limit.js.map +1 -0
- package/dist/src/services/reranker.d.ts +40 -0
- package/dist/src/services/reranker.d.ts.map +1 -0
- package/dist/src/services/reranker.js +97 -0
- package/dist/src/services/reranker.js.map +1 -0
- package/dist/src/services/search-engine.d.ts +89 -0
- package/dist/src/services/search-engine.d.ts.map +1 -0
- package/dist/src/services/search-engine.js +225 -0
- package/dist/src/services/search-engine.js.map +1 -0
- package/dist/src/services/tool-call-logger.d.ts +36 -0
- package/dist/src/services/tool-call-logger.d.ts.map +1 -0
- package/dist/src/services/tool-call-logger.js +34 -0
- package/dist/src/services/tool-call-logger.js.map +1 -0
- package/dist/src/types/env.d.ts +18 -0
- package/dist/src/types/env.d.ts.map +1 -0
- package/dist/src/types/env.js +2 -0
- package/dist/src/types/env.js.map +1 -0
- package/dist/src/types/index.d.ts +145 -0
- package/dist/src/types/index.d.ts.map +1 -0
- package/dist/src/types/index.js +6 -0
- package/dist/src/types/index.js.map +1 -0
- package/dist/src/utils/d1-utils.d.ts +6 -0
- package/dist/src/utils/d1-utils.d.ts.map +1 -0
- package/dist/src/utils/d1-utils.js +29 -0
- package/dist/src/utils/d1-utils.js.map +1 -0
- package/dist/src/utils/logger.d.ts +11 -0
- package/dist/src/utils/logger.d.ts.map +1 -0
- package/dist/src/utils/logger.js +26 -0
- package/dist/src/utils/logger.js.map +1 -0
- package/dist/src/utils/query-cleaner.d.ts +20 -0
- package/dist/src/utils/query-cleaner.d.ts.map +1 -0
- package/dist/src/utils/query-cleaner.js +117 -0
- package/dist/src/utils/query-cleaner.js.map +1 -0
- package/dist/src/utils/request-info.d.ts +18 -0
- package/dist/src/utils/request-info.d.ts.map +1 -0
- package/dist/src/utils/request-info.js +32 -0
- package/dist/src/utils/request-info.js.map +1 -0
- package/dist/src/utils/telegram-notifier.d.ts +4 -0
- package/dist/src/utils/telegram-notifier.d.ts.map +1 -0
- package/dist/src/utils/telegram-notifier.js +33 -0
- package/dist/src/utils/telegram-notifier.js.map +1 -0
- package/dist/src/utils/url-processor.d.ts +15 -0
- package/dist/src/utils/url-processor.d.ts.map +1 -0
- package/dist/src/utils/url-processor.js +54 -0
- package/dist/src/utils/url-processor.js.map +1 -0
- package/dist/src/worker.d.ts +15 -0
- package/dist/src/worker.d.ts.map +1 -0
- package/dist/src/worker.js +136 -0
- package/dist/src/worker.js.map +1 -0
- package/migrations/schema.sql +155 -0
- package/package.json +49 -0
- package/scripts/semantic-release-server-json.js +34 -0
- package/server.json +25 -0
- package/src/auth/auth-middleware.ts +104 -0
- package/src/auth/token-validator.ts +96 -0
- package/src/mcp/formatters/response-formatter.ts +157 -0
- package/src/mcp/manifest.ts +48 -0
- package/src/mcp/middleware/request-validator.ts +135 -0
- package/src/mcp/protocol-handler.ts +412 -0
- package/src/mcp/tools/fetch-tool.ts +146 -0
- package/src/mcp/tools/search-tool.ts +165 -0
- package/src/services/database.ts +202 -0
- package/src/services/deepinfra-base.ts +81 -0
- package/src/services/embedding.ts +96 -0
- package/src/services/index.ts +59 -0
- package/src/services/ip-authentication.ts +62 -0
- package/src/services/rag.ts +158 -0
- package/src/services/rate-limit.ts +141 -0
- package/src/services/reranker.ts +171 -0
- package/src/services/search-engine.ts +333 -0
- package/src/services/tool-call-logger.ts +98 -0
- package/src/types/env.ts +22 -0
- package/src/types/index.ts +189 -0
- package/src/utils/d1-utils.ts +45 -0
- package/src/utils/logger.ts +33 -0
- package/src/utils/query-cleaner.ts +151 -0
- package/src/utils/request-info.ts +47 -0
- package/src/utils/telegram-notifier.ts +47 -0
- package/src/utils/url-processor.ts +65 -0
- package/src/worker.ts +176 -0
- package/tsconfig.json +32 -0
- package/wrangler.toml.example +39 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* URL processing utility for Apple Developer documentation
|
|
3
|
+
* Handles URL validation, normalization, and malformed URL detection
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Validates and normalizes a single URL using elegant malformed URL detection
|
|
7
|
+
* Integrates the sophisticated filtering logic for comprehensive validation
|
|
8
|
+
*/
|
|
9
|
+
export function validateAndNormalizeUrl(url) {
|
|
10
|
+
// Basic validation
|
|
11
|
+
if (!url || typeof url !== "string" || url.trim().length === 0) {
|
|
12
|
+
return {
|
|
13
|
+
isValid: false,
|
|
14
|
+
normalizedUrl: url,
|
|
15
|
+
error: "URL is required",
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
// Apply malformed URL detection - global optimal solution
|
|
19
|
+
const isValidUrl = ![
|
|
20
|
+
url.split("https://").length > 2 || url.split("http://").length > 2, // Duplicate protocol
|
|
21
|
+
url.includes("%ef%bb%bf") || url.includes("\ufeff"), // BOM characters
|
|
22
|
+
url.split("/documentation/").length > 2, // Path duplication
|
|
23
|
+
url.includes("https:/") && !url.startsWith("https://"), // Protocol format error
|
|
24
|
+
url.length > 200, // Abnormal length
|
|
25
|
+
url.split("developer.apple.com").length > 2, // Duplicate domain
|
|
26
|
+
].some(Boolean);
|
|
27
|
+
if (!isValidUrl) {
|
|
28
|
+
return {
|
|
29
|
+
isValid: false,
|
|
30
|
+
normalizedUrl: url,
|
|
31
|
+
error: "URL contains malformed patterns",
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
// Clean and normalize URL - elegant, modern, and concise
|
|
35
|
+
try {
|
|
36
|
+
const parsed = new URL(url);
|
|
37
|
+
// Preserve case sensitivity for Apple Developer paths
|
|
38
|
+
const normalizedPath = parsed.pathname === "/" ? "/" : parsed.pathname.replace(/\/+$/, ""); // Remove trailing slashes except root
|
|
39
|
+
// Remove query parameters and fragments to match pages table format
|
|
40
|
+
const normalizedUrl = `${parsed.protocol.toLowerCase()}//${parsed.hostname.toLowerCase()}${normalizedPath}`;
|
|
41
|
+
return {
|
|
42
|
+
isValid: true,
|
|
43
|
+
normalizedUrl,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return {
|
|
48
|
+
isValid: false,
|
|
49
|
+
normalizedUrl: url,
|
|
50
|
+
error: "Invalid URL format",
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=url-processor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"url-processor.js","sourceRoot":"","sources":["../../../src/utils/url-processor.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAQH;;;GAGG;AACH,MAAM,UAAU,uBAAuB,CAAC,GAAW;IACjD,mBAAmB;IACnB,IAAI,CAAC,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC/D,OAAO;YACL,OAAO,EAAE,KAAK;YACd,aAAa,EAAE,GAAG;YAClB,KAAK,EAAE,iBAAiB;SACzB,CAAC;IACJ,CAAC;IAED,0DAA0D;IAC1D,MAAM,UAAU,GAAG,CAAC;QAClB,GAAG,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,MAAM,GAAG,CAAC,IAAI,GAAG,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,MAAM,GAAG,CAAC,EAAE,qBAAqB;QAC1F,GAAG,CAAC,QAAQ,CAAC,WAAW,CAAC,IAAI,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,iBAAiB;QACtE,GAAG,CAAC,KAAK,CAAC,iBAAiB,CAAC,CAAC,MAAM,GAAG,CAAC,EAAE,mBAAmB;QAC5D,GAAG,CAAC,QAAQ,CAAC,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,wBAAwB;QAChF,GAAG,CAAC,MAAM,GAAG,GAAG,EAAE,kBAAkB;QACpC,GAAG,CAAC,KAAK,CAAC,qBAAqB,CAAC,CAAC,MAAM,GAAG,CAAC,EAAE,mBAAmB;KACjE,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAEhB,IAAI,CAAC,UAAU,EAAE,CAAC;QAChB,OAAO;YACL,OAAO,EAAE,KAAK;YACd,aAAa,EAAE,GAAG;YAClB,KAAK,EAAE,iCAAiC;SACzC,CAAC;IACJ,CAAC;IAED,yDAAyD;IACzD,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;QAC5B,sDAAsD;QACtD,MAAM,cAAc,GAClB,MAAM,CAAC,QAAQ,KAAK,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,CAAC,sCAAsC;QAE7G,oEAAoE;QACpE,MAAM,aAAa,GAAG,GAAG,MAAM,CAAC,QAAQ,CAAC,WAAW,EAAE,KAAK,MAAM,CAAC,QAAQ,CAAC,WAAW,EAAE,GAAG,cAAc,EAAE,CAAC;QAE5G,OAAO;YACL,OAAO,EAAE,IAAI;YACb,aAAa;SACd,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,OAAO;YACL,OAAO,EAAE,KAAK;YACd,aAAa,EAAE,GAAG;YAClB,KAAK,EAAE,oBAAoB;SAC5B,CAAC;IACJ,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Apple RAG MCP Server - Cloudflare Worker Native
|
|
3
|
+
* Ultra-modern, zero-dependency MCP 2025-06-18 compliant server
|
|
4
|
+
* Global optimal solution with maximum performance
|
|
5
|
+
*/
|
|
6
|
+
import type { WorkerEnv } from "./types/index.js";
|
|
7
|
+
/**
|
|
8
|
+
* Cloudflare Worker entry point - Global optimal implementation
|
|
9
|
+
* Handles all MCP protocol requests with edge-optimized performance
|
|
10
|
+
*/
|
|
11
|
+
declare const _default: {
|
|
12
|
+
fetch(request: Request, env: WorkerEnv, ctx: ExecutionContext): Promise<Response>;
|
|
13
|
+
};
|
|
14
|
+
export default _default;
|
|
15
|
+
//# sourceMappingURL=worker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worker.d.ts","sourceRoot":"","sources":["../../src/worker.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAIlD;;;GAGG;;mBAGU,OAAO,OACX,SAAS,OACT,gBAAgB,GACpB,OAAO,CAAC,QAAQ,CAAC;;AALtB,wBA8JE"}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Apple RAG MCP Server - Cloudflare Worker Native
|
|
3
|
+
* Ultra-modern, zero-dependency MCP 2025-06-18 compliant server
|
|
4
|
+
* Global optimal solution with maximum performance
|
|
5
|
+
*/
|
|
6
|
+
import { HEALTH_STATUS, SERVER_MANIFEST } from "./mcp/manifest.js";
|
|
7
|
+
import { MCPProtocolHandler } from "./mcp/protocol-handler.js";
|
|
8
|
+
import { createServices } from "./services/index.js";
|
|
9
|
+
import { logger } from "./utils/logger.js";
|
|
10
|
+
import { configureTelegram } from "./utils/telegram-notifier.js";
|
|
11
|
+
/**
|
|
12
|
+
* Cloudflare Worker entry point - Global optimal implementation
|
|
13
|
+
* Handles all MCP protocol requests with edge-optimized performance
|
|
14
|
+
*/
|
|
15
|
+
export default {
|
|
16
|
+
async fetch(request, env, ctx) {
|
|
17
|
+
const startTime = performance.now();
|
|
18
|
+
// Configure Telegram notification and set execution context for waitUntil
|
|
19
|
+
configureTelegram(env.TELEGRAM_BOT_URL);
|
|
20
|
+
logger.setContext(ctx);
|
|
21
|
+
try {
|
|
22
|
+
const url = new URL(request.url);
|
|
23
|
+
// Health check endpoint - ultra-fast response
|
|
24
|
+
if (request.method === "GET" && url.pathname === "/health") {
|
|
25
|
+
return new Response(JSON.stringify({
|
|
26
|
+
...HEALTH_STATUS,
|
|
27
|
+
timestamp: new Date().toISOString(),
|
|
28
|
+
}), {
|
|
29
|
+
status: 200,
|
|
30
|
+
headers: {
|
|
31
|
+
"Content-Type": "application/json",
|
|
32
|
+
"Cache-Control": "no-cache",
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
// Manifest endpoint - server discovery
|
|
37
|
+
if (request.method === "GET" && url.pathname === "/manifest") {
|
|
38
|
+
return new Response(JSON.stringify(SERVER_MANIFEST), {
|
|
39
|
+
status: 200,
|
|
40
|
+
headers: {
|
|
41
|
+
"Content-Type": "application/json",
|
|
42
|
+
"Cache-Control": "public, max-age=3600",
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
// Reject other GET requests to the MCP endpoint
|
|
47
|
+
if (request.method === "GET" && url.pathname === "/") {
|
|
48
|
+
return new Response("Method Not Allowed", {
|
|
49
|
+
status: 405,
|
|
50
|
+
headers: {
|
|
51
|
+
"Access-Control-Allow-Origin": "*",
|
|
52
|
+
Allow: "POST, OPTIONS",
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
// Handle POST /manifest requests (VPS compatibility)
|
|
57
|
+
if (request.method === "POST" && url.pathname === "/manifest") {
|
|
58
|
+
try {
|
|
59
|
+
const body = await request.json();
|
|
60
|
+
// Empty body → return manifest (common client behavior)
|
|
61
|
+
if (!body || Object.keys(body).length === 0) {
|
|
62
|
+
return new Response(JSON.stringify(SERVER_MANIFEST), {
|
|
63
|
+
status: 200,
|
|
64
|
+
headers: { "Content-Type": "application/json" },
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
// MCP request to wrong endpoint → redirect to correct endpoint
|
|
68
|
+
if (body
|
|
69
|
+
.jsonrpc === "2.0" &&
|
|
70
|
+
body.method) {
|
|
71
|
+
return new Response(JSON.stringify({
|
|
72
|
+
error: "Endpoint redirect",
|
|
73
|
+
message: "MCP protocol requests should be sent to /",
|
|
74
|
+
redirect: "/",
|
|
75
|
+
}), {
|
|
76
|
+
status: 307,
|
|
77
|
+
headers: {
|
|
78
|
+
"Content-Type": "application/json",
|
|
79
|
+
Location: "/",
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
// Any other POST data → helpful error
|
|
84
|
+
return new Response(JSON.stringify({
|
|
85
|
+
error: "Invalid manifest request",
|
|
86
|
+
message: "Use GET /manifest for server discovery or POST / for MCP communication",
|
|
87
|
+
endpoints: {
|
|
88
|
+
manifest: "GET /manifest",
|
|
89
|
+
mcp: "POST /",
|
|
90
|
+
},
|
|
91
|
+
}), {
|
|
92
|
+
status: 400,
|
|
93
|
+
headers: { "Content-Type": "application/json" },
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
catch (_error) {
|
|
97
|
+
return new Response(JSON.stringify({
|
|
98
|
+
error: "Invalid JSON",
|
|
99
|
+
message: "Request body must be valid JSON",
|
|
100
|
+
}), {
|
|
101
|
+
status: 400,
|
|
102
|
+
headers: { "Content-Type": "application/json" },
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
// Initialize services with Worker environment
|
|
107
|
+
const services = await createServices(env);
|
|
108
|
+
// Authenticate request using auth service
|
|
109
|
+
const authContext = await services.auth.optionalAuth(request);
|
|
110
|
+
// Create MCP protocol handler
|
|
111
|
+
const handler = new MCPProtocolHandler(services);
|
|
112
|
+
// Handle MCP request
|
|
113
|
+
const response = await handler.handleRequest(request, authContext);
|
|
114
|
+
return response;
|
|
115
|
+
}
|
|
116
|
+
catch (error) {
|
|
117
|
+
const duration = performance.now() - startTime;
|
|
118
|
+
const errorUrl = new URL(request.url);
|
|
119
|
+
logger.error(`Worker error for ${request.method} ${errorUrl.pathname} (duration: ${Math.round(duration)}ms): ${error instanceof Error ? error.message : String(error)}`);
|
|
120
|
+
return new Response(JSON.stringify({
|
|
121
|
+
jsonrpc: "2.0",
|
|
122
|
+
error: {
|
|
123
|
+
code: -32603,
|
|
124
|
+
message: "Internal server error",
|
|
125
|
+
},
|
|
126
|
+
}), {
|
|
127
|
+
status: 500,
|
|
128
|
+
headers: {
|
|
129
|
+
"Content-Type": "application/json",
|
|
130
|
+
"Cache-Control": "no-cache",
|
|
131
|
+
},
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
//# sourceMappingURL=worker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worker.js","sourceRoot":"","sources":["../../src/worker.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
-- Apple RAG Database Schema
|
|
2
|
+
-- Cloudflare D1 (SQLite)
|
|
3
|
+
-- Last updated: 2025-11-26
|
|
4
|
+
|
|
5
|
+
-- ============================================================
|
|
6
|
+
-- USERS TABLE
|
|
7
|
+
-- Primary user authentication and profile table
|
|
8
|
+
-- ============================================================
|
|
9
|
+
CREATE TABLE IF NOT EXISTS users (
|
|
10
|
+
id TEXT PRIMARY KEY,
|
|
11
|
+
email TEXT UNIQUE NOT NULL,
|
|
12
|
+
password_hash TEXT,
|
|
13
|
+
name TEXT,
|
|
14
|
+
avatar TEXT,
|
|
15
|
+
provider TEXT NOT NULL DEFAULT 'email',
|
|
16
|
+
provider_id TEXT,
|
|
17
|
+
oauth_provider TEXT,
|
|
18
|
+
oauth_id TEXT,
|
|
19
|
+
stripe_customer_id TEXT,
|
|
20
|
+
reset_token TEXT,
|
|
21
|
+
reset_token_expires_at TEXT,
|
|
22
|
+
last_login TEXT,
|
|
23
|
+
created_at TEXT NOT NULL,
|
|
24
|
+
updated_at TEXT NOT NULL
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
CREATE INDEX IF NOT EXISTS idx_users_email ON users(email);
|
|
28
|
+
CREATE INDEX IF NOT EXISTS idx_users_provider ON users(provider, provider_id);
|
|
29
|
+
CREATE INDEX IF NOT EXISTS idx_users_stripe ON users(stripe_customer_id);
|
|
30
|
+
|
|
31
|
+
-- ============================================================
|
|
32
|
+
-- MCP TOKENS TABLE
|
|
33
|
+
-- MCP token management for API access control
|
|
34
|
+
-- ============================================================
|
|
35
|
+
CREATE TABLE IF NOT EXISTS mcp_tokens (
|
|
36
|
+
id TEXT PRIMARY KEY,
|
|
37
|
+
user_id TEXT NOT NULL,
|
|
38
|
+
name TEXT NOT NULL,
|
|
39
|
+
mcp_token TEXT NOT NULL,
|
|
40
|
+
last_used_at TEXT,
|
|
41
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
42
|
+
updated_at TEXT DEFAULT (datetime('now')),
|
|
43
|
+
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
CREATE INDEX IF NOT EXISTS idx_mcp_tokens_user_id ON mcp_tokens(user_id);
|
|
47
|
+
CREATE INDEX IF NOT EXISTS idx_mcp_tokens_token ON mcp_tokens(mcp_token);
|
|
48
|
+
|
|
49
|
+
-- ============================================================
|
|
50
|
+
-- SEARCH LOGS TABLE
|
|
51
|
+
-- Tracks search operations for usage monitoring and rate limiting
|
|
52
|
+
-- ============================================================
|
|
53
|
+
CREATE TABLE IF NOT EXISTS search_logs (
|
|
54
|
+
id TEXT DEFAULT (lower(hex(randomblob(16)))) PRIMARY KEY,
|
|
55
|
+
user_id TEXT NOT NULL,
|
|
56
|
+
mcp_token TEXT,
|
|
57
|
+
requested_query TEXT NOT NULL,
|
|
58
|
+
actual_query TEXT NOT NULL,
|
|
59
|
+
result_count INTEGER DEFAULT 0,
|
|
60
|
+
response_time_ms INTEGER,
|
|
61
|
+
status_code INTEGER,
|
|
62
|
+
error_code TEXT,
|
|
63
|
+
ip_address TEXT,
|
|
64
|
+
country_code TEXT,
|
|
65
|
+
created_at TEXT DEFAULT (datetime('now'))
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
CREATE INDEX IF NOT EXISTS idx_search_logs_user_id ON search_logs(user_id);
|
|
69
|
+
CREATE INDEX IF NOT EXISTS idx_search_logs_created_at ON search_logs(created_at);
|
|
70
|
+
CREATE INDEX IF NOT EXISTS idx_search_logs_country ON search_logs(country_code);
|
|
71
|
+
|
|
72
|
+
-- ============================================================
|
|
73
|
+
-- FETCH LOGS TABLE
|
|
74
|
+
-- Tracks fetch operations for usage monitoring and rate limiting
|
|
75
|
+
-- ============================================================
|
|
76
|
+
CREATE TABLE IF NOT EXISTS fetch_logs (
|
|
77
|
+
id TEXT DEFAULT (lower(hex(randomblob(16)))) PRIMARY KEY,
|
|
78
|
+
user_id TEXT NOT NULL,
|
|
79
|
+
mcp_token TEXT,
|
|
80
|
+
requested_url TEXT NOT NULL,
|
|
81
|
+
actual_url TEXT,
|
|
82
|
+
page_id TEXT,
|
|
83
|
+
response_time_ms INTEGER,
|
|
84
|
+
status_code INTEGER,
|
|
85
|
+
error_code TEXT,
|
|
86
|
+
ip_address TEXT,
|
|
87
|
+
country_code TEXT,
|
|
88
|
+
created_at TEXT DEFAULT (datetime('now'))
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
CREATE INDEX IF NOT EXISTS idx_fetch_logs_user_id ON fetch_logs(user_id);
|
|
92
|
+
CREATE INDEX IF NOT EXISTS idx_fetch_logs_created_at ON fetch_logs(created_at);
|
|
93
|
+
CREATE INDEX IF NOT EXISTS idx_fetch_logs_country ON fetch_logs(country_code);
|
|
94
|
+
|
|
95
|
+
-- ============================================================
|
|
96
|
+
-- USER SUBSCRIPTIONS TABLE
|
|
97
|
+
-- Stripe integration and subscription lifecycle tracking
|
|
98
|
+
-- ============================================================
|
|
99
|
+
CREATE TABLE IF NOT EXISTS user_subscriptions (
|
|
100
|
+
user_id TEXT PRIMARY KEY,
|
|
101
|
+
stripe_customer_id TEXT,
|
|
102
|
+
stripe_subscription_id TEXT,
|
|
103
|
+
plan_type TEXT DEFAULT 'hobby',
|
|
104
|
+
status TEXT DEFAULT 'active',
|
|
105
|
+
current_period_start TEXT,
|
|
106
|
+
current_period_end TEXT,
|
|
107
|
+
cancel_at_period_end BOOLEAN DEFAULT FALSE,
|
|
108
|
+
price REAL DEFAULT 0,
|
|
109
|
+
billing_interval TEXT DEFAULT 'month',
|
|
110
|
+
stripe_price_id TEXT,
|
|
111
|
+
updated_at TEXT DEFAULT (datetime('now')),
|
|
112
|
+
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
CREATE INDEX IF NOT EXISTS idx_user_subscriptions_stripe_price_id ON user_subscriptions(stripe_price_id);
|
|
116
|
+
|
|
117
|
+
-- ============================================================
|
|
118
|
+
-- USER AUTHORIZED IPS TABLE
|
|
119
|
+
-- IP-based authentication and access control
|
|
120
|
+
-- ============================================================
|
|
121
|
+
CREATE TABLE IF NOT EXISTS user_authorized_ips (
|
|
122
|
+
id TEXT DEFAULT (lower(hex(randomblob(16)))) PRIMARY KEY,
|
|
123
|
+
user_id TEXT NOT NULL,
|
|
124
|
+
ip_address TEXT NOT NULL,
|
|
125
|
+
name TEXT NOT NULL,
|
|
126
|
+
last_used_at TEXT,
|
|
127
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
128
|
+
updated_at TEXT DEFAULT (datetime('now')),
|
|
129
|
+
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
CREATE INDEX IF NOT EXISTS idx_user_authorized_ips_lookup ON user_authorized_ips(ip_address, user_id);
|
|
133
|
+
CREATE INDEX IF NOT EXISTS idx_user_authorized_ips_user_id ON user_authorized_ips(user_id);
|
|
134
|
+
CREATE INDEX IF NOT EXISTS idx_user_authorized_ips_last_used ON user_authorized_ips(last_used_at);
|
|
135
|
+
|
|
136
|
+
-- ============================================================
|
|
137
|
+
-- CONTACT MESSAGES TABLE
|
|
138
|
+
-- Contact form submissions with bidirectional messaging support
|
|
139
|
+
-- ============================================================
|
|
140
|
+
CREATE TABLE IF NOT EXISTS contact_messages (
|
|
141
|
+
id TEXT DEFAULT (lower(hex(randomblob(16)))) PRIMARY KEY,
|
|
142
|
+
user_id TEXT,
|
|
143
|
+
email TEXT,
|
|
144
|
+
message TEXT NOT NULL,
|
|
145
|
+
ip_address TEXT,
|
|
146
|
+
admin_reply TEXT,
|
|
147
|
+
replied_at TEXT,
|
|
148
|
+
user_read_at TEXT,
|
|
149
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
150
|
+
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE SET NULL
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
CREATE INDEX IF NOT EXISTS idx_contact_messages_created_at ON contact_messages(created_at DESC);
|
|
154
|
+
CREATE INDEX IF NOT EXISTS idx_contact_messages_replied_at ON contact_messages(replied_at DESC);
|
|
155
|
+
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@iflow-mcp/apple-rag-mcp",
|
|
3
|
+
"version": "4.6.2",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "dist/src/worker.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"apple-rag-mcp": "./dist/src/worker.js"
|
|
8
|
+
},
|
|
9
|
+
"mcpName": "io.github.BingoWon/apple-rag-mcp",
|
|
10
|
+
"scripts": {
|
|
11
|
+
"deploy:dev": "wrangler deploy --env development",
|
|
12
|
+
"deploy:prod": "wrangler deploy --env production",
|
|
13
|
+
"build": "tsc",
|
|
14
|
+
"format": "biome check --write --unsafe ."
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"mcp",
|
|
18
|
+
"mcp-2025-06-18",
|
|
19
|
+
"streamable-http",
|
|
20
|
+
"oauth-2.1",
|
|
21
|
+
"authorization",
|
|
22
|
+
"rag",
|
|
23
|
+
"apple",
|
|
24
|
+
"documentation",
|
|
25
|
+
"ai",
|
|
26
|
+
"vector-search",
|
|
27
|
+
"semantic-search"
|
|
28
|
+
],
|
|
29
|
+
"author": "Apple RAG Team",
|
|
30
|
+
"license": "MIT",
|
|
31
|
+
"description": "Modern MCP 2025-06-18 compliant server with OAuth 2.1 Authorization for Apple Developer Documentation with Semantic Search for RAG, Keyword Search, and Hybrid Search - Production ready with vector similarity and semantic AI reranking",
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"@types/node": "^20.19.11",
|
|
34
|
+
"postgres": "^3.4.7"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@biomejs/biome": "^2.2.2",
|
|
38
|
+
"@cloudflare/workers-types": "^4.20250831.0",
|
|
39
|
+
"@semantic-release/changelog": "^6.0.3",
|
|
40
|
+
"@semantic-release/git": "^10.0.1",
|
|
41
|
+
"semantic-release": "^24.2.0",
|
|
42
|
+
"typescript": "^5.9.2",
|
|
43
|
+
"wrangler": "^4.33.1"
|
|
44
|
+
},
|
|
45
|
+
"engines": {
|
|
46
|
+
"node": ">=18.0.0"
|
|
47
|
+
},
|
|
48
|
+
"packageManager": "pnpm@10.7.0+sha512.6b865ad4b62a1d9842b61d674a393903b871d9244954f652b8842c2b553c72176b278f64c463e52d40fff8aba385c235c8c9ecf5cc7de4fd78b8bb6d49633ab6"
|
|
49
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom Semantic-Release plugin to update server.json version
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import fs from "node:fs";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
|
|
8
|
+
function updateServerJson(_pluginConfig, context) {
|
|
9
|
+
const { nextRelease, logger } = context;
|
|
10
|
+
const serverJsonPath = path.resolve(process.cwd(), "server.json");
|
|
11
|
+
|
|
12
|
+
if (!fs.existsSync(serverJsonPath)) {
|
|
13
|
+
logger.log("server.json not found, skipping version update");
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
try {
|
|
18
|
+
const serverJson = JSON.parse(fs.readFileSync(serverJsonPath, "utf8"));
|
|
19
|
+
serverJson.version = nextRelease.version;
|
|
20
|
+
|
|
21
|
+
fs.writeFileSync(
|
|
22
|
+
serverJsonPath,
|
|
23
|
+
`${JSON.stringify(serverJson, null, 2)}\n`
|
|
24
|
+
);
|
|
25
|
+
logger.log(`Updated server.json version to ${nextRelease.version}`);
|
|
26
|
+
} catch (error) {
|
|
27
|
+
logger.error("Failed to update server.json:", error);
|
|
28
|
+
throw error;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export default {
|
|
33
|
+
prepare: updateServerJson,
|
|
34
|
+
};
|
package/server.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-07-09/server.schema.json",
|
|
3
|
+
"name": "com.apple-rag/mcp-server",
|
|
4
|
+
"description": "Apple Developer Documentation with Semantic Search, RAG, and AI reranking for MCP clients",
|
|
5
|
+
"status": "active",
|
|
6
|
+
"repository": {
|
|
7
|
+
"url": "https://github.com/BingoWon/apple-rag-mcp",
|
|
8
|
+
"source": "github"
|
|
9
|
+
},
|
|
10
|
+
"version": "4.6.2",
|
|
11
|
+
"remotes": [
|
|
12
|
+
{
|
|
13
|
+
"type": "streamable-http",
|
|
14
|
+
"url": "https://mcp.apple-rag.com",
|
|
15
|
+
"headers": [
|
|
16
|
+
{
|
|
17
|
+
"name": "Authorization",
|
|
18
|
+
"description": "MCP Token for authentication (optional - free tier available without token)",
|
|
19
|
+
"is_required": false,
|
|
20
|
+
"is_secret": true
|
|
21
|
+
}
|
|
22
|
+
]
|
|
23
|
+
}
|
|
24
|
+
]
|
|
25
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Simple MCP Authentication Middleware
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { IPAuthenticationService } from "../services/ip-authentication.js";
|
|
6
|
+
import type { AuthContext } from "../types/index.js";
|
|
7
|
+
import { logger } from "../utils/logger.js";
|
|
8
|
+
import { TokenValidator, type UserTokenData } from "./token-validator.js";
|
|
9
|
+
|
|
10
|
+
export class AuthMiddleware {
|
|
11
|
+
private readonly tokenValidator: TokenValidator;
|
|
12
|
+
private readonly ipAuthService: IPAuthenticationService;
|
|
13
|
+
|
|
14
|
+
constructor(d1: D1Database) {
|
|
15
|
+
this.tokenValidator = new TokenValidator(d1);
|
|
16
|
+
this.ipAuthService = new IPAuthenticationService(d1);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Extract Bearer token from Authorization header
|
|
21
|
+
* Handles multiple "Bearer" prefixes and validates token format
|
|
22
|
+
*/
|
|
23
|
+
private extractBearerToken(authHeader?: string): string | null {
|
|
24
|
+
if (!authHeader) return null;
|
|
25
|
+
|
|
26
|
+
// Remove all "Bearer " prefixes (case-insensitive, supports multiple)
|
|
27
|
+
const token = authHeader.replace(/^(Bearer\s+)+/gi, "").trim();
|
|
28
|
+
|
|
29
|
+
// Only return if it matches valid token format
|
|
30
|
+
return /^at_[a-f0-9]{32}$/.test(token) ? token : null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Optional authentication middleware
|
|
35
|
+
* Validates token if present, or checks IP-based authentication, allows access without either
|
|
36
|
+
*/
|
|
37
|
+
async optionalAuth(request: Request): Promise<AuthContext> {
|
|
38
|
+
const authHeader = request.headers.get("authorization");
|
|
39
|
+
const token = this.extractBearerToken(authHeader || undefined);
|
|
40
|
+
const clientIP = this.getClientIP(request);
|
|
41
|
+
|
|
42
|
+
// Try token authentication first
|
|
43
|
+
if (token) {
|
|
44
|
+
const validation = await this.tokenValidator.validateToken(token);
|
|
45
|
+
|
|
46
|
+
if (validation.valid) {
|
|
47
|
+
logger.info(
|
|
48
|
+
`Token authentication successful for userId: ${validation.userData?.userId}`
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
isAuthenticated: true,
|
|
53
|
+
userId: validation.userData?.userId,
|
|
54
|
+
email: validation.userData?.email,
|
|
55
|
+
token: token,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
logger.error(
|
|
60
|
+
`Token validation failed. Raw header: "${authHeader}", Extracted token: "${token}", IP: ${clientIP}, Error: ${validation.error}`
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Try IP-based authentication
|
|
65
|
+
const ipAuthResult =
|
|
66
|
+
await this.ipAuthService.checkIPAuthentication(clientIP);
|
|
67
|
+
if (ipAuthResult) {
|
|
68
|
+
logger.info(
|
|
69
|
+
`IP-based authentication successful for userId: ${ipAuthResult.userId} from IP: ${clientIP}`
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
isAuthenticated: true,
|
|
74
|
+
userId: ipAuthResult.userId,
|
|
75
|
+
email: ipAuthResult.email,
|
|
76
|
+
token: "ip-based",
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// No authentication method succeeded
|
|
81
|
+
logger.info(
|
|
82
|
+
`No authentication provided - allowing unauthenticated access from IP: ${clientIP} (hasToken: ${!!token})`
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
return { isAuthenticated: false };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async getUserData(userId: string): Promise<UserTokenData> {
|
|
89
|
+
return this.tokenValidator.getUserDataById(userId);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Get client IP address from request (Worker optimized)
|
|
94
|
+
*/
|
|
95
|
+
private getClientIP(request: Request): string {
|
|
96
|
+
// Cloudflare provides client IP in CF-Connecting-IP header
|
|
97
|
+
return (
|
|
98
|
+
request.headers.get("CF-Connecting-IP") ||
|
|
99
|
+
request.headers.get("X-Forwarded-For") ||
|
|
100
|
+
request.headers.get("X-Real-IP") ||
|
|
101
|
+
"unknown"
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
}
|