# Resources (/docs/core-concepts/resources)

`xmcp` automatically detects and registers files under the `/src/resources/` directory as resources. This path can be configured in your `xmcp.config.ts` file.

Each resource file should export three elements:

* **Schema**: Input parameters defined using Zod schemas
* **Metadata**: The resource's identity and behavior configuration
* **Default**: The resource handler function

There are two types of resources: **static** and **dynamic**. When creating resources, it's important to understand how the folder structure determines the resource URI.

## URI composition rules

Each resource is uniquely identified by a URI composed from its file path using these rules:

* The URI scheme is detected from folders with parentheses. For example, a parent folder named `(users)` creates the URI scheme `users`.
* Static folders become literal path segments.
* Brackets `[]` indicate dynamic parameters.

For example, the following file path:

```
src/resources/(users)/[userId]/profile.ts
```

Will result in the URI `users://{userId}/profile`.

## 1. Static resource

Static resources are files that don't require any parameters. Following the composition rules above, the resource below will have the URI `config://app`:

```typescript title="src/resources/(config)/app.ts"
import { type ResourceMetadata } from "xmcp";

export const metadata: ResourceMetadata = {
  name: "app-config",
  title: "Application Config",
  description: "Application configuration data",
};

export default function handler() {
  return "App configuration here";
}
```

## 2. Dynamic resource

Dynamic resources accept parameters. The example below creates a resource with the URI `users://{userId}/profile`:

```typescript title="src/resources/(users)/[userId]/profile.ts"
import { z } from "zod";
import { type ResourceMetadata, type InferSchema } from "xmcp";

export const schema = {
  userId: z.string().describe("The ID of the user"),
};

export const metadata: ResourceMetadata = {
  name: "user-profile",
  title: "User Profile",
  description: "User profile information",
};

export default function handler({ userId }: InferSchema<typeof schema>) {
  return `Profile data for user ${userId}`;
}
```

## References

### Schema (optional)

The schema object defines the resource's parameters with:

* **Key**: Parameter name.
* **Value**: Zod schema with `.describe()` for documentation and resource inspection. This will be visible through the inspector.
* **Purpose**: Type validation and automatic parameter documentation.

This is the exact same as the schema object for tools and prompts.

### Metadata (optional)

The metadata object provides:

* **Name**: Unique identifier for the resource
* **Title**: Human-readable title for the resource
* **Description**: Brief explanation of what the resource does
* **MimeType**: The MIME type of the resource
* **Size**: The size of the resource

### Implementation (required)

The default export function that performs the actual work.

* **Parameters**: Automatically typed from your schema using the built-in `InferSchema`.
* **Returns**: MCP-compatible response with content type.

## Troubleshooting

### Resource Loading Errors

When `xmcp` starts, it loads every file under your resources directory.

* Empty resource files are skipped with a friendly warning
* Files without a `default` export are skipped with a friendly warning
* Real syntax or import errors still fail normally so you can see the full stack trace

For example, if `src/resources/(drafts)/latest.ts` is empty, startup will log:

```txt
[xmcp] Failed to load resource file: src/resources/(drafts)/latest.ts
   -> File is empty.
[xmcp] 1 resource skipped due to empty files or missing default exports
```

If the file exists but does not export a default handler, startup will log:

```txt
[xmcp] Failed to load resource file: src/resources/(drafts)/latest.ts
   -> File does not export a default resource handler.
```

<Callout variant="info">
  Friendly handling is intentionally limited to empty files and missing default
  exports. Invalid implementations and real import/syntax errors still surface
  as normal runtime errors.
</Callout>
