diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index b6233aa..65b8970 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -6,14 +6,14 @@ }, "metadata": { "description": "Official Perplexity AI plugin providing real-time web search, reasoning, and research capabilities", - "version": "1.0.0" + "version": "1.1.0" }, "plugins": [ { "name": "perplexity", "source": "./", "description": "Real-time web search, reasoning, and research through Perplexity's API", - "version": "1.0.0", + "version": "1.1.0", "author": { "name": "Perplexity AI", "email": "api@perplexity.ai" diff --git a/.gitignore b/.gitignore index 6101699..3fb67a6 100644 --- a/.gitignore +++ b/.gitignore @@ -59,3 +59,5 @@ Desktop.ini *.pem mcp-publisher migrate-namespace.sh + +*.tgz diff --git a/README.md b/README.md index 3e9fd69..86f3773 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ The official MCP server implementation for the Perplexity API Platform, providin ## Available Tools ### **perplexity_search** -Direct web search using the Perplexity Search API. Returns ranked search results with metadata, perfect for finding current information. +Direct web search using the Perplexity Search API. Returns ranked search results with metadata, perfect for finding current information. Supports recency filters (`search_recency_filter`) and domain restrictions (`search_domain_filter`). ### **perplexity_ask** General-purpose conversational AI with real-time web search, backed by the Agent API `fast` preset. Great for quick questions and everyday searches. diff --git a/package-lock.json b/package-lock.json index fb03157..61dd964 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@perplexity-ai/mcp-server", - "version": "1.0.0", + "version": "1.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@perplexity-ai/mcp-server", - "version": "1.0.0", + "version": "1.1.0", "license": "MIT", "dependencies": { "@modelcontextprotocol/sdk": "^1.29.0", diff --git a/package.json b/package.json index 3cb6761..6153285 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@perplexity-ai/mcp-server", - "version": "1.0.0", + "version": "1.1.0", "mcpName": "ai.perplexity/mcp-server", "description": "Real-time web search, reasoning, and research through Perplexity's API", "keywords": [ @@ -23,14 +23,25 @@ "access": "public" }, "type": "module", - "main": "dist/index.js", + "main": "dist/server.js", + "types": "dist/server.d.ts", + "exports": { + ".": { + "types": "./dist/server.d.ts", + "default": "./dist/server.js" + }, + "./package.json": "./package.json" + }, "bin": { "perplexity-mcp": "dist/index.js" }, "files": [ "dist/*.js", + "dist/*.d.ts", "!dist/*.test.js", + "!dist/*.test.d.ts", "!dist/vitest.config.js", + "!dist/vitest.config.d.ts", "README.md", ".claude-plugin" ], diff --git a/server.json b/server.json index 2f456ac..c904821 100644 --- a/server.json +++ b/server.json @@ -3,12 +3,12 @@ "name": "ai.perplexity/mcp-server", "title": "Perplexity API Platform", "description": "Real-time web search, reasoning, and research through Perplexity's API", - "version": "1.0.0", + "version": "1.1.0", "packages": [ { "registryType": "npm", "identifier": "@perplexity-ai/mcp-server", - "version": "1.0.0", + "version": "1.1.0", "transport": { "type": "stdio" } diff --git a/src/server.ts b/src/server.ts index a6f03cd..ccf82ce 100644 --- a/src/server.ts +++ b/src/server.ts @@ -14,7 +14,7 @@ import { AgentResponseSchema, SearchResponseSchema } from "./validation.js"; const PERPLEXITY_API_KEY = process.env.PERPLEXITY_API_KEY; const PERPLEXITY_BASE_URL = process.env.PERPLEXITY_BASE_URL || "https://api.perplexity.ai"; -const VERSION = "1.0.0"; +const VERSION = "1.1.0"; // Agent API presets backing each tool: https://docs.perplexity.ai/docs/agent-api/presets export const ASK_PRESET = "fast"; @@ -462,6 +462,7 @@ export async function performSearch( maxResults: number = 10, maxTokensPerPage: number = 1024, country?: string, + filters?: Pick, serviceOrigin?: string ): Promise { const body: Record = { @@ -469,6 +470,8 @@ export async function performSearch( max_results: maxResults, max_tokens_per_page: maxTokensPerPage, ...(country && { country }), + ...(filters?.search_recency_filter && { search_recency_filter: filters.search_recency_filter }), + ...(filters?.search_domain_filter && { search_domain_filter: filters.search_domain_filter }), }; const response = await makeApiRequest("search", body, serviceOrigin); @@ -524,7 +527,7 @@ export function createPerplexityServer(serviceOrigin?: string) { { instructions: "Perplexity AI server for web-grounded search, research, and reasoning, backed by the Perplexity Agent API. " + - "Use perplexity_search for finding URLs, facts, and recent news. " + + "Use perplexity_search for finding URLs, facts, and recent news. Supports recency filters and domain restrictions. " + "Use perplexity_ask for quick AI-answered questions with citations. Supports recency filters, domain restrictions, and search context size control. " + "Use perplexity_research for in-depth multi-source investigation (slow, can take minutes). " + "Use perplexity_reason for complex analysis requiring step-by-step logic. Supports recency filters, domain restrictions, and search context size control. " + @@ -699,6 +702,8 @@ export function createPerplexityServer(serviceOrigin?: string) { .describe("Maximum tokens to extract per webpage (default: 1024)"), country: z.string().optional() .describe("ISO 3166-1 alpha-2 country code for regional results (e.g., 'US', 'GB')"), + search_recency_filter: searchRecencyFilterField, + search_domain_filter: searchDomainFilterField, }; const searchOutputSchema = { @@ -712,6 +717,7 @@ export function createPerplexityServer(serviceOrigin?: string) { description: "Search the web and return a ranked list of results with titles, URLs, snippets, and dates. " + "Best for: finding specific URLs, checking recent news, verifying facts, discovering sources. " + "Returns formatted results (title, URL, snippet, date) with no AI synthesis. " + + "Supports recency filters and domain restrictions. " + "For AI-generated answers with citations, use perplexity_ask instead.", inputSchema: searchInputSchema as any, outputSchema: searchOutputSchema as any, @@ -723,17 +729,23 @@ export function createPerplexityServer(serviceOrigin?: string) { }, }, async (args: any) => { - const { query, max_results, max_tokens_per_page, country } = args as { + const { query, max_results, max_tokens_per_page, country, search_recency_filter, search_domain_filter } = args as { query: string; max_results?: number; max_tokens_per_page?: number; country?: string; + search_recency_filter?: "hour" | "day" | "week" | "month" | "year"; + search_domain_filter?: string[]; }; const maxResults = typeof max_results === "number" ? max_results : 10; const maxTokensPerPage = typeof max_tokens_per_page === "number" ? max_tokens_per_page : 1024; const countryCode = typeof country === "string" ? country : undefined; + const filters = { + ...(search_recency_filter && { search_recency_filter }), + ...(search_domain_filter && { search_domain_filter }), + }; - const result = await performSearch(query, maxResults, maxTokensPerPage, countryCode, serviceOrigin); + const result = await performSearch(query, maxResults, maxTokensPerPage, countryCode, filters, serviceOrigin); return { content: [{ type: "text" as const, text: result }], structuredContent: { results: result }, diff --git a/src/transport.test.ts b/src/transport.test.ts index 5c3e962..dacd104 100644 --- a/src/transport.test.ts +++ b/src/transport.test.ts @@ -6,6 +6,7 @@ import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js"; import express from "express"; import cors from "cors"; import { Server } from "http"; +import { readFileSync } from "node:fs"; function agentSseResponse(text: string): Response { const completed = { @@ -267,6 +268,19 @@ describe("Transport Integration Tests", () => { }); describe("Backward Compatibility", () => { + it("should advertise the package.json version to clients", async () => { + const { client, server } = await connectInMemoryClient(); + try { + const pkg = JSON.parse( + readFileSync(new URL("../package.json", import.meta.url), "utf8") + ); + expect(client.getServerVersion()?.version).toBe(pkg.version); + } finally { + await client.close(); + await server.close(); + } + }); + it("should keep the historical response shape including the citations block", async () => { global.fetch = vi .fn() @@ -348,6 +362,38 @@ describe("Transport Integration Tests", () => { } }); + it("should forward search filters to the search API request body", async () => { + global.fetch = vi.fn().mockResolvedValue({ + ok: true, + json: async () => ({ results: [] }), + } as Response); + + const { client, server } = await connectInMemoryClient(); + try { + const result: any = await client.callTool({ + name: "perplexity_search", + arguments: { + query: "test", + search_recency_filter: "week", + search_domain_filter: ["wikipedia.org", "-reddit.com"], + }, + }); + + expect(result.isError).toBeFalsy(); + const upstreamBody = JSON.parse( + (global.fetch as ReturnType).mock.calls[0][1].body as string + ); + expect(upstreamBody).toMatchObject({ + query: "test", + search_recency_filter: "week", + search_domain_filter: ["wikipedia.org", "-reddit.com"], + }); + } finally { + await client.close(); + await server.close(); + } + }); + it("should emit progress notifications when the client requests progress", async () => { const events = [ { type: "response.created", response: { id: "resp_progress" } }, diff --git a/tsconfig.json b/tsconfig.json index 190e48e..c8c1125 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,6 +4,7 @@ "module": "ESNext", "outDir": "./dist", "rootDir": "./src", + "declaration": true, "strict": true, "esModuleInterop": true, "forceConsistentCasingInFileNames": true,