@attrove/mcp 0.1.6 → 0.1.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/README.md +101 -3
- package/cjs/index.js +6 -1
- package/cjs/server.js +115 -56
- package/cjs/tools/events.js +134 -0
- package/cjs/tools/index.js +9 -1
- package/cjs/tools/integrations.js +11 -4
- package/cjs/tools/meetings.js +149 -0
- package/cjs/tools/query.js +12 -5
- package/cjs/tools/search.js +21 -7
- package/cjs/transport/http.js +192 -0
- package/esm/index.d.ts +3 -1
- package/esm/index.d.ts.map +1 -1
- package/esm/index.js +3 -1
- package/esm/index.js.map +1 -1
- package/esm/server.d.ts +7 -5
- package/esm/server.d.ts.map +1 -1
- package/esm/server.js +116 -57
- package/esm/server.js.map +1 -1
- package/esm/tools/events.d.ts +24 -0
- package/esm/tools/events.d.ts.map +1 -0
- package/esm/tools/events.js +131 -0
- package/esm/tools/events.js.map +1 -0
- package/esm/tools/index.d.ts +6 -69
- package/esm/tools/index.d.ts.map +1 -1
- package/esm/tools/index.js +5 -2
- package/esm/tools/index.js.map +1 -1
- package/esm/tools/integrations.d.ts +3 -10
- package/esm/tools/integrations.d.ts.map +1 -1
- package/esm/tools/integrations.js +11 -4
- package/esm/tools/integrations.js.map +1 -1
- package/esm/tools/meetings.d.ts +26 -0
- package/esm/tools/meetings.d.ts.map +1 -0
- package/esm/tools/meetings.js +146 -0
- package/esm/tools/meetings.js.map +1 -0
- package/esm/tools/query.d.ts +3 -27
- package/esm/tools/query.d.ts.map +1 -1
- package/esm/tools/query.js +12 -5
- package/esm/tools/query.js.map +1 -1
- package/esm/tools/search.d.ts +3 -35
- package/esm/tools/search.d.ts.map +1 -1
- package/esm/tools/search.js +21 -7
- package/esm/tools/search.js.map +1 -1
- package/esm/transport/http.d.ts +63 -0
- package/esm/transport/http.d.ts.map +1 -0
- package/esm/transport/http.js +189 -0
- package/esm/transport/http.js.map +1 -0
- package/package.json +2 -2
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Meetings Tool
|
|
4
|
+
*
|
|
5
|
+
* MCP tool for listing meetings with AI-generated summaries and action items.
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.executeMeetingsTool = exports.meetingsToolDefinition = void 0;
|
|
9
|
+
/**
|
|
10
|
+
* MCP schema for discovering and filtering meetings with AI-generated summaries.
|
|
11
|
+
*/
|
|
12
|
+
exports.meetingsToolDefinition = {
|
|
13
|
+
name: "attrove_meetings",
|
|
14
|
+
description: `List meetings with AI-generated summaries and action items from the user's own connected accounts.
|
|
15
|
+
|
|
16
|
+
This read-only tool returns meetings from the authenticated user's authorized meeting integrations (e.g., Google Meet, Zoom, Teams) with AI-generated summaries. Only meetings from services the user has explicitly connected are accessed. Use this when the user asks about:
|
|
17
|
+
- "What happened in my last meeting?"
|
|
18
|
+
- "Summarize yesterday's standup"
|
|
19
|
+
- "What are the action items from the product review?"
|
|
20
|
+
- "Show me my recent meetings"`,
|
|
21
|
+
inputSchema: {
|
|
22
|
+
type: "object",
|
|
23
|
+
properties: {
|
|
24
|
+
start_date: {
|
|
25
|
+
type: "string",
|
|
26
|
+
description: "Start of date range (ISO 8601). If omitted, the server determines the default.",
|
|
27
|
+
},
|
|
28
|
+
end_date: {
|
|
29
|
+
type: "string",
|
|
30
|
+
description: "End of date range (ISO 8601). If omitted, the server determines the default.",
|
|
31
|
+
},
|
|
32
|
+
provider: {
|
|
33
|
+
type: "string",
|
|
34
|
+
enum: ["google_meet", "zoom", "teams"],
|
|
35
|
+
description: "Optional: Filter by meeting provider",
|
|
36
|
+
},
|
|
37
|
+
limit: {
|
|
38
|
+
type: "number",
|
|
39
|
+
description: "Max meetings to return (default 10, max 50)",
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
required: [],
|
|
43
|
+
},
|
|
44
|
+
annotations: {
|
|
45
|
+
title: "Meeting Summaries",
|
|
46
|
+
readOnlyHint: true,
|
|
47
|
+
destructiveHint: false,
|
|
48
|
+
idempotentHint: true,
|
|
49
|
+
openWorldHint: true,
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* Format a meeting's time range for display.
|
|
54
|
+
* Returns a fallback string when the meeting contains unparseable dates.
|
|
55
|
+
*/
|
|
56
|
+
function formatMeetingTime(meeting) {
|
|
57
|
+
const start = new Date(meeting.start_time);
|
|
58
|
+
const end = new Date(meeting.end_time);
|
|
59
|
+
if (Number.isNaN(start.getTime()) || Number.isNaN(end.getTime())) {
|
|
60
|
+
return meeting.start_time ?? "Unknown time";
|
|
61
|
+
}
|
|
62
|
+
const dateStr = start.toLocaleDateString(undefined, {
|
|
63
|
+
weekday: "short",
|
|
64
|
+
month: "short",
|
|
65
|
+
day: "numeric",
|
|
66
|
+
});
|
|
67
|
+
const startTime = start.toLocaleTimeString(undefined, {
|
|
68
|
+
hour: "numeric",
|
|
69
|
+
minute: "2-digit",
|
|
70
|
+
});
|
|
71
|
+
const endTime = end.toLocaleTimeString(undefined, {
|
|
72
|
+
hour: "numeric",
|
|
73
|
+
minute: "2-digit",
|
|
74
|
+
});
|
|
75
|
+
return `${dateStr}, ${startTime} – ${endTime}`;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Fetch meetings via the SDK and format them as a human-readable summary
|
|
79
|
+
* including AI-generated summaries and action items.
|
|
80
|
+
*/
|
|
81
|
+
async function executeMeetingsTool(client, input) {
|
|
82
|
+
const options = {
|
|
83
|
+
expand: ["summary", "short_summary", "action_items", "attendees"],
|
|
84
|
+
};
|
|
85
|
+
if (input.start_date) {
|
|
86
|
+
options.startDate = input.start_date;
|
|
87
|
+
}
|
|
88
|
+
if (input.end_date) {
|
|
89
|
+
options.endDate = input.end_date;
|
|
90
|
+
}
|
|
91
|
+
if (input.provider) {
|
|
92
|
+
options.provider = input.provider;
|
|
93
|
+
}
|
|
94
|
+
options.limit = Math.max(1, Math.min(input.limit ?? 10, 50));
|
|
95
|
+
const response = await client.meetings.list(options);
|
|
96
|
+
if (!response.data) {
|
|
97
|
+
// prettier-ignore
|
|
98
|
+
console.error("[AttroveMCP]", JSON.stringify({ level: "warn", msg: "Meetings API returned success but data was nullish", errorId: "MCP_MEETINGS_MALFORMED_RESPONSE" }));
|
|
99
|
+
return "The meetings API returned an unexpected response format. Please try again.";
|
|
100
|
+
}
|
|
101
|
+
if (response.data.length === 0) {
|
|
102
|
+
return "No meetings found for the specified date range.";
|
|
103
|
+
}
|
|
104
|
+
const meetings = response.data;
|
|
105
|
+
let result = `Found ${meetings.length} meeting(s):\n`;
|
|
106
|
+
for (const meeting of meetings) {
|
|
107
|
+
result += `\n### ${meeting.title}\n`;
|
|
108
|
+
result += `- **When:** ${formatMeetingTime(meeting)}\n`;
|
|
109
|
+
if (meeting.provider) {
|
|
110
|
+
const providerLabel = meeting.provider
|
|
111
|
+
.replace(/_/g, " ")
|
|
112
|
+
.replace(/\b\w/g, (c) => c.toUpperCase());
|
|
113
|
+
result += `- **Provider:** ${providerLabel}\n`;
|
|
114
|
+
}
|
|
115
|
+
if (meeting.attendees?.length) {
|
|
116
|
+
const attendeeList = meeting.attendees
|
|
117
|
+
.map((a) => {
|
|
118
|
+
const name = a.name || a.email || "Unknown";
|
|
119
|
+
const role = a.is_organizer ? " (organizer)" : "";
|
|
120
|
+
return `${name}${role}`;
|
|
121
|
+
})
|
|
122
|
+
.join(", ");
|
|
123
|
+
result += `- **Attendees:** ${attendeeList}\n`;
|
|
124
|
+
}
|
|
125
|
+
if (meeting.meeting_link) {
|
|
126
|
+
result += `- **Meeting link:** ${meeting.meeting_link}\n`;
|
|
127
|
+
}
|
|
128
|
+
if (meeting.summary) {
|
|
129
|
+
result += `\n**Summary:**\n${meeting.summary}\n`;
|
|
130
|
+
}
|
|
131
|
+
else if (meeting.short_summary) {
|
|
132
|
+
result += `\n**Summary:** ${meeting.short_summary}\n`;
|
|
133
|
+
}
|
|
134
|
+
if (meeting.action_items?.length) {
|
|
135
|
+
result += `\n**Action Items:**\n`;
|
|
136
|
+
for (const item of meeting.action_items) {
|
|
137
|
+
const assignee = item.assignee ? ` (${item.assignee})` : "";
|
|
138
|
+
const dueDate = item.due_date ? ` — due ${item.due_date}` : "";
|
|
139
|
+
const status = item.completed ? " [Done]" : "";
|
|
140
|
+
result += `- ${item.description}${assignee}${dueDate}${status}\n`;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
if (response.pagination?.has_more) {
|
|
145
|
+
result += `\n_Showing ${meetings.length} of ${response.pagination.total_count ?? "more"} meetings. Adjust start_date, end_date, or limit to refine results._\n`;
|
|
146
|
+
}
|
|
147
|
+
return result;
|
|
148
|
+
}
|
|
149
|
+
exports.executeMeetingsTool = executeMeetingsTool;
|
package/cjs/tools/query.js
CHANGED
|
@@ -11,15 +11,15 @@ exports.executeQueryTool = exports.queryToolDefinition = void 0;
|
|
|
11
11
|
*/
|
|
12
12
|
exports.queryToolDefinition = {
|
|
13
13
|
name: "attrove_query",
|
|
14
|
-
description: `Ask a question
|
|
14
|
+
description: `Ask a question and get an AI-generated answer from the user's own connected accounts.
|
|
15
15
|
|
|
16
|
-
This tool searches
|
|
16
|
+
This read-only tool searches the authenticated user's connected services (e.g., Gmail, Slack, Google Calendar) and returns an answer based on their own data. Only data the user has explicitly connected and authorized is accessed.
|
|
17
17
|
|
|
18
18
|
Use this tool when the user asks questions like:
|
|
19
19
|
- "What did Sarah say about the Q4 budget?"
|
|
20
20
|
- "Summarize my meeting with the engineering team"
|
|
21
21
|
- "What are the action items from yesterday's standup?"
|
|
22
|
-
- "
|
|
22
|
+
- "What context do I need for my 3pm meeting?"`,
|
|
23
23
|
inputSchema: {
|
|
24
24
|
type: "object",
|
|
25
25
|
properties: {
|
|
@@ -40,9 +40,16 @@ Use this tool when the user asks questions like:
|
|
|
40
40
|
},
|
|
41
41
|
required: ["query"],
|
|
42
42
|
},
|
|
43
|
+
annotations: {
|
|
44
|
+
title: "Ask Attrove",
|
|
45
|
+
readOnlyHint: true,
|
|
46
|
+
destructiveHint: false,
|
|
47
|
+
idempotentHint: true,
|
|
48
|
+
openWorldHint: true,
|
|
49
|
+
},
|
|
43
50
|
};
|
|
44
51
|
/**
|
|
45
|
-
*
|
|
52
|
+
* Run a RAG query via the SDK and format the answer with optional sources.
|
|
46
53
|
*/
|
|
47
54
|
async function executeQueryTool(client, input) {
|
|
48
55
|
const options = {};
|
|
@@ -61,7 +68,7 @@ async function executeQueryTool(client, input) {
|
|
|
61
68
|
result += `\n- ${source.title}: "${source.snippet}"`;
|
|
62
69
|
}
|
|
63
70
|
}
|
|
64
|
-
if (response.used_message_ids
|
|
71
|
+
if (response.used_message_ids?.length) {
|
|
65
72
|
result += `\n\n_Based on ${response.used_message_ids.length} messages._`;
|
|
66
73
|
}
|
|
67
74
|
return result;
|
package/cjs/tools/search.js
CHANGED
|
@@ -12,10 +12,10 @@ const constants_js_1 = require("../constants.js");
|
|
|
12
12
|
*/
|
|
13
13
|
exports.searchToolDefinition = {
|
|
14
14
|
name: "attrove_search",
|
|
15
|
-
description: `Search for
|
|
15
|
+
description: `Search for messages or conversations in the user's own connected accounts.
|
|
16
16
|
|
|
17
|
-
This tool performs semantic search across connected
|
|
18
|
-
-
|
|
17
|
+
This read-only tool performs semantic search across the authenticated user's connected services and returns matching messages grouped by conversation. Only the user's own authorized data is searched. Use this when you need:
|
|
18
|
+
- Message data without AI summarization
|
|
19
19
|
- To find specific conversations or threads
|
|
20
20
|
- To explore what information is available about a topic
|
|
21
21
|
|
|
@@ -45,15 +45,23 @@ Use this tool when the user asks things like:
|
|
|
45
45
|
},
|
|
46
46
|
include_body_text: {
|
|
47
47
|
type: "boolean",
|
|
48
|
-
|
|
48
|
+
// NOTE: The "200 characters" must match MAX_BODY_PREVIEW_LENGTH in constants.ts
|
|
49
|
+
description: "Optional: Include message body text preview (truncated to 200 characters)",
|
|
49
50
|
default: true,
|
|
50
51
|
},
|
|
51
52
|
},
|
|
52
53
|
required: ["query"],
|
|
53
54
|
},
|
|
55
|
+
annotations: {
|
|
56
|
+
title: "Search Messages",
|
|
57
|
+
readOnlyHint: true,
|
|
58
|
+
destructiveHint: false,
|
|
59
|
+
idempotentHint: true,
|
|
60
|
+
openWorldHint: true,
|
|
61
|
+
},
|
|
54
62
|
};
|
|
55
63
|
/**
|
|
56
|
-
*
|
|
64
|
+
* Search user messages via the SDK and format results grouped by conversation.
|
|
57
65
|
*/
|
|
58
66
|
async function executeSearchTool(client, input) {
|
|
59
67
|
const options = {
|
|
@@ -85,17 +93,21 @@ async function executeSearchTool(client, input) {
|
|
|
85
93
|
let skippedThreads = 0;
|
|
86
94
|
let skippedMessages = 0;
|
|
87
95
|
for (const [convId, conv] of Object.entries(response.conversations)) {
|
|
88
|
-
// Skip malformed conversation entries
|
|
96
|
+
// Skip malformed conversation entries - may occur with API version mismatches or partial responses
|
|
89
97
|
if (!conv || !conv.threads) {
|
|
90
98
|
skippedConversations++;
|
|
99
|
+
// prettier-ignore
|
|
100
|
+
console.error("[AttroveMCP]", JSON.stringify({ level: "warn", msg: "Search skipped malformed conversation", errorId: "MCP_SEARCH_MALFORMED_CONVERSATION", conversationId: convId, hasConv: !!conv, hasThreads: !!(conv?.threads) }));
|
|
91
101
|
continue;
|
|
92
102
|
}
|
|
93
103
|
const convName = conv.conversation_name || `Conversation ${convId}`;
|
|
94
104
|
result += `\n## ${convName}\n`;
|
|
95
105
|
for (const [threadId, messages] of Object.entries(conv.threads)) {
|
|
96
|
-
// Skip malformed thread entries
|
|
106
|
+
// Skip malformed thread entries - may occur with API version mismatches or partial responses
|
|
97
107
|
if (!Array.isArray(messages)) {
|
|
98
108
|
skippedThreads++;
|
|
109
|
+
// prettier-ignore
|
|
110
|
+
console.error("[AttroveMCP]", JSON.stringify({ level: "warn", msg: "Search skipped malformed thread", errorId: "MCP_SEARCH_MALFORMED_THREAD", conversationId: convId, threadId, messagesType: typeof messages }));
|
|
99
111
|
continue;
|
|
100
112
|
}
|
|
101
113
|
if (messages.length > 1) {
|
|
@@ -104,6 +116,8 @@ async function executeSearchTool(client, input) {
|
|
|
104
116
|
for (const msg of messages) {
|
|
105
117
|
if (!msg) {
|
|
106
118
|
skippedMessages++;
|
|
119
|
+
// prettier-ignore
|
|
120
|
+
console.error("[AttroveMCP]", JSON.stringify({ level: "warn", msg: "Search skipped null message", errorId: "MCP_SEARCH_NULL_MESSAGE", conversationId: convId, threadId }));
|
|
107
121
|
continue;
|
|
108
122
|
}
|
|
109
123
|
// Defensive date formatting to handle invalid dates gracefully
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* HTTP Transport for Attrove MCP Server
|
|
4
|
+
*
|
|
5
|
+
* Provides HTTP-based transport for the MCP server, enabling AI assistants
|
|
6
|
+
* like ChatGPT to connect via HTTP endpoints instead of stdio.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.createHttpHandler = void 0;
|
|
10
|
+
const node_crypto_1 = require("node:crypto");
|
|
11
|
+
const streamableHttp_js_1 = require("@modelcontextprotocol/sdk/server/streamableHttp.js");
|
|
12
|
+
const server_js_1 = require("../server.js");
|
|
13
|
+
/**
|
|
14
|
+
* Create an HTTP request handler for the MCP server.
|
|
15
|
+
*
|
|
16
|
+
* Each call to handleRequest creates new server and transport instances,
|
|
17
|
+
* providing complete isolation between requests. Callers should create
|
|
18
|
+
* a new handler when the config (API key/user ID) changes.
|
|
19
|
+
*
|
|
20
|
+
* @param config - MCP server configuration with API key and user ID
|
|
21
|
+
* @param options - Optional handler configuration
|
|
22
|
+
* @returns Object with handleRequest method for processing HTTP requests
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```typescript
|
|
26
|
+
* // In a Fastify route handler
|
|
27
|
+
* const handler = createHttpHandler({
|
|
28
|
+
* apiKey: 'sk_...',
|
|
29
|
+
* userId: 'user-uuid',
|
|
30
|
+
* baseUrl: 'https://api.attrove.com', // optional, SDK has its own default
|
|
31
|
+
* });
|
|
32
|
+
*
|
|
33
|
+
* // Handle POST request
|
|
34
|
+
* const result = await handler.handleRequest(req.raw, reply.raw, req.body);
|
|
35
|
+
* if (!result.handled) {
|
|
36
|
+
* // No response sent - caller must send error response
|
|
37
|
+
* res.status(500).json({ error: result.error });
|
|
38
|
+
* }
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
/**
|
|
42
|
+
* Type guard for Node.js system errors with error codes.
|
|
43
|
+
*/
|
|
44
|
+
function isNodeError(error) {
|
|
45
|
+
return error instanceof Error && "code" in error;
|
|
46
|
+
}
|
|
47
|
+
function createHttpHandler(config, options = {}) {
|
|
48
|
+
const { enableJsonResponse = true, timeoutMs = 30000 } = options;
|
|
49
|
+
// Validate timeoutMs to prevent unexpected behavior
|
|
50
|
+
if (timeoutMs <= 0) {
|
|
51
|
+
throw new Error("timeoutMs must be a positive number");
|
|
52
|
+
}
|
|
53
|
+
return {
|
|
54
|
+
/**
|
|
55
|
+
* Handle an incoming HTTP request.
|
|
56
|
+
*
|
|
57
|
+
* @param req - Node.js IncomingMessage (or Fastify raw request)
|
|
58
|
+
* @param res - Node.js ServerResponse (or Fastify raw reply)
|
|
59
|
+
* @param parsedBody - Pre-parsed request body (optional, Fastify provides this)
|
|
60
|
+
* @returns Promise resolving to handler result indicating if request was processed
|
|
61
|
+
*/
|
|
62
|
+
handleRequest: async (req, res, parsedBody) => {
|
|
63
|
+
let server;
|
|
64
|
+
let timeoutId;
|
|
65
|
+
let result;
|
|
66
|
+
let cleanupFailed = false;
|
|
67
|
+
let timeoutOccurred = false;
|
|
68
|
+
// Correlation ID links timeout errors with subsequent suppressed errors for debugging
|
|
69
|
+
const correlationId = (0, node_crypto_1.randomUUID)().slice(0, 8);
|
|
70
|
+
try {
|
|
71
|
+
server = (0, server_js_1.createServer)(config);
|
|
72
|
+
const transport = new streamableHttp_js_1.StreamableHTTPServerTransport({
|
|
73
|
+
sessionIdGenerator: undefined, // Stateless mode - no session tracking needed
|
|
74
|
+
enableJsonResponse,
|
|
75
|
+
});
|
|
76
|
+
await server.connect(transport);
|
|
77
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
78
|
+
timeoutId = setTimeout(() => {
|
|
79
|
+
timeoutOccurred = true;
|
|
80
|
+
reject(new Error(`MCP request timed out after ${timeoutMs}ms`));
|
|
81
|
+
}, timeoutMs);
|
|
82
|
+
});
|
|
83
|
+
// Store reference to transport promise so we can attach a catch handler.
|
|
84
|
+
// This prevents unhandled rejection if timeout wins the race and server.close()
|
|
85
|
+
// later causes the transport to reject.
|
|
86
|
+
const transportPromise = transport.handleRequest(req, res, parsedBody);
|
|
87
|
+
// When timeout occurs, Promise.race exits with the timeout error (caught below),
|
|
88
|
+
// but transportPromise may reject later when server.close() interrupts it.
|
|
89
|
+
// Only log here if a timeout actually occurred - otherwise Promise.race will
|
|
90
|
+
// propagate the rejection to the main catch block and logging here would be duplicate.
|
|
91
|
+
transportPromise.catch((suppressedError) => {
|
|
92
|
+
if (!timeoutOccurred) {
|
|
93
|
+
// No timeout - Promise.race will handle this rejection via the main catch block
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
const errorMsg = suppressedError instanceof Error
|
|
97
|
+
? suppressedError.message
|
|
98
|
+
: String(suppressedError);
|
|
99
|
+
// Critical errors that indicate systemic issues (not just slow requests)
|
|
100
|
+
const isCritical = /SSL|certificate|ENOTFOUND|ECONNREFUSED|auth/i.test(errorMsg);
|
|
101
|
+
const logData = {
|
|
102
|
+
level: isCritical ? "error" : "warn",
|
|
103
|
+
msg: "MCP transport rejected after timeout - suppressed to prevent unhandled rejection",
|
|
104
|
+
errorId: isCritical
|
|
105
|
+
? "MCP_HTTP_SUPPRESSED_CRITICAL_ERROR"
|
|
106
|
+
: "MCP_HTTP_SUPPRESSED_POST_TIMEOUT",
|
|
107
|
+
correlationId,
|
|
108
|
+
error: errorMsg,
|
|
109
|
+
userId: config.userId,
|
|
110
|
+
};
|
|
111
|
+
// prettier-ignore
|
|
112
|
+
console.error("[AttroveMCP]", JSON.stringify(logData));
|
|
113
|
+
});
|
|
114
|
+
await Promise.race([transportPromise, timeoutPromise]);
|
|
115
|
+
result = { handled: true };
|
|
116
|
+
}
|
|
117
|
+
catch (error) {
|
|
118
|
+
// Preserve context for non-Error values (e.g., throw "string", throw { code: ... })
|
|
119
|
+
const message = error instanceof Error
|
|
120
|
+
? error.message
|
|
121
|
+
: typeof error === "object" && error !== null
|
|
122
|
+
? `Unknown error: ${JSON.stringify(error)}`
|
|
123
|
+
: `Unknown error: ${String(error)}`;
|
|
124
|
+
const stack = error instanceof Error ? error.stack : undefined;
|
|
125
|
+
const isTimeout = error instanceof Error && error.message.includes("timed out");
|
|
126
|
+
// Classify error type for appropriate logging level
|
|
127
|
+
// Use error.code for socket errors (more precise than string matching)
|
|
128
|
+
const isSocketError = isNodeError(error) &&
|
|
129
|
+
(error.code === "ECONNRESET" ||
|
|
130
|
+
error.code === "EPIPE" ||
|
|
131
|
+
error.code === "ECONNABORTED");
|
|
132
|
+
const isExpectedError = isTimeout || isSocketError;
|
|
133
|
+
// Log with structured JSON for observability (callers should also log via their infrastructure)
|
|
134
|
+
const errorId = isTimeout
|
|
135
|
+
? "MCP_HTTP_TIMEOUT"
|
|
136
|
+
: isSocketError
|
|
137
|
+
? "MCP_HTTP_SOCKET_ERROR"
|
|
138
|
+
: "MCP_HTTP_UNEXPECTED_ERROR";
|
|
139
|
+
const logData = {
|
|
140
|
+
level: isExpectedError ? "warn" : "error",
|
|
141
|
+
msg: isExpectedError
|
|
142
|
+
? "MCP HTTP handler operational failure"
|
|
143
|
+
: "MCP HTTP handler unexpected failure",
|
|
144
|
+
errorId,
|
|
145
|
+
correlationId,
|
|
146
|
+
error: message,
|
|
147
|
+
isTimeout,
|
|
148
|
+
userId: config.userId,
|
|
149
|
+
...(stack && !isExpectedError && { stack }),
|
|
150
|
+
...(!isExpectedError && { errorType: error?.constructor?.name }),
|
|
151
|
+
};
|
|
152
|
+
// prettier-ignore
|
|
153
|
+
console.error("[AttroveMCP]", JSON.stringify(logData));
|
|
154
|
+
result = { handled: false, error: message, stack, isTimeout };
|
|
155
|
+
}
|
|
156
|
+
finally {
|
|
157
|
+
// Clear timeout to prevent timer accumulation under load
|
|
158
|
+
if (timeoutId !== undefined) {
|
|
159
|
+
clearTimeout(timeoutId);
|
|
160
|
+
}
|
|
161
|
+
if (server) {
|
|
162
|
+
try {
|
|
163
|
+
await server.close();
|
|
164
|
+
}
|
|
165
|
+
catch (cleanupError) {
|
|
166
|
+
cleanupFailed = true;
|
|
167
|
+
// Cleanup failures could indicate resource leaks - always log for monitoring
|
|
168
|
+
const cleanupLogData = {
|
|
169
|
+
level: "error",
|
|
170
|
+
msg: "MCP server cleanup failed - potential resource leak",
|
|
171
|
+
errorId: "MCP_CLEANUP_FAILURE",
|
|
172
|
+
correlationId,
|
|
173
|
+
error: cleanupError instanceof Error
|
|
174
|
+
? cleanupError.message
|
|
175
|
+
: String(cleanupError),
|
|
176
|
+
stack: cleanupError instanceof Error ? cleanupError.stack : undefined,
|
|
177
|
+
userId: config.userId,
|
|
178
|
+
};
|
|
179
|
+
// prettier-ignore
|
|
180
|
+
console.error("[AttroveMCP]", JSON.stringify(cleanupLogData));
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
// Include cleanup failure warning in success result for monitoring
|
|
185
|
+
if (result.handled && cleanupFailed) {
|
|
186
|
+
return { handled: true, cleanupFailed: true };
|
|
187
|
+
}
|
|
188
|
+
return result;
|
|
189
|
+
},
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
exports.createHttpHandler = createHttpHandler;
|
package/esm/index.d.ts
CHANGED
|
@@ -28,6 +28,8 @@
|
|
|
28
28
|
*/
|
|
29
29
|
export { createServer, startServer, getConfigFromEnv } from "./server.js";
|
|
30
30
|
export type { McpServerConfig } from "./server.js";
|
|
31
|
-
export { allToolDefinitions, queryToolDefinition, searchToolDefinition, integrationsToolDefinition, } from "./tools/index.js";
|
|
31
|
+
export { allToolDefinitions, queryToolDefinition, searchToolDefinition, integrationsToolDefinition, eventsToolDefinition, meetingsToolDefinition, } from "./tools/index.js";
|
|
32
32
|
export { getVersion } from "./version.js";
|
|
33
|
+
export { createHttpHandler } from "./transport/http.js";
|
|
34
|
+
export type { McpHttpHandler, McpHttpHandlerResult, McpHttpHandlerFailure, CreateHttpHandlerOptions, } from "./transport/http.js";
|
|
33
35
|
//# sourceMappingURL=index.d.ts.map
|
package/esm/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../packages/mcp/src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC1E,YAAY,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAEnD,OAAO,EACL,kBAAkB,EAClB,mBAAmB,EACnB,oBAAoB,EACpB,0BAA0B,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../packages/mcp/src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC1E,YAAY,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAEnD,OAAO,EACL,kBAAkB,EAClB,mBAAmB,EACnB,oBAAoB,EACpB,0BAA0B,EAC1B,oBAAoB,EACpB,sBAAsB,GACvB,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAG1C,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,YAAY,EACV,cAAc,EACd,oBAAoB,EACpB,qBAAqB,EACrB,wBAAwB,GACzB,MAAM,qBAAqB,CAAC"}
|
package/esm/index.js
CHANGED
|
@@ -27,6 +27,8 @@
|
|
|
27
27
|
* @packageDocumentation
|
|
28
28
|
*/
|
|
29
29
|
export { createServer, startServer, getConfigFromEnv } from "./server.js";
|
|
30
|
-
export { allToolDefinitions, queryToolDefinition, searchToolDefinition, integrationsToolDefinition, } from "./tools/index.js";
|
|
30
|
+
export { allToolDefinitions, queryToolDefinition, searchToolDefinition, integrationsToolDefinition, eventsToolDefinition, meetingsToolDefinition, } from "./tools/index.js";
|
|
31
31
|
export { getVersion } from "./version.js";
|
|
32
|
+
// HTTP transport for hosted MCP endpoints
|
|
33
|
+
export { createHttpHandler } from "./transport/http.js";
|
|
32
34
|
//# sourceMappingURL=index.js.map
|
package/esm/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../packages/mcp/src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAG1E,OAAO,EACL,kBAAkB,EAClB,mBAAmB,EACnB,oBAAoB,EACpB,0BAA0B,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../packages/mcp/src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAG1E,OAAO,EACL,kBAAkB,EAClB,mBAAmB,EACnB,oBAAoB,EACpB,0BAA0B,EAC1B,oBAAoB,EACpB,sBAAsB,GACvB,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE1C,0CAA0C;AAC1C,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC"}
|
package/esm/server.d.ts
CHANGED
|
@@ -5,35 +5,37 @@
|
|
|
5
5
|
* to access user context through the Attrove API.
|
|
6
6
|
*/
|
|
7
7
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
8
|
+
import { ApiKeyFormat } from "@attrove/sdk";
|
|
8
9
|
/**
|
|
9
10
|
* Configuration for the MCP server.
|
|
10
11
|
*/
|
|
11
12
|
export interface McpServerConfig {
|
|
12
13
|
/**
|
|
13
14
|
* Attrove API key (sk_ prefixed).
|
|
15
|
+
* Must be a valid ApiKeyFormat string starting with "sk_".
|
|
14
16
|
*/
|
|
15
|
-
apiKey:
|
|
17
|
+
apiKey: ApiKeyFormat;
|
|
16
18
|
/**
|
|
17
19
|
* User ID (UUID) to scope API calls.
|
|
18
20
|
*/
|
|
19
21
|
userId: string;
|
|
20
22
|
/**
|
|
21
|
-
* Optional API base URL.
|
|
23
|
+
* Optional API base URL. If not specified, the SDK's default is used.
|
|
22
24
|
*/
|
|
23
25
|
baseUrl?: string;
|
|
24
26
|
}
|
|
25
27
|
/**
|
|
26
|
-
*
|
|
28
|
+
* Wire up tool definitions and dispatch logic for the Attrove MCP server.
|
|
27
29
|
*/
|
|
28
30
|
export declare function createServer(config: McpServerConfig): Server;
|
|
29
31
|
/**
|
|
30
|
-
*
|
|
32
|
+
* Connect the MCP server to stdin/stdout and begin serving requests.
|
|
31
33
|
*/
|
|
32
34
|
export declare function startServer(config: McpServerConfig): Promise<void>;
|
|
33
35
|
/**
|
|
34
36
|
* Get configuration from environment variables.
|
|
35
37
|
*
|
|
36
|
-
* @throws {Error} If required environment variables are missing
|
|
38
|
+
* @throws {Error} If required environment variables are missing or invalid
|
|
37
39
|
*/
|
|
38
40
|
export declare function getConfigFromEnv(): McpServerConfig;
|
|
39
41
|
//# sourceMappingURL=server.d.ts.map
|
package/esm/server.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../../../packages/mcp/src/server.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../../../packages/mcp/src/server.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AAMnE,OAAO,EAKL,YAAY,EAEb,MAAM,cAAc,CAAC;AAuDtB;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B;;;OAGG;IACH,MAAM,EAAE,YAAY,CAAC;IAErB;;OAEG;IACH,MAAM,EAAE,MAAM,CAAC;IAEf;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAgQD;;GAEG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,eAAe,GAAG,MAAM,CAgG5D;AAED;;GAEG;AACH,wBAAsB,WAAW,CAAC,MAAM,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAIxE;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,IAAI,eAAe,CA8BlD"}
|