Skip to content

Commit a750257

Browse files
authored
Mcp support (#73)
* feat: Implement Hono-based MCP server with SSE transport - Introduced a new HonoSSETransport class for managing Server-Sent Events (SSE) connections, enabling real-time communication with the MCP server. - Created endpoints for establishing SSE connections and handling incoming messages, ensuring robust error handling and response management. - Integrated the MCP server with the Hono framework, allowing for streamlined message processing and transport management. - Updated package.json to include the @modelcontextprotocol/sdk dependency for necessary SDK functionalities. * refactor: Update MCP tools import path - Changed the import path for addMCPTools from 'mcp/initTools' to './src/mcp/registry.js' to improve module organization and maintainability. * refactor: Update MCP server configuration and tool registration - Changed the MCP server name from 'weather' to 'askSentientAI' for better alignment with project goals. - Refactored tool registration by replacing addMCPTools with ToolRegistry.registerMcpTools to enhance modularity and maintainability. - Updated package.json to include a new script for starting the MCP server, improving usability for developers. * feat: Add MCP tool registration functionality - Introduced the `registerMcpTools` static method in `ToolRegistry` to facilitate the registration of tools with the MCP server. - Enhanced the tool registration process by iterating over enabled tools and their schemas, allowing for dynamic tool setup. - Improved the execution handling of registered tools, ensuring proper response formatting and error management. * test: add test for mcp tool registration * feat: Enhance specialty domains with additional tools - Added Calculator and Timestamp Converter tools to multiple specialty domains, improving functionality and versatility. - Updated the tools array in the domains configuration to include these new tools, ensuring comprehensive coverage across relevant domains. * chore: Update .dockerignore and .gitignore to include JSON configuration files - Added configs/*.json to both .dockerignore and .gitignore to prevent JSON configuration files from being included in Docker builds and version control, enhancing project cleanliness and maintainability. * test: Add Calculator and Timestamp Converter to AskSpecialtyTool tests - Updated the test suite for AskSpecialtyTool to include new tools: Calculator and Timestamp Converter. - Enhanced test coverage by verifying the integration of these tools in the toolset, ensuring comprehensive validation of functionality. * chore: Update ai dependency version in package.json - Upgraded the "ai" package from version 4.1.45 to 4.3 to ensure compatibility with the latest features and improvements. - This change supports ongoing efforts to maintain an up-to-date and efficient codebase. * docs: Update README.md for improved clarity and formatting - Refined the available tools section by standardizing formatting and enhancing descriptions for better readability. - Improved API reference details, including clearer parameter descriptions and examples for various endpoints. - Added whitespace for better visual separation and clarity throughout the document. - Enhanced overall structure and organization to facilitate easier navigation and understanding for users. * chore: Update pnpm-lock.yaml with dependency version changes - Added '@modelcontextprotocol/sdk' version 1.11.0 to the lock file for improved functionality. - Upgraded 'ai' package from version 4.1.45 to 4.3.13 to ensure compatibility with the latest features. - Updated various other dependencies to their latest versions, enhancing overall project stability and performance. * docs: Update README.md to include MCP mode instructions - Added detailed instructions for running in MCP (Model Context Protocol) mode, including command examples and client configuration. - Introduced new API endpoints specific to MCP mode, with request and response examples for better clarity. - Enhanced overall documentation structure to improve user understanding of MCP functionalities.
1 parent 1c68bee commit a750257

File tree

10 files changed

+1050
-159
lines changed

10 files changed

+1050
-159
lines changed

.dockerignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,6 @@ npm-debug.log
55
.env.*
66

77
configs/.env.*
8+
configs/*.json
89

910
.cursor

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ yarn-error.log*
2525
configs/*.env
2626
!configs/template.env
2727
configs/.env.*
28+
configs/*.json
2829

2930
# Temporary files
3031
*~

README.md

Lines changed: 82 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -152,13 +152,33 @@ graph TD
152152
bun run start
153153
```
154154

155-
6. Test API query:
155+
6. Run in MCP (Model Context Protocol) mode:
156+
157+
```bash
158+
bun run start:mcp
159+
```
160+
161+
This starts Quicksilver in MCP compatibility mode on port 3000 by default. To connect an MCP-compatible client, add the following to your client configuration:
162+
163+
```json
164+
{
165+
"mcpServers": {
166+
"askSentai": {
167+
"url": "http://yourServerUrl/sse"
168+
}
169+
}
170+
}
171+
```
172+
173+
Note that the standard API endpoints (`/ask`, `/stream`, etc.) are not available in MCP mode.
174+
175+
7. Test API query:
156176

157177
```bash
158178
curl http://localhost:8000/ask -X POST -H "Content-Type: application/json" -d '{"q": "What is the weather in San Francisco?"}'
159179
```
160180

161-
7. Access raw tool data:
181+
8. Access raw tool data:
162182

163183
```bash
164184
# Get raw weather data for San Francisco
@@ -171,7 +191,6 @@ graph TD
171191
curl "http://localhost:8000/raw?tool=depin-metrics&isLatest=true"
172192
```
173193

174-
175194
---
176195

177196
## Configuration
@@ -217,7 +236,7 @@ Quicksilver supports running multiple instances with different tool configuratio
217236
#### Configuration Structure
218237

219238
```bash
220-
configs/ # Your instance-specific configuration (gitignored)
239+
configs/ # Your instance-specific configuration (gitignored)
221240
├── .env.weather # Instance with only weather-related tools
222241
├── .env.news # Instance with news and analytics tools
223242
└── .env.full # Instance with all tools enabled
@@ -318,22 +337,22 @@ PORT=8003
318337

319338
The following tools can be enabled in your configuration:
320339

321-
| Tool Name | Description | Required Environment Variables |
322-
| ------------------ | --------------------- | -------------------------------------- |
323-
| `news` | News API integration | `NEWSAPI_API_KEY` |
324-
| `weather-current` | Current weather data | `NUBILA_API_KEY` |
325-
| `weather-forecast` | Weather forecasts | `NUBILA_API_KEY` |
326-
| `depin-metrics` | DePIN network metrics | `DEPIN_API_KEY` |
327-
| `depin-projects` | DePIN project data | `DEPIN_API_KEY` |
328-
| `l1data` | L1 blockchain data | `API_V2_KEY` |
329-
| `dimo` | Vehicle IoT data | `CLIENT_ID`, `REDIRECT_URI`, `API_KEY` |
330-
| `nuclear` | Nuclear outage data | `EIA_API_KEY` |
331-
| `mapbox` | Mapbox API integration | `MAPBOX_ACCESS_TOKEN` |
332-
| `messari` | Messari API integration | `MESSARI_API_KEY` |
333-
| `thirdweb` | Thirdweb API integration | `THIRDWEB_SECRET_KEY`, `THIRDWEB_SESSION_ID` |
334-
| `cmc` | CoinMarketCap API integration | `CMC_API_KEY` |
335-
| `airquality` | Air quality data | `AIRVISUAL_API_KEY` |
336-
| `depinninja` | Depin Ninja API integration | `DEPINNINJA_API_KEY` |
340+
| Tool Name | Description | Required Environment Variables |
341+
| ------------------ | ----------------------------- | -------------------------------------------- |
342+
| `news` | News API integration | `NEWSAPI_API_KEY` |
343+
| `weather-current` | Current weather data | `NUBILA_API_KEY` |
344+
| `weather-forecast` | Weather forecasts | `NUBILA_API_KEY` |
345+
| `depin-metrics` | DePIN network metrics | `DEPIN_API_KEY` |
346+
| `depin-projects` | DePIN project data | `DEPIN_API_KEY` |
347+
| `l1data` | L1 blockchain data | `API_V2_KEY` |
348+
| `dimo` | Vehicle IoT data | `CLIENT_ID`, `REDIRECT_URI`, `API_KEY` |
349+
| `nuclear` | Nuclear outage data | `EIA_API_KEY` |
350+
| `mapbox` | Mapbox API integration | `MAPBOX_ACCESS_TOKEN` |
351+
| `messari` | Messari API integration | `MESSARI_API_KEY` |
352+
| `thirdweb` | Thirdweb API integration | `THIRDWEB_SECRET_KEY`, `THIRDWEB_SESSION_ID` |
353+
| `cmc` | CoinMarketCap API integration | `CMC_API_KEY` |
354+
| `airquality` | Air quality data | `AIRVISUAL_API_KEY` |
355+
| `depinninja` | Depin Ninja API integration | `DEPINNINJA_API_KEY` |
337356

338357
### Best Practices
339358

@@ -387,6 +406,7 @@ curl -H "API-KEY: your_api_key" http://localhost:8000/endpoint
387406
Simple health check endpoint.
388407

389408
**Response**
409+
390410
```
391411
hello world, Sentient AI!
392412
```
@@ -396,9 +416,11 @@ hello world, Sentient AI!
396416
Send a query to the Sentient AI system.
397417

398418
**Parameters**
419+
399420
- `q` or `content` (string, required) - The query text
400421

401422
**Request Examples**
423+
402424
```bash
403425
# URL parameter
404426
curl "http://localhost:8000/ask?q=What%20is%20the%20weather%20in%20San%20Francisco?"
@@ -411,6 +433,7 @@ curl http://localhost:8000/ask \
411433
```
412434

413435
**Response**
436+
414437
```json
415438
{
416439
"data": "The current weather in San Francisco is 62°F with partly cloudy conditions..."
@@ -422,10 +445,12 @@ curl http://localhost:8000/ask \
422445
Stream a response from the Sentient AI system.
423446

424447
**Parameters**
448+
425449
- `text` (form data, required) - The query text
426450
- `recentMessages` (form data, optional) - Previous conversation context
427451

428452
**Request Example**
453+
429454
```bash
430455
curl http://localhost:8000/stream \
431456
-X POST \
@@ -442,10 +467,12 @@ Stream of text data.
442467
Get raw data from a specific tool without LLM processing.
443468

444469
**Parameters**
470+
445471
- `tool` (string, required) - The tool name to query
446472
- Additional parameters specific to each tool
447473

448474
**Request Examples**
475+
449476
```bash
450477
# Get current weather
451478
curl "http://localhost:8000/raw?tool=weather-current&lat=37.7749&lon=-122.4194"
@@ -458,12 +485,47 @@ curl "http://localhost:8000/raw?tool=depin-metrics&isLatest=true"
458485
```
459486

460487
**Response**
488+
461489
```json
462490
{
463491
"data": "Tool-specific response data"
464492
}
465493
```
466494

495+
#### GET `/sse` (MCP Mode)
496+
497+
Establishes a Server-Sent Events (SSE) connection for Model Context Protocol interactions. Available when running in MCP mode.
498+
499+
**Request Example**
500+
501+
```bash
502+
# Connect to the MCP server via SSE
503+
curl -N http://localhost:3000/sse
504+
```
505+
506+
**Response**
507+
Stream of SSE events with tool capabilities and message exchange.
508+
509+
#### POST `/messages` (MCP Mode)
510+
511+
Endpoint for sending messages to the MCP server after establishing an SSE connection.
512+
513+
**Parameters**
514+
- `sessionId` (query parameter, required) - The session ID received from the SSE connection
515+
516+
**Request Example**
517+
518+
```bash
519+
# Send a message to the MCP server (session ID will be provided by the SSE connection)
520+
curl http://localhost:3000/messages?sessionId=YOUR_SESSION_ID \
521+
-X POST \
522+
-H "Content-Type: application/json" \
523+
-d '{"jsonrpc":"2.0","method":"invoke","params":{"name":"tool_name","arguments":{}},"id":"request-id"}'
524+
```
525+
526+
**Response**
527+
Confirmation of message receipt or error information.
528+
467529
---
468530

469531
## Integrations

mcpServer.ts

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
import { Hono } from 'hono';
2+
import { streamSSE } from 'hono/streaming';
3+
4+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
5+
import { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';
6+
import { JSONRPCMessage, JSONRPCMessageSchema } from '@modelcontextprotocol/sdk/types.js';
7+
8+
import { ToolRegistry } from './src/registry/registry.js';
9+
10+
const app = new Hono();
11+
12+
class HonoSSETransport implements Transport {
13+
sessionId: string;
14+
private sseStream: any; // Hono SSE stream type
15+
private endpoint: string;
16+
17+
onclose?: () => void;
18+
onerror?: (error: Error) => void;
19+
onmessage?: (message: JSONRPCMessage) => void;
20+
21+
constructor(endpoint: string, stream: any) {
22+
this.endpoint = endpoint;
23+
this.sessionId = crypto.randomUUID();
24+
this.sseStream = stream;
25+
}
26+
27+
async start(): Promise<void> {
28+
// Send initial ping
29+
await this.sseStream.writeSSE({ data: 'ping', event: 'heartbeat' });
30+
31+
// Send endpoint information
32+
await this.sseStream.writeSSE({
33+
data: `${encodeURI(this.endpoint)}?sessionId=${this.sessionId}`,
34+
event: 'endpoint',
35+
});
36+
}
37+
38+
async handlePostMessage(request: Request): Promise<Response> {
39+
if (!this.sseStream) {
40+
return new Response('SSE connection not established', { status: 500 });
41+
}
42+
43+
try {
44+
const contentType = request.headers.get('content-type');
45+
if (contentType !== 'application/json') {
46+
throw new Error(`Unsupported content-type: ${contentType}`);
47+
}
48+
49+
const body = await request.json();
50+
let parsedMessage: JSONRPCMessage;
51+
52+
try {
53+
parsedMessage = JSONRPCMessageSchema.parse(body);
54+
} catch (error) {
55+
this.onerror?.(error as Error);
56+
throw error;
57+
}
58+
59+
this.onmessage?.(parsedMessage);
60+
return new Response('Accepted', { status: 202 });
61+
} catch (error) {
62+
this.onerror?.(error as Error);
63+
return new Response(String(error), { status: 400 });
64+
}
65+
}
66+
67+
async close(): Promise<void> {
68+
this.onclose?.();
69+
}
70+
71+
async send(message: JSONRPCMessage): Promise<void> {
72+
if (!this.sseStream) {
73+
throw new Error('Not connected');
74+
}
75+
76+
await this.sseStream.writeSSE({
77+
data: JSON.stringify(message),
78+
event: 'message',
79+
});
80+
}
81+
82+
get id(): string {
83+
return this.sessionId;
84+
}
85+
}
86+
87+
// Create server instance
88+
const server = new McpServer({
89+
name: 'askSentientAI',
90+
version: '1.0.0',
91+
capabilities: {
92+
tools: {},
93+
},
94+
});
95+
96+
ToolRegistry.registerMcpTools(server);
97+
98+
// Store active transports by session ID
99+
const transports: Record<string, HonoSSETransport> = {};
100+
101+
app.get('/sse', c => {
102+
return streamSSE(c, async stream => {
103+
const transport = new HonoSSETransport('/messages', stream);
104+
105+
// Store the transport with its session ID
106+
transports[transport.id] = transport;
107+
108+
// Clean up when connection closes
109+
stream.onAbort(() => {
110+
delete transports[transport.id];
111+
transport.close();
112+
});
113+
114+
// Connect to MCP server
115+
await server.connect(transport);
116+
await transport.start();
117+
118+
// Keep the connection alive
119+
while (true) {
120+
await stream.sleep(30000);
121+
await stream.writeSSE({ data: 'ping', event: 'heartbeat' });
122+
}
123+
});
124+
});
125+
126+
app.post('/messages', async c => {
127+
const sessionId = c.req.query('sessionId');
128+
const transport = sessionId ? transports[sessionId] : null;
129+
130+
if (!transport) {
131+
return c.text('No active SSE connection for the provided session ID', 400);
132+
}
133+
134+
return transport.handlePostMessage(c.req.raw);
135+
});
136+
137+
export default {
138+
port: process.env.PORT ? parseInt(process.env.PORT) : 3000,
139+
fetch: app.fetch,
140+
idleTimeout: 120,
141+
};

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
"type": "module",
77
"scripts": {
88
"start": "bun run server.ts --server-options=\"{\\\"idleTimeout\\\": 120000}\"",
9+
"start:mcp": "bun run mcpServer.ts --server-options=\"{\\\"idleTimeout\\\": 120000}\"",
910
"dev": "bun run --watch server.ts --server-options=\"{\\\"idleTimeout\\\": 120000}\"",
1011
"test": "vitest run",
1112
"test:watch": "vitest",
@@ -27,9 +28,10 @@
2728
"@langchain/core": "^0.3.40",
2829
"@langchain/openai": "^0.4.4",
2930
"@langchain/qdrant": "^0.1.1",
31+
"@modelcontextprotocol/sdk": "^1.10.2",
3032
"@qdrant/js-client-rest": "^1.13.0",
3133
"@upstash/redis": "^1.34.4",
32-
"ai": "^4.1.45",
34+
"ai": "^4.3",
3335
"axios": "^1.7.9",
3436
"chalk": "^4.1.2",
3537
"dotenv": "^16.4.7",

0 commit comments

Comments
 (0)