Skip to content

Commit 45bf396

Browse files
waleedlatif1claude
andauthored
fix(deps): bump drizzle-orm 0.45.2 + adopt MCP SDK 1.25.3 native types (#4252)
* fix(deps): bump drizzle-orm to 0.45.2 (GHSA-gpj5-g38j-94v9) Resolves Dependabot alert #98. Drizzle ORM <0.45.2 improperly escaped quoted SQL identifiers, allowing SQL injection via untrusted input passed to APIs like sql.identifier() or .as(). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * chore(mcp): adopt native SDK types after @modelcontextprotocol/sdk 1.25.3 bump Replace hand-written schema/annotation shapes with the SDK's exported Tool, JSONRPCResultResponse, and Tool['annotations'] types so changes upstream flow through automatically. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * refactor(types): use drizzle $inferSelect for row types Replace hand-written interfaces that duplicated schema shape with typeof table.$inferSelect aliases for webhook, workflow, and workspaceFiles rows. Also simplify metadata insert/update to use .returning() instead of field-by-field copies. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(uploads): fall through to INSERT if restore-deleted row races a hard delete If a hard delete races between the initial SELECT and the restore UPDATE, .returning() yields no row. Previously the function would return undefined and silently violate the Promise<FileMetadataRecord> contract. Now the function falls through to the INSERT path, which already handles uniqueness races via the 23505 catch. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * chore(uploads): align metadata.ts with global standards Replace dynamic uuid import with generateId() per @sim/utils/id convention, narrow the error catch off `any`, and convert the inline comment to TSDoc. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 193f06f commit 45bf396

13 files changed

Lines changed: 84 additions & 195 deletions

File tree

apps/docs/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
"@vercel/og": "^0.6.5",
2121
"class-variance-authority": "^0.7.1",
2222
"clsx": "^2.1.1",
23-
"drizzle-orm": "^0.44.5",
23+
"drizzle-orm": "^0.45.2",
2424
"fumadocs-core": "16.6.7",
2525
"fumadocs-mdx": "14.2.8",
2626
"fumadocs-openapi": "10.3.13",

apps/sim/app/api/mcp/serve/[serverId]/route.ts

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,10 @@ import {
1010
isJSONRPCRequest,
1111
type JSONRPCError,
1212
type JSONRPCMessage,
13-
type JSONRPCResponse,
13+
type JSONRPCResultResponse,
1414
type ListToolsResult,
1515
type RequestId,
16+
type Tool,
1617
} from '@modelcontextprotocol/sdk/types.js'
1718
import { db } from '@sim/db'
1819
import { workflow, workflowMcpServer, workflowMcpTool, workspace } from '@sim/db/schema'
@@ -41,11 +42,11 @@ interface ExecuteAuthContext {
4142
apiKey?: string | null
4243
}
4344

44-
function createResponse(id: RequestId, result: unknown): JSONRPCResponse {
45+
function createResponse(id: RequestId, result: unknown): JSONRPCResultResponse {
4546
return {
4647
jsonrpc: '2.0',
4748
id,
48-
result: result as JSONRPCResponse['result'],
49+
result: result as JSONRPCResultResponse['result'],
4950
}
5051
}
5152

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

236237
const result: ListToolsResult = {
237238
tools: tools.map((tool) => {
238-
const schema = tool.parameterSchema as {
239-
type?: string
240-
properties?: Record<string, unknown>
241-
required?: string[]
242-
} | null
239+
const schema = tool.parameterSchema as Partial<Tool['inputSchema']> | null
243240
return {
244241
name: tool.toolName,
245242
description: tool.toolDescription || `Execute workflow: ${tool.toolName}`,

apps/sim/lib/copilot/tools/mcp/definitions.ts

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,19 @@
1-
export type ToolAnnotations = {
2-
readOnlyHint?: boolean
3-
destructiveHint?: boolean
4-
idempotentHint?: boolean
5-
openWorldHint?: boolean
6-
}
1+
import type { Tool } from '@modelcontextprotocol/sdk/types.js'
2+
3+
export type ToolAnnotations = NonNullable<Tool['annotations']>
74

85
export type DirectToolDef = {
96
name: string
107
description: string
11-
inputSchema: { type: 'object'; properties?: Record<string, unknown>; required?: string[] }
8+
inputSchema: Tool['inputSchema']
129
toolId: string
1310
annotations?: ToolAnnotations
1411
}
1512

1613
export type SubagentToolDef = {
1714
name: string
1815
description: string
19-
inputSchema: { type: 'object'; properties?: Record<string, unknown>; required?: string[] }
16+
inputSchema: Tool['inputSchema']
2017
agentId: string
2118
annotations?: ToolAnnotations
2219
}

apps/sim/lib/mcp/client.ts

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -97,9 +97,7 @@ export class McpClient {
9797
version: '1.0.0',
9898
},
9999
{
100-
capabilities: {
101-
tools: {},
102-
},
100+
capabilities: {},
103101
}
104102
)
105103
}
@@ -261,21 +259,11 @@ export class McpClient {
261259
}
262260
}
263261

264-
/**
265-
* Check if server has capability
266-
*/
267-
hasCapability(capability: string): boolean {
268-
const serverCapabilities = this.client.getServerCapabilities()
269-
return !!serverCapabilities?.[capability]
270-
}
271-
272262
/**
273263
* Check if the server declared `capabilities.tools.listChanged: true` during initialization.
274264
*/
275265
hasListChangedCapability(): boolean {
276-
const caps = this.client.getServerCapabilities()
277-
const toolsCap = caps?.tools as Record<string, unknown> | undefined
278-
return !!toolsCap?.listChanged
266+
return !!this.client.getServerCapabilities()?.tools?.listChanged
279267
}
280268

281269
/**

apps/sim/lib/uploads/server/metadata.ts

Lines changed: 31 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,13 @@
11
import { db } from '@sim/db'
22
import { workspaceFiles } from '@sim/db/schema'
33
import { createLogger } from '@sim/logger'
4+
import { generateId } from '@sim/utils/id'
45
import { and, eq, isNull } from 'drizzle-orm'
56
import type { StorageContext } from '../shared/types'
67

78
const logger = createLogger('FileMetadata')
89

9-
export interface FileMetadataRecord {
10-
id: string
11-
key: string
12-
userId: string
13-
workspaceId: string | null
14-
context: string
15-
originalName: string
16-
contentType: string
17-
size: number
18-
deletedAt?: Date | null
19-
uploadedAt: Date
20-
}
10+
export type FileMetadataRecord = typeof workspaceFiles.$inferSelect
2111

2212
export interface FileMetadataInsertOptions {
2313
key: string
@@ -27,7 +17,8 @@ export interface FileMetadataInsertOptions {
2717
originalName: string
2818
contentType: string
2919
size: number
30-
id?: string // Optional - will generate UUID if not provided
20+
/** Optional — a UUID is generated when omitted. */
21+
id?: string
3122
}
3223

3324
export interface FileMetadataQueryOptions {
@@ -52,7 +43,7 @@ export async function insertFileMetadata(
5243
.limit(1)
5344

5445
if (existingDeleted.length > 0 && existingDeleted[0].deletedAt) {
55-
await db
46+
const [restored] = await db
5647
.update(workspaceFiles)
5748
.set({
5849
userId,
@@ -65,18 +56,10 @@ export async function insertFileMetadata(
6556
uploadedAt: new Date(),
6657
})
6758
.where(eq(workspaceFiles.id, existingDeleted[0].id))
59+
.returning()
6860

69-
return {
70-
id: existingDeleted[0].id,
71-
key,
72-
userId,
73-
workspaceId: workspaceId || null,
74-
context,
75-
originalName,
76-
contentType,
77-
size,
78-
deletedAt: null,
79-
uploadedAt: new Date(),
61+
if (restored) {
62+
return restored
8063
}
8164
}
8265

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

8972
if (existing.length > 0) {
90-
return {
91-
id: existing[0].id,
92-
key: existing[0].key,
93-
userId: existing[0].userId,
94-
workspaceId: existing[0].workspaceId,
95-
context: existing[0].context,
96-
originalName: existing[0].originalName,
97-
contentType: existing[0].contentType,
98-
size: existing[0].size,
99-
deletedAt: existing[0].deletedAt,
100-
uploadedAt: existing[0].uploadedAt,
101-
}
73+
return existing[0]
10274
}
10375

104-
const fileId = id || (await import('uuid')).v4()
76+
const fileId = id || generateId()
10577

10678
try {
107-
await db.insert(workspaceFiles).values({
108-
id: fileId,
109-
key,
110-
userId,
111-
workspaceId: workspaceId || null,
112-
context,
113-
originalName,
114-
contentType,
115-
size,
116-
deletedAt: null,
117-
uploadedAt: new Date(),
118-
})
79+
const [inserted] = await db
80+
.insert(workspaceFiles)
81+
.values({
82+
id: fileId,
83+
key,
84+
userId,
85+
workspaceId: workspaceId || null,
86+
context,
87+
originalName,
88+
contentType,
89+
size,
90+
deletedAt: null,
91+
uploadedAt: new Date(),
92+
})
93+
.returning()
11994

120-
return {
121-
id: fileId,
122-
key,
123-
userId,
124-
workspaceId: workspaceId || null,
125-
context,
126-
originalName,
127-
contentType,
128-
size,
129-
deletedAt: null,
130-
uploadedAt: new Date(),
131-
}
95+
return inserted
13296
} catch (error) {
133-
if (
134-
(error as any)?.code === '23505' ||
135-
(error instanceof Error && error.message.includes('unique'))
136-
) {
97+
const code = (error as { code?: string } | null)?.code
98+
if (code === '23505' || (error instanceof Error && error.message.includes('unique'))) {
13799
const existingAfterError = await db
138100
.select()
139101
.from(workspaceFiles)
140102
.where(and(eq(workspaceFiles.key, key), isNull(workspaceFiles.deletedAt)))
141103
.limit(1)
142104

143105
if (existingAfterError.length > 0) {
144-
return {
145-
id: existingAfterError[0].id,
146-
key: existingAfterError[0].key,
147-
userId: existingAfterError[0].userId,
148-
workspaceId: existingAfterError[0].workspaceId,
149-
context: existingAfterError[0].context,
150-
originalName: existingAfterError[0].originalName,
151-
contentType: existingAfterError[0].contentType,
152-
size: existingAfterError[0].size,
153-
deletedAt: existingAfterError[0].deletedAt,
154-
uploadedAt: existingAfterError[0].uploadedAt,
155-
}
106+
return existingAfterError[0]
156107
}
157108
}
158109

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

189-
if (!record) {
190-
return null
191-
}
192-
193-
return {
194-
id: record.id,
195-
key: record.key,
196-
userId: record.userId,
197-
workspaceId: record.workspaceId,
198-
context: record.context,
199-
originalName: record.originalName,
200-
contentType: record.contentType,
201-
size: record.size,
202-
deletedAt: record.deletedAt,
203-
uploadedAt: record.uploadedAt,
204-
}
140+
return record ?? null
205141
}
206142

207143
/**
@@ -225,24 +161,11 @@ export async function getFileMetadataByContext(
225161
conditions.push(isNull(workspaceFiles.deletedAt))
226162
}
227163

228-
const records = await db
164+
return db
229165
.select()
230166
.from(workspaceFiles)
231167
.where(conditions.length > 1 ? and(...conditions) : conditions[0])
232168
.orderBy(workspaceFiles.uploadedAt)
233-
234-
return records.map((record) => ({
235-
id: record.id,
236-
key: record.key,
237-
userId: record.userId,
238-
workspaceId: record.workspaceId,
239-
context: record.context,
240-
originalName: record.originalName,
241-
contentType: record.contentType,
242-
size: record.size,
243-
deletedAt: record.deletedAt,
244-
uploadedAt: record.uploadedAt,
245-
}))
246169
}
247170

248171
/**

apps/sim/lib/webhooks/polling/orchestrator.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { createLogger } from '@sim/logger'
22
import { generateShortId } from '@sim/utils/id'
33
import { getPollingHandler } from '@/lib/webhooks/polling/registry'
4-
import type { PollSummary, WebhookRecord, WorkflowRecord } from '@/lib/webhooks/polling/types'
4+
import type { PollSummary } from '@/lib/webhooks/polling/types'
55
import { fetchActiveWebhooks, runWithConcurrency } from '@/lib/webhooks/polling/utils'
66

77
/** Poll all active webhooks for a given provider. */
@@ -27,8 +27,8 @@ export async function pollProvider(providerName: string): Promise<PollSummary> {
2727
async (entry) => {
2828
const requestId = generateShortId()
2929
return handler.pollWebhook({
30-
webhookData: entry.webhook as WebhookRecord,
31-
workflowData: entry.workflow as WorkflowRecord,
30+
webhookData: entry.webhook,
31+
workflowData: entry.workflow,
3232
requestId,
3333
logger,
3434
})

apps/sim/lib/webhooks/polling/types.ts

Lines changed: 3 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type { webhook, workflow } from '@sim/db/schema'
12
import type { Logger } from '@sim/logger'
23

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

18-
/** DB row shape for the webhook table. */
19-
export interface WebhookRecord {
20-
id: string
21-
path: string
22-
provider: string | null
23-
blockId: string | null
24-
providerConfig: unknown
25-
credentialSetId: string | null
26-
workflowId: string
27-
[key: string]: unknown
28-
}
29-
30-
/** DB row shape for the workflow table. */
31-
export interface WorkflowRecord {
32-
id: string
33-
userId: string
34-
workspaceId: string
35-
[key: string]: unknown
36-
}
19+
export type WebhookRecord = typeof webhook.$inferSelect
20+
export type WorkflowRecord = typeof workflow.$inferSelect
3721

3822
/**
3923
* Strategy interface for provider-specific polling behavior.

apps/sim/lib/webhooks/polling/utils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ export async function fetchActiveWebhooks(
9696
)
9797
)
9898

99-
return rows as unknown as { webhook: WebhookRecord; workflow: WorkflowRecord }[]
99+
return rows
100100
}
101101

102102
/**

0 commit comments

Comments
 (0)