@youdotcom-oss/mcp 3.3.0 → 3.4.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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@youdotcom-oss/mcp",
3
- "version": "3.3.0",
4
- "description": "You.com MCP server web search, AI research, and content extraction via You.com APIs",
3
+ "version": "3.4.0",
4
+ "description": "You.com MCP STDIO bridge for the hosted You.com MCP server",
5
5
  "license": "MIT",
6
6
  "engines": {
7
7
  "node": ">=18",
@@ -25,25 +25,21 @@
25
25
  ],
26
26
  "bin": "bin/stdio.js",
27
27
  "type": "module",
28
- "exports": {
29
- ".": "./src/main.ts"
30
- },
31
28
  "files": [
32
29
  "bin/stdio.js",
33
- "src",
34
30
  "server.json"
35
31
  ],
36
32
  "publishConfig": {
37
33
  "access": "public"
38
34
  },
39
35
  "scripts": {
40
- "build": "bun build ./src/stdio-server.ts --outfile ./bin/stdio.js --target=node",
36
+ "build": "bun build ./src/stdio-bridge.ts --outfile ./bin/stdio.js --target=node",
41
37
  "check": "bun run check:biome && bun run check:types && bun run check:package",
42
38
  "check:biome": "biome check",
43
39
  "check:package": "format-package --check",
44
40
  "check:types": "tsc --noEmit",
45
41
  "check:write": "biome check --write && bun run format:package",
46
- "dev": "bun src/stdio-server.ts",
42
+ "dev": "bun src/stdio-bridge.ts",
47
43
  "format:package": "format-package --write",
48
44
  "inspector": "test -n \"$YDC_API_KEY\" || (echo 'YDC_API_KEY is not set' && exit 1); mcp-inspector --config mcp-inspector.json -e YDC_API_KEY=$YDC_API_KEY",
49
45
  "prepublishOnly": "bun run build",
@@ -52,9 +48,7 @@
52
48
  },
53
49
  "mcpName": "io.github.youdotcom-oss/mcp",
54
50
  "dependencies": {
55
- "@modelcontextprotocol/sdk": "^1.28.0",
56
- "@youdotcom-oss/api": "0.5.2",
57
- "zod": "^4.3.6"
51
+ "@modelcontextprotocol/sdk": "^1.28.0"
58
52
  },
59
53
  "devDependencies": {
60
54
  "@modelcontextprotocol/inspector": "0.21.2",
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.2.3",
6
+ "version": "3.3.0",
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.2.3",
25
+ "version": "3.3.0",
26
26
  "transport": {
27
27
  "type": "stdio"
28
28
  },
@@ -35,8 +35,15 @@
35
35
  "format": "string"
36
36
  },
37
37
  {
38
- "name": "MCP_SERVER_URL",
39
- "description": "Remote MCP server URL (defaults to https://api.you.com/mcp)",
38
+ "name": "YDC_PROFILE",
39
+ "description": "Hosted MCP profile to expose through the STDIO bridge (currently supports free)",
40
+ "isRequired": false,
41
+ "isSecret": false,
42
+ "format": "string"
43
+ },
44
+ {
45
+ "name": "YDC_ALLOWED_TOOLS",
46
+ "description": "Comma-separated hosted tool ids to expose through the STDIO bridge",
40
47
  "isRequired": false,
41
48
  "isSecret": false,
42
49
  "format": "string"
@@ -1,59 +0,0 @@
1
- import type { ContentsApiResponse } from '@youdotcom-oss/api'
2
-
3
- /**
4
- * Format contents API response for MCP output
5
- * @param response - Validated API response
6
- * @param formats - Formats used for extraction
7
- * @returns Text content blocks for the MCP response
8
- */
9
- export const formatContentsResponse = (
10
- response: ContentsApiResponse,
11
- formats: string[],
12
- ): Array<{ type: 'text'; text: string }> => {
13
- const textParts: string[] = [`Successfully extracted content from ${response.length} URL(s):\n`]
14
- textParts.push(`Formats: ${formats.join(', ')}\n`)
15
-
16
- for (const item of response) {
17
- textParts.push(`\n## ${item.title || 'Untitled'}`)
18
- textParts.push(`URL: ${item.url}\n`)
19
- textParts.push('---\n')
20
-
21
- if (formats.includes('markdown') && item.markdown) {
22
- textParts.push('\n### Markdown Content\n')
23
- textParts.push(item.markdown)
24
- textParts.push('\n')
25
- }
26
-
27
- if (formats.includes('html') && item.html) {
28
- // Text output is a brief preview only — full HTML is in structuredContent.output[].html
29
- textParts.push('\n### HTML Content\n')
30
- textParts.push(`Length: ${item.html.length} characters\n`)
31
- textParts.push(item.html.substring(0, 500))
32
- if (item.html.length > 500) {
33
- textParts.push('...\n(truncated for display — full HTML available in structuredContent)')
34
- }
35
- textParts.push('\n')
36
- }
37
-
38
- if (formats.includes('metadata') && item.metadata) {
39
- textParts.push('\n### Metadata\n')
40
-
41
- if (item.metadata.site_name) {
42
- textParts.push(`**Site Name:** ${item.metadata.site_name}\n`)
43
- }
44
-
45
- if (item.metadata.favicon_url) {
46
- textParts.push(`**Favicon:** ${item.metadata.favicon_url}\n`)
47
- }
48
- }
49
-
50
- textParts.push('\n---\n')
51
- }
52
-
53
- return [
54
- {
55
- type: 'text',
56
- text: textParts.join('\n'),
57
- },
58
- ]
59
- }
@@ -1,95 +0,0 @@
1
- import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
2
- import {
3
- ContentsApiResponseSchema,
4
- ContentsQuerySchema,
5
- fetchContents,
6
- generateErrorReportLink,
7
- } from '@youdotcom-oss/api'
8
- import * as z from 'zod'
9
- import { getLogger } from '../shared/get-logger.ts'
10
- import { formatContentsResponse } from './contents.utils.ts'
11
-
12
- /**
13
- * Register the you-contents tool with the MCP server
14
- * Extracts and returns full content from multiple URLs in markdown or HTML format
15
- */
16
- export const registerContentsTool = ({
17
- mcp,
18
- YDC_API_KEY,
19
- getUserAgent,
20
- }: {
21
- mcp: McpServer
22
- YDC_API_KEY?: string
23
- getUserAgent: () => string
24
- }) => {
25
- mcp.registerTool(
26
- 'you-contents',
27
- {
28
- title: 'Extract Web Page Contents',
29
- description: 'Extract page content in markdown or HTML',
30
- inputSchema: ContentsQuerySchema,
31
- outputSchema: z.object({
32
- output: ContentsApiResponseSchema,
33
- }),
34
- },
35
- async (contentsQuery, { sendNotification }) => {
36
- const logger = getLogger(sendNotification)
37
-
38
- try {
39
- const { urls, formats, format, crawl_timeout } = contentsQuery
40
-
41
- // Handle backward compatibility: prefer formats array, fallback to format string, default to ['markdown']
42
- const requestFormats = formats || (format ? [format] : ['markdown'])
43
-
44
- const timeoutInfo = crawl_timeout ? ` with timeout: ${crawl_timeout}s` : ''
45
- await logger({
46
- level: 'info',
47
- data: `Contents API call initiated for ${urls.length} URL(s) with formats: ${requestFormats.join(', ')}${timeoutInfo}`,
48
- })
49
-
50
- const response = await fetchContents({
51
- contentsQuery,
52
- YDC_API_KEY,
53
- getUserAgent,
54
- })
55
-
56
- const content = formatContentsResponse(response, requestFormats)
57
-
58
- await logger({
59
- level: 'info',
60
- data: `Contents API call successful: extracted ${response.length} page(s)`,
61
- })
62
-
63
- return {
64
- content,
65
- structuredContent: {
66
- output: response,
67
- },
68
- }
69
- } catch (err: unknown) {
70
- const errorMessage = err instanceof Error ? err.message : String(err)
71
- const reportLink = generateErrorReportLink({
72
- errorMessage,
73
- tool: 'you-contents',
74
- clientInfo: getUserAgent(),
75
- })
76
-
77
- await logger({
78
- level: 'error',
79
- data: `Contents API call failed: ${errorMessage}\n\nReport this issue: ${reportLink}`,
80
- })
81
-
82
- return {
83
- content: [
84
- {
85
- type: 'text' as const,
86
- text: `Error extracting contents: ${errorMessage}`,
87
- },
88
- ],
89
- structuredContent: undefined,
90
- isError: true,
91
- }
92
- }
93
- },
94
- )
95
- }
@@ -1,99 +0,0 @@
1
- import { describe, expect, test } from 'bun:test'
2
- import type { ContentsApiResponse } from '@youdotcom-oss/api'
3
- import { formatContentsResponse } from '../contents.utils.ts'
4
-
5
- describe('formatContentsResponse', () => {
6
- test('formats single markdown content correctly', () => {
7
- const mockResponse: ContentsApiResponse = [
8
- {
9
- url: 'https://example.com',
10
- title: 'Example Page',
11
- markdown: '# Hello\n\nThis is a test page with some content.',
12
- },
13
- ]
14
-
15
- const result = formatContentsResponse(mockResponse, ['markdown'])
16
-
17
- expect(Array.isArray(result)).toBe(true)
18
- expect(result[0]).toHaveProperty('type', 'text')
19
- expect(result[0]).toHaveProperty('text')
20
-
21
- const text = result[0]?.text
22
- expect(text).toContain('Example Page')
23
- expect(text).toContain('https://example.com')
24
- expect(text).toContain('Formats: markdown')
25
- expect(text).toContain('# Hello')
26
- expect(text).toContain('This is a test page with some content.')
27
- })
28
-
29
- test('formats multiple items correctly', () => {
30
- const mockResponse: ContentsApiResponse = [
31
- {
32
- url: 'https://example1.com',
33
- title: 'Page 1',
34
- markdown: 'Content 1',
35
- },
36
- {
37
- url: 'https://example2.com',
38
- title: 'Page 2',
39
- markdown: 'Content 2',
40
- },
41
- ]
42
-
43
- const result = formatContentsResponse(mockResponse, ['markdown'])
44
-
45
- const text = result[0]?.text
46
- expect(text).toContain('Successfully extracted content from 2 URL(s)')
47
- expect(text).toContain('Page 1')
48
- expect(text).toContain('Page 2')
49
- expect(text).toContain('https://example1.com')
50
- expect(text).toContain('https://example2.com')
51
- })
52
-
53
- test('handles html format', () => {
54
- const mockResponse: ContentsApiResponse = [
55
- {
56
- url: 'https://example.com',
57
- title: 'HTML Page',
58
- html: '<html><body><h1>Hello</h1></body></html>',
59
- },
60
- ]
61
-
62
- const result = formatContentsResponse(mockResponse, ['html'])
63
-
64
- const text = result[0]?.text
65
- expect(text).toContain('Formats: html')
66
- expect(text).toContain('<html>')
67
- })
68
-
69
- test('includes full content for long text', () => {
70
- const longContent = 'a'.repeat(1000)
71
- const mockResponse: ContentsApiResponse = [
72
- {
73
- url: 'https://example.com',
74
- title: 'Long Page',
75
- markdown: longContent,
76
- },
77
- ]
78
-
79
- const result = formatContentsResponse(mockResponse, ['markdown'])
80
-
81
- const text = result[0]?.text
82
- expect(text).toContain(longContent)
83
- })
84
-
85
- test('handles empty content gracefully', () => {
86
- const mockResponse: ContentsApiResponse = [
87
- {
88
- url: 'https://example.com',
89
- title: 'Empty Page',
90
- markdown: '',
91
- },
92
- ]
93
-
94
- const result = formatContentsResponse(mockResponse, ['markdown'])
95
-
96
- const text = result[0]?.text
97
- expect(text).toContain('Empty Page')
98
- })
99
- })
@@ -1,17 +0,0 @@
1
- import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
2
- import packageJson from '../package.json' with { type: 'json' }
3
-
4
- export const getMcpServer = () =>
5
- new McpServer(
6
- {
7
- name: 'You.com',
8
- version: packageJson.version,
9
- },
10
- {
11
- capabilities: {
12
- logging: {},
13
- tools: { listChanged: true },
14
- },
15
- 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.`,
16
- },
17
- )
package/src/main.ts DELETED
@@ -1,5 +0,0 @@
1
- export { registerContentsTool } from './contents/register-contents-tool.ts'
2
- export { getMcpServer } from './get-mcp-server.ts'
3
- export { registerResearchTool } from './research/register-research-tool.ts'
4
- export { registerSearchTool } from './search/register-search-tool.ts'
5
- export { useGetClientVersion } from './shared/use-client-version.ts'
@@ -1,68 +0,0 @@
1
- import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
2
- import { callResearch, generateErrorReportLink, ResearchQuerySchema, ResearchResponseSchema } from '@youdotcom-oss/api'
3
- import { getLogger } from '../shared/get-logger.ts'
4
- import { formatResearchResults } from './research.utils.ts'
5
-
6
- export const registerResearchTool = ({
7
- mcp,
8
- YDC_API_KEY,
9
- getUserAgent,
10
- }: {
11
- mcp: McpServer
12
- YDC_API_KEY?: string
13
- getUserAgent: () => string
14
- }) => {
15
- mcp.registerTool(
16
- 'you-research',
17
- {
18
- title: 'Research',
19
- description:
20
- 'Research a topic with comprehensive answers and cited sources. Configurable effort levels (lite, standard, deep, exhaustive).',
21
- inputSchema: ResearchQuerySchema,
22
- outputSchema: ResearchResponseSchema,
23
- },
24
- async (researchQuery, { sendNotification }) => {
25
- const logger = getLogger(sendNotification)
26
- try {
27
- const response = await callResearch({
28
- researchQuery,
29
- YDC_API_KEY,
30
- getUserAgent,
31
- })
32
-
33
- const sourceCount = response.output.sources.length
34
-
35
- await logger({
36
- level: 'info',
37
- data: `Research for "${researchQuery.input.substring(0, 100)}" complete: ${sourceCount} source(s)`,
38
- })
39
-
40
- const content = formatResearchResults(response)
41
- return { content, structuredContent: response }
42
- } catch (err: unknown) {
43
- const errorMessage = err instanceof Error ? err.message : String(err)
44
- const reportLink = generateErrorReportLink({
45
- errorMessage,
46
- tool: 'you-research',
47
- clientInfo: getUserAgent(),
48
- })
49
-
50
- await logger({
51
- level: 'error',
52
- data: `Research API call failed: ${errorMessage}\n\nReport this issue: ${reportLink}`,
53
- })
54
-
55
- return {
56
- content: [
57
- {
58
- type: 'text' as const,
59
- text: `Error: ${errorMessage}`,
60
- },
61
- ],
62
- structuredContent: undefined,
63
- isError: true,
64
- }
65
- }
66
- },
67
- )
68
- }
@@ -1,12 +0,0 @@
1
- import type { ResearchResponse } from '@youdotcom-oss/api'
2
- import { formatResearchResponse } from '@youdotcom-oss/api'
3
-
4
- export const formatResearchResults = (response: ResearchResponse): Array<{ type: 'text'; text: string }> => {
5
- const text = formatResearchResponse(response)
6
- return [
7
- {
8
- type: 'text',
9
- text,
10
- },
11
- ]
12
- }
@@ -1,51 +0,0 @@
1
- import { describe, expect, test } from 'bun:test'
2
- import type { ResearchResponse } from '@youdotcom-oss/api'
3
- import { formatResearchResults } from '../research.utils.ts'
4
-
5
- describe('formatResearchResults', () => {
6
- test('formats research response with sources correctly', () => {
7
- const mockResponse: ResearchResponse = {
8
- output: {
9
- content: '# Research Answer\n\nThis is a comprehensive answer about the topic.',
10
- content_type: 'text',
11
- sources: [
12
- {
13
- url: 'https://example.com/source1',
14
- title: 'Source One',
15
- snippets: ['First snippet', 'Second snippet'],
16
- },
17
- {
18
- url: 'https://example.com/source2',
19
- title: 'Source Two',
20
- snippets: ['Another snippet'],
21
- },
22
- ],
23
- },
24
- }
25
-
26
- const result = formatResearchResults(mockResponse)
27
-
28
- expect(Array.isArray(result)).toBe(true)
29
- expect(result[0]).toHaveProperty('type', 'text')
30
- expect(result[0]).toHaveProperty('text')
31
-
32
- const text = result[0]?.text
33
- expect(text).toContain('Research Answer')
34
- expect(text).toContain('Source One')
35
- expect(text).toContain('https://example.com/source1')
36
- })
37
-
38
- test('handles response with zero sources', () => {
39
- const mockResponse: ResearchResponse = {
40
- output: {
41
- content: 'An answer with no cited sources.',
42
- content_type: 'text',
43
- sources: [],
44
- },
45
- }
46
-
47
- const result = formatResearchResults(mockResponse)
48
-
49
- expect(result[0]?.text).toContain('An answer with no cited sources.')
50
- })
51
- })
@@ -1,86 +0,0 @@
1
- import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
2
- import {
3
- fetchSearchResults,
4
- generateErrorReportLink,
5
- SearchQuerySchema,
6
- SearchResponseSchema,
7
- } from '@youdotcom-oss/api'
8
- import { getLogger } from '../shared/get-logger.ts'
9
- import { formatSearchResults } from './search.utils.ts'
10
-
11
- export const registerSearchTool = ({
12
- mcp,
13
- YDC_API_KEY,
14
- getUserAgent,
15
- }: {
16
- mcp: McpServer
17
- YDC_API_KEY?: string
18
- getUserAgent: () => string
19
- }) => {
20
- mcp.registerTool(
21
- 'you-search',
22
- {
23
- title: 'Web Search',
24
- description:
25
- 'Web and news search via You.com. Supports domain filtering, language selection, livecrawl for full page content, and date freshness controls.',
26
- inputSchema: SearchQuerySchema,
27
- outputSchema: SearchResponseSchema,
28
- },
29
- async (searchQuery, { sendNotification }) => {
30
- const logger = getLogger(sendNotification)
31
- try {
32
- const response = await fetchSearchResults({
33
- searchQuery,
34
- YDC_API_KEY,
35
- getUserAgent,
36
- })
37
-
38
- const webCount = response.results.web?.length ?? 0
39
- const newsCount = response.results.news?.length ?? 0
40
-
41
- if (!webCount && !newsCount) {
42
- await logger({
43
- level: 'info',
44
- data: `No results found for query: "${searchQuery.query}"`,
45
- })
46
-
47
- return {
48
- content: [{ type: 'text' as const, text: 'No results found.' }],
49
- structuredContent: response,
50
- }
51
- }
52
-
53
- await logger({
54
- level: 'info',
55
- data: `Search successful for query: "${searchQuery.query}" - ${webCount} web results, ${newsCount} news results (${webCount + newsCount} total)`,
56
- })
57
-
58
- const content = formatSearchResults(response)
59
- return { content, structuredContent: response }
60
- } catch (err: unknown) {
61
- const errorMessage = err instanceof Error ? err.message : String(err)
62
- const reportLink = generateErrorReportLink({
63
- errorMessage,
64
- tool: 'you-search',
65
- clientInfo: getUserAgent(),
66
- })
67
-
68
- await logger({
69
- level: 'error',
70
- data: `Search API call failed: ${errorMessage}\n\nReport this issue: ${reportLink}`,
71
- })
72
-
73
- return {
74
- content: [
75
- {
76
- type: 'text' as const,
77
- text: `Error: ${errorMessage}`,
78
- },
79
- ],
80
- structuredContent: undefined,
81
- isError: true,
82
- }
83
- }
84
- },
85
- )
86
- }
@@ -1,27 +0,0 @@
1
- import type { SearchResponse } from '@youdotcom-oss/api'
2
- import { formatSearchResultsText } from '../shared/format-search-results-text.ts'
3
-
4
- export const formatSearchResults = (response: SearchResponse) => {
5
- let formattedResults = ''
6
-
7
- if (response.results.web?.length) {
8
- const webResults = formatSearchResultsText(response.results.web)
9
- formattedResults += `WEB RESULTS:\n\n${webResults}`
10
- }
11
-
12
- if (response.results.news?.length) {
13
- const newsResults = formatSearchResultsText(response.results.news)
14
-
15
- if (formattedResults) {
16
- formattedResults += `\n\n${'='.repeat(50)}\n\n`
17
- }
18
- formattedResults += `NEWS RESULTS:\n\n${newsResults}`
19
- }
20
-
21
- return [
22
- {
23
- type: 'text' as const,
24
- text: `Search Results for "${response.metadata.query}":\n\n${formattedResults}`,
25
- },
26
- ]
27
- }