@fre4x/hn 1.0.50 → 1.0.53

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.
Files changed (2) hide show
  1. package/dist/index.js +633 -139
  2. package/package.json +5 -5
package/dist/index.js CHANGED
@@ -40847,6 +40847,250 @@ var ExperimentalMcpServerTasks = class {
40847
40847
  }
40848
40848
  };
40849
40849
 
40850
+ // ../node_modules/zod/index.js
40851
+ var zod_exports = {};
40852
+ __export(zod_exports, {
40853
+ $brand: () => $brand,
40854
+ $input: () => $input,
40855
+ $output: () => $output,
40856
+ NEVER: () => NEVER,
40857
+ TimePrecision: () => TimePrecision,
40858
+ ZodAny: () => ZodAny2,
40859
+ ZodArray: () => ZodArray2,
40860
+ ZodBase64: () => ZodBase64,
40861
+ ZodBase64URL: () => ZodBase64URL,
40862
+ ZodBigInt: () => ZodBigInt2,
40863
+ ZodBigIntFormat: () => ZodBigIntFormat,
40864
+ ZodBoolean: () => ZodBoolean2,
40865
+ ZodCIDRv4: () => ZodCIDRv4,
40866
+ ZodCIDRv6: () => ZodCIDRv6,
40867
+ ZodCUID: () => ZodCUID,
40868
+ ZodCUID2: () => ZodCUID2,
40869
+ ZodCatch: () => ZodCatch2,
40870
+ ZodCodec: () => ZodCodec,
40871
+ ZodCustom: () => ZodCustom,
40872
+ ZodCustomStringFormat: () => ZodCustomStringFormat,
40873
+ ZodDate: () => ZodDate2,
40874
+ ZodDefault: () => ZodDefault2,
40875
+ ZodDiscriminatedUnion: () => ZodDiscriminatedUnion2,
40876
+ ZodE164: () => ZodE164,
40877
+ ZodEmail: () => ZodEmail,
40878
+ ZodEmoji: () => ZodEmoji,
40879
+ ZodEnum: () => ZodEnum2,
40880
+ ZodError: () => ZodError2,
40881
+ ZodExactOptional: () => ZodExactOptional,
40882
+ ZodFile: () => ZodFile,
40883
+ ZodFirstPartyTypeKind: () => ZodFirstPartyTypeKind2,
40884
+ ZodFunction: () => ZodFunction2,
40885
+ ZodGUID: () => ZodGUID,
40886
+ ZodIPv4: () => ZodIPv4,
40887
+ ZodIPv6: () => ZodIPv6,
40888
+ ZodISODate: () => ZodISODate,
40889
+ ZodISODateTime: () => ZodISODateTime,
40890
+ ZodISODuration: () => ZodISODuration,
40891
+ ZodISOTime: () => ZodISOTime,
40892
+ ZodIntersection: () => ZodIntersection2,
40893
+ ZodIssueCode: () => ZodIssueCode2,
40894
+ ZodJWT: () => ZodJWT,
40895
+ ZodKSUID: () => ZodKSUID,
40896
+ ZodLazy: () => ZodLazy2,
40897
+ ZodLiteral: () => ZodLiteral2,
40898
+ ZodMAC: () => ZodMAC,
40899
+ ZodMap: () => ZodMap2,
40900
+ ZodNaN: () => ZodNaN2,
40901
+ ZodNanoID: () => ZodNanoID,
40902
+ ZodNever: () => ZodNever2,
40903
+ ZodNonOptional: () => ZodNonOptional,
40904
+ ZodNull: () => ZodNull2,
40905
+ ZodNullable: () => ZodNullable2,
40906
+ ZodNumber: () => ZodNumber2,
40907
+ ZodNumberFormat: () => ZodNumberFormat,
40908
+ ZodObject: () => ZodObject2,
40909
+ ZodOptional: () => ZodOptional2,
40910
+ ZodPipe: () => ZodPipe,
40911
+ ZodPrefault: () => ZodPrefault,
40912
+ ZodPromise: () => ZodPromise2,
40913
+ ZodReadonly: () => ZodReadonly2,
40914
+ ZodRealError: () => ZodRealError,
40915
+ ZodRecord: () => ZodRecord2,
40916
+ ZodSet: () => ZodSet2,
40917
+ ZodString: () => ZodString2,
40918
+ ZodStringFormat: () => ZodStringFormat,
40919
+ ZodSuccess: () => ZodSuccess,
40920
+ ZodSymbol: () => ZodSymbol2,
40921
+ ZodTemplateLiteral: () => ZodTemplateLiteral,
40922
+ ZodTransform: () => ZodTransform,
40923
+ ZodTuple: () => ZodTuple2,
40924
+ ZodType: () => ZodType2,
40925
+ ZodULID: () => ZodULID,
40926
+ ZodURL: () => ZodURL,
40927
+ ZodUUID: () => ZodUUID,
40928
+ ZodUndefined: () => ZodUndefined2,
40929
+ ZodUnion: () => ZodUnion2,
40930
+ ZodUnknown: () => ZodUnknown2,
40931
+ ZodVoid: () => ZodVoid2,
40932
+ ZodXID: () => ZodXID,
40933
+ ZodXor: () => ZodXor,
40934
+ _ZodString: () => _ZodString,
40935
+ _default: () => _default2,
40936
+ _function: () => _function,
40937
+ any: () => any,
40938
+ array: () => array,
40939
+ base64: () => base642,
40940
+ base64url: () => base64url2,
40941
+ bigint: () => bigint2,
40942
+ boolean: () => boolean2,
40943
+ catch: () => _catch2,
40944
+ check: () => check,
40945
+ cidrv4: () => cidrv42,
40946
+ cidrv6: () => cidrv62,
40947
+ clone: () => clone,
40948
+ codec: () => codec,
40949
+ coerce: () => coerce_exports2,
40950
+ config: () => config,
40951
+ core: () => core_exports2,
40952
+ cuid: () => cuid3,
40953
+ cuid2: () => cuid22,
40954
+ custom: () => custom,
40955
+ date: () => date3,
40956
+ decode: () => decode2,
40957
+ decodeAsync: () => decodeAsync2,
40958
+ default: () => zod_default,
40959
+ describe: () => describe2,
40960
+ discriminatedUnion: () => discriminatedUnion,
40961
+ e164: () => e1642,
40962
+ email: () => email2,
40963
+ emoji: () => emoji2,
40964
+ encode: () => encode2,
40965
+ encodeAsync: () => encodeAsync2,
40966
+ endsWith: () => _endsWith,
40967
+ enum: () => _enum2,
40968
+ exactOptional: () => exactOptional,
40969
+ file: () => file,
40970
+ flattenError: () => flattenError,
40971
+ float32: () => float32,
40972
+ float64: () => float64,
40973
+ formatError: () => formatError,
40974
+ fromJSONSchema: () => fromJSONSchema,
40975
+ function: () => _function,
40976
+ getErrorMap: () => getErrorMap2,
40977
+ globalRegistry: () => globalRegistry,
40978
+ gt: () => _gt,
40979
+ gte: () => _gte,
40980
+ guid: () => guid2,
40981
+ hash: () => hash,
40982
+ hex: () => hex2,
40983
+ hostname: () => hostname2,
40984
+ httpUrl: () => httpUrl,
40985
+ includes: () => _includes,
40986
+ instanceof: () => _instanceof,
40987
+ int: () => int,
40988
+ int32: () => int32,
40989
+ int64: () => int64,
40990
+ intersection: () => intersection,
40991
+ ipv4: () => ipv42,
40992
+ ipv6: () => ipv62,
40993
+ iso: () => iso_exports2,
40994
+ json: () => json,
40995
+ jwt: () => jwt,
40996
+ keyof: () => keyof,
40997
+ ksuid: () => ksuid2,
40998
+ lazy: () => lazy,
40999
+ length: () => _length,
41000
+ literal: () => literal,
41001
+ locales: () => locales_exports,
41002
+ looseObject: () => looseObject,
41003
+ looseRecord: () => looseRecord,
41004
+ lowercase: () => _lowercase,
41005
+ lt: () => _lt,
41006
+ lte: () => _lte,
41007
+ mac: () => mac2,
41008
+ map: () => map,
41009
+ maxLength: () => _maxLength,
41010
+ maxSize: () => _maxSize,
41011
+ meta: () => meta2,
41012
+ mime: () => _mime,
41013
+ minLength: () => _minLength,
41014
+ minSize: () => _minSize,
41015
+ multipleOf: () => _multipleOf,
41016
+ nan: () => nan,
41017
+ nanoid: () => nanoid2,
41018
+ nativeEnum: () => nativeEnum,
41019
+ negative: () => _negative,
41020
+ never: () => never,
41021
+ nonnegative: () => _nonnegative,
41022
+ nonoptional: () => nonoptional,
41023
+ nonpositive: () => _nonpositive,
41024
+ normalize: () => _normalize,
41025
+ null: () => _null3,
41026
+ nullable: () => nullable,
41027
+ nullish: () => nullish2,
41028
+ number: () => number2,
41029
+ object: () => object2,
41030
+ optional: () => optional,
41031
+ overwrite: () => _overwrite,
41032
+ parse: () => parse2,
41033
+ parseAsync: () => parseAsync2,
41034
+ partialRecord: () => partialRecord,
41035
+ pipe: () => pipe,
41036
+ positive: () => _positive,
41037
+ prefault: () => prefault,
41038
+ preprocess: () => preprocess,
41039
+ prettifyError: () => prettifyError,
41040
+ promise: () => promise,
41041
+ property: () => _property,
41042
+ readonly: () => readonly,
41043
+ record: () => record,
41044
+ refine: () => refine,
41045
+ regex: () => _regex,
41046
+ regexes: () => regexes_exports,
41047
+ registry: () => registry,
41048
+ safeDecode: () => safeDecode2,
41049
+ safeDecodeAsync: () => safeDecodeAsync2,
41050
+ safeEncode: () => safeEncode2,
41051
+ safeEncodeAsync: () => safeEncodeAsync2,
41052
+ safeParse: () => safeParse3,
41053
+ safeParseAsync: () => safeParseAsync3,
41054
+ set: () => set,
41055
+ setErrorMap: () => setErrorMap,
41056
+ size: () => _size,
41057
+ slugify: () => _slugify,
41058
+ startsWith: () => _startsWith,
41059
+ strictObject: () => strictObject,
41060
+ string: () => string2,
41061
+ stringFormat: () => stringFormat,
41062
+ stringbool: () => stringbool,
41063
+ success: () => success,
41064
+ superRefine: () => superRefine,
41065
+ symbol: () => symbol,
41066
+ templateLiteral: () => templateLiteral,
41067
+ toJSONSchema: () => toJSONSchema,
41068
+ toLowerCase: () => _toLowerCase,
41069
+ toUpperCase: () => _toUpperCase,
41070
+ transform: () => transform,
41071
+ treeifyError: () => treeifyError,
41072
+ trim: () => _trim,
41073
+ tuple: () => tuple,
41074
+ uint32: () => uint32,
41075
+ uint64: () => uint64,
41076
+ ulid: () => ulid2,
41077
+ undefined: () => _undefined3,
41078
+ union: () => union,
41079
+ unknown: () => unknown,
41080
+ uppercase: () => _uppercase,
41081
+ url: () => url,
41082
+ util: () => util_exports,
41083
+ uuid: () => uuid2,
41084
+ uuidv4: () => uuidv4,
41085
+ uuidv6: () => uuidv6,
41086
+ uuidv7: () => uuidv7,
41087
+ void: () => _void2,
41088
+ xid: () => xid2,
41089
+ xor: () => xor,
41090
+ z: () => external_exports3
41091
+ });
41092
+ var zod_default = external_exports3;
41093
+
40850
41094
  // ../node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js
40851
41095
  var McpServer = class {
40852
41096
  constructor(serverInfo, options) {
@@ -41725,6 +41969,71 @@ var StdioServerTransport = class {
41725
41969
  }
41726
41970
  };
41727
41971
 
41972
+ // ../packages/shared/dist/errors.js
41973
+ function createInternalError(error48) {
41974
+ const message = error48 instanceof Error ? error48.message : String(error48);
41975
+ return {
41976
+ isError: true,
41977
+ content: [
41978
+ {
41979
+ type: "text",
41980
+ text: `Internal Error: ${message}
41981
+
41982
+ Suggestion: If the problem persists, check server logs.`
41983
+ }
41984
+ ]
41985
+ };
41986
+ }
41987
+
41988
+ // ../packages/shared/dist/format.js
41989
+ function formatListItems(items, renderer, emptyMessage = "_No results found._") {
41990
+ if (items.length === 0)
41991
+ return emptyMessage;
41992
+ return items.map((item, i) => renderer(item, i)).join("\n\n");
41993
+ }
41994
+ function formatField(label, value) {
41995
+ if (value === null || value === void 0 || value === "")
41996
+ return "";
41997
+ return `**${label}:** ${value}`;
41998
+ }
41999
+ function formatFields(fields) {
42000
+ return fields.map(([label, value]) => formatField(label, value)).filter(Boolean).join(" \n");
42001
+ }
42002
+ function formatPaginationFooter(offset, limit, total) {
42003
+ const start = offset + 1;
42004
+ const end = Math.min(offset + limit, total);
42005
+ return `
42006
+ ---
42007
+ _Showing ${start}\u2013${end} of ${total}. Use \`offset: ${offset + limit}\` for the next page._`;
42008
+ }
42009
+
42010
+ // ../packages/shared/dist/pagination.js
42011
+ var z2 = external_exports3 || zod_default || zod_exports;
42012
+ var paginationSchema = z2.object({
42013
+ limit: z2.number().int().min(1).max(100).default(20).describe("Maximum results to return (1\u2013100, default 20)"),
42014
+ offset: z2.number().int().min(0).default(0).describe("Number of results to skip for pagination (default 0)")
42015
+ });
42016
+ function applyPagination(items, params) {
42017
+ const { limit, offset } = params;
42018
+ const total = items.length;
42019
+ const sliced = items.slice(offset, offset + limit);
42020
+ return {
42021
+ items: sliced,
42022
+ total,
42023
+ offset,
42024
+ limit,
42025
+ hasMore: offset + limit < total
42026
+ };
42027
+ }
42028
+
42029
+ // ../packages/shared/dist/package.js
42030
+ import { createRequire as createJsonRequire } from "node:module";
42031
+ function getPackageVersion(moduleUrl) {
42032
+ const require2 = createJsonRequire(moduleUrl);
42033
+ const packageJson = require2("../package.json");
42034
+ return packageJson.version ?? "0.0.0";
42035
+ }
42036
+
41728
42037
  // src/api.ts
41729
42038
  import https2 from "node:https";
41730
42039
 
@@ -45505,6 +45814,46 @@ var {
45505
45814
  mergeConfig: mergeConfig2
45506
45815
  } = axios_default;
45507
45816
 
45817
+ // src/mock.ts
45818
+ function isMock() {
45819
+ return process.env.MOCK === "true" || process.env.HN_MOCK === "true";
45820
+ }
45821
+ var MOCK_ITEM = {
45822
+ id: 1001,
45823
+ type: "story",
45824
+ by: "fre4x",
45825
+ time: 1712232e3,
45826
+ title: "Mock HN Story",
45827
+ url: "https://example.com/mock-hn-story",
45828
+ score: 42,
45829
+ descendants: 7,
45830
+ kids: [1002, 1003],
45831
+ text: "Mock story content for offline development."
45832
+ };
45833
+ var MOCK_USER = {
45834
+ id: "fre4x",
45835
+ created: 16e8,
45836
+ karma: 1337,
45837
+ about: "Mock Hacker News user for MCP development.",
45838
+ submitted: [1001, 1002, 1003]
45839
+ };
45840
+ var MOCK_UPDATES = {
45841
+ items: [1003, 1002, 1001],
45842
+ profiles: ["fre4x", "b1te"]
45843
+ };
45844
+ var MOCK_FIXTURES = {
45845
+ item: MOCK_ITEM,
45846
+ user: MOCK_USER,
45847
+ maxItem: 1003,
45848
+ topStories: [1003, 1002, 1001],
45849
+ newStories: [1003, 1002, 1001],
45850
+ bestStories: [1001, 1003, 1002],
45851
+ askStories: [2003, 2002, 2001],
45852
+ showStories: [3003, 3002, 3001],
45853
+ jobStories: [4003, 4002, 4001],
45854
+ updates: MOCK_UPDATES
45855
+ };
45856
+
45508
45857
  // src/api.ts
45509
45858
  var BASE_URL = "https://hacker-news.firebaseio.com/v0";
45510
45859
  var httpClient = axios_default.create({
@@ -45541,36 +45890,69 @@ var HNApiClient = class {
45541
45890
  return response.data;
45542
45891
  }
45543
45892
  async getItem(id) {
45893
+ if (isMock()) {
45894
+ return { ...MOCK_FIXTURES.item, id };
45895
+ }
45544
45896
  const response = await httpClient.get(`/item/${id}.json`);
45545
45897
  return response.data;
45546
45898
  }
45547
45899
  async getUser(id) {
45900
+ if (isMock()) {
45901
+ return { ...MOCK_FIXTURES.user, id };
45902
+ }
45548
45903
  const response = await httpClient.get(`/user/${id}.json`);
45549
45904
  return response.data;
45550
45905
  }
45551
45906
  async getMaxItem() {
45907
+ if (isMock()) {
45908
+ return MOCK_FIXTURES.maxItem;
45909
+ }
45552
45910
  const response = await httpClient.get(`/maxitem.json`);
45553
45911
  return response.data;
45554
45912
  }
45555
45913
  async getTopStories() {
45914
+ if (isMock()) {
45915
+ return [...MOCK_FIXTURES.topStories];
45916
+ }
45556
45917
  return this.fetchWithCache(`/topstories`);
45557
45918
  }
45558
45919
  async getNewStories() {
45920
+ if (isMock()) {
45921
+ return [...MOCK_FIXTURES.newStories];
45922
+ }
45559
45923
  return this.fetchWithCache(`/newstories`);
45560
45924
  }
45561
45925
  async getBestStories() {
45926
+ if (isMock()) {
45927
+ return [...MOCK_FIXTURES.bestStories];
45928
+ }
45562
45929
  return this.fetchWithCache(`/beststories`);
45563
45930
  }
45564
45931
  async getAskStories() {
45932
+ if (isMock()) {
45933
+ return [...MOCK_FIXTURES.askStories];
45934
+ }
45565
45935
  return this.fetchWithCache(`/askstories`);
45566
45936
  }
45567
45937
  async getShowStories() {
45938
+ if (isMock()) {
45939
+ return [...MOCK_FIXTURES.showStories];
45940
+ }
45568
45941
  return this.fetchWithCache(`/showstories`);
45569
45942
  }
45570
45943
  async getJobStories() {
45944
+ if (isMock()) {
45945
+ return [...MOCK_FIXTURES.jobStories];
45946
+ }
45571
45947
  return this.fetchWithCache(`/jobstories`);
45572
45948
  }
45573
45949
  async getUpdates() {
45950
+ if (isMock()) {
45951
+ return {
45952
+ items: [...MOCK_FIXTURES.updates.items],
45953
+ profiles: [...MOCK_FIXTURES.updates.profiles]
45954
+ };
45955
+ }
45574
45956
  const response = await httpClient.get(`/updates.json`);
45575
45957
  return response.data;
45576
45958
  }
@@ -45586,24 +45968,30 @@ var CHARACTER_LIMIT = 12e3;
45586
45968
 
45587
45969
  // src/index.ts
45588
45970
  var api = new HNApiClient();
45971
+ var PACKAGE_VERSION = getPackageVersion(import.meta.url);
45589
45972
  var server = new McpServer({
45590
45973
  name: "hackernews-mcp-server",
45591
- version: "1.0.0"
45974
+ version: PACKAGE_VERSION
45592
45975
  });
45593
45976
  function formatItemMarkdown(item) {
45594
45977
  if (!item) return "Item not found.";
45595
45978
  const lines = [];
45596
- if (item.title) lines.push(`# ${item.title}`);
45597
- if (item.by) lines.push(`**By**: ${item.by}`);
45598
- if (item.time)
45599
- lines.push(`**Time**: ${new Date(item.time * 1e3).toLocaleString()}`);
45600
- if (item.score !== void 0) lines.push(`**Score**: ${item.score}`);
45601
- if (item.url) lines.push(`**URL**: ${item.url}`);
45979
+ if (item.title) lines.push(`# ${item.title}
45980
+ `);
45981
+ lines.push(
45982
+ formatFields([
45983
+ ["By", item.by],
45984
+ [
45985
+ "Time",
45986
+ item.time ? new Date(item.time * 1e3).toLocaleString() : void 0
45987
+ ],
45988
+ ["Score", item.score],
45989
+ ["URL", item.url],
45990
+ ["Comments", item.descendants]
45991
+ ])
45992
+ );
45602
45993
  if (item.text) lines.push(`
45603
45994
  ${item.text}`);
45604
- if (item.descendants !== void 0)
45605
- lines.push(`
45606
- **Comments**: ${item.descendants}`);
45607
45995
  if (item.kids && item.kids.length > 0) {
45608
45996
  lines.push(`
45609
45997
  **Kids/Replies**: ${item.kids.join(", ")}`);
@@ -45617,27 +46005,24 @@ function handleLimit(content, format) {
45617
46005
 
45618
46006
  > [!NOTE]
45619
46007
  > Content truncated due to size limit. Use pagination (limit/offset) to fetch specific parts if applicable.`;
45620
- } else {
45621
- return JSON.stringify({
45622
- content: content.substring(0, CHARACTER_LIMIT),
45623
- truncated: true,
45624
- hint: "Content truncated due to size limit. Use limit/offset for lists."
45625
- });
45626
46008
  }
46009
+ return JSON.stringify({
46010
+ content: content.substring(0, CHARACTER_LIMIT),
46011
+ truncated: true,
46012
+ hint: "Content truncated due to size limit. Use limit/offset for lists."
46013
+ });
45627
46014
  }
45628
- var storySchema = {
45629
- limit: external_exports3.number().int().min(1).max(500).default(20).describe("Number of stories to return"),
45630
- offset: external_exports3.number().int().min(0).default(0).describe("Offset for pagination"),
46015
+ var storySchema = paginationSchema.extend({
45631
46016
  response_format: external_exports3.enum(Object.values(ResponseFormat)).default("markdown" /* MARKDOWN */).describe("Output format (markdown or json)")
45632
- };
45633
- async function handleStories(ids, params) {
45634
- const paginatedIds = ids.slice(params.offset, params.offset + params.limit);
46017
+ });
46018
+ async function handleHNStories(ids, params) {
46019
+ const paginated = applyPagination(ids, params);
45635
46020
  const result = {
45636
- total: ids.length,
45637
- count: paginatedIds.length,
45638
- offset: params.offset,
45639
- items: paginatedIds,
45640
- has_more: ids.length > params.offset + params.limit
46021
+ total: paginated.total,
46022
+ count: paginated.items.length,
46023
+ offset: paginated.offset,
46024
+ items: paginated.items,
46025
+ has_more: paginated.hasMore
45641
46026
  };
45642
46027
  if (params.response_format === "json" /* JSON */) {
45643
46028
  return {
@@ -45655,13 +46040,14 @@ async function handleStories(ids, params) {
45655
46040
  }
45656
46041
  const markdown = `# Hacker News Stories
45657
46042
 
45658
- Showing ${paginatedIds.length} of ${ids.length} stories (Offset: ${params.offset})
45659
-
45660
- ` + paginatedIds.map(
45661
- (id, i) => `${i + 1 + params.offset}. [${id}](https://news.ycombinator.com/item?id=${id})`
45662
- ).join("\n") + (result.has_more ? `
45663
-
45664
- *More stories available. Use offset=${params.offset + params.limit}*` : "");
46043
+ ` + formatListItems(
46044
+ paginated.items,
46045
+ (id, i) => `${i + 1 + paginated.offset}. [${id}](https://news.ycombinator.com/item?id=${id})`
46046
+ ) + (paginated.hasMore ? formatPaginationFooter(
46047
+ paginated.offset,
46048
+ paginated.limit,
46049
+ paginated.total
46050
+ ) : "");
45665
46051
  return {
45666
46052
  content: [
45667
46053
  {
@@ -45671,89 +46057,174 @@ Showing ${paginatedIds.length} of ${ids.length} stories (Offset: ${params.offset
45671
46057
  ]
45672
46058
  };
45673
46059
  }
46060
+ async function handleHNGetTopStories(params) {
46061
+ try {
46062
+ return await handleHNStories(await api.getTopStories(), params);
46063
+ } catch (error48) {
46064
+ return createInternalError(error48);
46065
+ }
46066
+ }
46067
+ async function handleHNGetItem(params) {
46068
+ try {
46069
+ const item = await api.getItem(params.id);
46070
+ if (params.response_format === "json" /* JSON */) {
46071
+ return {
46072
+ content: [
46073
+ {
46074
+ type: "text",
46075
+ text: handleLimit(
46076
+ JSON.stringify(item, null, 2),
46077
+ "json" /* JSON */
46078
+ )
46079
+ }
46080
+ ],
46081
+ structuredContent: item
46082
+ };
46083
+ }
46084
+ return {
46085
+ content: [
46086
+ {
46087
+ type: "text",
46088
+ text: handleLimit(
46089
+ formatItemMarkdown(item),
46090
+ "markdown" /* MARKDOWN */
46091
+ )
46092
+ }
46093
+ ]
46094
+ };
46095
+ } catch (error48) {
46096
+ return createInternalError(error48);
46097
+ }
46098
+ }
45674
46099
  server.registerTool(
45675
46100
  "hn_get_top_stories",
45676
46101
  {
45677
46102
  title: "Get Top Stories",
45678
46103
  description: "Retrieve IDs of the current top stories on Hacker News.",
45679
- inputSchema: external_exports3.object(storySchema),
46104
+ inputSchema: storySchema,
45680
46105
  annotations: {
45681
46106
  readOnlyHint: true,
45682
46107
  idempotentHint: true,
45683
46108
  openWorldHint: true
45684
46109
  }
45685
46110
  },
45686
- async (params) => handleStories(await api.getTopStories(), params)
46111
+ // @ts-expect-error
46112
+ handleHNGetTopStories
45687
46113
  );
45688
46114
  server.registerTool(
45689
46115
  "hn_get_new_stories",
45690
46116
  {
45691
46117
  title: "Get New Stories",
45692
46118
  description: "Retrieve IDs of the newest stories on Hacker News.",
45693
- inputSchema: external_exports3.object(storySchema),
46119
+ inputSchema: storySchema,
45694
46120
  annotations: {
45695
46121
  readOnlyHint: true,
45696
46122
  idempotentHint: true,
45697
46123
  openWorldHint: true
45698
46124
  }
45699
46125
  },
45700
- async (params) => handleStories(await api.getNewStories(), params)
46126
+ async (params) => {
46127
+ try {
46128
+ return await handleHNStories(
46129
+ await api.getNewStories(),
46130
+ params
46131
+ );
46132
+ } catch (error48) {
46133
+ return createInternalError(error48);
46134
+ }
46135
+ }
45701
46136
  );
45702
46137
  server.registerTool(
45703
46138
  "hn_get_best_stories",
45704
46139
  {
45705
46140
  title: "Get Best Stories",
45706
46141
  description: "Retrieve IDs of the best stories on Hacker News.",
45707
- inputSchema: external_exports3.object(storySchema),
46142
+ inputSchema: storySchema,
45708
46143
  annotations: {
45709
46144
  readOnlyHint: true,
45710
46145
  idempotentHint: true,
45711
46146
  openWorldHint: true
45712
46147
  }
45713
46148
  },
45714
- async (params) => handleStories(await api.getBestStories(), params)
46149
+ async (params) => {
46150
+ try {
46151
+ return await handleHNStories(
46152
+ await api.getBestStories(),
46153
+ params
46154
+ );
46155
+ } catch (error48) {
46156
+ return createInternalError(error48);
46157
+ }
46158
+ }
45715
46159
  );
45716
46160
  server.registerTool(
45717
46161
  "hn_get_ask_stories",
45718
46162
  {
45719
46163
  title: "Get Ask HN Stories",
45720
46164
  description: "Retrieve IDs of the latest Ask HN stories.",
45721
- inputSchema: external_exports3.object(storySchema),
46165
+ inputSchema: storySchema,
45722
46166
  annotations: {
45723
46167
  readOnlyHint: true,
45724
46168
  idempotentHint: true,
45725
46169
  openWorldHint: true
45726
46170
  }
45727
46171
  },
45728
- async (params) => handleStories(await api.getAskStories(), params)
46172
+ async (params) => {
46173
+ try {
46174
+ return await handleHNStories(
46175
+ await api.getAskStories(),
46176
+ params
46177
+ );
46178
+ } catch (error48) {
46179
+ return createInternalError(error48);
46180
+ }
46181
+ }
45729
46182
  );
45730
46183
  server.registerTool(
45731
46184
  "hn_get_show_stories",
45732
46185
  {
45733
46186
  title: "Get Show HN Stories",
45734
46187
  description: "Retrieve IDs of the latest Show HN stories.",
45735
- inputSchema: external_exports3.object(storySchema),
46188
+ inputSchema: storySchema,
45736
46189
  annotations: {
45737
46190
  readOnlyHint: true,
45738
46191
  idempotentHint: true,
45739
46192
  openWorldHint: true
45740
46193
  }
45741
46194
  },
45742
- async (params) => handleStories(await api.getShowStories(), params)
46195
+ async (params) => {
46196
+ try {
46197
+ return await handleHNStories(
46198
+ await api.getShowStories(),
46199
+ params
46200
+ );
46201
+ } catch (error48) {
46202
+ return createInternalError(error48);
46203
+ }
46204
+ }
45743
46205
  );
45744
46206
  server.registerTool(
45745
46207
  "hn_get_job_stories",
45746
46208
  {
45747
46209
  title: "Get Job Stories",
45748
46210
  description: "Retrieve IDs of the latest Job stories.",
45749
- inputSchema: external_exports3.object(storySchema),
46211
+ inputSchema: storySchema,
45750
46212
  annotations: {
45751
46213
  readOnlyHint: true,
45752
46214
  idempotentHint: true,
45753
46215
  openWorldHint: true
45754
46216
  }
45755
46217
  },
45756
- async (params) => handleStories(await api.getJobStories(), params)
46218
+ async (params) => {
46219
+ try {
46220
+ return await handleHNStories(
46221
+ await api.getJobStories(),
46222
+ params
46223
+ );
46224
+ } catch (error48) {
46225
+ return createInternalError(error48);
46226
+ }
46227
+ }
45757
46228
  );
45758
46229
  server.registerTool(
45759
46230
  "hn_get_item",
@@ -45770,34 +46241,8 @@ server.registerTool(
45770
46241
  openWorldHint: true
45771
46242
  }
45772
46243
  },
45773
- async (params) => {
45774
- const item = await api.getItem(params.id);
45775
- if (params.response_format === "json" /* JSON */) {
45776
- return {
45777
- content: [
45778
- {
45779
- type: "text",
45780
- text: handleLimit(
45781
- JSON.stringify(item, null, 2),
45782
- "json" /* JSON */
45783
- )
45784
- }
45785
- ],
45786
- structuredContent: item
45787
- };
45788
- }
45789
- return {
45790
- content: [
45791
- {
45792
- type: "text",
45793
- text: handleLimit(
45794
- formatItemMarkdown(item),
45795
- "markdown" /* MARKDOWN */
45796
- )
45797
- }
45798
- ]
45799
- };
45800
- }
46244
+ // @ts-expect-error
46245
+ handleHNGetItem
45801
46246
  );
45802
46247
  server.registerTool(
45803
46248
  "hn_get_user",
@@ -45815,43 +46260,48 @@ server.registerTool(
45815
46260
  }
45816
46261
  },
45817
46262
  async (params) => {
45818
- const user = await api.getUser(params.id);
45819
- if (params.response_format === "json" /* JSON */) {
46263
+ try {
46264
+ const user = await api.getUser(params.id);
46265
+ if (params.response_format === "json" /* JSON */) {
46266
+ return {
46267
+ content: [
46268
+ {
46269
+ type: "text",
46270
+ text: handleLimit(
46271
+ JSON.stringify(user, null, 2),
46272
+ "json" /* JSON */
46273
+ )
46274
+ }
46275
+ ],
46276
+ structuredContent: user
46277
+ };
46278
+ }
46279
+ const lines = [
46280
+ `# User: ${user.id}
46281
+ `,
46282
+ formatFields([
46283
+ ["Karma", user.karma],
46284
+ ["Created", new Date(user.created * 1e3).toLocaleString()],
46285
+ ["Submitted Items Count", user.submitted?.length]
46286
+ ])
46287
+ ];
46288
+ if (user.about) lines.push(`
46289
+ **About**:
46290
+ ${user.about}`);
45820
46291
  return {
45821
46292
  content: [
45822
46293
  {
45823
46294
  type: "text",
45824
46295
  text: handleLimit(
45825
- JSON.stringify(user, null, 2),
45826
- "json" /* JSON */
46296
+ lines.join("\n"),
46297
+ "markdown" /* MARKDOWN */
45827
46298
  )
45828
46299
  }
45829
- ],
45830
- structuredContent: user
46300
+ ]
45831
46301
  };
46302
+ } catch (error48) {
46303
+ return createInternalError(error48);
45832
46304
  }
45833
- const lines = [
45834
- `# User: ${user.id}`,
45835
- `**Karma**: ${user.karma}`,
45836
- `**Created**: ${new Date(user.created * 1e3).toLocaleString()}`
45837
- ];
45838
- if (user.about) lines.push(`
45839
- **About**:
45840
- ${user.about}`);
45841
- if (user.submitted)
45842
- lines.push(`
45843
- **Submitted Items Count**: ${user.submitted.length}`);
45844
- return {
45845
- content: [
45846
- {
45847
- type: "text",
45848
- text: handleLimit(
45849
- lines.join("\n"),
45850
- "markdown" /* MARKDOWN */
45851
- )
45852
- }
45853
- ]
45854
- };
45855
46305
  }
45856
46306
  );
45857
46307
  server.registerTool(
@@ -45859,9 +46309,7 @@ server.registerTool(
45859
46309
  {
45860
46310
  title: "Get Updates",
45861
46311
  description: "Retrieve recently updated item and profile IDs.",
45862
- inputSchema: external_exports3.object({
45863
- response_format: external_exports3.enum(Object.values(ResponseFormat)).default("markdown" /* MARKDOWN */)
45864
- }),
46312
+ inputSchema: storySchema,
45865
46313
  annotations: {
45866
46314
  readOnlyHint: true,
45867
46315
  idempotentHint: true,
@@ -45869,34 +46317,68 @@ server.registerTool(
45869
46317
  }
45870
46318
  },
45871
46319
  async (params) => {
45872
- const updates = await api.getUpdates();
45873
- if (params.response_format === "json" /* JSON */) {
46320
+ try {
46321
+ const updates = await api.getUpdates();
46322
+ const paginatedItems = applyPagination(updates.items, params);
46323
+ const paginatedProfiles = applyPagination(updates.profiles, params);
46324
+ if (params.response_format === "json" /* JSON */) {
46325
+ const result = {
46326
+ items: {
46327
+ total: paginatedItems.total,
46328
+ count: paginatedItems.items.length,
46329
+ offset: paginatedItems.offset,
46330
+ data: paginatedItems.items,
46331
+ has_more: paginatedItems.hasMore
46332
+ },
46333
+ profiles: {
46334
+ total: paginatedProfiles.total,
46335
+ count: paginatedProfiles.items.length,
46336
+ offset: paginatedProfiles.offset,
46337
+ data: paginatedProfiles.items,
46338
+ has_more: paginatedProfiles.hasMore
46339
+ }
46340
+ };
46341
+ return {
46342
+ content: [
46343
+ {
46344
+ type: "text",
46345
+ text: handleLimit(
46346
+ JSON.stringify(result, null, 2),
46347
+ "json" /* JSON */
46348
+ )
46349
+ }
46350
+ ],
46351
+ structuredContent: result
46352
+ };
46353
+ }
46354
+ let markdown = "# HN Recent Updates\n\n";
46355
+ markdown += "## Items\n" + formatListItems(
46356
+ paginatedItems.items,
46357
+ (id) => `- [${id}](https://news.ycombinator.com/item?id=${id})`
46358
+ ) + (paginatedItems.hasMore ? formatPaginationFooter(
46359
+ paginatedItems.offset,
46360
+ paginatedItems.limit,
46361
+ paginatedItems.total
46362
+ ) : "") + "\n\n";
46363
+ markdown += "## Profiles\n" + formatListItems(
46364
+ paginatedProfiles.items,
46365
+ (profile) => `- [${profile}](https://news.ycombinator.com/user?id=${profile})`
46366
+ ) + (paginatedProfiles.hasMore ? formatPaginationFooter(
46367
+ paginatedProfiles.offset,
46368
+ paginatedProfiles.limit,
46369
+ paginatedProfiles.total
46370
+ ) : "");
45874
46371
  return {
45875
46372
  content: [
45876
46373
  {
45877
46374
  type: "text",
45878
- text: handleLimit(
45879
- JSON.stringify(updates, null, 2),
45880
- "json" /* JSON */
45881
- )
46375
+ text: handleLimit(markdown, "markdown" /* MARKDOWN */)
45882
46376
  }
45883
- ],
45884
- structuredContent: updates
46377
+ ]
45885
46378
  };
46379
+ } catch (error48) {
46380
+ return createInternalError(error48);
45886
46381
  }
45887
- const markdown = `# HN Recent Updates
45888
-
45889
- **Items**: ${updates.items.slice(0, 50).join(", ")}${updates.items.length > 50 ? "..." : ""}
45890
-
45891
- **Profiles**: ${updates.profiles.slice(0, 50).join(", ")}${updates.profiles.length > 50 ? "..." : ""}`;
45892
- return {
45893
- content: [
45894
- {
45895
- type: "text",
45896
- text: handleLimit(markdown, "markdown" /* MARKDOWN */)
45897
- }
45898
- ]
45899
- };
45900
46382
  }
45901
46383
  );
45902
46384
  server.registerTool(
@@ -45912,16 +46394,20 @@ server.registerTool(
45912
46394
  }
45913
46395
  },
45914
46396
  async () => {
45915
- const maxId = await api.getMaxItem();
45916
- return {
45917
- content: [
45918
- {
45919
- type: "text",
45920
- text: `Current Max Item ID: ${maxId}
46397
+ try {
46398
+ const maxId = await api.getMaxItem();
46399
+ return {
46400
+ content: [
46401
+ {
46402
+ type: "text",
46403
+ text: `Current Max Item ID: ${maxId}
45921
46404
  URL: https://news.ycombinator.com/item?id=${maxId}`
45922
- }
45923
- ]
45924
- };
46405
+ }
46406
+ ]
46407
+ };
46408
+ } catch (error48) {
46409
+ return createInternalError(error48);
46410
+ }
45925
46411
  }
45926
46412
  );
45927
46413
  async function run() {
@@ -45929,10 +46415,18 @@ async function run() {
45929
46415
  await server.connect(transport);
45930
46416
  console.error("Hacker News MCP server running via stdio");
45931
46417
  }
45932
- run().catch((error48) => {
45933
- console.error("Fatal error:", error48);
45934
- process.exit(1);
45935
- });
46418
+ if (process.env.NODE_ENV !== "test") {
46419
+ run().catch((error48) => {
46420
+ console.error("Fatal error:", error48);
46421
+ process.exit(1);
46422
+ });
46423
+ }
46424
+ export {
46425
+ handleHNGetItem,
46426
+ handleHNGetTopStories,
46427
+ handleHNStories,
46428
+ server
46429
+ };
45936
46430
  /*! Bundled license information:
45937
46431
 
45938
46432
  mime-db/index.js:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fre4x/hn",
3
- "version": "1.0.50",
3
+ "version": "1.0.53",
4
4
  "description": "A Hacker News MCP server for LLMs.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -11,7 +11,7 @@
11
11
  "dist"
12
12
  ],
13
13
  "scripts": {
14
- "build": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\" && npx esbuild src/index.ts --bundle --outfile=dist/index.js --platform=node --format=esm --banner:js=\"import{createRequire}from'module';const require=createRequire(import.meta.url);\" && node -e \"const fs=require('fs');const p='dist/index.js';const c=fs.readFileSync(p,'utf8');const next=c.startsWith('#!/usr/bin/env node')?c:'#!/usr/bin/env node\\n'+c;fs.writeFileSync(p,next);fs.chmodSync(p,0o755);\"",
14
+ "build": "node ../scripts/build-package.mjs",
15
15
  "typecheck": "cross-env NODE_OPTIONS=--max-old-space-size=4096 tsc --noEmit",
16
16
  "start": "node dist/index.js",
17
17
  "dev": "tsx src/index.ts",
@@ -28,12 +28,12 @@
28
28
  "author": "fritzprix",
29
29
  "license": "MIT",
30
30
  "dependencies": {
31
- "@modelcontextprotocol/sdk": "^1.26.0",
31
+ "@modelcontextprotocol/sdk": "^1.27.1",
32
32
  "axios": "^1.13.5",
33
- "zod": "^4.0.0"
33
+ "zod": "^4.3.6"
34
34
  },
35
35
  "devDependencies": {
36
- "@types/node": "^25.3.0",
36
+ "@types/node": "^25.3.5",
37
37
  "tsx": "^4.21.0",
38
38
  "typescript": "^5.9.3",
39
39
  "vitest": "^4.0.18"