mcp-scraper 0.44.2 → 0.44.3

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/README.md CHANGED
@@ -90,7 +90,7 @@ Build the branded one-click bundle:
90
90
  npm run build:mcpb
91
91
  ```
92
92
 
93
- The generated bundle is written to `build/mcpb/mcp-scraper-<version>.mcpb` and copied to `public/downloads/` for the hosted download. The current public bundle is `https://mcpscraper.dev/downloads/mcp-scraper.mcpb` (`0.44.2`, SHA-256 `6ce5675c82d1fa0b7c645e0337aaac97de7760282067e5744259e8d0a4f03fc2`). Install it by opening or dragging it into Claude Desktop. Claude displays the `MCP Scraper` install card, icon, and API-key configuration field from the bundle manifest.
93
+ The generated bundle is written to `build/mcpb/mcp-scraper-<version>.mcpb` and copied to `public/downloads/` for the hosted download. The current public bundle is `https://mcpscraper.dev/downloads/mcp-scraper.mcpb` (`0.44.3`, SHA-256 `421ad2771f6f4790e4122b88071d293659947dd37b06d7ed491a864f9484179e`). Install it by opening or dragging it into Claude Desktop. Claude displays the `MCP Scraper` install card, icon, and API-key configuration field from the bundle manifest.
94
94
 
95
95
  The MCPB install exposes every tool — web-intelligence plus all `browser_*` tools — through the one `mcp-scraper` server.
96
96
 
@@ -233,7 +233,7 @@ The `mcp-scraper` server (and the MCPB bundle, which runs it) exposes both secti
233
233
 
234
234
  All MCP tools expose output schemas and return `structuredContent` with the IDs, URLs, CSV paths, transcripts, browser session handles, replay paths, artifacts, recipe fields, or blueprint fields needed by the next step. Browser Agent tools keep a JSON text block for older clients, but structured data is the primary contract. All tools carry MCP annotations; file-writing tools such as replay downloads and annotations state their filesystem side effects.
235
235
 
236
- The canonical tool inventory is generated at `docs/mcp-tool-manifest.generated.json`. The unified server exposes 215 tools: 114 scraper, browser, workflow, billing, and connected-service tools plus 101 durable-memory tools. The scraper inventory includes governed Local Sourcebook tools that follow a Memory-style contract/tag/prepare/validate/capture sequence before paid acquisition, plus Transparent Commons tools for public entity search, planning, validation, governed contribution, ledgers, needs-link discovery, and saved filters. Local Sourcebook publication remains administrator-only. Release verification compares the exact local and hosted tool-name sets, not only the count.
236
+ The canonical tool inventory is generated at `docs/mcp-tool-manifest.generated.json`. The unified server exposes 215 tools: 114 scraper, browser, workflow, billing, and connected-service tools plus 101 durable-memory tools. The scraper inventory includes governed Local Sourcebook tools that follow a Memory-style contract/tag/prepare/validate/capture sequence before paid acquisition, plus Transparent Commons tools for public entity search, planning, validation, governed contribution, ledgers, needs-link discovery, and saved filters. Successful evidence-compiled Local Sourcebook revisions publish automatically to their canonical `localsourcebook.com` category profile and review URLs; administrator controls handle exceptional rejection or unpublishing. Release verification compares the exact local and hosted tool-name sets, not only the count.
237
237
 
238
238
  For contract parity, stdio and MCPB memory calls invoke the matching public tool on the hosted MCP Scraper `/mcp` endpoint. The hosted aggregate runtime owns MCP Scraper-specific billing, scheduling, credential, and in-process cutover policy; its internal `/memory/mcp-call` bridge is a fallback to the standalone memory service, not the public stdio execution path. Direct `mcp-memory` OAuth and stdio clients continue to use `memory.mcpscraper.dev` and must be verified as a separate dependent release surface.
239
239
 
@@ -24789,6 +24789,14 @@ async function listPublicLocalSourcebook(category, state) {
24789
24789
  const result = await getDb().execute({ sql: `SELECT r.payload_json FROM local_sourcebook_submissions s JOIN local_sourcebook_revisions r ON r.submission_id = s.id AND r.revision = s.published_revision WHERE s.category = ? AND s.state = ? AND s.published_revision IS NOT NULL AND s.status NOT IN ('unpublished', 'rejected') ORDER BY s.business_name`, args: [category, state] });
24790
24790
  return result.rows.map((row) => parseJson2(row.payload_json));
24791
24791
  }
24792
+ async function listAllPublicLocalSourcebook(category) {
24793
+ await ensureLocalSourcebookSchema();
24794
+ const result = category ? await getDb().execute({
24795
+ sql: `SELECT r.payload_json FROM local_sourcebook_submissions s JOIN local_sourcebook_revisions r ON r.submission_id = s.id AND r.revision = s.published_revision WHERE s.category = ? AND s.published_revision IS NOT NULL AND s.status NOT IN ('unpublished', 'rejected') ORDER BY s.state, s.business_name`,
24796
+ args: [category]
24797
+ }) : await getDb().execute(`SELECT r.payload_json FROM local_sourcebook_submissions s JOIN local_sourcebook_revisions r ON r.submission_id = s.id AND r.revision = s.published_revision WHERE s.published_revision IS NOT NULL AND s.status NOT IN ('unpublished', 'rejected') ORDER BY s.category, s.state, s.business_name`);
24798
+ return result.rows.map((row) => parseJson2(row.payload_json));
24799
+ }
24792
24800
  async function getPublicLocalSourcebook(category, state, slug2) {
24793
24801
  await ensureLocalSourcebookSchema();
24794
24802
  const result = await getDb().execute({ sql: `SELECT r.payload_json FROM local_sourcebook_submissions s JOIN local_sourcebook_revisions r ON r.submission_id = s.id AND r.revision = s.published_revision WHERE s.category = ? AND s.state = ? AND s.slug = ? AND s.published_revision IS NOT NULL AND s.status NOT IN ('unpublished', 'rejected') LIMIT 1`, args: [category, state, slug2] });
@@ -25444,6 +25452,41 @@ var init_MapsExtractor = __esm({
25444
25452
  }
25445
25453
  });
25446
25454
 
25455
+ // src/api/local-sourcebook-public-urls.ts
25456
+ function rootDomain() {
25457
+ const configured = process.env.LOCAL_SOURCEBOOK_PUBLIC_ROOT_DOMAIN?.trim() || DEFAULT_ROOT_DOMAIN;
25458
+ return configured.replace(/^https?:\/\//i, "").replace(/^www\./i, "").replace(/\/.*$/, "").replace(/\.+$/, "").toLowerCase();
25459
+ }
25460
+ function localSourcebookPublicUrls(category, state, slug2) {
25461
+ const categoryUrl = `https://${category}.${rootDomain()}`;
25462
+ const statePath = `/${state.toLowerCase()}`;
25463
+ const profilePath = `${statePath}/${slug2}/`;
25464
+ const reviewsPath = `${statePath}/${slug2}/reviews`;
25465
+ return {
25466
+ categoryUrl,
25467
+ nationwideDirectoryUrl: `${categoryUrl}/directory`,
25468
+ stateUrl: `${categoryUrl}${statePath}`,
25469
+ profileUrl: `${categoryUrl}${profilePath}`,
25470
+ reviewsUrl: `${categoryUrl}${reviewsPath}`,
25471
+ profilePath,
25472
+ reviewsPath
25473
+ };
25474
+ }
25475
+ function localSourcebookPublicationLinks(submission) {
25476
+ return {
25477
+ ...localSourcebookPublicUrls(submission.category, submission.state, submission.slug),
25478
+ publicationStatus: submission.status,
25479
+ isLive: submission.status === "published" && submission.publishedRevision !== null
25480
+ };
25481
+ }
25482
+ var DEFAULT_ROOT_DOMAIN;
25483
+ var init_local_sourcebook_public_urls = __esm({
25484
+ "src/api/local-sourcebook-public-urls.ts"() {
25485
+ "use strict";
25486
+ DEFAULT_ROOT_DOMAIN = "localsourcebook.com";
25487
+ }
25488
+ });
25489
+
25447
25490
  // src/api/local-sourcebook-compiler.ts
25448
25491
  function digest(value) {
25449
25492
  return (0, import_node_crypto13.createHash)("sha256").update(value).digest("hex").slice(0, 20);
@@ -25719,6 +25762,7 @@ function compileLocalSourcebookListing(input) {
25719
25762
  evidenceIds: mapsEvidenceId ? [mapsEvidenceId] : []
25720
25763
  }
25721
25764
  ];
25765
+ const publicUrls = localSourcebookPublicUrls(input.submission.category, input.submission.state, input.submission.slug);
25722
25766
  const listing = {
25723
25767
  id: input.submission.id,
25724
25768
  publicationRevision: input.submission.draftRevision + 1,
@@ -25727,6 +25771,14 @@ function compileLocalSourcebookListing(input) {
25727
25771
  state: input.submission.state.toLowerCase(),
25728
25772
  stateName: STATE_NAMES[input.submission.state.toUpperCase()] ?? input.submission.state.toUpperCase(),
25729
25773
  slug: input.submission.slug,
25774
+ canonicalPaths: {
25775
+ profile: publicUrls.profilePath,
25776
+ reviews: publicUrls.reviewsPath
25777
+ },
25778
+ canonicalUrls: {
25779
+ profile: publicUrls.profileUrl,
25780
+ reviews: publicUrls.reviewsUrl
25781
+ },
25730
25782
  name: maps?.name || input.submission.businessName,
25731
25783
  summary,
25732
25784
  description,
@@ -25790,6 +25842,7 @@ var init_local_sourcebook_compiler = __esm({
25790
25842
  "src/api/local-sourcebook-compiler.ts"() {
25791
25843
  "use strict";
25792
25844
  import_node_crypto13 = require("crypto");
25845
+ init_local_sourcebook_public_urls();
25793
25846
  CATEGORY_LABELS = {
25794
25847
  home: "Home & property services",
25795
25848
  professional: "Professional services",
@@ -36621,7 +36674,11 @@ function getLocalSourcebookContract(category) {
36621
36674
  categories: LOCAL_SOURCEBOOK_CATEGORIES,
36622
36675
  selectedCategory: category ?? null,
36623
36676
  categoryTag: category ? CATEGORY_TAG[category] : null,
36624
- canonicalRoutes: { profile: "/{state}/{business-slug}/", reviews: "/{state}/{business-slug}/reviews" },
36677
+ canonicalRoutes: {
36678
+ nationwideDirectory: "https://{category}.localsourcebook.com/directory",
36679
+ profile: "https://{category}.localsourcebook.com/{state}/{business-slug}/",
36680
+ reviews: "https://{category}.localsourcebook.com/{state}/{business-slug}/reviews"
36681
+ },
36625
36682
  requiredCreateFields: ["category", "state", "businessName", "websiteUrl", "tags", "idempotencyKey"],
36626
36683
  systemCompiledSections: ["identity", "services/products", "service areas/locations", "genuine media", "reviews and theme counts", "evidence", "answered FAQ", "collection receipts"],
36627
36684
  workflow: ["list-local-sourcebook-tags", "get-local-sourcebook-contract", "prepare-local-sourcebook-write", "validate-local-sourcebook-write", "local-sourcebook-capture", "local_sourcebook_submission_status"],
@@ -36641,9 +36698,10 @@ async function prepareLocalSourcebookWrite(input) {
36641
36698
  ...parsed.tags.filter((tag) => tag !== categoryTag).map((tag) => ({ tag }))
36642
36699
  ];
36643
36700
  const tagResolutions = await resolveLocalSourcebookTags(candidates);
36701
+ const publicUrls = localSourcebookPublicUrls(parsed.category, parsed.state, slug2);
36644
36702
  return {
36645
36703
  ok: true,
36646
- route: { category: parsed.category, state: parsed.state, slug: slug2, profile: `/${parsed.state}/${slug2}/`, reviews: `/${parsed.state}/${slug2}/reviews` },
36704
+ route: { category: parsed.category, state: parsed.state, slug: slug2, ...publicUrls },
36647
36705
  proposedWrite: { ...parsed, slug: slug2, tags: tagResolutions.filter((item) => item.action === "reuse").map((item) => item.tag) },
36648
36706
  contract: getLocalSourcebookContract(parsed.category),
36649
36707
  tagResolutions,
@@ -36709,6 +36767,7 @@ var init_local_sourcebook_governance = __esm({
36709
36767
  import_zod29 = require("zod");
36710
36768
  init_db();
36711
36769
  init_local_sourcebook_repository();
36770
+ init_local_sourcebook_public_urls();
36712
36771
  DIRECTORY_TAG_SEEDS = [
36713
36772
  { tag: "home-services", description: "Businesses that maintain, repair, improve, or protect homes and property.", aliases: ["home service", "home and property services"] },
36714
36773
  { tag: "professional-services", description: "Local professional and business service providers.", aliases: ["professional service"] },
@@ -36778,6 +36837,7 @@ var init_local_sourcebook_routes = __esm({
36778
36837
  init_local_sourcebook_dispatch();
36779
36838
  init_local_sourcebook_governance();
36780
36839
  init_local_sourcebook_repository();
36840
+ init_local_sourcebook_public_urls();
36781
36841
  slug = (value) => value.toLowerCase().trim().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 100);
36782
36842
  SubmitSchema = import_zod30.z.object({
36783
36843
  category: import_zod30.z.enum(LOCAL_SOURCEBOOK_CATEGORIES),
@@ -36826,6 +36886,7 @@ var init_local_sourcebook_routes = __esm({
36826
36886
  return c.json({ error: "idempotency_key_required", message: "A stable idempotencyKey is required for a new Local Sourcebook capture." }, 400);
36827
36887
  }
36828
36888
  const listingSlug = identity.slug ?? slug(identity.businessName);
36889
+ const publicUrls = localSourcebookPublicUrls(identity.category, identity.state, listingSlug);
36829
36890
  const submission = await createLocalSourcebookSubmission({
36830
36891
  ownerUserId: Number(user.id),
36831
36892
  category: identity.category,
@@ -36842,7 +36903,8 @@ var init_local_sourcebook_routes = __esm({
36842
36903
  name: identity.businessName,
36843
36904
  websiteUrl: identity.websiteUrl,
36844
36905
  tags: validation.normalizedTags,
36845
- canonicalPaths: { profile: `/${identity.state}/${listingSlug}`, reviews: `/${identity.state}/${listingSlug}/reviews` },
36906
+ canonicalPaths: { profile: publicUrls.profilePath, reviews: publicUrls.reviewsPath },
36907
+ canonicalUrls: { profile: publicUrls.profileUrl, reviews: publicUrls.reviewsUrl },
36846
36908
  verificationState: "submitted",
36847
36909
  acquisitionPlan: acquisitionPlan(identity.websiteUrl),
36848
36910
  evidence: [],
@@ -36852,7 +36914,7 @@ var init_local_sourcebook_routes = __esm({
36852
36914
  });
36853
36915
  await persistLocalSourcebookTags(submission.id, Number(user.id), validation.normalizedTags, parsed.data.tagDecisions);
36854
36916
  const dispatched = await dispatchLocalSourcebookAcquisition(submission.id).catch(() => false);
36855
- return c.json({ ok: true, valid: true, submission, normalizedTags: validation.normalizedTags, acquisition: { queued: true, dispatched }, acquisitionPlan: acquisitionPlan(identity.websiteUrl) }, 202);
36917
+ return c.json({ ok: true, valid: true, submission, publicUrls: localSourcebookPublicationLinks(submission), normalizedTags: validation.normalizedTags, acquisition: { queued: true, dispatched }, acquisitionPlan: acquisitionPlan(identity.websiteUrl) }, 202);
36856
36918
  } catch (error) {
36857
36919
  const message = error instanceof Error ? error.message : String(error);
36858
36920
  const conflict = message.includes("revision conflict") ? "revision_conflict" : message.includes("idempotency key conflicts") ? "idempotency_conflict" : null;
@@ -36866,6 +36928,7 @@ var init_local_sourcebook_routes = __esm({
36866
36928
  if (!parsed.success) return c.json({ error: "invalid_submission", issues: parsed.error.issues }, 400);
36867
36929
  const input = parsed.data;
36868
36930
  const listingSlug = input.slug ?? slug(input.businessName);
36931
+ const publicUrls = localSourcebookPublicUrls(input.category, input.state, listingSlug);
36869
36932
  try {
36870
36933
  const submission = await createLocalSourcebookSubmission({
36871
36934
  ownerUserId: Number(user.id),
@@ -36878,7 +36941,8 @@ var init_local_sourcebook_routes = __esm({
36878
36941
  slug: listingSlug,
36879
36942
  name: input.businessName,
36880
36943
  websiteUrl: input.websiteUrl,
36881
- canonicalPaths: { profile: `/${input.state}/${listingSlug}`, reviews: `/${input.state}/${listingSlug}/reviews` },
36944
+ canonicalPaths: { profile: publicUrls.profilePath, reviews: publicUrls.reviewsPath },
36945
+ canonicalUrls: { profile: publicUrls.profileUrl, reviews: publicUrls.reviewsUrl },
36882
36946
  verificationState: "submitted",
36883
36947
  acquisitionPlan: acquisitionPlan(input.websiteUrl),
36884
36948
  evidence: [],
@@ -36887,7 +36951,7 @@ var init_local_sourcebook_routes = __esm({
36887
36951
  }
36888
36952
  });
36889
36953
  const dispatched = await dispatchLocalSourcebookAcquisition(submission.id).catch(() => false);
36890
- return c.json({ ok: true, submission, acquisition: { queued: true, dispatched }, acquisitionPlan: acquisitionPlan(input.websiteUrl) }, 202);
36954
+ return c.json({ ok: true, submission, publicUrls: localSourcebookPublicationLinks(submission), acquisition: { queued: true, dispatched }, acquisitionPlan: acquisitionPlan(input.websiteUrl) }, 202);
36891
36955
  } catch (error) {
36892
36956
  return c.json({ error: "submission_conflict", message: error instanceof Error ? error.message : String(error) }, 409);
36893
36957
  }
@@ -36896,7 +36960,7 @@ var init_local_sourcebook_routes = __esm({
36896
36960
  const userId = Number(c.get("user").id);
36897
36961
  const submission = await getLocalSourcebookSubmission(c.req.param("id"), userId);
36898
36962
  if (!submission) return c.json({ error: "not_found" }, 404);
36899
- return c.json({ ok: true, submission, draft: await getLocalSourcebookDraft(submission.id, userId) });
36963
+ return c.json({ ok: true, submission, publicUrls: localSourcebookPublicationLinks(submission), draft: await getLocalSourcebookDraft(submission.id, userId) });
36900
36964
  });
36901
36965
  localSourcebookApp.on(["PATCH", "POST"], "/submissions/:id/draft", createApiKeyAuth(), (c) => c.json({
36902
36966
  error: "evidence_refresh_required",
@@ -36909,12 +36973,14 @@ var init_local_sourcebook_routes = __esm({
36909
36973
  const submission = await queueLocalSourcebookRefresh(c.req.param("id"), Number(user.id));
36910
36974
  if (!submission) return c.json({ error: "not_found" }, 404);
36911
36975
  const dispatched = await dispatchLocalSourcebookAcquisition(submission.id).catch(() => false);
36912
- return c.json({ ok: true, submission, acquisition: { queued: true, dispatched }, acquisitionPlan: acquisitionPlan(submission.websiteUrl) }, 202);
36976
+ return c.json({ ok: true, submission, publicUrls: localSourcebookPublicationLinks(submission), acquisition: { queued: true, dispatched }, acquisitionPlan: acquisitionPlan(submission.websiteUrl) }, 202);
36913
36977
  } catch (error) {
36914
36978
  return c.json({ error: "moderated_listing", message: error instanceof Error ? error.message : String(error) }, 409);
36915
36979
  }
36916
36980
  });
36917
36981
  publicLocalSourcebookApp = new import_hono17.Hono();
36982
+ publicLocalSourcebookApp.get("/", async (c) => c.json({ ok: true, listings: await listAllPublicLocalSourcebook() }));
36983
+ publicLocalSourcebookApp.get("/:category", async (c) => c.json({ ok: true, listings: await listAllPublicLocalSourcebook(c.req.param("category")) }));
36918
36984
  publicLocalSourcebookApp.get("/:category/:state", async (c) => c.json({ ok: true, listings: await listPublicLocalSourcebook(c.req.param("category"), c.req.param("state").toLowerCase()) }));
36919
36985
  publicLocalSourcebookApp.get("/:category/:state/:slug", async (c) => {
36920
36986
  const listing = await getPublicLocalSourcebook(c.req.param("category"), c.req.param("state").toLowerCase(), c.req.param("slug"));
@@ -40932,7 +40998,7 @@ var PACKAGE_VERSION;
40932
40998
  var init_version = __esm({
40933
40999
  "src/version.ts"() {
40934
41000
  "use strict";
40935
- PACKAGE_VERSION = "0.44.2";
41001
+ PACKAGE_VERSION = "0.44.3";
40936
41002
  }
40937
41003
  });
40938
41004
 
@@ -41103,6 +41169,8 @@ Multi-step orchestrations \u2014 prefer these over hand-chaining primitives when
41103
41169
  publishes automatically. Never supply owner-authored services, reviews, images, or other public claims.
41104
41170
  - Read progress and the compiled record with **local_sourcebook_submission_status**. Use
41105
41171
  **local_sourcebook_refresh** only when the owner intentionally wants to pay for a new acquisition pass.
41172
+ - Capture and status responses return \`publicUrls.profileUrl\` and \`publicUrls.reviewsUrl\`. Always give those
41173
+ exact links to the user; do not report a listing as live without including its clickable profile URL.
41106
41174
  - The last published revision remains public during refresh. Administrators can reject or unpublish an
41107
41175
  exceptional record, but routine subscriber publication does not require administrator review.
41108
41176
 
@@ -45182,7 +45250,7 @@ function registerPaaExtractorMcpTools(server, executor, options = {}) {
45182
45250
  }, async (input) => formatDirectoryWorkflow(await executor.directoryWorkflowStatus(input), input, ctx));
45183
45251
  server.registerTool("get-local-sourcebook-contract", {
45184
45252
  title: "Get Local Sourcebook Contract",
45185
- description: "Read the governed listing purpose, supported categories, canonical profile/review routes, required sections, acquisition limits, tag policy, ownership boundary, and admin-only publication rule. Call this before composing a listing when its required shape is uncertain.",
45253
+ description: "Read the governed listing purpose, supported categories, exact LocalSourcebook.com profile/review URLs, nationwide directory URL, required sections, acquisition limits, tag policy, ownership boundary, and automatic evidence-publication rule. Call this before composing a listing when its required shape is uncertain.",
45186
45254
  inputSchema: GetLocalSourcebookContractInputSchema,
45187
45255
  outputSchema: recordOutputSchema("get-local-sourcebook-contract", LocalSourcebookOutputSchema),
45188
45256
  annotations: localPlanningToolAnnotations("Get Local Sourcebook Contract")
@@ -45217,14 +45285,14 @@ function registerPaaExtractorMcpTools(server, executor, options = {}) {
45217
45285
  }, async (input) => executor.validateLocalSourcebookWrite(input));
45218
45286
  server.registerTool("local-sourcebook-capture", {
45219
45287
  title: "Capture Governed Local Sourcebook Listing",
45220
- description: "Strict owner-scoped write path after list, contract, prepare, and validate. Capture registers canonical tags and queues paid website, exact-place, review, service-area, staff, and genuine-media acquisition. A successful system-compiled evidence revision publishes automatically; owner-authored public claims are not accepted.",
45288
+ description: "Strict owner-scoped write path after list, contract, prepare, and validate. Capture registers canonical tags, returns the exact LocalSourcebook.com profile and reviews URLs, and queues paid website, exact-place, review, service-area, staff, and genuine-media acquisition. A successful system-compiled evidence revision publishes automatically; owner-authored public claims are not accepted.",
45221
45289
  inputSchema: LocalSourcebookCaptureInputSchema,
45222
45290
  outputSchema: recordOutputSchema("local-sourcebook-capture", LocalSourcebookOutputSchema),
45223
45291
  annotations: liveWebToolAnnotations("Capture Governed Local Sourcebook Listing")
45224
45292
  }, async (input) => executor.localSourcebookCapture(input));
45225
45293
  server.registerTool("local_sourcebook_submission_status", {
45226
45294
  title: "Local Sourcebook Submission Status",
45227
- description: "Read the authenticated caller\u2019s listing draft, enrichment coverage, immutable revision number, and publication state.",
45295
+ description: "Read the authenticated caller\u2019s listing draft, enrichment coverage, immutable revision number, publication state, and exact live LocalSourcebook.com profile and reviews URLs.",
45228
45296
  inputSchema: LocalSourcebookSubmissionStatusInputSchema,
45229
45297
  outputSchema: recordOutputSchema("local_sourcebook_submission_status", LocalSourcebookOutputSchema),
45230
45298
  annotations: localPlanningToolAnnotations("Local Sourcebook Submission Status")