---
title: Type Safety
description: "Get end-to-end TypeScript type inference from server to view with generateHelpers."
---


**Problem:** Your server defines tool schemas, but your view has no idea what types to expect. You end up duplicating type definitions or using `any`.

**Solution:** `generateHelpers` creates typed hooks from your server type, giving you autocomplete and type checking across the stack. 

## The Challenge

Without type inference, you duplicate types:

```typescript
// src/server.ts
server.registerTool(
  {
    name: "search",
    inputSchema: { query: z.string(), limit: z.number() },
    view: { component: "search" },
  },
  async ({ query, limit }) => {
    // ...
  },
);

// src/views/search.tsx
type SearchInput = { query: string; limit: number }; // Duplicated!
type SearchOutput = { results: Result[] }; // Duplicated!

const { callTool } = useCallTool<SearchInput, SearchOutput>("search");
```

If the server schema changes, the view types are now wrong.

## The Solution: generateHelpers

Export your server's type and use `generateHelpers`:

**Step 1: Export the server type**

```typescript
// src/server.ts
import { McpServer } from "skybridge/server";
import { z } from "zod";

const server = new McpServer({ name: "my-app", version: "1.0" }, {})
  .registerTool(
    {
      name: "search-hotels",
      inputSchema: {
        city: z.string(),
        checkIn: z.string(),
      },
      outputSchema: {
        hotels: z.array(z.object({ id: z.string(), name: z.string() })),
      },
      view: { component: "search-hotels" },
    },
    async ({ city, checkIn }) => {
      const hotels = await searchHotels(city, checkIn);
      return { structuredContent: { hotels } };
    },
  )
  .registerTool(
    {
      name: "hotel-details",
      inputSchema: { hotelId: z.string() },
      view: { component: "hotel-details" },
    },
    async ({ hotelId }) => {
      const hotel = await getHotel(hotelId);
      return { structuredContent: hotel };
    },
  );

// Export the type
export type AppType = typeof server;
```

**Step 2: Generate typed hooks**

```typescript
// src/helpers.ts
import type { AppType } from "./server";
import { generateHelpers } from "skybridge/web";

export const { useCallTool, useToolInfo } = generateHelpers<AppType>();
```

**Step 3: Use typed hooks**

```tsx
// src/views/search-hotels.tsx
import { useCallTool } from "../helpers";

export default function SearchHotels() {
  const { callTool, data } = useCallTool("search-hotels");
  //                                       ^ autocomplete shows available tools

  callTool({ city: "Paris", checkIn: "2025-12-15" });
  //         ^ autocomplete for input fields

  if (data) {
    data.structuredContent.hotels.map(hotel => hotel.name);
    //                              ^ fully typed
  }
}
```

## Method Chaining Requirement

<Warning>
**Required for type inference**

You **must use method chaining** when registering tools. TypeScript captures types at assignment — without chaining, `typeof server` sees an empty registry.
</Warning>

```typescript
// Works — types accumulate through the chain
const server = new McpServer({ name: "app", version: "1.0" }, {})
  .registerTool({ name: "a", view: { component: "a" } }, async () => ({ structuredContent: {} }))
  .registerTool({ name: "b", view: { component: "b" } }, async () => ({ structuredContent: {} }));

// Doesn't work — typeof server = McpServer<{}> (empty!)
const server = new McpServer({ name: "app", version: "1.0" }, {});
server.registerTool({ name: "a", view: { component: "a" } }, async () => ({ structuredContent: {} }));
server.registerTool({ name: "b", view: { component: "b" } }, async () => ({ structuredContent: {} }));
```

## How It Works

The `$types` property pattern enables cross-package type inference:

```typescript
// McpServer internally tracks tool types
class McpServer<ToolRegistry = {}> {
  $types!: McpServerTypes<ToolRegistry>;

  registerTool<Name, Input, Output>(
    config: { name: Name; inputSchema?: Input; view?: ViewConfig; /* ... */ },
    ...
  ): McpServer<ToolRegistry & { [K in Name]: ToolDef<Input, Output> }> {
    // Returns a new type with the tool added
  }
}
```

`generateHelpers` extracts these types:

```typescript
function generateHelpers<ServerType>() {
  type Tools = InferTools<ServerType>;

  return {
    useCallTool: <ToolName extends keyof Tools>(name: ToolName) => {
      // Input and output types are inferred from Tools[ToolName]
    },
  };
}
```

## Type Utilities

Skybridge exports utilities for extracting types:

```typescript
import type {
  InferTools,
  ToolNames,
  ToolInput,
  ToolOutput,
} from "skybridge/server";
import type { AppType } from "./server";

// Get all tool names as a union
type MyToolNames = ToolNames<AppType>;
// "search-hotels" | "hotel-details"

// Get input type for a specific tool
type SearchInput = ToolInput<AppType, "search-hotels">;
// { city: string; checkIn: string }

// Get output type for a specific tool
type SearchOutput = ToolOutput<AppType, "search-hotels">;
// { hotels: { id: string; name: string }[] }
```

## Zod Schema Connection

The magic comes from Zod schemas. When you define:

```typescript
inputSchema: {
  city: z.string(),
  limit: z.number().optional(),
}
```

Skybridge infers:
```typescript
type Input = {
  city: string;
  limit?: number;
}
```

Complex Zod types work too:

```typescript
inputSchema: {
  filters: z.object({
    minPrice: z.number(),
    maxPrice: z.number(),
    amenities: z.array(z.enum(["wifi", "pool", "gym"])),
  }).optional(),
}
```

Becomes:

```typescript
type Input = {
  filters?: {
    minPrice: number;
    maxPrice: number;
    amenities: ("wifi" | "pool" | "gym")[];
  };
}
```

## Troubleshooting


### "Property does not exist on type"

Make sure you're importing from your generated helpers file, not directly from `skybridge/web`:

```typescript
// Wrong
import { useCallTool } from "skybridge/web";

// Right
import { useCallTool } from "../helpers";
```

### No autocomplete for tool names

Check that:
1. Your server exports `type AppType = typeof server`
2. Your `helpers.ts` imports this type correctly
3. You're using method chaining

## Related

- [generateHelpers API](/api-reference/generate-helpers) - Full API documentation
- [Type Utilities](/api-reference/infer-utility-types) - Type extraction helpers
