Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions connectors/ids.lock.json
Original file line number Diff line number Diff line change
Expand Up @@ -488,6 +488,8 @@
"opoint#search-by-ids",
"opoint#search-headlines",
"opoint#suggest",
"parallel#fetch",
"parallel#search",
"pdl#v5/company/enrich",
"pdl#v5/company/search",
"pdl#v5/person/enrich",
Expand Down
32 changes: 32 additions & 0 deletions connectors/parallel/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Parallel Search MCP

Free web search and page extraction with no Parallel account or API key. Search
uses Fast mode on the anonymous
[Search MCP endpoint](https://docs.parallel.ai/integrations/mcp/search-mcp). The
free tier is rate limited and intended for exploration and light use.

From the repository root, with Deno 2.x installed:

```bash
deno task check && deno task test
deno task catalog inspect 'parallel#search'
deno task engine:run 'parallel#search' --body '{"objective":"Find the official Deno 2 release announcement","search_queries":["Deno 2 release official"],"session_id":"5b160d4d-4dce-4ed3-a459-2eafdd467a47"}'
deno task engine:run 'parallel#fetch' --body '{"urls":["https://deno.com/blog/v2.0"],"objective":"Deno 2 release features","session_id":"5b160d4d-4dce-4ed3-a459-2eafdd467a47"}'
```

Generate a new `session_id` once per conversation and reuse it for related
search and fetch calls. `model_name` is optional; supply the exact runtime model
identifier only when known. `parallel#fetch` accepts up to 20 HTTP/HTTPS URLs,
an optional objective (up to 200 characters), `search_queries`, `full_content`
and `allow_live_fetch`. Prefer focused excerpts; full markdown can be large.
Fetch returns successful results and per-URL errors.

The connector returns each tool's `structuredContent` directly. Both endpoints
use the FREE usage model and do not forward vendor credentials. Each engine run
opens its own Streamable HTTP MCP session and terminates it best-effort. JSON
and finite SSE POST replies are supported. There is no persistent stream, SSE
reconnection or automatic retry. Request timeout is 30 seconds.

Replay tests run offline. The two success fixtures are trimmed recordings of
anonymous calls on 2026-10-11, with transport session IDs replaced by a fixed
placeholder. Protocol failures and SSE responses use synthetic test responses.
38 changes: 38 additions & 0 deletions connectors/parallel/endpoints/fetch/endpoint.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { defineEndpoint } from "@shared/core";
import { z } from "zod";
import { zFetchBody } from "./schema/inputs.ts";

export default defineEndpoint({
endpoint: "/fetch",
meta: {
displayName: "Parallel Web Fetch",
summary:
"Extract focused excerpts or full markdown from up to 20 URLs, free without a key.",
description:
"Fetch specific HTTP/HTTPS pages and return focused excerpts " +
"with source URLs, plus per-URL errors. Supply an objective and " +
"optional search queries to focus extraction. Request full_content " +
"only when you need complete markdown; responses can be large.",
categories: ["web-scraping"],
},
request: { method: "POST", path: "/mcp" },
input: {
schema: {
body: zFetchBody.extend({
urls: z.array(
z.url({ protocol: /^https?$/ }).regex(/^https?:\/\//),
).min(1).max(20),
objective: zFetchBody.shape.objective.unwrap().unwrap().max(200)
.nullable().optional(),
}),
},
toRequest: ({ data }) => ({
body: {
jsonrpc: "2.0",
id: 2,
method: "tools/call",
params: { name: "web_fetch", arguments: data.input.body ?? {} },
},
}),
},
});
20 changes: 20 additions & 0 deletions connectors/parallel/endpoints/fetch/schema/inputs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { z } from "zod";
import { conversationFields } from "../../../schema/common.ts";

// Mirror of web_fetch tools/list (2026-10-11); limits live at the binding.
export const zFetchBody = z.strictObject({
urls: z.array(z.string()).describe("HTTP/HTTPS URLs to extract, up to 20."),
objective: z.string().nullable().describe(
"What to extract; up to 200 characters.",
).optional(),
search_queries: z.array(z.string()).nullable().describe(
"Queries that surfaced these URLs.",
).optional(),
full_content: z.boolean().describe(
"Request full markdown as well as focused excerpts; may be large.",
).optional(),
allow_live_fetch: z.boolean().describe(
"Allow fresh live fetching (server default true). False uses cached content only.",
).optional(),
...conversationFields,
});
36 changes: 36 additions & 0 deletions connectors/parallel/endpoints/search/endpoint.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { defineEndpoint } from "@shared/core";
import { zSearchBody } from "./schema/inputs.ts";

export default defineEndpoint({
endpoint: "/search",
meta: {
displayName: "Parallel Web Search",
summary:
"Search the web for ranked sources and focused excerpts, free without a key.",
description:
"Search the live web in Fast mode with a natural-language " +
"objective and related keyword queries. Returns ranked URLs, titles " +
"and excerpts. Use parallel#fetch when excerpts are insufficient " +
"or you need a specific page's content.",
categories: ["web-search"],
},
request: { method: "POST", path: "/mcp" },
input: {
schema: {
body: zSearchBody.extend({
search_queries: zSearchBody.shape.search_queries.min(1),
}),
},
toRequest: ({ data }) => ({
body: {
jsonrpc: "2.0",
id: 2,
method: "tools/call",
params: {
name: "web_search",
arguments: data.input.body ?? {},
},
},
}),
},
});
13 changes: 13 additions & 0 deletions connectors/parallel/endpoints/search/schema/inputs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { z } from "zod";
import { conversationFields } from "../../../schema/common.ts";

// Mirror of web_search tools/list (2026-10-11), no authored defaults.
export const zSearchBody = z.strictObject({
objective: z.string().describe(
"Focused natural-language search objective.",
),
search_queries: z.array(z.string()).describe(
"Related keyword queries; at least one, preferably 2–3.",
),
...conversationFields,
});
169 changes: 169 additions & 0 deletions connectors/parallel/fixtures/fetch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
{
"name": "parallel-fetch-recorded",
"description": "Recorded anonymous MCP initialization, notification, tool result and session termination, 2026-10-11. Session header replaced with fixture-session; responses trimmed and scrubbed.",
"calls": [
{
"req": {
"method": "POST",
"url": "https://search.parallel.ai/mcp",
"body": {
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2025-03-26",
"capabilities": {},
"clientInfo": {
"name": "monid",
"version": "1.0.0"
}
}
}
},
"res": {
"status": 200,
"headers": {
"mcp-session-id": "fixture-session"
},
"body": {
"jsonrpc": "2.0",
"id": 1,
"result": {
"protocolVersion": "2025-03-26",
"capabilities": {
"experimental": {},
"prompts": {
"listChanged": false
},
"resources": {
"subscribe": false,
"listChanged": false
},
"tools": {
"listChanged": false
}
},
"serverInfo": {
"name": "Parallel Web Search MCP Server",
"version": "1.27.0",
"websiteUrl": "https://parallel.ai",
"icons": [
{
"src": "https://assets.parallel.ai/dark-parallel-avatar-270.svg",
"mimeType": "image/svg+xml",
"theme": "light"
},
{
"src": "https://assets.parallel.ai/dark-parallel-avatar-270.png",
"mimeType": "image/png",
"sizes": [
"270x270"
],
"theme": "light"
}
]
},
"instructions": "\nThe search and fetch tools are read-only, idempotent, and safe to call\nconcurrently. When several independent search or fetch objectives are already\nknown, issue one tool call per objective concurrently; do not wait for one\nresult before starting another. Batch related search queries or URLs that share\na common objective into one call.\n\n\n\nUse web_search first for most factual, current-information, research,\ncomparison, documentation, and troubleshooting questions.\n\nThe web_search results includ"
}
}
}
},
{
"req": {
"method": "POST",
"url": "https://search.parallel.ai/mcp",
"body": {
"jsonrpc": "2.0",
"method": "notifications/initialized"
}
},
"res": {
"status": 202,
"headers": {
"mcp-session-id": "fixture-session"
},
"body": null
}
},
{
"req": {
"method": "POST",
"url": "https://search.parallel.ai/mcp",
"body": {
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "web_fetch",
"arguments": {
"urls": [
"https://deno.com/blog/v2.0"
],
"objective": "Deno 2 release features",
"session_id": "c60dac7f-d33a-4643-bd06-7f7bf514b1f8"
}
}
}
},
"res": {
"status": 200,
"headers": {
"mcp-session-id": "fixture-session"
},
"body": {
"jsonrpc": "2.0",
"id": 2,
"result": {
"_meta": {
"parallel/usage": [
{
"name": "sku_extract_excerpts",
"count": 1,
"cost_usd": 0.001
}
]
},
"content": [
{
"type": "text",
"text": "{\n \"extract_id\": \"extract_35ba304a689e504f491225a3be81a805\",\n \"results\": [\n {\n \"url\": \"https://deno.com/blog/v2.0\",\n \"title\": \"Announcing Deno 2 | Deno\",\n \"publish_date\": null,\n \"excerpts\": [\n \"Introducing celld: self-hosted, distributed Durable Objects\\n[Learn more ->](https://celld.dev/)\\nDismiss\\n[](https://deno.com/)\\n`⌘K`\\n`↑↓ Up or down to` navigate `↵ Enter to` select `ESC Escape to` close Search powered by Orama Search powered by Orama\\n`⌘K`\\n\\n...\\n\\n# An"
}
],
"structuredContent": {
"extract_id": "extract_35ba304a689e504f491225a3be81a805",
"results": [
{
"url": "https://deno.com/blog/v2.0",
"title": "Announcing Deno 2 | Deno",
"publish_date": null,
"excerpts": [
"Introducing celld: self-hosted, distributed Durable Objects\n[Learn more ->](https://celld.dev/)\nDismiss\n[](https://deno.com/)\n`⌘K`\n`↑↓ Up or down to` navigate `↵ Enter to` select `ESC Escape to` close Search powered by Orama Search powered by Orama\n`⌘K`\n\n...\n\n# Announcing Deno 2\n## Announcing Deno 2\n* **Native TypeScript support**\n* **Built on web standards** : Promises, fetch, and ES Modules\n* **Batteries included** : builtin formatter, linter, type checker, test\n framework, compile to executa"
],
"full_content": null
}
],
"errors": [],
"warnings": null,
"metadata": null,
"session_id": "c60dac7f-d33a-4643-bd06-7f7bf514b1f8"
},
"isError": false
}
}
}
},
{
"req": {
"method": "DELETE",
"url": "https://search.parallel.ai/mcp"
},
"res": {
"status": 200,
"headers": {
"mcp-session-id": "fixture-session"
},
"body": null
}
}
]
}
Loading