diff --git a/README.md b/README.md index df4fe215..0024e968 100644 --- a/README.md +++ b/README.md @@ -103,6 +103,26 @@ from contacts that are not on this allowlist. The connector without them permits outbound token access but does not forward incoming messages to OpenInstinct. +## Public web search + +The root agent can search the web and read public pages through +[Parallel Search MCP](https://docs.parallel.ai/integrations/mcp/search-mcp). +The native Eve connection in `agent/connections/parallel.ts` is discovered +automatically. No Parallel account, API key, OAuth setup, or extra package is +required. Free anonymous access has lower rate limits and is intended for +exploration and light use. + +The agent discovers the connection with `connection_search`, then calls +`parallel__web_search` or `parallel__web_fetch`. Only those two tools are +allowed. Existing gateway search remains available. Interactive browsing, +sign-in, forms, and purchases still belong to the browser worker. + +Search terms and requested URLs are sent to Parallel. Use this connection only +for public information, never private messages, account data, vault contents, +credentials, or authenticated or signed URLs. Retrieved pages remain +untrusted content. Restart development or rebuild the application after +changing connection files. + ## Google Workspace connection OpenInstinct can use a user's Gmail, Calendar, and read-only Contacts through a diff --git a/agent/connections/parallel.ts b/agent/connections/parallel.ts new file mode 100644 index 00000000..e1b90081 --- /dev/null +++ b/agent/connections/parallel.ts @@ -0,0 +1,8 @@ +import { defineMcpClientConnection } from "eve/connections"; + +export default defineMcpClientConnection({ + url: "https://search.parallel.ai/mcp", + description: + "Search the public web and extract content from public URLs with Parallel. Find current facts and sources without an API key. Send only public information, never private account data or secrets.", + tools: { allow: ["web_search", "web_fetch"] }, +}); diff --git a/agent/instructions.md b/agent/instructions.md index 34d0c4fa..c38e5764 100644 --- a/agent/instructions.md +++ b/agent/instructions.md @@ -37,6 +37,8 @@ The main conversation is the control plane. Coordinate the user's work there, de - Recover from a browser failure with at most two materially different tactics. If neither works, stop promptly and report the last verified state and exact blocker instead of leaving the task running. - Prefer the narrowest capable integration: root vault setup for non-secret coordination, connected tools for their supported services, `web_search` for public discovery and current facts, `web_fetch` for reading a known public page, and `worker` only for work that requires browser interaction or browser state. - Perform public research, source discovery, comparisons, and current-information lookups directly with `web_search`. Never delegate a search-only task or use a browser to visit a search engine or browse search-result pages. When a known public URL only needs to be read, try `web_fetch` before browser automation. +- The `parallel` connection offers public web search and public page extraction. Discover `parallel__web_search` and `parallel__web_fetch` with `connection_search` when those capabilities fit the task. Keep source links in factual answers, and report search failures or rate limits instead of inventing results. +- Send only public search terms and public URLs to `parallel`. Never send credentials, vault data, private messages, private account content, or authenticated or signed URLs to public search or extraction tools. - Prefer `google_workspace_read` and `google_workspace_write` over browser automation for connected Gmail, Calendar, and Contacts work. Never ask for Google tokens or credentials in chat. If authorization is required, let the connection surface its sign-in challenge. - Use exact Gmail message IDs for reversible inbox updates. Before sending email or creating a calendar event, make the recipients, content, timing, attendees, and other material fields explicit in the approval request. - Keep the user's constraints intact while delegating, comparing alternatives, recovering from failures, and synthesizing results. diff --git a/knip.config.ts b/knip.config.ts index eca9ef5c..462f2f39 100644 --- a/knip.config.ts +++ b/knip.config.ts @@ -3,6 +3,7 @@ import type { KnipConfig } from "knip"; export default { entry: [ "agent/channels/**/*.ts", + "agent/connections/**/*.ts", "agent/hooks/**/*.ts", "agent/instructions/**/*.ts", "agent/memory/**/*.ts", diff --git a/tests/parallel-connection.test.ts b/tests/parallel-connection.test.ts new file mode 100644 index 00000000..f8105566 --- /dev/null +++ b/tests/parallel-connection.test.ts @@ -0,0 +1,24 @@ +import { describe, expect, it } from "vitest"; +import parallel from "../agent/connections/parallel"; + +describe("Parallel public web connection", () => { + it("uses the free MCP endpoint", () => { + expect(parallel.url).toBe("https://search.parallel.ai/mcp"); + }); + + it("exposes only public search and page extraction", () => { + expect(parallel.tools).toEqual({ + allow: ["web_search", "web_fetch"], + }); + }); + + it("does not attach credentials or session-derived arguments", () => { + expect(parallel.auth).toBeUndefined(); + expect(parallel.headers).toBeUndefined(); + expect(parallel.toolCall).toBeUndefined(); + }); + + it("describes the public-only data boundary during discovery", () => { + expect(parallel.description).toContain("Send only public information"); + }); +});