{"name":"net.foxnose/knowledge","title":"FoxNose Knowledge","description":"Hybrid search, schema introspection and record read/write over FoxNose collections","version":"2.0.0","websiteUrl":"https://foxnose.net/product/knowledge-mcp","protocolVersion":"2025-03-26","endpoint":{"url":"https://mcp.foxnose.net/_mcp","transport":"streamable-http"},"authentication":{"required":true,"schemes":["Simple","Bearer"],"description":"Send Authorization: Simple <public_key>:<private_key>, or Authorization: Bearer <fxk_ token> for hosted connectors that cannot choose a scheme."},"headers":[{"name":"x-foxnose-prefix","required":false,"description":"Which Flux API to expose. Only needed when the key can read more than one."}],"tools":[{"name":"discover_resources","description":"List available Flux resources and capabilities. Start here: every other tool takes a resource_id from this list, and a resource's capabilities say which of them it will accept.","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"annotations":{"title":"Discover resources","readOnlyHint":true,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false},"outputSchema":{"type":"object","required":["api","resources"],"properties":{"api":{"type":"string","description":"Prefix of the Flux API these resources are reached through."},"resources":{"type":"array","items":{"type":"object","required":["resource_id","capabilities"],"properties":{"resource_id":{"type":"string","description":"Pass verbatim as `resource_id` to every other tool."},"title":{"type":"string"},"name":{"type":"string"},"description":{"type":"string","description":"The only free text about what a collection holds. Empty when the owner set none."},"path_template":{"type":"string"},"capabilities":{"type":"array","items":{"type":"string"},"description":"Which reads this resource accepts: get_many, get_one, search."},"writable":{"type":"array","items":{"type":"string"},"description":"Empty unless the key carries write grants for this resource."},"required_parents":{"type":"array","description":"Placeholders the `parents` argument must fill for a nested resource.","items":{"type":"object","properties":{"name":{"type":"string"},"resource":{"type":"string"},"required":{"type":"boolean"}}}},"schema_ref":{"type":"string"}}}},"usage_rules":{"type":"array","items":{"type":"string"}}}}},{"name":"describe_resource","description":"Return schema and searchability for a resource_id. Call it before filtering or sorting, to learn the field names and which fields are searchable. Searching by text does not need it.","inputSchema":{"type":"object","required":["resource_id"],"properties":{"resource_id":{"type":"string","description":"Resource identifier from discover_resources."},"parents":{"type":"object","description":"Parent placeholders for nested resource_id templates."}},"additionalProperties":false},"annotations":{"title":"Describe resource","readOnlyHint":true,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false},"outputSchema":{"type":"object","required":["json_schema","resource_id"],"properties":{"json_schema":{"type":"object","description":"JSON Schema of the record body. Reference fields appear as plain strings; nothing here marks them as references.","additionalProperties":true},"searchable_fields":{"type":"array","items":{"type":"string"}},"non_searchable_fields":{"type":"array","items":{"type":"string"}},"actions":{"type":"array","items":{"type":"string"}},"required_parents":{"type":"array","items":{"type":"object","additionalProperties":true}},"alias":{"type":"string"},"content_type":{"type":"string"},"schema_key":{"type":"string"},"schema_version_key":{"type":"string"},"path":{"type":"string"},"resource_id":{"type":"string"},"description":{"type":"string"},"name":{"type":"string"}}}},{"name":"get_record","description":"Fetch one record by id for a resource_id, whole and untruncated. Use it when the id is already known; where the resource lists get_many among its discover_resources capabilities, search_records or query_records finds one first.","inputSchema":{"type":"object","required":["resource_id","record_id"],"properties":{"resource_id":{"type":"string","description":"Resource identifier from discover_resources."},"record_id":{"type":"string","description":"Document key (_sys.key) to retrieve."},"parents":{"type":"object","description":"Parent placeholders for nested resource_id templates."},"locales":{"type":"array","items":{"type":"string"},"description":"Locale codes (`en`, `fr`, ...) to keep in localized fields. Omit it to get every locale the record carries, which is the right default unless one language was asked for. The codes are the keys of a localized field in any record of this collection; a code the environment does not use is not an error, it simply matches nothing."},"populate":{"type":"array","items":{"type":"string"},"description":"Reference and relation fields come back as opaque record keys. Name those fields here and the records they point at are fetched and grafted in place of the key, which costs one call instead of two. Populated records are never shortened, whatever the text limit is."}},"additionalProperties":false},"annotations":{"title":"Get record","readOnlyHint":true,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false},"outputSchema":{"type":"object","required":["found"],"properties":{"found":{"type":"boolean"},"record":{"type":["object","null"],"required":["id","data","_sys"],"description":"Null when `found` is false. Never shortened, whatever the collection's text limit is.","properties":{"id":{"type":"string","description":"Document key. Pass it to get_record as `record_id`."},"data":{"type":"object","description":"The record itself, shaped by the collection schema that describe_resource returns.","additionalProperties":true},"_sys":{"type":"object","required":["key"],"properties":{"key":{"type":"string"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"},"revision":{"type":"string"},"folder":{"type":"string"},"relevance":{"type":["number","null"]},"truncated":{"type":"array","description":"Present only when this record had text shortened. One entry per field that was cut.","items":{"type":"object","required":["field","original_length"],"properties":{"field":{"type":"string","description":"Path to the shortened field, `author.bio` for one reached through populate."},"locale":{"type":["string","null"]},"original_length":{"type":"integer"}}}}}}}}}}},{"name":"query_records","description":"List records by filters/sort/cursor for a resource_id. Use it when the selection is expressible as field conditions; when the request is in the user's own words, search_records ranks by meaning instead. Long text fields may be shortened in results; a shortened record carries `_sys.truncated`. Where the resource lists get_one among its discover_resources capabilities, call get_record with that record's id for the full document. Returns 5 records unless `limit` says otherwise — enough to find a document and then fetch it. When the task is to enumerate or count, raise `limit` (up to 100) instead of paging through small pages: every page is resent on every later turn, so many small pages cost far more than one large one.","inputSchema":{"type":"object","required":["resource_id"],"properties":{"resource_id":{"type":"string","description":"Resource identifier from discover_resources."},"parents":{"type":"object","description":"Parent placeholders for nested resource_id templates."},"filters":{"type":"array","description":"Field conditions, all of which must hold. `field` is a field path from the collection's describe_resource schema, `op` defaults to `eq`, and `value` follows the operator: a list for `in`/`not_in`, a two-element list for `between`, a single value otherwise.","items":{"type":"object","required":["field","value"],"properties":{"field":{"type":"string"},"op":{"type":"string","enum":["between","contains","endswith","eq","gt","gte","icontains","iendswith","ieq","iincludes","in","includes","istartswith","lt","lte","ne","not_eq","not_in","startswith"]},"value":{}},"additionalProperties":false}},"sort":{"type":"array","items":{"type":"string"},"description":"Field names to order by, most significant first. Prefix a name with `-` for descending, so `-_sys.created_at` is newest first. `_sys` fields are sortable as well as schema fields; describe_resource lists the schema ones."},"limit":{"type":"integer","minimum":1,"maximum":100,"default":5,"description":"Records per call. Defaults to 5, which is enough to pick a hit and fetch it in full with get_record. Raise it (up to 100) when the task is to enumerate or count rather than to find."},"cursor":{"type":"string","description":"Pass back `page.next_cursor` from a previous response to get the following page. Reach for it only when a page really has to be continued: raising `limit` costs one call where paging costs one per page, and every page already fetched is re-sent on every later turn."},"locales":{"type":"array","items":{"type":"string"},"description":"Locale codes (`en`, `fr`, ...) to keep in localized fields. Omit it to get every locale the record carries, which is the right default unless one language was asked for. The codes are the keys of a localized field in any record of this collection; a code the environment does not use is not an error, it simply matches nothing."},"populate":{"type":"array","items":{"type":"string"},"description":"Reference and relation fields come back as opaque record keys. Name those fields here and the records they point at are fetched and grafted in place of the key. Prefer it whenever a list result carries references the task needs: resolving them afterwards costs one call per record, and every earlier result is re-sent on every later turn, so ten resolved by hand cost far more than one populated call. Populated records obey the same text limit as the rest of this response; call get_record on a key for the unshortened record."}},"additionalProperties":false},"annotations":{"title":"Query records","readOnlyHint":true,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false},"outputSchema":{"type":"object","required":["items","page"],"properties":{"items":{"type":"array","description":"The matching records, at most `limit` of them.","items":{"type":"object","required":["id","data","_sys"],"properties":{"id":{"type":"string","description":"Document key. Pass it to get_record as `record_id`."},"data":{"type":"object","description":"The record itself, shaped by the collection schema that describe_resource returns.","additionalProperties":true},"_sys":{"type":"object","required":["key"],"properties":{"key":{"type":"string"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"},"revision":{"type":"string"},"folder":{"type":"string"},"relevance":{"type":["number","null"]},"truncated":{"type":"array","description":"Present only when this record had text shortened. One entry per field that was cut.","items":{"type":"object","required":["field","original_length"],"properties":{"field":{"type":"string","description":"Path to the shortened field, `author.bio` for one reached through populate."},"locale":{"type":["string","null"]},"original_length":{"type":"integer"}}}}}}}}},"page":{"type":"object","required":["has_more","returned","limit"],"properties":{"has_more":{"type":"boolean"},"next_cursor":{"type":["string","null"],"description":"Pass back as `cursor` to continue. Null when there is no further page."},"previous_cursor":{"type":["string","null"]},"returned":{"type":"integer"},"limit":{"type":"integer"}}},"truncation":{"type":"object","description":"Present when a text limit is in force for this collection.","properties":{"applied":{"type":"boolean"},"max_text_chars":{"type":"integer"},"note":{"type":"string"}}}}}},{"name":"search_records","description":"Search records for a resource_id; `search_type` decides how the query is matched and defaults to hybrid. Use it when the request is in the user's own words; when the selection is an exact field condition, query_records filters without ranking. Long text fields may be shortened in results; a shortened record carries `_sys.truncated`. Where the resource lists get_one among its discover_resources capabilities, call get_record with that record's id for the full document. Returns 5 records unless `limit` says otherwise — enough to choose among hits and then fetch one. When the task is to enumerate or count, raise `limit` (up to 100) instead of paging through small pages: every page is resent on every later turn, so many small pages cost far more than one large one.","inputSchema":{"type":"object","required":["resource_id","query"],"properties":{"resource_id":{"type":"string","description":"Resource identifier from discover_resources."},"query":{"type":"string","description":"The text to search for. Whether it is matched by keyword, by meaning, or by both is decided by `search_type`."},"search_type":{"type":"string","enum":["text","semantic","hybrid","vector_boosted"],"default":"hybrid","description":"How `query` is matched. `hybrid` (the default) ranks a union of keyword and meaning matches and is the right choice when in doubt. `text` matches keywords only — use it for identifiers, codes, names and exact phrases. `semantic` matches meaning only, so it can return records that share no word with the query, and it returns nothing at all on a collection with no embeddings. `vector_boosted` keeps `text`'s guarantee that every result really contains the query terms and only reorders them, lifting the closest few by meaning to the top; the rest keep the collection's default order and report `_sys.relevance` 0."},"parents":{"type":"object","description":"Parent placeholders for nested resource_id templates."},"filters":{"type":"array","description":"Field conditions, all of which must hold. `field` is a field path from the collection's describe_resource schema, `op` defaults to `eq`, and `value` follows the operator: a list for `in`/`not_in`, a two-element list for `between`, a single value otherwise.","items":{"type":"object","required":["field","value"],"properties":{"field":{"type":"string"},"op":{"type":"string","enum":["between","contains","endswith","eq","gt","gte","icontains","iendswith","ieq","iincludes","in","includes","istartswith","lt","lte","ne","not_eq","not_in","startswith"]},"value":{}},"additionalProperties":false}},"limit":{"type":"integer","minimum":1,"maximum":100,"default":5,"description":"Records per call. Defaults to 5, which is enough to pick a hit and fetch it in full with get_record. Raise it (up to 100) when the task is to enumerate or count rather than to find."},"cursor":{"type":"string","description":"Pass back `page.next_cursor` from a previous response to get the following page. Reach for it only when a page really has to be continued: raising `limit` costs one call where paging costs one per page, and every page already fetched is re-sent on every later turn."},"locales":{"type":"array","items":{"type":"string"},"description":"Locale codes (`en`, `fr`, ...) to keep in localized fields. Omit it to get every locale the record carries, which is the right default unless one language was asked for. The codes are the keys of a localized field in any record of this collection; a code the environment does not use is not an error, it simply matches nothing."},"populate":{"type":"array","items":{"type":"string"},"description":"Reference and relation fields come back as opaque record keys. Name those fields here and the records they point at are fetched and grafted in place of the key. Prefer it whenever a list result carries references the task needs: resolving them afterwards costs one call per record, and every earlier result is re-sent on every later turn, so ten resolved by hand cost far more than one populated call. Populated records obey the same text limit as the rest of this response; call get_record on a key for the unshortened record."}},"additionalProperties":false},"annotations":{"title":"Search records","readOnlyHint":true,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false},"outputSchema":{"type":"object","required":["items","page"],"properties":{"items":{"type":"array","description":"The matching records, ranked, at most `limit` of them.","items":{"type":"object","required":["id","data","_sys"],"properties":{"id":{"type":"string","description":"Document key. Pass it to get_record as `record_id`."},"data":{"type":"object","description":"The record itself, shaped by the collection schema that describe_resource returns.","additionalProperties":true},"_sys":{"type":"object","required":["key"],"properties":{"key":{"type":"string"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"},"revision":{"type":"string"},"folder":{"type":"string"},"relevance":{"type":["number","null"]},"truncated":{"type":"array","description":"Present only when this record had text shortened. One entry per field that was cut.","items":{"type":"object","required":["field","original_length"],"properties":{"field":{"type":"string","description":"Path to the shortened field, `author.bio` for one reached through populate."},"locale":{"type":["string","null"]},"original_length":{"type":"integer"}}}}}}}}},"page":{"type":"object","required":["has_more","returned","limit"],"properties":{"has_more":{"type":"boolean"},"next_cursor":{"type":["string","null"],"description":"Pass back as `cursor` to continue. Null when there is no further page."},"previous_cursor":{"type":["string","null"]},"returned":{"type":"integer"},"limit":{"type":"integer"}}},"truncation":{"type":"object","description":"Present when a text limit is in force for this collection.","properties":{"applied":{"type":"boolean"},"max_text_chars":{"type":"integer"},"note":{"type":"string"}}},"execution_info":{"type":"object","description":"Which search mode actually ran, which can differ from `search_type` when a mode is unavailable.","properties":{"applied_search_type":{"type":"string"},"search_mode":{"type":"string"}}}}}},{"name":"create_record","description":"Create a record in a collection and publish it immediately. `data` must match the collection schema (inspect it with describe_resource). Returns the resource_key to use with get_record/update_record. Requires the CREATE permission on this API.","inputSchema":{"type":"object","required":["collection","data"],"properties":{"collection":{"type":"string","description":"Collection resource_id from discover_resources."},"data":{"type":"object","description":"Document body matching the collection schema."},"key":{"type":"string","description":"Optional external identifier for deduplication; creating a second record with the same key returns a conflict."},"parents":{"type":"object","description":"Parent placeholders for nested collection resource_ids."}},"additionalProperties":false},"annotations":{"title":"Create record","readOnlyHint":false,"destructiveHint":false,"idempotentHint":false,"openWorldHint":false}},{"name":"update_record","description":"Replace a record's document with a new published revision (full-document replace, not a partial patch). `data` must match the collection schema (see describe_resource). Requires the UPDATE permission on this API.","inputSchema":{"type":"object","required":["collection","record_id","data"],"properties":{"collection":{"type":"string","description":"Collection resource_id from discover_resources."},"record_id":{"type":"string","description":"resource_key of the record to replace."},"data":{"type":"object","description":"Full replacement document matching the schema."},"parents":{"type":"object","description":"Parent placeholders for nested collection resource_ids."},"expected_revision":{"type":"string","maxLength":64,"pattern":"^[A-Za-z0-9]{1,64}$","description":"Optional. Apply the write only if the record's current revision is still this one -- pass the `_sys.revision` value from the record you read. Prevents silently overwriting another writer's update; returns precondition_failed if the record moved on."}},"additionalProperties":false},"annotations":{"title":"Update record","readOnlyHint":false,"destructiveHint":true,"idempotentHint":false,"openWorldHint":false}}],"notes":["This endpoint resolves the environment from the credential, so the URL carries no environment key and no API prefix. If the key can read exactly one API, that API is selected automatically; otherwise send the API prefix in the x-foxnose-prefix header. A request with no credential returns 401.","The write tools (create_record, update_record) are advertised here but are not offered to every caller: they appear in tools/list only when the deployment has the internal write path configured and the presented key grants create/update on the selected API. The read tools are always present."]}