feat: async-job pattern tools for sonar-deep-research (re: #110)#111
Open
allenfbyrd wants to merge 1 commit into
Open
feat: async-job pattern tools for sonar-deep-research (re: #110)#111allenfbyrd wants to merge 1 commit into
allenfbyrd wants to merge 1 commit into
Conversation
Adds perplexity_research_start / _poll / _cancel as additive tools that expose the existing sonar-deep-research model via a job-id + poll pattern. This works against MCP clients whose tools/call timeout is shorter than typical deep-research wall-clock AND which don't include _meta.progressToken on requests (e.g. Claude Desktop v1.8555). The existing perplexity_research tool is unchanged; this is purely additive. Complementary to the notifications/progress fix proposed in perplexityai#110 (which works for clients that DO request progress). Refs: perplexityai#110
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds three new tools —
perplexity_research_start/_poll/_cancel— that expose the existingsonar-deep-researchmodel via a job-id + poll pattern, for MCP clients whosetools/calltimeout fires before deep-research completes AND which don't include_meta.progressTokento opt intonotifications/progress.The existing
perplexity_researchtool is unchanged — this is purely additive.Why this is complementary to #110
Issue #110 proposes a
notifications/progressfix on the existingperplexity_researchtool, which works for MCP clients that:_meta.progressTokenon thetools/callrequest, andresetTimeoutOnProgressto extend their internal timeout when notifications arrive.That's the right fix when both conditions hold (e.g., Claude Code v2.1.142+, several other clients per the TS SDK behavior). However, instrumentation of Claude Desktop v1.8555 (stdin-tee of the MCP transport) showed it sends
params = { name, arguments }with no_metaat all ontools/call, so a spec-compliant server cannot emit progress notifications addressed to the request — and Desktop'stools/calltimeout is also not driven by progress per the spec, so a syntheticprogressTokendoesn't help (the TS SDK matches incoming notifications against_progressHandlerskeyed by the client-issued token).The job-id + poll pattern works regardless of client progress support — the heavy lifting happens in a background
Promiseinside the server process, and each_pollcall returns within a configurable budget (default 45 sec) that is well under typical client timeouts. This is the same pattern the GitHub MCP server uses for long-running Actions runs and that the MCP spec authors recommend for long-running tools in modelcontextprotocol/modelcontextprotocol#982.Both fixes are useful for different client populations and don't conflict — a future PR could land #110's
notifications/progresspatch onperplexity_researchalongside these new tools, giving each client population a working option.What changes (3 files, 6 hunks, single commit)
src/server.ts:ResearchJobStoreclass withstart/poll/cancelmethods, an in-memoryMap<jobId, ResearchJobHandle>(TTL-swept), a background poll loop using the existing async cadence (8/8/12/18/27/40 sec) capped byPERPLEXITY_ASYNC_MAX_WAIT_MS, and three newregisterToolcalls. Theinstructionsstring increatePerplexityServeris also updated to mention the new tools.src/server.test.ts: vitest suite coveringstart(happy path + missing-id),poll(NOT_FOUND + in-progress race against the poll budget), andcancel(NOT_FOUND + marks-active). 7 new tests; existing tests untouched.src/transport.test.ts: one assertion updated — the "should handle HTTP MCP requests" test'stoHaveLength(4)becomestoHaveLength(7)plus three newtoolNames.toContain(...)lines covering the new tools.README.md: new "Available Tools" entries + new optional env-var section.perplexity_research(sync),perplexity_ask,perplexity_reason,perplexity_search.POST /v1/async/sonarandGET /v1/async/sonar/{id}, the same async endpoints Perplexity introduced for this model (see the community post and the API docs at https://docs.perplexity.ai/api-reference/async-sonar-post / https://docs.perplexity.ai/api-reference/async-sonar-api-request-get).Configuration
All defaults match the existing pattern (numeric ms env vars). Tuning knobs:
PERPLEXITY_ASYNC_MAX_WAIT_MS900000(15 min)PERPLEXITY_TIMEOUT_MS300000(5 min)PERPLEXITY_RESEARCH_JOB_TTL_MS1800000(30 min)PERPLEXITY_RESEARCH_POLL_BUDGET_MS45000(45 sec)_pollcall wall-clock budget — set lower than client'stools/callcapPERPLEXITY_RESEARCH_SWEEP_INTERVAL_MS300000(5 min)Migration
None required. Existing users of
perplexity_researchare unaffected.Test plan
npm ci && npm run build— TypeScript compiles cleanly (the JobStore class uses an index signature onResearchJobPayloadso it satisfies the SDK'sstructuredContent: Record<string, unknown>constraint; an inline comment inserver.tsexplains why)npm run test— 85/85 tests pass in vitest (3 test files, including the 7 new ResearchJobStore tests and the updated transport assertion)_startreturns in<1 sec;_pollcalls return in 28–54 sec each;COMPLETEDarrives at 187 sec wall-clock total for a typical deep-research prompt; 0 client cancellations across the full lifecycle-32001reliably on the originalperplexity_research; new flow completes cleanly. Wire-level capture (stdin-tee of MCP transport) verified that all 4 sequential_pollcalls returned within the 45-sec budget and the client never sentnotifications/cancelled. (Evidence captured at/c/Users/allen/AppData/Local/perplexity-mcp-logs/stdin-1779621300.jsonlon 2026-05-24 — happy to attach if useful.)Notes for review
ResearchJobStoreis exported so the test suite can construct fresh instances; it's also useful for any client embedding the server programmatically. The_hasJob/_jobCountmethods are explicit test helpers (underscore-prefixed) — happy to mark them@internalor move to a non-public location if the maintainer prefers..unref()'d so it doesn't keep the Node process alive on its own.notifications/cancelledfrom the client on_startdoes NOT propagate to the background poll loop — that cancellation only kills the MCP request; the background work continues so subsequent_pollcalls can retrieve the result. Use_cancelto explicitly free a job's slot._cancelis local-only and is documented that way in both the tool description and the README.