mcp-searxng 1.0.2 → 1.0.3
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/dist/http-server.d.ts +1 -1
- package/dist/http-server.js +24 -19
- package/dist/index.d.ts +7 -1
- package/dist/index.js +142 -139
- package/package.json +1 -1
package/dist/http-server.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import express from "express";
|
|
2
2
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
-
export declare function createHttpServer(
|
|
3
|
+
export declare function createHttpServer(createMcpServer: () => McpServer): Promise<express.Application>;
|
package/dist/http-server.js
CHANGED
|
@@ -6,7 +6,7 @@ import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
|
|
|
6
6
|
import { logMessage } from "./logging.js";
|
|
7
7
|
import { packageVersion } from "./index.js";
|
|
8
8
|
import { getHttpSecurityConfig, isOriginAllowed, isRequestAuthorized, validateHttpSecurityConfig, } from "./http-security.js";
|
|
9
|
-
export async function createHttpServer(
|
|
9
|
+
export async function createHttpServer(createMcpServer) {
|
|
10
10
|
const app = express();
|
|
11
11
|
const security = getHttpSecurityConfig();
|
|
12
12
|
validateHttpSecurityConfig(security);
|
|
@@ -33,8 +33,8 @@ export async function createHttpServer(mcpServer) {
|
|
|
33
33
|
id: null,
|
|
34
34
|
});
|
|
35
35
|
}
|
|
36
|
-
// Map to store
|
|
37
|
-
const
|
|
36
|
+
// Map to store sessions by session ID
|
|
37
|
+
const sessions = new Map();
|
|
38
38
|
// Handle POST requests for client-to-server communication
|
|
39
39
|
app.post('/mcp', async (req, res) => {
|
|
40
40
|
if (!isRequestAuthorized(req.headers.authorization, security)) {
|
|
@@ -43,32 +43,34 @@ export async function createHttpServer(mcpServer) {
|
|
|
43
43
|
}
|
|
44
44
|
const sessionId = req.headers['mcp-session-id'];
|
|
45
45
|
let transport;
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
46
|
+
let mcpServer;
|
|
47
|
+
if (sessionId && sessions.has(sessionId)) {
|
|
48
|
+
// Reuse existing session
|
|
49
|
+
const session = sessions.get(sessionId);
|
|
50
|
+
transport = session.transport;
|
|
51
|
+
mcpServer = session.mcpServer;
|
|
49
52
|
logMessage(mcpServer, "debug", `Reusing session: ${sessionId}`);
|
|
50
53
|
}
|
|
51
54
|
else if (!sessionId && isInitializeRequest(req.body)) {
|
|
52
|
-
// New initialization request
|
|
53
|
-
|
|
55
|
+
// New initialization request — create fresh McpServer and transport
|
|
56
|
+
mcpServer = createMcpServer();
|
|
54
57
|
transport = new StreamableHTTPServerTransport({
|
|
55
58
|
sessionIdGenerator: () => randomUUID(),
|
|
56
59
|
onsessioninitialized: (sessionId) => {
|
|
57
|
-
|
|
60
|
+
sessions.set(sessionId, { transport, mcpServer });
|
|
58
61
|
logMessage(mcpServer, "debug", `Session initialized: ${sessionId}`);
|
|
59
62
|
},
|
|
60
63
|
enableDnsRebindingProtection: security.enableDnsRebindingProtection,
|
|
61
64
|
allowedHosts: security.allowedHosts,
|
|
62
65
|
allowedOrigins: security.allowedOrigins,
|
|
63
66
|
});
|
|
64
|
-
// Clean up
|
|
67
|
+
// Clean up session when transport closes
|
|
65
68
|
transport.onclose = () => {
|
|
66
69
|
if (transport.sessionId) {
|
|
67
|
-
|
|
68
|
-
delete transports[transport.sessionId];
|
|
70
|
+
sessions.delete(transport.sessionId);
|
|
69
71
|
}
|
|
70
72
|
};
|
|
71
|
-
// Connect
|
|
73
|
+
// Connect this session's McpServer to its transport
|
|
72
74
|
await mcpServer.connect(transport);
|
|
73
75
|
}
|
|
74
76
|
else {
|
|
@@ -116,7 +118,7 @@ export async function createHttpServer(mcpServer) {
|
|
|
116
118
|
return;
|
|
117
119
|
}
|
|
118
120
|
const sessionId = req.headers['mcp-session-id'];
|
|
119
|
-
if (!sessionId || !
|
|
121
|
+
if (!sessionId || !sessions.has(sessionId)) {
|
|
120
122
|
console.warn(`⚠️ GET request rejected - missing or invalid session ID:`, {
|
|
121
123
|
clientIP: req.ip || req.socket.remoteAddress,
|
|
122
124
|
sessionId: sessionId || 'undefined',
|
|
@@ -125,9 +127,9 @@ export async function createHttpServer(mcpServer) {
|
|
|
125
127
|
res.status(400).send('Invalid or missing session ID');
|
|
126
128
|
return;
|
|
127
129
|
}
|
|
128
|
-
const
|
|
130
|
+
const session = sessions.get(sessionId);
|
|
129
131
|
try {
|
|
130
|
-
await transport.handleRequest(req, res);
|
|
132
|
+
await session.transport.handleRequest(req, res);
|
|
131
133
|
}
|
|
132
134
|
catch (error) {
|
|
133
135
|
console.warn(`⚠️ GET request failed:`, {
|
|
@@ -145,7 +147,7 @@ export async function createHttpServer(mcpServer) {
|
|
|
145
147
|
return;
|
|
146
148
|
}
|
|
147
149
|
const sessionId = req.headers['mcp-session-id'];
|
|
148
|
-
if (!sessionId || !
|
|
150
|
+
if (!sessionId || !sessions.has(sessionId)) {
|
|
149
151
|
console.warn(`⚠️ DELETE request rejected - missing or invalid session ID:`, {
|
|
150
152
|
clientIP: req.ip || req.socket.remoteAddress,
|
|
151
153
|
sessionId: sessionId || 'undefined',
|
|
@@ -154,9 +156,9 @@ export async function createHttpServer(mcpServer) {
|
|
|
154
156
|
res.status(400).send('Invalid or missing session ID');
|
|
155
157
|
return;
|
|
156
158
|
}
|
|
157
|
-
const
|
|
159
|
+
const session = sessions.get(sessionId);
|
|
158
160
|
try {
|
|
159
|
-
await transport.handleRequest(req, res);
|
|
161
|
+
await session.transport.handleRequest(req, res);
|
|
160
162
|
}
|
|
161
163
|
catch (error) {
|
|
162
164
|
console.warn(`⚠️ DELETE request failed:`, {
|
|
@@ -166,6 +168,9 @@ export async function createHttpServer(mcpServer) {
|
|
|
166
168
|
});
|
|
167
169
|
throw error;
|
|
168
170
|
}
|
|
171
|
+
finally {
|
|
172
|
+
sessions.delete(sessionId);
|
|
173
|
+
}
|
|
169
174
|
});
|
|
170
175
|
// Health check endpoint
|
|
171
176
|
app.get('/health', (_req, res) => {
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
2
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
declare const packageVersion = "1.0.3";
|
|
3
4
|
export { packageVersion };
|
|
4
5
|
export declare function isWebUrlReadArgs(args: unknown): args is {
|
|
5
6
|
url: string;
|
|
@@ -9,3 +10,8 @@ export declare function isWebUrlReadArgs(args: unknown): args is {
|
|
|
9
10
|
paragraphRange?: string;
|
|
10
11
|
readHeadings?: boolean;
|
|
11
12
|
};
|
|
13
|
+
/**
|
|
14
|
+
* Creates and configures a new McpServer with all handlers registered.
|
|
15
|
+
* Called once per HTTP session, or once for STDIO mode.
|
|
16
|
+
*/
|
|
17
|
+
export declare function createMcpServer(): McpServer;
|
package/dist/index.js
CHANGED
|
@@ -4,17 +4,15 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
4
4
|
import { CallToolRequestSchema, ListToolsRequestSchema, SetLevelRequestSchema, ListResourcesRequestSchema, ListResourceTemplatesRequestSchema, ReadResourceRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
5
5
|
// Import modularized functionality
|
|
6
6
|
import { WEB_SEARCH_TOOL, READ_URL_TOOL, isSearXNGWebSearchArgs } from "./types.js";
|
|
7
|
-
import { logMessage, setLogLevel } from "./logging.js";
|
|
7
|
+
import { logMessage, setLogLevel, getCurrentLogLevel } from "./logging.js";
|
|
8
8
|
import { performWebSearch } from "./search.js";
|
|
9
9
|
import { fetchAndConvertToMarkdown } from "./url-reader.js";
|
|
10
10
|
import { createConfigResource, createHelpResource } from "./resources.js";
|
|
11
11
|
import { createHttpServer } from "./http-server.js";
|
|
12
12
|
// Use a static version string that will be updated by the version script
|
|
13
|
-
const packageVersion = "1.0.
|
|
13
|
+
const packageVersion = "1.0.3";
|
|
14
14
|
// Export the version for use in other modules
|
|
15
15
|
export { packageVersion };
|
|
16
|
-
// Global state for logging level
|
|
17
|
-
let currentLogLevel = "info";
|
|
18
16
|
// Type guard for URL reading args
|
|
19
17
|
export function isWebUrlReadArgs(args) {
|
|
20
18
|
if (typeof args !== "object" ||
|
|
@@ -47,142 +45,146 @@ export function isWebUrlReadArgs(args) {
|
|
|
47
45
|
}
|
|
48
46
|
return true;
|
|
49
47
|
}
|
|
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
|
-
|
|
48
|
+
/**
|
|
49
|
+
* Creates and configures a new McpServer with all handlers registered.
|
|
50
|
+
* Called once per HTTP session, or once for STDIO mode.
|
|
51
|
+
*/
|
|
52
|
+
export function createMcpServer() {
|
|
53
|
+
const mcpServer = new McpServer({
|
|
54
|
+
name: "ihor-sokoliuk/mcp-searxng",
|
|
55
|
+
version: packageVersion,
|
|
56
|
+
}, {
|
|
57
|
+
capabilities: {
|
|
58
|
+
logging: {},
|
|
59
|
+
resources: {},
|
|
60
|
+
tools: {},
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
const server = mcpServer.server;
|
|
64
|
+
// List tools handler
|
|
65
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
66
|
+
logMessage(mcpServer, "debug", "Handling list_tools request");
|
|
67
|
+
return {
|
|
68
|
+
tools: [WEB_SEARCH_TOOL, READ_URL_TOOL],
|
|
69
|
+
};
|
|
70
|
+
});
|
|
71
|
+
// Call tool handler
|
|
72
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
73
|
+
const { name, arguments: args } = request.params;
|
|
74
|
+
logMessage(mcpServer, "debug", `Handling call_tool request: ${name}`);
|
|
75
|
+
try {
|
|
76
|
+
if (name === "searxng_web_search") {
|
|
77
|
+
if (!isSearXNGWebSearchArgs(args)) {
|
|
78
|
+
throw new Error("Invalid arguments for web search");
|
|
79
|
+
}
|
|
80
|
+
const result = await performWebSearch(mcpServer, args.query, args.pageno, args.time_range, args.language, args.safesearch);
|
|
81
|
+
return {
|
|
82
|
+
content: [
|
|
83
|
+
{
|
|
84
|
+
type: "text",
|
|
85
|
+
text: result,
|
|
86
|
+
},
|
|
87
|
+
],
|
|
88
|
+
};
|
|
78
89
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
90
|
+
else if (name === "web_url_read") {
|
|
91
|
+
if (!isWebUrlReadArgs(args)) {
|
|
92
|
+
throw new Error("Invalid arguments for URL reading");
|
|
93
|
+
}
|
|
94
|
+
const paginationOptions = {
|
|
95
|
+
startChar: args.startChar,
|
|
96
|
+
maxLength: args.maxLength,
|
|
97
|
+
section: args.section,
|
|
98
|
+
paragraphRange: args.paragraphRange,
|
|
99
|
+
readHeadings: args.readHeadings,
|
|
100
|
+
};
|
|
101
|
+
const result = await fetchAndConvertToMarkdown(mcpServer, args.url, 10000, paginationOptions);
|
|
102
|
+
return {
|
|
103
|
+
content: [
|
|
104
|
+
{
|
|
105
|
+
type: "text",
|
|
106
|
+
text: result,
|
|
107
|
+
},
|
|
108
|
+
],
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
92
113
|
}
|
|
93
|
-
const paginationOptions = {
|
|
94
|
-
startChar: args.startChar,
|
|
95
|
-
maxLength: args.maxLength,
|
|
96
|
-
section: args.section,
|
|
97
|
-
paragraphRange: args.paragraphRange,
|
|
98
|
-
readHeadings: args.readHeadings,
|
|
99
|
-
};
|
|
100
|
-
const result = await fetchAndConvertToMarkdown(mcpServer, args.url, 10000, paginationOptions);
|
|
101
|
-
return {
|
|
102
|
-
content: [
|
|
103
|
-
{
|
|
104
|
-
type: "text",
|
|
105
|
-
text: result,
|
|
106
|
-
},
|
|
107
|
-
],
|
|
108
|
-
};
|
|
109
114
|
}
|
|
110
|
-
|
|
111
|
-
|
|
115
|
+
catch (error) {
|
|
116
|
+
logMessage(mcpServer, "error", `Tool execution error: ${error instanceof Error ? error.message : String(error)}`, {
|
|
117
|
+
tool: name,
|
|
118
|
+
args: args,
|
|
119
|
+
error: error instanceof Error ? error.stack : String(error)
|
|
120
|
+
});
|
|
121
|
+
throw error;
|
|
112
122
|
}
|
|
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
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
text: createHelpResource()
|
|
179
|
-
}
|
|
180
|
-
]
|
|
181
|
-
};
|
|
182
|
-
default:
|
|
183
|
-
throw new Error(`Unknown resource: ${uri}`);
|
|
184
|
-
}
|
|
185
|
-
});
|
|
123
|
+
});
|
|
124
|
+
// Logging level handler
|
|
125
|
+
server.setRequestHandler(SetLevelRequestSchema, async (request) => {
|
|
126
|
+
const { level } = request.params;
|
|
127
|
+
logMessage(mcpServer, "info", `Setting log level to: ${level}`);
|
|
128
|
+
setLogLevel(level);
|
|
129
|
+
return {};
|
|
130
|
+
});
|
|
131
|
+
// List resources handler
|
|
132
|
+
server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
133
|
+
logMessage(mcpServer, "debug", "Handling list_resources request");
|
|
134
|
+
return {
|
|
135
|
+
resources: [
|
|
136
|
+
{
|
|
137
|
+
uri: "config://server-config",
|
|
138
|
+
mimeType: "application/json",
|
|
139
|
+
name: "Server Configuration",
|
|
140
|
+
description: "Current server configuration and environment variables"
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
uri: "help://usage-guide",
|
|
144
|
+
mimeType: "text/markdown",
|
|
145
|
+
name: "Usage Guide",
|
|
146
|
+
description: "How to use the MCP SearXNG server effectively"
|
|
147
|
+
}
|
|
148
|
+
]
|
|
149
|
+
};
|
|
150
|
+
});
|
|
151
|
+
// List resource templates handler
|
|
152
|
+
// Returns empty list — required by MCP spec even when no templates exist
|
|
153
|
+
server.setRequestHandler(ListResourceTemplatesRequestSchema, async () => {
|
|
154
|
+
logMessage(mcpServer, "debug", "Handling list_resource_templates request");
|
|
155
|
+
return { resourceTemplates: [] };
|
|
156
|
+
});
|
|
157
|
+
// Read resource handler
|
|
158
|
+
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
159
|
+
const { uri } = request.params;
|
|
160
|
+
logMessage(mcpServer, "debug", `Handling read_resource request for: ${uri}`);
|
|
161
|
+
switch (uri) {
|
|
162
|
+
case "config://server-config":
|
|
163
|
+
return {
|
|
164
|
+
contents: [
|
|
165
|
+
{
|
|
166
|
+
uri: uri,
|
|
167
|
+
mimeType: "application/json",
|
|
168
|
+
text: createConfigResource()
|
|
169
|
+
}
|
|
170
|
+
]
|
|
171
|
+
};
|
|
172
|
+
case "help://usage-guide":
|
|
173
|
+
return {
|
|
174
|
+
contents: [
|
|
175
|
+
{
|
|
176
|
+
uri: uri,
|
|
177
|
+
mimeType: "text/markdown",
|
|
178
|
+
text: createHelpResource()
|
|
179
|
+
}
|
|
180
|
+
]
|
|
181
|
+
};
|
|
182
|
+
default:
|
|
183
|
+
throw new Error(`Unknown resource: ${uri}`);
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
return mcpServer;
|
|
187
|
+
}
|
|
186
188
|
// Main function
|
|
187
189
|
async function main() {
|
|
188
190
|
// Check for HTTP transport mode
|
|
@@ -194,7 +196,7 @@ async function main() {
|
|
|
194
196
|
process.exit(1);
|
|
195
197
|
}
|
|
196
198
|
console.log(`Starting HTTP transport on port ${port}`);
|
|
197
|
-
const app = await createHttpServer(
|
|
199
|
+
const app = await createHttpServer(createMcpServer);
|
|
198
200
|
const httpServer = app.listen(port, () => {
|
|
199
201
|
console.log(`HTTP server listening on port ${port}`);
|
|
200
202
|
console.log(`Health check: http://localhost:${port}/health`);
|
|
@@ -212,7 +214,8 @@ async function main() {
|
|
|
212
214
|
process.on('SIGTERM', () => shutdown('SIGTERM'));
|
|
213
215
|
}
|
|
214
216
|
else {
|
|
215
|
-
// Default STDIO transport
|
|
217
|
+
// Default STDIO transport — single session, single server
|
|
218
|
+
const mcpServer = createMcpServer();
|
|
216
219
|
// Show helpful message when running in terminal
|
|
217
220
|
if (process.stdin.isTTY) {
|
|
218
221
|
console.error(`🔍 MCP SearXNG Server v${packageVersion} - Ready`);
|
|
@@ -228,7 +231,7 @@ async function main() {
|
|
|
228
231
|
await mcpServer.connect(transport);
|
|
229
232
|
// Log after connection is established
|
|
230
233
|
logMessage(mcpServer, "info", `MCP SearXNG Server v${packageVersion} connected via STDIO`);
|
|
231
|
-
logMessage(mcpServer, "info", `Log level: ${
|
|
234
|
+
logMessage(mcpServer, "info", `Log level: ${getCurrentLogLevel()}`);
|
|
232
235
|
logMessage(mcpServer, "info", `Environment: ${process.env.NODE_ENV || 'development'}`);
|
|
233
236
|
logMessage(mcpServer, "info", `SearXNG URL: ${process.env.SEARXNG_URL || 'not configured'}`);
|
|
234
237
|
}
|