@youdotcom-oss/mcp 3.2.3 → 3.3.0

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/bin/stdio.js CHANGED
@@ -12889,29 +12889,12 @@ var getLogger = (sendNotification) => async (params) => {
12889
12889
  await sendNotification({ method: "notifications/message", params });
12890
12890
  };
12891
12891
 
12892
- // src/contents/contents.schemas.ts
12893
- var ContentsStructuredContentSchema = object({
12894
- count: number2().describe("URLs processed"),
12895
- formats: array(string2()).describe("Content formats requested"),
12896
- items: array(object({
12897
- url: string2().describe("URL"),
12898
- title: string2().optional().describe("Title"),
12899
- markdown: string2().optional().describe("Markdown content"),
12900
- html: string2().optional().describe("HTML content"),
12901
- metadata: object({
12902
- favicon_url: string2().describe("Favicon URL"),
12903
- site_name: string2().optional().nullable().describe("Site name")
12904
- }).optional().nullable().describe("Page metadata")
12905
- })).describe("Extracted items")
12906
- });
12907
-
12908
12892
  // src/contents/contents.utils.ts
12909
12893
  var formatContentsResponse = (response, formats) => {
12910
12894
  const textParts = [`Successfully extracted content from ${response.length} URL(s):
12911
12895
  `];
12912
12896
  textParts.push(`Formats: ${formats.join(", ")}
12913
12897
  `);
12914
- const items = [];
12915
12898
  for (const item of response) {
12916
12899
  textParts.push(`
12917
12900
  ## ${item.title || "Untitled"}`);
@@ -12957,28 +12940,14 @@ var formatContentsResponse = (response, formats) => {
12957
12940
  textParts.push(`
12958
12941
  ---
12959
12942
  `);
12960
- items.push({
12961
- url: item.url,
12962
- title: item.title ?? undefined,
12963
- markdown: item.markdown ?? undefined,
12964
- html: item.html ?? undefined,
12965
- metadata: item.metadata ?? undefined
12966
- });
12967
12943
  }
12968
- return {
12969
- content: [
12970
- {
12971
- type: "text",
12972
- text: textParts.join(`
12944
+ return [
12945
+ {
12946
+ type: "text",
12947
+ text: textParts.join(`
12973
12948
  `)
12974
- }
12975
- ],
12976
- structuredContent: {
12977
- count: response.length,
12978
- formats,
12979
- items
12980
12949
  }
12981
- };
12950
+ ];
12982
12951
  };
12983
12952
 
12984
12953
  // src/contents/register-contents-tool.ts
@@ -12990,8 +12959,10 @@ var registerContentsTool = ({
12990
12959
  mcp.registerTool("you-contents", {
12991
12960
  title: "Extract Web Page Contents",
12992
12961
  description: "Extract page content in markdown or HTML",
12993
- inputSchema: ContentsQuerySchema.shape,
12994
- outputSchema: ContentsStructuredContentSchema.shape
12962
+ inputSchema: ContentsQuerySchema,
12963
+ outputSchema: object({
12964
+ output: ContentsApiResponseSchema
12965
+ })
12995
12966
  }, async (contentsQuery, { sendNotification }) => {
12996
12967
  const logger = getLogger(sendNotification);
12997
12968
  try {
@@ -13007,14 +12978,16 @@ var registerContentsTool = ({
13007
12978
  YDC_API_KEY,
13008
12979
  getUserAgent
13009
12980
  });
13010
- const { content, structuredContent } = formatContentsResponse(response, requestFormats);
12981
+ const content = formatContentsResponse(response, requestFormats);
13011
12982
  await logger({
13012
12983
  level: "info",
13013
12984
  data: `Contents API call successful: extracted ${response.length} page(s)`
13014
12985
  });
13015
12986
  return {
13016
12987
  content,
13017
- structuredContent
12988
+ structuredContent: {
12989
+ output: response
12990
+ }
13018
12991
  };
13019
12992
  } catch (err) {
13020
12993
  const errorMessage = err instanceof Error ? err.message : String(err);
@@ -20464,7 +20437,7 @@ var EMPTY_COMPLETION_RESULT = {
20464
20437
  // package.json
20465
20438
  var package_default = {
20466
20439
  name: "@youdotcom-oss/mcp",
20467
- version: "3.2.3",
20440
+ version: "3.3.0",
20468
20441
  description: "You.com MCP server — web search, AI research, and content extraction via You.com APIs",
20469
20442
  license: "MIT",
20470
20443
  engines: {
@@ -20521,7 +20494,8 @@ var package_default = {
20521
20494
  zod: "^4.3.6"
20522
20495
  },
20523
20496
  devDependencies: {
20524
- "@modelcontextprotocol/inspector": "0.21.1"
20497
+ "@modelcontextprotocol/inspector": "0.21.2",
20498
+ "@types/bun": "latest"
20525
20499
  }
20526
20500
  };
20527
20501
 
@@ -20537,37 +20511,15 @@ var getMcpServer = () => new McpServer({
20537
20511
  instructions: `Use this server to search the web, get AI-powered answers with web context, and extract content from web pages using You.com. The you-contents tool extracts page content and returns it in markdown or HTML format. Use HTML format for layout preservation, interactive content, and visual fidelity; use markdown for text extraction and simpler consumption.`
20538
20512
  });
20539
20513
 
20540
- // src/research/research.schemas.ts
20541
- var ResearchStructuredContentSchema = object({
20542
- contentType: string2().describe("Format of the content field"),
20543
- sourceCount: number2().describe("Number of sources used"),
20544
- sources: array(object({
20545
- url: string2().describe("Source URL"),
20546
- title: string2().optional().describe("Source title"),
20547
- snippetCount: number2().describe("Number of excerpts from this source")
20548
- })).describe("Sources used in the research answer")
20549
- });
20550
-
20551
20514
  // src/research/research.utils.ts
20552
20515
  var formatResearchResults = (response) => {
20553
20516
  const text = formatResearchResponse(response);
20554
- return {
20555
- content: [
20556
- {
20557
- type: "text",
20558
- text
20559
- }
20560
- ],
20561
- structuredContent: {
20562
- contentType: response.output.content_type,
20563
- sourceCount: response.output.sources.length,
20564
- sources: response.output.sources.map((source) => ({
20565
- url: source.url,
20566
- title: source.title,
20567
- snippetCount: source.snippets?.length ?? 0
20568
- }))
20517
+ return [
20518
+ {
20519
+ type: "text",
20520
+ text
20569
20521
  }
20570
- };
20522
+ ];
20571
20523
  };
20572
20524
 
20573
20525
  // src/research/register-research-tool.ts
@@ -20579,8 +20531,8 @@ var registerResearchTool = ({
20579
20531
  mcp.registerTool("you-research", {
20580
20532
  title: "Research",
20581
20533
  description: "Research a topic with comprehensive answers and cited sources. Configurable effort levels (lite, standard, deep, exhaustive).",
20582
- inputSchema: ResearchQuerySchema.shape,
20583
- outputSchema: ResearchStructuredContentSchema.shape
20534
+ inputSchema: ResearchQuerySchema,
20535
+ outputSchema: ResearchResponseSchema
20584
20536
  }, async (researchQuery, { sendNotification }) => {
20585
20537
  const logger = getLogger(sendNotification);
20586
20538
  try {
@@ -20594,8 +20546,8 @@ var registerResearchTool = ({
20594
20546
  level: "info",
20595
20547
  data: `Research for "${researchQuery.input.substring(0, 100)}" complete: ${sourceCount} source(s)`
20596
20548
  });
20597
- const { content, structuredContent } = formatResearchResults(response);
20598
- return { content, structuredContent };
20549
+ const content = formatResearchResults(response);
20550
+ return { content, structuredContent: response };
20599
20551
  } catch (err) {
20600
20552
  const errorMessage = err instanceof Error ? err.message : String(err);
20601
20553
  const reportLink = generateErrorReportLink({
@@ -20623,36 +20575,6 @@ Report this issue: ${reportLink}`
20623
20575
  });
20624
20576
  };
20625
20577
 
20626
- // src/search/search.schemas.ts
20627
- var SearchStructuredContentSchema = object({
20628
- resultCounts: object({
20629
- web: number2().describe("Web results"),
20630
- news: number2().describe("News results"),
20631
- total: number2().describe("Total results")
20632
- }),
20633
- results: object({
20634
- web: array(object({
20635
- url: string2().describe("URL"),
20636
- title: string2().describe("Title"),
20637
- page_age: string2().optional().describe("Publication timestamp"),
20638
- snippets: array(string2()).optional().describe("Content snippets"),
20639
- contents: object({
20640
- html: string2().optional().describe("Full HTML content"),
20641
- markdown: string2().optional().describe("Full Markdown content")
20642
- }).optional().describe("Livecrawled page content")
20643
- })).optional().describe("Web results"),
20644
- news: array(object({
20645
- url: string2().describe("URL"),
20646
- title: string2().describe("Title"),
20647
- page_age: string2().describe("Publication timestamp"),
20648
- contents: object({
20649
- html: string2().optional().describe("Full HTML content"),
20650
- markdown: string2().optional().describe("Full Markdown content")
20651
- }).optional().describe("Livecrawled page content")
20652
- })).optional().describe("News results")
20653
- }).optional().describe("Search results")
20654
- });
20655
-
20656
20578
  // src/shared/format-search-results-text.ts
20657
20579
  var formatCharCount = (count) => count.toLocaleString();
20658
20580
  var formatSearchResultsText = (results) => {
@@ -20713,53 +20635,14 @@ ${"=".repeat(50)}
20713
20635
 
20714
20636
  ${newsResults}`;
20715
20637
  }
20716
- const structuredResults = {};
20717
- if (response.results.web?.length) {
20718
- structuredResults.web = response.results.web.map((result) => {
20719
- const item = {
20720
- url: result.url,
20721
- title: result.title
20722
- };
20723
- if (result.page_age)
20724
- item.page_age = result.page_age;
20725
- if (result.snippets?.length)
20726
- item.snippets = result.snippets;
20727
- if (result.contents)
20728
- item.contents = result.contents ?? undefined;
20729
- return item;
20730
- });
20731
- }
20732
- if (response.results.news?.length) {
20733
- structuredResults.news = response.results.news.map((article) => {
20734
- const item = {
20735
- url: article.url,
20736
- title: article.title,
20737
- page_age: article.page_age
20738
- };
20739
- if (article.contents)
20740
- item.contents = article.contents ?? undefined;
20741
- return item;
20742
- });
20743
- }
20744
- return {
20745
- content: [
20746
- {
20747
- type: "text",
20748
- text: `Search Results for "${response.metadata.query}":
20638
+ return [
20639
+ {
20640
+ type: "text",
20641
+ text: `Search Results for "${response.metadata.query}":
20749
20642
 
20750
20643
  ${formattedResults}`
20751
- }
20752
- ],
20753
- structuredContent: {
20754
- resultCounts: {
20755
- web: response.results.web?.length || 0,
20756
- news: response.results.news?.length || 0,
20757
- total: (response.results.web?.length || 0) + (response.results.news?.length || 0)
20758
- },
20759
- results: Object.keys(structuredResults).length > 0 ? structuredResults : undefined
20760
- },
20761
- fullResponse: response
20762
- };
20644
+ }
20645
+ ];
20763
20646
  };
20764
20647
 
20765
20648
  // src/search/register-search-tool.ts
@@ -20771,8 +20654,8 @@ var registerSearchTool = ({
20771
20654
  mcp.registerTool("you-search", {
20772
20655
  title: "Web Search",
20773
20656
  description: "Web and news search via You.com. Supports domain filtering, language selection, livecrawl for full page content, and date freshness controls.",
20774
- inputSchema: SearchQuerySchema.shape,
20775
- outputSchema: SearchStructuredContentSchema.shape
20657
+ inputSchema: SearchQuerySchema,
20658
+ outputSchema: SearchResponseSchema
20776
20659
  }, async (searchQuery, { sendNotification }) => {
20777
20660
  const logger = getLogger(sendNotification);
20778
20661
  try {
@@ -20790,21 +20673,15 @@ var registerSearchTool = ({
20790
20673
  });
20791
20674
  return {
20792
20675
  content: [{ type: "text", text: "No results found." }],
20793
- structuredContent: {
20794
- resultCounts: {
20795
- web: 0,
20796
- news: 0,
20797
- total: 0
20798
- }
20799
- }
20676
+ structuredContent: response
20800
20677
  };
20801
20678
  }
20802
20679
  await logger({
20803
20680
  level: "info",
20804
20681
  data: `Search successful for query: "${searchQuery.query}" - ${webCount} web results, ${newsCount} news results (${webCount + newsCount} total)`
20805
20682
  });
20806
- const { content, structuredContent } = formatSearchResults(response);
20807
- return { content, structuredContent };
20683
+ const content = formatSearchResults(response);
20684
+ return { content, structuredContent: response };
20808
20685
  } catch (err) {
20809
20686
  const errorMessage = err instanceof Error ? err.message : String(err);
20810
20687
  const reportLink = generateErrorReportLink({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@youdotcom-oss/mcp",
3
- "version": "3.2.3",
3
+ "version": "3.3.0",
4
4
  "description": "You.com MCP server — web search, AI research, and content extraction via You.com APIs",
5
5
  "license": "MIT",
6
6
  "engines": {
@@ -57,6 +57,7 @@
57
57
  "zod": "^4.3.6"
58
58
  },
59
59
  "devDependencies": {
60
- "@modelcontextprotocol/inspector": "0.21.1"
60
+ "@modelcontextprotocol/inspector": "0.21.2",
61
+ "@types/bun": "latest"
61
62
  }
62
63
  }
package/server.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "name": "io.github.youdotcom-oss/mcp",
4
4
  "title": "You.com Web Access & AI",
5
5
  "description": "Web search, AI agent, and content extraction via You.com APIs",
6
- "version": "3.0.0",
6
+ "version": "3.2.3",
7
7
  "remotes": [
8
8
  {
9
9
  "type": "streamable-http",
@@ -22,7 +22,7 @@
22
22
  {
23
23
  "registryType": "npm",
24
24
  "identifier": "@youdotcom-oss/mcp",
25
- "version": "3.0.0",
25
+ "version": "3.2.3",
26
26
  "transport": {
27
27
  "type": "stdio"
28
28
  },
@@ -1,33 +1,23 @@
1
1
  import type { ContentsApiResponse } from '@youdotcom-oss/api'
2
- import type { ContentsStructuredContent } from './contents.schemas.ts'
3
2
 
4
3
  /**
5
4
  * Format contents API response for MCP output
6
- * Returns full content in both text and structured formats
7
5
  * @param response - Validated API response
8
6
  * @param formats - Formats used for extraction
9
- * @returns Formatted response with content and structuredContent
7
+ * @returns Text content blocks for the MCP response
10
8
  */
11
9
  export const formatContentsResponse = (
12
10
  response: ContentsApiResponse,
13
11
  formats: string[],
14
- ): {
15
- content: Array<{ type: 'text'; text: string }>
16
- structuredContent: ContentsStructuredContent
17
- } => {
18
- // Build text content with full extracted content
12
+ ): Array<{ type: 'text'; text: string }> => {
19
13
  const textParts: string[] = [`Successfully extracted content from ${response.length} URL(s):\n`]
20
14
  textParts.push(`Formats: ${formats.join(', ')}\n`)
21
15
 
22
- const items: ContentsStructuredContent['items'] = []
23
-
24
16
  for (const item of response) {
25
- // Add header for this item
26
17
  textParts.push(`\n## ${item.title || 'Untitled'}`)
27
18
  textParts.push(`URL: ${item.url}\n`)
28
19
  textParts.push('---\n')
29
20
 
30
- // Add content based on requested formats
31
21
  if (formats.includes('markdown') && item.markdown) {
32
22
  textParts.push('\n### Markdown Content\n')
33
23
  textParts.push(item.markdown)
@@ -35,7 +25,7 @@ export const formatContentsResponse = (
35
25
  }
36
26
 
37
27
  if (formats.includes('html') && item.html) {
38
- // Text output is a brief preview only — full HTML is in structuredContent.items[].html
28
+ // Text output is a brief preview only — full HTML is in structuredContent.output[].html
39
29
  textParts.push('\n### HTML Content\n')
40
30
  textParts.push(`Length: ${item.html.length} characters\n`)
41
31
  textParts.push(item.html.substring(0, 500))
@@ -58,28 +48,12 @@ export const formatContentsResponse = (
58
48
  }
59
49
 
60
50
  textParts.push('\n---\n')
61
-
62
- // Add to structured content
63
- items.push({
64
- url: item.url,
65
- title: item.title ?? undefined,
66
- markdown: item.markdown ?? undefined,
67
- html: item.html ?? undefined,
68
- metadata: item.metadata ?? undefined,
69
- })
70
51
  }
71
52
 
72
- return {
73
- content: [
74
- {
75
- type: 'text',
76
- text: textParts.join('\n'),
77
- },
78
- ],
79
- structuredContent: {
80
- count: response.length,
81
- formats,
82
- items,
53
+ return [
54
+ {
55
+ type: 'text',
56
+ text: textParts.join('\n'),
83
57
  },
84
- }
58
+ ]
85
59
  }
@@ -1,7 +1,12 @@
1
1
  import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
2
- import { ContentsQuerySchema, fetchContents, generateErrorReportLink } from '@youdotcom-oss/api'
2
+ import {
3
+ ContentsApiResponseSchema,
4
+ ContentsQuerySchema,
5
+ fetchContents,
6
+ generateErrorReportLink,
7
+ } from '@youdotcom-oss/api'
8
+ import * as z from 'zod'
3
9
  import { getLogger } from '../shared/get-logger.ts'
4
- import { ContentsStructuredContentSchema } from './contents.schemas.ts'
5
10
  import { formatContentsResponse } from './contents.utils.ts'
6
11
 
7
12
  /**
@@ -17,43 +22,39 @@ export const registerContentsTool = ({
17
22
  YDC_API_KEY?: string
18
23
  getUserAgent: () => string
19
24
  }) => {
20
- // Register the tool
21
25
  mcp.registerTool(
22
26
  'you-contents',
23
27
  {
24
28
  title: 'Extract Web Page Contents',
25
29
  description: 'Extract page content in markdown or HTML',
26
- inputSchema: ContentsQuerySchema.shape,
27
- outputSchema: ContentsStructuredContentSchema.shape,
30
+ inputSchema: ContentsQuerySchema,
31
+ outputSchema: z.object({
32
+ output: ContentsApiResponseSchema,
33
+ }),
28
34
  },
29
35
  async (contentsQuery, { sendNotification }) => {
30
36
  const logger = getLogger(sendNotification)
31
37
 
32
38
  try {
33
- // Validate and parse input
34
39
  const { urls, formats, format, crawl_timeout } = contentsQuery
35
40
 
36
41
  // Handle backward compatibility: prefer formats array, fallback to format string, default to ['markdown']
37
42
  const requestFormats = formats || (format ? [format] : ['markdown'])
38
43
 
39
- // Log the request
40
44
  const timeoutInfo = crawl_timeout ? ` with timeout: ${crawl_timeout}s` : ''
41
45
  await logger({
42
46
  level: 'info',
43
47
  data: `Contents API call initiated for ${urls.length} URL(s) with formats: ${requestFormats.join(', ')}${timeoutInfo}`,
44
48
  })
45
49
 
46
- // Fetch contents from API
47
50
  const response = await fetchContents({
48
51
  contentsQuery,
49
52
  YDC_API_KEY,
50
53
  getUserAgent,
51
54
  })
52
55
 
53
- // Format response with full content
54
- const { content, structuredContent } = formatContentsResponse(response, requestFormats)
56
+ const content = formatContentsResponse(response, requestFormats)
55
57
 
56
- // Log success
57
58
  await logger({
58
59
  level: 'info',
59
60
  data: `Contents API call successful: extracted ${response.length} page(s)`,
@@ -61,10 +62,11 @@ export const registerContentsTool = ({
61
62
 
62
63
  return {
63
64
  content,
64
- structuredContent,
65
+ structuredContent: {
66
+ output: response,
67
+ },
65
68
  }
66
69
  } catch (err: unknown) {
67
- // Handle and log errors
68
70
  const errorMessage = err instanceof Error ? err.message : String(err)
69
71
  const reportLink = generateErrorReportLink({
70
72
  errorMessage,
@@ -14,30 +14,16 @@ describe('formatContentsResponse', () => {
14
14
 
15
15
  const result = formatContentsResponse(mockResponse, ['markdown'])
16
16
 
17
- expect(result).toHaveProperty('content')
18
- expect(result).toHaveProperty('structuredContent')
19
- expect(Array.isArray(result.content)).toBe(true)
20
- expect(result.content[0]).toHaveProperty('type', 'text')
21
- expect(result.content[0]).toHaveProperty('text')
17
+ expect(Array.isArray(result)).toBe(true)
18
+ expect(result[0]).toHaveProperty('type', 'text')
19
+ expect(result[0]).toHaveProperty('text')
22
20
 
23
- const text = result.content[0]?.text
21
+ const text = result[0]?.text
24
22
  expect(text).toContain('Example Page')
25
23
  expect(text).toContain('https://example.com')
26
24
  expect(text).toContain('Formats: markdown')
27
25
  expect(text).toContain('# Hello')
28
26
  expect(text).toContain('This is a test page with some content.')
29
-
30
- expect(result.structuredContent).toHaveProperty('count', 1)
31
- expect(result.structuredContent).toHaveProperty('formats')
32
- expect(result.structuredContent.formats).toEqual(['markdown'])
33
- expect(result.structuredContent.items).toHaveLength(1)
34
-
35
- const item = result.structuredContent.items[0]
36
- expect(item).toBeDefined()
37
-
38
- expect(item).toHaveProperty('url', 'https://example.com')
39
- expect(item).toHaveProperty('title', 'Example Page')
40
- expect(item).toHaveProperty('markdown', '# Hello\n\nThis is a test page with some content.')
41
27
  })
42
28
 
43
29
  test('formats multiple items correctly', () => {
@@ -56,10 +42,8 @@ describe('formatContentsResponse', () => {
56
42
 
57
43
  const result = formatContentsResponse(mockResponse, ['markdown'])
58
44
 
59
- expect(result.structuredContent.count).toBe(2)
60
- expect(result.structuredContent.items).toHaveLength(2)
61
-
62
- const text = result.content[0]?.text
45
+ const text = result[0]?.text
46
+ expect(text).toContain('Successfully extracted content from 2 URL(s)')
63
47
  expect(text).toContain('Page 1')
64
48
  expect(text).toContain('Page 2')
65
49
  expect(text).toContain('https://example1.com')
@@ -77,8 +61,7 @@ describe('formatContentsResponse', () => {
77
61
 
78
62
  const result = formatContentsResponse(mockResponse, ['html'])
79
63
 
80
- expect(result.structuredContent.formats).toEqual(['html'])
81
- const text = result.content[0]?.text
64
+ const text = result[0]?.text
82
65
  expect(text).toContain('Formats: html')
83
66
  expect(text).toContain('<html>')
84
67
  })
@@ -95,13 +78,8 @@ describe('formatContentsResponse', () => {
95
78
 
96
79
  const result = formatContentsResponse(mockResponse, ['markdown'])
97
80
 
98
- const text = result.content[0]?.text
99
- // Full content should be included (not truncated)
81
+ const text = result[0]?.text
100
82
  expect(text).toContain(longContent)
101
-
102
- // Structured content should have full markdown content
103
- const item = result.structuredContent.items[0]
104
- expect(item?.markdown).toBe(longContent)
105
83
  })
106
84
 
107
85
  test('handles empty content gracefully', () => {
@@ -115,9 +93,7 @@ describe('formatContentsResponse', () => {
115
93
 
116
94
  const result = formatContentsResponse(mockResponse, ['markdown'])
117
95
 
118
- expect(result.structuredContent.items[0]?.markdown).toBe('')
119
- const text = result.content[0]?.text
96
+ const text = result[0]?.text
120
97
  expect(text).toContain('Empty Page')
121
- // Empty content should still be handled gracefully
122
98
  })
123
99
  })
package/src/main.ts CHANGED
@@ -1,8 +1,5 @@
1
- export type { ContentsStructuredContent } from './contents/contents.schemas.ts'
2
1
  export { registerContentsTool } from './contents/register-contents-tool.ts'
3
2
  export { getMcpServer } from './get-mcp-server.ts'
4
3
  export { registerResearchTool } from './research/register-research-tool.ts'
5
- export type { ResearchStructuredContent } from './research/research.schemas.ts'
6
4
  export { registerSearchTool } from './search/register-search-tool.ts'
7
- export type { SearchStructuredContent } from './search/search.schemas.ts'
8
5
  export { useGetClientVersion } from './shared/use-client-version.ts'
@@ -1,7 +1,6 @@
1
1
  import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
2
- import { callResearch, generateErrorReportLink, ResearchQuerySchema } from '@youdotcom-oss/api'
2
+ import { callResearch, generateErrorReportLink, ResearchQuerySchema, ResearchResponseSchema } from '@youdotcom-oss/api'
3
3
  import { getLogger } from '../shared/get-logger.ts'
4
- import { ResearchStructuredContentSchema } from './research.schemas.ts'
5
4
  import { formatResearchResults } from './research.utils.ts'
6
5
 
7
6
  export const registerResearchTool = ({
@@ -19,8 +18,8 @@ export const registerResearchTool = ({
19
18
  title: 'Research',
20
19
  description:
21
20
  'Research a topic with comprehensive answers and cited sources. Configurable effort levels (lite, standard, deep, exhaustive).',
22
- inputSchema: ResearchQuerySchema.shape,
23
- outputSchema: ResearchStructuredContentSchema.shape,
21
+ inputSchema: ResearchQuerySchema,
22
+ outputSchema: ResearchResponseSchema,
24
23
  },
25
24
  async (researchQuery, { sendNotification }) => {
26
25
  const logger = getLogger(sendNotification)
@@ -38,8 +37,8 @@ export const registerResearchTool = ({
38
37
  data: `Research for "${researchQuery.input.substring(0, 100)}" complete: ${sourceCount} source(s)`,
39
38
  })
40
39
 
41
- const { content, structuredContent } = formatResearchResults(response)
42
- return { content, structuredContent }
40
+ const content = formatResearchResults(response)
41
+ return { content, structuredContent: response }
43
42
  } catch (err: unknown) {
44
43
  const errorMessage = err instanceof Error ? err.message : String(err)
45
44
  const reportLink = generateErrorReportLink({
@@ -1,30 +1,12 @@
1
1
  import type { ResearchResponse } from '@youdotcom-oss/api'
2
2
  import { formatResearchResponse } from '@youdotcom-oss/api'
3
- import type { ResearchStructuredContent } from './research.schemas.ts'
4
3
 
5
- export const formatResearchResults = (
6
- response: ResearchResponse,
7
- ): {
8
- content: Array<{ type: 'text'; text: string }>
9
- structuredContent: ResearchStructuredContent
10
- } => {
4
+ export const formatResearchResults = (response: ResearchResponse): Array<{ type: 'text'; text: string }> => {
11
5
  const text = formatResearchResponse(response)
12
-
13
- return {
14
- content: [
15
- {
16
- type: 'text',
17
- text,
18
- },
19
- ],
20
- structuredContent: {
21
- contentType: response.output.content_type,
22
- sourceCount: response.output.sources.length,
23
- sources: response.output.sources.map((source) => ({
24
- url: source.url,
25
- title: source.title,
26
- snippetCount: source.snippets?.length ?? 0,
27
- })),
6
+ return [
7
+ {
8
+ type: 'text',
9
+ text,
28
10
  },
29
- }
11
+ ]
30
12
  }
@@ -25,89 +25,14 @@ describe('formatResearchResults', () => {
25
25
 
26
26
  const result = formatResearchResults(mockResponse)
27
27
 
28
- expect(result).toHaveProperty('content')
29
- expect(result).toHaveProperty('structuredContent')
30
- expect(Array.isArray(result.content)).toBe(true)
31
- expect(result.content[0]).toHaveProperty('type', 'text')
32
- expect(result.content[0]).toHaveProperty('text')
28
+ expect(Array.isArray(result)).toBe(true)
29
+ expect(result[0]).toHaveProperty('type', 'text')
30
+ expect(result[0]).toHaveProperty('text')
33
31
 
34
- const text = result.content[0]?.text
32
+ const text = result[0]?.text
35
33
  expect(text).toContain('Research Answer')
36
34
  expect(text).toContain('Source One')
37
35
  expect(text).toContain('https://example.com/source1')
38
-
39
- expect(result.structuredContent.contentType).toBe('text')
40
- expect(result.structuredContent.sourceCount).toBe(2)
41
- expect(result.structuredContent.sources).toHaveLength(2)
42
- expect(result.structuredContent.sources[0]).toMatchObject({
43
- url: 'https://example.com/source1',
44
- title: 'Source One',
45
- snippetCount: 2,
46
- })
47
- expect(result.structuredContent.sources[1]).toMatchObject({
48
- url: 'https://example.com/source2',
49
- title: 'Source Two',
50
- snippetCount: 1,
51
- })
52
- })
53
-
54
- test('handles source with undefined title', () => {
55
- const mockResponse: ResearchResponse = {
56
- output: {
57
- content: 'Answer text',
58
- content_type: 'text',
59
- sources: [
60
- {
61
- url: 'https://example.com/no-title',
62
- snippets: ['A snippet'],
63
- },
64
- ],
65
- },
66
- }
67
-
68
- const result = formatResearchResults(mockResponse)
69
-
70
- expect(result.structuredContent.sourceCount).toBe(1)
71
- expect(result.structuredContent.sources[0]).toMatchObject({
72
- url: 'https://example.com/no-title',
73
- title: undefined,
74
- snippetCount: 1,
75
- })
76
- })
77
-
78
- test('handles source with empty snippets array', () => {
79
- const mockResponse: ResearchResponse = {
80
- output: {
81
- content: 'Answer with no-snippet source',
82
- content_type: 'text',
83
- sources: [
84
- {
85
- url: 'https://example.com/empty-snippets',
86
- title: 'Empty Snippets Source',
87
- snippets: [],
88
- },
89
- ],
90
- },
91
- }
92
-
93
- const result = formatResearchResults(mockResponse)
94
-
95
- expect(result.structuredContent.sourceCount).toBe(1)
96
- expect(result.structuredContent.sources[0]?.snippetCount).toBe(0)
97
- })
98
-
99
- test('handles source with undefined snippets', () => {
100
- const mockResponse: ResearchResponse = {
101
- output: {
102
- content: 'Answer',
103
- content_type: 'text',
104
- sources: [{ url: 'https://example.com/no-snippets', title: 'No Snippets' }],
105
- },
106
- }
107
-
108
- const result = formatResearchResults(mockResponse)
109
-
110
- expect(result.structuredContent.sources[0]?.snippetCount).toBe(0)
111
36
  })
112
37
 
113
38
  test('handles response with zero sources', () => {
@@ -121,8 +46,6 @@ describe('formatResearchResults', () => {
121
46
 
122
47
  const result = formatResearchResults(mockResponse)
123
48
 
124
- expect(result.structuredContent.sourceCount).toBe(0)
125
- expect(result.structuredContent.sources).toHaveLength(0)
126
- expect(result.content[0]?.text).toContain('An answer with no cited sources.')
49
+ expect(result[0]?.text).toContain('An answer with no cited sources.')
127
50
  })
128
51
  })
@@ -1,7 +1,11 @@
1
1
  import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
2
- import { fetchSearchResults, generateErrorReportLink, SearchQuerySchema } from '@youdotcom-oss/api'
2
+ import {
3
+ fetchSearchResults,
4
+ generateErrorReportLink,
5
+ SearchQuerySchema,
6
+ SearchResponseSchema,
7
+ } from '@youdotcom-oss/api'
3
8
  import { getLogger } from '../shared/get-logger.ts'
4
- import { SearchStructuredContentSchema } from './search.schemas.ts'
5
9
  import { formatSearchResults } from './search.utils.ts'
6
10
 
7
11
  export const registerSearchTool = ({
@@ -19,8 +23,8 @@ export const registerSearchTool = ({
19
23
  title: 'Web Search',
20
24
  description:
21
25
  'Web and news search via You.com. Supports domain filtering, language selection, livecrawl for full page content, and date freshness controls.',
22
- inputSchema: SearchQuerySchema.shape,
23
- outputSchema: SearchStructuredContentSchema.shape,
26
+ inputSchema: SearchQuerySchema,
27
+ outputSchema: SearchResponseSchema,
24
28
  },
25
29
  async (searchQuery, { sendNotification }) => {
26
30
  const logger = getLogger(sendNotification)
@@ -42,13 +46,7 @@ export const registerSearchTool = ({
42
46
 
43
47
  return {
44
48
  content: [{ type: 'text' as const, text: 'No results found.' }],
45
- structuredContent: {
46
- resultCounts: {
47
- web: 0,
48
- news: 0,
49
- total: 0,
50
- },
51
- },
49
+ structuredContent: response,
52
50
  }
53
51
  }
54
52
 
@@ -57,8 +55,8 @@ export const registerSearchTool = ({
57
55
  data: `Search successful for query: "${searchQuery.query}" - ${webCount} web results, ${newsCount} news results (${webCount + newsCount} total)`,
58
56
  })
59
57
 
60
- const { content, structuredContent } = formatSearchResults(response)
61
- return { content, structuredContent }
58
+ const content = formatSearchResults(response)
59
+ return { content, structuredContent: response }
62
60
  } catch (err: unknown) {
63
61
  const errorMessage = err instanceof Error ? err.message : String(err)
64
62
  const reportLink = generateErrorReportLink({
@@ -4,13 +4,11 @@ import { formatSearchResultsText } from '../shared/format-search-results-text.ts
4
4
  export const formatSearchResults = (response: SearchResponse) => {
5
5
  let formattedResults = ''
6
6
 
7
- // Format web results using shared utility
8
7
  if (response.results.web?.length) {
9
8
  const webResults = formatSearchResultsText(response.results.web)
10
9
  formattedResults += `WEB RESULTS:\n\n${webResults}`
11
10
  }
12
11
 
13
- // Format news results using shared utility (consistent with web formatting)
14
12
  if (response.results.news?.length) {
15
13
  const newsResults = formatSearchResultsText(response.results.news)
16
14
 
@@ -20,64 +18,10 @@ export const formatSearchResults = (response: SearchResponse) => {
20
18
  formattedResults += `NEWS RESULTS:\n\n${newsResults}`
21
19
  }
22
20
 
23
- // Extract fields for structuredContent
24
- const structuredResults: {
25
- web?: Array<{
26
- url: string
27
- title: string
28
- page_age?: string
29
- snippets?: string[]
30
- contents?: { html?: string; markdown?: string }
31
- }>
32
- news?: Array<{ url: string; title: string; page_age: string; contents?: { html?: string; markdown?: string } }>
33
- } = {}
34
-
35
- if (response.results.web?.length) {
36
- structuredResults.web = response.results.web.map((result) => {
37
- const item: {
38
- url: string
39
- title: string
40
- page_age?: string
41
- snippets?: string[]
42
- contents?: { html?: string; markdown?: string }
43
- } = {
44
- url: result.url,
45
- title: result.title,
46
- }
47
- if (result.page_age) item.page_age = result.page_age
48
- if (result.snippets?.length) item.snippets = result.snippets
49
- if (result.contents) item.contents = result.contents ?? undefined
50
- return item
51
- })
52
- }
53
-
54
- if (response.results.news?.length) {
55
- structuredResults.news = response.results.news.map((article) => {
56
- const item: { url: string; title: string; page_age: string; contents?: { html?: string; markdown?: string } } = {
57
- url: article.url,
58
- title: article.title,
59
- page_age: article.page_age,
60
- }
61
- if (article.contents) item.contents = article.contents ?? undefined
62
- return item
63
- })
64
- }
65
-
66
- return {
67
- content: [
68
- {
69
- type: 'text' as const,
70
- text: `Search Results for "${response.metadata.query}":\n\n${formattedResults}`,
71
- },
72
- ],
73
- structuredContent: {
74
- resultCounts: {
75
- web: response.results.web?.length || 0,
76
- news: response.results.news?.length || 0,
77
- total: (response.results.web?.length || 0) + (response.results.news?.length || 0),
78
- },
79
- results: Object.keys(structuredResults).length > 0 ? structuredResults : undefined,
21
+ return [
22
+ {
23
+ type: 'text' as const,
24
+ text: `Search Results for "${response.metadata.query}":\n\n${formattedResults}`,
80
25
  },
81
- fullResponse: response,
82
- }
26
+ ]
83
27
  }
@@ -88,9 +88,7 @@ describe('registerSearchTool', () => {
88
88
  const toolResult = await result.client.callTool({ name: 'you-search', arguments: { query: 'nonexistent' } })
89
89
 
90
90
  expect(toolResult.content).toEqual([{ type: 'text', text: 'No results found.' }])
91
- expect(toolResult.structuredContent).toEqual({
92
- resultCounts: { web: 0, news: 0, total: 0 },
93
- })
91
+ expect(toolResult.structuredContent).toEqual(emptyResponse)
94
92
  })
95
93
 
96
94
  test('returns formatted results for successful search', async () => {
@@ -104,9 +102,7 @@ describe('registerSearchTool', () => {
104
102
  expect(text).toContain('Example')
105
103
  expect(text).toContain('https://example.com')
106
104
 
107
- const structured = toolResult.structuredContent as Record<string, unknown>
108
- expect(structured).toHaveProperty('resultCounts')
109
- expect((structured as { resultCounts: { total: number } }).resultCounts.total).toBe(1)
105
+ expect(toolResult.structuredContent).toEqual(oneResultResponse)
110
106
  })
111
107
 
112
108
  test('returns error when API call fails', async () => {
@@ -27,32 +27,13 @@ describe('formatSearchResults', () => {
27
27
 
28
28
  const result = formatSearchResults(mockResponse)
29
29
 
30
- expect(result).toHaveProperty('content')
31
- expect(result).toHaveProperty('structuredContent')
32
- expect(result).toHaveProperty('fullResponse')
33
- expect(Array.isArray(result.content)).toBe(true)
34
- expect(result.content[0]).toHaveProperty('type', 'text')
35
- expect(result.content[0]).toHaveProperty('text')
36
- expect(result.content[0]?.text).toContain('WEB RESULTS:')
37
- expect(result.content[0]?.text).toContain('Test Title')
38
- // URL and page_age should be in text content
39
- expect(result.content[0]?.text).toContain('URL: https://example.com')
40
- expect(result.content[0]?.text).toContain('Published: 2023-01-01T00:00:00')
41
- expect(result.structuredContent).toHaveProperty('resultCounts')
42
- expect(result.structuredContent.resultCounts).toHaveProperty('web', 1)
43
- expect(result.structuredContent.resultCounts).toHaveProperty('news', 0)
44
- expect(result.structuredContent.resultCounts).toHaveProperty('total', 1)
45
- // All fields should be in structuredContent.results
46
- expect(result.structuredContent).toHaveProperty('results')
47
- expect(result.structuredContent.results?.web).toBeDefined()
48
- expect(result.structuredContent.results?.web?.length).toBe(1)
49
- expect(result.structuredContent.results?.web?.[0]).toMatchObject({
50
- url: 'https://example.com',
51
- title: 'Test Title',
52
- page_age: '2023-01-01T00:00:00',
53
- snippets: ['snippet 1', 'snippet 2'],
54
- })
55
- expect(result.fullResponse).toBe(mockResponse)
30
+ expect(Array.isArray(result)).toBe(true)
31
+ expect(result[0]).toHaveProperty('type', 'text')
32
+ expect(result[0]).toHaveProperty('text')
33
+ expect(result[0]?.text).toContain('WEB RESULTS:')
34
+ expect(result[0]?.text).toContain('Test Title')
35
+ expect(result[0]?.text).toContain('URL: https://example.com')
36
+ expect(result[0]?.text).toContain('Published: 2023-01-01T00:00:00')
56
37
  })
57
38
 
58
39
  test('formats news results correctly', () => {
@@ -77,25 +58,11 @@ describe('formatSearchResults', () => {
77
58
 
78
59
  const result = formatSearchResults(mockResponse)
79
60
 
80
- expect(result.content[0]?.text).toContain('NEWS RESULTS:')
81
- expect(result.content[0]?.text).toContain('News Title')
82
- expect(result.content[0]?.text).toContain('Published: 2023-01-01T00:00:00')
83
- // URL and Description should be in text content (routed through formatSearchResultsText)
84
- expect(result.content[0]?.text).toContain('URL: https://news.com/article')
85
- expect(result.content[0]?.text).toContain('Description: News description')
86
- expect(result.structuredContent).toHaveProperty('resultCounts')
87
- expect(result.structuredContent.resultCounts).toHaveProperty('web', 0)
88
- expect(result.structuredContent.resultCounts).toHaveProperty('news', 1)
89
- expect(result.structuredContent.resultCounts).toHaveProperty('total', 1)
90
- // All fields should be in structuredContent.results
91
- expect(result.structuredContent).toHaveProperty('results')
92
- expect(result.structuredContent.results?.news).toBeDefined()
93
- expect(result.structuredContent.results?.news?.length).toBe(1)
94
- expect(result.structuredContent.results?.news?.[0]).toMatchObject({
95
- url: 'https://news.com/article',
96
- title: 'News Title',
97
- page_age: '2023-01-01T00:00:00',
98
- })
61
+ expect(result[0]?.text).toContain('NEWS RESULTS:')
62
+ expect(result[0]?.text).toContain('News Title')
63
+ expect(result[0]?.text).toContain('Published: 2023-01-01T00:00:00')
64
+ expect(result[0]?.text).toContain('URL: https://news.com/article')
65
+ expect(result[0]?.text).toContain('Description: News description')
99
66
  })
100
67
 
101
68
  test('formats both web and news results', () => {
@@ -129,35 +96,14 @@ describe('formatSearchResults', () => {
129
96
 
130
97
  const result = formatSearchResults(mockResponse)
131
98
 
132
- expect(result.content[0]?.text).toContain('WEB RESULTS:')
133
- expect(result.content[0]?.text).toContain('NEWS RESULTS:')
134
- expect(result.content[0]?.text).toContain(`=${'='.repeat(49)}`)
135
- // URLs should be in text content
136
- expect(result.content[0]?.text).toContain('URL: https://web.com')
137
- expect(result.content[0]?.text).toContain('URL: https://news.com/article')
138
- expect(result.structuredContent.resultCounts).toHaveProperty('web', 1)
139
- expect(result.structuredContent.resultCounts).toHaveProperty('news', 1)
140
- expect(result.structuredContent.resultCounts).toHaveProperty('total', 2)
141
- // All fields should be in structuredContent.results
142
- expect(result.structuredContent).toHaveProperty('results')
143
- expect(result.structuredContent.results?.web).toBeDefined()
144
- expect(result.structuredContent.results?.news).toBeDefined()
145
- expect(result.structuredContent.results?.web?.length).toBe(1)
146
- expect(result.structuredContent.results?.news?.length).toBe(1)
147
- expect(result.structuredContent.results?.web?.[0]).toMatchObject({
148
- url: 'https://web.com',
149
- title: 'Web Title',
150
- page_age: '2023-01-01T00:00:00',
151
- snippets: ['web snippet'],
152
- })
153
- expect(result.structuredContent.results?.news?.[0]).toMatchObject({
154
- url: 'https://news.com/article',
155
- title: 'News Title',
156
- page_age: '2023-01-01T00:00:00',
157
- })
99
+ expect(result[0]?.text).toContain('WEB RESULTS:')
100
+ expect(result[0]?.text).toContain('NEWS RESULTS:')
101
+ expect(result[0]?.text).toContain(`=${'='.repeat(49)}`)
102
+ expect(result[0]?.text).toContain('URL: https://web.com')
103
+ expect(result[0]?.text).toContain('URL: https://news.com/article')
158
104
  })
159
105
 
160
- test('includes contents in structuredContent and text indicator when livecrawl returns page content', () => {
106
+ test('includes page content indicator when livecrawl returns contents', () => {
161
107
  const mockResponse: SearchResponse = {
162
108
  results: {
163
109
  web: [
@@ -185,23 +131,12 @@ describe('formatSearchResults', () => {
185
131
 
186
132
  const result = formatSearchResults(mockResponse)
187
133
 
188
- // Text content should include the contents indicator
189
- expect(result.content[0]?.text).toContain('Page content available:')
190
- expect(result.content[0]?.text).toContain('chars (markdown)')
191
- expect(result.content[0]?.text).toContain('chars (html)')
192
-
193
- // structuredContent should include contents
194
- expect(result.structuredContent.results?.web?.[0]).toMatchObject({
195
- url: 'https://example.com',
196
- title: 'Livecrawl Title',
197
- contents: {
198
- markdown: 'Full page content in markdown format.',
199
- html: '<p>Full page content in HTML format.</p>',
200
- },
201
- })
134
+ expect(result[0]?.text).toContain('Page content available:')
135
+ expect(result[0]?.text).toContain('chars (markdown)')
136
+ expect(result[0]?.text).toContain('chars (html)')
202
137
  })
203
138
 
204
- test('omits contents when not present in response', () => {
139
+ test('omits content indicator when livecrawl contents absent', () => {
205
140
  const mockResponse: SearchResponse = {
206
141
  results: {
207
142
  web: [
@@ -223,11 +158,10 @@ describe('formatSearchResults', () => {
223
158
 
224
159
  const result = formatSearchResults(mockResponse)
225
160
 
226
- expect(result.content[0]?.text).not.toContain('Page content available:')
227
- expect(result.structuredContent.results?.web?.[0]?.contents).toBeUndefined()
161
+ expect(result[0]?.text).not.toContain('Page content available:')
228
162
  })
229
163
 
230
- test('includes contents indicator for news results with livecrawl', () => {
164
+ test('includes content indicator for news results with livecrawl', () => {
231
165
  const mockResponse: SearchResponse = {
232
166
  results: {
233
167
  web: [],
@@ -252,40 +186,7 @@ describe('formatSearchResults', () => {
252
186
 
253
187
  const result = formatSearchResults(mockResponse)
254
188
 
255
- // Text content should include the contents indicator for news too
256
- expect(result.content[0]?.text).toContain('Page content available:')
257
- expect(result.content[0]?.text).toContain('chars (markdown)')
258
-
259
- // structuredContent should include contents for news
260
- expect(result.structuredContent.results?.news?.[0]).toMatchObject({
261
- url: 'https://news.com/article',
262
- title: 'News with Content',
263
- contents: { markdown: 'Full news article content in markdown.' },
264
- })
265
- })
266
-
267
- test('includes snippets in structuredContent for web results', () => {
268
- const mockResponse: SearchResponse = {
269
- results: {
270
- web: [
271
- {
272
- url: 'https://example.com',
273
- title: 'With Snippets',
274
- description: 'Has snippets',
275
- snippets: ['first snippet', 'second snippet'],
276
- },
277
- ],
278
- news: [],
279
- },
280
- metadata: {
281
- search_uuid: 'test-uuid',
282
- query: 'test',
283
- latency: 0.1,
284
- },
285
- }
286
-
287
- const result = formatSearchResults(mockResponse)
288
-
289
- expect(result.structuredContent.results?.web?.[0]?.snippets).toEqual(['first snippet', 'second snippet'])
189
+ expect(result[0]?.text).toContain('Page content available:')
190
+ expect(result[0]?.text).toContain('chars (markdown)')
290
191
  })
291
192
  })
@@ -1,30 +0,0 @@
1
- import * as z from 'zod'
2
-
3
- /**
4
- * Structured content schema for MCP response
5
- * Includes full content and metadata for each URL
6
- */
7
- export const ContentsStructuredContentSchema = z.object({
8
- count: z.number().describe('URLs processed'),
9
- formats: z.array(z.string()).describe('Content formats requested'),
10
- items: z
11
- .array(
12
- z.object({
13
- url: z.string().describe('URL'),
14
- title: z.string().optional().describe('Title'),
15
- markdown: z.string().optional().describe('Markdown content'),
16
- html: z.string().optional().describe('HTML content'),
17
- metadata: z
18
- .object({
19
- favicon_url: z.string().describe('Favicon URL'),
20
- site_name: z.string().optional().nullable().describe('Site name'),
21
- })
22
- .optional()
23
- .nullable()
24
- .describe('Page metadata'),
25
- }),
26
- )
27
- .describe('Extracted items'),
28
- })
29
-
30
- export type ContentsStructuredContent = z.infer<typeof ContentsStructuredContentSchema>
@@ -1,19 +0,0 @@
1
- import * as z from 'zod'
2
-
3
- // Minimal schema for structuredContent (reduces payload duplication)
4
- // Full research content is in the text content field
5
- export const ResearchStructuredContentSchema = z.object({
6
- contentType: z.string().describe('Format of the content field'),
7
- sourceCount: z.number().describe('Number of sources used'),
8
- sources: z
9
- .array(
10
- z.object({
11
- url: z.string().describe('Source URL'),
12
- title: z.string().optional().describe('Source title'),
13
- snippetCount: z.number().describe('Number of excerpts from this source'),
14
- }),
15
- )
16
- .describe('Sources used in the research answer'),
17
- })
18
-
19
- export type ResearchStructuredContent = z.infer<typeof ResearchStructuredContentSchema>
@@ -1,53 +0,0 @@
1
- import * as z from 'zod'
2
-
3
- // Minimal schema for structuredContent (reduces payload duplication)
4
- // Excludes metadata (query, search_uuid, latency) as these are not actionable by LLM
5
- export const SearchStructuredContentSchema = z.object({
6
- resultCounts: z.object({
7
- web: z.number().describe('Web results'),
8
- news: z.number().describe('News results'),
9
- total: z.number().describe('Total results'),
10
- }),
11
- results: z
12
- .object({
13
- web: z
14
- .array(
15
- z.object({
16
- url: z.string().describe('URL'),
17
- title: z.string().describe('Title'),
18
- page_age: z.string().optional().describe('Publication timestamp'),
19
- snippets: z.array(z.string()).optional().describe('Content snippets'),
20
- contents: z
21
- .object({
22
- html: z.string().optional().describe('Full HTML content'),
23
- markdown: z.string().optional().describe('Full Markdown content'),
24
- })
25
- .optional()
26
- .describe('Livecrawled page content'),
27
- }),
28
- )
29
- .optional()
30
- .describe('Web results'),
31
- news: z
32
- .array(
33
- z.object({
34
- url: z.string().describe('URL'),
35
- title: z.string().describe('Title'),
36
- page_age: z.string().describe('Publication timestamp'),
37
- contents: z
38
- .object({
39
- html: z.string().optional().describe('Full HTML content'),
40
- markdown: z.string().optional().describe('Full Markdown content'),
41
- })
42
- .optional()
43
- .describe('Livecrawled page content'),
44
- }),
45
- )
46
- .optional()
47
- .describe('News results'),
48
- })
49
- .optional()
50
- .describe('Search results'),
51
- })
52
-
53
- export type SearchStructuredContent = z.infer<typeof SearchStructuredContentSchema>