MCP server
Call the JOGL Network API as tools from an LLM agent over Model Context Protocol.
The MCP server exposes the same handlers as the REST API as tools an LLM agent can call, over stateless JSON-RPC 2.0. Authentication, scopes, rate limits, and every community fence are identical to the HTTP surface because both transports call the same shared handlers — they can't diverge.
Endpoint
A single HTTP POST endpoint (stateless — no sessions):
https://my.jogl.network/mcp- OAuth one-click connector → this branded URL is required. It is the resource identifier
the WorkOS AuthKit tokens are bound to (the expected JWT
aud), and the host that serves RFC 9728 discovery. Pointing an OAuth client at the raw Supabase function URL breaks the discovery/token handshake. - Static
jogl_*API key → the same branded URL works, and it is what the app hands you. The raw Supabase function URL (https://<project-ref>.supabase.co/functions/v1/mcp, the same project ref as the REST base URL) reaches the same server, because the branded host is a transparent proxy in front of the function. The snippets below use the branded URL, which needs no ref.
The in-product Settings → API/MCP → "Connect a client" panel hands out that /mcp endpoint
in both the sign-in and the static-key snippets. It falls back to the raw function URL only on
preview or local builds, where no branded host exists — that URL stays usable with a static key
anywhere.
On the wire it is a valid stateless Streamable-HTTP MCP server: it answers a POST with a
single JSON response and negotiates MCP-Protocol-Version (negotiated through 2025-11-25,
negotiating newer clients — including the modern 2026-07-28 era — down; 2024-11-05 is
accepted on the header but not negotiated, since its HTTP+SSE transport isn't hosted here).
A GET probe is answered by intent: an unauthenticated GET
returns HTTP 401 with a WWW-Authenticate header pointing at the RFC 9728
protected-resource metadata — this is the OAuth discovery challenge, not a failure, and a
connector follows it. An authenticated GET returns 405 (there is no standalone GET SSE
channel; progress is streamed only inline on a POST tools/call).
Progress streaming for long calls. There is one exception to the single-response rule: for
a tools/call where the client sends a progressToken and includes
Accept: text/event-stream, the server streams the response as SSE, emitting periodic
notifications/progress heartbeats while the tool runs and the final JSON-RPC result as the
last event. Each heartbeat resets the client's request timeout, so a long search can outlive
an agent client's ~60s ceiling. Clients that don't opt in (no progressToken, or no SSE in
Accept) get the ordinary single JSON response — use the async search job contract instead
(see search / get_search_result).
Authentication
Send your API key on every request (including initialize and tools/list, so the tool
surface isn't enumerable without a key):
Authorization: Bearer jogl_live_…An X-API-Key: jogl_live_… header works too, as on the REST API. The same Authorization
header also accepts a short-lived OAuth 2.1 access token (a JWT) issued through
the WorkOS AuthKit connector — the one-click connector path — verified against its
configured issuer/audience; an ordinary Supabase app-session token is not accepted. get_me
tells the two apart: an OAuth connection's key prefix starts with oauth_, an API key's with
jogl_. An
invalid, revoked, or expired static key returns a JSON-RPC error -32001 inside HTTP 200, and
so does a rate-limit hit (with data.retryAfterSeconds, and a message that says how long to
wait). Only a tool call counts against the rate limit:
initialize, notifications and tools/list are authenticated but free. A missing credential or a bad
OAuth token returns HTTP 401 with a WWW-Authenticate challenge so the connector can run the
RFC 9728 discovery/refresh handshake. That 401 is reached only by a well-formed request
carrying an id — do not build a client that waits for it on any other request shape.
Unparseable JSON, a batch, or a malformed envelope is answered with a JSON-RPC error inside
HTTP 200 before authentication is consulted at all, and a notification (no id) is
answered with the empty 202 whether or not the credential was good.
A few wire details worth knowing: JSON-RPC batches are not supported (send one request per
POST); a notification is answered with an empty 202; the SSE progress heartbeat fires every
5 seconds; and the write tools have no equivalent of the REST Idempotency-Key header, so retry
them with care. search, continue_search and run_proposal take an optional
idempotency_key instead: where search runs through the app, a retry with the same key returns
the same search rather than starting and billing a second one. Only a call that started a search
is kept under the key: a refusal before that, such as history_withheld from continue_search
or run_proposal, or already_run, proposal_busy, already_claimed or plan_unrunnable
from run_proposal, is not, so asking again with the same key asks again and, once nothing
refuses it, starts and bills a search.
Tools
Each tool is 1:1 with a REST endpoint and enforces the same scope + fences. tools/list shows
only the tools your key's scopes allow; a scope you gain later shows the next time your client
lists the tools, usually when it reconnects.
| Tool | Scope |
|---|---|
get_me / list_my_communities / list_events | (none) |
search / get_search_result | search:read |
list_searches / get_search | history:read |
continue_search / run_proposal | search:read and history:read |
get_my_profile / list_my_connections / get_member / list_members / list_event_attendees / get_community_analytics | profile:read |
self_enrich / get_my_enrich_run | self:enrich |
enrich_community / get_run_status | enrich:write / enrich:read |
add_members / import_contacts / erase_member / create_member_import_upload / preview_member_import / run_member_import / get_member_import / update_community_field | member:write |
create_community | community:write |
get_community_schema | member:write |
sync_event_guests / get_event_guest_sync | member:write and enrich:write / member:write |
list_crm_connections / start_crm_connection / get_crm_fields / save_crm_mapping / run_crm_sync / get_crm_sync | crm:write |
send_message / request_meeting | contact:write |
list_my_notes / add_note | notes:read / notes:write |
get_my_settings / update_my_settings | profile:read or notes:read / settings:write or notes:write |
list_my_communities returns the communities you can read, with your role, whether API writes
are open to you there and their upcoming events; it is where an agent finds the community_id
the community tools take. create_community creates a community you own and pay for, and returns
its community_id, under the rules of Creating a community:
your free trial or plan must cover it, which the tool cannot start (it then answers
coverage_required with a link to the app), and an idempotency_key makes a retried call return
the community the first call created. The CRM tools map and run the syncs of a CRM an admin of
the community connected, on the key that connection saved (see
CRM syncs): no tool takes or returns a CRM key, and their
descriptions tell the model never to ask you for one. To connect a CRM, or give a connection a
new key, start_crm_connection hands your agent a link for you to open, signed in to JOGL, where
you type the key yourself; list_crm_connections with its intent_id tells the agent when the
connection is saved. A one-click connection carries crm:write with write on;
one made before CRM syncs were added gains it from Add new permissions on its card under
Settings → API/MCP → Connected apps, while write is on for it. list_my_connections lists the people you know: your JOGL connections,
the contacts of your own LinkedIn imports and those imported from your mailbox, with how you know
each one. It filters by name, by organisation (where someone works now or worked before) and by
source, and can list the people you dealt with most recently first (see
Identity). import_contacts builds your personal network from
your mailbox: your assistant reads your mail and calendar with its own connectors and sends JOGL
each person's name, addresses, employer and title and how often you dealt with them, never a
message's content. It invites nobody; someone already on JOGL is reported rather than added, so
your assistant can suggest sending them a connection request. With profile:read, list_my_communities also
carries linkedin_import on each community where contacts from your own LinkedIn imports are
members (null everywhere without it), and get_member says how you know one member in
relationship. list_events lists a community's events, past ones included, and
list_event_attendees who attends one, with what each attendee is looking for: a
community's members are not all attendees of its events. An event's attendee list answers
its attendees and the community's admins, as in the app. sync_event_guests pulls a Luma event's new
guests into the community and starts the enrichment that follows (the new guests, and any other
member not enriched yet), as the app's Re-sync button does
(a guest with a JOGL account of their own is sent the community's invitation from you instead), for
the community's owner or an admin; called first with dry_run, it says how many guests are new
and the most credits the enrichment may use, so your agent can ask you before it runs, and
get_event_guest_sync follows the import (see
Event guest sync). Each event also carries the times
the organiser set aside for attendees to meet, the step meetings are booked on and the places
suggested for it. get_member returns the email addresses and LinkedIn links the app shows
you: those the member shares with you, or all of them if you are one of the community's admins
(see Community members). send_message sends a
member who has signed in to JOGL a direct message from you, in two calls: the first returns a
preview and a confirmation_id, and only a second call with that id sends it (see
Direct messages). Your agent is asked to show you
the message before that second call, but JOGL cannot check that it did, so give the scope only
to an agent you trust to write as you. request_meeting asks an attendee of an event you both
attend for a meeting, the same way: called without a time, it lists the times still free for
both of you, as the app's picker does; called with one, it returns a preview and a
confirmation_id; called with that id alone, it sends the request (see
Meeting requests). Both need
contact:write, on a static key or on a connection approved on a consent screen that lists them;
without it, an agent can draft the messages and you send them yourself. list_my_notes reads
the private notes you keep on people's profile pages in the app, and add_note adds one, as you
(see Private notes). Both reach only the people you can
see in JOGL. A note in which AI structuring marked a detail as sensitive (health, religious beliefs
and the like) comes back withheld, with no text, but a note never run through AI structuring comes
back as you wrote it, and so does a detail you add to a note after its last structuring. add_members adds people
to a community, with the tags you give each of them, such as a cohort or a role: tags are added to
the ones a person carries, never replace them, and a name the community has no tag for becomes a
new tag (see Community members). Each person needs
an email, a LinkedIn URL, a phone number in international form (with their name) or an ORCID, or
else a name with both an organisation and a title: a name alone is refused.
create_member_import_upload, preview_member_import, run_member_import and
get_member_import import a spreadsheet of people into a community. For any file longer than a
few dozen rows, the agent first asks for an upload link and gives it to you: open it in your
browser and drop the file there, a .csv or an .xlsx (the page turns its first sheet into CSV).
The agent never has to copy the file out, which would be slow for a large file, could change a
value on the way, and stops at 4.5 MB through this connector. The agent then reads the file's
columns, a suggested mapping and the community's fields, starts the import with a mapping it chose,
which can create community fields, and follows it until it ends. A short file can also be sent as
CSV text. Ask your agent to show you the mapping before it runs the import (see
Importing a spreadsheet).
A field the import creates is seen by the community's admins only unless the agent says otherwise
when it creates it; update_community_field changes afterwards who sees a field's values, whether
search filters by it and whether it holds a sensitive kind of data.
get_community_analytics answers questions about a whole community in one call: where its members work now and in which
sectors, their roles, countries, schools, skills and research domains, and how they answered
the community's own list fields. Apart from the countries and the sectors, these are the
figures the app's analytics page shows (see
Community analytics).
get_community_schema lists a community's custom fields and tags, so your agent can map the
file to the fields the community already has and check what an import created, and get_member
returns a member's values in the custom fields you can see in the app, so it can check what landed
where.
Searches started through the search tool are billed exactly like the REST endpoint —
see Credits. search takes the same options as
POST /v1/search: event_id keeps a community search to
one event's attendees (an id from list_events or list_my_communities), and location
(country, city or near) keeps it to the members whose stored location matches.
On search, both need the search to run through the app, as described there. list_members
takes the same location filter, on any key, so an agent can count who is where.
self_enrich and get_my_enrich_run need a static key with self:enrich: a connection made
through the one-click connector never carries that scope, so those tools are not in its tool
list. A self_enrich call costs what POST /v1/me/enrich
does: 1 credit whatever the owner's plan (on the free plan, from the monthly free credits when the
account receives them, or bought ones), with no cooldown. send_message and request_meeting need your account's
Write access on (422 while it is off) and contact:write, which a one-click connection
carries when you approved it on a consent screen that lists sending messages and meeting
requests, as every screen has since that permission was added, and kept write on. A connection
approved on an earlier screen does not carry it, and reconnecting may not add it, since a client
can reconnect on its original approval. Its card under Settings → API/MCP → Connected apps
then offers Add new permissions, which lists what the connection would gain, worded as on the
consent screen, and adds it once you approve, contact:write only while write is on for the
connection. Revoking the connection there and connecting the client again also works.
list_my_notes needs notes:read and add_note needs notes:write and your account's Write
access on (422 while it is off). Neither is in a key preset: tick them under Advanced. A
one-click connection carries notes:read when you approved it on a consent screen that lists
reading your private notes, as every screen has since that permission was added, and
notes:write too while write is on for it; making it read-only takes notes:write off and keeps
notes:read. A connection approved on an earlier screen carries neither, and reconnecting may
not add them: use Add new permissions on its card under Settings → API/MCP → Connected
apps (notes:write only while write is on for it), or revoke it there and connect the client
again.
get_my_settings reads, and update_my_settings changes, a few of your own settings, so that you
can reach them through your agent without opening the app: who can see your profile, who can
message you, who can send you connection requests, the notification and admin-reminder emails
JOGL sends you, and whether your notes serve as context for JOGL's search and AI (see
Account settings). Nothing else, and never anyone
else's. Reading needs profile:read (the notes switch notes:read); changing needs
settings:write, except the notes switch, which needs notes:write, and settings:write too to
turn it on. Your account's Write access must be on (422 while it is off), and a profile can
be made public only once JOGL has verified your account. Your agent is asked to tell you what a
setting does and to change it only once you agree, never on its own initiative, but JOGL cannot
check that it did. When add_note reports that you never answered whether to use your notes as
context, your agent asks you once and records your answer. settings:write is not in a key
preset: tick it under Advanced. A one-click connection carries it beside the write tier when you
approved it on a consent screen that lists changing your own settings; one approved on an earlier
screen gains it from Add new permissions on its card, while write is on for it. Making a
connection read-only takes settings:write off.
list_searches and get_search read your past searches, app searches included (see
Search history), continue_search asks a
follow-up question in one of them (see
Follow-up questions), and run_proposal runs a
Deep Search proposal one of them holds (see
Running a proposal). All four need
history:read, and continue_search and run_proposal need search:read too. A static key
carries history:read when created with the Read or Read + Write preset. A connection made
through the one-click connector carries it when you approved it after the date the scope was
switched on for connections. One approved before that date does not, even when its consent
screen already listed reading your past searches, and reconnecting may not add it, since a client
can reconnect on its original approval: to gain it, use Add new permissions on the
connection's card under Settings → API/MCP → Connected apps, or revoke it there and connect
the client again. Until the scope is switched on, no one-click connection carries it, and the card
does not offer it. Without it, the four tools
are left out of the tool list and a call to one returns a scope error, and a Deep Search proposal
from a search in mode auto says that it runs in the app, from its app_url. Settings → API/MCP → Connected apps shows each connection's scopes.
Connecting a client
Claude Code
claude mcp add --transport http --scope user jogl-network \
https://my.jogl.network/mcp \
--header "Authorization: Bearer jogl_live_…"--scope user stores it in your user config so it's available in every project. Verify with
claude mcp list (it should show ✓ Connected). Tools become callable on the next
session start.
Project-scoped .mcp.json (never commit a key)
{
"mcpServers": {
"jogl-network": {
"type": "http",
"url": "https://my.jogl.network/mcp",
"headers": { "Authorization": "Bearer ${JOGL_API_KEY:-}" }
}
}
}Use the empty-default form ${JOGL_API_KEY:-} (not a bare ${JOGL_API_KEY}) so an unset
variable doesn't break parsing of the whole file. Export JOGL_API_KEY in your shell (local)
or set it in the environment's variables (Claude Code on the web).
Claude Desktop (static key)
Desktop's connector UI is OAuth-only, so for a static key bridge through
@automattic/mcp-remote in
claude_desktop_config.json, keeping the key in env:
{
"mcpServers": {
"jogl-network": {
"command": "npx",
"args": [
"-y", "@automattic/mcp-remote",
"https://my.jogl.network/mcp",
"--header", "Authorization:${AUTH_HEADER}"
],
"env": { "AUTH_HEADER": "Bearer jogl_live_…" }
}
}
}For one-click sign-in instead of a key, use the in-product Settings → API/MCP → "Connect a client" panel. It has a step for Claude Code, Claude Desktop, Codex, Cursor and any other client that can sign in through the browser, with the URL filled in: you sign in to JOGL once, and no API key is needed.