infranodus-mcp-server 1.2.4 → 1.4.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.
Files changed (129) hide show
  1. package/README.md +57 -31
  2. package/dist/http-server.js +209 -27
  3. package/dist/http-server.js.map +1 -1
  4. package/dist/index.d.ts.map +1 -1
  5. package/dist/index.js +12 -4
  6. package/dist/index.js.map +1 -1
  7. package/dist/instructions.d.ts +11 -0
  8. package/dist/instructions.d.ts.map +1 -0
  9. package/dist/instructions.js +37 -0
  10. package/dist/instructions.js.map +1 -0
  11. package/dist/prompts/index.js +1 -1
  12. package/dist/resources/about.d.ts.map +1 -1
  13. package/dist/resources/about.js +26 -24
  14. package/dist/resources/about.js.map +1 -1
  15. package/dist/resources/llms-txt.d.ts +33 -0
  16. package/dist/resources/llms-txt.d.ts.map +1 -0
  17. package/dist/resources/llms-txt.js +54 -0
  18. package/dist/resources/llms-txt.js.map +1 -0
  19. package/dist/schemas/index.d.ts +673 -86
  20. package/dist/schemas/index.d.ts.map +1 -1
  21. package/dist/schemas/index.js +304 -87
  22. package/dist/schemas/index.js.map +1 -1
  23. package/dist/tools/analyzeExistingGraph.d.ts.map +1 -1
  24. package/dist/tools/analyzeExistingGraph.js +13 -8
  25. package/dist/tools/analyzeExistingGraph.js.map +1 -1
  26. package/dist/tools/analyzeText.d.ts +36 -0
  27. package/dist/tools/analyzeText.d.ts.map +1 -0
  28. package/dist/tools/analyzeText.js +96 -0
  29. package/dist/tools/analyzeText.js.map +1 -0
  30. package/dist/tools/createKnowledgeGraph.d.ts +2 -2
  31. package/dist/tools/createKnowledgeGraph.d.ts.map +1 -1
  32. package/dist/tools/createKnowledgeGraph.js +26 -2
  33. package/dist/tools/createKnowledgeGraph.js.map +1 -1
  34. package/dist/tools/developConceptualBridges.d.ts +4 -3
  35. package/dist/tools/developConceptualBridges.d.ts.map +1 -1
  36. package/dist/tools/developConceptualBridges.js +45 -11
  37. package/dist/tools/developConceptualBridges.js.map +1 -1
  38. package/dist/tools/developLatentTopics.d.ts +4 -3
  39. package/dist/tools/developLatentTopics.d.ts.map +1 -1
  40. package/dist/tools/developLatentTopics.js +45 -11
  41. package/dist/tools/developLatentTopics.js.map +1 -1
  42. package/dist/tools/developTextTool.d.ts +3 -2
  43. package/dist/tools/developTextTool.d.ts.map +1 -1
  44. package/dist/tools/developTextTool.js +73 -23
  45. package/dist/tools/developTextTool.js.map +1 -1
  46. package/dist/tools/generateContentGaps.d.ts +3 -2
  47. package/dist/tools/generateContentGaps.d.ts.map +1 -1
  48. package/dist/tools/generateContentGaps.js +38 -10
  49. package/dist/tools/generateContentGaps.js.map +1 -1
  50. package/dist/tools/generateContextualHint.d.ts +3 -2
  51. package/dist/tools/generateContextualHint.d.ts.map +1 -1
  52. package/dist/tools/generateContextualHint.js +36 -8
  53. package/dist/tools/generateContextualHint.js.map +1 -1
  54. package/dist/tools/generateDifferenceGraphFromTexts.d.ts +15 -6
  55. package/dist/tools/generateDifferenceGraphFromTexts.d.ts.map +1 -1
  56. package/dist/tools/generateDifferenceGraphFromTexts.js +90 -7
  57. package/dist/tools/generateDifferenceGraphFromTexts.js.map +1 -1
  58. package/dist/tools/generateGoogleResultsVsQueriesGraph.d.ts +3 -1
  59. package/dist/tools/generateGoogleResultsVsQueriesGraph.d.ts.map +1 -1
  60. package/dist/tools/generateGoogleResultsVsQueriesGraph.js +18 -9
  61. package/dist/tools/generateGoogleResultsVsQueriesGraph.js.map +1 -1
  62. package/dist/tools/generateGoogleSearchQueriesGraph.d.ts +3 -1
  63. package/dist/tools/generateGoogleSearchQueriesGraph.d.ts.map +1 -1
  64. package/dist/tools/generateGoogleSearchQueriesGraph.js +21 -10
  65. package/dist/tools/generateGoogleSearchQueriesGraph.js.map +1 -1
  66. package/dist/tools/generateGoogleSearchResultsGraph.d.ts +4 -2
  67. package/dist/tools/generateGoogleSearchResultsGraph.d.ts.map +1 -1
  68. package/dist/tools/generateGoogleSearchResultsGraph.js +22 -10
  69. package/dist/tools/generateGoogleSearchResultsGraph.js.map +1 -1
  70. package/dist/tools/generateKnowledgeGraph.d.ts +2 -2
  71. package/dist/tools/generateKnowledgeGraph.d.ts.map +1 -1
  72. package/dist/tools/generateKnowledgeGraph.js +26 -2
  73. package/dist/tools/generateKnowledgeGraph.js.map +1 -1
  74. package/dist/tools/generateMergedGraphFromTexts.d.ts +52 -0
  75. package/dist/tools/generateMergedGraphFromTexts.d.ts.map +1 -0
  76. package/dist/tools/generateMergedGraphFromTexts.js +159 -0
  77. package/dist/tools/generateMergedGraphFromTexts.js.map +1 -0
  78. package/dist/tools/generateOverlapGraphFromTexts.d.ts +15 -6
  79. package/dist/tools/generateOverlapGraphFromTexts.d.ts.map +1 -1
  80. package/dist/tools/generateOverlapGraphFromTexts.js +91 -7
  81. package/dist/tools/generateOverlapGraphFromTexts.js.map +1 -1
  82. package/dist/tools/generateResearchIdeas.d.ts +4 -2
  83. package/dist/tools/generateResearchIdeas.d.ts.map +1 -1
  84. package/dist/tools/generateResearchIdeas.js +54 -13
  85. package/dist/tools/generateResearchIdeas.js.map +1 -1
  86. package/dist/tools/generateResearchQuestions.d.ts +3 -2
  87. package/dist/tools/generateResearchQuestions.d.ts.map +1 -1
  88. package/dist/tools/generateResearchQuestions.js +44 -9
  89. package/dist/tools/generateResearchQuestions.js.map +1 -1
  90. package/dist/tools/generateResponses.d.ts +34 -0
  91. package/dist/tools/generateResponses.d.ts.map +1 -0
  92. package/dist/tools/generateResponses.js +110 -0
  93. package/dist/tools/generateResponses.js.map +1 -0
  94. package/dist/tools/generateSEOGraphTool.d.ts +2 -0
  95. package/dist/tools/generateSEOGraphTool.d.ts.map +1 -1
  96. package/dist/tools/generateSEOGraphTool.js +16 -75
  97. package/dist/tools/generateSEOGraphTool.js.map +1 -1
  98. package/dist/tools/generateTopicalClusters.d.ts +3 -2
  99. package/dist/tools/generateTopicalClusters.d.ts.map +1 -1
  100. package/dist/tools/generateTopicalClusters.js +38 -10
  101. package/dist/tools/generateTopicalClusters.js.map +1 -1
  102. package/dist/tools/getMemory.d.ts.map +1 -1
  103. package/dist/tools/getMemory.js +7 -5
  104. package/dist/tools/getMemory.js.map +1 -1
  105. package/dist/tools/index.d.ts +4 -2
  106. package/dist/tools/index.d.ts.map +1 -1
  107. package/dist/tools/index.js +4 -2
  108. package/dist/tools/index.js.map +1 -1
  109. package/dist/tools/listGraphs.d.ts +1 -1
  110. package/dist/tools/listGraphs.d.ts.map +1 -1
  111. package/dist/tools/listGraphs.js +7 -5
  112. package/dist/tools/listGraphs.js.map +1 -1
  113. package/dist/tools/optimizeTextStructure.d.ts +34 -0
  114. package/dist/tools/optimizeTextStructure.d.ts.map +1 -0
  115. package/dist/tools/optimizeTextStructure.js +108 -0
  116. package/dist/tools/optimizeTextStructure.js.map +1 -0
  117. package/dist/types/index.d.ts +23 -0
  118. package/dist/types/index.d.ts.map +1 -1
  119. package/dist/utils/transformers.d.ts +2 -1
  120. package/dist/utils/transformers.d.ts.map +1 -1
  121. package/dist/utils/transformers.js +39 -1
  122. package/dist/utils/transformers.js.map +1 -1
  123. package/dist/utils/urlContent.d.ts +35 -0
  124. package/dist/utils/urlContent.d.ts.map +1 -0
  125. package/dist/utils/urlContent.js +87 -0
  126. package/dist/utils/urlContent.js.map +1 -0
  127. package/llms-full.txt +469 -0
  128. package/llms.txt +64 -0
  129. package/package.json +4 -2
package/README.md CHANGED
@@ -34,68 +34,75 @@ InfraNodus MCP Server enables LLM workflows and AI assistants to analyze text us
34
34
  - Access previously saved analyses
35
35
  - Export graph data with full statistics
36
36
 
37
- 3. **generate_content_gaps**
37
+ 3. **analyze_text**
38
+
39
+ - Analyze a text, URL, or YouTube transcript
40
+ - Extract and analyze a graph from text or URL; provide either text or url
41
+ - Get topics, clusters, statements, graph structure, and AI summary as requested
42
+
43
+ 4. **generate_content_gaps**
38
44
 
39
45
  - Detect missing connections in discourse
40
46
  - Identify underexplored topics
41
47
  - Generate research questions
42
48
  - Suggest content development opportunities
43
49
 
44
- 4. **generate_topical_clusters**
50
+ 5. **generate_topical_clusters**
45
51
 
46
52
  - Generate topics and clusters of keywords from text using knowledge graph analysis
47
53
  - Make sure to beyond genetic insights and detect smaller topics
48
54
  - Use the topical clusters to establish topical authority for SEO
49
55
 
50
- 5. **generate_contextual_hint**
56
+ 6. **generate_contextual_hint**
51
57
 
52
58
  - Generate a topical overview of a text and provide insights for LLMs to generate better responses
53
59
  - Use it to get a high-level understanding of a text
54
60
  - Use it to augment prompts in your LLM workflows and AI assistants
55
61
 
56
- 6. **generate_research_questions**
62
+ 7. **generate_research_questions**
57
63
 
58
- - Generate research questions that bridge content gaps
64
+ - Generate research questions that bridge content gaps from text, URL, or an existing InfraNodus graph
59
65
  - Use them as prompts in your LLM models and AI workflows
60
66
  - Use any AI model (included in InfraNodus API)
61
67
  - Content gaps are identified based on topical clustering
62
68
 
63
- 7. **generate_research_ideas**
69
+ 8. **generate_research_ideas**
64
70
 
65
71
  - Generate innovative research ideas based on content gaps identified in the text
66
72
  - Get actionable ideas to improve the text and develop the discourse
67
73
  - Use any AI model (included in InfraNodus API)
68
74
  - Ideas are generated from gaps between topical clusters
69
75
 
70
- 8. **research_questions_from_graph**
76
+ 9. **optimize_text_structure**
71
77
 
72
- - Generate research questions based on an existing InfraNodus graph
73
- - Use them as prompts in your LLM models
74
- - Use any AI model (included in InfraNodus API)
75
- - Content gaps are identified based on topical clustering
78
+ - Analyze the level of bias and coherence in text using knowledge graph analysis
79
+ - If the text is too biased, develop the represented topics to balance the discourse
80
+ - If the text is focused or diversified, develop the content gaps to deepen the analysis
81
+ - If the text is dispersed, focus the most common gap topics to improve coherence
82
+ - Choose response type: response, idea, question, or transcend
76
83
 
77
- 9. **generate_responses_from_graph**
84
+ 10. **generate_responses_from_graph**
78
85
 
79
- - Generate responses based on an existing InfraNodus graph
80
- - Integrate them into your LLM workflows and AI assistants
81
- - Use any AI model (included in InfraNodus API)
82
- - Use any prompt
86
+ - Generate responses based on an existing InfraNodus graph
87
+ - Integrate them into your LLM workflows and AI assistants
88
+ - Use any AI model (included in InfraNodus API)
89
+ - Use any prompt
83
90
 
84
- 10. **develop_conceptual_bridges**
91
+ 11. **develop_conceptual_bridges**
85
92
 
86
93
  - Analyze text and develop latent ideas based on concepts that connect this text to a broader discourse
87
94
  - Discover hidden themes and patterns that link your text to wider contexts
88
95
  - Use any AI model (included in InfraNodus API)
89
96
  - Generate insights that help develop the discourse
90
97
 
91
- 11. **develop_latent_topics**
98
+ 12. **develop_latent_topics**
92
99
 
93
100
  - Analyze text and extract underdeveloped topics with ideas on how to develop them
94
101
  - Identify topics that need more attention and elaboration
95
102
  - Use any AI model (included in InfraNodus API)
96
103
  - Get actionable suggestions for content expansion
97
104
 
98
- 12. **develop_text_tool**
105
+ 13. **develop_text_tool**
99
106
 
100
107
  - Comprehensive text analysis combining content gap ideas, latent topics, and conceptual bridges
101
108
  - Executes multiple analyses in sequence with progress tracking
@@ -103,44 +110,49 @@ InfraNodus MCP Server enables LLM workflows and AI assistants to analyze text us
103
110
  - Identifies latent topics and conceptual bridges to develop
104
111
  - Finds content gaps for deeper exploration
105
112
 
106
- 13. **create_knowledge_graph**
113
+ 14. **create_knowledge_graph**
107
114
 
108
115
  - Create a knowledge graph in InfraNodus from text and provide a link to it
109
116
  - Use it to create a knowledge graph in InfraNodus from text
110
117
 
111
- 14. **overlap_between_texts**
118
+ 15. **overlap_between_texts**
112
119
 
113
120
  - Create knowledge graphs from two or more texts and find the overlap (similarities) between them
114
121
  - Use it to find similar topics and keywords across different texts
115
122
 
116
- 15. **difference_between_texts**
123
+ 16. **merged_graph_from_texts**
124
+
125
+ - Build a graph of all the texts and URLs provided, providing topical clusters and gaps present in the merged graph generated from all the texts
126
+ - Use it to combine multiple sources into one graph and see clusters and content gaps across the merged content
127
+
128
+ 17. **difference_between_texts**
117
129
 
118
130
  - Compare knowledge graphs from two or more texts and find what's not present in the first graph that's present in the others
119
131
  - Use it to find how one text can be enriched with the others
120
132
 
121
- 16. **analyze_google_search_results**
133
+ 18. **analyze_google_search_results**
122
134
 
123
135
  - Generate a graph with keywords and topics for Google search results for a certain query
124
136
  - Use it to understand the current informational supply (what people find)
125
137
 
126
- 17. **analyze_related_search_queries**
138
+ 19. **analyze_related_search_queries**
127
139
 
128
140
  - Generate a graph from the search queries suggested by Google for a certain query
129
141
  - Use it to understand the current informational demand (what people are looking for)
130
142
 
131
- 18. **search_queries_vs_search_results**
143
+ 20. **search_queries_vs_search_results**
132
144
 
133
145
  - Generate a graph of keyword combinations and topics people tend to search for that do not readily appear in the search results for the same queries
134
146
  - Use it to understand what people search for but don't yet find
135
147
 
136
- 19. **generate_seo_report**
148
+ 21. **generate_seo_report**
137
149
 
138
150
  - Analyze content for SEO optimization by comparing it with Google search results and search queries
139
151
  - Identify content gaps and opportunities for better search visibility
140
152
  - Get comprehensive analysis of what's in search results but not in your text
141
153
  - Discover what people search for but don't find in current results
142
154
 
143
- 20. **memory_add_relations**
155
+ 22. **memory_add_relations**
144
156
 
145
157
  - Add relations to the InfraNodus memory from text
146
158
  - Automatically detect entities or use [[wikilinks]] syntax to mark them
@@ -148,7 +160,7 @@ InfraNodus MCP Server enables LLM workflows and AI assistants to analyze text us
148
160
  - Support automatic entity extraction or manual entity marking
149
161
  - Provide links to created memory graphs for easy access
150
162
 
151
- 21. **memory_get_relations**
163
+ 23. **memory_get_relations**
152
164
 
153
165
  - Retrieve relations from InfraNodus memory for specific entities
154
166
  - Search for entity relations using [[wikilinks]] syntax
@@ -156,7 +168,7 @@ InfraNodus MCP Server enables LLM workflows and AI assistants to analyze text us
156
168
  - Extract statements and relationships from stored knowledge graphs
157
169
  - Support both entity-specific searches and full context retrieval
158
170
 
159
- 22. **retrieve_from_knowledge_base**
171
+ 24. **retrieve_from_knowledge_base**
160
172
 
161
173
  - Retrieve context from an existing InfraNodus knowledge graph using GraphRAG
162
174
  - Query your knowledge base with a natural language prompt to get relevant statements
@@ -164,13 +176,13 @@ InfraNodus MCP Server enables LLM workflows and AI assistants to analyze text us
164
176
  - Optionally retrieve the full graph, statements, or extended analysis
165
177
  - Ideal for augmenting LLM responses with domain-specific knowledge
166
178
 
167
- 23. **search**
179
+ 25. **search**
168
180
 
169
181
  - Search through existing InfraNodus graphs
170
182
  - Also use it to search through the public graphs of a specific user
171
183
  - Compatible with ChatGPT Deep Research mode via Developer Mode > Connectors
172
184
 
173
- 24. **fetch**
185
+ 26. **fetch**
174
186
 
175
187
  - Fetch a specific search result for a graph
176
188
  - Can be used in ChatGPT Deep Research mode via Developer Mode > Connectors
@@ -523,6 +535,20 @@ Retrieves and analyzes an existing graph from your InfraNodus account.
523
535
  - `includeStatements` (boolean): Include statements in response
524
536
  - `includeGraphSummary` (boolean): Include graph summary
525
537
 
538
+ ### analyze_text
539
+
540
+ Analyze a text, URL, or YouTube transcript. Extract and analyze a graph from text or URL; provide either text or url.
541
+
542
+ **Parameters:**
543
+
544
+ - `text` (string, optional): Text to analyze. Provide either this or url.
545
+ - `url` (string, optional): URL to fetch content from (e.g. webpage or YouTube transcript). Provide either this or text.
546
+ - `includeStatements` (boolean): Include processed statements in response
547
+ - `includeGraph` (boolean): Include full graph structure in response
548
+ - `addNodesAndEdges` (boolean): Include nodes and edges in response
549
+ - `includeGraphSummary` (boolean): Include AI-generated graph summary for RAG prompt augmentation
550
+ - `modifyAnalyzedText` (string): Entity detection — "none", "detectEntities", or "extractEntitiesOnly"
551
+
526
552
  ### generate_content_gaps
527
553
 
528
554
  Identifies content gaps and missing connections in text.
@@ -5,16 +5,36 @@
5
5
  import express from "express";
6
6
  import cors from "cors";
7
7
  import helmet from "helmet";
8
+ import crypto from "crypto";
8
9
  import * as dotenv from "dotenv";
9
10
  import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
11
+ import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
10
12
  import createServer from "./index.js";
11
13
  import { exchangeApiKeyForToken, verifyAccessToken, revokeSession, getSessionCount, registerClient, getClient, createAuthorizationCode, exchangeAuthorizationCode, validateApiKey, } from "./auth/oauth-provider.js";
14
+ import * as fs from "fs";
15
+ import * as path from "path";
16
+ import { fileURLToPath } from "url";
12
17
  // Load environment variables
13
18
  dotenv.config();
14
19
  // Configuration
15
20
  const PORT = parseInt(process.env.PORT || "3000", 10);
16
21
  const CORS_ORIGIN = process.env.CORS_ORIGIN || "*";
17
22
  const INFRANODUS_API_BASE = process.env.INFRANODUS_API_BASE || "https://infranodus.com/api/v1";
23
+ // Allow HTTP only for localhost/127.0.0.1 (local dev); otherwise require HTTPS
24
+ function isRedirectUriAllowed(redirectUri) {
25
+ if (redirectUri.startsWith("https://"))
26
+ return true;
27
+ if (!redirectUri.startsWith("http://"))
28
+ return false;
29
+ try {
30
+ const u = new URL(redirectUri);
31
+ const host = u.hostname.toLowerCase();
32
+ return host === "localhost" || host === "127.0.0.1";
33
+ }
34
+ catch {
35
+ return false;
36
+ }
37
+ }
18
38
  // Store auth info on request (using a symbol to avoid conflicts with MCP SDK)
19
39
  const AUTH_KEY = Symbol("auth");
20
40
  // Create Express app
@@ -40,16 +60,21 @@ app.use((req, res, next) => {
40
60
  })}`);
41
61
  next();
42
62
  });
43
- // Store transports per session for MCP
63
+ // Store transports per session for MCP (Streamable HTTP)
44
64
  const transports = new Map();
65
+ // Store SSE transports per session for legacy MCP clients
66
+ const sseTransports = new Map();
45
67
  /**
46
- * Auth middleware - validates Bearer token and attaches auth info to request
68
+ * Auth middleware - validates Bearer token and attaches auth info to request.
69
+ * Supports both JWT access tokens (from OAuth flow) and raw InfraNodus API keys.
47
70
  */
48
- function authMiddleware(req, res, next) {
71
+ async function authMiddleware(req, res, next) {
49
72
  console.log(`[AUTH] Checking auth for ${req.method} ${req.path}`);
50
73
  const authHeader = req.headers.authorization;
51
74
  if (!authHeader || !authHeader.startsWith("Bearer ")) {
52
75
  console.log(`[AUTH] Missing or invalid Authorization header`);
76
+ const baseUrl = `${req.protocol}://${req.get("host")}`;
77
+ res.setHeader("WWW-Authenticate", `Bearer resource_metadata="${baseUrl}/.well-known/oauth-protected-resource"`);
53
78
  const error = {
54
79
  error: "unauthorized",
55
80
  error_description: "Missing or invalid Authorization header",
@@ -59,19 +84,41 @@ function authMiddleware(req, res, next) {
59
84
  }
60
85
  const token = authHeader.slice(7); // Remove "Bearer " prefix
61
86
  console.log(`[AUTH] Token present, verifying...`);
87
+ // Try JWT verification first (fast path for OAuth-authenticated clients)
62
88
  const authInfo = verifyAccessToken(token);
63
- if (!authInfo) {
64
- console.log(`[AUTH] Token verification failed`);
65
- const error = {
66
- error: "invalid_token",
67
- error_description: "Access token is invalid or expired",
68
- };
69
- res.status(401).json(error);
89
+ if (authInfo) {
90
+ console.log(`[AUTH] Authenticated via JWT as ${authInfo.userName} (${authInfo.userId})`);
91
+ req[AUTH_KEY] = authInfo;
92
+ next();
70
93
  return;
71
94
  }
72
- console.log(`[AUTH] Authenticated as ${authInfo.userName} (${authInfo.userId})`);
73
- req[AUTH_KEY] = authInfo;
74
- next();
95
+ // JWT failed — try treating the token as a raw InfraNodus API key
96
+ console.log(`[AUTH] JWT verification failed, trying as raw API key...`);
97
+ try {
98
+ const userInfo = await validateApiKey(token);
99
+ if (userInfo) {
100
+ console.log(`[AUTH] Authenticated via raw API key as ${userInfo.userName} (${userInfo.userId})`);
101
+ req[AUTH_KEY] = {
102
+ userId: userInfo.userId,
103
+ userName: userInfo.userName,
104
+ apiKey: token,
105
+ sessionId: crypto.randomUUID(),
106
+ };
107
+ next();
108
+ return;
109
+ }
110
+ }
111
+ catch (err) {
112
+ console.log(`[AUTH] Raw API key validation error: ${err}`);
113
+ }
114
+ console.log(`[AUTH] All authentication methods failed`);
115
+ const baseUrl = `${req.protocol}://${req.get("host")}`;
116
+ res.setHeader("WWW-Authenticate", `Bearer resource_metadata="${baseUrl}/.well-known/oauth-protected-resource"`);
117
+ const error = {
118
+ error: "invalid_token",
119
+ error_description: "Access token is invalid or expired",
120
+ };
121
+ res.status(401).json(error);
75
122
  }
76
123
  /**
77
124
  * Helper to get auth info from request
@@ -142,12 +189,11 @@ app.get("/oauth/authorize", (req, res) => {
142
189
  });
143
190
  return;
144
191
  }
145
- // Accept any HTTPS redirect_uri (stateless validation)
146
- // The real security is in the API key validation
147
- if (!redirect_uri.startsWith("https://")) {
192
+ // Accept HTTPS or http://localhost / http://127.0.0.1 (for local dev)
193
+ if (!isRedirectUriAllowed(redirect_uri)) {
148
194
  res.status(400).json({
149
195
  error: "invalid_request",
150
- error_description: "redirect_uri must use HTTPS",
196
+ error_description: "redirect_uri must use HTTPS or http://localhost or http://127.0.0.1",
151
197
  });
152
198
  return;
153
199
  }
@@ -205,8 +251,8 @@ app.post("/oauth/authorize", express.urlencoded({ extended: true }), async (req,
205
251
  res.status(400).json({ error: "invalid_client" });
206
252
  return;
207
253
  }
208
- // Validate redirect_uri (stateless - accept any HTTPS URL)
209
- if (!redirect_uri || !redirect_uri.startsWith("https://")) {
254
+ // Validate redirect_uri (HTTPS or localhost/127.0.0.1 for local dev)
255
+ if (!redirect_uri || !isRedirectUriAllowed(redirect_uri)) {
210
256
  res.status(400).json({
211
257
  error: "invalid_request",
212
258
  error_description: "Invalid redirect_uri",
@@ -365,6 +411,19 @@ app.get("/.well-known/openid-configuration", (req, res) => {
365
411
  scopes_supported: ["mcp", "read", "write"],
366
412
  });
367
413
  });
414
+ /**
415
+ * GET /.well-known/oauth-protected-resource - OAuth2 Protected Resource Metadata (RFC 9728)
416
+ * MCP clients fetch this to discover the authorization server for this resource.
417
+ */
418
+ app.get("/.well-known/oauth-protected-resource", (req, res) => {
419
+ const baseUrl = `${req.protocol}://${req.get("host")}`;
420
+ res.json({
421
+ resource: baseUrl,
422
+ authorization_servers: [baseUrl],
423
+ scopes_supported: ["mcp", "read", "write"],
424
+ bearer_methods_supported: ["header"],
425
+ });
426
+ });
368
427
  // ============================================================================
369
428
  // MCP Endpoints
370
429
  // ============================================================================
@@ -481,14 +540,23 @@ app.post("/mcp", authMiddleware, async (req, res) => {
481
540
  }
482
541
  });
483
542
  /**
484
- * GET /mcp - SSE endpoint for server-to-client notifications
543
+ * GET /mcp - Auto-detect transport:
544
+ * SSE client (no mcp-session-id, Accept: text/event-stream) → SSE transport
545
+ * Streamable HTTP client (has mcp-session-id) → existing Streamable HTTP
485
546
  */
486
547
  app.get("/mcp", authMiddleware, async (req, res) => {
487
548
  try {
488
- await handleMcpRequest(req, res);
549
+ const wantsSSE = !req.headers["mcp-session-id"] &&
550
+ req.headers.accept?.includes("text/event-stream");
551
+ if (wantsSSE) {
552
+ await handleSseConnection(req, res);
553
+ }
554
+ else {
555
+ await handleMcpRequest(req, res);
556
+ }
489
557
  }
490
558
  catch (error) {
491
- console.error("MCP GET error:", error);
559
+ console.error("MCP GET /mcp error:", error);
492
560
  if (!res.headersSent) {
493
561
  res.status(500).json({ error: "Internal server error" });
494
562
  }
@@ -509,6 +577,97 @@ app.delete("/mcp", authMiddleware, async (req, res) => {
509
577
  }
510
578
  });
511
579
  // ============================================================================
580
+ // Legacy SSE Transport Endpoints (for clients like Make.com)
581
+ // ============================================================================
582
+ /**
583
+ * Shared helper: establish an SSE connection for the authenticated user.
584
+ * Creates an SSEServerTransport, wires up an MCP server, and starts streaming.
585
+ */
586
+ async function handleSseConnection(req, res) {
587
+ const auth = getAuth(req);
588
+ if (!auth) {
589
+ res.status(401).json({ error: "unauthorized" });
590
+ return;
591
+ }
592
+ console.log(`[SSE] New SSE connection from ${auth.userName} (${auth.userId})`);
593
+ const transport = new SSEServerTransport("/message", res);
594
+ const sessionId = transport.sessionId;
595
+ sseTransports.set(sessionId, transport);
596
+ const config = {
597
+ apiKey: auth.apiKey,
598
+ apiBase: INFRANODUS_API_BASE,
599
+ };
600
+ const server = createServer({ config });
601
+ transport.onclose = () => {
602
+ console.log(`[SSE] Transport closed for session ${sessionId}`);
603
+ sseTransports.delete(sessionId);
604
+ };
605
+ await server.connect(transport);
606
+ console.log(`[SSE] Server connected, session ${sessionId}`);
607
+ }
608
+ /**
609
+ * GET /sse - Establish SSE stream (legacy MCP transport)
610
+ * The client connects here first to get an SSE stream, then POSTs messages to /message
611
+ */
612
+ app.get("/sse", authMiddleware, async (req, res) => {
613
+ try {
614
+ await handleSseConnection(req, res);
615
+ }
616
+ catch (error) {
617
+ console.error("SSE GET /sse error:", error);
618
+ if (!res.headersSent) {
619
+ res.status(500).json({ error: "Internal server error" });
620
+ }
621
+ }
622
+ });
623
+ /**
624
+ * POST /message - Receive messages from SSE clients (legacy MCP transport)
625
+ */
626
+ app.post("/message", authMiddleware, async (req, res) => {
627
+ const sessionId = req.query.sessionId;
628
+ if (!sessionId) {
629
+ res.status(400).json({ error: "Missing sessionId query parameter" });
630
+ return;
631
+ }
632
+ const transport = sseTransports.get(sessionId);
633
+ if (!transport) {
634
+ res.status(404).json({
635
+ error: "Session not found. The SSE connection may have been closed.",
636
+ });
637
+ return;
638
+ }
639
+ console.log(`[SSE] POST /message for session ${sessionId}`);
640
+ try {
641
+ await transport.handlePostMessage(req, res, req.body);
642
+ }
643
+ catch (error) {
644
+ console.error(`[SSE] Error handling message:`, error);
645
+ if (!res.headersSent) {
646
+ res.status(500).json({ error: "Internal server error" });
647
+ }
648
+ }
649
+ });
650
+ // ============================================================================
651
+ // LLM Documentation Endpoints (llms.txt standard)
652
+ // ============================================================================
653
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
654
+ function serveLlmsFile(filename, res) {
655
+ const filePath = path.resolve(__dirname, "../", filename);
656
+ try {
657
+ const content = fs.readFileSync(filePath, "utf-8");
658
+ res.type("text/markdown; charset=utf-8").send(content);
659
+ }
660
+ catch {
661
+ res.status(404).send(`${filename} not found`);
662
+ }
663
+ }
664
+ app.get("/llms.txt", (_req, res) => {
665
+ serveLlmsFile("llms.txt", res);
666
+ });
667
+ app.get("/llms-full.txt", (_req, res) => {
668
+ serveLlmsFile("llms-full.txt", res);
669
+ });
670
+ // ============================================================================
512
671
  // Health & Status Endpoints
513
672
  // ============================================================================
514
673
  /**
@@ -523,16 +682,20 @@ app.get("/health", (req, res) => {
523
682
  });
524
683
  });
525
684
  /**
526
- * GET / - Server info (only when no auth header - otherwise it's MCP)
685
+ * GET / - Server info (only when no auth header and not an MCP/SSE client)
527
686
  */
528
687
  app.get("/", (req, res, next) => {
529
688
  // If auth header is present, treat as MCP request
530
689
  if (req.headers.authorization) {
531
690
  return next();
532
691
  }
692
+ // If client wants SSE or MCP, let it fall through to authMiddleware (→ 401 triggers OAuth)
693
+ if (req.headers.accept?.includes("text/event-stream")) {
694
+ return next();
695
+ }
533
696
  res.json({
534
697
  name: "InfraNodus MCP Server",
535
- version: "1.2.4",
698
+ version: "1.4.0",
536
699
  description: "MCP server for InfraNodus knowledge graph analysis",
537
700
  endpoints: {
538
701
  oauth: {
@@ -546,6 +709,10 @@ app.get("/", (req, res, next) => {
546
709
  disconnect: "DELETE / or DELETE /mcp",
547
710
  },
548
711
  health: "GET /health",
712
+ llms: {
713
+ index: "GET /llms.txt",
714
+ full: "GET /llms-full.txt",
715
+ },
549
716
  },
550
717
  authentication: "Bearer token (obtain via /oauth/token with api_key)",
551
718
  });
@@ -566,11 +733,20 @@ app.post("/", authMiddleware, async (req, res) => {
566
733
  }
567
734
  });
568
735
  /**
569
- * GET / with auth - SSE endpoint for MCP at root
736
+ * GET / with auth - Auto-detect transport:
737
+ * SSE client (no mcp-session-id, Accept: text/event-stream) → SSE transport
738
+ * Streamable HTTP client (has mcp-session-id) → existing Streamable HTTP
570
739
  */
571
740
  app.get("/", authMiddleware, async (req, res) => {
572
741
  try {
573
- await handleMcpRequest(req, res);
742
+ const wantsSSE = !req.headers["mcp-session-id"] &&
743
+ req.headers.accept?.includes("text/event-stream");
744
+ if (wantsSSE) {
745
+ await handleSseConnection(req, res);
746
+ }
747
+ else {
748
+ await handleMcpRequest(req, res);
749
+ }
574
750
  }
575
751
  catch (error) {
576
752
  console.error("MCP GET / error:", error);
@@ -599,10 +775,12 @@ app.delete("/", authMiddleware, async (req, res) => {
599
775
  // Handle graceful shutdown
600
776
  process.on("SIGINT", () => {
601
777
  console.log("\nShutting down HTTP server...");
602
- // Close all transports
603
778
  for (const transport of transports.values()) {
604
779
  transport.close();
605
780
  }
781
+ for (const transport of sseTransports.values()) {
782
+ transport.close();
783
+ }
606
784
  process.exit(0);
607
785
  });
608
786
  process.on("SIGTERM", () => {
@@ -610,6 +788,9 @@ process.on("SIGTERM", () => {
610
788
  for (const transport of transports.values()) {
611
789
  transport.close();
612
790
  }
791
+ for (const transport of sseTransports.values()) {
792
+ transport.close();
793
+ }
613
794
  process.exit(0);
614
795
  });
615
796
  // Start the server
@@ -618,6 +799,7 @@ app.listen(PORT, () => {
618
799
  console.log(` - Health: http://localhost:${PORT}/health`);
619
800
  console.log(` - OAuth token: POST http://localhost:${PORT}/oauth/token`);
620
801
  console.log(` - MCP endpoint: http://localhost:${PORT}/mcp`);
802
+ console.log(` - SSE endpoint: http://localhost:${PORT}/sse (legacy)`);
621
803
  console.log("");
622
804
  console.log("Environment:");
623
805
  console.log(` - CORS_ORIGIN: ${CORS_ORIGIN}`);