mirror of
https://github.com/aljazceru/mcp-code.git
synced 2025-12-17 04:35:19 +01:00
refactor and add list_snippets
This commit is contained in:
78
logic/fetch_snippet_by_id.ts
Normal file
78
logic/fetch_snippet_by_id.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
import { z } from "zod";
|
||||
import { formatSnippets } from "../lib/converters/index.js";
|
||||
import { ndk } from "../ndk.js";
|
||||
import { SNIPPET_KIND } from "../lib/nostr/utils.js";
|
||||
import { log } from "../lib/utils/log.js";
|
||||
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||
import { toSnippet } from "../lib/converters/index.js";
|
||||
|
||||
/**
|
||||
* Fetch a snippet by its ID
|
||||
* @param id Snippet ID to fetch
|
||||
* @returns The snippet content or an error message
|
||||
*/
|
||||
export async function fetchSnippetById(id: string): Promise<{
|
||||
content: Array<{ type: "text"; text: string }>;
|
||||
}> {
|
||||
try {
|
||||
log(`Fetching snippet with ID: ${id}`);
|
||||
|
||||
// Create filter for the specific event ID
|
||||
const filter = {
|
||||
kinds: [SNIPPET_KIND as number],
|
||||
ids: [id],
|
||||
};
|
||||
|
||||
// Fetch the event
|
||||
const events = await ndk.fetchEvents(filter);
|
||||
const event = Array.from(events)[0]; // Get the first (and should be only) event
|
||||
|
||||
if (!event) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `No snippet found with ID: ${id}`,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
// Convert event to snippet
|
||||
const snippet = toSnippet(event);
|
||||
|
||||
// Format the snippet for display
|
||||
const formattedSnippet = formatSnippets([snippet]);
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: formattedSnippet,
|
||||
},
|
||||
],
|
||||
};
|
||||
} catch (error: unknown) {
|
||||
const errorMessage =
|
||||
error instanceof Error ? error.message : String(error);
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Error fetching snippet: ${errorMessage}`,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export function addFetchSnippetByIdCommand(server: McpServer) {
|
||||
server.tool(
|
||||
"fetch_snippet_by_id",
|
||||
"Fetch and display a snippet by its ID",
|
||||
{
|
||||
id: z.string().describe("ID of the snippet to fetch"),
|
||||
},
|
||||
async ({ id }) => fetchSnippetById(id)
|
||||
);
|
||||
}
|
||||
@@ -1,44 +1,52 @@
|
||||
import { z } from "zod";
|
||||
import { ndk } from "../ndk.js";
|
||||
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||
import { knownUsers } from "../users.js";
|
||||
import { queryUser } from "../users.js";
|
||||
import { toPubkeys, formatUser } from "../lib/converters/index.js";
|
||||
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||
|
||||
/**
|
||||
* Find a user by name, npub, or other profile information
|
||||
* @param query The search query to find a user
|
||||
* @returns Results with formatted user information
|
||||
* @param query Search query to find a user
|
||||
* @returns Formatted user data or error message
|
||||
*/
|
||||
export async function findUser(query: string) {
|
||||
export async function findUser(query: string): Promise<{
|
||||
content: Array<{ type: "text"; text: string }>;
|
||||
}> {
|
||||
try {
|
||||
const pubkeys = queryUser(query);
|
||||
// Convert the query to pubkeys
|
||||
const pubkeys = toPubkeys(query);
|
||||
|
||||
if (pubkeys.length === 0) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text" as const,
|
||||
text: "No users found matching the query.",
|
||||
type: "text",
|
||||
text: `No users found matching: ${query}`,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
// Format the found users for display
|
||||
// Format user data for each pubkey
|
||||
const formattedUsers = pubkeys.map(formatUser).join("\n\n---\n\n");
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text" as const,
|
||||
text: `Found ${pubkeys.length} users:\n\n${formattedUsers}`,
|
||||
type: "text",
|
||||
text: formattedUsers,
|
||||
},
|
||||
],
|
||||
};
|
||||
} catch (error: unknown) {
|
||||
const errorMessage =
|
||||
error instanceof Error ? error.message : String(error);
|
||||
throw new Error(`Failed to find user: ${errorMessage}`);
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Error finding user: ${errorMessage}`,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,29 +57,7 @@ export function addFindUserCommand(server: McpServer) {
|
||||
{
|
||||
query: z.string().describe("The search query to find a user"),
|
||||
},
|
||||
async ({ query }) => {
|
||||
return findUser(query);
|
||||
}
|
||||
async ({ query }) => findUser(query)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format user profile data for display
|
||||
* @param pubkey User public key
|
||||
* @returns Formatted string representation
|
||||
*/
|
||||
function formatUser(pubkey: string): string {
|
||||
const profile = knownUsers[pubkey]?.profile;
|
||||
const user = ndk.getUser({ pubkey });
|
||||
const keys: Record<string, string> = {
|
||||
Npub: user.npub,
|
||||
};
|
||||
|
||||
if (profile?.name) keys.Name = profile.name;
|
||||
if (profile?.about) keys.About = profile.about;
|
||||
if (profile?.picture) keys.Picture = profile.picture;
|
||||
|
||||
return Object.entries(keys)
|
||||
.map(([key, value]) => `${key}: ${value}`)
|
||||
.join("\n");
|
||||
}
|
||||
|
||||
88
logic/list_snippets.ts
Normal file
88
logic/list_snippets.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
import { z } from "zod";
|
||||
import type { CodeSnippet, FindSnippetsParams } from "../lib/types/index.js";
|
||||
import { getSnippets } from "../lib/nostr/snippets.js";
|
||||
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||
import { toMetadataString } from "../lib/converters/index.js";
|
||||
|
||||
/**
|
||||
* List snippets with metadata
|
||||
* @param params Parameters to filter snippets
|
||||
* @returns List of snippet metadata
|
||||
*/
|
||||
export async function listSnippets(
|
||||
params: FindSnippetsParams = {}
|
||||
): Promise<{ content: Array<{ type: "text", text: string }> }> {
|
||||
try {
|
||||
const result = await getSnippets(params);
|
||||
let list = result.snippets
|
||||
.map(toMetadataString)
|
||||
.join("\n\n------------------\n\n");
|
||||
const extra = result.otherSnippets
|
||||
.map(toMetadataString)
|
||||
.join("\n\n------------------\n\n");
|
||||
|
||||
// Include partial matches if they exist
|
||||
if (result.otherSnippets.length > 0) {
|
||||
list += "\n\nSome other events not included in this result since they had less in common with your search, here is a list of the events that had partial matches:\n\n";
|
||||
list += extra;
|
||||
}
|
||||
|
||||
if (list.length === 0) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: "No snippets found",
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: list,
|
||||
},
|
||||
],
|
||||
};
|
||||
} catch (error: unknown) {
|
||||
const errorMessage =
|
||||
error instanceof Error ? error.message : String(error);
|
||||
throw new Error(`Failed to list snippets: ${errorMessage}`);
|
||||
}
|
||||
}
|
||||
|
||||
export function addListSnippetsCommand(server: McpServer) {
|
||||
server.tool(
|
||||
"list_snippets",
|
||||
"List code snippets metadata (without code content) with filtering by language and tags. Use this to get a large list of available code snippets.",
|
||||
{
|
||||
since: z
|
||||
.number()
|
||||
.optional()
|
||||
.describe("Fetch snippets newer than this timestamp"),
|
||||
until: z
|
||||
.number()
|
||||
.optional()
|
||||
.describe("Fetch snippets older than this timestamp"),
|
||||
authors: z
|
||||
.array(z.string())
|
||||
.optional()
|
||||
.describe(
|
||||
"List of author names to filter by (in username format!)"
|
||||
),
|
||||
languages: z
|
||||
.array(z.string())
|
||||
.optional()
|
||||
.describe("List of programming languages to filter by"),
|
||||
tags: z
|
||||
.array(z.string())
|
||||
.optional()
|
||||
.describe(
|
||||
"List of tags to filter by, be exhaustive, e.g. [ 'ndk', 'nostr', 'pubkey', 'signer' ], we use OR matches"
|
||||
),
|
||||
},
|
||||
async (args) => listSnippets(args)
|
||||
);
|
||||
}
|
||||
@@ -2,15 +2,16 @@ import { NDKEvent } from "@nostr-dev-kit/ndk";
|
||||
import { z } from "zod";
|
||||
import { SNIPPET_KIND, getSigner } from "../lib/nostr/utils.js";
|
||||
import { ndk } from "../ndk.js";
|
||||
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||
import { writeFileSync, readFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import { homedir, tmpdir } from "node:os";
|
||||
import { readConfig, getUser } from "../config.js";
|
||||
import { readConfig } from "../config.js";
|
||||
import { existsSync } from "node:fs";
|
||||
import * as Bun from "bun";
|
||||
import { createMetadataString, parseMetadataFromString } from "../lib/converters/index.js";
|
||||
|
||||
function log(message: string, ...args: any[]) {
|
||||
function log(message: string): void {
|
||||
// append to ~/.nmcp-nostr.log
|
||||
const logFilePath = join(homedir(), ".nmcp-nostr.log");
|
||||
const logMessage = `${new Date().toISOString()} - ${message}\n`;
|
||||
@@ -134,25 +135,12 @@ export async function publishCodeSnippet(
|
||||
username?: string
|
||||
): Promise<{ content: Array<{ type: "text", text: string }> }> {
|
||||
try {
|
||||
// Validate minimum requirements
|
||||
// if (tags.length < 5) {
|
||||
// throw new Error(
|
||||
// "Insufficient tags. At least 5 tags are required. Please add more relevant and accurate information."
|
||||
// );
|
||||
// }
|
||||
|
||||
// if (description.length < 140) {
|
||||
// throw new Error(
|
||||
// "Description is too short. At least 140 characters are required. Please add more relevant and accurate information."
|
||||
// );
|
||||
// }
|
||||
|
||||
// put the code snippet in a temp file and run the command in config.editor or `code` and wait until it's closed -- then read the file and publish it
|
||||
const config = readConfig();
|
||||
const tempFilePath = join(tmpdir(), `snippet-${Date.now()}.${language}`);
|
||||
|
||||
// Create file content with metadata section for editing
|
||||
const fileContent = createFileWithMetadata(title, description, language, tags, code);
|
||||
const fileContent = createMetadataString(title, description, language, tags, code);
|
||||
|
||||
// Write the content to the temp file
|
||||
writeFileSync(tempFilePath, fileContent);
|
||||
@@ -163,7 +151,7 @@ export async function publishCodeSnippet(
|
||||
// Spawn the editor process - first arg is the command array including both the command and its arguments
|
||||
const process = Bun.spawn([...editorCommand, tempFilePath]);
|
||||
|
||||
log("spawned editor process to edit " + tempFilePath);
|
||||
log(`spawned editor process to edit ${tempFilePath}`);
|
||||
|
||||
// Wait for the editor to close
|
||||
await process.exited;
|
||||
@@ -178,21 +166,21 @@ export async function publishCodeSnippet(
|
||||
if (existsSync(tempFilePath)) {
|
||||
const updatedContent = readFileSync(tempFilePath, "utf-8");
|
||||
try {
|
||||
log("updatedContent: " + updatedContent);
|
||||
const parsed = parseMetadata(updatedContent);
|
||||
log(`updatedContent: ${updatedContent}`);
|
||||
const parsed = parseMetadataFromString(updatedContent);
|
||||
updatedTitle = parsed.metadata.title || title;
|
||||
updatedDescription = parsed.metadata.description || description;
|
||||
updatedLanguage = parsed.metadata.language || language;
|
||||
updatedTags = parsed.metadata.tags.length >= 5 ? parsed.metadata.tags : tags;
|
||||
updatedCode = parsed.code;
|
||||
} catch (error) {
|
||||
log("error " + error);
|
||||
log(`error ${error}`);
|
||||
console.error("Error parsing metadata:", error);
|
||||
// Fallback to using the file content as just code if metadata parsing fails
|
||||
updatedCode = updatedContent;
|
||||
}
|
||||
} else {
|
||||
log("tempFilePath does not exist", tempFilePath);
|
||||
log(`tempFilePath does not exist ${tempFilePath}`);
|
||||
}
|
||||
|
||||
const eventTags = [
|
||||
|
||||
Reference in New Issue
Block a user