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
3 changes: 3 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ GOOGLE_CONNECTOR_UID=
# optional click-to-message shortcut in the workspace.
LINQ_CONNECTOR=
LINQ_PHONE_NUMBER=
# Optional You.com web search connection. Set to give the agent You.com MCP
# search tools; unset, the agent keeps its built-in search behavior.
YOU_API_KEY=
# Development benchmarks only (pnpm bench:browser).
BROWSER_BENCH_LABEL=self-hosted
BROWSER_BENCH_REPETITIONS=1
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,23 @@ Gotchas:
- Sending email and creating confirmed calendar events always require approval.
Calendar events with attendees send Google invitations.

## You.com web search

OpenInstinct can search the web through [You.com](https://you.com) by setting
one environment variable. With `YOU_API_KEY` set, the agent connects to the
You.com MCP server and gains its search tools for public research and
current facts; eve lists the connection and its tools automatically. Without
the key, nothing changes: the connection is omitted entirely and the agent
keeps its built-in search behavior.

```bash
vercel env add YOU_API_KEY production --value <you-com-api-key> --yes
```

Keys are available from the [You.com API dashboard](https://api.you.com).
The key is sent only as a bearer token to `https://api.you.com/mcp`; it never
enters conversation history or the model context.

## Local development

The **Deploy with Vercel** flow above is the simplest way to run OpenInstinct. It
Expand Down
19 changes: 19 additions & 0 deletions agent/connections/youcom.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { defineDynamic, defineMcpClientConnection } from "eve/connections";
import { env } from "@shared/environment";

export default defineDynamic({
events: {
"session.started": () => {
const apiKey = env.YOU_API_KEY;
return apiKey === undefined
? null
: defineMcpClientConnection({
auth: { getToken: async () => ({ token: apiKey }) },
description:
"You.com web search and research: current information, facts, news, and primary sources. Use for public research, source discovery, comparisons, and verifying time-sensitive claims.",
instanceKey: "youcom",
url: "https://api.you.com/mcp",
});
},
},
});
1 change: 1 addition & 0 deletions knip.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
3 changes: 3 additions & 0 deletions shared/environment/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,9 @@ export const env = createEnv({
"LINQ_PHONE_NUMBER must use E.164 format"
)
.optional(),
// Optional You.com web search connection for the agent. Set to expose the
// You.com MCP search tools; unset, the agent keeps its built-in behavior.
YOU_API_KEY: requiredValue.optional(),
NODE_ENV: z
.enum(["development", "production", "test"])
.default("production"),
Expand Down
99 changes: 99 additions & 0 deletions tests/agent/connections/youcom.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import type { McpClientConnectionDefinition } from "eve/connections";
import type { DynamicResolveContext } from "eve/tools";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";

const requiredEnvironment = {
BETTER_AUTH_SECRET: "test-auth-secret-0123456789abcdefghijklmnop",
BETTER_AUTH_URL: "https://example.com",
BLOB_READ_WRITE_TOKEN: "vercel_blob_rw_test",
DATABASE_URL: "postgresql://user:***@example.com/database",
KERNEL_API_KEY: "test-kernel-key",
SECRET_ENCRYPTION_KEY: "AQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQE=",
};

const resolveContext = {
model: null,
channel: { kind: "channel:linq", metadata: {} },
messages: [],
session: {
auth: {
current: {
attributes: { workspaceId: "personal:workspace" },
authenticator: "linq-message",
principalId: "user-1",
principalType: "user",
},
initiator: null,
},
id: "session-1",
},
} satisfies DynamicResolveContext;

describe("youcom connection", () => {
beforeEach(() => {
vi.resetModules();
for (const [name, value] of Object.entries(requiredEnvironment)) {
vi.stubEnv(name, value);
}
});

afterEach(() => {
vi.unstubAllEnvs();
});

it("is omitted without a You.com API key", async () => {
vi.stubEnv("YOU_API_KEY", "");

const { default: youcom } = await import("@agent/connections/youcom");
const resolve = youcom.events["session.started"];
expect(resolve).toBeDefined();
if (!resolve) return;

expect(await resolve({}, resolveContext)).toBeNull();
});

it("exposes the You.com MCP search connection with an API key", async () => {
vi.stubEnv("YOU_API_KEY", "test-youcom-key");

const { default: youcom } = await import("@agent/connections/youcom");
const resolve = youcom.events["session.started"];
expect(resolve).toBeDefined();
if (!resolve) return;

const resolved = await resolve({}, resolveContext);
// SAFETY: The resolver returns a single connection definition, so the
// dynamic result is the MCP connection definition this module builds.
// oxlint-disable-next-line typescript/no-unsafe-type-assertion -- This test asserts the exact connection definition the youcom resolver builds.
const connection = resolved as McpClientConnectionDefinition;
expect(connection).toMatchObject({
instanceKey: "youcom",
url: "https://api.you.com/mcp",
});
expect(connection.description).toContain("You.com web search");
});

it("sends the configured API key as the bearer token", async () => {
vi.stubEnv("YOU_API_KEY", "test-youcom-key");

const { default: youcom } = await import("@agent/connections/youcom");
const resolve = youcom.events["session.started"];
expect(resolve).toBeDefined();
if (!resolve) return;

const resolved = await resolve({}, resolveContext);
// SAFETY: The resolver returns a single connection definition, so the
// dynamic result is the MCP connection definition this module builds.
// oxlint-disable-next-line typescript/no-unsafe-type-assertion -- This test asserts the exact connection definition the youcom resolver builds.
const connection = resolved as McpClientConnectionDefinition;
const auth = connection.auth;
expect(auth).toBeDefined();
if (!auth || !("getToken" in auth)) return;

await expect(
auth.getToken({
connection: { url: "https://api.you.com/mcp" },
principal: { type: "app" },
})
).resolves.toEqual({ token: "test-youcom-key" });
});
});