fellow-mcp 1.0.4 → 1.0.5

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 (2) hide show
  1. package/dist/index.js +79 -47
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -84,6 +84,40 @@ class FellowClient {
84
84
  async getRecording(recordingId) {
85
85
  return this.request("GET", `/recording/${recordingId}`);
86
86
  }
87
+ /**
88
+ * Fetch a single recording with its transcript.
89
+ * Tries GET /recording/{id} first (which may include transcript).
90
+ * If transcript is missing, falls back to POST /recordings with include.transcript
91
+ * and paginates until the recording is found.
92
+ */
93
+ async getRecordingWithTranscript(recordingId) {
94
+ // Try direct fetch first - single resource endpoints often return full object
95
+ try {
96
+ const recording = await this.getRecording(recordingId);
97
+ if (recording && recording.transcript) {
98
+ return recording;
99
+ }
100
+ // If we got the recording but no transcript, fall back to list with include
101
+ }
102
+ catch {
103
+ // Direct fetch failed, fall back to list endpoint
104
+ }
105
+ // Fall back to list with transcript include, paginating until we find it
106
+ let cursor;
107
+ do {
108
+ const resp = await this.listRecordings({
109
+ include_transcript: true,
110
+ cursor,
111
+ page_size: 50,
112
+ });
113
+ const found = resp.recordings.data.find((r) => r.id === recordingId);
114
+ if (found) {
115
+ return found;
116
+ }
117
+ cursor = resp.recordings.page_info.cursor ?? undefined;
118
+ } while (cursor);
119
+ return null;
120
+ }
87
121
  async listNotes(options) {
88
122
  const body = {};
89
123
  // Build filters
@@ -590,8 +624,8 @@ function formatTranscript(transcript) {
590
624
  }
591
625
  let output = `Language: ${transcript.language_code}\n\n`;
592
626
  for (const segment of transcript.speech_segments) {
593
- const startTime = formatTime(segment.start_time);
594
- const endTime = formatTime(segment.end_time);
627
+ const startTime = formatTime(segment.start ?? segment.start_time ?? 0);
628
+ const endTime = formatTime(segment.end ?? segment.end_time ?? 0);
595
629
  output += `[${startTime} - ${endTime}] ${segment.speaker}: ${segment.text}\n`;
596
630
  }
597
631
  return output;
@@ -709,22 +743,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
709
743
  const { recording_id, meeting_title } = args;
710
744
  let recordingWithTranscript = null;
711
745
  if (recording_id) {
712
- // Get the specific recording with transcript
713
- const recordingsResp = await client.listRecordings({
714
- include_transcript: true,
715
- page_size: 50,
716
- });
717
- recordingWithTranscript =
718
- recordingsResp.recordings.data.find((r) => r.id === recording_id) ?? null;
719
- if (!recordingWithTranscript) {
720
- // Try fetching all to find it
721
- const allRecordingsResp = await client.listRecordings({
722
- include_transcript: true,
723
- page_size: 50,
724
- });
725
- recordingWithTranscript =
726
- allRecordingsResp.recordings.data.find((r) => r.id === recording_id) ?? null;
727
- }
746
+ // Direct fetch by ID with transcript (paginates if needed)
747
+ recordingWithTranscript = await client.getRecordingWithTranscript(recording_id);
728
748
  }
729
749
  else if (meeting_title) {
730
750
  // Search by title and get transcript
@@ -765,10 +785,19 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
765
785
  let targetRecordingId = recording_id;
766
786
  // If recording_id provided, get the associated note_id
767
787
  if (!noteId && recording_id) {
768
- const recordingsResp = await client.listRecordings({ page_size: 50 });
769
- const recording = recordingsResp.recordings.data.find((r) => r.id === recording_id);
770
- if (recording) {
771
- noteId = recording.note_id;
788
+ try {
789
+ const recording = await client.getRecording(recording_id);
790
+ if (recording) {
791
+ noteId = recording.note_id;
792
+ }
793
+ }
794
+ catch {
795
+ // Direct fetch failed, try list as fallback
796
+ const recordingsResp = await client.listRecordings({ page_size: 50 });
797
+ const recording = recordingsResp.recordings.data.find((r) => r.id === recording_id);
798
+ if (recording) {
799
+ noteId = recording.note_id;
800
+ }
772
801
  }
773
802
  }
774
803
  // If meeting_title provided, search for the note and recording
@@ -800,16 +829,18 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
800
829
  let eventStart = null;
801
830
  let eventGuid = null;
802
831
  if (noteId) {
803
- const notesResp = await client.listNotes({
804
- include_content: true,
805
- page_size: 50,
806
- });
807
- const note = notesResp.notes.data.find((n) => n.id === noteId);
808
- if (note) {
809
- noteTitle = note.title;
810
- eventStart = note.event_start ?? null;
811
- eventGuid = note.event_guid ?? null;
812
- noteContent = note.content_markdown ?? null;
832
+ // Direct fetch by ID - much more reliable than listing and filtering
833
+ try {
834
+ const note = await client.getNote(noteId);
835
+ if (note) {
836
+ noteTitle = note.title;
837
+ eventStart = note.event_start ?? null;
838
+ eventGuid = note.event_guid ?? null;
839
+ noteContent = note.content_markdown ?? null;
840
+ }
841
+ }
842
+ catch {
843
+ // Note fetch failed, continue without note content
813
844
  }
814
845
  }
815
846
  // Find recording ID if we don't have it
@@ -823,11 +854,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
823
854
  // Fetch transcript
824
855
  let transcriptText = null;
825
856
  if (targetRecordingId) {
826
- const recordingsResp = await client.listRecordings({
827
- include_transcript: true,
828
- page_size: 50,
829
- });
830
- const recording = recordingsResp.recordings.data.find((r) => r.id === targetRecordingId);
857
+ // Direct fetch by ID with transcript (paginates if needed)
858
+ const recording = await client.getRecordingWithTranscript(targetRecordingId);
831
859
  if (recording?.transcript) {
832
860
  transcriptText = formatTranscript(recording.transcript);
833
861
  if (!noteTitle || noteTitle === "Unknown Meeting") {
@@ -867,11 +895,13 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
867
895
  const { note_id, meeting_title } = args;
868
896
  let note = null;
869
897
  if (note_id) {
870
- const notesResp = await client.listNotes({
871
- include_content: true,
872
- page_size: 50,
873
- });
874
- note = notesResp.notes.data.find((n) => n.id === note_id) ?? null;
898
+ // Direct fetch by ID
899
+ try {
900
+ note = await client.getNote(note_id);
901
+ }
902
+ catch {
903
+ note = null;
904
+ }
875
905
  }
876
906
  else if (meeting_title) {
877
907
  const notesResp = await client.listNotes({
@@ -919,11 +949,13 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
919
949
  const { note_id, meeting_title } = args;
920
950
  let note = null;
921
951
  if (note_id) {
922
- const notesResp = await client.listNotes({
923
- include_attendees: true,
924
- page_size: 50,
925
- });
926
- note = notesResp.notes.data.find((n) => n.id === note_id) ?? null;
952
+ // Direct fetch by ID
953
+ try {
954
+ note = await client.getNote(note_id);
955
+ }
956
+ catch {
957
+ note = null;
958
+ }
927
959
  }
928
960
  else if (meeting_title) {
929
961
  const notesResp = await client.listNotes({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fellow-mcp",
3
- "version": "1.0.4",
3
+ "version": "1.0.5",
4
4
  "description": "MCP server for Fellow.ai API - access meeting data, transcripts, summaries, and action items",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",