@youdotcom-oss/mcp 1.5.1 → 1.6.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.
Files changed (49) hide show
  1. package/bin/stdio.js +394 -415
  2. package/package.json +12 -23
  3. package/src/contents/contents.schemas.ts +3 -48
  4. package/src/contents/contents.utils.ts +33 -133
  5. package/src/contents/register-contents-tool.ts +24 -24
  6. package/src/contents/tests/contents.utils.spec.ts +58 -134
  7. package/src/express/express.schema.ts +23 -0
  8. package/src/express/express.utils.ts +10 -121
  9. package/src/express/register-express-tool.ts +19 -19
  10. package/src/express/tests/express.utils.spec.ts +80 -159
  11. package/src/get-mcp-server.ts +3 -3
  12. package/src/http.ts +38 -38
  13. package/src/search/register-search-tool.ts +23 -23
  14. package/src/search/search.schema.ts +38 -0
  15. package/src/search/search.utils.ts +18 -96
  16. package/src/search/tests/search.utils.spec.ts +61 -194
  17. package/src/shared/format-search-results-text.ts +16 -16
  18. package/src/shared/get-logger.ts +4 -4
  19. package/src/shared/tests/shared.utils.spec.ts +56 -56
  20. package/src/shared/use-client-version.ts +8 -8
  21. package/src/stdio.ts +16 -16
  22. package/src/tests/http.spec.ts +105 -105
  23. package/src/tests/tool.spec.ts +212 -212
  24. package/dist/contents/contents.schemas.d.ts +0 -55
  25. package/dist/contents/contents.utils.d.ts +0 -28
  26. package/dist/contents/register-contents-tool.d.ts +0 -10
  27. package/dist/express/express.schemas.d.ts +0 -56
  28. package/dist/express/express.utils.d.ts +0 -45
  29. package/dist/express/register-express-tool.d.ts +0 -6
  30. package/dist/get-mcp-server.d.ts +0 -2
  31. package/dist/http.d.ts +0 -3
  32. package/dist/main.d.ts +0 -9
  33. package/dist/search/register-search-tool.d.ts +0 -6
  34. package/dist/search/search.schemas.d.ts +0 -133
  35. package/dist/search/search.utils.d.ts +0 -98
  36. package/dist/shared/api-constants.d.ts +0 -9
  37. package/dist/shared/check-response-for-errors.d.ts +0 -6
  38. package/dist/shared/format-search-results-text.d.ts +0 -19
  39. package/dist/shared/generate-error-report-link.d.ts +0 -9
  40. package/dist/shared/get-logger.d.ts +0 -7
  41. package/dist/shared/use-client-version.d.ts +0 -6
  42. package/dist/stdio.d.ts +0 -2
  43. package/src/express/express.schemas.ts +0 -99
  44. package/src/main.ts +0 -9
  45. package/src/search/search.schemas.ts +0 -147
  46. package/src/shared/api-constants.ts +0 -10
  47. package/src/shared/check-response-for-errors.ts +0 -13
  48. package/src/shared/generate-error-report-link.ts +0 -37
  49. package/src/tests/exports.spec.ts +0 -24
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@youdotcom-oss/mcp",
3
- "version": "1.5.1",
4
- "description": "You.com API Model Context Protocol Server",
3
+ "version": "1.6.0",
4
+ "description": "You.com API Model Context Protocol Server - For programmatic API access, use @youdotcom-oss/api",
5
5
  "license": "MIT",
6
6
  "engines": {
7
7
  "node": ">=18",
@@ -24,14 +24,8 @@
24
24
  ],
25
25
  "bin": "bin/stdio.js",
26
26
  "type": "module",
27
- "main": "./src/main.ts",
28
27
  "exports": {
29
- ".": {
30
- "types": "./dist/main.d.ts",
31
- "default": "./src/main.ts"
32
- },
33
- "./http": "./src/http.ts",
34
- "./stdio": "./src/stdio.ts"
28
+ "./http": "./src/http.ts"
35
29
  },
36
30
  "files": [
37
31
  "bin/stdio.js",
@@ -44,36 +38,31 @@
44
38
  "access": "public"
45
39
  },
46
40
  "scripts": {
47
- "build": "bun run build:stdio && bun run build:types",
48
- "build:stdio": "bun build ./src/stdio.ts --outfile ./bin/stdio.js --target=node",
49
- "build:types": "tsc --project tsconfig.json --declaration --emitDeclarationOnly --noEmit false",
41
+ "build": "bun build ./src/stdio.ts --outfile ./bin/stdio.js --target=node",
50
42
  "check": "bun run check:biome && bun run check:types && bun run check:package",
51
43
  "check:biome": "biome check",
52
44
  "check:package": "format-package --check",
53
45
  "check:types": "tsc --noEmit",
54
- "check:write": "bun run format && bun run lint:fix && bun run format:package",
46
+ "check:write": "biome check --write && bun run format:package",
55
47
  "dev": "bun src/stdio.ts",
56
- "format": "biome format --write",
57
- "format:check": "biome format",
58
48
  "format:package": "format-package --write",
59
49
  "inspect": "bash -c 'source .env 2>/dev/null || true; bunx @modelcontextprotocol/inspector -e YDC_API_KEY=$YDC_API_KEY bun dev'",
60
- "lint": "biome lint",
61
- "lint:fix": "biome lint --write",
62
- "start": "bun run bin/http",
50
+ "prepublishOnly": "bun run build",
51
+ "start": "bun run src/http.ts",
63
52
  "test": "bun test",
64
53
  "test:coverage": "bun test --coverage",
65
54
  "test:coverage:watch": "bun test --coverage --watch",
66
55
  "test:watch": "bun test --watch"
67
56
  },
68
57
  "mcpName": "io.github.youdotcom-oss/mcp",
69
- "types": "./dist/main.d.ts",
70
58
  "dependencies": {
71
- "zod": "^4.3.5",
59
+ "@youdotcom-oss/api": "0.1.1",
60
+ "zod": "^4.3.6",
72
61
  "@hono/mcp": "^0.2.3",
73
- "@modelcontextprotocol/sdk": "^1.25.2",
74
- "hono": "^4.11.4"
62
+ "@modelcontextprotocol/sdk": "^1.25.3",
63
+ "hono": "^4.11.7"
75
64
  },
76
65
  "devDependencies": {
77
- "@modelcontextprotocol/inspector": "0.18.0"
66
+ "@modelcontextprotocol/inspector": "0.19.0"
78
67
  }
79
68
  }
@@ -1,49 +1,4 @@
1
- import * as z from 'zod';
2
-
3
- /**
4
- * Input schema for the you-contents tool
5
- * Accepts an array of URLs, optional formats array (or legacy format string), and optional crawl timeout
6
- */
7
- export const ContentsQuerySchema = z.object({
8
- urls: z
9
- .array(z.string().url())
10
- .min(1)
11
- .describe('Array of webpage URLs to extract content from (e.g., ["https://example.com"])'),
12
- formats: z
13
- .array(z.enum(['markdown', 'html', 'metadata']))
14
- .optional()
15
- .describe('Output formats: array of "markdown" (text), "html" (layout), or "metadata" (structured data)'),
16
- format: z.enum(['markdown', 'html']).optional().describe('(Deprecated) Output format - use formats array instead'),
17
- crawl_timeout: z.number().min(1).max(60).optional().describe('Optional timeout in seconds (1-60) for page crawling'),
18
- });
19
-
20
- export type ContentsQuery = z.infer<typeof ContentsQuerySchema>;
21
-
22
- /**
23
- * Schema for a single content item in the API response
24
- */
25
- const ContentsItemSchema = z.object({
26
- url: z.string().describe('URL'),
27
- title: z.string().optional().describe('Title'),
28
- html: z.string().optional().describe('HTML content'),
29
- markdown: z.string().optional().describe('Markdown content'),
30
- metadata: z
31
- .object({
32
- jsonld: z.array(z.record(z.string(), z.unknown())).optional().describe('JSON-LD structured data (Schema.org)'),
33
- opengraph: z.record(z.string(), z.string()).optional().describe('OpenGraph meta tags'),
34
- twitter: z.record(z.string(), z.string()).optional().describe('Twitter Card metadata'),
35
- })
36
- .optional()
37
- .describe('Structured metadata when available'),
38
- });
39
-
40
- /**
41
- * API response schema from You.com Contents API
42
- * Validates the full response array
43
- */
44
- export const ContentsApiResponseSchema = z.array(ContentsItemSchema);
45
-
46
- export type ContentsApiResponse = z.infer<typeof ContentsApiResponseSchema>;
1
+ import * as z from 'zod'
47
2
 
48
3
  /**
49
4
  * Structured content schema for MCP response
@@ -70,6 +25,6 @@ export const ContentsStructuredContentSchema = z.object({
70
25
  }),
71
26
  )
72
27
  .describe('Extracted items'),
73
- });
28
+ })
74
29
 
75
- export type ContentsStructuredContent = z.infer<typeof ContentsStructuredContentSchema>;
30
+ export type ContentsStructuredContent = z.infer<typeof ContentsStructuredContentSchema>
@@ -1,105 +1,5 @@
1
- import { CONTENTS_API_URL } from '../shared/api-constants.ts';
2
- import { checkResponseForErrors } from '../shared/check-response-for-errors.ts';
3
- import {
4
- type ContentsApiResponse,
5
- ContentsApiResponseSchema,
6
- type ContentsQuery,
7
- type ContentsStructuredContent,
8
- } from './contents.schemas.ts';
9
-
10
- /**
11
- * Fetch content from You.com Contents API
12
- * The API accepts multiple URLs in a single request and returns all results
13
- * @param contentsQuery - Query parameters including URLs and format
14
- * @param YDC_API_KEY - You.com API key
15
- * @param getUserAgent - Function to get User-Agent string
16
- * @returns Parsed and validated API response
17
- */
18
- export const fetchContents = async ({
19
- contentsQuery: { urls, formats, format, crawl_timeout },
20
- YDC_API_KEY = process.env.YDC_API_KEY,
21
- getUserAgent,
22
- }: {
23
- contentsQuery: ContentsQuery;
24
- YDC_API_KEY?: string;
25
- getUserAgent: () => string;
26
- }): Promise<ContentsApiResponse> => {
27
- if (!YDC_API_KEY) {
28
- throw new Error('YDC_API_KEY is required for Contents API');
29
- }
30
-
31
- // Handle backward compatibility: prefer formats array, fallback to format string, default to ['markdown']
32
- const requestFormats = formats || (format ? [format] : ['markdown']);
33
-
34
- // Build request body
35
- const requestBody: {
36
- urls: string[];
37
- formats: string[];
38
- crawl_timeout?: number;
39
- } = {
40
- urls,
41
- formats: requestFormats,
42
- };
43
-
44
- if (crawl_timeout !== undefined) {
45
- requestBody.crawl_timeout = crawl_timeout;
46
- }
47
-
48
- // Make single API call with all URLs
49
- const options = {
50
- method: 'POST',
51
- headers: new Headers({
52
- 'X-API-Key': YDC_API_KEY,
53
- 'Content-Type': 'application/json',
54
- 'User-Agent': getUserAgent(),
55
- }),
56
- body: JSON.stringify(requestBody),
57
- };
58
-
59
- const response = await fetch(CONTENTS_API_URL, options);
60
-
61
- // Handle HTTP errors
62
- if (!response.ok) {
63
- const errorCode = response.status;
64
-
65
- // Try to parse error response body
66
- let errorDetail = `Failed to fetch contents. HTTP ${errorCode}`;
67
- try {
68
- const errorBody = await response.json();
69
- if (errorBody && typeof errorBody === 'object' && 'detail' in errorBody) {
70
- errorDetail = String(errorBody.detail);
71
- }
72
- } catch {
73
- // If parsing fails, use default error message
74
- }
75
-
76
- // Handle specific error codes
77
- if (errorCode === 401) {
78
- throw new Error(`Authentication failed: ${errorDetail}. Please check your You.com API key.`);
79
- }
80
- if (errorCode === 403) {
81
- throw new Error(`Forbidden: ${errorDetail}. Your API key may not have access to the Contents API.`);
82
- }
83
- if (errorCode === 429) {
84
- throw new Error('Rate limited by You.com API. Please try again later.');
85
- }
86
- if (errorCode >= 500) {
87
- throw new Error(`You.com API server error: ${errorDetail}`);
88
- }
89
-
90
- throw new Error(errorDetail);
91
- }
92
-
93
- const results = await response.json();
94
-
95
- // Check for error field in 200 responses
96
- checkResponseForErrors(results);
97
-
98
- // Validate schema
99
- const parsedResults = ContentsApiResponseSchema.parse(results);
100
-
101
- return parsedResults;
102
- };
1
+ import type { ContentsApiResponse } from '@youdotcom-oss/api'
2
+ import type { ContentsStructuredContent } from './contents.schemas.ts'
103
3
 
104
4
  /**
105
5
  * Format contents API response for MCP output
@@ -112,69 +12,69 @@ export const formatContentsResponse = (
112
12
  response: ContentsApiResponse,
113
13
  formats: string[],
114
14
  ): {
115
- content: Array<{ type: 'text'; text: string }>;
116
- structuredContent: ContentsStructuredContent;
15
+ content: Array<{ type: 'text'; text: string }>
16
+ structuredContent: ContentsStructuredContent
117
17
  } => {
118
18
  // Build text content with full extracted content
119
- const textParts: string[] = [`Successfully extracted content from ${response.length} URL(s):\n`];
120
- textParts.push(`Formats: ${formats.join(', ')}\n`);
19
+ const textParts: string[] = [`Successfully extracted content from ${response.length} URL(s):\n`]
20
+ textParts.push(`Formats: ${formats.join(', ')}\n`)
121
21
 
122
- const items: ContentsStructuredContent['items'] = [];
22
+ const items: ContentsStructuredContent['items'] = []
123
23
 
124
24
  for (const item of response) {
125
25
  // Add header for this item
126
- textParts.push(`\n## ${item.title || 'Untitled'}`);
127
- textParts.push(`URL: ${item.url}\n`);
128
- textParts.push('---\n');
26
+ textParts.push(`\n## ${item.title || 'Untitled'}`)
27
+ textParts.push(`URL: ${item.url}\n`)
28
+ textParts.push('---\n')
129
29
 
130
30
  // Add content based on requested formats
131
31
  if (formats.includes('markdown') && item.markdown) {
132
- textParts.push('\n### Markdown Content\n');
133
- textParts.push(item.markdown);
134
- textParts.push('\n');
32
+ textParts.push('\n### Markdown Content\n')
33
+ textParts.push(item.markdown)
34
+ textParts.push('\n')
135
35
  }
136
36
 
137
37
  if (formats.includes('html') && item.html) {
138
- textParts.push('\n### HTML Content\n');
139
- textParts.push(`Length: ${item.html.length} characters\n`);
140
- textParts.push(item.html.substring(0, 500));
38
+ textParts.push('\n### HTML Content\n')
39
+ textParts.push(`Length: ${item.html.length} characters\n`)
40
+ textParts.push(item.html.substring(0, 500))
141
41
  if (item.html.length > 500) {
142
- textParts.push('...\n(truncated for display)');
42
+ textParts.push('...\n(truncated for display)')
143
43
  }
144
- textParts.push('\n');
44
+ textParts.push('\n')
145
45
  }
146
46
 
147
47
  if (formats.includes('metadata') && item.metadata) {
148
- textParts.push('\n### Metadata\n');
48
+ textParts.push('\n### Metadata\n')
149
49
 
150
50
  if (item.metadata.jsonld && item.metadata.jsonld.length > 0) {
151
- textParts.push('\n**JSON-LD:**\n');
152
- const jsonldStr = JSON.stringify(item.metadata.jsonld, null, 2);
51
+ textParts.push('\n**JSON-LD:**\n')
52
+ const jsonldStr = JSON.stringify(item.metadata.jsonld, null, 2)
153
53
  if (jsonldStr.length > 2000) {
154
- textParts.push(jsonldStr.substring(0, 2000));
155
- textParts.push('\n...(truncated for display, see structuredContent for full data)');
54
+ textParts.push(jsonldStr.substring(0, 2000))
55
+ textParts.push('\n...(truncated for display, see structuredContent for full data)')
156
56
  } else {
157
- textParts.push(jsonldStr);
57
+ textParts.push(jsonldStr)
158
58
  }
159
- textParts.push('\n');
59
+ textParts.push('\n')
160
60
  }
161
61
 
162
62
  if (item.metadata.opengraph) {
163
- textParts.push('\n**OpenGraph:**\n');
63
+ textParts.push('\n**OpenGraph:**\n')
164
64
  for (const [key, value] of Object.entries(item.metadata.opengraph)) {
165
- textParts.push(`- ${key}: ${value}\n`);
65
+ textParts.push(`- ${key}: ${value}\n`)
166
66
  }
167
67
  }
168
68
 
169
69
  if (item.metadata.twitter) {
170
- textParts.push('\n**Twitter:**\n');
70
+ textParts.push('\n**Twitter:**\n')
171
71
  for (const [key, value] of Object.entries(item.metadata.twitter)) {
172
- textParts.push(`- ${key}: ${value}\n`);
72
+ textParts.push(`- ${key}: ${value}\n`)
173
73
  }
174
74
  }
175
75
  }
176
76
 
177
- textParts.push('\n---\n');
77
+ textParts.push('\n---\n')
178
78
 
179
79
  // Add to structured content
180
80
  items.push({
@@ -183,7 +83,7 @@ export const formatContentsResponse = (
183
83
  markdown: item.markdown,
184
84
  html: item.html,
185
85
  metadata: item.metadata,
186
- });
86
+ })
187
87
  }
188
88
 
189
89
  return {
@@ -198,5 +98,5 @@ export const formatContentsResponse = (
198
98
  formats,
199
99
  items,
200
100
  },
201
- };
202
- };
101
+ }
102
+ }
@@ -1,8 +1,8 @@
1
- import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
- import { generateErrorReportLink } from '../shared/generate-error-report-link.ts';
3
- import { getLogger } from '../shared/get-logger.ts';
4
- import { ContentsQuerySchema, ContentsStructuredContentSchema } from './contents.schemas.ts';
5
- import { fetchContents, formatContentsResponse } from './contents.utils.ts';
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
2
+ import { ContentsQuerySchema, fetchContents, generateErrorReportLink } from '@youdotcom-oss/api'
3
+ import { getLogger } from '../shared/get-logger.ts'
4
+ import { ContentsStructuredContentSchema } from './contents.schemas.ts'
5
+ import { formatContentsResponse } from './contents.utils.ts'
6
6
 
7
7
  /**
8
8
  * Register the you-contents tool with the MCP server
@@ -13,9 +13,9 @@ export const registerContentsTool = ({
13
13
  YDC_API_KEY,
14
14
  getUserAgent,
15
15
  }: {
16
- mcp: McpServer;
17
- YDC_API_KEY?: string;
18
- getUserAgent: () => string;
16
+ mcp: McpServer
17
+ YDC_API_KEY?: string
18
+ getUserAgent: () => string
19
19
  }) => {
20
20
  // Register the tool
21
21
  mcp.registerTool(
@@ -27,56 +27,56 @@ export const registerContentsTool = ({
27
27
  outputSchema: ContentsStructuredContentSchema.shape,
28
28
  },
29
29
  async (toolInput) => {
30
- const logger = getLogger(mcp);
30
+ const logger = getLogger(mcp)
31
31
 
32
32
  try {
33
33
  // Validate and parse input
34
- const contentsQuery = ContentsQuerySchema.parse(toolInput);
35
- const { urls, formats, format, crawl_timeout } = contentsQuery;
34
+ const contentsQuery = ContentsQuerySchema.parse(toolInput)
35
+ const { urls, formats, format, crawl_timeout } = contentsQuery
36
36
 
37
37
  // Handle backward compatibility: prefer formats array, fallback to format string, default to ['markdown']
38
- const requestFormats = formats || (format ? [format] : ['markdown']);
38
+ const requestFormats = formats || (format ? [format] : ['markdown'])
39
39
 
40
40
  // Log the request
41
- const timeoutInfo = crawl_timeout ? ` with timeout: ${crawl_timeout}s` : '';
41
+ const timeoutInfo = crawl_timeout ? ` with timeout: ${crawl_timeout}s` : ''
42
42
  await logger({
43
43
  level: 'info',
44
44
  data: `Contents API call initiated for ${urls.length} URL(s) with formats: ${requestFormats.join(', ')}${timeoutInfo}`,
45
- });
45
+ })
46
46
 
47
47
  // Fetch contents from API
48
48
  const response = await fetchContents({
49
49
  contentsQuery,
50
50
  YDC_API_KEY,
51
51
  getUserAgent,
52
- });
52
+ })
53
53
 
54
54
  // Format response with full content
55
- const { content, structuredContent } = formatContentsResponse(response, requestFormats);
55
+ const { content, structuredContent } = formatContentsResponse(response, requestFormats)
56
56
 
57
57
  // Log success
58
58
  await logger({
59
59
  level: 'info',
60
60
  data: `Contents API call successful: extracted ${response.length} page(s)`,
61
- });
61
+ })
62
62
 
63
63
  return {
64
64
  content,
65
65
  structuredContent,
66
- };
66
+ }
67
67
  } catch (err: unknown) {
68
68
  // Handle and log errors
69
- const errorMessage = err instanceof Error ? err.message : String(err);
69
+ const errorMessage = err instanceof Error ? err.message : String(err)
70
70
  const reportLink = generateErrorReportLink({
71
71
  errorMessage,
72
72
  tool: 'you-contents',
73
73
  clientInfo: getUserAgent(),
74
- });
74
+ })
75
75
 
76
76
  await logger({
77
77
  level: 'error',
78
78
  data: `Contents API call failed: ${errorMessage}\n\nReport this issue: ${reportLink}`,
79
- });
79
+ })
80
80
 
81
81
  return {
82
82
  content: [
@@ -87,8 +87,8 @@ export const registerContentsTool = ({
87
87
  ],
88
88
  structuredContent: undefined,
89
89
  isError: true,
90
- };
90
+ }
91
91
  }
92
92
  },
93
- );
94
- };
93
+ )
94
+ }