diff --git a/.optimize-cache.json b/.optimize-cache.json index 91ce8a0ea92..5a35abcaec0 100644 --- a/.optimize-cache.json +++ b/.optimize-cache.json @@ -233,6 +233,8 @@ "static/images/blog/announcing-init-faster-smoother-better/init-ticket.png": "fe4e16ef27d3fcba378c52882ce3458aab3f1de84cb183d39db577e5264ef905", "static/images/blog/announcing-inversion-queries/cover.png": "232f806b8b655f469cb5398ba3abce2074e959d2fb49b9782b1889b22f1ee16e", "static/images/blog/announcing-list-cache-ttl/cover.png": "ca1554dc34d1222b86ccc295252af8e07b2f635b9b10e1227a21fff81138e409", + "static/images/blog/announcing-mcp-server-template/cover.png": "dde7e9ccefa808a87be43ebc6f80a9b676414eebcaf0293b700bce8e55071de5", + "static/images/blog/announcing-mcp-server-template/template.png": "b1646eb4855b86243e941b1c4041127ad7d231f8b7b3ca3255127b8cd163aeca", "static/images/blog/announcing-message-based-realtime-sdk/cover.jpg": "00559db66e7c1333bc9b9acc0784fd4d6fb2fc7c4e1964e3cbb93e49640323a4", "static/images/blog/announcing-new-push-notifications-features/cover.png": "a0c758cf6c8a95e09a0d2ca562b0775a50d34a4d691d675cda70e44ad21805ac", "static/images/blog/announcing-opt-in-relationship-loading/cover.png": "e16cc16ea6d968b29af19bcd6274741141584a7efe5e1bb18be19b77c3a380c8", @@ -641,6 +643,7 @@ "static/images/blog/february-and-march-product-update-realtime-queries-appwrite-skills-and-new-database-features/Introducing_Realtime_queries.png": "0b632e9ceac3a763f5ba2ed50ab54fffe389fde34fcdfb7ad99599ec7ae83b9e", "static/images/blog/february-and-march-product-update-realtime-queries-appwrite-skills-and-new-database-features/Relationship_queries.png": "2d9772691f05b1be3ec6cb954d77babc10d5666c9994fe3975d2a078facc87b8", "static/images/blog/file-tokens.png": "23d2fa4a88db2d9548f43f95df15b5ca60d512481570ed2d1b3d66ce1b1f504f", + "static/images/blog/financial-reporting-mcp-server/cover.png": "b0e9dfac0480c1715b8b985b879db26aef157cf31af4d99e8a9863ae180a3709", "static/images/blog/firebase-vs-open-source-tradeoffs/cover.png": "1be4185b4fa90c5f37e96003a74b8b55c31956d0258c70000fdec0990f2496eb", "static/images/blog/first-pr.png": "f369419a756ccb2c784dac916d79a1cc33317fa4c43f37c7f41ae62bf0a603dd", "static/images/blog/first-pr1.png": "256144fd88d0564c239cb73c16882113e4fe2cb23156fcbc4f15f6f70437faaf", diff --git a/src/routes/blog/post/announcing-mcp-server-template/+page.markdoc b/src/routes/blog/post/announcing-mcp-server-template/+page.markdoc new file mode 100644 index 00000000000..4baed8e6af7 --- /dev/null +++ b/src/routes/blog/post/announcing-mcp-server-template/+page.markdoc @@ -0,0 +1,156 @@ +--- +layout: post +title: "Build and deploy an MCP server with Appwrite Functions" +description: Deploy your own MCP server on Appwrite Functions with the new Python template. Expose custom tools to Claude Code, Cursor, and other AI clients over HTTPS. +date: 2026-08-06 +cover: /images/blog/announcing-mcp-server-template/cover.avif +timeToRead: 6 +author: chirag-aggarwal +category: announcement, ai +featured: false +faqs: + - question: "What is the MCP server template for Appwrite Functions?" + answer: "It is a function template that deploys a stateless Model Context Protocol server over HTTPS, built with the official MCP Python SDK. It ships with two demo tools (`echo` and `add`), optional bearer authentication, and a package structure ready for your own tools. You can create it from the Appwrite Console under **Functions** > **Templates**." + - question: "How do I connect Claude Code or Cursor to an MCP server hosted on Appwrite?" + answer: "Deploy the template and copy your function's domain. In Claude Code, run `claude mcp add --transport http my-mcp https://.appwrite.run`. In Cursor or Claude Desktop, add the URL to the `mcpServers` section of your MCP configuration. The server speaks Streamable HTTP, so there is no local process to run." + - question: "How do I add my own tools to the MCP server template?" + answer: "Register tools in `src/app.py` with the `@server.tool` decorator. Python type hints on the function's parameters become the tool's `inputSchema` automatically. Push the change to your connected repository, and once the new deployment is live, clients pick up the updated tool list." + - question: "How do I secure an MCP server running on Appwrite Functions?" + answer: "Set the `MCP_AUTH_MODE` environment variable to `bearer` and `MCP_AUTH_TOKEN` to a long random secret. The server then rejects any request without a matching `Authorization: Bearer` header, using constant-time comparison. Clients pass the token through the `headers` field of their MCP configuration." + - question: "Does the template support the stateless MCP 2026-07-28 specification?" + answer: "Yes. The template handles both the legacy handshake used by clients on protocol version `2025-06-18` and the modern stateless protocol introduced in the [MCP 2026-07-28 specification](/blog/post/mcp-goes-stateless-in-the-2026-07-28-specification). Appwrite Functions are stateless request/response workers, which matches the direction the protocol has taken." + - question: "What are the limitations of the MCP server template?" + answer: "Two main ones. The server is stateless JSON over HTTPS, so there is no SSE streaming, no progress updates during tool calls, and no server-initiated messages like sampling. And tool calls run as [synchronous executions](/docs/products/functions/execute#synchronous-executions), which Appwrite caps at 30 seconds. Keep `MCP_TOOL_TIMEOUT` at its default of 25 seconds so a slow tool returns a clean JSON-RPC error before the cap. Long-running work should start a job and return a handle that another tool can poll." + - question: "Is this the same as the Appwrite MCP server?" + answer: "No. The [Appwrite MCP server](/docs/tooling/ai/mcp-servers) gives AI tools access to Appwrite's own APIs, like creating users or managing databases, through `https://mcp.appwrite.io/`. The MCP server template is for building and hosting your own MCP server with your own custom tools on Appwrite Functions." +--- + +The Model Context Protocol has become the standard way to give AI tools new capabilities. Claude Code, Cursor, Claude Desktop, and most other AI clients speak it, and when an agent needs a skill it does not have, the answer is an MCP server. Writing one is the easy part: with the official SDKs, a tool is a decorated function. Hosting one is where the friction starts. You need an HTTPS endpoint, authentication, deployments, and infrastructure that scales, all for what is often fifty lines of code. + +Today, we are announcing the **MCP server template** for Appwrite Functions. It deploys a working, stateless MCP server over HTTPS, built with the official MCP Python SDK, that you can connect to Claude Code, Cursor, and any other MCP client in minutes. You will find it in the Appwrite Console under **Functions** > **Templates**. + +# What you get out of the box + +- **A working MCP server**: the template ships with two demo tools, `echo` and `add`, so you can verify the full loop from AI client to hosted server before writing any code. +- **The official Python SDK**: tools are plain Python functions registered with a decorator. Type hints become each tool's `inputSchema` automatically. +- **Streamable HTTP transport**: stateless JSON-RPC over your function's domain, supporting both the legacy MCP handshake and the stateless protocol from the [MCP 2026-07-28 specification](/blog/post/mcp-goes-stateless-in-the-2026-07-28-specification). +- **Optional bearer authentication**: one environment variable gates the endpoint behind an `Authorization` header. +- **Nothing to operate**: your function's domain is the server URL. TLS, deployments, scaling, and logs all come from [Appwrite Functions](/docs/products/functions). + +The template pins the MCP Python SDK to `mcp==2.0.0`. Version 2.0.0 [became stable on July 28, 2026](https://github.com/modelcontextprotocol/python-sdk/releases/tag/v2.0.0), so it is still a new major release. The exact pin is deliberate because the template's serverless adapter uses lower-level SDK entry points that can change between releases. Review compatibility before upgrading it. + +# Deploy an MCP server from the Console + +1. In the [Appwrite Console](https://cloud.appwrite.io), open your project and head to **Functions** > **Templates**. + +2. Search for **MCP server** and click **Create**. + +![Appwrite MCP server template](/images/blog/announcing-mcp-server-template/template.avif) + +3. Optionally set environment variables: `MCP_SERVER_NAME` for the display name clients see, `MCP_AUTH_MODE` and `MCP_AUTH_TOKEN` for bearer authentication, and `MCP_TOOL_TIMEOUT` for a soft deadline on tool calls (best kept at the 25-second default). All of them have sensible defaults. + +4. Optionally, connect a GitHub repository. You can connect one later through the function's settings page too. + +5. Deploy the function. + +Once the deployment is live, your function's domain is your MCP server URL: + +``` +https://.appwrite.run +``` + +There is no separate hosting step and no reverse proxy to configure. The domain serves MCP directly. + +# Connect it to your AI tools + +In Claude Code, adding the server is one command: + +```bash +claude mcp add --transport http my-mcp https://.appwrite.run +``` + +In Cursor or Claude Desktop, add the URL to your MCP configuration: + +```json +{ + "mcpServers": { + "my-mcp": { + "url": "https://.appwrite.run" + } + } +} +``` + +That is the whole setup. Ask your agent to list the server's available tools, then call one to see the request travel from your editor to a serverless function and back. + +# Write your own tools + +The demo tools exist to be replaced. Tools live in `src/app.py`, and each one is a Python function registered with a decorator: + +```python +# src/app.py +from mcp.server.mcpserver import MCPServer + +server = MCPServer(name="my-mcp", version="0.1.0") + +@server.tool(description="Do something useful.") +def my_tool(query: str) -> str: + return f"got: {query}" +``` + +Type hints become the tool's `inputSchema`, so clients know exactly what arguments to send. Push the change to your connected repository, and Appwrite builds and activates the new deployment automatically. + +Because the server runs inside your Appwrite project, every request carries a [dynamic API key](/docs/products/functions/develop#dynamic-api-key) in its headers. A tool that needs your data can initialize the Appwrite SDK with that key and query your databases, storage, or users, without you creating, scoping, or rotating any credentials. + +# Secure the endpoint + +By default, the endpoint is open, which is convenient while you experiment. Before you expose anything internal, set two environment variables on the function: `MCP_AUTH_MODE=bearer` and `MCP_AUTH_TOKEN` set to a long random secret. The server then rejects every request that does not carry the matching header, comparing tokens in constant time. + +On the client side, pass the token in your MCP configuration: + +```json +{ + "mcpServers": { + "my-mcp": { + "url": "https://.appwrite.run", + "headers": { + "Authorization": "Bearer your-long-random-secret" + } + } + } +} +``` + +# Built for the stateless MCP era + +For most of its life, MCP was a stateful protocol, and it fought serverless hosting. Sessions had to land on the same instance, and the transport leaned on long-lived SSE streams. The [MCP 2026-07-28 specification](/blog/post/mcp-goes-stateless-in-the-2026-07-28-specification) changed that by making the protocol core stateless: every request is self-describing, and any request can be handled by any instance. + +That shift makes request/response platforms like Appwrite Functions a natural home for MCP servers, and the template meets the protocol on both sides of the transition. It answers the legacy `initialize` handshake for clients still on protocol version `2025-06-18`, and it handles the modern stateless path for clients that have moved to `2026-07-28`. + +# When to reach for this template + +The template is the right starting point when you want AI agents to call tools that are yours: + +- **Internal team tools**: look up orders, query a product database, check the status of a job, or trigger a workflow, all from a chat in your editor. +- **A controlled wrapper around an existing API**: instead of handing an agent raw API credentials, expose three or four specific operations with your validation in the middle. +- **Tools over your Appwrite data**: with the dynamic API key, tools can read and write your project's databases and storage without extra configuration. + +## What it does not do + +The template makes two deliberate trade-offs, and they are worth knowing before you build on it: + +- **No streaming.** The server is stateless JSON over HTTPS. There is no SSE support, so tools cannot stream progress while they run, and the server cannot send server-initiated messages like sampling requests back to the client. A tool call returns once, with its final result. +- **No long-running tool calls.** Requests to a function's domain are [synchronous executions](/docs/products/functions/execute#synchronous-executions), which Appwrite caps at 30 seconds. We recommend keeping `MCP_TOOL_TIMEOUT` at its default of 25 seconds, so a slow tool returns a clean JSON-RPC error before the platform cuts the connection at 30. For work that needs minutes, have one tool start the job and return a handle that a second tool can poll. + +If streaming or long-running synchronous calls are hard requirements, a container-based host is the better fit for that server today. And if what you want is for AI tools to manage Appwrite itself, creating users, provisioning databases, or deploying functions, you do not need to build anything: the hosted [Appwrite MCP server](/docs/tooling/ai/mcp-servers) already does that with one URL. + +# Start building your MCP server + +The MCP server template is available now on [Appwrite Cloud](https://cloud.appwrite.io) and for self-hosted instances. Open your project, head to **Functions** > **Templates**, search for **MCP server**, and you will have a live endpoint your AI tools can call in a few minutes. If you are new to the Model Context Protocol, our [complete guide to MCP](/blog/post/what-is-mcp-a-complete-guide-for-developers) covers how the protocol works before you start building on it. + +To see the template applied to a realistic use case, follow our [financial analysis MCP server tutorial](/blog/post/financial-analysis-mcp-server). It shows how to turn account and transaction data into controlled tools for portfolio summaries, statements, spending analysis, and cash-flow reports. + +- [Build an MCP server guide](/docs/tooling/ai/build-mcp-server) +- [Appwrite Functions development guide](/docs/products/functions/develop) +- [MCP server template source on GitHub](https://github.com/appwrite/templates/tree/main/python/mcp-server) +- [What's new in the MCP 2026-07-28 specification](/blog/post/mcp-goes-stateless-in-the-2026-07-28-specification) diff --git a/src/routes/blog/post/financial-analysis-mcp-server/+page.markdoc b/src/routes/blog/post/financial-analysis-mcp-server/+page.markdoc new file mode 100644 index 00000000000..935f500af1a --- /dev/null +++ b/src/routes/blog/post/financial-analysis-mcp-server/+page.markdoc @@ -0,0 +1,292 @@ +--- +layout: post +title: "Build a financial analysis MCP server with Appwrite Functions" +description: Learn how to build a financial analysis MCP server with Appwrite Functions, TablesDB, and custom tools for statements, cash flow, and portfolio insights. +date: 2026-08-06 +cover: /images/blog/financial-analysis-mcp-server/cover.avif +timeToRead: 8 +author: aditya-oberai +category: tutorial, ai +featured: false +faqs: + - question: "What does the Appwrite MCP server template include?" + answer: "The template includes a Python MCP server, stateless Streamable HTTP transport, optional bearer authentication, configurable tool timeouts, and the adapter needed to run the server as an Appwrite Function. Replace the example tools with your own domain-specific operations." + - question: "How does the Appwrite MCP server differ from the MCP server template?" + answer: "The [Appwrite MCP server](/docs/tooling/ai/mcp-servers/api) lets an AI agent manage Appwrite resources, so you can use it to create the database schema and seed demo data. The MCP server template lets you build and host your own domain-specific server, such as the financial analysis server in this tutorial." + - question: "Which AI clients can connect to an MCP server hosted on Appwrite?" + answer: "Any MCP client that supports remote servers over Streamable HTTP can connect to the Function's generated domain. This includes clients such as Claude Code, Cursor, and Claude Desktop. If bearer authentication is enabled, configure the client to send the token in its `Authorization` header." + - question: "What does the financial analysis MCP server in this tutorial do?" + answer: "It exposes controlled reporting operations as tools that an AI client can call. The server runs as an Appwrite Function and defines eight read-only tools over financial data in TablesDB, including portfolio summaries, account statements, spending by category, monthly cash flow, and transaction search. The AI client gets useful reports without unrestricted database access." + - question: "How does the financial analysis function access Appwrite TablesDB?" + answer: "Appwrite passes a dynamic API key to each Function execution in the `x-appwrite-key` request header. The server uses that key with the supplied project endpoint and project ID. The demo limits the key to `databases.read`, `tables.read`, and `rows.read`, which allows reporting without granting write access." + - question: "How should a financial MCP server handle multiple currencies?" + answer: "Keep totals grouped by currency unless you have an explicit exchange-rate source and conversion policy. The demo reports USD, EUR, and GBP separately so it never adds unlike monetary values or implies a conversion rate that was not supplied." + - question: "Can I use this MCP server with real financial data?" + answer: "The architecture can be adapted for real data, but the demo is not a production banking system. Before using sensitive data, add user-specific authorization, audit logging, data minimization, stricter secret management, and compliance controls appropriate to your jurisdiction and use case." +--- + +MCP is moving AI beyond isolated chat experiences. It gives models a standard, controlled way to work with the systems that run real products, from support platforms and internal operations to commerce and financial analysis. Instead of pasting data into a prompt, developers can expose focused tools that retrieve live information, enforce business rules, and return structured results. + +We wanted to test that impact with a scenario where accuracy and controlled access matter. Using our new [MCP server Function template](/blog/post/announcing-mcp-server-template), we developed a **financial analysis MCP server** that can summarize a portfolio, generate account statements, analyze spending, calculate monthly cash flow, and search transactions from an AI client. + +We built the entire scenario on Appwrite. First, we used the [Appwrite MCP server](/docs/tooling/ai/mcp-servers/api) to create a connected financial data model and populate it with realistic dummy data. Then we deployed the MCP server template as an Appwrite Function and extended its example tools into a read-only reporting layer over that data. + +# How we built the financial analysis MCP server + +The project uses two MCP servers with different responsibilities: + +- **Appwrite MCP server:** Connects your AI coding agent to Appwrite so it can create the TablesDB schema and seed test data. +- **Your financial analysis MCP server:** Runs as an Appwrite Function and exposes only the reporting tools you define. + +The second server never needs a long-lived Appwrite API key. Appwrite Functions receive a dynamic API key for each execution, and its permissions are limited by the Function scopes you select. This lets the AI client request useful reports without receiving unrestricted database access. + +You will need an [Appwrite Cloud account](https://cloud.appwrite.io), an Appwrite project, a GitHub account, and an MCP-compatible client such as Claude Code, Cursor, or OpenAI Codex. + +# Test our financial analysis MCP server + +You can try the completed server before building your own. Add this configuration to an MCP client that supports Streamable HTTP, such as Cursor or Claude Desktop: + +```json +{ + "mcpServers": { + "finance-mcp": { + "url": "https://6a73369c003d9823a215.fra.appwrite.run/", + "headers": { + "Authorization": "Bearer test-string-123" + } + } + } +} +``` + +The bearer token is intentionally public because this server contains only fictional demo data. Do not reuse the token for another deployment. Once connected, ask for a portfolio summary, generate a 30-day statement for `acc-001`, or compare spending and monthly cash flow for `cust-001`. + +The complete implementation, Appwrite configuration, tool definitions, and additional test prompts are available in the [GitHub repository](https://github.com/appwrite-community/finance-mcp-demo). + +# Model a real financial scenario with the Appwrite MCP server + +Before developing the reporting tools, we needed an Appwrite project that reflected the structure and edge cases of a real financial system. We connected the hosted Appwrite MCP server to our AI client so it could create and populate that backend through natural-language instructions. + +Add the hosted Appwrite MCP server to your AI client: + +```json +{ + "mcpServers": { + "appwrite": { + "type": "http", + "url": "https://mcp.appwrite.io/" + } + } +} +``` + +When you connect for the first time, sign in with your Appwrite account and authorize the project you want to use. The hosted server uses OAuth, so you do not need to create an API key for this step. + +We created a TablesDB database named `FinDB` with the ID `findb`. Its five tables model the reporting relationships we needed: + +| Table | Important columns | Relationship | +| --- | --- | --- | +| `customers` | `fullName`, `email`, `phone`, `dateOfBirth`, `kycStatus`, `country` | One customer has many accounts | +| `accounts` | `accountNumber`, `accountType`, `balance`, `currency`, `accountStatus`, `openedDate` | Belongs to a customer | +| `cards` | `cardNumberMasked`, `cardType`, `cardNetwork`, `expiryDate`, `cardStatus` | Belongs to an account | +| `categories` | `name`, `categoryType`, `description` | Groups transactions | +| `transactions` | `transactionType`, `amount`, `transactionDate`, `transactionStatus` | Belongs to an account and category | + +Ask your agent to create the schema with a prompt like this: + +```text +In my Appwrite project , create a TablesDB database named FinDB +with the ID findb and these tables: + +- customers: fullName, email, phone, dateOfBirth, kycStatus, and country +- accounts: accountNumber, accountType, balance, currency, accountStatus, + and openedDate +- cards: cardNumberMasked, cardType, cardNetwork, expiryDate, and cardStatus +- categories: name, categoryType, and description +- transactions: transactionType, amount, transactionDate, and transactionStatus + +Use the enum values verified|pending|rejected for KYC; checking|savings|credit +for account type; active|frozen|closed for account status; debit|credit for card +and transaction type; visa|mastercard|amex for card network; +active|blocked|expired for card status; income|expense for category type; +completed|pending|failed for transaction status; and USD|EUR|GBP for currency. + +Add two-way one-to-many relationships from customers to accounts, accounts to +cards, accounts to transactions, and categories to transactions. Add unique +indexes for customer email, account number, and category name. + +Before changing the project, show me the proposed schema. After I approve it, +create the tables and verify that every relationship and index is available. +``` + +This lets the agent translate the model into API calls while giving you a review point before it changes the project. + +## Replicate a real situation with fictional data + +We developed this fictional database specifically for the tutorial because a public demo should never expose real customer or transaction data. The scenario itself is not a toy model. Its customers, accounts, cards, categories, relationships, account states, transaction states, currencies, and reporting requirements replicate what a possible real-world financial application could contain. + +We then asked the agent to populate the tables with synthetic but internally consistent records: + +```text +Seed FinDB with fictional financial data for reporting tests. Create customers +across several countries, one or two accounts per customer, a mix of checking, +savings, and credit accounts, masked card numbers, income and expense +categories, and twelve months of transactions. + +Keep every relationship valid. Include active, frozen, and closed accounts, +and completed, pending, and failed transactions. Distribute account currencies +across USD, EUR, and GBP. Never create real personal or payment-card data. +Create and verify the data in manageable batches. +``` + +The resulting demo contains 30 customers, 47 accounts, 32 cards, 19 categories, and more than 5,000 transactions. You can start smaller. What matters is enough variation to test filters, date ranges, relationships, and aggregations in a realistic reporting workflow. + +# Set up the MCP server Function template + +In the Appwrite Console, open **Functions** > **Templates**, search for **MCP server**, and create the Python Function. Connect it to a new GitHub repository so each push produces a deployment. The [Function templates documentation](/docs/products/functions/templates) explains the repository and production-branch options in the creation wizard. + +![Appwrite MCP server Function template](/images/blog/announcing-mcp-server-template/template.avif) + +Configure the Function with these settings: + +| Setting | Value | +| --- | --- | +| Runtime | Python 3.14 | +| Entrypoint | `src/main.py` | +| Build command | `pip install -r requirements.txt` | +| Timeout | 30 seconds | +| Execute permission | Any | +| Scopes | `databases.read`, `tables.read`, `rows.read` | + +Set `MCP_AUTH_MODE` to `bearer`, add a long random value as `MCP_AUTH_TOKEN`, and set `MCP_SERVER_NAME` to `finance-mcp`. The HTTP endpoint is public at the Function layer because MCP clients call its domain directly, while the bearer token protects the MCP request itself. + +Also set `FINDB_DATABASE_ID=findb` if you changed the default in the code. Keep `MCP_TOOL_TIMEOUT` at 25 seconds so a slow tool can return a clean error before the Function reaches its 30-second execution limit. + +# Extend the template with financial analysis tools + +The template includes `echo` and `add` tools to prove the transport works. Replace them in `src/app.py` with a server configured for financial analysis: + +```python +import os +from mcp.server.mcpserver import Context, MCPServer + +DATABASE_ID = os.environ.get("FINDB_DATABASE_ID") or "findb" + +server = MCPServer( + name=os.environ.get("MCP_SERVER_NAME") or "findb-reporting", + version="1.0.0", + instructions=( + "Read-only financial analysis over customers, accounts, cards, " + "categories, and transactions. Group totals by currency and never " + "convert currencies." + ), +) +``` + +These instructions help the model choose and combine tools correctly, but each tool must still validate its own inputs. + +## Query TablesDB with the dynamic API key + +Every tool needs a small request layer for the TablesDB REST API. Read the dynamic key from the MCP context and combine it with the endpoint and project ID supplied to the Function: + +```python +def appwrite_headers(ctx: Context) -> dict[str, str]: + api_key = (ctx.headers or {}).get("x-appwrite-key") + if not api_key: + raise ValueError("No dynamic Appwrite API key was provided") + + return { + "X-Appwrite-Project": os.environ["APPWRITE_FUNCTION_PROJECT_ID"], + "X-Appwrite-Key": api_key, + "Content-Type": "application/json", + } +``` + +The key can only use the read scopes configured on the Function. Even if a prompt asks a reporting tool to change a balance, Appwrite rejects the write because the Function has no row-write scope. See the [Functions development guide](/docs/products/functions/develop#dynamic-api-key) for details on dynamic keys. + +## Define narrow tools with useful descriptions + +MCP turns Python type hints into an input schema. A tool should describe the report it returns, its filters, accepted values, and important defaults: + +```python +@server.tool( + description=( + "List customers with basic profile fields. Filter by KYC status " + "or country. Returns at most 100 entries." + ) +) +def list_customers( + ctx: Context, + kyc_status: str | None = None, + country: str | None = None, + limit: int = 25, + offset: int = 0, +) -> dict: + limit = max(1, min(limit, 100)) + # Build Appwrite queries, call TablesDB, and return selected fields. +``` + +The [finance MCP demo](https://github.com/appwrite-community/finance-mcp-demo) implements eight read-only tools: + +| Tool | Report | +| --- | --- | +| `portfolio_summary` | Counts, balances by currency and account type, transaction status, and date coverage | +| `list_customers` | Customer profiles filtered by KYC status or country | +| `customer_overview` | One customer's accounts, cards, balances, and recent activity | +| `account_statement` | Transactions and totals for an account and date range | +| `spending_by_category` | Completed debit spending or credit income grouped by category | +| `monthly_cash_flow` | Monthly inflow, outflow, and net cash flow | +| `search_transactions` | Transactions filtered by account, category, status, type, amount, or date | +| `list_accounts` | Accounts filtered by customer, type, or status | + +Use [Appwrite queries](/docs/products/databases/queries) to filter and select only the columns each report needs. For aggregations, page through matching rows and calculate totals inside the Function. Cap list results, validate date strings, and require either an account ID or customer ID when a report accepts both scopes. + +Most importantly, do not add USD, EUR, and GBP into one number. The demo groups every balance and cash-flow total by currency. Without an exchange-rate source and a stated conversion time, a combined total would be misleading. + +# Deploy and connect the financial MCP server + +Commit your changes and push them to the production branch. Appwrite builds and deploys the Function automatically. When the deployment is active, copy its generated domain and add it to your MCP client: + +```json +{ + "mcpServers": { + "finance-mcp": { + "type": "http", + "url": "https://..appwrite.run/", + "headers": { + "Authorization": "Bearer " + } + } + } +} +``` + +Restart or reconnect the client, then ask it to list the available tools. Test simple reports before trying a request that requires several tools: + +```text +Give me a portfolio summary with balances separated by currency. + +Generate a 30-day statement for account acc-001. + +Compare customer cust-001's balances, spending by category, and monthly cash +flow. Keep currencies separate and state which tools you used. +``` + +The final prompt lets the client combine deterministic reports into a readable answer without receiving unrestricted access to TablesDB. + +# Harden financial analysis before using real data + +This project uses fictional data, shared bearer authentication, and whole-dataset reporting. Treat it as a technical pattern, not a production banking system. + +For real financial data, add identity-aware authorization so each caller can access only permitted customers and accounts. Minimize personally identifiable information in tool responses, keep audit logs, rotate secrets, and review the regulatory requirements for your application. You should also move large analytical workloads to a dedicated reporting pipeline instead of scanning thousands of operational rows during a synchronous Function request. + +The design principle still holds: expose the smallest reporting capability an agent needs, enforce access in code and Appwrite scopes, and return structured evidence the client can explain. + +# Build your agentic apps with Appwrite + +Build agentic apps on [Appwrite Cloud](https://cloud.appwrite.io) with data, permissions, and Functions. Explore the [Appwrite AI docs](/docs/tooling/ai) for implementation guides. Configure the backend with the Appwrite MCP server, then deploy the template with your agent's actions. Adapt the pattern to support, commerce, operations, and other workflows. + +- [Appwrite MCP server documentation](/docs/tooling/ai/mcp-servers/api) +- [MCP server template announcement](/blog/post/announcing-mcp-server-template) +- [Appwrite database queries](/docs/products/databases/queries) +- [Financial analysis MCP demo](https://github.com/appwrite-community/finance-mcp-demo) diff --git a/src/routes/changelog/(entries)/2026-08-06.markdoc b/src/routes/changelog/(entries)/2026-08-06.markdoc new file mode 100644 index 00000000000..76be882b65a --- /dev/null +++ b/src/routes/changelog/(entries)/2026-08-06.markdoc @@ -0,0 +1,22 @@ +--- +layout: changelog +title: "Deploy your own MCP server with the new Functions template" +date: 2026-08-06 +cover: /images/blog/announcing-mcp-server-template/cover.avif +--- + +The new **MCP server** [function template](/docs/products/functions/templates) deploys a stateless [Model Context Protocol](https://modelcontextprotocol.io/) server over HTTPS, built with the official MCP Python SDK. It ships with two demo tools, optional **bearer authentication**, and support for both the legacy MCP handshake and the stateless `2026-07-28` protocol. + +Create it from the Console under **Functions** > **Templates**, and your function's domain becomes an MCP endpoint you can connect to Claude Code, Cursor, and other AI clients: + +```bash +claude mcp add --transport http my-mcp https://.appwrite.run +``` + +Register your own tools in `src/app.py` with the `@server.tool` decorator, and use the [dynamic API key](/docs/products/functions/develop#dynamic-api-key) to build tools that read and write your Appwrite project's data. + +Two trade-offs to know: the server is stateless JSON only, so there is no SSE streaming or server-initiated messages, and tool calls are synchronous with a **30-second execution cap** (25-second soft deadline by default). The announcement covers both in more detail. + +{% arrow_link href="/blog/post/announcing-mcp-server-template" %} +Read the announcement +{% /arrow_link %} diff --git a/src/routes/docs/+page.svelte b/src/routes/docs/+page.svelte index 29767b8e501..154bfbb3e42 100644 --- a/src/routes/docs/+page.svelte +++ b/src/routes/docs/+page.svelte @@ -58,6 +58,13 @@ logoLight: '/images/docs/mcp/logos/vscode.svg', event: 'docs-ai-ide_vscode-click' }, + { + href: '/docs/tooling/ai/agents/zed', + title: 'Zed', + logoDark: '/images/docs/mcp/logos/dark/zed.svg', + logoLight: '/images/docs/mcp/logos/zed.svg', + event: 'docs-ai-ide_zed-click' + }, { href: '/docs/tooling/mcp/opencode', title: 'OpenCode', @@ -71,6 +78,13 @@ logoDark: '/images/docs/mcp/logos/dark/google-antigravity.svg', logoLight: '/images/docs/mcp/logos/google-antigravity.svg', event: 'docs-ai-ide_antigravity-click' + }, + { + href: '/docs/tooling/ai/agents/grok-build', + title: 'Grok Build', + logoDark: '/images/docs/mcp/logos/dark/grok-build.svg', + logoLight: '/images/docs/mcp/logos/grok-build.svg', + event: 'docs-ai-ide_grok-build-click' } ]; diff --git a/src/routes/docs/Sidebar.svelte b/src/routes/docs/Sidebar.svelte index e63faa47152..b36a28d1d05 100644 --- a/src/routes/docs/Sidebar.svelte +++ b/src/routes/docs/Sidebar.svelte @@ -154,6 +154,12 @@ icon: 'icon-sparkles', isParent: true }, + { + label: 'MCP server', + href: '/docs/tooling/ai/mcp-servers', + icon: 'icon-globe-alt', + isParent: true + }, { label: 'CLI', href: '/docs/tooling/command-line/installation', diff --git a/src/routes/docs/products/functions/templates/+page.markdoc b/src/routes/docs/products/functions/templates/+page.markdoc index 69ff3d97dda..0b3d3e52f74 100644 --- a/src/routes/docs/products/functions/templates/+page.markdoc +++ b/src/routes/docs/products/functions/templates/+page.markdoc @@ -102,6 +102,10 @@ A comment is made to your PR about the build, unless you enable **Silent mode**. * Generate PDFs programmatically with Appwrite Functions. * Node.js --- +* MCP server +* Expose custom tools to AI clients over HTTPS using the Model Context Protocol. +* Python +--- * Payments with Stripe * Receive card payments and store paid orders. * Node.js diff --git a/src/routes/docs/tooling/ai/+layout.svelte b/src/routes/docs/tooling/ai/+layout.svelte index 0ce74952a64..c868501ed99 100644 --- a/src/routes/docs/tooling/ai/+layout.svelte +++ b/src/routes/docs/tooling/ai/+layout.svelte @@ -25,7 +25,7 @@ label: 'Tooling', items: [ { - label: 'MCP servers', + label: 'MCP server', href: '/docs/tooling/ai/mcp-servers' }, { @@ -111,6 +111,10 @@ { label: 'Guides', items: [ + { + label: 'Build an MCP server', + href: '/docs/tooling/ai/build-mcp-server' + }, { label: 'AI in Functions', href: '/docs/tooling/ai/ai-in-functions' diff --git a/src/routes/docs/tooling/ai/+page.markdoc b/src/routes/docs/tooling/ai/+page.markdoc index f3c8c8d78ec..6d0444e177f 100644 --- a/src/routes/docs/tooling/ai/+page.markdoc +++ b/src/routes/docs/tooling/ai/+page.markdoc @@ -146,6 +146,9 @@ Open-source benchmark evaluating how well AI models understand Appwrite's APIs a Guides for building AI-powered features on top of Appwrite, from running models in Functions to building full agent pipelines. {% cards %} +{% cards_item href="/docs/tooling/ai/build-mcp-server" title="Build an MCP server" %} +Expose custom tools to AI clients over HTTPS with the MCP server Function template. +{% /cards_item %} {% cards_item href="/docs/tooling/ai/ai-in-functions" title="AI in Functions" %} Run AI models inside Appwrite Functions with providers like OpenAI and Anthropic. {% /cards_item %} diff --git a/src/routes/docs/tooling/ai/build-mcp-server/+page.markdoc b/src/routes/docs/tooling/ai/build-mcp-server/+page.markdoc new file mode 100644 index 00000000000..c9b4eb35f14 --- /dev/null +++ b/src/routes/docs/tooling/ai/build-mcp-server/+page.markdoc @@ -0,0 +1,215 @@ +--- +layout: article +title: Build an MCP server +description: Build and deploy a custom Model Context Protocol server on Appwrite Functions using the Python MCP server template. +--- + +The Appwrite MCP server template lets you expose your own tools to AI clients over HTTPS. It provides the Model Context Protocol (MCP) transport, request handling, optional bearer authentication, and an Appwrite Function entrypoint. You only need to define the tools and the application logic behind them. + +Use this guide when you want an AI agent to call operations that belong to your application, such as looking up an order, querying a reporting database, checking a job, or triggering a controlled workflow. + +{% info title="Appwrite MCP server or MCP server template?" %} +The hosted [Appwrite MCP server](/docs/tooling/ai/mcp-servers/api) lets AI agents manage Appwrite projects and search Appwrite documentation. The template in this guide creates a separate MCP server with tools that you design and host. +{% /info %} + +# Prerequisites {% #prerequisites %} + +- An [Appwrite Cloud](https://cloud.appwrite.io) project +- A GitHub account for the Function repository +- An MCP client that supports remote servers over Streamable HTTP, such as Claude Code, Cursor, or Claude Desktop + +{% section #create-function step=1 title="Create the MCP server Function" %} + +1. Open your project in the Appwrite Console. +1. Select **Functions** in the sidebar, then select **Templates**. +1. Search for **MCP server** and select **Create**. +1. Choose a name and Python runtime for the Function. +1. Connect a new or existing GitHub repository. +1. Select the production branch and root directory, then create the Function. + +Appwrite deploys a working MCP server with two sample tools, `echo` and `add`. After the first deployment becomes active, the Function's generated domain is the URL your MCP clients will use: + +```text +https://..appwrite.run +``` + +The Function uses `src/main.py` as its entrypoint and installs dependencies from `requirements.txt`. The template pins the official Python MCP SDK because its serverless adapter depends on specific SDK behavior. + +{% /section %} + +{% section #understand-template step=2 title="Review the template" %} + +The generated repository contains these important files: + +| Path | Purpose | +| --- | --- | +| `src/main.py` | Appwrite Function entrypoint that passes requests to the MCP adapter. | +| `src/app.py` | MCP server definition and tools. Edit this file to add your application logic. | +| `src/appwrite_mcp/` | Stateless HTTP transport and bearer-authentication helpers. | +| `requirements.txt` | Python dependencies, including the pinned MCP SDK. | + +The default tools in `src/app.py` demonstrate how the server maps Python functions to MCP tools: + +```python +from mcp.server.mcpserver import MCPServer + +server = MCPServer(name="my-mcp", version="0.1.0") + +@server.tool(description="Echo text back to the caller.") +def echo(text: str) -> str: + return text + +@server.tool(description="Add two numbers.") +def add(a: float, b: float) -> float: + return a + b +``` + +Python type hints become the tool's input schema. Tool descriptions help the model decide when to call each tool and which arguments to provide. + +{% /section %} + +{% section #write-tools step=3 title="Define your tools" %} + +Replace the sample tools with small, explicit operations. Each tool should have one responsibility, validate its inputs, and return structured data when the result contains multiple fields. + +This example calculates an order total without giving the agent control over how the calculation works: + +```python +from mcp.server.mcpserver import MCPServer + +server = MCPServer( + name="order-tools", + version="1.0.0", + instructions="Tools for validated order calculations.", +) + +@server.tool( + description=( + "Calculate an order total from a unit price, quantity, and optional " + "discount percentage between 0 and 100." + ) +) +def calculate_order_total( + unit_price: float, + quantity: int, + discount_percent: float = 0, +) -> dict: + if unit_price < 0: + raise ValueError("unit_price must be zero or greater") + if quantity < 1: + raise ValueError("quantity must be at least 1") + if not 0 <= discount_percent <= 100: + raise ValueError("discount_percent must be between 0 and 100") + + subtotal = unit_price * quantity + discount = subtotal * (discount_percent / 100) + + return { + "subtotal": round(subtotal, 2), + "discount": round(discount, 2), + "total": round(subtotal - discount, 2), + } +``` + +Avoid a single general-purpose tool that accepts arbitrary commands or queries. Narrow tools give you clearer validation, smaller permissions, and more predictable results. + +Do not name a source module `server.py`. Appwrite Open Runtimes already provides a top-level module with that name. + +{% /section %} + +{% section #access-appwrite step=4 title="Access Appwrite services from a tool" %} + +Tools can call Appwrite services in the same project without storing a long-lived API key. During each execution, Appwrite passes a [dynamic API key](/docs/products/functions/develop#dynamic-api-key) in the `x-appwrite-key` header. Its permissions are limited to the scopes configured for the Function. + +Open the Function's **Settings** page and select only the scopes your tools need. For example, a read-only reporting server can use `databases.read`, `tables.read`, and `rows.read` without receiving write access. + +Add the Appwrite Python SDK to `requirements.txt`: + +```text +appwrite +``` + +Then initialize its client from the tool context: + +```python +import os + +from appwrite.client import Client +from mcp.server.mcpserver import Context + +def appwrite_client(ctx: Context) -> Client: + api_key = (ctx.headers or {}).get("x-appwrite-key") + if not api_key: + raise ValueError("No dynamic Appwrite API key was provided") + + return ( + Client() + .set_endpoint(os.environ["APPWRITE_FUNCTION_API_ENDPOINT"]) + .set_project(os.environ["APPWRITE_FUNCTION_PROJECT_ID"]) + .set_key(api_key) + ) +``` + +Add `ctx: Context` to any tool that needs the client. Appwrite supplies the endpoint and project ID as Function environment variables. + +{% /section %} + +{% section #secure-server step=5 title="Secure the MCP endpoint" %} + +The template is open by default, which is useful for an initial transport test. Before exposing internal tools or data, add these environment variables in the Function settings: + +| Variable | Value | Purpose | +| --- | --- | --- | +| `MCP_AUTH_MODE` | `bearer` | Requires bearer authentication for MCP requests. | +| `MCP_AUTH_TOKEN` | A long random secret | Shared token that clients must send. | +| `MCP_SERVER_NAME` | Your server name | Name returned to clients during initialization. | +| `MCP_TOOL_TIMEOUT` | `25` | Returns a controlled timeout before the Function's 30-second limit. | + +The bearer token protects access to the MCP endpoint. Function scopes separately control which Appwrite APIs the tools can call. Use both layers. + +{% /section %} + +{% section #deploy-server step=6 title="Deploy the server" %} + +Commit your changes and push them to the Function's production branch. Appwrite creates and activates a new deployment automatically. + +After the deployment succeeds, open the Function in the Appwrite Console and copy its domain. Review the execution logs if initialization or a tool call fails. + +{% /section %} + +{% section #connect-client step=7 title="Connect an MCP client" %} + +Add the Function domain and bearer token to your client's MCP configuration: + +```json +{ + "mcpServers": { + "my-mcp": { + "url": "https://..appwrite.run", + "headers": { + "Authorization": "Bearer " + } + } + } +} +``` + +For Claude Code, you can add a server without bearer authentication from the command line: + +```bash +claude mcp add --transport http my-mcp https://..appwrite.run +``` + +Reconnect the client, list the server's tools, and call one with known inputs. For the example above, ask the client to calculate the total for three items at $20 each with a 10% discount. Verify both the arguments selected by the model and the structured result returned by the tool. + +{% /section %} + +# Design for Appwrite Functions {% #function-constraints %} + +The template uses stateless request and response handling. It does not provide long-lived SSE sessions, streamed progress updates, or server-initiated messages such as sampling. Each tool call returns one final result. + +Requests through a Function domain are [synchronous executions](/docs/products/functions/execute#synchronous-executions) with a 30-second limit. Keep tools focused and leave `MCP_TOOL_TIMEOUT` at 25 seconds so the server can return a clean error first. For longer work, create one tool that starts a job and returns an ID, then another tool that checks the job's status. + +{% arrow_link href="/blog/post/financial-analysis-mcp-server" %} +See a complete financial analysis MCP server built with this template +{% /arrow_link %} diff --git a/src/routes/docs/tooling/ai/mcp-servers/+page.markdoc b/src/routes/docs/tooling/ai/mcp-servers/+page.markdoc index c45b72e6e55..89f3eb96509 100644 --- a/src/routes/docs/tooling/ai/mcp-servers/+page.markdoc +++ b/src/routes/docs/tooling/ai/mcp-servers/+page.markdoc @@ -35,4 +35,16 @@ Some **popular use cases** for the Appwrite MCP server include: - **Documentation lookup**: Quickly find relevant documentation for specific API endpoints or SDK features. - **Project management**: Create, update, or delete resources in your Appwrite project using natural language commands. - **Debugging assistance**: Get help with debugging issues by providing context about your project and recent changes. -- **Learning and exploration**: Explore Appwrite's features and capabilities through interactive conversations with LLMs. \ No newline at end of file +- **Learning and exploration**: Explore Appwrite's features and capabilities through interactive conversations with LLMs. + +# Build your own MCP server + +Use the MCP server Function template to expose your application's own tools to AI clients over HTTPS. Appwrite provides the stateless transport, deployment, Function domain, and optional bearer authentication. + +{% cards %} + +{% cards_item href="/docs/tooling/ai/build-mcp-server" title="Build an MCP server" %} +Create, secure, deploy, and connect a custom MCP server on Appwrite Functions. +{% /cards_item %} + +{% /cards %} diff --git a/static/images/blog/announcing-mcp-server-template/cover.avif b/static/images/blog/announcing-mcp-server-template/cover.avif new file mode 100644 index 00000000000..f0a70b944d5 Binary files /dev/null and b/static/images/blog/announcing-mcp-server-template/cover.avif differ diff --git a/static/images/blog/announcing-mcp-server-template/template.avif b/static/images/blog/announcing-mcp-server-template/template.avif new file mode 100644 index 00000000000..6f8d468cba2 Binary files /dev/null and b/static/images/blog/announcing-mcp-server-template/template.avif differ diff --git a/static/images/blog/financial-analysis-mcp-server/cover.avif b/static/images/blog/financial-analysis-mcp-server/cover.avif new file mode 100644 index 00000000000..e0b3d5b5e99 Binary files /dev/null and b/static/images/blog/financial-analysis-mcp-server/cover.avif differ