@findtime/mcp-server 3.25.13 → 3.25.15

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.
Files changed (4) hide show
  1. package/README.md +2 -0
  2. package/SKILL.md +16 -1
  3. package/package.json +1 -1
  4. package/server.js +135 -2
package/README.md CHANGED
@@ -14,6 +14,7 @@ Published surfaces:
14
14
  ## Tool surface
15
15
 
16
16
  - `answer_time_question`
17
+ - `get_findtime_help`
17
18
  - `time_snapshot`
18
19
  - `get_current_time`
19
20
  - `get_dst_schedule`
@@ -49,6 +50,7 @@ Optional environment variables:
49
50
  - `TIME_API_BASE_URL`
50
51
  - `TIME_API_TIMEOUT_MS`
51
52
  - `FINDTIME_MCP_CLIENT_TYPE`
53
+ - `FINDTIME_MCP_TOOL_MODE=answer-only` to expose only `answer_time_question`, `get_findtime_help`, and `get_api_diagnostics` for enterprise bots that should route every natural-language request through the answer API.
52
54
  - `FINDTIME_MCP_CLIENT_ID` or `FINDTIME_MCP_INSTALL_ID` to provide a stable client identifier. If omitted, the server creates one locally under the user's state directory.
53
55
  - `FINDTIME_MCP_INSTRUMENTATION_ENABLED=false` to opt out of anonymous usage telemetry.
54
56
  - `FINDTIME_MCP_USAGE_TELEMETRY_URL` to override the default telemetry endpoint.
package/SKILL.md CHANGED
@@ -72,9 +72,23 @@ Most MCP clients need the same core config:
72
72
 
73
73
  For a company bot or server-side agent, set `FINDTIME_MCP_CLIENT_TYPE` to a stable identifier such as `company-bot`, and provide a stable install ID with `FINDTIME_MCP_CLIENT_ID` or `FINDTIME_MCP_INSTALL_ID` when possible.
74
74
 
75
+ For enterprise bots that should avoid model-level tool selection across the lower-level APIs, set:
76
+
77
+ ```text
78
+ FINDTIME_MCP_TOOL_MODE=answer-only
79
+ ```
80
+
81
+ In answer-only mode, the MCP server exposes only:
82
+
83
+ - `answer_time_question`
84
+ - `get_findtime_help`
85
+ - `get_api_diagnostics`
86
+
87
+ Use this mode when the bot should route all natural-language time requests through the answer API first.
88
+
75
89
  ## Tool Selection
76
90
 
77
- Use `answer_time_question` first for natural-language or ambiguous prompts.
91
+ Use `answer_time_question` first for natural-language or ambiguous prompts. In enterprise bot deployments, prefer `FINDTIME_MCP_TOOL_MODE=answer-only` so the agent sees `answer_time_question` as the default execution path instead of choosing lower-level tools directly.
78
92
 
79
93
  Examples:
80
94
 
@@ -86,6 +100,7 @@ Examples:
86
100
 
87
101
  Use specific tools when the agent has already parsed the task into structured inputs:
88
102
 
103
+ - `get_findtime_help`: examples of supported intents, answer API usage, ambiguity handling, and enterprise deployment guidance
89
104
  - `get_current_time`: current local time for a known city, country, timezone, or location
90
105
  - `convert_time`: convert a known date/time from one place or timezone to another
91
106
  - `get_dst_schedule`: DST status and transition dates
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@findtime/mcp-server",
3
- "version": "3.25.13",
3
+ "version": "3.25.15",
4
4
  "mcpName": "io.github.hkchao/findtime-mcp-server",
5
5
  "description": "Production-parity MCP server for the findtime.io Time API",
6
6
  "bin": {
package/server.js CHANGED
@@ -49,6 +49,7 @@ const DEFAULT_API_KEY = firstNonEmpty(
49
49
  process.env.FINDTIME_TIME_API_KEY
50
50
  );
51
51
  const TIMEZONE_HELPERS_PATH = path.join(REPO_ROOT, 'slack-bot', 'timezone-helpers.js');
52
+ const ANSWER_ONLY_TOOL_NAMES = new Set(['answer_time_question', 'get_findtime_help', 'get_api_diagnostics']);
52
53
 
53
54
  const TOOL_DEFINITIONS = [
54
55
  {
@@ -104,6 +105,18 @@ const TOOL_DEFINITIONS = [
104
105
  return { path: '/health', params: new URLSearchParams() };
105
106
  }
106
107
  },
108
+ {
109
+ name: 'get_findtime_help',
110
+ description: 'Return enterprise-friendly findtime.io MCP usage help, including supported time-intelligence intents, answer API examples, ambiguity handling examples, and recommended answer-only deployment guidance.',
111
+ inputSchema: {
112
+ type: 'object',
113
+ properties: {},
114
+ additionalProperties: false
115
+ },
116
+ buildRequest() {
117
+ return null;
118
+ }
119
+ },
107
120
  {
108
121
  name: 'time_snapshot',
109
122
  description: 'Return the production time snapshot payload for one location or a list of locations.',
@@ -385,6 +398,114 @@ const TOOL_DEFINITIONS_BY_NAME = new Map(TOOL_DEFINITIONS.map((tool) => [tool.na
385
398
  let cachedResolveLocation;
386
399
  let cachedMcpClientId;
387
400
 
401
+ function getMcpToolMode() {
402
+ const mode = String(firstNonEmpty(
403
+ process.env.FINDTIME_MCP_TOOL_MODE,
404
+ process.env.FINDTIME_MCP_TOOLS
405
+ ) || 'full').trim().toLowerCase();
406
+
407
+ return ['answer-only', 'answer_only', 'answer'].includes(mode)
408
+ ? 'answer-only'
409
+ : 'full';
410
+ }
411
+
412
+ function getVisibleToolDefinitions() {
413
+ if (getMcpToolMode() !== 'answer-only') {
414
+ return TOOL_DEFINITIONS;
415
+ }
416
+
417
+ return TOOL_DEFINITIONS.filter((tool) => ANSWER_ONLY_TOOL_NAMES.has(tool.name));
418
+ }
419
+
420
+ function buildFindtimeHelpPayload() {
421
+ return {
422
+ ok: true,
423
+ tool: 'get_findtime_help',
424
+ recommendedTool: 'answer_time_question',
425
+ recommendedMode: 'FINDTIME_MCP_TOOL_MODE=answer-only',
426
+ summary: 'Use findtime.io MCP for accurate timezone, DST, conversion, overlap-hours, and cross-timezone meeting-time intelligence. In enterprise bots, route natural-language questions through answer_time_question.',
427
+ install: {
428
+ command: 'npx',
429
+ args: ['-y', '@findtime/mcp-server'],
430
+ requiredEnv: ['FINDTIME_TIME_API_KEY'],
431
+ recommendedEnterpriseEnv: {
432
+ FINDTIME_TIME_API_BASE_URL: 'https://time-api.findtime.io',
433
+ FINDTIME_MCP_CLIENT_TYPE: 'company-bot',
434
+ FINDTIME_MCP_TOOL_MODE: 'answer-only'
435
+ }
436
+ },
437
+ intents: [
438
+ {
439
+ intent: 'current_time',
440
+ example: 'What time is it now in Tokyo?',
441
+ notes: 'Returns local date/time, timezone, UTC offset, and DST context when relevant.'
442
+ },
443
+ {
444
+ intent: 'timezone_lookup',
445
+ example: 'What is the IANA timezone for San Francisco?',
446
+ notes: 'Use IANA timezone IDs as canonical identifiers.'
447
+ },
448
+ {
449
+ intent: 'time_conversion',
450
+ example: 'Convert 3pm next Tuesday in New York to London, Berlin, and Singapore.',
451
+ notes: 'Include local dates because conversions often cross calendar days.'
452
+ },
453
+ {
454
+ intent: 'dst_status',
455
+ example: 'Is Mexico City on DST?',
456
+ notes: 'Returns DST status and transition context when available.'
457
+ },
458
+ {
459
+ intent: 'overlap_hours',
460
+ example: 'What working hours overlap for San Francisco, Berlin, and Tokyo?',
461
+ notes: 'Useful for distributed-team availability and handoff planning.'
462
+ },
463
+ {
464
+ intent: 'meeting_time_search',
465
+ example: 'Find a good 45-minute meeting time next week for San Francisco, Berlin, and Sydney.',
466
+ notes: 'Returns ranked meeting windows and tradeoffs across participants.'
467
+ },
468
+ {
469
+ intent: 'abbreviation_disambiguation',
470
+ example: 'What does CST mean for a customer in China versus a customer in Chicago?',
471
+ notes: 'Timezone abbreviations are aliases, not canonical identifiers.'
472
+ },
473
+ {
474
+ intent: 'location_disambiguation',
475
+ example: 'What time is it in Victoria?',
476
+ notes: 'Ambiguous place names should return clarification or country-aware choices instead of silent guessing.'
477
+ }
478
+ ],
479
+ ambiguityExamples: [
480
+ {
481
+ query: 'What time is it in Springfield?',
482
+ expectedBehavior: 'Ask for clarification or provide likely matches because many cities share this name.'
483
+ },
484
+ {
485
+ query: 'Convert 9am CST to London.',
486
+ expectedBehavior: 'Clarify whether CST means China Standard Time, Central Standard Time, Cuba Standard Time, or another regional meaning when context is insufficient.'
487
+ },
488
+ {
489
+ query: 'Schedule a meeting for Paris and Sydney next Friday.',
490
+ expectedBehavior: 'Resolve Paris, France unless context suggests otherwise; include date and local-time tradeoffs.'
491
+ }
492
+ ],
493
+ answerApiPattern: {
494
+ tool: 'answer_time_question',
495
+ arguments: {
496
+ query: 'Best meeting time for San Francisco, Berlin, and Sydney next week',
497
+ userTimezone: 'America/Los_Angeles',
498
+ locale: 'en-US'
499
+ }
500
+ },
501
+ failurePolicy: [
502
+ 'If a findtime.io MCP call fails, say the MCP call failed and include the visible error.',
503
+ 'Do not present fallback timezone or DST calculations as if they came from findtime.io MCP.',
504
+ 'For high-stakes scheduling, retry or ask the user before using fallback reasoning.'
505
+ ]
506
+ };
507
+ }
508
+
388
509
  function safeReadJson(filePath) {
389
510
  try {
390
511
  return JSON.parse(fs.readFileSync(filePath, 'utf8'));
@@ -1073,10 +1194,22 @@ function createFindtimeMcpServer(options = {}) {
1073
1194
 
1074
1195
  async function callTool(name, args = {}) {
1075
1196
  const tool = TOOL_DEFINITIONS_BY_NAME.get(name);
1076
- if (!tool) {
1197
+ if (!tool || (getMcpToolMode() === 'answer-only' && !ANSWER_ONLY_TOOL_NAMES.has(name))) {
1077
1198
  throw invalidParamsError(`Unknown tool: ${name}`);
1078
1199
  }
1079
1200
 
1201
+ if (name === 'get_findtime_help') {
1202
+ return {
1203
+ content: [
1204
+ {
1205
+ type: 'text',
1206
+ text: `get_findtime_help response\n${JSON.stringify(buildFindtimeHelpPayload(), null, 2)}`
1207
+ }
1208
+ ],
1209
+ structuredContent: buildFindtimeHelpPayload()
1210
+ };
1211
+ }
1212
+
1080
1213
  if (name === 'get_api_diagnostics') {
1081
1214
  const request = tool.buildRequest(args || {});
1082
1215
  const [apiResponse, latestMcpVersionCheck] = await Promise.all([
@@ -1231,7 +1364,7 @@ function createFindtimeMcpServer(options = {}) {
1231
1364
 
1232
1365
  if (method === 'tools/list') {
1233
1366
  return createSuccessResponse(message.id, {
1234
- tools: TOOL_DEFINITIONS.map(({ name, description, inputSchema }) => ({
1367
+ tools: getVisibleToolDefinitions().map(({ name, description, inputSchema }) => ({
1235
1368
  name,
1236
1369
  description,
1237
1370
  inputSchema