MCP for tool builders

Build your own MCP-aware client against Dalea.

Connect Claude Desktop is for end users who want their existing LLM to talk to Dalea. This page is different: it's for developers who want to build their own MCP-aware client — a custom agent, an internal tool, a CLI, a desktop assistant — that uses Dalea's tools.

What you get

A bearer-authenticated HTTP MCP endpoint at https://dalea.app/mcp that exposes:

  • Documents — list, search, read, create, update, version
  • Data — list environments, run saved queries, query result tables, bulk-fetch objects
  • Inventory — list containers, items, lots; query by location
  • Files — list, get info, fetch via presigned URL
  • Search — unified workspace search
  • Marketplace — read-only package search

By design, destructive actions are not exposed via MCPdelete, archive, supersede, and other state-collapsing operations stay in the in-app chat where a human approves them through a confirmation card. This is a security property, not an oversight.

Set up

Register an OAuth client

  1. Settings → Workspaces → OAuth clients → New client

    Pick the workspace the client should access. Give it a name ("internal lab agent v1") and a redirect URI for the OAuth callback.

  2. Pick the role the client will act as

    Most agents only need read access. Pick Viewer or Commenter for read-only patterns. Editor or Data Engineer if your agent needs to create documents or records (those write actions go through the in-app chat or your own UI, not via MCP).

  3. Pick scopes

    For an MCP client, you'll typically request:

    • openid profile email — identity
    • offline_access — refresh tokens
    • mcp:read — call read-only MCP tools
  4. Save the client_id and client_secret

    Treat the secret like a server-side credential.

Implement the OAuth flow

Standard OAuth 2.1 PKCE for native and CLI clients, server-side flow for web. The exchange:

your client ──/authorize?... ─────────────────────► dalea.app
              ◄── 302 with ?code=... ───────────────
              ──/token { code, code_verifier, ... }─►
              ◄── { access_token, refresh_token } ──

Save the refresh token; use the access token until it returns 401, then refresh.

Make a tool call

Once you have an access token, the MCP endpoint is just JSON-over-HTTP:

import requests

MCP_URL = "https://dalea.app/mcp"

def mcp_call(token, tool, arguments):
    resp = requests.post(
        MCP_URL,
        headers={
            "Authorization": f"Bearer {token}",
            "Content-Type": "application/json",
        },
        json={
            "jsonrpc": "2.0",
            "id": 1,
            "method": "tools/call",
            "params": {"name": tool, "arguments": arguments},
        },
    )
    resp.raise_for_status()
    return resp.json()["result"]

# Example: search for protocols
result = mcp_call(token, "search.unified", {"query": "DLA-7", "types": ["document"]})
for hit in result["results"]:
    print(hit["title"], hit["url"])
async function mcpCall(token: string, tool: string, args: Record<string, unknown>) {
  const resp = await fetch("https://dalea.app/mcp", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${token}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      jsonrpc: "2.0",
      id: 1,
      method: "tools/call",
      params: { name: tool, arguments: args },
    }),
  });
  if (!resp.ok) throw new Error(`MCP ${resp.status}`);
  return (await resp.json()).result;
}

For most clients you'll use an MCP SDK (the official @modelcontextprotocol/sdk for Node, or its Python sibling) which handles framing and reconnection for you.

Discovering available tools

Send tools/list:

{ "jsonrpc": "2.0", "id": 1, "method": "tools/list" }

The response enumerates every tool the calling token has access to, with name, description, input schema, and output shape. Cache this for 5 minutes; the tool catalogue is stable but can change between platform releases.

Pagination, filtering, and rate limits

MCP tools follow the same conventions as the REST API:

  • List tools accept limit and cursor.
  • Filters are explicit arguments, not query strings.
  • Rate-limit errors return a standard error response — back off and retry.

A small worked example: a daily PK summary

Goal: every morning, post a Slack message summarising overnight PK results.

The exact tool names below depend on the catalogue your token sees — discover them with tools/list first. The shape is illustrative:

import os, requests

TOKEN = refresh_or_use_cached_token()  # OAuth helper

# 1. Discover available tools
tools = mcp_list(TOKEN)
# Look for tools related to saved queries; pick the one that lists them.
list_tool = next(t for t in tools if "list" in t["name"] and "quer" in t["name"])
run_tool  = next(t for t in tools if "run"  in t["name"] and "quer" in t["name"])

# 2. Find the saved query for "DLA-7 PK timecourse by dose group"
queries = mcp_call(TOKEN, list_tool["name"], {"name_contains": "DLA-7 PK timecourse"})
qid = queries["items"][0]["id"]

# 3. Run it for the latest data
data = mcp_call(TOKEN, run_tool["name"], {"query_id": qid})

# 4. Format and post to Slack
summary = format_pk_table(data["rows"])  # your own helper
requests.post(os.environ["SLACK_WEBHOOK"], json={"text": summary})

Schedule this with cron, GitHub Actions, or your scheduler of choice. No human in the loop for the MCP call; the data tools are read-only so MCP is the right surface.

What MCP isn't

  • It isn't a webhook. MCP is request/response. Event-driven push notifications (e.g. "tell me when a result batch closes") aren't yet available — for now, integrators poll the REST API on a schedule. Native webhooks are on the roadmap.
  • It isn't for destructive actions. No deletes, no closes, no signatures. Those go through the in-app chat.
  • It isn't optimised for bulk transfer. For exporting 100 000 rows, use the REST API with cursor pagination.

Tips

Build with the SDK

The official MCP SDKs handle the JSON-RPC envelope, retries and reconnection. Save yourself the boilerplate and use them for any non-trivial client.

Scope down aggressively

An MCP client doesn't need the same role as the user who registered it. A read-only Slack notifier should be Viewer; an analysis agent that ingests data into a customer-built reporting tool should be Commenter. Match the principle of least privilege.

What's next