@cavuno/board 4.64.0 → 4.65.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +26 -0
- package/dist/{_spec-D1Qg_Twd.d.mts → _spec-CIufstif.d.mts} +57 -1
- package/dist/{_spec-D1Qg_Twd.d.ts → _spec-CIufstif.d.ts} +57 -1
- package/dist/{blog-Dp2jAVVM.d.ts → blog-DuNJLfIh.d.ts} +1 -1
- package/dist/{blog-e0YbZBjg.d.mts → blog-teDJ9KaO.d.mts} +1 -1
- package/dist/{board-Cc3wbTKI.d.mts → board-DZNCHGgl.d.mts} +2 -2
- package/dist/{board-BeNZLOYC.d.ts → board-iCHYuNhr.d.ts} +2 -2
- package/dist/{breadcrumbs-C5WjB8jm.d.mts → breadcrumbs-Bt3oNIAx.d.mts} +2 -2
- package/dist/{breadcrumbs-BOFeIYEX.d.ts → breadcrumbs-DYGo0yB9.d.ts} +2 -2
- package/dist/browser/cavuno-board.global.min.js +1 -1
- package/dist/chunk-RDHHRDKN.mjs +6 -0
- package/dist/doctor.js +1 -1
- package/dist/filters.d.mts +2 -2
- package/dist/filters.d.ts +2 -2
- package/dist/format.d.mts +4 -4
- package/dist/format.d.ts +4 -4
- package/dist/index.d.mts +15 -14
- package/dist/index.d.ts +15 -14
- package/dist/index.js +1 -1
- package/dist/index.mjs +4 -4
- package/dist/{jobs-CClqlUld.d.mts → jobs-CbXbDBij.d.mts} +1 -1
- package/dist/{jobs-D04ngjCP.d.ts → jobs-DM_QMccX.d.ts} +1 -1
- package/dist/mcp.d.mts +52 -0
- package/dist/mcp.d.ts +52 -0
- package/dist/mcp.js +150 -0
- package/dist/mcp.mjs +128 -0
- package/dist/{search-DZeNdYN6.d.ts → search-B7x4qt9A.d.ts} +1 -1
- package/dist/{search-BtrwcLkc.d.mts → search-PrqcJMft.d.mts} +1 -1
- package/dist/{seo-DLoyZRTo.d.ts → seo-C1Br7ygq.d.ts} +1 -1
- package/dist/seo-files.d.mts +2 -2
- package/dist/seo-files.d.ts +2 -2
- package/dist/{seo-LfpgbdnO.d.mts → seo-ohehUCzN.d.mts} +1 -1
- package/dist/seo.d.mts +22 -7
- package/dist/seo.d.ts +22 -7
- package/dist/seo.js +12 -3
- package/dist/seo.mjs +12 -3
- package/dist/server.d.mts +6 -6
- package/dist/server.d.ts +6 -6
- package/dist/sitemap.d.mts +6 -6
- package/dist/sitemap.d.ts +6 -6
- package/dist/skills.js +1 -1
- package/dist/suggest.d.mts +2 -2
- package/dist/suggest.d.ts +2 -2
- package/package.json +13 -2
- package/skills/cavuno-board-mcp/SKILL.md +80 -0
- package/skills/cavuno-board-seo/BLOG.md +13 -8
- package/skills/manifest.json +8 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import { S as Schemas, c as components } from './_spec-
|
|
2
|
-
import { F as FetchOptions, S as StorageMode, B as BoardRequest, A as Awaitable, L as Logger, a as BoardClient, b as SearchSuggestQuery } from './search-
|
|
3
|
-
export { c as ACCESS_TOKEN_KEY, d as BOARD_ACCESS_GRANT_KEY, C as CompanySuggestion, e as CustomStorage, M as MarketSuggestion, R as REFRESH_TOKEN_KEY, f as SearchSuggestType, g as SuggestResult, h as SuggestionItem, T as TermSuggestion } from './search-
|
|
4
|
-
import { L as ListEnvelope, R as RemoteOption, E as EmploymentType, S as Seniority, a as JobCapability, b as CustomFieldFilter, O as ObjectReferenceFilter, c as JobCollectionChoiceQuery, d as JobsListQuery, e as JobCardListEnvelope, f as JobRetrieveQuery, g as JobsSearchBody, h as JobCardSearchEnvelope, i as JobsSimilarQuery, j as SearchEnvelope } from './jobs-
|
|
5
|
-
export { k as CustomFieldValue, C as CustomFieldValues, l as CustomFilterValue, m as EducationRequirement, n as JobCatalogPagination, o as JobCollectionChoiceList, p as JobCollectionFieldDefinition, q as JobCompany, r as JobCustomEmploymentType, J as JobSort, s as OfficeLocation, t as OffsetPagination, P as PublicJob, u as PublicJobCard, v as RelatedSearch, w as RemotePermit, x as RemoteTimezone, y as ResolvedJobCollectionField, z as StorefrontPagination } from './jobs-
|
|
6
|
-
import { c as CompaniesListQuery, d as CompanyListEnvelope, e as CompaniesSearchBody, f as CompanyJobsListQuery, g as CompanySimilarQuery, h as CompanyMarketsListQuery, i as SalaryDetailQuery, b as CompanySalary } from './board-
|
|
7
|
-
export { B as BoardCustomEmploymentType, j as BoardFormBuiltinField, k as BoardFormField, l as BoardFormLayout, m as BoardFormLockReason, n as BoardJobFormField, o as BoardProfileFormField, p as BoardRoleSignIn, q as BoardSignInSsoConnection, a as CompanyCategorySalary, r as CompanyMarket, s as CompanyMarketRef, t as CompanySalarySummary, C as CustomFieldDefinition, u as CustomFieldOption, v as CustomFieldType, L as LocationSalaryDetail, w as LocationSkillsIndex, x as LocationTitlesIndex, P as PublicBoard, y as PublicBoardAds, z as PublicBoardAnalytics, A as PublicBoardFeatures, D as PublicBoardJobForm, E as PublicBoardSignIn, F as PublicCompany, G as PublicCompanyDetail, H as PublicCompanyMembership, I as SalaryCompany, J as SalaryLocation, K as SalarySkill, M as SalaryTitle, N as SkillLocationSalary, O as SkillLocationsIndex, S as SkillSalaryDetail, Q as TitleLocationSalary, R as TitleLocationsIndex, T as TitleSalaryDetail } from './board-
|
|
8
|
-
export { B as BoardSeo } from './seo-
|
|
9
|
-
import { a as BlogPostsListQuery, b as BlogSimilarQuery, c as BlogSearchBody } from './blog-
|
|
10
|
-
export { B as BlogAuthorEmbed, d as BlogTagEmbed, e as PublicBlogAdjacentPosts, f as PublicBlogAuthor, g as PublicBlogPost, P as PublicBlogPostSummary, h as PublicBlogTag } from './blog-
|
|
1
|
+
import { S as Schemas, c as components } from './_spec-CIufstif.js';
|
|
2
|
+
import { F as FetchOptions, S as StorageMode, B as BoardRequest, A as Awaitable, L as Logger, a as BoardClient, b as SearchSuggestQuery } from './search-B7x4qt9A.js';
|
|
3
|
+
export { c as ACCESS_TOKEN_KEY, d as BOARD_ACCESS_GRANT_KEY, C as CompanySuggestion, e as CustomStorage, M as MarketSuggestion, R as REFRESH_TOKEN_KEY, f as SearchSuggestType, g as SuggestResult, h as SuggestionItem, T as TermSuggestion } from './search-B7x4qt9A.js';
|
|
4
|
+
import { L as ListEnvelope, R as RemoteOption, E as EmploymentType, S as Seniority, a as JobCapability, b as CustomFieldFilter, O as ObjectReferenceFilter, c as JobCollectionChoiceQuery, d as JobsListQuery, e as JobCardListEnvelope, f as JobRetrieveQuery, g as JobsSearchBody, h as JobCardSearchEnvelope, i as JobsSimilarQuery, j as SearchEnvelope } from './jobs-DM_QMccX.js';
|
|
5
|
+
export { k as CustomFieldValue, C as CustomFieldValues, l as CustomFilterValue, m as EducationRequirement, n as JobCatalogPagination, o as JobCollectionChoiceList, p as JobCollectionFieldDefinition, q as JobCompany, r as JobCustomEmploymentType, J as JobSort, s as OfficeLocation, t as OffsetPagination, P as PublicJob, u as PublicJobCard, v as RelatedSearch, w as RemotePermit, x as RemoteTimezone, y as ResolvedJobCollectionField, z as StorefrontPagination } from './jobs-DM_QMccX.js';
|
|
6
|
+
import { c as CompaniesListQuery, d as CompanyListEnvelope, e as CompaniesSearchBody, f as CompanyJobsListQuery, g as CompanySimilarQuery, h as CompanyMarketsListQuery, i as SalaryDetailQuery, b as CompanySalary } from './board-iCHYuNhr.js';
|
|
7
|
+
export { B as BoardCustomEmploymentType, j as BoardFormBuiltinField, k as BoardFormField, l as BoardFormLayout, m as BoardFormLockReason, n as BoardJobFormField, o as BoardProfileFormField, p as BoardRoleSignIn, q as BoardSignInSsoConnection, a as CompanyCategorySalary, r as CompanyMarket, s as CompanyMarketRef, t as CompanySalarySummary, C as CustomFieldDefinition, u as CustomFieldOption, v as CustomFieldType, L as LocationSalaryDetail, w as LocationSkillsIndex, x as LocationTitlesIndex, P as PublicBoard, y as PublicBoardAds, z as PublicBoardAnalytics, A as PublicBoardFeatures, D as PublicBoardJobForm, E as PublicBoardSignIn, F as PublicCompany, G as PublicCompanyDetail, H as PublicCompanyMembership, I as SalaryCompany, J as SalaryLocation, K as SalarySkill, M as SalaryTitle, N as SkillLocationSalary, O as SkillLocationsIndex, S as SkillSalaryDetail, Q as TitleLocationSalary, R as TitleLocationsIndex, T as TitleSalaryDetail } from './board-iCHYuNhr.js';
|
|
8
|
+
export { B as BoardSeo } from './seo-C1Br7ygq.js';
|
|
9
|
+
import { a as BlogPostsListQuery, b as BlogSimilarQuery, c as BlogSearchBody } from './blog-DuNJLfIh.js';
|
|
10
|
+
export { B as BlogAuthorEmbed, d as BlogTagEmbed, e as PublicBlogAdjacentPosts, f as PublicBlogAuthor, g as PublicBlogPost, P as PublicBlogPostSummary, h as PublicBlogTag } from './blog-DuNJLfIh.js';
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* Well-known `<domain>_<snake_reason>` codes the Board API sends, grouped by
|
|
@@ -202,7 +202,7 @@ declare function paginate<Q extends Record<string, unknown>, P extends PageShape
|
|
|
202
202
|
* constant because the package is platform-neutral and cannot read
|
|
203
203
|
* package.json at runtime.
|
|
204
204
|
*/
|
|
205
|
-
declare const SDK_VERSION = "4.
|
|
205
|
+
declare const SDK_VERSION = "4.65.0";
|
|
206
206
|
|
|
207
207
|
type SavedJob = Schemas['SavedJob'];
|
|
208
208
|
type SavedJobsListQuery = {
|
|
@@ -1613,6 +1613,7 @@ declare function createBoardClient(options: CreateBoardClientOptions): {
|
|
|
1613
1613
|
messaging: boolean;
|
|
1614
1614
|
contactPage: boolean;
|
|
1615
1615
|
placeSuggestions: boolean;
|
|
1616
|
+
mcp: boolean;
|
|
1616
1617
|
};
|
|
1617
1618
|
talentAccessModel: "paid_messaging" | "paid_unlocks_and_messaging" | null;
|
|
1618
1619
|
posting: {
|
|
@@ -3795,7 +3796,7 @@ declare function createBoardClient(options: CreateBoardClientOptions): {
|
|
|
3795
3796
|
object: "marketing_consent";
|
|
3796
3797
|
status: "granted" | "withdrawn";
|
|
3797
3798
|
source: string;
|
|
3798
|
-
reason: "person_request" | "operator_request" | "account_deleted" | null;
|
|
3799
|
+
reason: "person_request" | "operator_request" | "account_deleted" | "spam_report" | null;
|
|
3799
3800
|
grantedAt: string | null;
|
|
3800
3801
|
withdrawnAt: string | null;
|
|
3801
3802
|
revision: number;
|
|
@@ -3805,7 +3806,7 @@ declare function createBoardClient(options: CreateBoardClientOptions): {
|
|
|
3805
3806
|
object: "marketing_consent";
|
|
3806
3807
|
status: "granted" | "withdrawn";
|
|
3807
3808
|
source: string;
|
|
3808
|
-
reason: "person_request" | "operator_request" | "account_deleted" | null;
|
|
3809
|
+
reason: "person_request" | "operator_request" | "account_deleted" | "spam_report" | null;
|
|
3809
3810
|
grantedAt: string | null;
|
|
3810
3811
|
withdrawnAt: string | null;
|
|
3811
3812
|
revision: number;
|
|
@@ -3815,7 +3816,7 @@ declare function createBoardClient(options: CreateBoardClientOptions): {
|
|
|
3815
3816
|
object: "marketing_consent";
|
|
3816
3817
|
status: "granted" | "withdrawn";
|
|
3817
3818
|
source: string;
|
|
3818
|
-
reason: "person_request" | "operator_request" | "account_deleted" | null;
|
|
3819
|
+
reason: "person_request" | "operator_request" | "account_deleted" | "spam_report" | null;
|
|
3819
3820
|
grantedAt: string | null;
|
|
3820
3821
|
withdrawnAt: string | null;
|
|
3821
3822
|
revision: number;
|
package/dist/index.js
CHANGED
package/dist/index.mjs
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DEFAULT_CAVUNO_API_URL
|
|
3
|
+
} from "./chunk-RDHHRDKN.mjs";
|
|
1
4
|
import {
|
|
2
5
|
ACCESS_TOKEN_KEY,
|
|
3
6
|
BOARD_ACCESS_GRANT_KEY,
|
|
@@ -46,7 +49,7 @@ function toSearchParams(query) {
|
|
|
46
49
|
}
|
|
47
50
|
|
|
48
51
|
// src/version.ts
|
|
49
|
-
var SDK_VERSION = "4.
|
|
52
|
+
var SDK_VERSION = "4.65.0";
|
|
50
53
|
|
|
51
54
|
// src/client.ts
|
|
52
55
|
function isRawBody(body) {
|
|
@@ -150,9 +153,6 @@ var BoardClient = class {
|
|
|
150
153
|
}
|
|
151
154
|
};
|
|
152
155
|
|
|
153
|
-
// src/constants.ts
|
|
154
|
-
var DEFAULT_CAVUNO_API_URL = "https://api.cavuno.com";
|
|
155
|
-
|
|
156
156
|
// src/sso.ts
|
|
157
157
|
var SIGN_IN_REDIRECT_ERROR_CODES = [
|
|
158
158
|
"oauth_cancelled",
|
package/dist/mcp.d.mts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The board's public MCP server on a self-hosted board's own host.
|
|
3
|
+
*
|
|
4
|
+
* Mount the handler at `/mcp`, `/mcp/*` and `/.well-known/ai-catalog.json`.
|
|
5
|
+
* It forwards to the Board API's `POST /v1/boards/{board}/mcp` and its
|
|
6
|
+
* `GET /v1/boards/{board}/mcp/docs`, `/mcp/server-card` and `/mcp/ai-catalog`,
|
|
7
|
+
* the same endpoints Cavuno-hosted boards answer from, so the tools, limits,
|
|
8
|
+
* setup page and discovery documents stay identical everywhere:
|
|
9
|
+
*
|
|
10
|
+
* - `POST /mcp` is the stateless Streamable HTTP transport.
|
|
11
|
+
* - A browser opening `/mcp` is sent to `/mcp/docs`; any other method is 405.
|
|
12
|
+
* - `GET|HEAD /mcp/docs` is the HTML setup page listed in AI directories.
|
|
13
|
+
* - `GET|HEAD /mcp/server-card` is the MCP Server Card: the server's name,
|
|
14
|
+
* version and URL, for an assistant to read before it connects.
|
|
15
|
+
* - `GET|HEAD /.well-known/ai-catalog.json` is the AI Catalog listing that
|
|
16
|
+
* card, so an assistant can find the server from the board's domain alone.
|
|
17
|
+
*
|
|
18
|
+
* Only the MCP transport headers are forwarded: no cookies and no
|
|
19
|
+
* authorization, because every tool is an anonymous read. The Board API's
|
|
20
|
+
* per-visitor limit sees this server's address, so it applies to all of the
|
|
21
|
+
* board's MCP traffic together.
|
|
22
|
+
*
|
|
23
|
+
* Framework-portable: standard Request in, standard Response out.
|
|
24
|
+
*/
|
|
25
|
+
interface CreateMcpHandlerConfig {
|
|
26
|
+
/**
|
|
27
|
+
* The board's publishable key (`pk_…`). Job and company links in tool
|
|
28
|
+
* results then point at the key's own origin, this board.
|
|
29
|
+
*/
|
|
30
|
+
board: string;
|
|
31
|
+
/** Board API origin. Defaults to `https://api.cavuno.com`. */
|
|
32
|
+
baseUrl?: string;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Create the `(request) => Response` handler for `/mcp`, `/mcp/docs`,
|
|
36
|
+
* `/mcp/server-card` and `/.well-known/ai-catalog.json`.
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* // Next.js App Router: app/mcp/[[...path]]/route.ts and
|
|
40
|
+
* // app/.well-known/ai-catalog.json/route.ts, both exporting:
|
|
41
|
+
* import { createMcpHandler } from '@cavuno/board/mcp';
|
|
42
|
+
*
|
|
43
|
+
* const handler = createMcpHandler({
|
|
44
|
+
* board: process.env.NEXT_PUBLIC_CAVUNO_BOARD_KEY!,
|
|
45
|
+
* });
|
|
46
|
+
* export const GET = handler;
|
|
47
|
+
* export const HEAD = handler;
|
|
48
|
+
* export const POST = handler;
|
|
49
|
+
*/
|
|
50
|
+
declare function createMcpHandler(config: CreateMcpHandlerConfig): (request: Request) => Promise<Response>;
|
|
51
|
+
|
|
52
|
+
export { type CreateMcpHandlerConfig, createMcpHandler };
|
package/dist/mcp.d.ts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The board's public MCP server on a self-hosted board's own host.
|
|
3
|
+
*
|
|
4
|
+
* Mount the handler at `/mcp`, `/mcp/*` and `/.well-known/ai-catalog.json`.
|
|
5
|
+
* It forwards to the Board API's `POST /v1/boards/{board}/mcp` and its
|
|
6
|
+
* `GET /v1/boards/{board}/mcp/docs`, `/mcp/server-card` and `/mcp/ai-catalog`,
|
|
7
|
+
* the same endpoints Cavuno-hosted boards answer from, so the tools, limits,
|
|
8
|
+
* setup page and discovery documents stay identical everywhere:
|
|
9
|
+
*
|
|
10
|
+
* - `POST /mcp` is the stateless Streamable HTTP transport.
|
|
11
|
+
* - A browser opening `/mcp` is sent to `/mcp/docs`; any other method is 405.
|
|
12
|
+
* - `GET|HEAD /mcp/docs` is the HTML setup page listed in AI directories.
|
|
13
|
+
* - `GET|HEAD /mcp/server-card` is the MCP Server Card: the server's name,
|
|
14
|
+
* version and URL, for an assistant to read before it connects.
|
|
15
|
+
* - `GET|HEAD /.well-known/ai-catalog.json` is the AI Catalog listing that
|
|
16
|
+
* card, so an assistant can find the server from the board's domain alone.
|
|
17
|
+
*
|
|
18
|
+
* Only the MCP transport headers are forwarded: no cookies and no
|
|
19
|
+
* authorization, because every tool is an anonymous read. The Board API's
|
|
20
|
+
* per-visitor limit sees this server's address, so it applies to all of the
|
|
21
|
+
* board's MCP traffic together.
|
|
22
|
+
*
|
|
23
|
+
* Framework-portable: standard Request in, standard Response out.
|
|
24
|
+
*/
|
|
25
|
+
interface CreateMcpHandlerConfig {
|
|
26
|
+
/**
|
|
27
|
+
* The board's publishable key (`pk_…`). Job and company links in tool
|
|
28
|
+
* results then point at the key's own origin, this board.
|
|
29
|
+
*/
|
|
30
|
+
board: string;
|
|
31
|
+
/** Board API origin. Defaults to `https://api.cavuno.com`. */
|
|
32
|
+
baseUrl?: string;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Create the `(request) => Response` handler for `/mcp`, `/mcp/docs`,
|
|
36
|
+
* `/mcp/server-card` and `/.well-known/ai-catalog.json`.
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* // Next.js App Router: app/mcp/[[...path]]/route.ts and
|
|
40
|
+
* // app/.well-known/ai-catalog.json/route.ts, both exporting:
|
|
41
|
+
* import { createMcpHandler } from '@cavuno/board/mcp';
|
|
42
|
+
*
|
|
43
|
+
* const handler = createMcpHandler({
|
|
44
|
+
* board: process.env.NEXT_PUBLIC_CAVUNO_BOARD_KEY!,
|
|
45
|
+
* });
|
|
46
|
+
* export const GET = handler;
|
|
47
|
+
* export const HEAD = handler;
|
|
48
|
+
* export const POST = handler;
|
|
49
|
+
*/
|
|
50
|
+
declare function createMcpHandler(config: CreateMcpHandlerConfig): (request: Request) => Promise<Response>;
|
|
51
|
+
|
|
52
|
+
export { type CreateMcpHandlerConfig, createMcpHandler };
|
package/dist/mcp.js
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/mcp/index.ts
|
|
21
|
+
var mcp_exports = {};
|
|
22
|
+
__export(mcp_exports, {
|
|
23
|
+
createMcpHandler: () => createMcpHandler
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(mcp_exports);
|
|
26
|
+
|
|
27
|
+
// src/constants.ts
|
|
28
|
+
var DEFAULT_CAVUNO_API_URL = "https://api.cavuno.com";
|
|
29
|
+
|
|
30
|
+
// src/mcp/create-mcp-handler.ts
|
|
31
|
+
var DOCUMENT_UPSTREAMS = [
|
|
32
|
+
["/mcp/docs", "mcp/docs"],
|
|
33
|
+
["/mcp/server-card", "mcp/server-card"],
|
|
34
|
+
["/.well-known/ai-catalog.json", "mcp/ai-catalog"]
|
|
35
|
+
];
|
|
36
|
+
var MAX_BODY_BYTES = 64 * 1024;
|
|
37
|
+
var UPSTREAM_TIMEOUT_MS = 3e4;
|
|
38
|
+
var FORWARDED_HEADERS = [
|
|
39
|
+
"content-type",
|
|
40
|
+
"accept",
|
|
41
|
+
"mcp-protocol-version",
|
|
42
|
+
"user-agent"
|
|
43
|
+
];
|
|
44
|
+
function jsonRpcError(status, message, headers = {}) {
|
|
45
|
+
return new Response(
|
|
46
|
+
JSON.stringify({
|
|
47
|
+
jsonrpc: "2.0",
|
|
48
|
+
id: null,
|
|
49
|
+
error: { code: -32e3, message }
|
|
50
|
+
}),
|
|
51
|
+
{
|
|
52
|
+
status,
|
|
53
|
+
headers: { "content-type": "application/json", ...headers }
|
|
54
|
+
}
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
async function readCappedBody(request) {
|
|
58
|
+
if (Number(request.headers.get("content-length")) > MAX_BODY_BYTES) {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
if (!request.body) return new Uint8Array();
|
|
62
|
+
const reader = request.body.getReader();
|
|
63
|
+
const chunks = [];
|
|
64
|
+
let size = 0;
|
|
65
|
+
for (; ; ) {
|
|
66
|
+
const { done, value } = await reader.read();
|
|
67
|
+
if (done) break;
|
|
68
|
+
size += value.byteLength;
|
|
69
|
+
if (size > MAX_BODY_BYTES) {
|
|
70
|
+
await reader.cancel();
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
chunks.push(value);
|
|
74
|
+
}
|
|
75
|
+
const body = new Uint8Array(size);
|
|
76
|
+
let offset = 0;
|
|
77
|
+
for (const chunk of chunks) {
|
|
78
|
+
body.set(chunk, offset);
|
|
79
|
+
offset += chunk.byteLength;
|
|
80
|
+
}
|
|
81
|
+
return body;
|
|
82
|
+
}
|
|
83
|
+
function createMcpHandler(config) {
|
|
84
|
+
const base = `${(config.baseUrl ?? DEFAULT_CAVUNO_API_URL).replace(/\/+$/, "")}/v1/boards/${encodeURIComponent(config.board)}`;
|
|
85
|
+
return async function mcpHandler(request) {
|
|
86
|
+
const { pathname } = new URL(request.url);
|
|
87
|
+
const path = pathname.replace(/\/+$/, "");
|
|
88
|
+
const method = request.method;
|
|
89
|
+
let upstream;
|
|
90
|
+
const document = DOCUMENT_UPSTREAMS.find(
|
|
91
|
+
([suffix]) => path.endsWith(suffix)
|
|
92
|
+
);
|
|
93
|
+
if (document) {
|
|
94
|
+
if (method !== "GET" && method !== "HEAD") {
|
|
95
|
+
return new Response(null, {
|
|
96
|
+
status: 405,
|
|
97
|
+
headers: { allow: "GET, HEAD" }
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
upstream = `${base}/${document[1]}`;
|
|
101
|
+
} else if (!path.endsWith("/mcp")) {
|
|
102
|
+
return new Response(null, { status: 404 });
|
|
103
|
+
} else {
|
|
104
|
+
const accept = request.headers.get("accept") ?? "";
|
|
105
|
+
if (method === "GET" && accept.includes("text/html") && !accept.includes("text/event-stream")) {
|
|
106
|
+
return new Response(null, {
|
|
107
|
+
status: 302,
|
|
108
|
+
headers: { location: `${path}/docs`, "cache-control": "no-store" }
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
if (method !== "POST") {
|
|
112
|
+
return jsonRpcError(405, "Method not allowed.", { allow: "POST" });
|
|
113
|
+
}
|
|
114
|
+
upstream = `${base}/mcp`;
|
|
115
|
+
}
|
|
116
|
+
let body;
|
|
117
|
+
if (method === "POST") {
|
|
118
|
+
const read = await readCappedBody(request);
|
|
119
|
+
if (!read) return jsonRpcError(413, "Request body too large.");
|
|
120
|
+
body = read;
|
|
121
|
+
}
|
|
122
|
+
const headers = new Headers();
|
|
123
|
+
for (const name of FORWARDED_HEADERS) {
|
|
124
|
+
const value = request.headers.get(name);
|
|
125
|
+
if (value) headers.set(name, value);
|
|
126
|
+
}
|
|
127
|
+
let response;
|
|
128
|
+
try {
|
|
129
|
+
response = await fetch(upstream, {
|
|
130
|
+
method,
|
|
131
|
+
headers,
|
|
132
|
+
body,
|
|
133
|
+
redirect: "manual",
|
|
134
|
+
signal: AbortSignal.timeout(UPSTREAM_TIMEOUT_MS)
|
|
135
|
+
});
|
|
136
|
+
} catch {
|
|
137
|
+
return jsonRpcError(503, "The board MCP server is unavailable.", {
|
|
138
|
+
"retry-after": "30"
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
const outHeaders = new Headers(response.headers);
|
|
142
|
+
outHeaders.delete("set-cookie");
|
|
143
|
+
outHeaders.delete("content-encoding");
|
|
144
|
+
outHeaders.delete("content-length");
|
|
145
|
+
return new Response(response.body, {
|
|
146
|
+
status: response.status,
|
|
147
|
+
headers: outHeaders
|
|
148
|
+
});
|
|
149
|
+
};
|
|
150
|
+
}
|
package/dist/mcp.mjs
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DEFAULT_CAVUNO_API_URL
|
|
3
|
+
} from "./chunk-RDHHRDKN.mjs";
|
|
4
|
+
|
|
5
|
+
// src/mcp/create-mcp-handler.ts
|
|
6
|
+
var DOCUMENT_UPSTREAMS = [
|
|
7
|
+
["/mcp/docs", "mcp/docs"],
|
|
8
|
+
["/mcp/server-card", "mcp/server-card"],
|
|
9
|
+
["/.well-known/ai-catalog.json", "mcp/ai-catalog"]
|
|
10
|
+
];
|
|
11
|
+
var MAX_BODY_BYTES = 64 * 1024;
|
|
12
|
+
var UPSTREAM_TIMEOUT_MS = 3e4;
|
|
13
|
+
var FORWARDED_HEADERS = [
|
|
14
|
+
"content-type",
|
|
15
|
+
"accept",
|
|
16
|
+
"mcp-protocol-version",
|
|
17
|
+
"user-agent"
|
|
18
|
+
];
|
|
19
|
+
function jsonRpcError(status, message, headers = {}) {
|
|
20
|
+
return new Response(
|
|
21
|
+
JSON.stringify({
|
|
22
|
+
jsonrpc: "2.0",
|
|
23
|
+
id: null,
|
|
24
|
+
error: { code: -32e3, message }
|
|
25
|
+
}),
|
|
26
|
+
{
|
|
27
|
+
status,
|
|
28
|
+
headers: { "content-type": "application/json", ...headers }
|
|
29
|
+
}
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
async function readCappedBody(request) {
|
|
33
|
+
if (Number(request.headers.get("content-length")) > MAX_BODY_BYTES) {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
if (!request.body) return new Uint8Array();
|
|
37
|
+
const reader = request.body.getReader();
|
|
38
|
+
const chunks = [];
|
|
39
|
+
let size = 0;
|
|
40
|
+
for (; ; ) {
|
|
41
|
+
const { done, value } = await reader.read();
|
|
42
|
+
if (done) break;
|
|
43
|
+
size += value.byteLength;
|
|
44
|
+
if (size > MAX_BODY_BYTES) {
|
|
45
|
+
await reader.cancel();
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
chunks.push(value);
|
|
49
|
+
}
|
|
50
|
+
const body = new Uint8Array(size);
|
|
51
|
+
let offset = 0;
|
|
52
|
+
for (const chunk of chunks) {
|
|
53
|
+
body.set(chunk, offset);
|
|
54
|
+
offset += chunk.byteLength;
|
|
55
|
+
}
|
|
56
|
+
return body;
|
|
57
|
+
}
|
|
58
|
+
function createMcpHandler(config) {
|
|
59
|
+
const base = `${(config.baseUrl ?? DEFAULT_CAVUNO_API_URL).replace(/\/+$/, "")}/v1/boards/${encodeURIComponent(config.board)}`;
|
|
60
|
+
return async function mcpHandler(request) {
|
|
61
|
+
const { pathname } = new URL(request.url);
|
|
62
|
+
const path = pathname.replace(/\/+$/, "");
|
|
63
|
+
const method = request.method;
|
|
64
|
+
let upstream;
|
|
65
|
+
const document = DOCUMENT_UPSTREAMS.find(
|
|
66
|
+
([suffix]) => path.endsWith(suffix)
|
|
67
|
+
);
|
|
68
|
+
if (document) {
|
|
69
|
+
if (method !== "GET" && method !== "HEAD") {
|
|
70
|
+
return new Response(null, {
|
|
71
|
+
status: 405,
|
|
72
|
+
headers: { allow: "GET, HEAD" }
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
upstream = `${base}/${document[1]}`;
|
|
76
|
+
} else if (!path.endsWith("/mcp")) {
|
|
77
|
+
return new Response(null, { status: 404 });
|
|
78
|
+
} else {
|
|
79
|
+
const accept = request.headers.get("accept") ?? "";
|
|
80
|
+
if (method === "GET" && accept.includes("text/html") && !accept.includes("text/event-stream")) {
|
|
81
|
+
return new Response(null, {
|
|
82
|
+
status: 302,
|
|
83
|
+
headers: { location: `${path}/docs`, "cache-control": "no-store" }
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
if (method !== "POST") {
|
|
87
|
+
return jsonRpcError(405, "Method not allowed.", { allow: "POST" });
|
|
88
|
+
}
|
|
89
|
+
upstream = `${base}/mcp`;
|
|
90
|
+
}
|
|
91
|
+
let body;
|
|
92
|
+
if (method === "POST") {
|
|
93
|
+
const read = await readCappedBody(request);
|
|
94
|
+
if (!read) return jsonRpcError(413, "Request body too large.");
|
|
95
|
+
body = read;
|
|
96
|
+
}
|
|
97
|
+
const headers = new Headers();
|
|
98
|
+
for (const name of FORWARDED_HEADERS) {
|
|
99
|
+
const value = request.headers.get(name);
|
|
100
|
+
if (value) headers.set(name, value);
|
|
101
|
+
}
|
|
102
|
+
let response;
|
|
103
|
+
try {
|
|
104
|
+
response = await fetch(upstream, {
|
|
105
|
+
method,
|
|
106
|
+
headers,
|
|
107
|
+
body,
|
|
108
|
+
redirect: "manual",
|
|
109
|
+
signal: AbortSignal.timeout(UPSTREAM_TIMEOUT_MS)
|
|
110
|
+
});
|
|
111
|
+
} catch {
|
|
112
|
+
return jsonRpcError(503, "The board MCP server is unavailable.", {
|
|
113
|
+
"retry-after": "30"
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
const outHeaders = new Headers(response.headers);
|
|
117
|
+
outHeaders.delete("set-cookie");
|
|
118
|
+
outHeaders.delete("content-encoding");
|
|
119
|
+
outHeaders.delete("content-length");
|
|
120
|
+
return new Response(response.body, {
|
|
121
|
+
status: response.status,
|
|
122
|
+
headers: outHeaders
|
|
123
|
+
});
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
export {
|
|
127
|
+
createMcpHandler
|
|
128
|
+
};
|
package/dist/seo-files.d.mts
CHANGED
package/dist/seo-files.d.ts
CHANGED
package/dist/seo.d.mts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { P as PublicBlogPostSummary, B as BlogAuthorEmbed } from './blog-
|
|
2
|
-
import { e as JsonLdObject } from './breadcrumbs-
|
|
3
|
-
export { A as ALL_COUNTRY_CODES, B as BreadcrumbItemInput, g as FaqItem, J as JobBreadcrumb, h as JsonLdBoard, N as NumberNotation, i as SENIORITY_ORDER, j as SalaryFaqEntry, k as SalaryJsonLdOptions, l as SalaryOccupationListOptions, b as buildJobBreadcrumbs, m as buildSalaryFaq, o as companyCategorySalaryJsonLd, p as companySalaryJsonLd, q as createBreadcrumbJsonLd, r as createJobPostingJsonLd, s as crossAxisSalaryJsonLd, t as faqJsonLd, c as formatSalaryStat, d as formatSalaryStatRange, u as itemListJsonLd, v as locationSalaryJsonLd, n as normalizeWebsiteUrl, w as skillSalaryJsonLd, x as sortBySeniority, y as titleSalaryJsonLd } from './breadcrumbs-
|
|
4
|
-
import './_spec-
|
|
5
|
-
import './board-
|
|
6
|
-
import './jobs-
|
|
1
|
+
import { P as PublicBlogPostSummary, B as BlogAuthorEmbed } from './blog-teDJ9KaO.mjs';
|
|
2
|
+
import { e as JsonLdObject } from './breadcrumbs-Bt3oNIAx.mjs';
|
|
3
|
+
export { A as ALL_COUNTRY_CODES, B as BreadcrumbItemInput, g as FaqItem, J as JobBreadcrumb, h as JsonLdBoard, N as NumberNotation, i as SENIORITY_ORDER, j as SalaryFaqEntry, k as SalaryJsonLdOptions, l as SalaryOccupationListOptions, b as buildJobBreadcrumbs, m as buildSalaryFaq, o as companyCategorySalaryJsonLd, p as companySalaryJsonLd, q as createBreadcrumbJsonLd, r as createJobPostingJsonLd, s as crossAxisSalaryJsonLd, t as faqJsonLd, c as formatSalaryStat, d as formatSalaryStatRange, u as itemListJsonLd, v as locationSalaryJsonLd, n as normalizeWebsiteUrl, w as skillSalaryJsonLd, x as sortBySeniority, y as titleSalaryJsonLd } from './breadcrumbs-Bt3oNIAx.mjs';
|
|
4
|
+
import './_spec-CIufstif.mjs';
|
|
5
|
+
import './board-DZNCHGgl.mjs';
|
|
6
|
+
import './jobs-CbXbDBij.mjs';
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Blog structured data on the `@cavuno/board` wire types, transcribed from
|
|
@@ -28,13 +28,24 @@ type ArticleJsonLdPost = Pick<PublicBlogPostSummary, 'title' | 'customExcerpt' |
|
|
|
28
28
|
* first, then `ogImageUrl` — pass your page's OG image URL (hosted passes
|
|
29
29
|
* its generated `/blog/:slug/og` card; OG generation itself is app-owned
|
|
30
30
|
* per ).
|
|
31
|
+
*
|
|
32
|
+
* Pass `authorProfileUrl` (the first author's profile page URL) so the
|
|
33
|
+
* `author` is the same `Person` (`@id` + `url`) that
|
|
34
|
+
* `createAuthorProfileJsonLd` declares on that page. Without it the author
|
|
35
|
+
* carries only a name and their website, and nothing links the post to the
|
|
36
|
+
* profile.
|
|
31
37
|
*/
|
|
32
|
-
declare function createBlogArticleJsonLd({ post, boardName, permalink, ogImageUrl, }: {
|
|
38
|
+
declare function createBlogArticleJsonLd({ post, boardName, permalink, ogImageUrl, authorProfileUrl, }: {
|
|
33
39
|
post: ArticleJsonLdPost;
|
|
34
40
|
boardName: string;
|
|
35
41
|
permalink: string;
|
|
36
42
|
/** Fallback image when the post has no cover, e.g. your generated OG card URL. */
|
|
37
43
|
ogImageUrl?: string | null;
|
|
44
|
+
/**
|
|
45
|
+
* Absolute canonical URL of the first author's profile page: the
|
|
46
|
+
* `canonical` you pass to `createAuthorProfileJsonLd` on that page.
|
|
47
|
+
*/
|
|
48
|
+
authorProfileUrl?: string | null;
|
|
38
49
|
}): JsonLdObject;
|
|
39
50
|
/**
|
|
40
51
|
* `ProfilePage` for a blog author page — the hosted shape: a `Person` main
|
|
@@ -55,6 +66,10 @@ declare function createAuthorProfileJsonLd({ author, canonical, description, ori
|
|
|
55
66
|
origin: string | null;
|
|
56
67
|
/** The author's recent posts (newest first) — the builder keeps the first 5. */
|
|
57
68
|
posts: Pick<PublicBlogPostSummary, 'title' | 'slug' | 'publishedAt'>[];
|
|
69
|
+
/**
|
|
70
|
+
* The author's total published post count: the posts list `count` for
|
|
71
|
+
* `authorSlug`, not the length of one page.
|
|
72
|
+
*/
|
|
58
73
|
totalPosts: number;
|
|
59
74
|
}): JsonLdObject | null;
|
|
60
75
|
|
package/dist/seo.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { P as PublicBlogPostSummary, B as BlogAuthorEmbed } from './blog-
|
|
2
|
-
import { e as JsonLdObject } from './breadcrumbs-
|
|
3
|
-
export { A as ALL_COUNTRY_CODES, B as BreadcrumbItemInput, g as FaqItem, J as JobBreadcrumb, h as JsonLdBoard, N as NumberNotation, i as SENIORITY_ORDER, j as SalaryFaqEntry, k as SalaryJsonLdOptions, l as SalaryOccupationListOptions, b as buildJobBreadcrumbs, m as buildSalaryFaq, o as companyCategorySalaryJsonLd, p as companySalaryJsonLd, q as createBreadcrumbJsonLd, r as createJobPostingJsonLd, s as crossAxisSalaryJsonLd, t as faqJsonLd, c as formatSalaryStat, d as formatSalaryStatRange, u as itemListJsonLd, v as locationSalaryJsonLd, n as normalizeWebsiteUrl, w as skillSalaryJsonLd, x as sortBySeniority, y as titleSalaryJsonLd } from './breadcrumbs-
|
|
4
|
-
import './_spec-
|
|
5
|
-
import './board-
|
|
6
|
-
import './jobs-
|
|
1
|
+
import { P as PublicBlogPostSummary, B as BlogAuthorEmbed } from './blog-DuNJLfIh.js';
|
|
2
|
+
import { e as JsonLdObject } from './breadcrumbs-DYGo0yB9.js';
|
|
3
|
+
export { A as ALL_COUNTRY_CODES, B as BreadcrumbItemInput, g as FaqItem, J as JobBreadcrumb, h as JsonLdBoard, N as NumberNotation, i as SENIORITY_ORDER, j as SalaryFaqEntry, k as SalaryJsonLdOptions, l as SalaryOccupationListOptions, b as buildJobBreadcrumbs, m as buildSalaryFaq, o as companyCategorySalaryJsonLd, p as companySalaryJsonLd, q as createBreadcrumbJsonLd, r as createJobPostingJsonLd, s as crossAxisSalaryJsonLd, t as faqJsonLd, c as formatSalaryStat, d as formatSalaryStatRange, u as itemListJsonLd, v as locationSalaryJsonLd, n as normalizeWebsiteUrl, w as skillSalaryJsonLd, x as sortBySeniority, y as titleSalaryJsonLd } from './breadcrumbs-DYGo0yB9.js';
|
|
4
|
+
import './_spec-CIufstif.js';
|
|
5
|
+
import './board-iCHYuNhr.js';
|
|
6
|
+
import './jobs-DM_QMccX.js';
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Blog structured data on the `@cavuno/board` wire types, transcribed from
|
|
@@ -28,13 +28,24 @@ type ArticleJsonLdPost = Pick<PublicBlogPostSummary, 'title' | 'customExcerpt' |
|
|
|
28
28
|
* first, then `ogImageUrl` — pass your page's OG image URL (hosted passes
|
|
29
29
|
* its generated `/blog/:slug/og` card; OG generation itself is app-owned
|
|
30
30
|
* per ).
|
|
31
|
+
*
|
|
32
|
+
* Pass `authorProfileUrl` (the first author's profile page URL) so the
|
|
33
|
+
* `author` is the same `Person` (`@id` + `url`) that
|
|
34
|
+
* `createAuthorProfileJsonLd` declares on that page. Without it the author
|
|
35
|
+
* carries only a name and their website, and nothing links the post to the
|
|
36
|
+
* profile.
|
|
31
37
|
*/
|
|
32
|
-
declare function createBlogArticleJsonLd({ post, boardName, permalink, ogImageUrl, }: {
|
|
38
|
+
declare function createBlogArticleJsonLd({ post, boardName, permalink, ogImageUrl, authorProfileUrl, }: {
|
|
33
39
|
post: ArticleJsonLdPost;
|
|
34
40
|
boardName: string;
|
|
35
41
|
permalink: string;
|
|
36
42
|
/** Fallback image when the post has no cover, e.g. your generated OG card URL. */
|
|
37
43
|
ogImageUrl?: string | null;
|
|
44
|
+
/**
|
|
45
|
+
* Absolute canonical URL of the first author's profile page: the
|
|
46
|
+
* `canonical` you pass to `createAuthorProfileJsonLd` on that page.
|
|
47
|
+
*/
|
|
48
|
+
authorProfileUrl?: string | null;
|
|
38
49
|
}): JsonLdObject;
|
|
39
50
|
/**
|
|
40
51
|
* `ProfilePage` for a blog author page — the hosted shape: a `Person` main
|
|
@@ -55,6 +66,10 @@ declare function createAuthorProfileJsonLd({ author, canonical, description, ori
|
|
|
55
66
|
origin: string | null;
|
|
56
67
|
/** The author's recent posts (newest first) — the builder keeps the first 5. */
|
|
57
68
|
posts: Pick<PublicBlogPostSummary, 'title' | 'slug' | 'publishedAt'>[];
|
|
69
|
+
/**
|
|
70
|
+
* The author's total published post count: the posts list `count` for
|
|
71
|
+
* `authorSlug`, not the length of one page.
|
|
72
|
+
*/
|
|
58
73
|
totalPosts: number;
|
|
59
74
|
}): JsonLdObject | null;
|
|
60
75
|
|