sondahub / MCP test server
MCP test server
A free, public Model Context Protocol server with real data behind it: tools over seven populated APIs, domain tools with real rules, resources, prompts and completions — and tools made to put an MCP client through its paces. With an OAuth-protected twin for the authorization flow.
Streamable HTTP at https://api.sondahub.com/mcp. No keys, no accounts. 26 tools, 47 resources (paged 20 at a time) and 2 templates, 4 prompts.
Connect
- Address
- https://api.sondahub.com/mcp
- Transport
- Streamable HTTP, stateless
- Protocol
- 2025-11-25, 2025-06-18, 2025-03-26, 2024-11-05
- Auth
- none — or OAuth 2.1 at /mcp/secure
Any MCP client that speaks Streamable HTTP connects with the address alone. A few common ones:
claude mcp add --transport http sondahub https://api.sondahub.com/mcp
{
"mcpServers": {
"sondahub": {
"url": "https://api.sondahub.com/mcp"
}
}
}
{
"servers": {
"sondahub": {
"type": "http",
"url": "https://api.sondahub.com/mcp"
}
}
}
npx @modelcontextprotocol/inspector # Transport: Streamable HTTP · URL: https://api.sondahub.com/mcp
How it behaves
Every request is answered on its own: no session, no server-to-client stream (a GET for one answers 405), nothing remembered between requests. That is the only kind of MCP server a site that stores nothing can be — and a valid one under the specification.
Reads are real. The write tools — create_record, update_record, delete_record, bank_transfer, flights_book, fleet_send_command, helpdesk_reply — validate, run the real rules and answer as a real server would. The server keeps nothing: the HTTP answer carries the change in an X-Sondahub-Session token, and a client that sends it back sees its writes. Their results carry _note in the structured content and "sondahub/write" in _meta. Calls in one JSON-RPC batch share the session, so a batch can create and then read.
Answers are JSON, except when the client accepts text/event-stream and calls a tool that streams (slow_operation): then progress notifications (when the request carries a progressToken) and log messages arrive first, the result last. Wrong arguments come back as a tool result with isError: true naming each problem, so a model can correct itself; an unknown tool or method is a JSON-RPC error.
Data
describe_api Describe an API · read-only
The collections of one sondahub API with their record counts, fields (types, required, read-only, allowed values) and relations. Start here to learn what list_records and the other data tools can ask for.
| Argument | Type | Notes |
|---|---|---|
apirequired | string: store | fleet | bank | social | helpdesk | flights | identity | Which API: store, fleet, bank, social, helpdesk, flights, identity. |
list_records List records · read-only · structured output
A page of records from any collection, with filters, search, sorting, field selection and embedded relations. Filters are an object whose keys are field names, optionally with a suffix: _ne, _gt, _gte, _lt, _lte, _like (contains, case-insensitive), _in (a list), _null (true/false); a dotted key reaches into a JSON field (address.country). Example: {"price_lt": 20, "in_stock": true, "category_id_in": [1, 6]}.
| Argument | Type | Notes |
|---|---|---|
apirequired | string: store | fleet | bank | social | helpdesk | flights | identity | Which API: store, fleet, bank, social, helpdesk, flights, identity. |
collectionrequired | string | A collection of that API, e.g. products, orders, accounts, transactions, devices, flights, tickets, posts. describe_api lists them. |
filters | object | Field conditions, all of which must hold. |
q | string | Search across the text fields. |
sort | string | Comma-separated fields; prefix one with - for descending. Example: "-price,name". |
page | integer (≥ 1) | Default 1. |
limit | integer (≥ 1, ≤ 100) | Default 10. |
fields | array | Only these fields back (id is always included). |
expand | array | Relations to embed, e.g. ["customer", "items"] on orders. |
get_record Get a record · read-only
One record by id, optionally with relations embedded.
| Argument | Type | Notes |
|---|---|---|
apirequired | string: store | fleet | bank | social | helpdesk | flights | identity | Which API: store, fleet, bank, social, helpdesk, flights, identity. |
collectionrequired | string | A collection of that API, e.g. products, orders, accounts, transactions, devices, flights, tickets, posts. describe_api lists them. |
idrequired | integer (≥ 1) | The record id. |
expand | array |
create_record Create a record · writes · simulated
Create a record in any collection. Validated and answered exactly as the REST API answers a POST — ids, computed fields, related records — but simulated: nothing is stored. describe_api lists the fields.
| Argument | Type | Notes |
|---|---|---|
apirequired | string: store | fleet | bank | social | helpdesk | flights | identity | Which API: store, fleet, bank, social, helpdesk, flights, identity. |
collectionrequired | string | A collection of that API, e.g. products, orders, accounts, transactions, devices, flights, tickets, posts. describe_api lists them. |
recordrequired | object | The fields of the new record. |
update_record Update a record · writes · simulated
Change fields of a record (or replace it whole with replace: true). Validated and answered as the REST API answers a PATCH or PUT, including status rules such as a ticket’s allowed moves — simulated, nothing is stored.
| Argument | Type | Notes |
|---|---|---|
apirequired | string: store | fleet | bank | social | helpdesk | flights | identity | Which API: store, fleet, bank, social, helpdesk, flights, identity. |
collectionrequired | string | A collection of that API, e.g. products, orders, accounts, transactions, devices, flights, tickets, posts. describe_api lists them. |
idrequired | integer (≥ 1) | The record id. |
changesrequired | object | The fields to change. |
replace | boolean | true: the record becomes exactly these fields (PUT). Default false. |
delete_record Delete a record · destructive · simulated
Delete a record — simulated: the answer is what would have been removed; the record is still there afterwards.
| Argument | Type | Notes |
|---|---|---|
apirequired | string: store | fleet | bank | social | helpdesk | flights | identity | Which API: store, fleet, bank, social, helpdesk, flights, identity. |
collectionrequired | string | A collection of that API, e.g. products, orders, accounts, transactions, devices, flights, tickets, posts. describe_api lists them. |
idrequired | integer (≥ 1) | The record id. |
Store
store_search_products Search products · read-only · structured output
Search the store’s active products by words, category, price and stock.
| Argument | Type | Notes |
|---|---|---|
query | string | Words to look for in the name, brand and description. |
category | string: audio | computers | home | outdoors | fitness | books | toys | garden | |
min_price | number (≥ 0) | |
max_price | number (≥ 0) | |
in_stock | boolean | |
sort | string: price | -price | rating | -rating | name | Default "-rating". |
limit | integer (≥ 1, ≤ 50) | Default 10. |
store_order_status Order status · read-only · structured output
Where an order is: status, dates, tracking, the customer and the lines, by order number (SH-100042).
| Argument | Type | Notes |
|---|---|---|
order_numberrequired | string (matches ^SH-\d{6}$) | Like SH-100042. |
Bank
bank_account_summary Account summary · read-only
An account with its holder, cards and latest transactions.
| Argument | Type | Notes |
|---|---|---|
account_idrequired | integer (≥ 1) | The record id. |
transactions | integer (≥ 1, ≤ 50) | Default 5. |
bank_transfer Transfer money · writes · simulated
Move money between two accounts of the same currency. Checks both accounts and the funds, then answers with the transfer and both transactions. The balances move for whoever carries the session token.
| Argument | Type | Notes |
|---|---|---|
from_account_idrequired | integer (≥ 1) | The record id. |
to_account_idrequired | integer (≥ 1) | The record id. |
amountrequired | number (≥ 0.01) | |
description | string |
bank_convert_currency Convert currency · read-only · structured output
Convert an amount with the bank’s FX table, directly or across USD.
| Argument | Type | Notes |
|---|---|---|
amountrequired | number | |
fromrequired | string: USD | EUR | GBP | ARS | BRL | MXN | JPY | CAD | AUD | CHF | |
torequired | string: USD | EUR | GBP | ARS | BRL | MXN | JPY | CAD | AUD | CHF |
Fleet
fleet_device_status Device status · read-only
A device by serial: its site, latest readings and open alerts.
| Argument | Type | Notes |
|---|---|---|
serialrequired | string | Like TH200-7K2M4Q; list_records on fleet/devices shows them. |
readings | integer (≥ 1, ≤ 50) | Default 5. |
fleet_send_command Send a device command · writes · simulated
Send a command to a device; an online device acknowledges it in the answer, an offline one queues it — simulated.
| Argument | Type | Notes |
|---|---|---|
serialrequired | string | |
actionrequired | string: reboot | set_config | report_now | update_firmware | identify | |
params | object | For set_config: {"report_interval_s": 30}; for update_firmware: {"version": "3.0.0"}. |
Flights
flights_search Search flights · read-only
Flights between two airports (IATA codes), optionally on one local date. The seed’s flights span 2026-08-25 to 2026-09-08.
| Argument | Type | Notes |
|---|---|---|
originrequired | string (matches ^[A-Z]{3}$) | IATA code, e.g. MIA. |
destination | string (matches ^[A-Z]{3}$) | IATA code, e.g. EZE. |
date | string (date) | Departure date at the origin, e.g. 2026-09-02. |
include_cancelled | boolean | Default false. |
limit | integer (≥ 1, ≤ 50) | Default 10. |
flights_book Book a seat · writes · simulated
Book a seat on a flight: refuses cancelled, departed and full flights, picks a free seat in the cabin, prices the fare and mints a record locator — simulated.
| Argument | Type | Notes |
|---|---|---|
flight_idrequired | integer (≥ 1) | The record id. |
first_namerequired | string | |
last_namerequired | string | |
email | string | |
cabin | string: economy | premium | business | first | Default "economy". |
Helpdesk
helpdesk_ticket Read a ticket · read-only
A ticket by number (HD-10042) with its customer, assignee and whole message thread.
| Argument | Type | Notes |
|---|---|---|
ticket_numberrequired | string (matches ^HD-\d+$) |
helpdesk_reply Reply to a ticket · writes · simulated
Add a message to a ticket as the agent or the customer; a customer reply reopens a pending ticket — simulated.
| Argument | Type | Notes |
|---|---|---|
ticket_numberrequired | string (matches ^HD-\d+$) | |
bodyrequired | string | |
as | string: agent | customer | Default "agent". |
internal | boolean | Default false. |
Social
social_profile User profile · read-only
A user by username with their counters and latest posts.
| Argument | Type | Notes |
|---|---|---|
usernamerequired | string | |
posts | integer (≥ 1, ≤ 50) | Default 5. |
Testing a client
echo Echo · read-only
Answers with the message you send, optionally repeated.
| Argument | Type | Notes |
|---|---|---|
messagerequired | string | |
repeat | integer (≥ 1, ≤ 10) | Default 1. |
whoami Who am I · read-only
On the OAuth-protected endpoint (/mcp/secure): the identity and scopes in your access token. On the open endpoint: says there is none.
No arguments.
add Add two numbers · read-only · structured output
a + b, as text and as structured content checked by an output schema.
| Argument | Type | Notes |
|---|---|---|
arequired | number | |
brequired | number |
slow_operation Slow operation with progress · read-only · streams progress
Takes its time, in steps. When the client sends a progressToken and accepts text/event-stream, each step arrives as a progress notification and a log message before the result.
| Argument | Type | Notes |
|---|---|---|
seconds | number (≥ 0.5, ≤ 10) | Default 3. |
steps | integer (≥ 1, ≤ 20) | Default 5. |
fail Fail on purpose · read-only
Fails, to see how a client shows it: tool_error answers a result with isError: true (the model sees it), protocol_error answers a JSON-RPC error (the client sees it).
| Argument | Type | Notes |
|---|---|---|
mode | string: tool_error | protocol_error | Default "tool_error". |
generate_image Draw an image · read-only
A PNG of the size and colour you ask for, as image content.
| Argument | Type | Notes |
|---|---|---|
width | integer (≥ 1, ≤ 512) | Default 160. |
height | integer (≥ 1, ≤ 512) | Default 100. |
color | string (matches ^[0-9a-fA-F]{6}$) | Hex, no #. Default "f1772c". |
mixed_content Every content type · read-only
One result carrying every content type — text for the user and for the model (annotations), an image, a sound, a resource link and an embedded resource — to see how a client renders each.
No arguments.
large_text A large answer · read-only
A text result of the size you ask for, to see how a client copes with a big answer.
| Argument | Type | Notes |
|---|---|---|
kilobytes | integer (≥ 1, ≤ 512) | Default 64. |
Resources
Every collection is a resource — sondahub://{api}/{collection}, 37 of them — holding its first 25 records, the total and the fields, with later pages at …/page/{n}. Besides those:
| URI | Type | What |
|---|---|---|
sondahub://guide | text/markdown | What the tools, resources and prompts here are, and the rule that writes are simulated. |
sondahub://apis | application/json | The seven APIs with their collections and record counts. |
sondahub://store/openapi.json | application/json | The OpenAPI 3 description of the Store REST API. |
sondahub://fleet/openapi.json | application/json | The OpenAPI 3 description of the Fleet REST API. |
sondahub://bank/openapi.json | application/json | The OpenAPI 3 description of the Bank REST API. |
sondahub://social/openapi.json | application/json | The OpenAPI 3 description of the Social REST API. |
sondahub://helpdesk/openapi.json | application/json | The OpenAPI 3 description of the Helpdesk REST API. |
sondahub://flights/openapi.json | application/json | The OpenAPI 3 description of the Flights REST API. |
sondahub://identity/openapi.json | application/json | The OpenAPI 3 description of the Identity REST API. |
sondahub://utils/sample.png | image/png | A binary resource (a PNG), returned as a blob. |
Templates
| URI template | What |
|---|---|
sondahub://{api}/{collection}/{id} | One record of any collection by id, e.g. sondahub://store/orders/7. |
sondahub://{api}/{collection}/page/{page} | 25 records per page, e.g. sondahub://bank/transactions/page/3. |
Completion works on the template arguments: api, collection (given the api), id and page.
Prompts
| Prompt | Arguments | What |
|---|---|---|
explore_api | apirequired | Get to know one of the APIs: what it holds, how the collections relate, and three good first questions to ask it. |
investigate_order | order_numberrequired | Look into a store order: what was bought, where it is, and anything odd about it. |
fleet_health_report | site_code | A health report of the IoT fleet, or of one site. |
support_reply | ticket_numberrequiredtone | Draft the next agent reply on a helpdesk ticket from its whole thread. |
Each prompt embeds the data it is about as a resource in its messages. Completion offers API names, order numbers, ticket numbers, site codes and tones as you type.
Over plain HTTP
MCP over Streamable HTTP is JSON-RPC in a POST, so curl — or a request in Sonda — speaks it too:
curl https://api.sondahub.com/mcp \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"curl","version":"1"}}}'
curl https://api.sondahub.com/mcp \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"store_order_status","arguments":{"order_number":"SH-100007"}}}'
curl -N https://api.sondahub.com/mcp \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"slow_operation","arguments":{"seconds":3,"steps":3},"_meta":{"progressToken":"p1"}}}'
Questions
Is there a public MCP server I can test my client against?
This one: https://api.sondahub.com/mcp, Streamable HTTP, no auth, no signup. It has real data behind its tools, every content type a tool result can carry, a tool that streams progress, one that fails on purpose, and argument checking that answers like a strict server.
Does it keep a session or push notifications?
No. It is stateless: no Mcp-Session-Id, a GET for a server-to-client stream answers 405, and nothing is remembered between requests — a valid shape under the specification. Progress and log notifications arrive on the response stream of the call that streams (slow_operation).
Which MCP protocol versions does it speak?
2025-11-25, 2025-06-18, 2025-03-26, 2024-11-05. It answers initialize with the version the client asked for when it knows it, and the newest otherwise.
Can an agent change the data?
Yes, for itself. The write tools validate, run the real rules and answer as a real server would; the HTTP answer carries an X-Sondahub-Session token with the change, and a client that sends it back sees its writes on the next call. A client that does not gets the seed again — the results say which in _note and _meta, so a test can tell.
Is there an MCP server with OAuth to test the authorization flow?
Yes: https://api.sondahub.com/mcp/secure is this server behind OAuth 2.1 as the MCP specification describes it — a 401 pointing at protected-resource metadata, authorization-server metadata, dynamic client registration, PKCE, resource indicators and scopes. The flow, step by step.