@amplify-studio/open-mcp 0.8.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 IS
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,244 @@
1
+ # Open MCP
2
+
3
+ > An open-source MCP (Model Context Protocol) server solution for AI agent integration.
4
+
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
+
7
+ ## Overview
8
+
9
+ Open MCP is a comprehensive server solution that enables AI assistants (like Claude) to interact with external services through the Model Context Protocol. Currently focused on web search and content extraction, with plans to expand into a full-featured AI agent platform.
10
+
11
+ ## Current Features
12
+
13
+ ### 🌐 Web Search
14
+ - General web queries with pagination
15
+ - Time-based filtering (day, month, year)
16
+ - Language selection
17
+ - Safe search levels
18
+
19
+ ### 📄 URL Content Reading
20
+ - Extract web page content as text/markdown
21
+ - Intelligent caching with TTL
22
+ - Section extraction and pagination options
23
+
24
+ ## Roadmap
25
+
26
+ ### Phase 1: Current (Search & Content)
27
+ - ✅ Web search via Gateway API
28
+ - ✅ URL content reading
29
+ - ✅ Intelligent caching
30
+
31
+ ### Phase 2: Knowledge Base (RAG)
32
+ - 🔄 Document indexing
33
+ - 🔄 Semantic search
34
+ - 🔄 Vector storage integration
35
+
36
+ ### Phase 3: Data Integration
37
+ - ⏳ Database connectors
38
+ - ⏳ API integrations
39
+ - ⏳ File system access
40
+
41
+ ### Phase 4: Agent Framework
42
+ - ⏳ Tool composition
43
+ - ⏳ Workflow orchestration
44
+ - ⏳ Multi-agent coordination
45
+
46
+ ## Architecture
47
+
48
+ ```
49
+ ┌─────────────────────────────────────────────────────────────┐
50
+ │ Open MCP │
51
+ ├─────────────────────────────────────────────────────────────┤
52
+ │ │
53
+ │ ┌────────────┐ ┌────────────┐ ┌────────────┐ │
54
+ │ │ Search │ │ Content │ │ Future │ │
55
+ │ │ Module │ │ Reader │ │ Modules │ │
56
+ │ └─────┬──────┘ └─────┬──────┘ └─────┬──────┘ │
57
+ │ │ │ │ │
58
+ │ └────────────────┴────────────────┘ │
59
+ │ │ │
60
+ │ ┌─────▼─────┐ │
61
+ │ │ Core │ │
62
+ │ │ Layer │ │
63
+ │ └─────┬─────┘ │
64
+ │ │ │
65
+ │ ┌──────────────────────┼──────────────────────┐ │
66
+ │ │ │ │ │
67
+ │ ▼▼ ▼▼ ▼▼ │
68
+ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
69
+ │ Cache │ │ Gateway │ │ Plugins │ │
70
+ └─────────┘ └─────────┘ └─────────┘ │
71
+ │ │
72
+ └─────────────────────────────────────────────────────────────┘
73
+
74
+
75
+ ┌─────────────────┐
76
+ │ Gateway API │
77
+ │ (External) │
78
+ └─────────────────┘
79
+ ```
80
+
81
+ ## Installation
82
+
83
+ ### Option 1: From npm (Recommended for Users)
84
+
85
+ Install from npm registry:
86
+
87
+ **Using Claude CLI:**
88
+ ```bash
89
+ claude mcp add @amplify-studio/open-mcp
90
+ ```
91
+
92
+ **Or with npx:**
93
+ ```json
94
+ {
95
+ "mcpServers": {
96
+ "open-mcp": {
97
+ "command": "npx",
98
+ "args": ["-y", "@amplify-studio/open-mcp"]
99
+ }
100
+ }
101
+ }
102
+ ```
103
+
104
+ ### Option 2: From GitHub
105
+
106
+ No installation needed - runs directly from GitHub:
107
+
108
+ **Claude Desktop Config**:
109
+ ```json
110
+ {
111
+ "mcpServers": {
112
+ "open-mcp": {
113
+ "command": "npx",
114
+ "args": ["-y", "github:amplify-studio/open-mcp"]
115
+ }
116
+ }
117
+ }
118
+ ```
119
+
120
+ ### Option 3: Custom Gateway
121
+
122
+ If you have your own gateway instance:
123
+
124
+ ```json
125
+ {
126
+ "mcpServers": {
127
+ "open-mcp": {
128
+ "command": "npx",
129
+ "args": ["-y", "github:amplify-studio/open-mcp"],
130
+ "env": {
131
+ "GATEWAY_URL": "http://your-gateway.com:80"
132
+ }
133
+ }
134
+ }
135
+ }
136
+ ```
137
+
138
+ ### Option 4: Local Development (For Developers)
139
+
140
+ ```bash
141
+ # Clone the repository
142
+ git clone https://github.com/amplify-studio/open-mcp.git
143
+ cd open-mcp
144
+
145
+ # Install dependencies
146
+ npm install
147
+
148
+ # Build the project
149
+ npm run build
150
+
151
+ # Run directly
152
+ node dist/index.js
153
+ ```
154
+
155
+ **Claude Desktop Config** (local development):
156
+ ```json
157
+ {
158
+ "mcpServers": {
159
+ "open-mcp": {
160
+ "command": "node",
161
+ "args": ["/path/to/open-mcp/dist/index.js"]
162
+ }
163
+ }
164
+ }
165
+ ```
166
+
167
+ ### Option 5: Test with MCP Inspector
168
+
169
+ ```bash
170
+ cd open-mcp
171
+ npm run inspector
172
+ # Visit http://localhost:6274 to test
173
+ ```
174
+
175
+ ### Option 6: Global Install (Advanced)
176
+
177
+ ```bash
178
+ cd open-mcp
179
+ npm link
180
+ # Then use: open-mcp
181
+ ```
182
+
183
+ ## Configuration
184
+
185
+ | Variable | Required | Default | Description |
186
+ |----------|----------|---------|-------------|
187
+ | `GATEWAY_URL` | No | `http://115.190.91.253:80` | Gateway API base URL |
188
+ | `AUTH_USERNAME` | No | - | Basic auth username |
189
+ | `AUTH_PASSWORD` | No | - | Basic auth password |
190
+ | `HTTP_PROXY` | No | - | Proxy URL for HTTP requests |
191
+ | `HTTPS_PROXY` | No | - | Proxy URL for HTTPS requests |
192
+ | `NO_PROXY` | No | - | Comma-separated bypass list |
193
+ | `MCP_HTTP_PORT` | No | - | Enable HTTP transport on specified port |
194
+
195
+ ## Gateway API
196
+
197
+ The server connects to a Gateway API that provides:
198
+
199
+ | API | Method | Endpoint | Description |
200
+ |-----|--------|----------|-------------|
201
+ | Search | GET | `/api/search/` | Web search |
202
+ | Read | GET | `/api/read/{url}` | Extract web content |
203
+ | Health | GET | `/health` | Health check |
204
+ | Status | GET | `/api/status` | Service status |
205
+
206
+ ## Development
207
+
208
+ ```bash
209
+ # Install dependencies
210
+ npm install
211
+
212
+ # Development mode with file watching
213
+ npm run watch
214
+
215
+ # Run tests
216
+ npm test
217
+
218
+ # Test with MCP Inspector
219
+ npm run inspector
220
+
221
+ # Build for production
222
+ npm run build
223
+ ```
224
+
225
+ ## Contributing
226
+
227
+ We're building an open-source community for AI agent infrastructure. Contributions welcome!
228
+
229
+ - Fork the repository
230
+ - Create a feature branch
231
+ - Make your changes
232
+ - Submit a pull request
233
+
234
+ ## License
235
+
236
+ MIT
237
+
238
+ ## Credits
239
+
240
+ Based on [mcp-searxng](https://github.com/ihor-sokoliuk/mcp-searxng) by Ihor Sokoliuk
241
+
242
+ ---
243
+
244
+ **Made with ❤️ by [Amplify Studio](https://github.com/amplify-studio)**
@@ -0,0 +1,26 @@
1
+ interface CacheEntry {
2
+ htmlContent: string;
3
+ markdownContent: string;
4
+ timestamp: number;
5
+ }
6
+ declare class SimpleCache {
7
+ private cache;
8
+ private readonly ttlMs;
9
+ private cleanupInterval;
10
+ constructor(ttlMs?: number);
11
+ private startCleanup;
12
+ private cleanupExpired;
13
+ get(url: string): CacheEntry | null;
14
+ set(url: string, htmlContent: string, markdownContent: string): void;
15
+ clear(): void;
16
+ destroy(): void;
17
+ getStats(): {
18
+ size: number;
19
+ entries: Array<{
20
+ url: string;
21
+ age: number;
22
+ }>;
23
+ };
24
+ }
25
+ export declare const urlCache: SimpleCache;
26
+ export { SimpleCache };
package/dist/cache.js ADDED
@@ -0,0 +1,68 @@
1
+ class SimpleCache {
2
+ cache = new Map();
3
+ ttlMs;
4
+ cleanupInterval = null;
5
+ constructor(ttlMs = 60000) {
6
+ this.ttlMs = ttlMs;
7
+ this.startCleanup();
8
+ }
9
+ startCleanup() {
10
+ // Clean up expired entries every 30 seconds
11
+ this.cleanupInterval = setInterval(() => {
12
+ this.cleanupExpired();
13
+ }, 30000);
14
+ }
15
+ cleanupExpired() {
16
+ const now = Date.now();
17
+ for (const [key, entry] of this.cache.entries()) {
18
+ if (now - entry.timestamp > this.ttlMs) {
19
+ this.cache.delete(key);
20
+ }
21
+ }
22
+ }
23
+ get(url) {
24
+ const entry = this.cache.get(url);
25
+ if (!entry) {
26
+ return null;
27
+ }
28
+ // Check if expired
29
+ if (Date.now() - entry.timestamp > this.ttlMs) {
30
+ this.cache.delete(url);
31
+ return null;
32
+ }
33
+ return entry;
34
+ }
35
+ set(url, htmlContent, markdownContent) {
36
+ this.cache.set(url, {
37
+ htmlContent,
38
+ markdownContent,
39
+ timestamp: Date.now()
40
+ });
41
+ }
42
+ clear() {
43
+ this.cache.clear();
44
+ }
45
+ destroy() {
46
+ if (this.cleanupInterval) {
47
+ clearInterval(this.cleanupInterval);
48
+ this.cleanupInterval = null;
49
+ }
50
+ this.clear();
51
+ }
52
+ // Get cache statistics for debugging
53
+ getStats() {
54
+ const now = Date.now();
55
+ const entries = Array.from(this.cache.entries()).map(([url, entry]) => ({
56
+ url,
57
+ age: now - entry.timestamp
58
+ }));
59
+ return {
60
+ size: this.cache.size,
61
+ entries
62
+ };
63
+ }
64
+ }
65
+ // Global cache instance
66
+ export const urlCache = new SimpleCache();
67
+ // Export for testing and cleanup
68
+ export { SimpleCache };
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Concise error handling for MCP SearXNG server
3
+ * Provides clear, focused error messages that identify the root cause
4
+ */
5
+ export interface ErrorContext {
6
+ url?: string;
7
+ searxngUrl?: string;
8
+ gatewayUrl?: string;
9
+ proxyAgent?: boolean;
10
+ username?: string;
11
+ timeout?: number;
12
+ query?: string;
13
+ }
14
+ export declare class MCPSearXNGError extends Error {
15
+ constructor(message: string);
16
+ }
17
+ export declare function createConfigurationError(message: string): MCPSearXNGError;
18
+ export declare function createNetworkError(error: any, context: ErrorContext): MCPSearXNGError;
19
+ export declare function createServerError(status: number, statusText: string, responseBody: string, context: ErrorContext): MCPSearXNGError;
20
+ export declare function createJSONError(responseText: string, context: ErrorContext): MCPSearXNGError;
21
+ export declare function createDataError(data: any, context: ErrorContext): MCPSearXNGError;
22
+ export declare function createNoResultsMessage(query: string): string;
23
+ export declare function createURLFormatError(url: string): MCPSearXNGError;
24
+ export declare function createContentError(message: string, url: string): MCPSearXNGError;
25
+ export declare function createConversionError(error: any, url: string, htmlContent: string): MCPSearXNGError;
26
+ export declare function createTimeoutError(timeout: number, url: string): MCPSearXNGError;
27
+ export declare function createEmptyContentWarning(url: string, htmlLength: number, htmlPreview: string): string;
28
+ export declare function createUnexpectedError(error: any, context: ErrorContext): MCPSearXNGError;
29
+ export declare function validateEnvironment(): string | null;
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Concise error handling for MCP SearXNG server
3
+ * Provides clear, focused error messages that identify the root cause
4
+ */
5
+ export class MCPSearXNGError extends Error {
6
+ constructor(message) {
7
+ super(message);
8
+ this.name = 'MCPSearXNGError';
9
+ }
10
+ }
11
+ export function createConfigurationError(message) {
12
+ return new MCPSearXNGError(`🔧 Configuration Error: ${message}`);
13
+ }
14
+ export function createNetworkError(error, context) {
15
+ const target = context.searxngUrl ? 'SearXNG server' : (context.gatewayUrl ? 'Gateway server' : 'website');
16
+ if (error.code === 'ECONNREFUSED') {
17
+ return new MCPSearXNGError(`🌐 Connection Error: ${target} is not responding (${context.url})`);
18
+ }
19
+ if (error.code === 'ENOTFOUND' || error.code === 'EAI_NONAME') {
20
+ const hostname = context.url ? new URL(context.url).hostname : 'unknown';
21
+ return new MCPSearXNGError(`🌐 DNS Error: Cannot resolve hostname "${hostname}"`);
22
+ }
23
+ if (error.code === 'ETIMEDOUT') {
24
+ return new MCPSearXNGError(`🌐 Timeout Error: ${target} is too slow to respond`);
25
+ }
26
+ if (error.message?.includes('certificate')) {
27
+ return new MCPSearXNGError(`🌐 SSL Error: Certificate problem with ${target}`);
28
+ }
29
+ // For generic fetch failures, provide root cause guidance
30
+ const errorMsg = error.message || error.code || 'Connection failed';
31
+ if (errorMsg === 'fetch failed' || errorMsg === 'Connection failed') {
32
+ const guidance = context.searxngUrl
33
+ ? 'Check if the SEARXNG_URL is correct and the SearXNG server is available'
34
+ : (context.gatewayUrl
35
+ ? 'Check if the GATEWAY_URL is correct and the Gateway server is available'
36
+ : 'Check if the website URL is accessible');
37
+ return new MCPSearXNGError(`🌐 Network Error: ${errorMsg}. ${guidance}`);
38
+ }
39
+ return new MCPSearXNGError(`🌐 Network Error: ${errorMsg}`);
40
+ }
41
+ export function createServerError(status, statusText, responseBody, context) {
42
+ const target = context.searxngUrl ? 'SearXNG server' : (context.gatewayUrl ? 'Gateway server' : 'Website');
43
+ if (status === 403) {
44
+ const reason = context.searxngUrl ? 'Authentication required or IP blocked' : 'Access blocked (bot detection or geo-restriction)';
45
+ return new MCPSearXNGError(`🚫 ${target} Error (${status}): ${reason}`);
46
+ }
47
+ if (status === 404) {
48
+ const reason = context.searxngUrl ? 'Search endpoint not found' : 'Page not found';
49
+ return new MCPSearXNGError(`🚫 ${target} Error (${status}): ${reason}`);
50
+ }
51
+ if (status === 429) {
52
+ return new MCPSearXNGError(`🚫 ${target} Error (${status}): Rate limit exceeded`);
53
+ }
54
+ if (status >= 500) {
55
+ return new MCPSearXNGError(`🚫 ${target} Error (${status}): Internal server error`);
56
+ }
57
+ return new MCPSearXNGError(`🚫 ${target} Error (${status}): ${statusText}`);
58
+ }
59
+ export function createJSONError(responseText, context) {
60
+ const preview = responseText.substring(0, 100).replace(/\n/g, ' ');
61
+ return new MCPSearXNGError(`🔍 SearXNG Response Error: Invalid JSON format. Response: "${preview}..."`);
62
+ }
63
+ export function createDataError(data, context) {
64
+ return new MCPSearXNGError(`🔍 SearXNG Data Error: Missing results array in response`);
65
+ }
66
+ export function createNoResultsMessage(query) {
67
+ return `🔍 No results found for "${query}". Try different search terms or check if SearXNG search engines are working.`;
68
+ }
69
+ export function createURLFormatError(url) {
70
+ return new MCPSearXNGError(`🔧 URL Format Error: Invalid URL "${url}"`);
71
+ }
72
+ export function createContentError(message, url) {
73
+ return new MCPSearXNGError(`📄 Content Error: ${message} (${url})`);
74
+ }
75
+ export function createConversionError(error, url, htmlContent) {
76
+ return new MCPSearXNGError(`🔄 Conversion Error: Cannot convert HTML to Markdown (${url})`);
77
+ }
78
+ export function createTimeoutError(timeout, url) {
79
+ const hostname = new URL(url).hostname;
80
+ return new MCPSearXNGError(`⏱️ Timeout Error: ${hostname} took longer than ${timeout}ms to respond`);
81
+ }
82
+ export function createEmptyContentWarning(url, htmlLength, htmlPreview) {
83
+ return `📄 Content Warning: Page fetched but appears empty after conversion (${url}). May contain only media or require JavaScript.`;
84
+ }
85
+ export function createUnexpectedError(error, context) {
86
+ return new MCPSearXNGError(`❓ Unexpected Error: ${error.message || String(error)}`);
87
+ }
88
+ export function validateEnvironment() {
89
+ const issues = [];
90
+ const gatewayUrl = process.env.GATEWAY_URL;
91
+ if (gatewayUrl) {
92
+ try {
93
+ const url = new URL(gatewayUrl);
94
+ if (!['http:', 'https:'].includes(url.protocol)) {
95
+ issues.push(`GATEWAY_URL invalid protocol: ${url.protocol}`);
96
+ }
97
+ }
98
+ catch (error) {
99
+ issues.push(`GATEWAY_URL invalid format: ${gatewayUrl}`);
100
+ }
101
+ }
102
+ const authUsername = process.env.AUTH_USERNAME;
103
+ const authPassword = process.env.AUTH_PASSWORD;
104
+ if (authUsername && !authPassword) {
105
+ issues.push("AUTH_USERNAME set but AUTH_PASSWORD missing");
106
+ }
107
+ else if (!authUsername && authPassword) {
108
+ issues.push("AUTH_PASSWORD set but AUTH_USERNAME missing");
109
+ }
110
+ if (issues.length === 0) {
111
+ return null;
112
+ }
113
+ return `⚠️ Configuration Issues: ${issues.join(', ')}. GATEWAY_URL is optional (defaults to http://115.190.91.253:80)`;
114
+ }
@@ -0,0 +1,3 @@
1
+ import express from "express";
2
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
3
+ export declare function createHttpServer(server: Server): Promise<express.Application>;
@@ -0,0 +1,150 @@
1
+ import express from "express";
2
+ import cors from "cors";
3
+ import { randomUUID } from "crypto";
4
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
5
+ import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
6
+ import { logMessage } from "./logging.js";
7
+ import { packageVersion } from "./index.js";
8
+ export async function createHttpServer(server) {
9
+ const app = express();
10
+ app.use(express.json());
11
+ // Add CORS support for web clients
12
+ app.use(cors({
13
+ origin: '*', // Configure appropriately for production
14
+ exposedHeaders: ['Mcp-Session-Id'],
15
+ allowedHeaders: ['Content-Type', 'mcp-session-id'],
16
+ }));
17
+ // Map to store transports by session ID
18
+ const transports = {};
19
+ // Handle POST requests for client-to-server communication
20
+ app.post('/mcp', async (req, res) => {
21
+ const sessionId = req.headers['mcp-session-id'];
22
+ let transport;
23
+ if (sessionId && transports[sessionId]) {
24
+ // Reuse existing transport
25
+ transport = transports[sessionId];
26
+ logMessage(server, "debug", `Reusing session: ${sessionId}`);
27
+ }
28
+ else if (!sessionId && isInitializeRequest(req.body)) {
29
+ // New initialization request
30
+ logMessage(server, "info", "Creating new HTTP session");
31
+ transport = new StreamableHTTPServerTransport({
32
+ sessionIdGenerator: () => randomUUID(),
33
+ onsessioninitialized: (sessionId) => {
34
+ transports[sessionId] = transport;
35
+ logMessage(server, "debug", `Session initialized: ${sessionId}`);
36
+ },
37
+ // DNS rebinding protection disabled by default for backwards compatibility
38
+ // For production, consider enabling:
39
+ // enableDnsRebindingProtection: true,
40
+ // allowedHosts: ['127.0.0.1', 'localhost'],
41
+ });
42
+ // Clean up transport when closed
43
+ transport.onclose = () => {
44
+ if (transport.sessionId) {
45
+ logMessage(server, "debug", `Session closed: ${transport.sessionId}`);
46
+ delete transports[transport.sessionId];
47
+ }
48
+ };
49
+ // Connect the existing server to the new transport
50
+ await server.connect(transport);
51
+ }
52
+ else {
53
+ // Invalid request
54
+ console.warn(`⚠️ POST request rejected - invalid request:`, {
55
+ clientIP: req.ip || req.connection.remoteAddress,
56
+ sessionId: sessionId || 'undefined',
57
+ hasInitializeRequest: isInitializeRequest(req.body),
58
+ userAgent: req.headers['user-agent'],
59
+ contentType: req.headers['content-type'],
60
+ accept: req.headers['accept']
61
+ });
62
+ res.status(400).json({
63
+ jsonrpc: '2.0',
64
+ error: {
65
+ code: -32000,
66
+ message: 'Bad Request: No valid session ID provided',
67
+ },
68
+ id: null,
69
+ });
70
+ return;
71
+ }
72
+ // Handle the request
73
+ try {
74
+ await transport.handleRequest(req, res, req.body);
75
+ }
76
+ catch (error) {
77
+ // Log header-related rejections for debugging
78
+ if (error instanceof Error && error.message.includes('accept')) {
79
+ console.warn(`⚠️ Connection rejected due to missing headers:`, {
80
+ clientIP: req.ip || req.connection.remoteAddress,
81
+ userAgent: req.headers['user-agent'],
82
+ contentType: req.headers['content-type'],
83
+ accept: req.headers['accept'],
84
+ error: error.message
85
+ });
86
+ }
87
+ throw error;
88
+ }
89
+ });
90
+ // Handle GET requests for server-to-client notifications via SSE
91
+ app.get('/mcp', async (req, res) => {
92
+ const sessionId = req.headers['mcp-session-id'];
93
+ if (!sessionId || !transports[sessionId]) {
94
+ console.warn(`⚠️ GET request rejected - missing or invalid session ID:`, {
95
+ clientIP: req.ip || req.connection.remoteAddress,
96
+ sessionId: sessionId || 'undefined',
97
+ userAgent: req.headers['user-agent']
98
+ });
99
+ res.status(400).send('Invalid or missing session ID');
100
+ return;
101
+ }
102
+ const transport = transports[sessionId];
103
+ try {
104
+ await transport.handleRequest(req, res);
105
+ }
106
+ catch (error) {
107
+ console.warn(`⚠️ GET request failed:`, {
108
+ clientIP: req.ip || req.connection.remoteAddress,
109
+ sessionId,
110
+ error: error instanceof Error ? error.message : String(error)
111
+ });
112
+ throw error;
113
+ }
114
+ });
115
+ // Handle DELETE requests for session termination
116
+ app.delete('/mcp', async (req, res) => {
117
+ const sessionId = req.headers['mcp-session-id'];
118
+ if (!sessionId || !transports[sessionId]) {
119
+ console.warn(`⚠️ DELETE request rejected - missing or invalid session ID:`, {
120
+ clientIP: req.ip || req.connection.remoteAddress,
121
+ sessionId: sessionId || 'undefined',
122
+ userAgent: req.headers['user-agent']
123
+ });
124
+ res.status(400).send('Invalid or missing session ID');
125
+ return;
126
+ }
127
+ const transport = transports[sessionId];
128
+ try {
129
+ await transport.handleRequest(req, res);
130
+ }
131
+ catch (error) {
132
+ console.warn(`⚠️ DELETE request failed:`, {
133
+ clientIP: req.ip || req.connection.remoteAddress,
134
+ sessionId,
135
+ error: error instanceof Error ? error.message : String(error)
136
+ });
137
+ throw error;
138
+ }
139
+ });
140
+ // Health check endpoint
141
+ app.get('/health', (_req, res) => {
142
+ res.json({
143
+ status: 'healthy',
144
+ server: 'ihor-sokoliuk/mcp-searxng',
145
+ version: packageVersion,
146
+ transport: 'http'
147
+ });
148
+ });
149
+ return app;
150
+ }
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env node
2
+ declare const packageVersion = "0.8.0";
3
+ export { packageVersion };
4
+ export declare function isWebUrlReadArgs(args: unknown): args is {
5
+ url: string;
6
+ startChar?: number;
7
+ maxLength?: number;
8
+ section?: string;
9
+ paragraphRange?: string;
10
+ readHeadings?: boolean;
11
+ };