MCP server
Connect an MCP host to Artefaktum's hosted server and use its tools to find, upload and trust artifacts.
Connecting
Artefaktum runs one hosted MCP server over Streamable HTTP:
https://api.artefaktum.dev/mcp
No stdio entry point; a stdio-only host needs a bridge such as mcp-remote.
Claude Code:
claude mcp add --transport http artefaktum https://api.artefaktum.dev/mcp
Cursor, Windsurf, Cline, or any host that reads an mcp.json:
{ "mcpServers": { "artefaktum": { "url": "https://api.artefaktum.dev/mcp" } } }
What happens on first use
The host opens a browser tab for an OAuth 2.1 sign-in handled by Clerk: sign in with Google or GitHub, approve the connection once, and the host gets an access token it attaches to every call after that. There is no key to paste. Sign-in provisions a tenant the first time it happens, keyed to your personal account or Clerk organisation, with a default project already in place.
Authentication
The endpoint accepts two bearer credentials, checked by the same resolver chain as REST:
- OAuth 2.1 access token: issued by Clerk after the browser sign-in above. The default for hosts with an OAuth-capable MCP client.
- API key: the same key used against the REST API, created in the console, sent as
Authorization: Bearer <key>. Use it for hosts that can’t complete a browser flow, or for unattended runs: pass--headeronclaude mcp add, or add a header next tourlinmcp.jsonif the host allows one.
Two OAuth caveats: Clerk’s tokens carry no audience (aud) claim, so audience checking is relaxed rather than strict; and Clerk doesn’t know Artefaktum’s scope names, so every OAuth caller gets a fixed grant of read, write and search, never delete or admin. An API key carries exactly the scopes it was minted with. Neither matters for normal use, only for auditing what an OAuth agent can reach.
Tools
Every tool is scoped to a project. Call project_list first if you don’t have a project_id yet. This matters most after OAuth sign-in, which binds you to a tenant, not a project. List-shaped tools cap at 25 items and truncate long text; file bytes never enter a tool response.
| Tool | Purpose |
|---|---|
artifact_search |
Find artifacts by meaning, text, or structured filters. |
artifact_get |
Read one artifact’s metadata, lifecycle state, trust signals and relations. |
artifact_get_download_url |
Get a short-lived signed URL for an artifact’s bytes. |
project_list |
List the projects this credential can use. |
artifact_list |
List artifacts in a project, with filters and a cursor. |
artifact_create_upload |
Reserve a new artifact and get a signed upload URL. |
artifact_complete_upload |
Confirm the bytes were uploaded so the server can verify and ready it. |
artifact_resolve |
Reuse a fresh artifact by external key, or claim the right to create it. |
artifact_update_metadata |
Change an artifact’s title, description, tags, metadata or TTL. |
artifact_create_version |
Start a new immutable version of an existing artifact. |
artifact_add_relation |
State a lineage relation between two artifacts. |
artifact_delete |
Request permanent deletion of an artifact. |
Finding artifacts
artifact_search matches descriptors (title, description, tags, metadata), never file contents. mode is exact (filters only), text (full-text), semantic (pgvector) or hybrid (fuses both; the default). Scores aren’t comparable across modes. Superseded artifacts are hidden unless asked for.
| Parameter | Type | Default |
|---|---|---|
project_id |
UUID | required |
query |
string | "" |
mode |
string | "hybrid" |
tags_all |
string[] | none |
content_types |
string[] | none |
exclude_superseded |
boolean | true |
limit |
integer | 20 (capped at 25) |
{ "project_id": "3f5c...", "query": "q3 churn analysis", "mode": "hybrid", "tags_all": ["churn"], "limit": 10 }
artifact_get returns one artifact plus its relations (up to 25). Argument: { "artifact_id": "0199198a-8f21-7c9e-9b1a-4e2f6d1a0c33" }.
artifact_list filters a project’s artifacts with a cursor, not a free-text query.
| Parameter | Type | Default |
|---|---|---|
project_id |
UUID | required |
status |
string[] | none |
tag |
string | none |
content_type |
string | none |
external_key |
string | none |
limit |
integer | 20 (capped at 25) |
cursor |
string | none |
{ "project_id": "3f5c...", "tag": "churn", "limit": 20 }
project_list takes no arguments; it returns every project the credential can use.
Caching by name
artifact_resolve turns “fetch only if nobody already has” into one call: hit (a fresh artifact exists, use it), create (you hold the reservation: PUT to upload.url, then artifact_complete_upload), or pending (retry after retry_after_seconds).
| Parameter | Type | Default |
|---|---|---|
project_id |
UUID | required |
external_key |
string | required |
filename |
string | required |
content_type |
string | required |
size_bytes |
integer | required |
title |
string | required |
max_age_seconds |
integer | none (any age counts as fresh) |
run_id |
string | none |
{
"project_id": "3f5c...",
"external_key": "company-api/acme/2026-09-17",
"max_age_seconds": 86400,
"filename": "acme.json",
"content_type": "application/json",
"size_bytes": 5120,
"title": "Acme enrichment"
}
Uploading
artifact_create_upload reserves a new artifact and returns a signed, short-lived upload URL. PUT the bytes to upload.url with the given headers, then call artifact_complete_upload.
| Parameter | Type | Default |
|---|---|---|
project_id |
UUID | required |
filename |
string | required |
content_type |
string | required |
size_bytes |
integer | required |
title |
string | required |
description |
string | "" |
tags |
string[] | none |
metadata |
object | none |
external_key |
string | none |
run_id |
string | none |
infer_lineage |
boolean | true |
Field sizes are limited; see Limits.
{
"project_id": "3f5c...",
"filename": "report.pdf",
"content_type": "application/pdf",
"size_bytes": 88213,
"title": "Q3 churn analysis",
"tags": ["churn", "q3"]
}
artifact_complete_upload confirms the upload. The server verifies the object and marks it ready; the response says processing until that finishes, usually within a second or two; re-read with artifact_get. Safe to call more than once.
{ "artifact_id": "0199198a-...", "version_id": "0199198b-...", "sha256": "e3b0c4..." }
artifact_create_version starts a new immutable version and returns another signed upload URL. On completion the server writes a supersedes relation to the previous version.
| Parameter | Type | Default |
|---|---|---|
artifact_id |
UUID | required |
filename |
string | required |
content_type |
string | required |
size_bytes |
integer | required |
run_id |
string | none |
infer_lineage |
boolean | true |
Downloading
artifact_get_download_url returns a short-lived signed URL; fetch it yourself. Records a usage event.
| Parameter | Type | Default |
|---|---|---|
artifact_id |
UUID | required |
version_id |
UUID | none (latest version) |
run_id |
string | none |
Argument: { "artifact_id": "0199198a-8f21-7c9e-9b1a-4e2f6d1a0c33" }.
Lineage and lifecycle
artifact_add_relation states that one artifact relates to another: derived_from, supersedes, attachment_of, generated_by or related_to. Explicit relations replace inferred ones.
| Parameter | Type | Default |
|---|---|---|
artifact_id |
UUID | required |
to_artifact_id |
UUID | required |
relation_type |
string | required |
to_version_id |
UUID | none |
metadata |
object | none |
{ "artifact_id": "out-id", "to_artifact_id": "in-id", "relation_type": "derived_from" }
artifact_update_metadata changes title, description, tags, metadata or TTL. Bytes are immutable; use artifact_create_version for new content. Re-embeds when a searchable field changes.
| Parameter | Type | Default |
|---|---|---|
artifact_id |
UUID | required |
title |
string | none |
description |
string | none |
tags |
string[] | none |
metadata |
object | none |
expires_at |
string (date-time) | none |
clear_expires_at |
boolean | false |
artifact_delete moves the artifact to deleting; a worker removes the bytes. Not reversible. Argument: { "artifact_id": "0199198a-8f21-7c9e-9b1a-4e2f6d1a0c33" }.
The artifact resource
artefactai://artifacts/{artifact_id} is a pointer, not a data endpoint. A resource read carries no principal to authorize against, so it returns only the id you gave it plus a nudge to call artifact_get or artifact_get_download_url instead.
Typical agent flow
A research agent hands a report to a writing agent, neither knowing where the other runs.
- Research agent calls
artifact_create_upload, thenPUTs the bytes toupload.url. - Research agent calls
artifact_complete_upload, then pollsartifact_getuntilstatusisready. - Writing agent, on a separate host, calls
artifact_search(hybrid, sameproject_id) and picks a hit withsuperseded: falseandstale_upstream: false. - Writing agent calls
artifact_get_download_urland fetches the bytes itself. - Writing agent uploads its own output tagged with the same
run_id; since it downloaded the first artifact in that run before uploading, the server infers aderived_fromedge on its own, or either agent can callartifact_add_relationto state it explicitly.
This page as Markdown: /docs/mcp.md