Skip to main content
Glama
GoCanvas MCP Server (read-only)

GoCanvas MCP Server (read-only)

by kbates97

GoCanvas MCP Server (read-only)

A minimal Model Context Protocol server that exposes the read-only endpoints of the GoCanvas API v3 as MCP tools. Scope is limited to four areas: Forms, Submissions, Reports, and Reference Data. No create/update/delete operations are exposed.

Tools

Forms

Tool

Endpoint

list_forms

GET /forms

get_form

GET /forms/{form_id}

list_form_assigned_users

GET /forms/{form_id}/assigned_users

list_form_shared_departments

GET /forms/{form_id}/shared_departments

Submissions

Tool

Endpoint

list_submissions

GET /submissions (requires form_id)

get_submission

GET /submissions/{submission_id}

list_submission_revisions

GET /submissions/{submission_id}/revisions

get_submission_value

GET /submissions/{submission_id}/values/{value_id}

Reports

Tool

Endpoint

list_form_reports

GET /forms/{form_id}/reports

get_form_report

GET /forms/{form_id}/reports/{report_id}

get_submission_default_pdf

GET /submissions/{submission_id}/pdf (PDF)

get_submission_report_pdf

GET /submissions/{submission_id}/reports/{report_id} (PDF)

get_submission_standard_pdf

GET /submissions/{submission_id}/standard_pdf (PDF)

The three PDF tools return the binary PDF inline as base64 (content_base64, content_type, size_bytes) — the server is a pure passthrough and never writes to disk, so the tools work on read-only / ephemeral hosts such as AWS Lambda.

Reference Data

Tool

Endpoint

list_reference_data

GET /reference_data

get_reference_data

GET /reference_data/{reference_data_id}

Authentication

Tool

Endpoint

refresh_oauth_token

POST /oauth/token (client-credentials)

refresh_oauth_token forces a fresh bearer token to be fetched and cached. It only applies to server-side OAuth (GOCANVAS_CLIENT_ID / GOCANVAS_CLIENT_SECRET) mode; in passthrough mode the caller owns the token and the server cannot refresh it. It is normally unnecessary — the server fetches a token on startup and refreshes it automatically before expiry and on a 401 — but it is exposed so the agent can rotate the token explicitly. The returned access token is masked.

Related MCP server: N-central MCP Server

Setup

This project uses uv. With uv installed, no manual environment setup is required — uv run resolves and installs dependencies (from pyproject.toml) automatically on first launch.

# optional: pre-create the environment
uv sync
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt

Configuration

There are two independent authentication hops, and they use different credentials on purpose.

Hop 1 — caller to this server

Under an HTTP transport, every request must present a shared secret in the MCP_GATEWAY_HEADER header (default x-mcp-key). Anything else gets 401. This is what keeps a public endpoint from being usable by the public.

Variable

Description

MCP_GATEWAY_KEYS

Comma-separated list of accepted secrets. A list rather than one value so keys rotate with no downtime: add the new key, repoint the caller, drop the old. Generate with openssl rand -base64 32.

MCP_GATEWAY_HEADER

Header the secret is read from (default x-mcp-key). Never set this to Authorization — that header feeds the GoCanvas passthrough path, so reusing it would forward your gateway secret upstream to the GoCanvas API.

GOCANVAS_REQUIRE_GATEWAY_KEY

Default true. With no keys configured the endpoint returns 503 rather than serving openly. Set false only for local development bound to loopback.

stdio is unaffected — the check only applies to the HTTP transports.

Hop 2 — this server to the GoCanvas API

Resolved per request, in priority order:

Source

Description

Incoming Authorization header

Forwarded verbatim. Only when GOCANVAS_ALLOW_PASSTHROUGH=true, which is off by default — otherwise a caller could override the server's own credentials, and any bearer they send would be relayed to a third party.

GOCANVAS_CLIENT_ID / GOCANVAS_CLIENT_SECRET

OAuth 2.0 client credentials. A short-lived bearer is fetched from /oauth/token, cached, and auto-refreshed on expiry or 401. The recommended mode for hosted use.

GOCANVAS_API_TOKEN

Static bearer token.

GOCANVAS_USERNAME / GOCANVAS_PASSWORD

HTTP Basic auth (fallback).

Secrets Manager

For hosted deployments, keep both hops' credentials in one secret instead of in environment variables:

Variable

Description

GOCANVAS_SECRET_ID

Name or ARN of a Secrets Manager secret whose JSON body may hold client_id, client_secret, scope, gateway_keys. Read once per process (per Lambda container) and cached; takes priority over the equivalent environment variables.

GOCANVAS_SECRET_REGION

Defaults to AWS_REGION.

A failure to read the secret is logged and falls back to the environment variables rather than crashing the server.

Other optional variables

Variable

Description

GOCANVAS_OAUTH_SCOPE

Optional OAuth scope to request (server-side OAuth only).

GOCANVAS_BASE_URL

Defaults to https://api.gocanvas.com/api/v3.

GOCANVAS_TIMEOUT

HTTP timeout in seconds (default 30).

GOCANVAS_TRANSPORT

stdio (default), streamable-http, or sse.

GOCANVAS_HOST

Bind host for HTTP transports (default 127.0.0.1).

GOCANVAS_PORT

Bind port for HTTP transports (default 8000).

GOCANVAS_STATELESS_HTTP

No per-session state between requests (default true; required for Lambda).

GOCANVAS_JSON_RESPONSE

Return JSON instead of an SSE stream (default true; required for Lambda).

GOCANVAS_ALLOWED_HOSTS

Comma-separated Host allow-list for DNS-rebinding protection. Defaults to localhost only, which returns HTTP 421 behind API Gateway / a Function URL — set your public domain or * when hosting publicly.

GOCANVAS_ALLOWED_ORIGINS

Comma-separated Origin allow-list (same semantics).

If no usable credentials are available for a call, the tool returns a clear error — the server itself still starts fine.

Running

Locally over stdio (default)

GOCANVAS_CLIENT_ID=... GOCANVAS_CLIENT_SECRET=... uv run server.py

Publicly over HTTP

Run with an HTTP transport and a gateway key:

GOCANVAS_TRANSPORT=streamable-http GOCANVAS_HOST=0.0.0.0 GOCANVAS_PORT=8000 \
MCP_GATEWAY_KEYS="$(openssl rand -base64 32)" \
GOCANVAS_CLIENT_ID=... GOCANVAS_CLIENT_SECRET=... \
uv run server.py

The MCP endpoint is served at /mcp. No PDFs or other state are written to disk, so the server runs cleanly on read-only / ephemeral hosts.

Hosting publicly? Set GOCANVAS_ALLOWED_HOSTS to your public domain (or *). The default DNS-rebinding protection allows only localhost and returns HTTP 421 Misdirected Request for any other Host header.

Deploying to AWS Lambda

The module exposes an ASGI app (asgi_app()) and a Mangum-wrapped Lambda entry point (lambda_handler), so it runs on Lambda behind a Lambda Function URL or an API Gateway HTTP API with no long-running process. Stateless + JSON-response mode is the default (Lambda containers are ephemeral and don't share session state, and API Gateway can't proxy an SSE stream).

template.yaml is a complete AWS SAM stack: the function, a Function URL, a Secrets Manager secret, a log group with retention, and a concurrency cap.

Prerequisites

  • An AWS account and the AWS CLI, configured (aws configure) with permission to create Lambda functions, IAM roles, secrets, and log groups.

  • The SAM CLI (brew install aws-sam-cli on macOS).

  • GoCanvas API client ID and secret for a service account, from the GoCanvas admin console.

1. Build and deploy

sam build

The function uses BuildMethod: makefile, so this runs the build-GoCanvasMcpFunction target in Makefile rather than SAM's default Python builder. Two reasons: the default builder copies the entire CodeUri directory, which here would ship the 49 MB local .venv and the OpenAPI specs; and the Makefile pins --platform manylinux2014_aarch64 --python-version 3.12 --only-binary=:all: so the correct Linux/arm64 wheels are installed even when you build from macOS. No Docker or --use-container needed. If you change Runtime or Architectures in the template, update those flags to match.

Then deploy:

sam deploy --guided

Accept the defaults, except: answer yes to GoCanvasMcpFunction Function URL may not have authorization defined, Is this okay? — AuthType: NONE is required because no MCP client can produce a SigV4 signature. The gateway-key check inside the function is the access control.

Subsequent deploys are just sam build && sam deploy.

Note the stack outputs — McpEndpoint, SecretArn, GatewayHeaderName:

aws cloudformation describe-stacks --stack-name gocanvas-mcp \
  --query 'Stacks[0].Outputs' --output table

2. Populate the secret

The stack creates the secret itself but deliberately gives it no value — the value is not a template-managed property, so that a later stack update can never reassert a placeholder over your live credentials. Until you run the command below, the endpoint returns 503 and each cold start logs a warning naming this step.

Use put-secret-value, not create-secret: the secret already exists by this point, so create-secret fails with ResourceExistsException. The same command is also how you rotate later. Generate a gateway key and write both sets of credentials in one shot:

GATEWAY_KEY="$(openssl rand -base64 32)"
echo "Gateway key (save this, you will paste it into Copilot Studio): $GATEWAY_KEY"

aws secretsmanager put-secret-value \
  --secret-id gocanvas-mcp/config \
  --secret-string "$(jq -n \
      --arg cid   "YOUR_GOCANVAS_CLIENT_ID" \
      --arg csec  "YOUR_GOCANVAS_CLIENT_SECRET" \
      --arg key   "$GATEWAY_KEY" \
      '{client_id:$cid, client_secret:$csec, gateway_keys:[$key]}')"

Because the secret is cached per Lambda container, changes take effect on the next cold start. To apply immediately, force new containers:

aws lambda update-function-configuration --function-name gocanvas-mcp \
  --description "secret rotated $(date -u +%FT%TZ)"

3. Verify

Without the key — expect 401:

curl -s -o /dev/null -w '%{http_code}\n' -X POST "$MCP_ENDPOINT" \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"curl","version":"1"}}}'

With the key — expect 200 and a serverInfo block:

curl -s -X POST "$MCP_ENDPOINT" \
  -H "x-mcp-key: $GATEWAY_KEY" \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"curl","version":"1"}}}'

If you get 421 Misdirected Request, GOCANVAS_ALLOWED_HOSTS does not include the Function URL host. If you get 503 server not configured, the secret has no gateway_keys (or the function can't read it — check CloudWatch).

4. Tighten the host allow-list

The Function URL hostname isn't known until the stack exists, so the template bootstraps with AllowedHosts: "*". Once deployed, pin it:

sam deploy --parameter-overrides \
  "AllowedHosts=abc123xyz.lambda-url.us-east-1.on.aws"

Connecting Copilot Studio

Copilot Studio reaches an MCP server through a Power Platform custom connector. Its OAuth path is authorization-code-on-behalf-of-user (the PKCE flow GoCanvas doesn't play well with), and it would make each end user sign in — neither of which is what you want here. Use API Key auth instead, which gives the agent a standing credential:

  1. Create the connector. In Copilot Studio, Tools → Add a tool → New tool → Model Context Protocol, or import an OpenAPI definition carrying x-ms-agentic-protocol: mcp-streamable-1.0. Host = your Function URL hostname, base path = /mcp.

  2. Security tab. Authentication type API Key; parameter label anything; parameter name x-mcp-key; location Header.

  3. Create the connection once, pasting the gateway key from step 2 above.

  4. Set the tool to use the maker/author-provided connection, not end-user credentials. This is what makes the agent's access standing — no per-user sign-in prompt, and the agent works for every user of the published agent.

What this trades away

Every Copilot Studio user now acts as one GoCanvas service account. GoCanvas can no longer enforce per-user authorization, because it only ever sees that account. The tools here are read-only, which bounds the blast radius, but scope the service account's GoCanvas permissions to the minimum set of forms and data the agent should ever surface. "The agent won't ask for it" is not a control.

Hardening beyond the key

  • WAF IP allow-list. Front the Function URL with CloudFront + AWS WAF and allow only the published Power Platform outbound IP ranges for your region. Treat this as defense in depth, not a primary control: the ranges are shared across all Power Platform tenants and they change, so you have to track the published list. The value is that a leaked key alone stops being enough.

  • Concurrency cap. ReservedConcurrency (default 10) bounds what a leaked key can cost you and keeps it from exhausting your GoCanvas rate limit.

  • Watch the rejections. Failed gateway checks log at WARNING to /aws/lambda/gocanvas-mcp. A metric filter on Rejected MCP request plus an alarm turns a leaked or probed endpoint into a page.

Payload-size limit. API Gateway / a buffered Function URL caps a response at 6 MB. The PDF tools return the file base64-encoded inline (~33% overhead), so a PDF larger than ~4.5 MB can exceed that limit. Raise MemorySize/Timeout for large forms; for consistently large PDFs, front the function with a Function URL in RESPONSE_STREAM invoke mode or fetch the PDF out-of-band.

MCP client configuration

Use uv run as the command. --directory points uv at this project so it uses the right dependencies regardless of the client's working directory:

{
  "mcpServers": {
    "gocanvas": {
      "command": "uv",
      "args": [
        "run",
        "--directory", "/absolute/path/to/GoCanvas",
        "server.py"
      ],
      "env": {
        "GOCANVAS_CLIENT_ID": "your_client_id",
        "GOCANVAS_CLIENT_SECRET": "your_client_secret"
      }
    }
  }
}

If uv isn't on the client's PATH, use its absolute path (e.g. ~/.local/bin/uv) as the command.

{
  "mcpServers": {
    "gocanvas": {
      "command": "/absolute/path/to/GoCanvas/.venv/bin/python",
      "args": ["/absolute/path/to/GoCanvas/server.py"],
      "env": {
        "GOCANVAS_CLIENT_ID": "your_client_id",
        "GOCANVAS_CLIENT_SECRET": "your_client_secret"
      }
    }
  }
}

On Windows the interpreter is at .venv\Scripts\python.exe. Using a bare python will fail with ModuleNotFoundError: httpx because the client does not use your activated shell environment.

Notes

  • Pagination: list tools accept a page argument. Response pagination headers (link, current-page, page-items, total-count, total-pages) are surfaced under a pagination key in the tool result.

  • Warm Lambda containers: lambda_handler rebuilds the ASGI app on each invocation. Mangum runs the ASGI lifespan per invocation and StreamableHTTPSessionManager.run() raises if entered twice, so a cached app would make every request after a container's first one fail. Rebuilding costs a route table per request and is free of correctness cost in stateless mode.

  • Rate limiting: the server honors 429 Too Many Requests responses, waiting according to the RateLimit-Reset / RateLimit-Remaining headers (or a bounded exponential backoff) before retrying, per GoCanvas best practices.

Available Tools

16 tools
get_formA

Retrieve a single Form (including its full definition) by id.

Args: form_id: The identifier of the Form.

Inputs: form_id (required). Returns: object

get_form_reportB

Retrieve a single Report definition (including its full definition file) for a Form.

Args: form_id: The identifier of the Form. report_id: The identifier of the Report definition.

Inputs: form_id (required), report_id (required). Returns: object

get_reference_dataA

Retrieve a single Reference Data resource by id.

Args: reference_data_id: The identifier of the Reference Data resource.

Inputs: reference_data_id (required). Returns: object

get_submissionA

Retrieve a single Submission (including its values) by GUID.

Args: submission_id: The Submission GUID.

Inputs: submission_id (required). Returns: object

get_submission_default_pdfC

Download the default Report PDF for a Submission and return its bytes inline as base64.

Args: submission_id: The identifier of the Submission.

Inputs: submission_id (required). Returns: object

get_submission_report_pdfB

Generate and download a specific Report PDF for a Submission by Report id.

Args: submission_id: The identifier of the Submission. report_id: The identifier of the Report definition to render.

Inputs: report_id (required), submission_id (required). Returns: object

get_submission_standard_pdfC

Download the Standard Report PDF for a Submission and return its bytes inline as base64.

Args: submission_id: The identifier of the Submission.

Inputs: submission_id (required). Returns: object

get_submission_valueB

Retrieve a single Value from a Submission (e.g. a media field).

Args: submission_id: The Submission GUID. value_id: The identifier of the Value within the Submission.

Inputs: value_id (required), submission_id (required). Returns: object

list_form_assigned_usersC

List the Users assigned to a Form.

Args: form_id: The identifier of the Form.

Inputs: form_id (required). Returns: object

list_form_reportsB

List the Report definitions associated with a Form.

Args: form_id: The identifier of the Form. page: Optional page number for pagination.

Inputs: form_id (required), page. Returns: object

list_formsB

List all Forms in the company.

Args: page: Optional page number for pagination.

Inputs: page. Returns: object

list_form_shared_departmentsC

List the Departments a Form is shared with.

Args: form_id: The identifier of the Form.

Inputs: form_id (required). Returns: object

list_reference_dataA

List all Reference Data resources in the company.

Inputs: no named parameters. Returns: object

list_submission_revisionsC

List the revision history of a Submission.

Args: submission_id: The identifier of the Submission. page: Optional page number for pagination.

Inputs: submission_id (required), page. Returns: object

list_submissionsA

List Submissions for a Form.

Args: form_id: Required. The identifier for the Form associated with the Submissions. page: Optional page number for pagination. department_id: Filter by the Department associated with the Submission. user_id: Filter by the User who created the Submission. status: Status filter. One of: all, completed, deleted, in-progress, overdue, rejected, handed-off, assigned, unassigned, custom, saved-to-cloud, unfinished. hand_off: Workflow handoff state name (required when status is "handed-off"). custom_status: Custom status label (required when status is "custom"). start_date: DateTime lower bound for the Submission created_at. end_date: DateTime upper bound for the Submission created_at.

Inputs: form_id (required), page, status, user_id, +5 more. Returns: object

refresh_oauth_tokenA

Fetch a fresh OAuth bearer token from /oauth/token (client-credentials grant).

Requires GOCANVAS_CLIENT_ID and GOCANVAS_CLIENT_SECRET to be configured. The token is cached and used automatically for subsequent API calls; call this to force a refresh (e.g. after a 401). Returns token metadata with the access token masked for safety.

Inputs: no named parameters. Returns: object

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

    TDQS

    A3.7/5.0

    Scored across 16 tools

    Disambiguation5/5

    Each tool targets a distinct resource or operation: forms, submissions, reference data, PDFs, and OAuth. No overlapping purposes; descriptions clearly differentiate.

    Naming Consistency5/5

    All tools follow a consistent verb_noun pattern in snake_case (e.g., list_forms, get_submission). No deviations or mixed conventions.

    Tool Count5/5

    16 tools cover the read-only surface of forms, submissions, reports, reference data, and PDFs. The count is well-scoped for the server's purpose.

    Completeness5/5

    The tool set provides full read coverage for the domain: listing and getting forms, submissions, reports, reference data, plus PDF downloads. No obvious gaps remain.

    Maintenance

    ActivitySlowing
    ResponsivenessNo recent community issues

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      A
      quality
      A
      maintenance
      Exposes JustOneAPI endpoints as MCP tools, returning raw upstream JSON without field parsing for maximum data fidelity.
      7
      21 npm
      30
      MIT
    • A
      license
      Not graded
      quality
      D
      maintenance
      Exposes the Filly Forms REST API as MCP tools for AI agents, enabling form type listing, record CRUD, data preview, and document upload with AI extraction.
      MIT
    • F
      license
      Not graded
      quality
      C
      maintenance
      Exposes the CloudRadial REST API (client portal / PSA-adjacent MSP platform) as MCP tools, enabling operations on companies, articles, feedback, archives, flexible assets, and more via 34 tools with HTTP Basic Auth.
      1
      -