---
name: azion-run-an-mcp-server-on-azion
description: >-
  Build a Model Context Protocol server with the MCP SDK and Hono, run it as an Azion function, and deploy it with the Azion CLI.
---

# Run an MCP server on Azion

You can run your own Model Context Protocol (MCP) server as an Azion [function](/en/documentation/platform/functions/) and deploy it with the [Azion CLI](/en/documentation/devtools/cli/). To connect a coding agent to the MCP servers that Azion hosts, refer to [MCP server quickstart](/en/documentation/devtools/mcp/quickstart/).

MCP is an open specification that uses JSON-RPC to standardize how applications and AI agents communicate. A server exposes three kinds of capabilities: tools (actions), resources (data such as files or API responses), and prompts (shared prompt templates). Any compatible client can list and call the tools, read the resources, and fetch the prompts. A language model can then call your functions and read your data. For more information, refer to the [MCP documentation](https://modelcontextprotocol.io/introduction).

The server on this page uses the [MCP SDK for TypeScript](https://github.com/modelcontextprotocol/typescript-sdk/), `@modelcontextprotocol/sdk`, which implements the protocol. [Hono](/en/documentation/guides/application-development/frameworks/hono/) routes the HTTP requests. The SDK's `WebStandardStreamableHTTPServerTransport` answers them over streamable HTTP and works with the Fetch API `Request` and `Response` objects that a function receives and returns. For the other transports the protocol defines, refer to [Transports](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports).

---

## Prerequisites

- An Azion account. To create one, refer to [Create an account](/en/documentation/fundamentals/creating-account/).
- The Azion CLI, installed and logged in. For the setup, refer to [Azion CLI quickstart](/en/documentation/devtools/cli/quickstart/).
- Node.js and npm. The CLI runs Azion Bundler with `npx` to build the project, and npm installs the packages the server imports.

---

## Create and deploy the server

The CLI creates a Hono project from a template. You replace the template's entry with the MCP server, run it locally, and deploy it. The server uses the high-level `McpServer` class, which registers each tool, resource, and prompt with one method call.

To create, run, and deploy the server:

1. **Create a Hono project**

   Run `azion init` with a name for the project:

   ```bash
   azion init --name my-mcp-server
   ```

   At `Choose a preset:`, select *Hono*. You can type `Hono` to filter the list. At `Choose a template:`, select *Hono Boilerplate*. Answer `Y` to install the dependencies, and `n` to the local development server and to the deploy. The command prints this output:

   ```text
   ? Choose a preset: Hono  [Use arrows to move, type to filter]
   > Hono
   ? Choose a preset: Hono
   ? Choose a template:  [Use arrows to move, type to filter]
     Azion React Agent
   > Hono Boilerplate
   ? Choose a template: Hono Boilerplate

   Fetching selected template...

   Template successfully fetched
   Template successfully configured
   🤔 Do you want to install project dependencies? This may be required to generate initial configuration file (Y/n) Y
   Installing application dependencies

   added 2 packages, and audited 3 packages in 1s

   found 0 vulnerabilities
   …
   [Azion] [Build] › ℹ  info      Using preset: typescript
   [Azion] [Build] › ✔  success   Build completed successfully with only azion.config
   [Azion] [IaC] › ✔  success   Manifest generated successfully at <project-dir>/.edge/manifest.json
   🤔 Do you want to start a local development server? (y/N) n
   If you want to start a local development server later, run 'azion dev'
   Make sure to change to the new working directory before running building or deploying your project
   🤔 Do you want to deploy your project? (y/N) n
   If you want to deploy your application later, run 'azion deploy'
   Make sure to change to the new working directory before running building or deploying your project
   Your application my-mcp-server was initialized successfully
   ```

   The project is in the `my-mcp-server` folder, and its entry is `src/index.ts`. For every question and the flags that skip them, refer to [Azion CLI init](/en/documentation/devtools/cli/init/).

2. **Install the server's packages**

   The template installs `hono` only. Go to the project folder. Install the MCP SDK and version 3 of `zod`, which the SDK uses:

   ```bash
   cd my-mcp-server
   npm install @modelcontextprotocol/sdk zod@3
   ```

   npm adds the two packages to the `dependencies` of `package.json`, next to `hono`.

3. **Write the server**

   Replace the contents of `src/index.ts` with the following code. It registers an `add` tool and a `greeting` resource, and it answers MCP requests on `POST /mcp`:

   ```typescript
   import { Hono } from 'hono'
   import type { Context } from 'hono'
   import { McpServer, ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js'
   import { WebStandardStreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js'
   import { z } from 'zod'

   const app = new Hono()

   function getServer() {
     const server = new McpServer({
       name: "azion-mcp-server",
       version: "1.0.0"
     });

     server.registerTool("add",
       {
         title: "Addition Tool",
         description: "Add two numbers",
         inputSchema: { a: z.number(), b: z.number() }
       },
       async ({ a, b }) => ({
         content: [{ type: "text", text: String(a + b) }]
       })
     );

     server.registerResource(
       "greeting",
       new ResourceTemplate("greeting://{name}", { list: undefined }),
       {
         title: "Greeting Resource",
         description: "Dynamic greeting generator"
       },
       async (uri, { name }) => ({
         contents: [{
           uri: uri.href,
           text: `Hello, ${name}!`
         }]
       })
     );

     return server;
   }

   app.post('/mcp', async (c: Context) => {
     try {
       const server = getServer();
       const transport = new WebStandardStreamableHTTPServerTransport({ sessionIdGenerator: undefined });

       await server.connect(transport);

       return await transport.handleRequest(c.req.raw);
     } catch (error) {
       console.error('Error handling MCP request:', error);

       return c.json({
         jsonrpc: '2.0',
         error: { code: -32603, message: 'Internal server error' },
         id: null,
       }, 500);
     }
   });

   export default app
   ```

   Each request gets a new server and a new transport with no session ID, so the server keeps no state between requests. An `McpServer` instance connects to one transport at a time, so the code creates it in `getServer()` for each request. On an error, the handler logs the error and returns the JSON-RPC error `-32603`, `Internal server error`, with HTTP status `500`.

4. **Run the server locally**

   From the project folder, start the local development server:

   ```bash
   azion dev
   ```

   The command builds `src/index.ts` and serves the function on port `3333`:

   ```text
   …
   [Azion] [Build] › ℹ  info      Using preset: typescript
   [Azion] [Pre-Build] › ℹ  info      Starting pre-build...
   [Azion] [Pre-Build] › ℹ  info      Pre-build completed successfully
   [Azion] [Build] › ℹ  info      Using entry point(s): src/index.ts
   [Azion] [Build] › ℹ  info      Starting build...
   [Azion] [Build] › ✔  success   Build completed successfully
   [Azion] [Post-Build] › ℹ  info      Starting post-build...
   [Azion] [Post-Build] › ✔  success   Post-build completed successfully
   [Azion] [Server] › ✔  success   Function running on port 0.0.0.0:3333, url: http://localhost:3333
   [Azion] [Server] › ℹ  info      Initial scan complete. Ready for changes.
   ```

   The server keeps running until you stop it. For the flags of the command, refer to [Azion CLI dev](/en/documentation/devtools/cli/dev-command/).

5. **Check the server locally**

   In a second terminal, send an MCP `initialize` request to `http://localhost:3333/mcp`. The client must accept both JSON and an event stream:

   ```bash
   curl -s -i -X POST http://localhost:3333/mcp \
     -H 'Content-Type: application/json' \
     -H 'Accept: application/json, text/event-stream' \
     --data '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"curl","version":"1.0.0"}}}'
   ```

   The server answers with one event that holds its name and capabilities:

   ```text
   HTTP/1.1 200 OK
   cache-control: no-cache, no-transform
   connection: keep-alive
   content-type: text/event-stream
   x-accel-buffering: no
   Date: Thu, 01 Jan 2026 12:00:00 GMT
   Transfer-Encoding: chunked

   event: message
   data: {"result":{"protocolVersion":"2025-03-26","capabilities":{"tools":{"listChanged":true},"resources":{"listChanged":true}},"serverInfo":{"name":"azion-mcp-server","version":"1.0.0"}},"jsonrpc":"2.0","id":1}
   ```

   To list the tools, send a `tools/list` request to the same URL:

   ```bash
   curl -s -i -X POST http://localhost:3333/mcp \
     -H 'Content-Type: application/json' \
     -H 'Accept: application/json, text/event-stream' \
     --data '{"jsonrpc":"2.0","id":2,"method":"tools/list"}'
   ```

   The result lists the `add` tool with the input schema that the code declares:

   ```text
   HTTP/1.1 200 OK
   cache-control: no-cache, no-transform
   connection: keep-alive
   content-type: text/event-stream
   x-accel-buffering: no
   Date: Thu, 01 Jan 2026 12:00:00 GMT
   Transfer-Encoding: chunked

   event: message
   data: {"result":{"tools":[{"name":"add","title":"Addition Tool","description":"Add two numbers","inputSchema":{"type":"object","properties":{"a":{"type":"number"},"b":{"type":"number"}},"required":["a","b"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"},"execution":{"taskSupport":"forbidden"}}]},"jsonrpc":"2.0","id":2}
   ```

6. **Deploy the server**

   Stop the local server, or open a second terminal, and deploy the project from the project folder:

   ```bash
   azion deploy
   ```

   The CLI uploads and builds the project. At the end, it prints the URL of the project's domain, in the form `https://xxxxxxxxxx.map.azionedge.net`. For the deploy output and its flags, refer to [Azion CLI deploy](/en/documentation/devtools/cli/deploy/).

The MCP server runs on Azion's distributed infrastructure and answers at `https://<your-domain>/mcp`, where `<your-domain>` is the domain that `azion deploy` printed. The first deploy can take several minutes to answer from every location. Later deploys take about two minutes.

---

## Read the deploy configuration

`azion deploy` creates the resources that `azion.config.ts` declares, and `azion init` wrote that file from the Hono template. It builds `src/index.ts` with the `typescript` preset and with polyfills turned on. The build writes the function to `.edge/functions/index.js`, which the file names `./functions/index.js`. An application runs that function on every path through a request rule named `Execute Function`, and a workload serves the application:

```typescript
export default {
  build: {
    preset: 'typescript',
    entry: 'src/index.ts',
    polyfills: true
  },
  functions: [
    {
      name: '$FUNCTION_NAME',
      path: './functions/index.js'
    }
  ],
  applications: [
    {
      name: '$APPLICATION_NAME',
      rules: {
        request: [
          {
            name: 'Execute Function',
            description: 'Execute function for all requests',
            active: true,
            criteria: [
              [
                {
                  variable: '${uri}',
                  conditional: 'if',
                  operator: 'matches',
                  argument: '^/'
                }
              ]
            ],
            behaviors: [
              {
                type: 'run_function',
                attributes: {
                  value: '$FUNCTION_NAME'
                }
              }
            ]
          }
        ]
      },
      functionsInstances: [
        {
          name: '$FUNCTION_INSTANCE_NAME',
          ref: '$FUNCTION_NAME'
        }
      ]
    }
  ],
  workloads: [
    {
      name: '$WORKLOAD_NAME',
      active: true,
      infrastructure: 1,
      deployments: [
        {
          name: '$DEPLOYMENT_NAME',
          current: true,
          active: true,
          strategy: {
            type: 'default',
            attributes: {
              application: '$APPLICATION_NAME'
            }
          }
        }
      ]
    }
  ]
}
```

The CLI fills each `$` name when it creates the resource, and it records the resource IDs in `azion/azion.json`. For every setting in the file, refer to [azion.config.js](/en/documentation/devtools/cli/azion-config-js/).

A project folder that `azion init` did not create has neither file, and `azion build` refuses to build it with `Azion configuration not found`. Run `azion link` in that folder first, then `azion build` and `azion deploy`. For the questions `azion link` asks, refer to [Azion CLI link](/en/documentation/devtools/cli/link-command/).

---

## Connect a client to the server

An MCP client needs the URL of the server: `https://<your-domain>/mcp` after the deploy, or `http://localhost:3333/mcp` while `azion dev` runs. The path is `/mcp` because the server's Hono route is `app.post('/mcp', …)`.

Configure the client as for the Azion MCP servers, with your URL in place of `https://docs-mcp.azion.com/mcp`. For the client settings, refer to [MCP server quickstart](/en/documentation/devtools/mcp/quickstart/). The example server checks no credentials, so the client sends no `Authorization` header, and anyone with the URL can call its tools.

---

## Use the Server class

The SDK's low-level `Server` class replaces the register methods of `McpServer` with request handlers. You write the handler for each MCP method yourself, which gives you full control over each response. Each capability needs one handler that lists it and one that calls or reads it:

| Capability | `McpServer` method | `Server` list handler        | `Server` call or read handler |
| ---------- | ------------------ | ---------------------------- | ----------------------------- |
| Tools      | `registerTool`     | `ListToolsRequestSchema`     | `CallToolRequestSchema`       |
| Resources  | `registerResource` | `ListResourcesRequestSchema` | `ReadResourceRequestSchema`   |
| Prompts    | `registerPrompt`   | `ListPromptsRequestSchema`   | `GetPromptRequestSchema`      |

Register each handler with `server.setRequestHandler(<schema>, async (request) => { ... })`. The schemas come from `@modelcontextprotocol/sdk/types.js`, which also exports the schemas of the other MCP methods. For more information, refer to the [MCP SDK for TypeScript](https://github.com/modelcontextprotocol/typescript-sdk/).

The following `src/index.ts` serves the tools and prompts of the HubSpot MCP server package, `@hubspot/mcp-server`. It declares the `tools`, `prompts`, and `resources` capabilities and registers four handlers. The route, the transport, and the error handling are the same as in the `McpServer` server. Add the package to the project before you build it:

```bash
npm install @hubspot/mcp-server
```

```typescript
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { WebStandardStreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js';
import { CallToolRequestSchema, GetPromptRequestSchema, ListPromptsRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
import { getPrompts, getPromptMessages } from '@hubspot/mcp-server/dist/prompts/index.js';
import { getTools, handleToolCall } from '@hubspot/mcp-server/dist/tools/index.js';
import '@hubspot/mcp-server/dist/prompts/promptsRegistry.js';
import '@hubspot/mcp-server/dist/tools/toolsRegistry.js';
import { Hono } from 'hono';

// Create a server instance for each request
function getServer() {
    const server = new Server({
        name: 'azion-hubspot-mcp-server',
        version: '1.0.0',
    }, {
        capabilities: {
            tools: {},
            prompts: {},
            resources: {},
        },
    });

    // Handler for listing tools
    server.setRequestHandler(ListToolsRequestSchema, async () => {
        return {
            tools: getTools(),
        };
    });

    // Handler for calling tools
    server.setRequestHandler(CallToolRequestSchema, async (request) => {
        const { name, arguments: args } = request.params;
        return handleToolCall(name, args);
    });

    // Handler for listing prompts
    server.setRequestHandler(ListPromptsRequestSchema, async () => {
        return {
            prompts: getPrompts(),
        };
    });

    // Handler for getting specific prompt
    server.setRequestHandler(GetPromptRequestSchema, async (request) => {
        const { name, arguments: args } = request.params;
        return getPromptMessages(name, args);
    });

    return server;
}

// Create Hono app
const app = new Hono();

app.post('/mcp', async (c) => {
    try {
        const server = getServer();
        const transport = new WebStandardStreamableHTTPServerTransport({
            sessionIdGenerator: undefined,
        });

        await server.connect(transport);

        return await transport.handleRequest(c.req.raw);
    } catch (error) {
        console.error('Error handling MCP request:', error);
        return c.json({
            jsonrpc: '2.0',
            error: {
                code: -32603,
                message: 'Internal server error',
            },
            id: null,
        }, 500);
    }
});

export default app
```

The HubSpot package reads a HubSpot access token from the `PRIVATE_APP_ACCESS_TOKEN` environment variable. Without the variable, `azion dev` builds the project and then stops with `HubSpot access token is required`.

Run and deploy this server with the same `azion dev` and `azion deploy` commands as the `McpServer` server.

---

## Prepare the server for agents

An agent chooses a tool from its name and description, and calls it with the inputs its schema declares. Check these points before you give the URL to others:

| Area              | Practice                                                                                                                 |
| ----------------- | ------------------------------------------------------------------------------------------------------------------------ |
| Tool names        | Name each tool with a verb and a noun, such as `search_docs` or `create_rule`.                                           |
| Tool descriptions | Say when an agent uses the tool and how.                                                                                 |
| Tool inputs       | Declare a type for every parameter, and validate each value before you use it.                                           |
| Errors            | Return a message that says what failed.                                                                                  |
| Repeated calls    | Make each tool safe to call more than once.                                                                              |
| Responses         | Cache data that tools read often, paginate large results, set timeouts on long operations, and compress large responses. |
| Access            | Limit the number of requests each token can send, and log every sensitive operation.                                     |
| Output            | Remove sensitive data from a response before you return it.                                                              |

For a larger example of a server built with Hono and the MCP SDK, refer to the [aziontech/mcp-server repository](https://github.com/aziontech/mcp-server).

---

## Next steps

- [How the MCP server works](/en/documentation/devtools/mcp/how-it-works.md): See how the Azion MCP servers authenticate agents and answer their requests.
- [Tools and resources](/en/documentation/devtools/mcp/tools.md): Compare your server with the tools and resources the Azion MCP servers expose.
- [Azion CLI deploy](/en/documentation/devtools/cli/deploy.md): Read the deploy output and the flags that change a deploy.
- [Functions](/en/documentation/platform/functions.md): Learn how the function that runs your server works.
- [Deploy remote MCP servers](/en/documentation/use-cases/build-and-run-ai-workloads/deploy-remote-mcp-servers.md): Map an existing API to MCP tools, behind WAF and a rate limit, with a log line per tool call.
