# Structuring a Server

> Where the code that isn't any one tool's job goes in a FrontMCP server. Providers for shared services and state, plugins for reusable behaviour, and hooks that run around every call.

Source: https://frontmcp.dev/learn/structuring-a-server

[Grouping Capabilities into Apps](https://frontmcp.dev/learn/grouping-capabilities-into-apps) split a server into apps, and showed a provider shared between them. As a server grows, more of its code stops belonging to any one tool: the store every tool reads, the settings they all follow, the audit log every call should reach, the checks that should run before any tool does. This chapter is about where that code goes, so that tools stay small and new ones get it all for free.

**In this chapter**
- [How to share services and state between tools with providers](https://frontmcp.dev/learn/sharing-state-with-providers)
- [How to package behaviour for many tools as a plugin](https://frontmcp.dev/learn/extending-with-plugins)
- [How to run code before and after the stages of every call with hooks](https://frontmcp.dev/learn/hooking-into-calls)

## Sharing state with providers

A variable at the top of a file is shared by every request, and nothing can replace it in a test. A **provider** is a class FrontMCP creates for you and hands to any tool that asks with `this.get()`. Its scope decides how long it lives: `GLOBAL` for as long as the server runs, `CONTEXT` for one request. Call `close_ticket` twice, once for `T-1` and once for `T-3`: the store remembers the first call, and each receipt only has its own steps.

```ts help-desk.app.ts
import { App, Provider, ProviderScope, Tool, ToolContext, z } from "@frontmcp/sdk";

@Provider({ name: "TicketStore", scope: ProviderScope.GLOBAL })
export class TicketStore {
  open = new Set(["T-1", "T-3"]);
}

@Provider({ name: "Receipt", scope: ProviderScope.CONTEXT })
export class Receipt {
  steps: string[] = [];
}

@Tool({ name: "close_ticket", description: "Close a support ticket", inputSchema: { id: z.string() } })
export class CloseTicket extends ToolContext {
  async execute({ id }: { id: string }) {
    const store = this.get(TicketStore);
    this.get(Receipt).steps.push(store.open.delete(id) ? `closed ${id}` : `${id} was already closed`);
    return { receipt: this.get(Receipt).steps, stillOpen: [...store.open] };
  }
}

@App({ id: "help-desk", name: "Help Desk", tools: [CloseTicket], providers: [TicketStore, Receipt] })
export class HelpDeskApp {}
```

Read more: [Sharing State with Providers](https://frontmcp.dev/learn/sharing-state-with-providers)
Learn why module-level state breaks under concurrent calls, what `GLOBAL` and `CONTEXT` mean per request, how to build a provider from configuration, and how to swap one for a fake in tests.

## Extending with plugins

Some behaviour belongs around every tool rather than inside one: an audit log, a maintenance switch, a cache. A **plugin** (`@Plugin`) packages it, with its own providers, the tools it adds and the hooks it runs, and an app turns it on in `plugins`. Call `close_ticket`, then `audit_log`:

```ts help-desk.app.ts
import { App, DynamicPlugin, FlowCtxOf, Plugin, Provider, Tool, ToolContext, ToolHook, z } from "@frontmcp/sdk";

@Provider({ name: "AuditLog" })
export class AuditLog {
  entries: string[] = [];
}

@Tool({ name: "audit_log", description: "Show every tool call made so far", inputSchema: {} })
export class ShowAuditLog extends ToolContext {
  async execute() {
    return { entries: [...this.get(AuditLog).entries] };
  }
}

@Plugin({ name: "audit", providers: [AuditLog], tools: [ShowAuditLog] })
export class AuditPlugin extends DynamicPlugin<object> {
  @ToolHook.Did("execute")
  async record(ctx: FlowCtxOf<"tools:call-tool">) {
    this.get(AuditLog).entries.push(`${ctx.state.tool?.metadata.name} ${JSON.stringify(ctx.state.toolContext?.input)}`);
  }
}

@Tool({ name: "close_ticket", description: "Close a support ticket", inputSchema: { id: z.string() } })
export class CloseTicket extends ToolContext {
  async execute({ id }: { id: string }) {
    return { id, status: "closed" };
  }
}

@App({ id: "help-desk", name: "Help Desk", tools: [CloseTicket], plugins: [AuditPlugin] })
export class HelpDeskApp {}
```

Read more: [Extending with Plugins](https://frontmcp.dev/learn/extending-with-plugins)
Learn what a plugin can add to an app, how it shares its providers, how to make it configurable, where to register it, and what the official plugins do.

## Hooking into calls

Every `tools/call` runs through a flow of named stages: find the tool, validate the arguments, execute, format the result. A **hook** in a plugin runs just before (`Will`) or just after (`Did`) a stage, on every call. This one cleans up ticket ids before the schema checks them, so `" t-2 "` works:

```ts help-desk.app.ts
import { App, DynamicPlugin, FlowCtxOf, Plugin, Tool, ToolContext, ToolHook, z } from "@frontmcp/sdk";

@Plugin({ name: "ticket-ids" })
export class TicketIdsPlugin extends DynamicPlugin<object> {
  @ToolHook.Will("validateInput")
  async normalizeTicketId(ctx: FlowCtxOf<"tools:call-tool">) {
    const args = ctx.state.input?.arguments;
    if (typeof args?.id === "string") args.id = args.id.trim().toUpperCase();
  }
}

@Tool({
  name: "get_ticket",
  description: "Get one support ticket by its id",
  inputSchema: { id: z.string().regex(/^T-\d+$/).describe("Ticket id, like T-1") },
})
export class GetTicket extends ToolContext {
  async execute({ id }: { id: string }) {
    return { id, title: "Invoice total is wrong", status: "closed" };
  }
}

@App({ id: "help-desk", name: "Help Desk", tools: [GetTicket], plugins: [TicketIdsPlugin] })
export class HelpDeskApp {}
```

Read more: [Hooking into Calls](https://frontmcp.dev/learn/hooking-into-calls)
Learn the stages of a tool call, what a hook can read and change at each, how to log calls, block them and rewrite their results, and the order hooks run in.

## What's next?

Start the chapter with [Sharing State with Providers](https://frontmcp.dev/learn/sharing-state-with-providers). After it, [Securing a Server](https://frontmcp.dev/learn/securing-a-server) covers who may call your server and what each caller is allowed to do.
