octagon-mcp
Version:
MCP server for Octagon API. Provides specialized AI agents for investment research of public and private markets.
137 lines (136 loc) • 5.59 kB
JavaScript
import { z } from "zod";
import { createTextErrorResult } from "#tools/shared";
import { DOCS_DEFAULT_MAX_CHARS } from "./config.js";
import { defaultDocsService, } from "./service.js";
const sourceSchema = z.enum(["docs", "site", "all"]).optional();
export const docsListInputShape = {
section: z.string().trim().min(1).optional(),
source: sourceSchema.describe("Docs source to list. Use docs for API/docs corpus, site for broader site index, or all."),
limit: z.number().int().min(1).max(100).optional(),
};
export const docsSearchInputShape = {
query: z.string().trim().min(1).describe("Search query for Octagon docs"),
section: z.string().trim().min(1).optional(),
source: sourceSchema,
limit: z.number().int().min(1).max(100).optional(),
includeSnippets: z.boolean().optional(),
};
export const docsReadInputShape = {
target: z
.string()
.trim()
.min(1)
.describe("Docs title, URL, path, or catalog id to read"),
source: sourceSchema,
maxChars: z.number().int().min(1000).max(50000).optional(),
preferCachedContent: z.boolean().optional(),
};
export const docsRefreshInputShape = {
includeSite: z
.boolean()
.optional()
.describe("Also refresh the broader octagonai.co site index"),
};
function summarizeEntries(entries) {
if (entries.length === 0) {
return "No Octagon docs entries matched.";
}
return entries
.map((entry, index) => `${index + 1}. ${entry.title} (${entry.section})\n${entry.url}${entry.summary ? `\n${entry.summary}` : ""}`)
.join("\n\n");
}
export async function executeDocsListTool(service, params) {
try {
const entries = await service.list(params);
return {
content: [{ type: "text", text: summarizeEntries(entries) }],
structuredContent: {
entries,
count: entries.length,
},
};
}
catch (error) {
console.error("Error listing Octagon docs:", error);
return createTextErrorResult("Error: Failed to list Octagon docs.");
}
}
export async function executeDocsSearchTool(service, params) {
try {
const results = await service.search({
...params,
includeSnippets: params.includeSnippets ?? true,
});
const entries = results.map(result => ({
...result.entry,
score: result.score,
snippet: result.snippet,
}));
return {
content: [{ type: "text", text: summarizeEntries(entries) }],
structuredContent: {
query: params.query,
results: entries,
count: entries.length,
},
};
}
catch (error) {
console.error("Error searching Octagon docs:", error);
return createTextErrorResult("Error: Failed to search Octagon docs.");
}
}
export async function executeDocsReadTool(service, params) {
try {
const result = await service.read({
target: params.target,
source: params.source,
maxChars: params.maxChars ?? DOCS_DEFAULT_MAX_CHARS,
preferCachedContent: params.preferCachedContent ?? true,
});
return {
content: [{ type: "text", text: result.markdown }],
structuredContent: result,
};
}
catch (error) {
console.error("Error reading Octagon docs:", error);
return createTextErrorResult(error instanceof Error
? `Error: ${error.message}`
: "Error: Failed to read Octagon docs.");
}
}
export async function executeDocsRefreshTool(service, params) {
try {
const catalogs = await service.refresh({
includeSite: params.includeSite ?? false,
});
const text = catalogs
.map(catalog => `${catalog.source}: ${catalog.entries.length} entries from ${catalog.sourceUrl}`)
.join("\n");
return {
content: [{ type: "text", text }],
structuredContent: {
catalogs: catalogs.map(catalog => ({
source: catalog.source,
sourceUrl: catalog.sourceUrl,
fetchedAt: catalog.fetchedAt,
expiresAt: catalog.expiresAt,
entries: catalog.entries.length,
sections: catalog.sections,
})),
},
};
}
catch (error) {
console.error("Error refreshing Octagon docs:", error);
return createTextErrorResult("Error: Failed to refresh Octagon docs.");
}
}
export function registerDocsTools(server, service = defaultDocsService) {
const toolServer = server;
toolServer.tool("octagon-docs-list", "List live Octagon documentation sections and pages from the docs LLM corpus.", docsListInputShape, async (params) => executeDocsListTool(service, params));
toolServer.tool("octagon-docs-search", "Search live Octagon API, agent, MCP, and plugin documentation with source URLs.", docsSearchInputShape, async (params) => executeDocsSearchTool(service, params));
toolServer.tool("octagon-docs-read", "Read a live Octagon docs page or section by title, path, URL, or catalog id.", docsReadInputShape, async (params) => executeDocsReadTool(service, params));
toolServer.tool("octagon-docs-refresh", "Refresh the in-memory Octagon docs catalog from live LLM-friendly docs endpoints.", docsRefreshInputShape, async (params) => executeDocsRefreshTool(service, params));
}