Skip to content

feat: async-job pattern tools for sonar-deep-research (re: #110)#111

Open
allenfbyrd wants to merge 1 commit into
perplexityai:mainfrom
allenfbyrd:feat/async-job-poll-tools-for-deep-research
Open

feat: async-job pattern tools for sonar-deep-research (re: #110)#111
allenfbyrd wants to merge 1 commit into
perplexityai:mainfrom
allenfbyrd:feat/async-job-poll-tools-for-deep-research

Conversation

@allenfbyrd
Copy link
Copy Markdown

@allenfbyrd allenfbyrd commented May 24, 2026

Summary

Adds three new tools — perplexity_research_start / _poll / _cancel — that expose the existing sonar-deep-research model via a job-id + poll pattern, for MCP clients whose tools/call timeout fires before deep-research completes AND which don't include _meta.progressToken to opt into notifications/progress.

Note on which timeout this addresses: This is the MCP-client→server tools/call cap (hardcoded ~60 sec in Claude Desktop, configurable in some other clients). It is distinct from PERPLEXITY_TIMEOUT_MS (already documented in the README), which controls the server→Perplexity-API HTTP timeout. Raising PERPLEXITY_TIMEOUT_MS does not extend the client-side cap; see #110 for the same clarification.

The existing perplexity_research tool is unchanged — this is purely additive.

Why this is complementary to #110

Issue #110 proposes a notifications/progress fix on the existing perplexity_research tool, which works for MCP clients that:

  1. Set _meta.progressToken on the tools/call request, and
  2. Honor resetTimeoutOnProgress to 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 _meta at all on tools/call, so a spec-compliant server cannot emit progress notifications addressed to the request — and Desktop's tools/call timeout is also not driven by progress per the spec, so a synthetic progressToken doesn't help (the TS SDK matches incoming notifications against _progressHandlers keyed by the client-issued token).

The job-id + poll pattern works regardless of client progress support — the heavy lifting happens in a background Promise inside the server process, and each _poll call 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/progress patch on perplexity_research alongside these new tools, giving each client population a working option.

What changes (3 files, 6 hunks, single commit)

  • New (additive):
    • src/server.ts: ResearchJobStore class with start / poll / cancel methods, an in-memory Map<jobId, ResearchJobHandle> (TTL-swept), a background poll loop using the existing async cadence (8/8/12/18/27/40 sec) capped by PERPLEXITY_ASYNC_MAX_WAIT_MS, and three new registerTool calls. The instructions string in createPerplexityServer is also updated to mention the new tools.
    • src/server.test.ts: vitest suite covering start (happy path + missing-id), poll (NOT_FOUND + in-progress race against the poll budget), and cancel (NOT_FOUND + marks-active). 7 new tests; existing tests untouched.
    • src/transport.test.ts: one assertion updated — the "should handle HTTP MCP requests" test's toHaveLength(4) becomes toHaveLength(7) plus three new toolNames.toContain(...) lines covering the new tools.
    • README.md: new "Available Tools" entries + new optional env-var section.
  • Unchanged: perplexity_research (sync), perplexity_ask, perplexity_reason, perplexity_search.
  • Endpoints used: POST /v1/async/sonar and GET /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:

Env var Default Purpose
PERPLEXITY_ASYNC_MAX_WAIT_MS 900000 (15 min) Hard ceiling on a single job's lifetime
PERPLEXITY_TIMEOUT_MS 300000 (5 min) Per-HTTP-call timeout for submit + each poll
PERPLEXITY_RESEARCH_JOB_TTL_MS 1800000 (30 min) How long completed/failed job results are retained in memory
PERPLEXITY_RESEARCH_POLL_BUDGET_MS 45000 (45 sec) Per _poll call wall-clock budget — set lower than client's tools/call cap
PERPLEXITY_RESEARCH_SWEEP_INTERVAL_MS 300000 (5 min) TTL sweeper cadence

Migration

None required. Existing users of perplexity_research are unaffected.

Test plan

  • npm ci && npm run build — TypeScript compiles cleanly (the JobStore class uses an index signature on ResearchJobPayload so it satisfies the SDK's structuredContent: Record<string, unknown> constraint; an inline comment in server.ts explains why)
  • npm run test85/85 tests pass in vitest (3 test files, including the 7 new ResearchJobStore tests and the updated transport assertion)
  • Manual end-to-end against the real Perplexity async API: _start returns in <1 sec; _poll calls return in 28–54 sec each; COMPLETED arrives at 187 sec wall-clock total for a typical deep-research prompt; 0 client cancellations across the full lifecycle
  • Integration tested against Claude Desktop v1.8555 — previously hit -32001 reliably on the original perplexity_research; new flow completes cleanly. Wire-level capture (stdin-tee of MCP transport) verified that all 4 sequential _poll calls returned within the 45-sec budget and the client never sent notifications/cancelled. (Evidence captured at /c/Users/allen/AppData/Local/perplexity-mcp-logs/stdin-1779621300.jsonl on 2026-05-24 — happy to attach if useful.)

Notes for review

  • The ResearchJobStore is exported so the test suite can construct fresh instances; it's also useful for any client embedding the server programmatically. The _hasJob / _jobCount methods are explicit test helpers (underscore-prefixed) — happy to mark them @internal or move to a non-public location if the maintainer prefers.
  • The sweeper's interval handle is .unref()'d so it doesn't keep the Node process alive on its own.
  • notifications/cancelled from the client on _start does NOT propagate to the background poll loop — that cancellation only kills the MCP request; the background work continues so subsequent _poll calls can retrieve the result. Use _cancel to explicitly free a job's slot.
  • The Perplexity async API does not expose a cancel endpoint (verified 2026-05); _cancel is local-only and is documented that way in both the tool description and the README.

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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant