mcp-www 0.1.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 ADDED
@@ -0,0 +1,161 @@
1
+ # mcp-browse
2
+
3
+ **DNS-based MCP service discovery over UDP.**
4
+
5
+ ## Problem
6
+
7
+ Agents need to discover MCP servers, but current approaches lean on centralized registries or hardcoded configurations. This creates single points of failure, adds deployment overhead, and forces agents into walled gardens. There should be a way to discover MCP services using infrastructure that already exists everywhere: DNS.
8
+
9
+ ## How It Works
10
+
11
+ **mcp-browse** is itself a standard MCP server. An agent connects to it the same way it connects to any other MCP server — no new client code, no special SDK, no registry signup.
12
+
13
+ Once connected, the agent calls the `browse_domain` tool with a domain name. mcp-browse performs a standard **UDP DNS TXT lookup** for `_mcp.{domain}`, parses the semicolon-delimited record, and returns structured metadata about the MCP servers published by that domain.
14
+
15
+ ```
16
+ Agent → mcp-browse (MCP server) → UDP DNS query for _mcp.example.com TXT
17
+ ← "v=mcp1; src=https://mcp.example.com; ..."
18
+ ← Structured JSON response
19
+ ```
20
+
21
+ No HTTP registry in the loop. The DNS infrastructure **is** the registry.
22
+
23
+ ## Key Design Points
24
+
25
+ - **Uses UDP DNS (port 53) for lookups** — the lightest possible network primitive. No TCP handshake, no TLS negotiation, no HTTP overhead. A single UDP packet out, a single packet back.
26
+ - **The DNS infrastructure IS the registry** — no additional servers to deploy, no uptime to maintain, no accounts to create. If you can publish a TXT record, you can advertise your MCP server.
27
+ - **mcp-browse is a standard MCP server** — any MCP-compliant agent can use it with zero new client code. It's just another server in your agent's config.
28
+ - **Supports the `_mcp` TXT record convention** — records follow a semicolon-delimited format:
29
+ ```
30
+ v=mcp1; src=https://mcp.example.com; public=true; auth=oauth2; version=2024.1
31
+ ```
32
+ - **Works with split-horizon DNS** — enterprise and private networks can publish internal `_mcp` records visible only inside their network, enabling private service discovery without exposing anything to the public internet.
33
+
34
+ ## Tools Exposed
35
+
36
+ ### `browse_domain`
37
+
38
+ Lookup `_mcp.{domain}` TXT records and return a parsed list of discovered MCP servers.
39
+
40
+ ```json
41
+ {
42
+ "tool": "browse_domain",
43
+ "arguments": {
44
+ "domain": "example.com"
45
+ }
46
+ }
47
+ ```
48
+
49
+ Returns structured server metadata: server URL, protocol version, auth requirements, and any additional fields published in the TXT record.
50
+
51
+ ### `browse_discover`
52
+
53
+ Discover and inspect in one step. Looks up `_mcp.{domain}` TXT records, connects to the advertised server URL, and retrieves its full manifest — tools, resources, and prompts.
54
+
55
+ ```json
56
+ {
57
+ "tool": "browse_discover",
58
+ "arguments": {
59
+ "domain": "example.com"
60
+ }
61
+ }
62
+ ```
63
+
64
+ ### `browse_server`
65
+
66
+ Given a discovered server URL, connect to it and retrieve its full manifest: tools, resources, and prompts. Lets the agent inspect what a discovered server actually offers before deciding to connect.
67
+
68
+ ```json
69
+ {
70
+ "tool": "browse_server",
71
+ "arguments": {
72
+ "url": "https://mcp.example.com"
73
+ }
74
+ }
75
+ ```
76
+
77
+ ### `browse_multi`
78
+
79
+ Batch lookup across multiple domains in a single call. Useful for scanning a list of known domains or performing broad discovery.
80
+
81
+ ```json
82
+ {
83
+ "tool": "browse_multi",
84
+ "arguments": {
85
+ "domains": ["example.com", "acme.org", "internal.corp"]
86
+ }
87
+ }
88
+ ```
89
+
90
+ ### `call_remote_tool`
91
+
92
+ Call a tool on a remote MCP server. Use `browse_server` first to discover available tools, then use this to execute them. Handles the JSON-RPC initialize handshake and `tools/call` request.
93
+
94
+ ```json
95
+ {
96
+ "tool": "call_remote_tool",
97
+ "arguments": {
98
+ "url": "https://mcp.example.com",
99
+ "tool": "list_articles",
100
+ "arguments": { "limit": 5 }
101
+ }
102
+ }
103
+ ```
104
+
105
+ ### `read_remote_resource`
106
+
107
+ Read a resource from a remote MCP server. Use `browse_server` or `browse_discover` first to see available resources, then use this to read one by its URI.
108
+
109
+ ```json
110
+ {
111
+ "tool": "read_remote_resource",
112
+ "arguments": {
113
+ "url": "https://mcp.example.com",
114
+ "uri": "korm://bio"
115
+ }
116
+ }
117
+ ```
118
+
119
+ ### `get_remote_prompt`
120
+
121
+ Get a prompt from a remote MCP server. Use `browse_server` or `browse_discover` first to see available prompts, then use this to retrieve one with optional arguments.
122
+
123
+ ```json
124
+ {
125
+ "tool": "get_remote_prompt",
126
+ "arguments": {
127
+ "url": "https://mcp.example.com",
128
+ "prompt": "recommend-post",
129
+ "arguments": { "topic": "AI vision" }
130
+ }
131
+ }
132
+ ```
133
+
134
+ ## Try It
135
+
136
+ **[korm.co](https://korm.co)** publishes a live `_mcp` TXT record. You can discover and interact with it end-to-end:
137
+
138
+ ```
139
+ browse_discover("korm.co") → discovers server, returns tools + resources + prompts
140
+ browse_server("https://mcp.korm.co") → inspects tools, resources, and prompts
141
+ call_remote_tool("https://mcp.korm.co", "list_articles") → returns blog articles
142
+ read_remote_resource("https://mcp.korm.co", "korm://bio") → reads author bio
143
+ get_remote_prompt("https://mcp.korm.co", "recommend-post", { "topic": "AI" }) → gets prompt
144
+ ```
145
+
146
+ ## Status
147
+
148
+ **Working.** The server implements DNS-based discovery, server inspection, remote tool calling, resource reading, and prompt retrieval over the Streamable HTTP transport.
149
+
150
+ Feedback, criticism, and alternative approaches are welcome — open an issue or start a discussion.
151
+
152
+ ## Related
153
+
154
+ - [Model Context Protocol Specification](https://spec.modelcontextprotocol.io)
155
+ - [MCP Discussion #2334](https://github.com/modelcontextprotocol/specification/discussions/2334)
156
+ - [MCP PR #2127](https://github.com/modelcontextprotocol/specification/pull/2127)
157
+ - [MCP SEP #1959](https://github.com/nicobailon/mcp-seps/blob/main/SEP/1959/README.md)
158
+
159
+ ## License
160
+
161
+ MIT
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * mcp-browse
4
+ *
5
+ * A lightweight MCP server that performs DNS-based discovery of MCP services
6
+ * using UDP lookups. No registry server needed.
7
+ */
8
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,517 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ /**
4
+ * mcp-browse
5
+ *
6
+ * A lightweight MCP server that performs DNS-based discovery of MCP services
7
+ * using UDP lookups. No registry server needed.
8
+ */
9
+ var __importDefault = (this && this.__importDefault) || function (mod) {
10
+ return (mod && mod.__esModule) ? mod : { "default": mod };
11
+ };
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ const index_js_1 = require("@modelcontextprotocol/sdk/server/index.js");
14
+ const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
15
+ const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
16
+ const node_dns_1 = __importDefault(require("node:dns"));
17
+ const node_util_1 = require("node:util");
18
+ const resolveTxt = (0, node_util_1.promisify)(node_dns_1.default.resolveTxt);
19
+ // --- TXT Record Parser ---
20
+ function parseMcpTxtRecord(txtRecords) {
21
+ // TXT records come as arrays of strings (chunked), join them
22
+ const fullRecord = txtRecords.map((chunks) => chunks.join("")).join("");
23
+ const result = {};
24
+ // Parse semicolon-delimited key=value pairs
25
+ // e.g. "v=mcp1; endpoint=https://...; public=true"
26
+ const pairs = fullRecord.split(";").map((s) => s.trim()).filter(Boolean);
27
+ for (const pair of pairs) {
28
+ const eqIndex = pair.indexOf("=");
29
+ if (eqIndex > 0) {
30
+ const key = pair.slice(0, eqIndex).trim();
31
+ const value = pair.slice(eqIndex + 1).trim();
32
+ result[key] = value;
33
+ }
34
+ }
35
+ return result;
36
+ }
37
+ // --- DNS Lookup ---
38
+ async function lookupMcpDomain(domain) {
39
+ const mcpDomain = `_mcp.${domain}`;
40
+ try {
41
+ const records = await resolveTxt(mcpDomain);
42
+ if (records.length === 0) {
43
+ return null;
44
+ }
45
+ return parseMcpTxtRecord(records);
46
+ }
47
+ catch (err) {
48
+ if (err.code === "ENODATA" || err.code === "ENOTFOUND") {
49
+ return null; // No TXT record found
50
+ }
51
+ throw err;
52
+ }
53
+ }
54
+ // --- MCP Server Inspection ---
55
+ async function inspectMcpServer(url) {
56
+ // Initialize handshake
57
+ const initResponse = await fetch(url, {
58
+ method: "POST",
59
+ headers: { "Content-Type": "application/json" },
60
+ body: JSON.stringify({
61
+ jsonrpc: "2.0",
62
+ id: 1,
63
+ method: "initialize",
64
+ params: {
65
+ protocolVersion: "2024-11-05",
66
+ capabilities: {},
67
+ clientInfo: { name: "mcp-browse", version: "0.1.0" },
68
+ },
69
+ }),
70
+ });
71
+ if (!initResponse.ok) {
72
+ throw new Error(`Server returned ${initResponse.status}`);
73
+ }
74
+ const initResult = await initResponse.json();
75
+ const jsonRpcPost = (id, method) => fetch(url, {
76
+ method: "POST",
77
+ headers: { "Content-Type": "application/json" },
78
+ body: JSON.stringify({ jsonrpc: "2.0", id, method, params: {} }),
79
+ }).then(async (r) => (r.ok ? (await r.json()).result : null));
80
+ const [toolsResult, resourcesResult, promptsResult] = await Promise.all([
81
+ jsonRpcPost(2, "tools/list"),
82
+ jsonRpcPost(3, "resources/list").catch(() => null),
83
+ jsonRpcPost(4, "prompts/list").catch(() => null),
84
+ ]);
85
+ return {
86
+ serverInfo: initResult.result?.serverInfo || null,
87
+ protocolVersion: initResult.result?.protocolVersion || null,
88
+ instructions: initResult.result?.instructions || null,
89
+ tools: toolsResult?.tools || [],
90
+ resources: resourcesResult?.resources || [],
91
+ prompts: promptsResult?.prompts || [],
92
+ };
93
+ }
94
+ // --- Combined Discovery + Inspection ---
95
+ async function discoverMcpDomain(domain) {
96
+ const record = await lookupMcpDomain(domain);
97
+ if (record === null) {
98
+ return { domain, found: false, message: `No _mcp.${domain} TXT record found` };
99
+ }
100
+ const serverUrl = record.src || record.endpoint;
101
+ if (!serverUrl) {
102
+ return { domain, found: true, record, server: null, message: "No server URL (src/endpoint) in TXT record" };
103
+ }
104
+ try {
105
+ const serverInfo = await inspectMcpServer(serverUrl);
106
+ return { domain, found: true, record, server: { url: serverUrl, ...serverInfo } };
107
+ }
108
+ catch (err) {
109
+ return { domain, found: true, record, server: { url: serverUrl, error: err.message } };
110
+ }
111
+ }
112
+ // --- Remote Server Helpers ---
113
+ async function initRemoteServer(url) {
114
+ const response = await fetch(url, {
115
+ method: "POST",
116
+ headers: { "Content-Type": "application/json" },
117
+ body: JSON.stringify({
118
+ jsonrpc: "2.0",
119
+ id: 1,
120
+ method: "initialize",
121
+ params: {
122
+ protocolVersion: "2024-11-05",
123
+ capabilities: {},
124
+ clientInfo: { name: "mcp-browse", version: "0.1.0" },
125
+ },
126
+ }),
127
+ });
128
+ if (!response.ok) {
129
+ throw new Error(`Server returned ${response.status} during initialization`);
130
+ }
131
+ }
132
+ async function jsonRpcCall(url, id, method, params) {
133
+ const response = await fetch(url, {
134
+ method: "POST",
135
+ headers: { "Content-Type": "application/json" },
136
+ body: JSON.stringify({ jsonrpc: "2.0", id, method, params }),
137
+ });
138
+ if (!response.ok) {
139
+ throw new Error(`Server returned ${response.status} during ${method}`);
140
+ }
141
+ const result = await response.json();
142
+ if (result.error) {
143
+ throw new Error(result.error.message || JSON.stringify(result.error));
144
+ }
145
+ return result.result;
146
+ }
147
+ // --- Remote Tool Calling ---
148
+ async function callRemoteTool(url, toolName, toolArgs = {}) {
149
+ await initRemoteServer(url);
150
+ return jsonRpcCall(url, 2, "tools/call", { name: toolName, arguments: toolArgs });
151
+ }
152
+ // --- Remote Resource Reading ---
153
+ async function readRemoteResource(url, uri) {
154
+ await initRemoteServer(url);
155
+ return jsonRpcCall(url, 2, "resources/read", { uri });
156
+ }
157
+ // --- Remote Prompt Getting ---
158
+ async function getRemotePrompt(url, promptName, promptArgs = {}) {
159
+ await initRemoteServer(url);
160
+ return jsonRpcCall(url, 2, "prompts/get", { name: promptName, arguments: promptArgs });
161
+ }
162
+ // --- Response Formatting ---
163
+ function formatServerResult(serverData, url) {
164
+ const content = [];
165
+ // Surface instructions as natural language guidance the model will follow
166
+ if (serverData.instructions) {
167
+ content.push({
168
+ type: "text",
169
+ text: `[Server Instructions from ${serverData.serverInfo?.name || url}]\n` +
170
+ `${serverData.instructions}\n` +
171
+ `Use call_remote_tool with url "${url}" to execute any of the tools listed below.`,
172
+ });
173
+ }
174
+ // Add the structured data
175
+ content.push({
176
+ type: "text",
177
+ text: JSON.stringify({ url, ...serverData }, null, 2),
178
+ });
179
+ return content;
180
+ }
181
+ // --- Server Setup ---
182
+ const server = new index_js_1.Server({
183
+ name: "mcp-browse",
184
+ version: "0.1.0",
185
+ }, {
186
+ capabilities: {
187
+ tools: {},
188
+ },
189
+ instructions: "When a user mentions a domain name, company, or website — or when you need to find tools and services related to a domain — use mcp-browse first. " +
190
+ "Start with browse_discover to look up the domain and inspect what MCP services it offers in a single step. " +
191
+ "If you already have a server URL, use browse_server to see its tools, resources, and prompts. " +
192
+ "Then use call_remote_tool to execute tools on discovered servers.",
193
+ });
194
+ // --- Tool Definitions ---
195
+ server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => {
196
+ return {
197
+ tools: [
198
+ {
199
+ name: "browse_domain",
200
+ description: "Lookup _mcp.{domain} DNS TXT records and return parsed MCP server metadata. Uses standard UDP DNS queries.",
201
+ inputSchema: {
202
+ type: "object",
203
+ properties: {
204
+ domain: {
205
+ type: "string",
206
+ description: "The domain to look up (e.g., 'example.com')",
207
+ },
208
+ },
209
+ required: ["domain"],
210
+ },
211
+ },
212
+ {
213
+ name: "browse_server",
214
+ description: "Connect to a discovered MCP server URL and retrieve its full manifest: tools, resources, and prompts. Lets you inspect what a server offers before deciding to use it.",
215
+ inputSchema: {
216
+ type: "object",
217
+ properties: {
218
+ url: {
219
+ type: "string",
220
+ description: "The MCP server URL (e.g., 'https://mcp.example.com')",
221
+ },
222
+ },
223
+ required: ["url"],
224
+ },
225
+ },
226
+ {
227
+ name: "browse_multi",
228
+ description: "Batch lookup across multiple domains in a single call. Returns MCP server metadata for each domain that has _mcp TXT records.",
229
+ inputSchema: {
230
+ type: "object",
231
+ properties: {
232
+ domains: {
233
+ type: "array",
234
+ items: { type: "string" },
235
+ description: "Array of domains to look up",
236
+ },
237
+ },
238
+ required: ["domains"],
239
+ },
240
+ },
241
+ {
242
+ name: "browse_discover",
243
+ description: "Start here when a user mentions any domain name or website. Looks up _mcp.{domain} DNS TXT records, then connects to the advertised server URL to retrieve its full manifest (tools, resources, and prompts) — all in one step.",
244
+ inputSchema: {
245
+ type: "object",
246
+ properties: {
247
+ domain: {
248
+ type: "string",
249
+ description: "The domain to discover (e.g., 'example.com')",
250
+ },
251
+ },
252
+ required: ["domain"],
253
+ },
254
+ },
255
+ {
256
+ name: "call_remote_tool",
257
+ description: "Call a tool on a remote MCP server. Use browse_server first to discover available tools, then use this to execute them.",
258
+ inputSchema: {
259
+ type: "object",
260
+ properties: {
261
+ url: {
262
+ type: "string",
263
+ description: "The MCP server URL (e.g., 'https://mcp.example.com')",
264
+ },
265
+ tool: {
266
+ type: "string",
267
+ description: "The name of the tool to call on the remote server",
268
+ },
269
+ arguments: {
270
+ type: "object",
271
+ description: "Arguments to pass to the remote tool",
272
+ additionalProperties: true,
273
+ },
274
+ },
275
+ required: ["url", "tool"],
276
+ },
277
+ },
278
+ {
279
+ name: "read_remote_resource",
280
+ description: "Read a resource from a remote MCP server. Use browse_server or browse_discover first to see available resources, then use this to read one by its URI.",
281
+ inputSchema: {
282
+ type: "object",
283
+ properties: {
284
+ url: {
285
+ type: "string",
286
+ description: "The MCP server URL (e.g., 'https://mcp.example.com')",
287
+ },
288
+ uri: {
289
+ type: "string",
290
+ description: "The resource URI to read (e.g., 'file:///path/to/file')",
291
+ },
292
+ },
293
+ required: ["url", "uri"],
294
+ },
295
+ },
296
+ {
297
+ name: "get_remote_prompt",
298
+ description: "Get a prompt from a remote MCP server. Use browse_server or browse_discover first to see available prompts, then use this to retrieve one with optional arguments.",
299
+ inputSchema: {
300
+ type: "object",
301
+ properties: {
302
+ url: {
303
+ type: "string",
304
+ description: "The MCP server URL (e.g., 'https://mcp.example.com')",
305
+ },
306
+ prompt: {
307
+ type: "string",
308
+ description: "The name of the prompt to get",
309
+ },
310
+ arguments: {
311
+ type: "object",
312
+ description: "Arguments to pass to the prompt",
313
+ additionalProperties: { type: "string" },
314
+ },
315
+ },
316
+ required: ["url", "prompt"],
317
+ },
318
+ },
319
+ ],
320
+ };
321
+ });
322
+ // --- Tool Handlers ---
323
+ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
324
+ const { name, arguments: args } = request.params;
325
+ switch (name) {
326
+ case "browse_domain": {
327
+ const domain = args.domain;
328
+ try {
329
+ const result = await lookupMcpDomain(domain);
330
+ if (result === null) {
331
+ return {
332
+ content: [
333
+ {
334
+ type: "text",
335
+ text: JSON.stringify({ domain, found: false, message: `No _mcp.${domain} TXT record found` }, null, 2),
336
+ },
337
+ ],
338
+ };
339
+ }
340
+ return {
341
+ content: [
342
+ {
343
+ type: "text",
344
+ text: JSON.stringify({ domain, found: true, record: result }, null, 2),
345
+ },
346
+ ],
347
+ };
348
+ }
349
+ catch (err) {
350
+ return {
351
+ content: [
352
+ {
353
+ type: "text",
354
+ text: JSON.stringify({ domain, error: err.message }, null, 2),
355
+ },
356
+ ],
357
+ isError: true,
358
+ };
359
+ }
360
+ }
361
+ case "browse_server": {
362
+ const url = args.url;
363
+ try {
364
+ const result = await inspectMcpServer(url);
365
+ return { content: formatServerResult(result, url) };
366
+ }
367
+ catch (err) {
368
+ return {
369
+ content: [
370
+ {
371
+ type: "text",
372
+ text: JSON.stringify({ url, error: err.message }, null, 2),
373
+ },
374
+ ],
375
+ isError: true,
376
+ };
377
+ }
378
+ }
379
+ case "browse_multi": {
380
+ const domains = args.domains;
381
+ const results = {};
382
+ await Promise.all(domains.map(async (domain) => {
383
+ try {
384
+ const record = await lookupMcpDomain(domain);
385
+ results[domain] = record !== null ? { found: true, record } : { found: false };
386
+ }
387
+ catch (err) {
388
+ results[domain] = { error: err.message };
389
+ }
390
+ }));
391
+ return {
392
+ content: [
393
+ {
394
+ type: "text",
395
+ text: JSON.stringify(results, null, 2),
396
+ },
397
+ ],
398
+ };
399
+ }
400
+ case "browse_discover": {
401
+ const domain = args.domain;
402
+ try {
403
+ const result = await discoverMcpDomain(domain);
404
+ // If we got server data with instructions, surface them prominently
405
+ if (result.server && !result.server.error && result.server.instructions) {
406
+ const content = formatServerResult(result.server, result.server.url);
407
+ // Prepend the discovery context
408
+ content.unshift({
409
+ type: "text",
410
+ text: `Discovered MCP server for ${domain} via DNS lookup of _mcp.${domain}`,
411
+ });
412
+ return { content };
413
+ }
414
+ return {
415
+ content: [
416
+ {
417
+ type: "text",
418
+ text: JSON.stringify(result, null, 2),
419
+ },
420
+ ],
421
+ };
422
+ }
423
+ catch (err) {
424
+ return {
425
+ content: [
426
+ {
427
+ type: "text",
428
+ text: JSON.stringify({ domain, error: err.message }, null, 2),
429
+ },
430
+ ],
431
+ isError: true,
432
+ };
433
+ }
434
+ }
435
+ case "call_remote_tool": {
436
+ const { url, tool, arguments: remoteArgs } = args;
437
+ try {
438
+ const result = await callRemoteTool(url, tool, remoteArgs || {});
439
+ return {
440
+ content: result?.content || [
441
+ { type: "text", text: JSON.stringify(result, null, 2) },
442
+ ],
443
+ };
444
+ }
445
+ catch (err) {
446
+ return {
447
+ content: [
448
+ {
449
+ type: "text",
450
+ text: JSON.stringify({ url, tool, error: err.message }, null, 2),
451
+ },
452
+ ],
453
+ isError: true,
454
+ };
455
+ }
456
+ }
457
+ case "read_remote_resource": {
458
+ const { url, uri } = args;
459
+ try {
460
+ const result = await readRemoteResource(url, uri);
461
+ return {
462
+ content: result?.contents?.map((c) => ({
463
+ type: "text",
464
+ text: typeof c.text === "string" ? c.text : JSON.stringify(c, null, 2),
465
+ })) || [
466
+ { type: "text", text: JSON.stringify(result, null, 2) },
467
+ ],
468
+ };
469
+ }
470
+ catch (err) {
471
+ return {
472
+ content: [
473
+ {
474
+ type: "text",
475
+ text: JSON.stringify({ url, uri, error: err.message }, null, 2),
476
+ },
477
+ ],
478
+ isError: true,
479
+ };
480
+ }
481
+ }
482
+ case "get_remote_prompt": {
483
+ const { url, prompt, arguments: promptArgs } = args;
484
+ try {
485
+ const result = await getRemotePrompt(url, prompt, promptArgs || {});
486
+ return {
487
+ content: [
488
+ { type: "text", text: JSON.stringify(result, null, 2) },
489
+ ],
490
+ };
491
+ }
492
+ catch (err) {
493
+ return {
494
+ content: [
495
+ {
496
+ type: "text",
497
+ text: JSON.stringify({ url, prompt, error: err.message }, null, 2),
498
+ },
499
+ ],
500
+ isError: true,
501
+ };
502
+ }
503
+ }
504
+ default:
505
+ throw new Error(`Unknown tool: ${name}`);
506
+ }
507
+ });
508
+ // --- Start Server ---
509
+ async function main() {
510
+ const transport = new stdio_js_1.StdioServerTransport();
511
+ await server.connect(transport);
512
+ console.error("mcp-browse server running on stdio");
513
+ }
514
+ main().catch((err) => {
515
+ console.error("Fatal error:", err);
516
+ process.exit(1);
517
+ });
package/package.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "mcp-www",
3
+ "version": "0.1.0",
4
+ "description": "Lightweight MCP server for DNS-based agent service discovery over UDP. No registry needed.",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "bin": {
8
+ "mcp-browse": "dist/index.js"
9
+ },
10
+ "files": [
11
+ "dist",
12
+ "README.md"
13
+ ],
14
+ "scripts": {
15
+ "build": "tsc",
16
+ "start": "node dist/index.js",
17
+ "dev": "ts-node src/index.ts"
18
+ },
19
+ "keywords": [
20
+ "mcp",
21
+ "dns",
22
+ "service-discovery",
23
+ "udp",
24
+ "model-context-protocol",
25
+ "agent"
26
+ ],
27
+ "author": "kormco",
28
+ "license": "MIT",
29
+ "dependencies": {
30
+ "@modelcontextprotocol/sdk": "^1.0.0"
31
+ },
32
+ "devDependencies": {
33
+ "typescript": "^5.4.0",
34
+ "ts-node": "^10.9.0",
35
+ "@types/node": "^20.0.0"
36
+ }
37
+ }