Skip to content
Merged
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: 1 addition & 1 deletion apps/docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
"@vercel/og": "^0.6.5",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"drizzle-orm": "^0.44.5",
"drizzle-orm": "^0.45.2",
"fumadocs-core": "16.6.7",
"fumadocs-mdx": "14.2.8",
"fumadocs-openapi": "10.3.13",
Expand Down
13 changes: 5 additions & 8 deletions apps/sim/app/api/mcp/serve/[serverId]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@ import {
isJSONRPCRequest,
type JSONRPCError,
type JSONRPCMessage,
type JSONRPCResponse,
type JSONRPCResultResponse,
type ListToolsResult,
type RequestId,
type Tool,
} from '@modelcontextprotocol/sdk/types.js'
import { db } from '@sim/db'
import { workflow, workflowMcpServer, workflowMcpTool, workspace } from '@sim/db/schema'
Expand Down Expand Up @@ -41,11 +42,11 @@ interface ExecuteAuthContext {
apiKey?: string | null
}

function createResponse(id: RequestId, result: unknown): JSONRPCResponse {
function createResponse(id: RequestId, result: unknown): JSONRPCResultResponse {
return {
jsonrpc: '2.0',
id,
result: result as JSONRPCResponse['result'],
result: result as JSONRPCResultResponse['result'],
}
}

Expand Down Expand Up @@ -235,11 +236,7 @@ async function handleToolsList(id: RequestId, serverId: string): Promise<NextRes

const result: ListToolsResult = {
tools: tools.map((tool) => {
const schema = tool.parameterSchema as {
type?: string
properties?: Record<string, unknown>
required?: string[]
} | null
const schema = tool.parameterSchema as Partial<Tool['inputSchema']> | null
return {
name: tool.toolName,
description: tool.toolDescription || `Execute workflow: ${tool.toolName}`,
Expand Down
13 changes: 5 additions & 8 deletions apps/sim/lib/copilot/tools/mcp/definitions.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,19 @@
export type ToolAnnotations = {
readOnlyHint?: boolean
destructiveHint?: boolean
idempotentHint?: boolean
openWorldHint?: boolean
}
import type { Tool } from '@modelcontextprotocol/sdk/types.js'

export type ToolAnnotations = NonNullable<Tool['annotations']>

export type DirectToolDef = {
name: string
description: string
inputSchema: { type: 'object'; properties?: Record<string, unknown>; required?: string[] }
inputSchema: Tool['inputSchema']
toolId: string
annotations?: ToolAnnotations
}

export type SubagentToolDef = {
name: string
description: string
inputSchema: { type: 'object'; properties?: Record<string, unknown>; required?: string[] }
inputSchema: Tool['inputSchema']
agentId: string
annotations?: ToolAnnotations
}
Expand Down
16 changes: 2 additions & 14 deletions apps/sim/lib/mcp/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,9 +97,7 @@ export class McpClient {
version: '1.0.0',
},
{
capabilities: {
tools: {},
},
capabilities: {},
}
)
}
Expand Down Expand Up @@ -261,21 +259,11 @@ export class McpClient {
}
}

/**
* Check if server has capability
*/
hasCapability(capability: string): boolean {
const serverCapabilities = this.client.getServerCapabilities()
return !!serverCapabilities?.[capability]
}

/**
* Check if the server declared `capabilities.tools.listChanged: true` during initialization.
*/
hasListChangedCapability(): boolean {
const caps = this.client.getServerCapabilities()
const toolsCap = caps?.tools as Record<string, unknown> | undefined
return !!toolsCap?.listChanged
return !!this.client.getServerCapabilities()?.tools?.listChanged
}

/**
Expand Down
139 changes: 31 additions & 108 deletions apps/sim/lib/uploads/server/metadata.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,13 @@
import { db } from '@sim/db'
import { workspaceFiles } from '@sim/db/schema'
import { createLogger } from '@sim/logger'
import { generateId } from '@sim/utils/id'
import { and, eq, isNull } from 'drizzle-orm'
import type { StorageContext } from '../shared/types'

const logger = createLogger('FileMetadata')

export interface FileMetadataRecord {
id: string
key: string
userId: string
workspaceId: string | null
context: string
originalName: string
contentType: string
size: number
deletedAt?: Date | null
uploadedAt: Date
}
export type FileMetadataRecord = typeof workspaceFiles.$inferSelect

export interface FileMetadataInsertOptions {
key: string
Expand All @@ -27,7 +17,8 @@ export interface FileMetadataInsertOptions {
originalName: string
contentType: string
size: number
id?: string // Optional - will generate UUID if not provided
/** Optional — a UUID is generated when omitted. */
id?: string
}

export interface FileMetadataQueryOptions {
Expand All @@ -52,7 +43,7 @@ export async function insertFileMetadata(
.limit(1)

if (existingDeleted.length > 0 && existingDeleted[0].deletedAt) {
await db
const [restored] = await db
.update(workspaceFiles)
.set({
userId,
Expand All @@ -65,18 +56,10 @@ export async function insertFileMetadata(
uploadedAt: new Date(),
})
.where(eq(workspaceFiles.id, existingDeleted[0].id))
.returning()

return {
id: existingDeleted[0].id,
key,
userId,
workspaceId: workspaceId || null,
context,
originalName,
contentType,
size,
deletedAt: null,
uploadedAt: new Date(),
if (restored) {
return restored
}
}

Expand All @@ -87,72 +70,40 @@ export async function insertFileMetadata(
.limit(1)

if (existing.length > 0) {
return {
id: existing[0].id,
key: existing[0].key,
userId: existing[0].userId,
workspaceId: existing[0].workspaceId,
context: existing[0].context,
originalName: existing[0].originalName,
contentType: existing[0].contentType,
size: existing[0].size,
deletedAt: existing[0].deletedAt,
uploadedAt: existing[0].uploadedAt,
}
return existing[0]
}

const fileId = id || (await import('uuid')).v4()
const fileId = id || generateId()

try {
await db.insert(workspaceFiles).values({
id: fileId,
key,
userId,
workspaceId: workspaceId || null,
context,
originalName,
contentType,
size,
deletedAt: null,
uploadedAt: new Date(),
})
const [inserted] = await db
.insert(workspaceFiles)
.values({
id: fileId,
key,
userId,
workspaceId: workspaceId || null,
context,
originalName,
contentType,
size,
deletedAt: null,
uploadedAt: new Date(),
})
.returning()

return {
id: fileId,
key,
userId,
workspaceId: workspaceId || null,
context,
originalName,
contentType,
size,
deletedAt: null,
uploadedAt: new Date(),
}
return inserted
} catch (error) {
if (
(error as any)?.code === '23505' ||
(error instanceof Error && error.message.includes('unique'))
) {
const code = (error as { code?: string } | null)?.code
if (code === '23505' || (error instanceof Error && error.message.includes('unique'))) {
const existingAfterError = await db
.select()
.from(workspaceFiles)
.where(and(eq(workspaceFiles.key, key), isNull(workspaceFiles.deletedAt)))
.limit(1)

if (existingAfterError.length > 0) {
return {
id: existingAfterError[0].id,
key: existingAfterError[0].key,
userId: existingAfterError[0].userId,
workspaceId: existingAfterError[0].workspaceId,
context: existingAfterError[0].context,
originalName: existingAfterError[0].originalName,
contentType: existingAfterError[0].contentType,
size: existingAfterError[0].size,
deletedAt: existingAfterError[0].deletedAt,
uploadedAt: existingAfterError[0].uploadedAt,
}
return existingAfterError[0]
}
}

Expand Down Expand Up @@ -186,22 +137,7 @@ export async function getFileMetadataByKey(
.where(conditions.length > 1 ? and(...conditions) : conditions[0])
.limit(1)

if (!record) {
return null
}

return {
id: record.id,
key: record.key,
userId: record.userId,
workspaceId: record.workspaceId,
context: record.context,
originalName: record.originalName,
contentType: record.contentType,
size: record.size,
deletedAt: record.deletedAt,
uploadedAt: record.uploadedAt,
}
return record ?? null
}

/**
Expand All @@ -225,24 +161,11 @@ export async function getFileMetadataByContext(
conditions.push(isNull(workspaceFiles.deletedAt))
}

const records = await db
return db
.select()
.from(workspaceFiles)
.where(conditions.length > 1 ? and(...conditions) : conditions[0])
.orderBy(workspaceFiles.uploadedAt)

return records.map((record) => ({
id: record.id,
key: record.key,
userId: record.userId,
workspaceId: record.workspaceId,
context: record.context,
originalName: record.originalName,
contentType: record.contentType,
size: record.size,
deletedAt: record.deletedAt,
uploadedAt: record.uploadedAt,
}))
}

/**
Expand Down
6 changes: 3 additions & 3 deletions apps/sim/lib/webhooks/polling/orchestrator.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { createLogger } from '@sim/logger'
import { generateShortId } from '@sim/utils/id'
import { getPollingHandler } from '@/lib/webhooks/polling/registry'
import type { PollSummary, WebhookRecord, WorkflowRecord } from '@/lib/webhooks/polling/types'
import type { PollSummary } from '@/lib/webhooks/polling/types'
import { fetchActiveWebhooks, runWithConcurrency } from '@/lib/webhooks/polling/utils'

/** Poll all active webhooks for a given provider. */
Expand All @@ -27,8 +27,8 @@ export async function pollProvider(providerName: string): Promise<PollSummary> {
async (entry) => {
const requestId = generateShortId()
return handler.pollWebhook({
webhookData: entry.webhook as WebhookRecord,
workflowData: entry.workflow as WorkflowRecord,
webhookData: entry.webhook,
workflowData: entry.workflow,
requestId,
logger,
})
Expand Down
22 changes: 3 additions & 19 deletions apps/sim/lib/webhooks/polling/types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { webhook, workflow } from '@sim/db/schema'
import type { Logger } from '@sim/logger'

/** Summary returned after polling all webhooks for a provider. */
Expand All @@ -15,25 +16,8 @@ export interface PollWebhookContext {
logger: Logger
}

/** DB row shape for the webhook table. */
export interface WebhookRecord {
id: string
path: string
provider: string | null
blockId: string | null
providerConfig: unknown
credentialSetId: string | null
workflowId: string
[key: string]: unknown
}

/** DB row shape for the workflow table. */
export interface WorkflowRecord {
id: string
userId: string
workspaceId: string
[key: string]: unknown
}
export type WebhookRecord = typeof webhook.$inferSelect
export type WorkflowRecord = typeof workflow.$inferSelect

/**
* Strategy interface for provider-specific polling behavior.
Expand Down
2 changes: 1 addition & 1 deletion apps/sim/lib/webhooks/polling/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ export async function fetchActiveWebhooks(
)
)

return rows as unknown as { webhook: WebhookRecord; workflow: WorkflowRecord }[]
return rows
}

/**
Expand Down
Loading
Loading