Skip to content

Commit 0e76bf2

Browse files
fix: date filter and status defaults for v1.6.2
- Replace modifiedSince with createdAt query syntax for accurate creation date filtering - Add appendCreatedAtFilter helper with ISO 8601 validation and ms stripping - Fix advancedConversationSearch to default to status=all - Fix structuredConversationFilter to send status=all for ticket lookups - Update tests for new query format and version
1 parent ee89c87 commit 0e76bf2

10 files changed

Lines changed: 95 additions & 49 deletions

File tree

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,5 +55,5 @@ ENTRYPOINT ["node", "dist/index.js"]
5555
# Labels for metadata
5656
LABEL name="help-scout-mcp-server" \
5757
description="Help Scout MCP server for searching inboxes, conversations, and threads" \
58-
version="1.6.1" \
58+
version="1.6.2" \
5959
maintainer="Drew Burchfield"

README.md

Lines changed: 12 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -9,45 +9,32 @@
99
1010
## Table of Contents
1111

12-
- [What's New](#whats-new-in-v161)
12+
- [What's New](#whats-new-in-v162)
1313
- [Quick Start](#quick-start)
1414
- [API Credentials](#getting-your-api-credentials)
1515
- [Tools & Capabilities](#tools--capabilities)
1616
- [Configuration](#configuration-options)
1717
- [Troubleshooting](#troubleshooting)
1818
- [Contributing](#contributing)
1919

20-
## What's New in v1.6.1
20+
## What's New in v1.6.2
2121

22-
- **Pagination Bug Fix**: Multi-status searches now report accurate total counts. Previously, searching without a status filter (active/pending/closed in parallel) reported `totalResults` capped at the page limit instead of the real total across all statuses. Now returns `totalAvailable` (sum of API totals) and `totalByStatus` breakdown. ([#10](https://github.com/drewburchfield/help-scout-mcp-server/issues/10))
23-
- **Client-Side Date Filtering**: New `createdBefore` parameter for all search tools with clear metadata distinguishing filtered counts from API totals
24-
- **Partial Failure Transparency**: Multi-status searches now surface structured error info when individual status queries fail, instead of silently returning incomplete results
25-
- **Stronger Error Type Guard**: `isApiError` now validates against the schema enum, preventing false matches on Node.js system errors
22+
- **Date Filter Fix**: `createdAfter` and `timeframeDays` now correctly filter by conversation creation date instead of last modification date. Previously, all search tools mapped `createdAfter` to Help Scout's `modifiedSince` API parameter, silently excluding conversations that were created within the timeframe but not recently modified. Now uses Help Scout query syntax `createdAt:[date TO *]` for accurate creation-date filtering.
23+
- **Multi-Status Search Consistency**: `advancedConversationSearch` and `structuredConversationFilter` now search all statuses (active, pending, closed) by default, matching the behavior established in v1.6.0 for `searchConversations`. Previously, omitting the status parameter silently returned only active conversations.
24+
- **Ticket Number Lookup Fix**: `structuredConversationFilter` with `conversationNumber` now finds conversations regardless of status. Previously, looking up a closed ticket by number returned empty results.
25+
26+
### Previous Release (v1.6.1)
27+
28+
- **Pagination Bug Fix**: Multi-status searches now report accurate total counts with `totalAvailable` and `totalByStatus` breakdown ([#10](https://github.com/drewburchfield/help-scout-mcp-server/issues/10))
29+
- **Client-Side Date Filtering**: New `createdBefore` parameter for all search tools
30+
- **Partial Failure Transparency**: Multi-status searches surface structured error info when individual status queries fail
2631
- **Dependency Security Fixes**: Upgraded `@modelcontextprotocol/sdk` to 1.26.0, fixed axios, hono, and qs vulnerabilities
2732

2833
### Previous Release (v1.6.0)
2934

3035
- **Inbox Auto-Discovery**: Inboxes automatically discovered on server connect and included in server instructions
31-
- **Multi-Status Search Default**: `searchConversations` now searches all statuses (active, pending, closed) by default when no status specified
36+
- **Multi-Status Search Default**: `searchConversations` searches all statuses (active, pending, closed) by default when no status specified
3237
- **Simpler Workflow**: AI agents can use inbox IDs directly from server instructions without a preliminary lookup step
33-
- **Deprecated Tools**: `searchInboxes` and `listAllInboxes` remain functional but are deprecated (inboxes now in instructions)
34-
35-
### Previous Release (v1.5.0)
36-
37-
- MCP SDK with enhanced compatibility
38-
- `structuredConversationFilter` for ID-based refinement and ticket number lookup
39-
- Enhanced input validation and error handling
40-
- Standardized environment variable naming (`APP_ID`/`APP_SECRET`)
41-
42-
### Migration from v1.5.0
43-
44-
**For programmatic users:**
45-
- `HelpScoutMCPServer` now uses an async factory pattern: use `await HelpScoutMCPServer.create()` instead of `new HelpScoutMCPServer()`
46-
47-
**Response format change:**
48-
- `searchConversations` response now includes `statusesSearched` array instead of `status` string when searching without a specific status filter
49-
50-
**No action required for most users** - the MCP protocol interface remains unchanged.
5138

5239
## Prerequisites
5340

helpscout-mcp-extension/manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"manifest_version": "0.3",
33
"name": "help-scout-mcp-server",
44
"display_name": "Help Scout MCP Server",
5-
"version": "1.6.1",
5+
"version": "1.6.2",
66
"description": "Connect Claude and other AI assistants to your Help Scout data with enterprise-grade security and advanced search capabilities.",
77
"long_description": "Connect your AI assistant to Help Scout for intelligent customer support analysis.\n\n**Search & Analysis:**\n• Advanced conversation search with query syntax\n• Multi-status search across active, pending, and closed\n• Boolean queries with content and subject filtering\n• Conversation summaries and full thread retrieval\n• Direct ticket lookup by number\n\n**Enterprise Security:**\n• OAuth2 Client Credentials authentication\n• Optional content redaction for privacy\n• Built-in caching and rate limiting\n• Automatic retry with exponential backoff",
88
"author": {

mcp.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "helpscout-search",
33
"description": "Search-capable MCP server for Help Scout inboxes, conversations, and threads.",
4-
"version": "1.6.1",
4+
"version": "1.6.2",
55
"mcpVersion": "1.17.4",
66
"resources": [
77
"helpscout://inboxes",

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "help-scout-mcp-server",
3-
"version": "1.6.1",
3+
"version": "1.6.2",
44
"description": "The first MCP server for Help Scout - search conversations, threads, and inboxes with AI agents",
55
"main": "dist/index.js",
66
"type": "module",

src/__tests__/index.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ describe('HelpScoutMCPServer - THE ACTUAL APPLICATION', () => {
9898
expect(Server).toHaveBeenCalledWith(
9999
{
100100
name: 'helpscout-search',
101-
version: '1.6.1',
101+
version: '1.6.2',
102102
},
103103
expect.objectContaining({
104104
capabilities: {

src/__tests__/integration.test.ts

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -207,27 +207,34 @@ describe('Complete User Workflows - Integration Tests', () => {
207207
};
208208

209209
// Set up nock interceptors for each status
210+
// Query now includes createdAt range from timeframeDays (fix for modifiedSince mismatch)
210211
nock(baseURL)
211212
.get('/conversations')
212-
.query(params =>
213-
params.status === 'active' &&
214-
params.query === '(body:"billing" OR subject:"billing")'
213+
.query(params =>
214+
params.status === 'active' &&
215+
typeof params.query === 'string' &&
216+
params.query.includes('body:"billing"') &&
217+
params.query.includes('createdAt:')
215218
)
216219
.reply(200, mockActiveConversations);
217220

218221
nock(baseURL)
219222
.get('/conversations')
220-
.query(params =>
221-
params.status === 'pending' &&
222-
params.query === '(body:"billing" OR subject:"billing")'
223+
.query(params =>
224+
params.status === 'pending' &&
225+
typeof params.query === 'string' &&
226+
params.query.includes('body:"billing"') &&
227+
params.query.includes('createdAt:')
223228
)
224229
.reply(200, mockPendingConversations);
225230

226231
nock(baseURL)
227232
.get('/conversations')
228-
.query(params =>
229-
params.status === 'closed' &&
230-
params.query === '(body:"billing" OR subject:"billing")'
233+
.query(params =>
234+
params.status === 'closed' &&
235+
typeof params.query === 'string' &&
236+
params.query.includes('body:"billing"') &&
237+
params.query.includes('createdAt:')
231238
)
232239
.reply(200, mockClosedConversations);
233240

src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ export class HelpScoutMCPServer {
3030
this.server = new Server(
3131
{
3232
name: 'helpscout-search',
33-
version: '1.6.1',
33+
version: '1.6.2',
3434
},
3535
{
3636
capabilities: {

src/tools/index.ts

Lines changed: 59 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,37 @@ export class ToolHandler {
7474
return term.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
7575
}
7676

77+
/**
78+
* Append a createdAt date range to an existing Help Scout query string.
79+
* Help Scout has no native createdAfter/createdBefore URL params, so we
80+
* use query syntax: (createdAt:[start TO end]).
81+
*/
82+
private appendCreatedAtFilter(
83+
existingQuery: string | undefined,
84+
createdAfter?: string,
85+
createdBefore?: string
86+
): string | undefined {
87+
if (!createdAfter && !createdBefore) return existingQuery;
88+
89+
// Validate date format to prevent query injection and match Help Scout expectations
90+
const isoDatePattern = /^\d{4}-\d{2}-\d{2}(T[\d:.]+Z?)?$/;
91+
if (createdAfter && !isoDatePattern.test(createdAfter)) {
92+
throw new Error(`Invalid createdAfter date format: ${createdAfter}. Expected ISO 8601 (e.g., 2024-01-15T00:00:00Z)`);
93+
}
94+
if (createdBefore && !isoDatePattern.test(createdBefore)) {
95+
throw new Error(`Invalid createdBefore date format: ${createdBefore}. Expected ISO 8601 (e.g., 2024-01-15T00:00:00Z)`);
96+
}
97+
98+
// Strip milliseconds (Help Scout rejects .xxx format)
99+
const normalize = (d: string) => d.replace(/\.\d{3}Z$/, 'Z');
100+
const start = createdAfter ? normalize(createdAfter) : '*';
101+
const end = createdBefore ? normalize(createdBefore) : '*';
102+
const clause = `(createdAt:[${start} TO ${end}])`;
103+
104+
if (!existingQuery) return clause;
105+
return `(${existingQuery}) AND ${clause}`;
106+
}
107+
77108
/**
78109
* Set the current user query for context-aware validation
79110
*/
@@ -566,7 +597,12 @@ export class ToolHandler {
566597
}
567598

568599
if (input.tag) baseParams.tag = input.tag;
569-
if (input.createdAfter) baseParams.modifiedSince = input.createdAfter;
600+
601+
const queryWithDate = this.appendCreatedAtFilter(
602+
baseParams.query as string | undefined,
603+
input.createdAfter
604+
);
605+
if (queryWithDate) baseParams.query = queryWithDate;
570606

571607
let conversations: Conversation[] = [];
572608
let searchedStatuses: string[];
@@ -968,8 +1004,14 @@ export class ToolHandler {
9681004
queryParams.mailbox = effectiveInboxId;
9691005
}
9701006

971-
if (input.status) queryParams.status = input.status;
972-
if (input.createdAfter) queryParams.modifiedSince = input.createdAfter;
1007+
// Default to all statuses for consistency with searchConversations (v1.6.0+)
1008+
queryParams.status = input.status || 'all';
1009+
1010+
const queryWithDate = this.appendCreatedAtFilter(
1011+
queryParams.query as string | undefined,
1012+
input.createdAfter
1013+
);
1014+
if (queryWithDate) queryParams.query = queryWithDate;
9731015

9741016
const response = await helpScoutClient.get<PaginatedResponse<Conversation>>('/conversations', queryParams);
9751017

@@ -1232,14 +1274,18 @@ export class ToolHandler {
12321274
inboxId?: string;
12331275
createdBefore?: string;
12341276
}) {
1277+
const queryWithDate = this.appendCreatedAtFilter(
1278+
params.searchQuery,
1279+
params.createdAfter
1280+
);
1281+
12351282
const queryParams: Record<string, unknown> = {
12361283
page: 1,
12371284
size: params.limitPerStatus,
12381285
sortField: TOOL_CONSTANTS.DEFAULT_SORT_FIELD,
12391286
sortOrder: TOOL_CONSTANTS.DEFAULT_SORT_ORDER,
1240-
query: params.searchQuery,
1287+
query: queryWithDate || params.searchQuery,
12411288
status: params.status,
1242-
modifiedSince: params.createdAfter,
12431289
};
12441290

12451291
if (params.inboxId) {
@@ -1345,11 +1391,17 @@ export class ToolHandler {
13451391
// Apply combination filters
13461392
const effectiveInboxId = input.inboxId || config.helpscout.defaultInboxId;
13471393
if (effectiveInboxId) queryParams.mailbox = effectiveInboxId;
1348-
if (input.status && input.status !== 'all') queryParams.status = input.status;
1394+
// Send status=all explicitly (Help Scout defaults to active-only when omitted)
1395+
queryParams.status = input.status || 'all';
13491396
if (input.tag) queryParams.tag = input.tag;
1350-
if (input.createdAfter) queryParams.modifiedSince = input.createdAfter;
13511397
if (input.modifiedSince) queryParams.modifiedSince = input.modifiedSince;
13521398

1399+
const queryWithDate = this.appendCreatedAtFilter(
1400+
queryParams.query as string | undefined,
1401+
input.createdAfter
1402+
);
1403+
if (queryWithDate) queryParams.query = queryWithDate;
1404+
13531405
const response = await helpScoutClient.get<PaginatedResponse<Conversation>>('/conversations', queryParams);
13541406
let conversations = response._embedded?.conversations || [];
13551407

0 commit comments

Comments
 (0)