mcp-server-andru-intelligence 1.3.0 → 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.
package/package.json CHANGED
@@ -1,11 +1,12 @@
1
1
  {
2
2
  "name": "mcp-server-andru-intelligence",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
4
4
  "mcpName": "io.github.geter-andru/andru-intelligence",
5
5
  "description": "Buyer intelligence for technical founders who sell to enterprises — know who to target, what to say, and when to walk away. 19 MCP tools + CLI: ICP scoring, persona simulation, battlecards, deal classification, prospect discovery, sales hiring, investor matching, founder wellness.",
6
6
  "type": "module",
7
7
  "bin": {
8
8
  "andru-mcp": "./src/index.js",
9
+ "andru-mcp-http": "./src/httpServer.js",
9
10
  "andru-intel": "./src/cli.js"
10
11
  },
11
12
  "main": "./src/index.js",
@@ -45,6 +46,7 @@
45
46
  },
46
47
  "scripts": {
47
48
  "start": "node src/index.js",
49
+ "start:http": "node src/httpServer.js",
48
50
  "prepublishOnly": "node --input-type=module -e \"import {tools} from './src/catalog.js'; if(!tools?.length) {console.error('No tools in catalog'); process.exit(1)} console.log('Catalog OK:', tools.length, 'tools')\""
49
51
  },
50
52
  "files": [
package/src/client.js CHANGED
@@ -7,7 +7,7 @@
7
7
 
8
8
  const DEFAULT_API_URL = 'https://hs-andru-test.onrender.com';
9
9
  const REQUEST_TIMEOUT_MS = 60_000; // 60s for AI-calling tools
10
- const PACKAGE_VERSION = '1.3.0';
10
+ const PACKAGE_VERSION = '1.4.0';
11
11
 
12
12
  export class AndruClient {
13
13
  /**
@@ -0,0 +1,156 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Andru Revenue Intelligence MCP Server — Streamable HTTP Transport
5
+ *
6
+ * Hosted entry point for Smithery, Salesforce AgentExchange, and any
7
+ * MCP client that connects via Streamable HTTP (the MCP 2025-03-26 spec).
8
+ *
9
+ * Environment variables:
10
+ * ANDRU_API_URL (optional) — API base URL (default: https://hs-andru-test.onrender.com)
11
+ * PORT (optional) — HTTP port (default: 3100)
12
+ * HOST (optional) — Bind address (default: 0.0.0.0)
13
+ *
14
+ * Each client authenticates with X-API-Key header per request.
15
+ * Sessions are stateful — each initialization creates a scoped Server+Transport.
16
+ *
17
+ * Usage:
18
+ * PORT=3100 node src/httpServer.js
19
+ */
20
+
21
+ import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
22
+ import { createMcpExpressApp } from '@modelcontextprotocol/sdk/server/express.js';
23
+ import { randomUUID } from 'node:crypto';
24
+ import { AndruClient } from './client.js';
25
+ import { createServer } from './server.js';
26
+ import { createCachedClient } from './cachedClient.js';
27
+ import { initCache, closeCache } from './cache.js';
28
+
29
+ // Per-session transport map (stateful mode)
30
+ const transports = new Map();
31
+
32
+ async function main() {
33
+ const apiUrl = process.env.ANDRU_API_URL || 'https://hs-andru-test.onrender.com';
34
+ const port = parseInt(process.env.PORT || '3100', 10);
35
+ const host = process.env.HOST || '0.0.0.0';
36
+ const cacheEnabled = process.env.ANDRU_CACHE !== 'false';
37
+
38
+ // Initialize SQLite cache
39
+ if (cacheEnabled) {
40
+ try {
41
+ initCache();
42
+ console.log('[Andru MCP HTTP] Cache initialized');
43
+ } catch (err) {
44
+ console.warn('[Andru MCP HTTP] Cache init failed (continuing without):', err.message);
45
+ }
46
+ }
47
+
48
+ const app = createMcpExpressApp({ host });
49
+
50
+ // --- Health check ---
51
+ app.get('/health', (_req, res) => {
52
+ res.json({
53
+ status: 'ok',
54
+ transport: 'streamable-http',
55
+ sessions: transports.size,
56
+ });
57
+ });
58
+
59
+ // --- Auth middleware: require X-API-Key on MCP requests ---
60
+ function requireApiKey(req, res, next) {
61
+ const key = req.headers['x-api-key'];
62
+ if (!key) {
63
+ return res.status(401).json({
64
+ jsonrpc: '2.0',
65
+ error: { code: -32000, message: 'X-API-Key header required' },
66
+ id: null,
67
+ });
68
+ }
69
+ req.auth = { apiKey: key };
70
+ next();
71
+ }
72
+
73
+ // --- POST /mcp — JSON-RPC requests (initialize, tools/list, tools/call, etc.) ---
74
+ app.post('/mcp', requireApiKey, async (req, res) => {
75
+ const sessionId = req.headers['mcp-session-id'];
76
+ let transport = sessionId ? transports.get(sessionId) : undefined;
77
+
78
+ if (!transport) {
79
+ // New session — create per-key client, server, and transport
80
+ const baseClient = new AndruClient(req.auth.apiKey, apiUrl);
81
+ const client = cacheEnabled ? createCachedClient(baseClient) : baseClient;
82
+ const server = createServer(client);
83
+
84
+ transport = new StreamableHTTPServerTransport({
85
+ sessionIdGenerator: () => randomUUID(),
86
+ });
87
+
88
+ await server.connect(transport);
89
+
90
+ transport.onclose = () => {
91
+ if (transport.sessionId) {
92
+ transports.delete(transport.sessionId);
93
+ console.log(`[Andru MCP HTTP] Session closed: ${transport.sessionId}`);
94
+ }
95
+ };
96
+ }
97
+
98
+ await transport.handleRequest(req, res, req.body);
99
+
100
+ // Store transport by its generated session ID (set after first handleRequest)
101
+ if (transport.sessionId && !transports.has(transport.sessionId)) {
102
+ transports.set(transport.sessionId, transport);
103
+ console.log(`[Andru MCP HTTP] Session created: ${transport.sessionId}`);
104
+ }
105
+ });
106
+
107
+ // --- GET /mcp — SSE stream for server-initiated notifications ---
108
+ app.get('/mcp', requireApiKey, async (req, res) => {
109
+ const sessionId = req.headers['mcp-session-id'];
110
+ const transport = sessionId ? transports.get(sessionId) : undefined;
111
+ if (!transport) {
112
+ return res.status(400).json({
113
+ jsonrpc: '2.0',
114
+ error: { code: -32000, message: 'No active session. Send initialize first via POST.' },
115
+ id: null,
116
+ });
117
+ }
118
+ await transport.handleRequest(req, res);
119
+ });
120
+
121
+ // --- DELETE /mcp — session termination ---
122
+ app.delete('/mcp', async (req, res) => {
123
+ const sessionId = req.headers['mcp-session-id'];
124
+ const transport = sessionId ? transports.get(sessionId) : undefined;
125
+ if (transport) {
126
+ await transport.close();
127
+ transports.delete(sessionId);
128
+ console.log(`[Andru MCP HTTP] Session terminated: ${sessionId}`);
129
+ }
130
+ res.status(200).end();
131
+ });
132
+
133
+ // --- Cleanup on exit ---
134
+ const shutdown = async () => {
135
+ console.log('[Andru MCP HTTP] Shutting down...');
136
+ for (const [id, transport] of transports) {
137
+ await transport.close().catch(() => {});
138
+ transports.delete(id);
139
+ }
140
+ closeCache();
141
+ process.exit(0);
142
+ };
143
+ process.on('SIGINT', shutdown);
144
+ process.on('SIGTERM', shutdown);
145
+
146
+ app.listen(port, host, () => {
147
+ console.log(`[Andru MCP HTTP] Streamable HTTP server listening on http://${host}:${port}/mcp`);
148
+ console.log(`[Andru MCP HTTP] Health check: http://${host}:${port}/health`);
149
+ console.log(`[Andru MCP HTTP] API backend: ${apiUrl}`);
150
+ });
151
+ }
152
+
153
+ main().catch((err) => {
154
+ console.error('[Andru MCP HTTP] Fatal error:', err);
155
+ process.exit(1);
156
+ });