Skip to main content
Glama
midnight480

Backlog Remote MCP Server

by midnight480

Backlog Remote MCP Server

A remote MCP (Model Context Protocol) server for Backlog. Deployable to either Cloudflare Workers or AWS.

English | 日本語

Features

  • Multi-space — serve several Backlog spaces from one server

  • Read-only guard — mark a shared space readOnly to reject every write API call

  • OAuth 2.1 + PKCE — supports Dynamic Client Registration (DCR), so MCP clients connect directly

  • Email allowlist — restrict who can use the server

  • Two runtimes — the same business logic runs on Cloudflare or AWS

Related MCP server: backlog-mcp-server

Choosing a deployment

Cloudflare Workers

AWS

Runtime

Workers (edge)

Lambda + API Gateway HTTP API

MCP session

Durable Objects

Stateless

OAuth authorization server

@cloudflare/workers-oauth-provider

MCP SDK mcpAuthRouter

Upstream IdP

Cloudflare Access

Amazon Cognito

State storage

Workers KV

DynamoDB (TTL)

Secrets

Workers Secrets

Secrets Manager

IaC

wrangler

AWS SAM

Config file

.dev.vars

infra/aws/params.yaml

The tools and their behavior are identical on both.

Estimated Cost

Note These are reference figures only. Actual charges vary by region, usage, and pricing changes. Use the official calculators for real estimates.

Assumptions

Personal use or a small team.

Item

Assumption

Users

1–5

MCP requests

~3,000 / month

Backlog spaces

3

Log retention

30 days

Fixed costs (charged even when idle)

Cloudflare

AWS

Runtime

$0 (Free plan works)

$0

Auth platform

$0 (Zero Trust free up to 50 users)

$0 (within Cognito free tier)

Secrets

$0 (Workers Secrets are free)

~$0.80 (2 Secrets Manager secrets)

Certificates

$0

$0 (public ACM certificates are free)

Total

$0

~$1/month

On AWS the fixed cost is essentially just Secrets Manager, which bills per secret per month whether or not it is used. Cloudflare has no fixed cost because Workers Secrets are free.

What is metered

Cloudflare

AWS

Requests

Workers

Lambda + API Gateway

State storage

Durable Objects + KV

DynamoDB

Logs

Workers Logs

CloudWatch Logs

At the assumed volume (~3,000 requests/month) both stay within the free allowances. API Gateway HTTP API has no perpetual free tier, so AWS accrues a small charge proportional to request count (roughly $1 per million requests).

Thresholds worth knowing

Cloudflare — the 50-user line for Zero Trust

Zero Trust (Access) is free for up to 50 users. Beyond that you move to a paid plan billed per user per month. This is the cost that scales with headcount.

Cloudflare — Workers Free plan limits

This project uses SQLite-backed Durable Objects, which are available on the Workers Free plan. The Free plan does cap daily requests and other usage, and exceeding a cap returns errors. For sustained use consider Workers Paid (from $5/month).

AWS — the Lambda free tier is perpetual

Lambda includes a perpetual free tier of 1M requests and 400,000 GB-seconds per month. API Gateway and Secrets Manager have no perpetual free tier.

AWS — CloudWatch Logs

Logs are billed on ingestion volume. This template manages retention explicitly via LogRetentionDays (default 30), so logs do not accumulate indefinitely.

Summary

Scale

Cloudflare

AWS

Personal

roughly $0

~$1/month

Tens of users (≤50)

roughly $0–$5

$1 to a few dollars/month

51+ users

Zero Trust switches to per-user billing

depends on the Cognito MAU free tier

For small teams Cloudflare is cheaper and has no fixed cost. AWS carries the Secrets Manager fixed cost but is worth it if you want to consolidate into an existing AWS footprint or govern access through IAM.

Setup

0. Prerequisites

Node.js 20 or later.

git clone <this-repo>
cd backlog-remote-mcp-server
npm install

Additional tools depend on the deployment target:

Target

Requirements

Cloudflare Workers

Cloudflare account with Workers enabled, custom domain (optional)

AWS

AWS account, AWS CLI v2, AWS SAM CLI

Order to follow

  1. Backlog API keys and space configuration — shared by both platforms

  2. Pick an identity provider

  3. Pick a deployment target

If something goes wrong

Troubleshooting sections live at the end of each deployment guide.

Architecture

MCP client (Claude, Kiro, Cursor, ...)
    ↓ Streamable HTTP + OAuth
Runtime (Cloudflare Workers or AWS Lambda)
    ↓ Upstream IdP (Cloudflare Access or Amazon Cognito)
    ↓ Email allowlist check
    ↓ Backlog API key routing
Backlog space A / B / C ...

Directory layout

Business logic is separated from runtime wiring.

src/
  core/                    Runtime-independent
    backlog-client.ts      Backlog API client (including the readOnly guard)
    tools/                 40 MCP tools
    create-server.ts       MCP server assembly and authorization
  platforms/
    cloudflare/            Cloudflare Workers wiring
    aws/                   AWS Lambda wiring
infra/
  aws/                     SAM template and parameters

src/core depends only on @modelcontextprotocol/sdk and zod and references no runtime-specific API. Adding a platform means adding an adapter under src/platforms/ while sharing the same tool implementations.

Connecting from MCP Clients

Claude Desktop / Kiro / Cursor (via mcp-remote proxy)

{
  "mcpServers": {
    "backlog": {
      "command": "npx",
      "args": [
        "mcp-remote",
        "https://<MCP_HOSTNAME>/mcp"
      ]
    }
  }
}

On first connection, a browser window opens for authentication.

MCP Inspector (for testing)

npx @modelcontextprotocol/inspector@latest

Enter https://<MCP_HOSTNAME>/mcp in the inspector and complete the OAuth flow via OAuth Settings.

Usage

Specifying a Space

All tools accept an optional space parameter:

# Use default space
"Show me the issues for PROJECT-KEY"

# Specify a particular space
"List projects in the PERSONAL space"
→ space: "PERSONAL"

Examples

# List configured spaces
"What Backlog spaces are available?" → list_spaces

# List projects
"Show COMPANY_A projects" → get_project_list(space: "COMPANY_A")

# Create an issue
"Create a new bug issue in PROJECT-KEY" → add_issue(...)

# List pull requests
"Show open PRs in repo-name" → get_pull_requests(...)

Available Tools

Category

Tools

Space

list_spaces, get_space, get_users, get_myself

Project

get_project_list, get_project, add_project, update_project, delete_project, get_project_users

Issue

get_issue, get_issues, count_issues, add_issue, update_issue, delete_issue, get_issue_comments, add_issue_comment, get_priorities, get_issue_types, get_categories, get_version_milestones, add_version_milestone, get_resolutions

Wiki

get_wiki_pages, get_wikis_count, get_wiki, add_wiki

Git

get_git_repositories, get_git_repository, get_pull_requests, get_pull_request, add_pull_request, update_pull_request, get_pull_request_comments, add_pull_request_comment

Notification

get_notifications, get_notifications_count, reset_unread_notification_count, mark_notification_as_read

add_*, update_*, and delete_* are write operations. Calling them against a space configured with readOnly: true is rejected before any request reaches the Backlog API. Use list_spaces to see the readOnly status of each space.

Security

  • Authentication: Cloudflare Access → Google / Microsoft Entra ID. The entire OAuth flow is managed by Cloudflare

  • Authorization: ALLOWED_EMAILS provides an application-level email allowlist

  • Double-check: Access Policy (Cloudflare side) + in-app allowlist (Worker side)

  • API Key Protection: Backlog API keys are stored in Cloudflare Secrets and never exposed to clients

  • PKCE + CSRF: OAuth flow is protected with PKCE (S256) and CSRF tokens

  • Client consent: Dynamic Client Registration is open to anyone, so authorization is gated behind a consent screen that names the client and its redirect target and requires a CSRF-protected approval. Approvals are keyed on client_id + redirect_uri, so re-registering with a different redirect target cannot inherit a prior approval

  • Write guard: Spaces marked readOnly: true reject every non-GET call. The check lives in the API-call layer of src/core/backlog-client.ts, so it does not depend on individual tool implementations

  • Configuration isolation: All environment-specific values live in .dev.vars (untracked). The repository contains placeholders only

Operational notes

  • ALLOWED_EMAILS is the effective authorization boundary for this server. There is no zone-level Access application in front of the Worker

  • npm run deploy overwrites production secrets with the values in .dev.vars. If you need different values locally and in production, use deploy:no-secrets for routine deploys and push secrets explicitly with secrets:push

  • A Backlog API key carries the full permissions of its owner. For spaces that need no writes, issue a read-only key and set readOnly: true

Local Development

Local runs use the Cloudflare Workers build (wrangler dev). Because the business logic lives in src/core, whatever you verify here holds for the AWS deployment too.

cp .dev.vars.example .dev.vars   # fill in your values
npm run dev
# Server starts at http://localhost:8788/mcp

wrangler dev emulates KV and Durable Objects locally, so it never touches real Cloudflare resources.

Verifying the setup

Run the full OAuth-to-tool-call check in one command:

npm run check:local

It performs the following, opening a browser partway through so you can log in:

  1. Fetch the Authorization Server metadata

  2. Dynamic client registration

  3. Approve in the browser → IdP login

  4. Token exchange with PKCE

  5. initialize / tools/list

  6. Call get_space and show the real response from Backlog

If tools/list returns only access_denied, the email you logged in with is not in the allowlist.

It also works against a deployed endpoint:

npm run check:local -- --base https://your-deployed-host

Running over HTTPS

Use this when the IdP will not accept an http:// redirect URL.

npm run dev:https
# Server starts at https://localhost:8788/mcp (self-signed certificate)

Type checking and tests

Types are split per platform, so misusing a Workers global in AWS code (or vice versa) is a type error.

npm run type-check   # both tsconfig.cloudflare.json and tsconfig.aws.json
npm test             # runs all suites below

Command

Covers

npm run test:aws-oauth

OAuth authorization server logic (DCR, PKCE, single-use tokens, scopes, revocation)

npm run test:aws-consent

Consent screen (HTML escaping, signed cookies, CSRF, approval gate)

npm run test:aws-store

DynamoDB store client-registration TTL and renewal

None of them reach external services — DynamoDB and the upstream IdP are stubbed.

Configuration files

File

Purpose

Git

.dev.vars

Local development + Cloudflare deploy

ignored

.dev.vars.example

Template for the above

committed

infra/aws/params.yaml

AWS deploy

ignored

infra/aws/params.example.yaml

Template for the above

committed

See the deployment guides for how to fill them in.

License

MIT

A
license - permissive license
Not graded
quality - not tested
B
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Servers

View all related MCP servers

Related MCP Connectors

View all MCP Connectors

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/midnight480/backlog-remote-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server