> ## Documentation Index
> Fetch the complete documentation index at: https://si.easyaf.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Getting Started with McpEssentials

> An authenticated MCP server in a few steps: isolate tasks per user, elicit a form, swap the store.

Get an MCP server that stamps the logged-in user on every task, asks the client for a form, and persists work in the engine you already run.

> [card] Isolate
>
> `WithTasksPlus` records the owner. Another user sees not found.

> [card] Elicit
>
> `await server.ElicitAsync(form)` parks the form on the task. The SDK interceptor resumes when the client answers.

> [card] Swap
>
> Same identity factory. EF, Redis, Cosmos, or native Postgres.

**1. Install**

Core is enough for a single process. HTTP hosts also take the ASP.NET Core MCP package.

```bash HTTP theme={"dark"}
dotnet add package ModelContextProtocol.AspNetCore
dotnet add package McpEssentials.Core
```

```bash Stdio theme={"dark"}
dotnet add package ModelContextProtocol
dotnet add package McpEssentials.Core
```

Analyzers ship in Core. A rebuild (and sometimes an IDE restart) is enough for squiggles.

**2. Wire the server**

Same identity factory on every host. HTTP reads `HttpContext.User`. Stdio uses a constant user id.

> [tab] HTTP
>
> ```csharp theme={"dark"}
> builder.Services.AddHttpContextAccessor();
>
> builder.Services.AddMcpServer()
>     .WithHttpTransport(o => o.Stateless = true)
>     .WithTools<HelloWorldTools>()
>     .WithTasksPlus(
>         sp => sp.GetRequiredService<IHttpContextAccessor>().HttpContext?.User,
>         o => o.DefaultTimeToLive = TimeSpan.FromMinutes(5));
>
> var app = builder.Build();
> app.UseAuthentication(); // stamp HttpContext.User however you already authenticate
> app.MapMcp();
> app.Run();
> ```
>
> Register the store as a **singleton**. Stateless HTTP creates a fresh `McpServer` per POST; a scoped store looks empty on the next `tasks/get`.

> [tab] Stdio
>
> ```csharp theme={"dark"}
> builder.Services.AddMcpServer()
>     .WithTools<HelloWorldTools>()
>     .WithTasksPlus("local-user");
> ```

> [!TIP]
>
> Async `[McpServerTool]` methods that return `Task` / `Task<T>` automatically advertise task support. A client that opts into `io.modelcontextprotocol/tasks` gets a task id owned by the user on that request.

**3. Write a task tool**

Return `Task<string>` so a task-capable client can poll. Read the user from the principal the host already stamped.

```csharp theme={"dark"}
using System.ComponentModel;
using System.Security.Claims;
using CloudNimble.McpEssentials.Core.Tasks;
using ModelContextProtocol.Server;

[McpServerToolType]
public class HelloWorldTools
{
    [McpServerTool(Name = "HelloWorld"), Description("Says hello to the authenticated user.")]
    public static async Task<string> HelloWorld(ClaimsPrincipal user, CancellationToken cancellationToken)
    {
        var userId = McpTaskStoreBase.GetUserId(user);
        if (string.IsNullOrEmpty(userId))
        {
            return "Unauthenticated.";
        }

        await Task.Delay(2000, cancellationToken);
        return $"Hello, {userId}.";
    }
}
```

Claim lookup is `NameIdentifier`, then `sub`, `UPN`, `Name`. No user on a client-facing call throws [`McpTaskUserRequiredException`](https://si.easyaf.dev/mcpessentials/api-reference/CloudNimble/McpEssentials/Core/Tasks/McpTaskUserRequiredException).

**4. Ask the client for a form**

`await server.ElicitAsync(form)` is enough inside a task. The SDK interceptor parks the form and resumes when the client answers.

```csharp theme={"dark"}
using CloudNimble.McpEssentials.Core.Tasks.Elicitations;
using ModelContextProtocol.Server;

[McpServerTool(Name = "ShipOrder"), Description("Asks for a tracking number, then ships.")]
public static async Task<string> ShipOrder(McpServer server, CancellationToken cancellationToken)
{
    var result = await server.ElicitAsync(
        FormElicitation.Create("Need a tracking number")
            .String("trackingNumber").Required().MinLength(1),
        cancellationToken);

    if (!result.IsAccepted)
    {
        return "cancelled";
    }

    return $"Shipped as {result.Content!["trackingNumber"].GetString()}.";
}
```

Field-by-field `Create`, `From<T>()`, and metadata-driven builders are in [Form elicitation](https://si.easyaf.dev/mcpessentials/guides/form-elicitation.md). Broken `From<T>()` shapes are compiler diagnostics in [Analyzers](https://si.easyaf.dev/mcpessentials/guides/analyzers.md).

**5. Run it**

The in-memory sample is a complete Streamable HTTP host. Fake auth is the `X-User-Id` header.

```bash theme={"dark"}
dotnet run --project samples/CloudNimble.McpEssentials.Samples.Basic -f net11.0
```

MCP is at `http://localhost:5280/`. Send `X-User-Id: alice`.

> [accordion] Prove isolation
>
> 1. Call `HelloWorld` as `alice` with the Tasks extension. Keep the task id.
> 2. `tasks/get` as `alice` returns the task.
> 3. `tasks/get` as `bob` returns not found.
> 4. Omit `X-User-Id` and client-facing task operations throw `McpTaskUserRequiredException`.

> [accordion] Runnable samples
>
> [`samples/CloudNimble.McpEssentials.Samples.Basic`](https://github.com/CloudNimble/McpEssentials/tree/main/samples/CloudNimble.McpEssentials.Samples.Basic) is in-memory.
>
> Entity Framework Core (`localhost:5290`), Redis (`5300`), Cosmos DB (`5310`), and PostgreSQL (`5320`) each have a host in the same folder. The store is the only thing that changes.

**6. Swap the store**

Identity stays on the platform accessor. Production registers a durable store with the same factory.

```csharp Basic theme={"dark"}
builder.Services.AddHttpContextAccessor();

Func<IServiceProvider, ClaimsPrincipal?> currentUser =
    sp => sp.GetRequiredService<IHttpContextAccessor>().HttpContext?.User;

builder.Services.AddMcpServer()
    .WithHttpTransport(o => o.Stateless = true)
    .WithTools<HelloWorldTools>()
    .WithTasksPlus(currentUser, o => o.DefaultTimeToLive = TimeSpan.FromMinutes(5));
```

```csharp Entity Framework Core theme={"dark"}
builder.Services.AddHttpContextAccessor();
builder.Services.AddDbContext<AppDbContext>(o => o.UseSqlServer(connectionString));

Func<IServiceProvider, ClaimsPrincipal?> currentUser =
    sp => sp.GetRequiredService<IHttpContextAccessor>().HttpContext?.User;

builder.Services.AddMcpServer()
    .WithHttpTransport(o => o.Stateless = true)
    .WithTools<HelloWorldTools>()
    .WithTasksPlus<AppDbContext>(currentUser, o => o.DefaultTimeToLive = TimeSpan.FromMinutes(5));
```

```csharp Redis theme={"dark"}
builder.Services.AddHttpContextAccessor();
builder.Services.AddSingleton<IConnectionMultiplexer>(
    _ => ConnectionMultiplexer.Connect(redisConnectionString));

Func<IServiceProvider, ClaimsPrincipal?> currentUser =
    sp => sp.GetRequiredService<IHttpContextAccessor>().HttpContext?.User;

builder.Services.AddMcpServer()
    .WithHttpTransport(o => o.Stateless = true)
    .WithTools<HelloWorldTools>()
    .WithRedisTasks(currentUser, o => o.DefaultTimeToLive = TimeSpan.FromMinutes(5));
```

```csharp Cosmos DB theme={"dark"}
builder.Services.AddHttpContextAccessor();
builder.Services.AddSingleton(_ => new CosmosClient(
    cosmosConnectionString,
    CosmosMcpTaskStore.DefaultClientOptions()));

Func<IServiceProvider, ClaimsPrincipal?> currentUser =
    sp => sp.GetRequiredService<IHttpContextAccessor>().HttpContext?.User;

builder.Services.AddMcpServer()
    .WithHttpTransport(o => o.Stateless = true)
    .WithTools<HelloWorldTools>()
    .WithCosmosTasks(currentUser, o => o.DefaultTimeToLive = TimeSpan.FromMinutes(5));
```

```csharp PostgreSQL theme={"dark"}
builder.Services.AddHttpContextAccessor();
builder.Services.AddSingleton(_ => NpgsqlDataSource.Create(postgresConnectionString));

Func<IServiceProvider, ClaimsPrincipal?> currentUser =
    sp => sp.GetRequiredService<IHttpContextAccessor>().HttpContext?.User;

builder.Services.AddMcpServer()
    .WithHttpTransport(o => o.Stateless = true)
    .WithTools<HelloWorldTools>()
    .WithPostgreSqlTasks(currentUser, o => o.DefaultTimeToLive = TimeSpan.FromMinutes(5));
```

> [!NOTE]
>
> Map `McpTasks` with `AddMcpTasksTable` when you stay on Entity Framework Core. Redis and native Postgres are the buses for multi-node `ElicitAsync`. EF and Cosmos take an optional [`IMcpTaskInputSignalBus`](https://si.easyaf.dev/mcpessentials/api-reference/CloudNimble/McpEssentials/Core/Tasks/IMcpTaskInputSignalBus).

## Next steps

> [card] [Why MCP Essentials?](https://si.easyaf.dev/mcpessentials/why-mcpessentials.md)
>
> How this layer extends the official SDK for real-world servers.

> [card] [Authenticated tasks](https://si.easyaf.dev/mcpessentials/guides/authenticated-tasks.md)
>
> Isolation rules, overloads, and every sample.

> [card] [Form elicitation](https://si.easyaf.dev/mcpessentials/guides/form-elicitation.md)
>
> Field-by-field, `From<T>()`, and parking a form on a task.

> [card] [Analyzers](https://si.easyaf.dev/mcpessentials/guides/analyzers.md)
>
> MCPESS001–006 so a broken form fails the build.

> [card] [Choose a store](https://si.easyaf.dev/mcpessentials/guides/stores.md)
>
> EF Core, Redis, Cosmos, native Postgres, and the specified family.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.