Skip to content

Commit 4a2fa63

Browse files
committed
Add lookup MCP tool
1 parent 7331886 commit 4a2fa63

2 files changed

Lines changed: 129 additions & 5 deletions

File tree

src/mcp-server.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import pkg from '../package.json';
1111
import { initializePaymentMiddleware, PAYWALLED_TOOLS } from './lib/x402';
1212
import { analyzeHandler, analyzeTool } from './tools/analyze';
1313
import { fetchHandler, fetchTool } from './tools/fetch';
14+
import { lookupHandler, lookupTool } from './tools/lookup';
1415
import { notesHandler, notesTool } from './tools/notes';
1516
import { publishHandler, publishTool } from './tools/publish';
1617
import { queryHandler, queryTool } from './tools/query';
@@ -31,16 +32,12 @@ const registerWorkflowTools = (
3132
const register = server.registerTool.bind(server);
3233

3334
register('fetch', fetchTool, async (input) => await fetchHandler(input));
34-
3535
register('analyze', analyzeTool, async (input) => await analyzeHandler(input, logger));
36-
3736
register('notes', notesTool, async (input) => await notesHandler(input));
38-
3937
register('publish', publishTool, async (input) => await publishHandler(input));
40-
4138
register('query', queryTool, async (input) => await queryHandler(input));
42-
4339
register('show', showTool, async (input) => await showHandler(input));
40+
register('lookup', lookupTool, async (input) => await lookupHandler(input));
4441
};
4542

4643
const PORT = Number(process.env.GWALN_MCP_PORT ?? process.env.PORT ?? 3233);

src/tools/lookup.ts

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
/**
2+
* @file src/tools/lookup.ts
3+
* @description MCP tool for looking up topics in the catalog and searching APIs.
4+
*/
5+
6+
import { z } from 'zod';
7+
import { addTopicToCatalog, runLookupWorkflow } from '../workflows/lookup-workflow';
8+
import { textContent } from './utils';
9+
10+
export const LookupInputSchema = z
11+
.object({
12+
query: z.string(),
13+
searchApis: z.boolean().optional(),
14+
limit: z.number().min(1).max(20).optional(),
15+
action: z.enum(['search', 'add']).optional(),
16+
title: z.string().optional(),
17+
wikipediaSlug: z.string().optional(),
18+
grokipediaSlug: z.string().optional(),
19+
})
20+
.refine(
21+
(value) => {
22+
if (value.action === 'add') {
23+
return Boolean(value.title && value.wikipediaSlug && value.grokipediaSlug);
24+
}
25+
return true;
26+
},
27+
{
28+
message: 'When action is "add", title, wikipediaSlug, and grokipediaSlug are required.',
29+
path: ['action'],
30+
},
31+
);
32+
33+
export const lookupTool = {
34+
title: 'Lookup topics',
35+
description:
36+
'Searches for topics in the local catalog and optionally searches Grokipedia and Wikipedia APIs. Can also add topics to the catalog.',
37+
inputSchema: LookupInputSchema,
38+
};
39+
40+
const normalizeGrokSlug = (slug: string): string => {
41+
if (!slug.startsWith('page/')) {
42+
return `page/${slug}`;
43+
}
44+
return slug;
45+
};
46+
47+
export const lookupHandler = async (input: z.infer<typeof LookupInputSchema>) => {
48+
const { query, searchApis = false, limit = 5, action = 'search' } = input;
49+
50+
if (action === 'add') {
51+
if (!input.title || !input.wikipediaSlug || !input.grokipediaSlug) {
52+
throw new Error(
53+
'Missing required fields for adding topic: title, wikipediaSlug, grokipediaSlug',
54+
);
55+
}
56+
57+
const grokSlug = normalizeGrokSlug(input.grokipediaSlug);
58+
const newTopic = addTopicToCatalog({
59+
title: input.title,
60+
wikipediaSlug: input.wikipediaSlug,
61+
grokipediaSlug: grokSlug,
62+
});
63+
64+
return {
65+
content: textContent(
66+
`[lookup] Added topic "${newTopic.title}" to catalog (ID: ${newTopic.id})`,
67+
),
68+
structuredContent: {
69+
action: 'add',
70+
topic: newTopic,
71+
},
72+
};
73+
}
74+
75+
// Search action
76+
const result = await runLookupWorkflow({
77+
query,
78+
searchApis: searchApis ?? false,
79+
limit,
80+
});
81+
82+
if (result.found && result.topic) {
83+
return {
84+
content: textContent(
85+
`[lookup] Found topic "${result.topic.title}" in local catalog (ID: ${result.topic.id})`,
86+
),
87+
structuredContent: {
88+
action: 'search',
89+
found: true,
90+
topic: result.topic,
91+
searchResults: null,
92+
},
93+
};
94+
}
95+
96+
if (result.searchResults) {
97+
const { grokipedia, wikipedia } = result.searchResults;
98+
const hasResults = grokipedia.length > 0 || wikipedia.length > 0;
99+
100+
return {
101+
content: textContent(
102+
hasResults
103+
? `[lookup] Found ${grokipedia.length} Grokipedia and ${wikipedia.length} Wikipedia results for "${query}"`
104+
: `[lookup] No results found for "${query}"`,
105+
),
106+
structuredContent: {
107+
action: 'search',
108+
found: false,
109+
topic: null,
110+
searchResults: {
111+
grokipedia,
112+
wikipedia,
113+
},
114+
},
115+
};
116+
}
117+
118+
return {
119+
content: textContent(`[lookup] Topic "${query}" not found in local catalog`),
120+
structuredContent: {
121+
action: 'search',
122+
found: false,
123+
topic: null,
124+
searchResults: null,
125+
},
126+
};
127+
};

0 commit comments

Comments
 (0)