1- import { AuthInfo } from "./schemas/auth.js" ;
2- import { z , ZodRawShape } from "zod" ;
31import {
42 McpServer ,
5- RegisteredTool ,
6- ToolCallback ,
3+ RegisteredTool
74} from "@modelcontextprotocol/sdk/server/mcp.js" ;
5+ import { RequestHandlerExtra } from "@modelcontextprotocol/sdk/shared/protocol.js" ;
86import {
97 CallToolResult ,
10- ServerRequest ,
118 ServerNotification ,
9+ ServerRequest ,
1210 ToolAnnotations ,
1311} from "@modelcontextprotocol/sdk/types.js" ;
14- import { RequestHandlerExtra } from "@modelcontextprotocol/sdk/shared/protocol.js" ;
12+ import { z , ZodRawShape } from "zod" ;
13+ import { AuthInfo } from "./schemas/auth.js" ;
1514import { getOutboundToken } from "./utils/outboundToken.js" ;
1615import {
1716 getRequestContext ,
@@ -157,62 +156,127 @@ export function registerAuthenticatedTool(
157156 cb : ( ...args : any [ ] ) => CallToolResult | Promise < CallToolResult > ,
158157 requiredScopes : string [ ] = [ ] ,
159158) {
160- return ( server : McpServer ) => {
161- // eslint-disable-next-line @typescript-eslint/no-explicit-any
162- const wrapped : ToolCallback < any > = async (
163- args : unknown ,
164- extra : RequestHandlerExtra < ServerRequest , ServerNotification > ,
165- ) => {
166- // Get auth context from the server
167- const context = getRequestContext ( server as ServerWithContext ) ;
168-
169- if ( ! context ?. authInfo ) {
170- throw new Error (
171- `Authentication required for tool "${ name } ". Ensure a valid bearer token is provided.` ,
172- ) ;
173- }
174-
175- const { authInfo, descopeConfig } = context ;
176-
177- // Scope validation
178- if ( requiredScopes . length ) {
179- const missing = requiredScopes . filter (
180- ( s ) => ! authInfo . scopes ?. includes ( s ) ,
181- ) ;
182- if ( missing . length ) {
183- const userScopes = authInfo . scopes ?. join ( ", " ) || "none" ;
159+ return ( server : McpServer ) : RegisteredTool => {
160+ // Convert ZodRawShape to ZodObject and register with MCP server
161+ // We need to handle the two cases separately to maintain correct types
162+ if ( config . inputSchema ) {
163+ // Tool WITH input schema
164+ const wrapped = async (
165+ args : unknown ,
166+ extra : RequestHandlerExtra < ServerRequest , ServerNotification > ,
167+ ) => {
168+ // Get auth context from the server
169+ const context = getRequestContext ( server as ServerWithContext ) ;
170+
171+ if ( ! context ?. authInfo ) {
184172 throw new Error (
185- `Tool "${ name } " requires scopes: ${ requiredScopes . join ( ", " ) } . ` +
186- `User has scopes: ${ userScopes } . ` +
187- `Missing: ${ missing . join ( ", " ) } . ` +
188- `Request these scopes during authentication.` ,
173+ `Authentication required for tool "${ name } ". Ensure a valid bearer token is provided.` ,
174+ ) ;
175+ }
176+
177+ const { authInfo, descopeConfig } = context ;
178+
179+ // Scope validation
180+ if ( requiredScopes . length ) {
181+ const missing = requiredScopes . filter (
182+ ( s ) => ! authInfo . scopes ?. includes ( s ) ,
189183 ) ;
184+ if ( missing . length ) {
185+ const userScopes = authInfo . scopes ?. join ( ", " ) || "none" ;
186+ throw new Error (
187+ `Tool "${ name } " requires scopes: ${ requiredScopes . join ( ", " ) } . ` +
188+ `User has scopes: ${ userScopes } . ` +
189+ `Missing: ${ missing . join ( ", " ) } . ` +
190+ `Request these scopes during authentication.` ,
191+ ) ;
192+ }
190193 }
191- }
192194
193- // getOutboundToken bound to this request
194- const getOutboundTokenFn = ( appId : string , scopes ?: string [ ] ) =>
195- descopeConfig
196- ? getOutboundToken ( appId , authInfo , descopeConfig , scopes )
197- : Promise . resolve ( null ) ;
195+ // getOutboundToken bound to this request
196+ const getOutboundTokenFn = ( appId : string , scopes ?: string [ ] ) =>
197+ descopeConfig
198+ ? getOutboundToken ( appId , authInfo , descopeConfig , scopes )
199+ : Promise . resolve ( null ) ;
198200
199- const authExtra : AuthenticatedExtra = {
201+ const authExtra : AuthenticatedExtra = {
202+ ...extra ,
203+ authInfo,
204+ getOutboundToken : getOutboundTokenFn ,
205+ } ;
206+
207+ // Call user-supplied handler with args
200208 // eslint-disable-next-line @typescript-eslint/no-explicit-any
201- ...( extra as any ) ,
202- authInfo,
203- getOutboundToken : getOutboundTokenFn ,
209+ return ( cb as any ) ( args , authExtra ) ;
210+ } ;
211+
212+ // Use explicit any to prevent deep type instantiation errors with ZodObject
213+ const mcpConfigWithInput : any = {
214+ title : config . title ,
215+ description : config . description ,
216+ inputSchema : z . object ( config . inputSchema ) ,
217+ outputSchema : config . outputSchema ? z . object ( config . outputSchema ) : undefined ,
218+ annotations : config . annotations ,
204219 } ;
220+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
221+ return server . registerTool ( name , mcpConfigWithInput , wrapped as any ) ;
222+ } else {
223+ // Tool WITHOUT input schema
224+ const wrapped = async (
225+ extra : RequestHandlerExtra < ServerRequest , ServerNotification > ,
226+ ) => {
227+ // Get auth context from the server
228+ const context = getRequestContext ( server as ServerWithContext ) ;
205229
206- // Call user-supplied handler
207- return config . inputSchema
208- ? // eslint-disable-next-line @typescript-eslint/no-explicit-any
209- ( cb as any ) ( args , authExtra )
210- : // eslint-disable-next-line @typescript-eslint/no-explicit-any
211- ( cb as any ) ( authExtra ) ;
212- } ;
230+ if ( ! context ?. authInfo ) {
231+ throw new Error (
232+ `Authentication required for tool "${ name } ". Ensure a valid bearer token is provided.` ,
233+ ) ;
234+ }
235+
236+ const { authInfo, descopeConfig } = context ;
237+
238+ // Scope validation
239+ if ( requiredScopes . length ) {
240+ const missing = requiredScopes . filter (
241+ ( s ) => ! authInfo . scopes ?. includes ( s ) ,
242+ ) ;
243+ if ( missing . length ) {
244+ const userScopes = authInfo . scopes ?. join ( ", " ) || "none" ;
245+ throw new Error (
246+ `Tool "${ name } " requires scopes: ${ requiredScopes . join ( ", " ) } . ` +
247+ `User has scopes: ${ userScopes } . ` +
248+ `Missing: ${ missing . join ( ", " ) } . ` +
249+ `Request these scopes during authentication.` ,
250+ ) ;
251+ }
252+ }
253+
254+ // getOutboundToken bound to this request
255+ const getOutboundTokenFn = ( appId : string , scopes ?: string [ ] ) =>
256+ descopeConfig
257+ ? getOutboundToken ( appId , authInfo , descopeConfig , scopes )
258+ : Promise . resolve ( null ) ;
259+
260+ const authExtra : AuthenticatedExtra = {
261+ ...extra ,
262+ authInfo,
263+ getOutboundToken : getOutboundTokenFn ,
264+ } ;
213265
214- // eslint-disable-next-line @typescript-eslint/no-explicit-any
215- return server . registerTool ( name , config , wrapped as any ) ;
266+ // Call user-supplied handler without args
267+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
268+ return ( cb as any ) ( authExtra ) ;
269+ } ;
270+
271+ // Use explicit any to prevent deep type instantiation errors with ZodObject
272+ const mcpConfigWithoutInput : any = {
273+ title : config . title ,
274+ description : config . description ,
275+ outputSchema : config . outputSchema ? z . object ( config . outputSchema ) : undefined ,
276+ annotations : config . annotations ,
277+ } ;
278+ return server . registerTool ( name , mcpConfigWithoutInput , wrapped ) ;
279+ }
216280 } ;
217281}
218282
@@ -240,6 +304,38 @@ export function registerAuthenticatedTool(
240304 * });
241305 * ```
242306 */
307+
308+ // Overload with input schema
309+ export function defineTool <
310+ I extends ZodRawShape ,
311+ O extends ZodRawShape | undefined = undefined ,
312+ > ( cfg : {
313+ name : string ;
314+ title ?: string ;
315+ description ?: string ;
316+ input : I ;
317+ output ?: O ;
318+ scopes ?: string [ ] ;
319+ annotations ?: ToolAnnotations ;
320+ handler : (
321+ args : z . infer < z . ZodObject < I > > ,
322+ extra : AuthenticatedExtra ,
323+ ) => CallToolResult | Promise < CallToolResult > ;
324+ } ) : ( server : McpServer ) => RegisteredTool ;
325+
326+ // Overload without input schema
327+ export function defineTool < O extends ZodRawShape | undefined = undefined > ( cfg : {
328+ name : string ;
329+ title ?: string ;
330+ description ?: string ;
331+ input ?: undefined ;
332+ output ?: O ;
333+ scopes ?: string [ ] ;
334+ annotations ?: ToolAnnotations ;
335+ handler : ( extra : AuthenticatedExtra ) => CallToolResult | Promise < CallToolResult > ;
336+ } ) : ( server : McpServer ) => RegisteredTool ;
337+
338+ // Implementation
243339export function defineTool <
244340 I extends ZodRawShape | undefined = undefined ,
245341 O extends ZodRawShape | undefined = undefined ,
@@ -251,13 +347,9 @@ export function defineTool<
251347 output ?: O ;
252348 scopes ?: string [ ] ;
253349 annotations ?: ToolAnnotations ;
254- handler : I extends ZodRawShape
255- ? (
256- args : z . infer < z . ZodObject < I > > ,
257- extra : AuthenticatedExtra ,
258- ) => CallToolResult | Promise < CallToolResult >
259- : ( extra : AuthenticatedExtra ) => CallToolResult | Promise < CallToolResult > ;
260- } ) {
350+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
351+ handler : any ;
352+ } ) : ( server : McpServer ) => RegisteredTool {
261353 if ( cfg . input ) {
262354 // With input schema
263355 return registerAuthenticatedTool (
0 commit comments