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.
@@ -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, extractText } from '../api-client.js';
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
- if (conversationId) {
62
- const existingConv = getConversation(conversationId);
63
- if (!existingConv) {
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
- response.body.on('data', (chunk) => {
108
- buffer += chunk.toString();
109
- const lines = buffer.split('\n');
110
- buffer = lines.pop() || '';
111
-
112
- for (const line of lines) {
113
- if (line.startsWith('data: ')) {
114
- const data = line.slice(6);
115
- if (data === '[DONE]') continue;
116
-
117
- try {
118
- const parsed = JSON.parse(data);
119
- if (parsed.output) {
120
- for (const item of parsed.output) {
121
- if (item.type === 'message.answer' && item.text) {
122
- const newText = item.text.slice(fullContent.length);
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 = item.text;
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
- response.body.on('end', () => {
138
- // Store assistant response
139
- if (conversationId && fullContent) {
140
- addMessageToConversation(conversationId, 'assistant', fullContent);
141
- }
174
+ // Store assistant response
175
+ if (conversationId && fullContent) {
176
+ addMessageToConversation(conversationId, 'assistant', fullContent);
177
+ }
142
178
 
143
- // Send closing events
144
- res.write(createContentBlockStopEvent(0));
145
- res.write(createMessageDeltaEvent(Math.floor(fullContent.length / 4)));
146
- res.write(createMessageStopEvent());
147
- res.end();
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
- response.body.on('error', (error) => {
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
@@ -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, extractText } from '../api-client.js';
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
- console.log('📤 Sending request to You.com:', JSON.stringify({ ...youParams, input: youParams.input.substring(0, 200) + '...' }, null, 2));
73
- console.log(`💬 Conversation ID: ${conversationId}, Messages: ${fullMessages.length}`);
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.0",
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",