claude-sdk-proxy 3.1.3 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "claude-sdk-proxy",
3
- "version": "3.1.3",
3
+ "version": "3.2.0",
4
4
  "description": "Anthropic Messages API proxy backed by Claude Agent SDK — use Claude Max with any API client",
5
5
  "type": "module",
6
6
  "main": "./src/proxy/server.ts",
7
7
  "bin": {
8
- "claude-sdk-proxy": "./bin/claude-sdk-proxy.js"
8
+ "claude-sdk-proxy": "bin/claude-sdk-proxy.js"
9
9
  },
10
10
  "exports": {
11
11
  ".": "./src/proxy/server.ts"
package/src/mcpTools.ts CHANGED
@@ -1,3 +1,183 @@
1
- // mcpTools.ts — removed. The proxy no longer provides any MCP tools.
2
- // Tool definitions come from the API caller (standard Anthropic tool loop).
3
- export {}
1
+ // mcpTools.ts — Converts Anthropic API tool definitions to native MCP tools
2
+ // via the Claude Agent SDK's in-process MCP server support.
3
+
4
+ import { z } from "zod"
5
+ import { tool, createSdkMcpServer } from "@anthropic-ai/claude-agent-sdk"
6
+
7
+ // ── JSON Schema → Zod converter ─────────────────────────────────────────────
8
+
9
+ /**
10
+ * Convert a JSON Schema object (as used in Anthropic tool input_schema)
11
+ * to a Zod schema. Handles: string, number, integer, boolean, array,
12
+ * object, enum, nullable, required/optional, nested objects, anyOf/oneOf.
13
+ * Falls back to z.any() for unrecognized types.
14
+ */
15
+ export function jsonSchemaToZod(schema: any): z.ZodTypeAny {
16
+ if (!schema || typeof schema !== "object") return z.any()
17
+
18
+ // anyOf / oneOf → z.union
19
+ if (schema.anyOf || schema.oneOf) {
20
+ const variants = (schema.anyOf ?? schema.oneOf) as any[]
21
+ // Common pattern: { anyOf: [{ type: "string" }, { type: "null" }] } → nullable
22
+ const nonNull = variants.filter((v: any) => v.type !== "null")
23
+ const hasNull = variants.some((v: any) => v.type === "null")
24
+ if (hasNull && nonNull.length === 1) {
25
+ return jsonSchemaToZod(nonNull[0]).nullable()
26
+ }
27
+ if (variants.length === 0) return z.any()
28
+ if (variants.length === 1) return jsonSchemaToZod(variants[0])
29
+ const zodVariants = variants.map((v: any) => jsonSchemaToZod(v))
30
+ // z.union needs at least 2 elements
31
+ return z.union(zodVariants as [z.ZodTypeAny, z.ZodTypeAny, ...z.ZodTypeAny[]])
32
+ }
33
+
34
+ // enum → z.enum or z.union of literals
35
+ if (schema.enum) {
36
+ const values = schema.enum as any[]
37
+ if (values.length === 0) return z.any()
38
+ // String enums
39
+ if (values.every((v: any) => typeof v === "string")) {
40
+ return z.enum(values as [string, ...string[]])
41
+ }
42
+ // Mixed literal types
43
+ const literals = values.map((v: any) => z.literal(v))
44
+ if (literals.length === 1) return literals[0]!
45
+ return z.union(literals as unknown as [z.ZodTypeAny, z.ZodTypeAny, ...z.ZodTypeAny[]])
46
+ }
47
+
48
+ // const → z.literal
49
+ if ("const" in schema) {
50
+ return z.literal(schema.const)
51
+ }
52
+
53
+ switch (schema.type) {
54
+ case "string":
55
+ return z.string()
56
+
57
+ case "number":
58
+ case "integer":
59
+ return z.number()
60
+
61
+ case "boolean":
62
+ return z.boolean()
63
+
64
+ case "null":
65
+ return z.null()
66
+
67
+ case "array": {
68
+ const itemSchema = schema.items ? jsonSchemaToZod(schema.items) : z.any()
69
+ return z.array(itemSchema)
70
+ }
71
+
72
+ case "object": {
73
+ return jsonSchemaObjectToZod(schema)
74
+ }
75
+
76
+ default: {
77
+ // Handle arrays of types like { type: ["string", "null"] }
78
+ if (Array.isArray(schema.type)) {
79
+ const types = schema.type as string[]
80
+ const hasNull = types.includes("null")
81
+ const nonNullTypes = types.filter((t: string) => t !== "null")
82
+ if (nonNullTypes.length === 0) return z.null()
83
+ if (nonNullTypes.length === 1) {
84
+ const base = jsonSchemaToZod({ ...schema, type: nonNullTypes[0] })
85
+ return hasNull ? base.nullable() : base
86
+ }
87
+ const variants = nonNullTypes.map((t: string) => jsonSchemaToZod({ ...schema, type: t }))
88
+ if (hasNull) variants.push(z.null())
89
+ if (variants.length === 1) return variants[0]!
90
+ return z.union(variants as [z.ZodTypeAny, z.ZodTypeAny, ...z.ZodTypeAny[]])
91
+ }
92
+
93
+ // No type specified but has properties → treat as object
94
+ if (schema.properties) {
95
+ return jsonSchemaObjectToZod(schema)
96
+ }
97
+
98
+ return z.any()
99
+ }
100
+ }
101
+ }
102
+
103
+ function jsonSchemaObjectToZod(schema: any): z.ZodTypeAny {
104
+ const properties = schema.properties ?? {}
105
+ const required = new Set<string>(schema.required ?? [])
106
+ const shape: Record<string, z.ZodTypeAny> = {}
107
+
108
+ for (const [key, propSchema] of Object.entries(properties)) {
109
+ let zodProp = jsonSchemaToZod(propSchema as any)
110
+ if (!required.has(key)) {
111
+ zodProp = zodProp.optional()
112
+ }
113
+ shape[key] = zodProp
114
+ }
115
+
116
+ if (Object.keys(shape).length === 0) {
117
+ return z.object({}).passthrough()
118
+ }
119
+
120
+ // Use passthrough to allow additional properties (common in tool schemas)
121
+ return z.object(shape).passthrough()
122
+ }
123
+
124
+ // ── Zod shape extraction ────────────────────────────────────────────────────
125
+
126
+ /**
127
+ * Extract the "raw shape" from a Zod object schema for use with the SDK's
128
+ * tool() helper which expects a ZodRawShape (Record<string, ZodTypeAny>).
129
+ * If the schema isn't a ZodObject, wrap it as { input: schema }.
130
+ */
131
+ function extractZodShape(zodSchema: z.ZodTypeAny): Record<string, z.ZodTypeAny> {
132
+ // ZodObject stores its shape
133
+ if (zodSchema instanceof z.ZodObject) {
134
+ return zodSchema.shape
135
+ }
136
+ // Fallback: wrap non-object schemas
137
+ return { input: zodSchema }
138
+ }
139
+
140
+ // ── MCP Server factory ──────────────────────────────────────────────────────
141
+
142
+ interface AnthropicToolDef {
143
+ name: string
144
+ description?: string
145
+ input_schema?: any
146
+ }
147
+
148
+ /**
149
+ * Create an in-process MCP server from an array of Anthropic tool definitions.
150
+ * Each tool is registered with the SDK's tool() helper. The handlers are
151
+ * no-ops since canUseTool will deny execution (client handles it).
152
+ */
153
+ export function createToolMcpServer(tools: AnthropicToolDef[]) {
154
+ const toolDefs = tools.map((t) => {
155
+ let zodSchema: z.ZodTypeAny
156
+ try {
157
+ zodSchema = jsonSchemaToZod(t.input_schema ?? { type: "object", properties: {} })
158
+ } catch (e) {
159
+ // If conversion fails, use a passthrough object as fallback
160
+ zodSchema = z.object({}).passthrough()
161
+ }
162
+
163
+ const shape = extractZodShape(zodSchema)
164
+
165
+ return tool(
166
+ t.name,
167
+ t.description ?? "",
168
+ shape,
169
+ async () => {
170
+ // Should never be called — canUseTool denies all execution
171
+ return {
172
+ content: [{ type: "text" as const, text: "Error: tool execution denied by proxy" }],
173
+ isError: true,
174
+ }
175
+ }
176
+ )
177
+ })
178
+
179
+ return createSdkMcpServer({
180
+ name: "proxy-tools",
181
+ tools: toolDefs,
182
+ })
183
+ }