ydc-mcp-server 1.7.0 → 1.7.8
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/lib/anthropic-mapper.js +21 -14
- package/lib/request-logger.js +80 -0
- package/lib/routes/anthropic-messages.js +99 -47
- package/lib/routes/chat.js +34 -4
- package/package.json +1 -1
package/lib/anthropic-mapper.js
CHANGED
|
@@ -165,22 +165,29 @@ export function createAnthropicStreamEvent(eventType, data) {
|
|
|
165
165
|
/**
|
|
166
166
|
* Create message_start event
|
|
167
167
|
*/
|
|
168
|
-
export function createMessageStartEvent(model) {
|
|
168
|
+
export function createMessageStartEvent(model, conversationId = null) {
|
|
169
|
+
const message = {
|
|
170
|
+
id: `msg_${Date.now()}`,
|
|
171
|
+
type: 'message',
|
|
172
|
+
role: 'assistant',
|
|
173
|
+
content: [],
|
|
174
|
+
model: model,
|
|
175
|
+
stop_reason: null,
|
|
176
|
+
stop_sequence: null,
|
|
177
|
+
usage: {
|
|
178
|
+
input_tokens: 0,
|
|
179
|
+
output_tokens: 0
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
// Add conversation_id in metadata if provided
|
|
184
|
+
if (conversationId) {
|
|
185
|
+
message.metadata = { conversation_id: conversationId };
|
|
186
|
+
}
|
|
187
|
+
|
|
169
188
|
return createAnthropicStreamEvent('message_start', {
|
|
170
189
|
type: 'message_start',
|
|
171
|
-
message
|
|
172
|
-
id: `msg_${Date.now()}`,
|
|
173
|
-
type: 'message',
|
|
174
|
-
role: 'assistant',
|
|
175
|
-
content: [],
|
|
176
|
-
model: model,
|
|
177
|
-
stop_reason: null,
|
|
178
|
-
stop_sequence: null,
|
|
179
|
-
usage: {
|
|
180
|
-
input_tokens: 0,
|
|
181
|
-
output_tokens: 0
|
|
182
|
-
}
|
|
183
|
-
}
|
|
190
|
+
message
|
|
184
191
|
});
|
|
185
192
|
}
|
|
186
193
|
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Request Logger Module
|
|
3
|
+
* Pretty prints request/response info using cli-table3
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { getConversation } from './conversation-store.js';
|
|
7
|
+
|
|
8
|
+
let Table;
|
|
9
|
+
let tableAvailable = false;
|
|
10
|
+
|
|
11
|
+
// Try to load cli-table3 (optional dependency)
|
|
12
|
+
try {
|
|
13
|
+
Table = (await import('cli-table3')).default;
|
|
14
|
+
tableAvailable = true;
|
|
15
|
+
} catch (e) {
|
|
16
|
+
// cli-table3 not available, use simple logging
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Log stream complete with full info and input stack table
|
|
21
|
+
*/
|
|
22
|
+
export function logStreamComplete(info) {
|
|
23
|
+
const {
|
|
24
|
+
conversationId,
|
|
25
|
+
contentLength = 0,
|
|
26
|
+
messageCount = 0,
|
|
27
|
+
agent = 'unknown',
|
|
28
|
+
stream = true
|
|
29
|
+
} = info;
|
|
30
|
+
|
|
31
|
+
const streamMode = stream ? 'stream' : 'sync';
|
|
32
|
+
|
|
33
|
+
// Main info line
|
|
34
|
+
console.log(`📥 Complete: Conv. ID ${conversationId}, ${contentLength} chars, Messages: ${messageCount}`);
|
|
35
|
+
console.log(` ${agent}(${streamMode})`);
|
|
36
|
+
|
|
37
|
+
// Get history from conversation store
|
|
38
|
+
const conv = getConversation(conversationId);
|
|
39
|
+
const history = conv?.messages || [];
|
|
40
|
+
|
|
41
|
+
if (history.length > 0 && tableAvailable) {
|
|
42
|
+
const table = new Table({
|
|
43
|
+
head: ['#', 'Role', 'Content'],
|
|
44
|
+
colWidths: [4, 12, 60],
|
|
45
|
+
style: { head: ['cyan'] },
|
|
46
|
+
wordWrap: true
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
history.forEach((item, index) => {
|
|
50
|
+
const preview = item.content.length > 80 ? item.content.substring(0, 80) + '...' : item.content;
|
|
51
|
+
table.push([index + 1, item.role, preview]);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
console.log(' Input Stack:');
|
|
55
|
+
console.log(table.toString());
|
|
56
|
+
} else if (history.length > 0) {
|
|
57
|
+
console.log(' Input Stack:');
|
|
58
|
+
history.forEach((item, index) => {
|
|
59
|
+
const preview = item.content.length > 50 ? item.content.substring(0, 50) + '...' : item.content;
|
|
60
|
+
console.log(` ${index + 1}. [${item.role}] ${preview}`);
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
console.log(''); // Empty line for separation
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Log request (simplified)
|
|
69
|
+
*/
|
|
70
|
+
export function logRequest(info) {
|
|
71
|
+
const { conversationId } = info;
|
|
72
|
+
console.log(`📤 Request: Conv. ID ${conversationId || 'new'}`);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Log response (for non-streaming)
|
|
77
|
+
*/
|
|
78
|
+
export function logResponse(info) {
|
|
79
|
+
logStreamComplete(info);
|
|
80
|
+
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { Router } from 'express';
|
|
7
|
-
import { callYouApi
|
|
7
|
+
import { callYouApi } from '../api-client.js';
|
|
8
8
|
import {
|
|
9
9
|
mapAnthropicToYouParams,
|
|
10
10
|
convertToAnthropicResponse,
|
|
@@ -19,8 +19,9 @@ import {
|
|
|
19
19
|
getConversation,
|
|
20
20
|
createConversation,
|
|
21
21
|
addMessageToConversation,
|
|
22
|
-
generateConversationId
|
|
22
|
+
generateConversationId
|
|
23
23
|
} from '../conversation-store.js';
|
|
24
|
+
import { logRequest, logStreamComplete, logResponse } from '../request-logger.js';
|
|
24
25
|
|
|
25
26
|
const router = Router();
|
|
26
27
|
|
|
@@ -56,13 +57,11 @@ router.post('/v1/messages', async (req, res) => {
|
|
|
56
57
|
|
|
57
58
|
const apiKey = getApiKey();
|
|
58
59
|
|
|
59
|
-
// Handle conversation persistence via metadata
|
|
60
|
-
let conversationId = metadata?.conversation_id;
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
createConversation(conversationId);
|
|
65
|
-
}
|
|
60
|
+
// Handle conversation persistence via metadata or generate new one
|
|
61
|
+
let conversationId = metadata?.conversation_id || generateConversationId();
|
|
62
|
+
const existingConv = getConversation(conversationId);
|
|
63
|
+
if (!existingConv) {
|
|
64
|
+
createConversation(conversationId);
|
|
66
65
|
}
|
|
67
66
|
|
|
68
67
|
// Map to You.com parameters
|
|
@@ -75,6 +74,22 @@ router.post('/v1/messages', async (req, res) => {
|
|
|
75
74
|
stream
|
|
76
75
|
});
|
|
77
76
|
|
|
77
|
+
// Get current user input for logging
|
|
78
|
+
const lastUserMsg = messages.filter(m => m.role === 'user').pop();
|
|
79
|
+
const currentInput = typeof lastUserMsg?.content === 'string'
|
|
80
|
+
? lastUserMsg.content
|
|
81
|
+
: lastUserMsg?.content?.filter(c => c.type === 'text').map(c => c.text).join('\n') || '';
|
|
82
|
+
|
|
83
|
+
logRequest({
|
|
84
|
+
endpoint: '/v1/messages (Anthropic)',
|
|
85
|
+
agent: youParams.agent,
|
|
86
|
+
model,
|
|
87
|
+
stream,
|
|
88
|
+
conversationId,
|
|
89
|
+
messageCount: messages.length,
|
|
90
|
+
input: currentInput
|
|
91
|
+
});
|
|
92
|
+
|
|
78
93
|
// Store user message
|
|
79
94
|
if (conversationId) {
|
|
80
95
|
const lastUserMsg = messages.filter(m => m.role === 'user').pop();
|
|
@@ -92,8 +107,8 @@ router.post('/v1/messages', async (req, res) => {
|
|
|
92
107
|
res.setHeader('Cache-Control', 'no-cache');
|
|
93
108
|
res.setHeader('Connection', 'keep-alive');
|
|
94
109
|
|
|
95
|
-
// Send message_start
|
|
96
|
-
res.write(createMessageStartEvent(model));
|
|
110
|
+
// Send message_start with conversation_id
|
|
111
|
+
res.write(createMessageStartEvent(model, conversationId));
|
|
97
112
|
|
|
98
113
|
// Send content_block_start
|
|
99
114
|
res.write(createContentBlockStartEvent(0));
|
|
@@ -104,53 +119,88 @@ router.post('/v1/messages', async (req, res) => {
|
|
|
104
119
|
let fullContent = '';
|
|
105
120
|
let buffer = '';
|
|
106
121
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
const
|
|
115
|
-
if (
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
122
|
+
// Use Web Streams API (ReadableStream)
|
|
123
|
+
const reader = response.body.getReader();
|
|
124
|
+
const decoder = new TextDecoder();
|
|
125
|
+
|
|
126
|
+
const processStream = async () => {
|
|
127
|
+
try {
|
|
128
|
+
while (true) {
|
|
129
|
+
const { done, value } = await reader.read();
|
|
130
|
+
if (done) break;
|
|
131
|
+
|
|
132
|
+
buffer += decoder.decode(value, { stream: true });
|
|
133
|
+
const lines = buffer.split('\n');
|
|
134
|
+
buffer = lines.pop() || '';
|
|
135
|
+
|
|
136
|
+
for (const line of lines) {
|
|
137
|
+
if (line.startsWith('data: ')) {
|
|
138
|
+
const data = line.slice(6);
|
|
139
|
+
if (data === '[DONE]') continue;
|
|
140
|
+
|
|
141
|
+
try {
|
|
142
|
+
const parsed = JSON.parse(data);
|
|
143
|
+
|
|
144
|
+
// Handle streaming delta format
|
|
145
|
+
if (parsed.type === 'response.output_text.delta' &&
|
|
146
|
+
parsed.response?.type === 'message.answer' &&
|
|
147
|
+
parsed.response?.delta) {
|
|
148
|
+
const newText = parsed.response.delta;
|
|
123
149
|
if (newText) {
|
|
124
|
-
fullContent
|
|
150
|
+
fullContent += newText;
|
|
125
151
|
res.write(createContentBlockDeltaEvent(newText, 0));
|
|
126
152
|
}
|
|
127
153
|
}
|
|
154
|
+
|
|
155
|
+
// Also handle full output format (non-streaming fallback)
|
|
156
|
+
if (parsed.output) {
|
|
157
|
+
for (const item of parsed.output) {
|
|
158
|
+
if (item.type === 'message.answer' && item.text) {
|
|
159
|
+
const newText = item.text.slice(fullContent.length);
|
|
160
|
+
if (newText) {
|
|
161
|
+
fullContent = item.text;
|
|
162
|
+
res.write(createContentBlockDeltaEvent(newText, 0));
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
} catch (e) {
|
|
168
|
+
// Skip invalid JSON
|
|
128
169
|
}
|
|
129
170
|
}
|
|
130
|
-
} catch (e) {
|
|
131
|
-
// Skip invalid JSON
|
|
132
171
|
}
|
|
133
172
|
}
|
|
134
|
-
}
|
|
135
|
-
});
|
|
136
173
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
}
|
|
174
|
+
// Store assistant response
|
|
175
|
+
if (conversationId && fullContent) {
|
|
176
|
+
addMessageToConversation(conversationId, 'assistant', fullContent);
|
|
177
|
+
}
|
|
142
178
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
179
|
+
logStreamComplete({
|
|
180
|
+
conversationId,
|
|
181
|
+
contentLength: fullContent.length,
|
|
182
|
+
messageCount: messages.length + 1,
|
|
183
|
+
agent: youParams.agent,
|
|
184
|
+
stream: true,
|
|
185
|
+
responsePreview: fullContent
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
// Send closing events
|
|
189
|
+
res.write(createContentBlockStopEvent(0));
|
|
190
|
+
res.write(createMessageDeltaEvent(Math.floor(fullContent.length / 4)));
|
|
191
|
+
res.write(createMessageStopEvent());
|
|
192
|
+
res.end();
|
|
193
|
+
} catch (error) {
|
|
194
|
+
console.error('Stream processing error:', error);
|
|
195
|
+
res.write(createContentBlockDeltaEvent(`Error: ${error.message}`, 0));
|
|
196
|
+
res.write(createContentBlockStopEvent(0));
|
|
197
|
+
res.write(createMessageDeltaEvent(0));
|
|
198
|
+
res.write(createMessageStopEvent());
|
|
199
|
+
res.end();
|
|
200
|
+
}
|
|
201
|
+
};
|
|
149
202
|
|
|
150
|
-
|
|
151
|
-
console.error('Stream error:', error);
|
|
152
|
-
res.end();
|
|
153
|
-
});
|
|
203
|
+
processStream();
|
|
154
204
|
|
|
155
205
|
} catch (error) {
|
|
156
206
|
console.error('Streaming error:', error);
|
|
@@ -166,6 +216,8 @@ router.post('/v1/messages', async (req, res) => {
|
|
|
166
216
|
const response = await callYouApi(apiKey, youParams);
|
|
167
217
|
const data = await response.json();
|
|
168
218
|
|
|
219
|
+
console.log('📥 You.com response received');
|
|
220
|
+
|
|
169
221
|
const anthropicResponse = convertToAnthropicResponse(data, model);
|
|
170
222
|
|
|
171
223
|
// Store assistant response
|
package/lib/routes/chat.js
CHANGED
|
@@ -5,13 +5,13 @@
|
|
|
5
5
|
import { Router } from 'express';
|
|
6
6
|
import { authenticate } from '../auth-middleware.js';
|
|
7
7
|
import { mapOpenAIToYouParams, convertToOpenAIResponse, createStreamChunk } from '../openai-mapper.js';
|
|
8
|
-
import { callYouApi
|
|
8
|
+
import { callYouApi } from '../api-client.js';
|
|
9
9
|
import {
|
|
10
10
|
getConversation,
|
|
11
|
-
createConversation,
|
|
12
11
|
addMessageToConversation,
|
|
13
12
|
generateConversationId
|
|
14
13
|
} from '../conversation-store.js';
|
|
14
|
+
import { logRequest, logStreamComplete } from '../request-logger.js';
|
|
15
15
|
|
|
16
16
|
const router = Router();
|
|
17
17
|
const API_KEY = process.env.YDC_API_KEY;
|
|
@@ -69,8 +69,19 @@ router.post('/v1/chat/completions', authenticate, async (req, res) => {
|
|
|
69
69
|
|
|
70
70
|
const youParams = mapOpenAIToYouParams({ ...req.body, messages: fullMessages });
|
|
71
71
|
|
|
72
|
-
|
|
73
|
-
|
|
72
|
+
// Get current user input for logging
|
|
73
|
+
const currentUserMsg = fullMessages.filter(m => m.role === 'user').pop();
|
|
74
|
+
const currentInput = currentUserMsg?.content || '';
|
|
75
|
+
|
|
76
|
+
logRequest({
|
|
77
|
+
endpoint: '/v1/chat/completions (OpenAI)',
|
|
78
|
+
agent: youParams.agent,
|
|
79
|
+
model: req.body.model,
|
|
80
|
+
stream: req.body.stream || false,
|
|
81
|
+
conversationId,
|
|
82
|
+
messageCount: fullMessages.length,
|
|
83
|
+
input: currentInput
|
|
84
|
+
});
|
|
74
85
|
|
|
75
86
|
const timeoutMs = youParams.timeout || (youParams.agent === 'advanced' ? 3000000 : 300000);
|
|
76
87
|
|
|
@@ -120,7 +131,9 @@ async function handleStreamingResponse(req, res, response, youParams, conversati
|
|
|
120
131
|
const reader = response.body.getReader();
|
|
121
132
|
const decoder = new TextDecoder();
|
|
122
133
|
let buffer = '';
|
|
134
|
+
let fullContent = '';
|
|
123
135
|
const model = req.body.model || 'advanced';
|
|
136
|
+
const messageCount = req.body.messages?.length || 0;
|
|
124
137
|
const STREAM_TIMEOUT = youParams.timeout || (youParams.agent === 'advanced' ? 3000000 : 300000);
|
|
125
138
|
|
|
126
139
|
try {
|
|
@@ -159,6 +172,7 @@ async function handleStreamingResponse(req, res, response, youParams, conversati
|
|
|
159
172
|
res.end();
|
|
160
173
|
}, STREAM_TIMEOUT);
|
|
161
174
|
|
|
175
|
+
fullContent += data.response.delta;
|
|
162
176
|
const chunk = createStreamChunk(model, data.response.delta);
|
|
163
177
|
res.write(`data: ${JSON.stringify(chunk)}\n\n`);
|
|
164
178
|
}
|
|
@@ -170,6 +184,22 @@ async function handleStreamingResponse(req, res, response, youParams, conversati
|
|
|
170
184
|
}
|
|
171
185
|
|
|
172
186
|
clearTimeout(streamTimeout);
|
|
187
|
+
|
|
188
|
+
// Store assistant response
|
|
189
|
+
if (conversationId && fullContent) {
|
|
190
|
+
addMessageToConversation(conversationId, 'assistant', fullContent);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Log completion
|
|
194
|
+
logStreamComplete({
|
|
195
|
+
conversationId,
|
|
196
|
+
contentLength: fullContent.length,
|
|
197
|
+
messageCount: messageCount + 1,
|
|
198
|
+
agent: youParams.agent,
|
|
199
|
+
stream: true,
|
|
200
|
+
responsePreview: fullContent
|
|
201
|
+
});
|
|
202
|
+
|
|
173
203
|
} catch (streamError) {
|
|
174
204
|
console.error('❌ Streaming error:', streamError);
|
|
175
205
|
res.write(`data: {"error": "Streaming error: ${streamError.message}"}\n\n`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ydc-mcp-server",
|
|
3
|
-
"version": "1.7.
|
|
3
|
+
"version": "1.7.8",
|
|
4
4
|
"description": "MCP server for You.com Agents API - Express, Research, Advanced agents with multi-turn conversations, streaming, OpenAI and Anthropic/Claude API compatibility",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|