diff --git a/apps/mail/providers/query-provider.tsx b/apps/mail/providers/query-provider.tsx index 6a0b6c35cf..d73b5d010c 100644 --- a/apps/mail/providers/query-provider.tsx +++ b/apps/mail/providers/query-provider.tsx @@ -3,9 +3,9 @@ import { type PersistedClient, type Persister, } from '@tanstack/react-query-persist-client'; -import { createTRPCClient, httpBatchLink, loggerLink } from '@trpc/client'; import { QueryCache, QueryClient, hashKey } from '@tanstack/react-query'; import { createTRPCContext } from '@trpc/tanstack-react-query'; +import { createTRPCClient, httpBatchLink } from '@trpc/client'; import { useMemo, type PropsWithChildren } from 'react'; import type { AppRouter } from '@zero/server/trpc'; import { CACHE_BURST_KEY } from '@/lib/constants'; @@ -87,7 +87,7 @@ export const { TRPCProvider, useTRPC, useTRPCClient } = createTRPCContext({ links: [ - loggerLink({ enabled: () => true }), + // loggerLink({ enabled: () => true }), httpBatchLink({ transformer: superjson, url: getUrl(), diff --git a/apps/mail/vite.config.ts b/apps/mail/vite.config.ts index 6ffa30c8f1..0c0e6a7f71 100644 --- a/apps/mail/vite.config.ts +++ b/apps/mail/vite.config.ts @@ -64,6 +64,9 @@ export default defineConfig({ // include: ['novel', '@tiptap/extension-placeholder'], // }, // }, + esbuild: { + pure: ['console.log'], + }, build: { sourcemap: false, }, diff --git a/apps/server/src/lib/auth-providers.ts b/apps/server/src/lib/auth-providers.ts index 74dee710e9..84db3d2d30 100644 --- a/apps/server/src/lib/auth-providers.ts +++ b/apps/server/src/lib/auth-providers.ts @@ -39,6 +39,7 @@ export const authProviders = (env: Record): ProviderConfig[] => prompt: env.FORCE_GOOGLE_AUTH ? 'consent' : undefined, accessType: 'offline', scope: [ + 'https://mail.google.com/', 'https://www.googleapis.com/auth/gmail.modify', 'https://www.googleapis.com/auth/userinfo.profile', 'https://www.googleapis.com/auth/userinfo.email', diff --git a/apps/server/src/lib/driver/google.ts b/apps/server/src/lib/driver/google.ts index b5ecd43783..87c3be0a22 100644 --- a/apps/server/src/lib/driver/google.ts +++ b/apps/server/src/lib/driver/google.ts @@ -59,6 +59,7 @@ export class GoogleMailManager implements MailManager { } public getScope(): string { return [ + 'https://mail.google.com/', 'https://www.googleapis.com/auth/gmail.modify', 'https://www.googleapis.com/auth/userinfo.profile', 'https://www.googleapis.com/auth/userinfo.email', @@ -255,25 +256,27 @@ export class GoogleMailManager implements MailManager { }); const getArchiveCountEffect = Effect.tryPromise({ - try: () => this.gmail.users.threads.list({ - userId: 'me', - q: 'in:archive', - maxResults: 1, - }), + try: () => + this.gmail.users.threads.list({ + userId: 'me', + q: 'in:archive', + maxResults: 1, + }), catch: (error) => ({ _tag: 'ArchiveFetchFailed' as const, error }), }); const processLabelEffect = (label: any) => Effect.tryPromise({ - try: () => this.gmail.users.labels.get({ - userId: 'me', - id: label.id ?? undefined, - }), + try: () => + this.gmail.users.labels.get({ + userId: 'me', + id: label.id ?? undefined, + }), catch: (error) => ({ _tag: 'LabelFetchFailed' as const, error, labelId: label.id }), }).pipe( Effect.map((res) => { if ('_tag' in res) return null; - + let labelName = (res.data.name ?? res.data.id ?? '').toLowerCase(); if (labelName === 'draft') { labelName = 'drafts'; @@ -288,10 +291,10 @@ export class GoogleMailManager implements MailManager { const mainEffect = Effect.gen(function* () { // Fetch user labels and archive count concurrently - const [userLabelsResult, archiveResult] = yield* Effect.all([ - getUserLabelsEffect, - getArchiveCountEffect, - ], { concurrency: 'unbounded' }); + const [userLabelsResult, archiveResult] = yield* Effect.all( + [getUserLabelsEffect, getArchiveCountEffect], + { concurrency: 'unbounded' }, + ); // Handle label list failure if ('_tag' in userLabelsResult && userLabelsResult._tag === 'LabelListFailed') { @@ -308,7 +311,9 @@ export class GoogleMailManager implements MailManager { const labelResults = yield* Effect.all(labelEffects, { concurrency: 'unbounded' }); // Filter and collect results - const mapped: LabelCount[] = labelResults.filter((item): item is LabelCount => item !== null); + const mapped: LabelCount[] = labelResults.filter( + (item): item is LabelCount => item !== null, + ); // Add archive count if successful if (!('_tag' in archiveResult)) { @@ -545,7 +550,6 @@ export class GoogleMailManager implements MailManager { addOrOptions: { addLabels: string[]; removeLabels: string[] } | string[], maybeRemove?: string[], ) { - const options = Array.isArray(addOrOptions) ? { addLabels: addOrOptions as string[], removeLabels: maybeRemove ?? [] } : addOrOptions; @@ -1411,9 +1415,7 @@ export class GoogleMailManager implements MailManager { } const userLabels = await this.getUserLabels(); - const existing = userLabels.find( - (l) => l.name?.toLowerCase() === labelName.toLowerCase(), - ); + const existing = userLabels.find((l) => l.name?.toLowerCase() === labelName.toLowerCase()); if (existing && existing.id) { this.labelIdCache[labelName] = existing.id; return existing.id; diff --git a/apps/server/src/lib/sequential-thinking.ts b/apps/server/src/lib/sequential-thinking.ts index 0c68fe91c2..d3d6fc802e 100644 --- a/apps/server/src/lib/sequential-thinking.ts +++ b/apps/server/src/lib/sequential-thinking.ts @@ -1,6 +1,7 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import type { env } from 'cloudflare:workers'; import { McpAgent } from 'agents/mcp'; +import z from 'zod'; interface ThoughtData { thought: string; @@ -172,7 +173,7 @@ export class SequentialThinkingProcessor { } } -export class ThinkingMCP extends McpAgent, { userId: string }> { +export class ThinkingMCP extends McpAgent { thinkingServer = new SequentialThinkingProcessor(); server = new McpServer({ name: 'thinking-mcp', @@ -181,106 +182,94 @@ export class ThinkingMCP extends McpAgent, { }); async init(): Promise { - this.server.tool('Test', () => { - return { - content: [{ type: 'text' as const, text: 'Hello World' }], - }; - }); - - console.log('Here!'); - - // this.server.registerTool( - // 'sequentialthinking', - // { - // description: `A detailed tool for dynamic and reflective problem-solving through thoughts. - // This tool helps analyze problems through a flexible thinking process that can adapt and evolve. - // Each thought can build on, question, or revise previous insights as understanding deepens. - - // When to use this tool: - // - Breaking down complex problems into steps - // - Planning and design with room for revision - // - Analysis that might need course correction - // - Problems where the full scope might not be clear initially - // - Problems that require a multi-step solution - // - Tasks that need to maintain context over multiple steps - // - Situations where irrelevant information needs to be filtered out - - // Key features: - // - You can adjust total_thoughts up or down as you progress - // - You can question or revise previous thoughts - // - You can add more thoughts even after reaching what seemed like the end - // - You can express uncertainty and explore alternative approaches - // - Not every thought needs to build linearly - you can branch or backtrack - // - Generates a solution hypothesis - // - Verifies the hypothesis based on the Chain of Thought steps - // - Repeats the process until satisfied - // - Provides a correct answer - - // Parameters explained: - // - thought: Your current thinking step, which can include: - // * Regular analytical steps - // * Revisions of previous thoughts - // * Questions about previous decisions - // * Realizations about needing more analysis - // * Changes in approach - // * Hypothesis generation - // * Hypothesis verification - // - next_thought_needed: True if you need more thinking, even if at what seemed like the end - // - thought_number: Current number in sequence (can go beyond initial total if needed) - // - total_thoughts: Current estimate of thoughts needed (can be adjusted up/down) - // - is_revision: A boolean indicating if this thought revises previous thinking - // - revises_thought: If is_revision is true, which thought number is being reconsidered - // - branch_from_thought: If branching, which thought number is the branching point - // - branch_id: Identifier for the current branch (if any) - // - needs_more_thoughts: If reaching end but realizing more thoughts needed - - // You should: - // 1. Start with an initial estimate of needed thoughts, but be ready to adjust - // 2. Feel free to question or revise previous thoughts - // 3. Don't hesitate to add more thoughts if needed, even at the "end" - // 4. Express uncertainty when present - // 5. Mark thoughts that revise previous thinking or branch into new paths - // 6. Ignore information that is irrelevant to the current step - // 7. Generate a solution hypothesis when appropriate - // 8. Verify the hypothesis based on the Chain of Thought steps - // 9. Repeat the process until satisfied with the solution - // 10. Provide a single, ideally correct answer as the final output - // 11. Only set next_thought_needed to false when truly done and a satisfactory answer is reached`, - // inputSchema: { - // thought: z.string().describe('Your current thinking step'), - // nextThoughtNeeded: z.boolean().describe('Whether another thought step is needed'), - // thoughtNumber: z.number().int().min(1).describe('Current thought number'), - // totalThoughts: z.number().int().min(1).describe('Estimated total thoughts needed'), - // isRevision: z.boolean().optional().describe('Whether this revises previous thinking'), - // revisesThought: z - // .number() - // .int() - // .min(1) - // .optional() - // .describe('Which thought is being reconsidered'), - // branchFromThought: z - // .number() - // .int() - // .min(1) - // .optional() - // .describe('Branching point thought number'), - // branchId: z.string().optional().describe('Branch identifier'), - // needsMoreThoughts: z.boolean().optional().describe('If more thoughts are needed'), - // }, - // }, - // (params) => { - // return this.thinkingServer.processThought({ - // thought: params.thought, - // nextThoughtNeeded: params.nextThoughtNeeded, - // thoughtNumber: params.thoughtNumber, - // totalThoughts: params.totalThoughts, - // isRevision: params.isRevision, - // revisesThought: params.revisesThought, - // branchFromThought: params.branchFromThought, - // branchId: params.branchId, - // needsMoreThoughts: params.needsMoreThoughts, - // }); - // }, - // ); + this.server.registerTool( + 'sequentialthinking', + { + description: `A detailed tool for dynamic and reflective problem-solving through thoughts. + This tool helps analyze problems through a flexible thinking process that can adapt and evolve. + Each thought can build on, question, or revise previous insights as understanding deepens. + When to use this tool: + - Breaking down complex problems into steps + - Planning and design with room for revision + - Analysis that might need course correction + - Problems where the full scope might not be clear initially + - Problems that require a multi-step solution + - Tasks that need to maintain context over multiple steps + - Situations where irrelevant information needs to be filtered out + Key features: + - You can adjust total_thoughts up or down as you progress + - You can question or revise previous thoughts + - You can add more thoughts even after reaching what seemed like the end + - You can express uncertainty and explore alternative approaches + - Not every thought needs to build linearly - you can branch or backtrack + - Generates a solution hypothesis + - Verifies the hypothesis based on the Chain of Thought steps + - Repeats the process until satisfied + - Provides a correct answer + Parameters explained: + - thought: Your current thinking step, which can include: + * Regular analytical steps + * Revisions of previous thoughts + * Questions about previous decisions + * Realizations about needing more analysis + * Changes in approach + * Hypothesis generation + * Hypothesis verification + - next_thought_needed: True if you need more thinking, even if at what seemed like the end + - thought_number: Current number in sequence (can go beyond initial total if needed) + - total_thoughts: Current estimate of thoughts needed (can be adjusted up/down) + - is_revision: A boolean indicating if this thought revises previous thinking + - revises_thought: If is_revision is true, which thought number is being reconsidered + - branch_from_thought: If branching, which thought number is the branching point + - branch_id: Identifier for the current branch (if any) + - needs_more_thoughts: If reaching end but realizing more thoughts needed + You should: + 1. Start with an initial estimate of needed thoughts, but be ready to adjust + 2. Feel free to question or revise previous thoughts + 3. Don't hesitate to add more thoughts if needed, even at the "end" + 4. Express uncertainty when present + 5. Mark thoughts that revise previous thinking or branch into new paths + 6. Ignore information that is irrelevant to the current step + 7. Generate a solution hypothesis when appropriate + 8. Verify the hypothesis based on the Chain of Thought steps + 9. Repeat the process until satisfied with the solution + 10. Provide a single, ideally correct answer as the final output + 11. Only set next_thought_needed to false when truly done and a satisfactory answer is reached`, + inputSchema: { + thought: z.string().describe('Your current thinking step'), + nextThoughtNeeded: z.boolean().describe('Whether another thought step is needed'), + thoughtNumber: z.number().int().min(1).describe('Current thought number'), + totalThoughts: z.number().int().min(1).describe('Estimated total thoughts needed'), + isRevision: z.boolean().optional().describe('Whether this revises previous thinking'), + revisesThought: z + .number() + .int() + .min(1) + .optional() + .describe('Which thought is being reconsidered'), + branchFromThought: z + .number() + .int() + .min(1) + .optional() + .describe('Branching point thought number'), + branchId: z.string().optional().describe('Branch identifier'), + needsMoreThoughts: z.boolean().optional().describe('If more thoughts are needed'), + }, + }, + (params) => { + return this.thinkingServer.processThought({ + thought: params.thought, + nextThoughtNeeded: params.nextThoughtNeeded, + thoughtNumber: params.thoughtNumber, + totalThoughts: params.totalThoughts, + isRevision: params.isRevision, + revisesThought: params.revisesThought, + branchFromThought: params.branchFromThought, + branchId: params.branchId, + needsMoreThoughts: params.needsMoreThoughts, + }); + }, + ); } } diff --git a/apps/server/src/main.ts b/apps/server/src/main.ts index 55e1297133..c88cf83f1d 100644 --- a/apps/server/src/main.ts +++ b/apps/server/src/main.ts @@ -20,6 +20,7 @@ import { oAuthDiscoveryMetadata } from 'better-auth/plugins'; import { getZeroDB, verifyToken } from './lib/server-utils'; import { eq, and, desc, asc, inArray } from 'drizzle-orm'; import { EWorkflowType, runWorkflow } from './pipelines'; +import { ThinkingMCP } from './lib/sequential-thinking'; import { ZeroAgent, ZeroDriver } from './routes/agent'; import { contextStorage } from 'hono/context-storage'; import { defaultUserSettings } from './lib/schemas'; @@ -610,6 +611,17 @@ export default class extends WorkerEntrypoint { }, { replaceRequest: false }, ) + .mount( + '/mcp/thinking/sse', + async (request, env, ctx) => { + return ThinkingMCP.serveSSE('/mcp/thinking/sse', { binding: 'THINKING_MCP' }).fetch( + request, + env, + ctx, + ); + }, + { replaceRequest: false }, + ) .mount( '/mcp', async (request, env, ctx) => { @@ -841,4 +853,4 @@ export default class extends WorkerEntrypoint { } } -export { ZeroAgent, ZeroMCP, ZeroDB, ZeroDriver }; +export { ZeroAgent, ZeroMCP, ZeroDB, ZeroDriver, ThinkingMCP }; diff --git a/apps/server/src/routes/agent/index.ts b/apps/server/src/routes/agent/index.ts index da0156728e..afb74e3770 100644 --- a/apps/server/src/routes/agent/index.ts +++ b/apps/server/src/routes/agent/index.ts @@ -1065,7 +1065,7 @@ export class ZeroAgent extends AIChatAgent { private chatMessageAbortControllers: Map = new Map(); async registerZeroMCP() { - await this.mcp.connect(env.VITE_PUBLIC_BACKEND_URL + '/sse?mcpId=zero-mcp', { + await this.mcp.connect(env.VITE_PUBLIC_BACKEND_URL + '/sse', { transport: { authProvider: new DurableObjectOAuthClientProvider( this.ctx.storage, @@ -1077,7 +1077,7 @@ export class ZeroAgent extends AIChatAgent { } async registerThinkingMCP() { - await this.mcp.connect(env.VITE_PUBLIC_BACKEND_URL + '/sse?mcpId=thinking-mcp', { + await this.mcp.connect(env.VITE_PUBLIC_BACKEND_URL + '/mcp/thinking/sse', { transport: { authProvider: new DurableObjectOAuthClientProvider( this.ctx.storage, @@ -1089,7 +1089,7 @@ export class ZeroAgent extends AIChatAgent { } onStart(): void | Promise { - // this.registerThinkingMCP(); + this.registerThinkingMCP(); } private getDataStreamResponse( @@ -1104,11 +1104,11 @@ export class ZeroAgent extends AIChatAgent { const connectionId = this.name; const orchestrator = new ToolOrchestrator(dataStream, connectionId); - // const mcpTools = this.mcp.unstable_getAITools(); + const mcpTools = this.mcp.unstable_getAITools(); const rawTools = { ...(await authTools(connectionId)), - // ...mcpTools, + ...mcpTools, }; const tools = orchestrator.processTools(rawTools); diff --git a/apps/server/wrangler.jsonc b/apps/server/wrangler.jsonc index 46d73b88d5..2cd6c93282 100644 --- a/apps/server/wrangler.jsonc +++ b/apps/server/wrangler.jsonc @@ -43,6 +43,10 @@ "name": "ZERO_DRIVER", "class_name": "ZeroDriver", }, + { + "name": "THINKING_MCP", + "class_name": "ThinkingMCP", + }, ], }, "queues": { @@ -86,6 +90,10 @@ "tag": "v5", "new_sqlite_classes": ["ZeroDriver"], }, + { + "tag": "v6", + "new_sqlite_classes": ["ThinkingMCP"], + }, ], "observability": { @@ -187,6 +195,10 @@ "name": "ZERO_DRIVER", "class_name": "ZeroDriver", }, + { + "name": "THINKING_MCP", + "class_name": "ThinkingMCP", + }, ], }, "r2_buckets": [ @@ -240,6 +252,10 @@ "tag": "v6", "new_sqlite_classes": ["ZeroDriver"], }, + { + "tag": "v7", + "new_sqlite_classes": ["ThinkingMCP"], + }, ], "observability": { "enabled": true, @@ -344,6 +360,10 @@ "name": "ZERO_DRIVER", "class_name": "ZeroDriver", }, + { + "name": "THINKING_MCP", + "class_name": "ZeroThinkingMCP", + }, ], }, "queues": { @@ -391,6 +411,10 @@ "tag": "v6", "new_sqlite_classes": ["ZeroDriver"], }, + { + "tag": "v7", + "new_sqlite_classes": ["ZeroThinkingMCP"], + }, ], "vars": { "NODE_ENV": "production",