Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 106 additions & 0 deletions index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,112 @@ server.tool(
}
)

server.tool(
"exa_get_contents",
"Retrieve contents of documents using Exa API based on IDs (URLs or search result IDs)",
{
ids: z
.union([z.string(), z.array(z.string())])
.describe(
"A single ID (URL or search result ID), or an array of IDs to retrieve content for"
),
text: z
.union([
z.literal(true),
z.object({
maxCharacters: z
.number()
.optional()
.describe(
"Maximum number of characters for the text content of each result"
),
includeHtmlTags: z
.boolean()
.optional()
.describe("Include HTML tags in the text content"),
}),
])
.optional()
.describe(
"Request text content. Set to true for defaults, or provide an object for specific options."
),
highlights: z
.union([
z.literal(true),
z.object({
query: z
.string()
.optional()
.describe(
"The query to use for highlighting relevant sentences. Defaults to the original search query if available."
),
numSentences: z
.number()
.optional()
.describe("The number of sentences to include in the highlights"),
highlightsPerUrl: z
.number()
.optional()
.describe("Number of highlights to generate per URL"),
}),
])
.optional()
.describe(
"Request highlights. Set to true for defaults, or provide an object for specific options."
),
},
async (args) => {
// Define a type matching the expected structure for Exa's getContents options
// This is useful for type safety when constructing the options object.
type ExaContentsOptions = {
text?: true | { maxCharacters?: number; includeHtmlTags?: boolean }
highlights?:
| true
| { query?: string; numSentences?: number; highlightsPerUrl?: number }
}

// Construct options directly, leveraging undefined for omitted args
const options: ExaContentsOptions = {
text: args.text,
highlights: args.highlights,
}

// Default to getting text if neither text nor highlight options are provided, as per SDK docs
if (options.text === undefined && options.highlights === undefined) {
options.text = true // Explicitly set to true for default behavior
}

try {
const result = await exa.getContents(args.ids, options)
return {
content: [
{
type: "text",
text:
result.results.length === 0
? `(no content results found for the provided IDs)`
: [
`[content results start]`,
dump(result.results),
`[content results end]`,
].join("\n"),
},
],
}
} catch (error) {
return {
content: [
{
type: "text",
text: error instanceof Error ? error.message : String(error),
},
],
isError: true,
}
}
}
)

if (process.argv.includes("--sse")) {
const transports = new Map<string, SSEServerTransport>()
const port = Number(process.env.PORT || "3000")
Expand Down