@rawwee/interactive-mcp 1.4.3 → 1.5.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/README.md CHANGED
@@ -2,7 +2,11 @@
2
2
 
3
3
  [![npm version](https://img.shields.io/npm/v/%40rawwee%2Finteractive-mcp)](https://www.npmjs.com/package/@rawwee/interactive-mcp) [![npm downloads](https://img.shields.io/npm/dm/%40rawwee%2Finteractive-mcp)](https://www.npmjs.com/package/@rawwee/interactive-mcp) [![GitHub license](https://img.shields.io/github/license/josippapez/interactive-mcp-server)](https://github.com/josippapez/interactive-mcp-server/blob/main/LICENSE) [![code style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg?style=flat-square)](https://github.com/prettier/prettier) [![Platforms](https://img.shields.io/badge/Platform-Windows%20%7C%20macOS%20%7C%20Linux-blue)](https://github.com/josippapez/interactive-mcp-server) [![GitHub last commit](https://img.shields.io/github/last-commit/josippapez/interactive-mcp-server)](https://github.com/josippapez/interactive-mcp-server/commits/main)
4
4
 
5
- ![Interactive MCP screenshot](docs/image.png)
5
+ ## Repository
6
+
7
+ - GitHub: https://github.com/josippapez/interactive-mcp-server
8
+
9
+ ![Interactive MCP screenshot](https://raw.githubusercontent.com/josippapez/interactive-mcp-server/main/docs/image.png)
6
10
 
7
11
  A MCP Server implemented in Node.js/TypeScript, facilitating interactive communication between LLMs and users. **Note:** This server is designed to run locally alongside the MCP client (e.g., Claude Desktop, VS Code), as it needs direct access to the user's operating system to display notifications and command-line prompts.
8
12
 
@@ -19,7 +23,7 @@ This server exposes the following tools via the Model Context Protocol (MCP):
19
23
  - `stop_intensive_chat`: Closes an active intensive chat session.
20
24
 
21
25
  Prompt UIs support markdown-friendly question text (including multiline prompts, fenced code blocks, and diff snippets). When useful, you can also include VS Code-style file links in prompt text (for example, `vscode://file/<absolute-path>:<line>:<column>`).
22
- In TUI input mode, `Cmd/Ctrl+V` supports clipboard text plus file/image includes from pasted paths, copied file objects, and copied images (platform support varies). Files are sent as path references rather than full contents to optimize token usage—the AI can then read files directly using its available tools.
26
+ In TUI input mode, `Cmd/Ctrl+C` copies current input, `Cmd/Ctrl+V` supports clipboard text plus file/image includes from pasted paths, copied file objects, and copied images (platform support varies), and `Cmd/Ctrl+Z` / `Cmd/Ctrl+Shift+Z` provide undo/redo. Files are sent as path references rather than full contents to optimize token usage—the AI can then read files directly using its available tools.
23
27
 
24
28
  ## Usage Scenarios
25
29
 
@@ -278,7 +278,7 @@ const App = ({ sessionId, title, outputDir, searchRoot, timeoutSeconds, onCloseS
278
278
  : 0;
279
279
  return (_jsxs("box", { flexDirection: "column", width: "100%", height: "100%", backgroundColor: "black", paddingLeft: isNarrow ? 0 : 1, paddingRight: isNarrow ? 0 : 1, children: [_jsxs("box", { marginBottom: 1, flexDirection: "column", width: "100%", paddingLeft: 1, paddingRight: 1, gap: 0, children: [_jsx("text", { fg: "magenta", children: _jsx("strong", { children: title }) }), _jsxs("text", { fg: "gray", wrapMode: "word", children: ["Session ", sessionId] }), !isNarrow && _jsx("text", { fg: "gray", children: "Waiting for prompts\u2026" })] }), _jsx("scrollbox", { ref: scrollRef, flexGrow: 1, width: "100%", scrollY: true, stickyScroll: followInput, stickyStart: followInput ? 'bottom' : undefined, viewportCulling: false, scrollbarOptions: {
280
280
  showArrows: false,
281
- }, children: _jsxs("box", { flexDirection: "column", width: "100%", paddingBottom: 1, children: [_jsx("box", { flexDirection: "column", width: "100%", gap: 2, children: chatHistory.map((msg, i) => (_jsxs("box", { flexDirection: "column", width: "100%", paddingLeft: 1, paddingRight: 1, gap: 1, children: [msg.isQuestion ? (_jsxs("box", { flexDirection: "column", width: "100%", gap: 0, children: [_jsx("text", { fg: "cyan", children: _jsx("strong", { children: "QUESTION" }) }), _jsx("box", { paddingLeft: isNarrow ? 1 : 2, children: _jsx(MarkdownText, { content: msg.text, showCodeCopyControls: true }) })] })) : null, msg.answer ? (_jsxs("box", { flexDirection: "column", width: "100%", marginTop: 0, children: [_jsx("text", { fg: "green", children: _jsx("strong", { children: "ANSWER" }) }), _jsx("box", { paddingLeft: isNarrow ? 1 : 2, children: _jsx(MarkdownText, { content: msg.answer, showCodeCopyControls: true }) })] })) : null] }, `msg-${i}`))) }), currentQuestionId && (_jsx("box", { flexDirection: "column", marginTop: 1, paddingLeft: 1, paddingRight: 1, gap: 1, children: _jsx(InteractiveInput, { question: chatHistory
281
+ }, children: _jsxs("box", { flexDirection: "column", width: "100%", paddingBottom: 1, children: [_jsx("box", { flexDirection: "column", width: "100%", gap: 2, children: chatHistory.map((msg, i) => (_jsxs("box", { flexDirection: "column", width: "100%", paddingLeft: 1, paddingRight: 1, gap: 1, children: [msg.isQuestion ? (_jsxs("box", { flexDirection: "column", width: "100%", gap: 0, children: [_jsx("text", { fg: "cyan", children: _jsx("strong", { children: "QUESTION" }) }), _jsx("box", { paddingLeft: isNarrow ? 1 : 2, children: _jsx(MarkdownText, { content: msg.text, showContentCopyControl: true, contentCopyLabel: "Copy question", showCodeCopyControls: true }) })] })) : null, msg.answer ? (_jsxs("box", { flexDirection: "column", width: "100%", marginTop: 0, children: [_jsx("text", { fg: "green", children: _jsx("strong", { children: "ANSWER" }) }), _jsx("box", { paddingLeft: isNarrow ? 1 : 2, children: _jsx(MarkdownText, { content: msg.answer, showContentCopyControl: true, contentCopyLabel: "Copy answer", showCodeCopyControls: true }) })] })) : null] }, `msg-${i}`))) }), currentQuestionId && (_jsx("box", { flexDirection: "column", marginTop: 1, paddingLeft: 1, paddingRight: 1, gap: 1, children: _jsx(InteractiveInput, { question: chatHistory
282
282
  .slice()
283
283
  .reverse()
284
284
  .find((m) => m.isQuestion && !m.answer)
@@ -233,14 +233,40 @@ export function MarkdownText({ content, streaming = false, showContentCopyContro
233
233
  const segments = useMemo(() => splitMarkdownSegments(content), [content]);
234
234
  const [clipboardHint, setClipboardHint] = useState(null);
235
235
  const [copiedSnippetIndex, setCopiedSnippetIndex] = useState(null);
236
+ const clipboardHintTimeoutRef = useRef(null);
236
237
  const copiedSnippetTimeoutRef = useRef(null);
237
238
  useEffect(() => {
238
239
  return () => {
240
+ if (clipboardHintTimeoutRef.current) {
241
+ clearTimeout(clipboardHintTimeoutRef.current);
242
+ }
239
243
  if (copiedSnippetTimeoutRef.current) {
240
244
  clearTimeout(copiedSnippetTimeoutRef.current);
241
245
  }
242
246
  };
243
247
  }, []);
248
+ useEffect(() => {
249
+ if (!clipboardHint) {
250
+ if (clipboardHintTimeoutRef.current) {
251
+ clearTimeout(clipboardHintTimeoutRef.current);
252
+ clipboardHintTimeoutRef.current = null;
253
+ }
254
+ return;
255
+ }
256
+ if (clipboardHintTimeoutRef.current) {
257
+ clearTimeout(clipboardHintTimeoutRef.current);
258
+ }
259
+ clipboardHintTimeoutRef.current = setTimeout(() => {
260
+ setClipboardHint(null);
261
+ clipboardHintTimeoutRef.current = null;
262
+ }, 2000);
263
+ return () => {
264
+ if (clipboardHintTimeoutRef.current) {
265
+ clearTimeout(clipboardHintTimeoutRef.current);
266
+ clipboardHintTimeoutRef.current = null;
267
+ }
268
+ };
269
+ }, [clipboardHint]);
244
270
  const copyWithHint = useCallback(async (value, successMessage) => {
245
271
  if (!value) {
246
272
  setClipboardHint('Nothing to copy.');
@@ -23,7 +23,7 @@ export const isControlKeyShortcut = (key, letter) => {
23
23
  };
24
24
  export const isSubmitShortcut = (key) => isControlKeyShortcut(key, 's');
25
25
  export const isCopyShortcut = (key) => isControlKeyShortcut(key, 'c') ||
26
- (key.ctrl && key.shift && key.name.toLowerCase() === 'c');
26
+ ((key.ctrl || key.meta) && key.shift && key.name.toLowerCase() === 'c');
27
27
  export const isPasteShortcut = (key) => isControlKeyShortcut(key, 'v') ||
28
28
  (key.ctrl && key.shift && key.name.toLowerCase() === 'v');
29
29
  export const isReverseTabShortcut = (key) => key.name === 'backtab' ||
@@ -64,5 +64,12 @@ export const textareaKeyBindings = [
64
64
  { name: 'a', ctrl: true, action: 'select-all' },
65
65
  { name: 'a', meta: true, action: 'select-all' },
66
66
  { name: 'a', super: true, action: 'select-all' },
67
+ { name: 'z', ctrl: true, action: 'undo' },
68
+ { name: 'z', meta: true, action: 'undo' },
69
+ { name: 'z', super: true, action: 'undo' },
70
+ { name: 'z', ctrl: true, shift: true, action: 'redo' },
71
+ { name: 'z', meta: true, shift: true, action: 'redo' },
72
+ { name: 'z', super: true, shift: true, action: 'redo' },
73
+ { name: 'y', ctrl: true, action: 'redo' },
67
74
  { name: 'j', ctrl: true, action: 'newline' },
68
75
  ];
@@ -10,7 +10,7 @@ export const OptionList = ({ mode, options, selectedIndex, onSelectOption, onAct
10
10
  onActivateOptionMode();
11
11
  }, children: _jsxs("text", { wrapMode: "char", fg: index === selectedIndex && mode === 'option' ? 'cyan' : 'gray', children: [index === selectedIndex && mode === 'option' ? '› ' : ' ', opt] }) }, `${opt}-${index}`))) })] }));
12
12
  };
13
- export const InputEditor = ({ questionId, textareaRenderVersion, textareaRef, textareaContainerHeight, textareaRows, hasSuggestions, keyBindings, onFocusRequest, onContentSync, onSubmitFromTextarea, }) => (_jsxs("box", { flexDirection: "column", marginBottom: 0, width: "100%", children: [_jsx("text", { fg: "gray", children: "Input" }), _jsx("box", { border: true, borderStyle: "single", borderColor: hasSuggestions ? 'cyan' : 'gray', backgroundColor: "#1f1f1f", width: "100%", height: textareaContainerHeight, paddingLeft: 0, paddingRight: 0, onClick: onFocusRequest, children: _jsx("textarea", { ref: textareaRef, focused: true, height: textareaRows, wrapMode: "word", backgroundColor: "#1f1f1f", focusedBackgroundColor: "#1f1f1f", textColor: "white", focusedTextColor: "white", placeholderColor: "gray", placeholder: "Type your answer...", keyBindings: keyBindings, onContentChange: onContentSync, onCursorChange: onContentSync, onSubmit: onSubmitFromTextarea }, `textarea-${questionId}-${textareaRenderVersion}`) })] }));
13
+ export const InputEditor = ({ questionId, textareaRenderVersion, textareaRef, textareaContainerHeight, textareaRows, hasSuggestions, keyBindings, onFocusRequest, onContentSync, onSubmitFromTextarea, }) => (_jsxs("box", { flexDirection: "column", marginBottom: 0, width: "100%", children: [_jsx("text", { fg: "gray", children: "Input" }), _jsx("box", { border: true, borderStyle: "single", borderColor: hasSuggestions ? 'cyan' : 'gray', backgroundColor: "#1f1f1f", height: textareaContainerHeight, paddingLeft: 1, paddingRight: 1, onClick: onFocusRequest, children: _jsx("textarea", { ref: textareaRef, focused: true, height: textareaRows, wrapMode: "word", backgroundColor: "#1f1f1f", focusedBackgroundColor: "#1f1f1f", textColor: "white", focusedTextColor: "white", placeholderColor: "gray", placeholder: "Type your answer...", keyBindings: keyBindings, onContentChange: onContentSync, onCursorChange: onContentSync, onSubmit: onSubmitFromTextarea }, `textarea-${questionId}-${textareaRenderVersion}`) })] }));
14
14
  export const SuggestionsPanel = ({ hasOptions, isIndexingFiles, fileSuggestions, selectedSuggestionIndex, selectedSuggestionVscodeLink, hasSearchRoot, }) => (_jsxs("box", { flexDirection: "column", marginBottom: 0, width: "100%", gap: 0, children: [_jsx("text", { fg: "gray", children: hasOptions
15
15
  ? 'File suggestions • ↑/↓ or Ctrl+N/P navigate • Enter apply'
16
16
  : 'File suggestions • ↑/↓ or Ctrl+N/P navigate • Enter/Tab apply' }), isIndexingFiles ? (_jsx("text", { fg: "gray", children: "Indexing files..." })) : fileSuggestions.length > 0 ? (_jsxs("box", { flexDirection: "column", width: "100%", children: [fileSuggestions.map((suggestion, index) => (_jsx("box", { paddingLeft: 0, paddingRight: 1, gap: 0, children: _jsxs("text", { fg: index === selectedSuggestionIndex ? 'cyan' : 'gray', wrapMode: "char", children: [index === selectedSuggestionIndex ? '› ' : ' ', suggestion] }) }, suggestion))), selectedSuggestionVscodeLink && (_jsxs("box", { flexDirection: "column", width: "100%", children: [_jsx("text", { fg: "gray", wrapMode: "word", children: "open file with:" }), _jsx("text", { fg: "cyan", wrapMode: "word", onMouseUp: () => {
@@ -20,7 +20,7 @@ export const SuggestionsPanel = ({ hasOptions, isIndexingFiles, fileSuggestions,
20
20
  }, children: "\u2022 VS Code Insiders" })] }))] })) : (_jsx("text", { fg: "gray", children: hasSearchRoot
21
21
  ? '#search: no matches'
22
22
  : '#search: no search root configured' }))] }));
23
- export const QuestionBox = ({ question, MarkdownTextComponent, }) => (_jsxs("box", { flexDirection: "column", marginBottom: 0, width: "100%", gap: 0, border: true, borderStyle: "single", borderColor: "cyan", backgroundColor: "#121212", paddingLeft: 1, paddingRight: 1, paddingTop: 1, paddingBottom: 1, children: [_jsx("text", { fg: "cyan", children: _jsx("strong", { children: "PROMPT" }) }), _jsx(MarkdownTextComponent, { content: question, showCodeCopyControls: true })] }));
23
+ export const QuestionBox = ({ question, MarkdownTextComponent, }) => (_jsxs("box", { flexDirection: "column", marginBottom: 0, gap: 0, border: true, borderStyle: "single", borderColor: "cyan", backgroundColor: "#121212", paddingLeft: 1, paddingRight: 1, paddingTop: 1, paddingBottom: 1, children: [_jsx("text", { fg: "cyan", children: _jsx("strong", { children: "PROMPT" }) }), _jsx(MarkdownTextComponent, { content: question, showContentCopyControl: true, showCodeCopyControls: true })] }));
24
24
  export const SearchStatus = ({ isIndexingFiles, repositoryFiles, searchRoot, hasSearchRoot, }) => (_jsxs("box", { flexDirection: "column", marginBottom: 0, width: "100%", children: [_jsx("text", { fg: "gray", wrapMode: "char", children: hasSearchRoot
25
25
  ? `#search root: ${searchRoot}`
26
26
  : '#search root: no search root' }), _jsx("text", { fg: "gray", children: isIndexingFiles
@@ -33,5 +33,5 @@ export const ClipboardStatus = ({ status }) => (_jsx("text", { fg: status.starts
33
33
  export const AttachmentsDisplay = ({ queuedAttachments, }) => (_jsxs("box", { flexDirection: "column", width: "100%", gap: 0, children: [_jsxs("text", { fg: "yellow", children: [_jsx("strong", { children: "QUEUED ATTACHMENTS" }), " (Delete placeholder text to remove)"] }), queuedAttachments.map((attachment, index) => (_jsxs("text", { fg: "gray", wrapMode: "word", children: ["[File ", index + 1, "] ", attachment.label] }, attachment.id)))] }));
34
34
  export const SendButton = () => (_jsx("box", { backgroundColor: "cyan", paddingLeft: 1, paddingRight: 1, alignSelf: "flex-start", marginBottom: 0, children: _jsxs("text", { fg: "black", children: [_jsx("strong", { children: "Send" }), " \u2303S"] }) }));
35
35
  export const HelpText = ({ hasOptions }) => (_jsx("text", { fg: "gray", wrapMode: "word", children: hasOptions
36
- ? 'Enter/Ctrl+J newline (or #search apply) • #search nav: ↑/↓ or Ctrl+N/P • Tab mode switch • #path for repo file autocomplete • Cmd/Ctrl+C copy • Cmd/Ctrl+V paste/attach'
37
- : 'Enter/Ctrl+J newline • #search nav: ↑/↓ or Ctrl+N/P • Enter/Tab #search apply • #path for repo file autocomplete • Cmd/Ctrl+C copy • Cmd/Ctrl+V paste/attach' }));
36
+ ? 'Enter/Ctrl+J newline (or #search apply) • #search nav: ↑/↓ or Ctrl+N/P • Tab mode switch • #path for repo file autocomplete • Cmd/Ctrl+C copy input • Cmd/Ctrl+V paste/attach • Cmd/Ctrl+Z undo • Cmd/Ctrl+Shift+Z redo'
37
+ : 'Enter/Ctrl+J newline • #search nav: ↑/↓ or Ctrl+N/P • Enter/Tab #search apply • #path for repo file autocomplete • Cmd/Ctrl+C copy input • Cmd/Ctrl+V paste/attach • Cmd/Ctrl+Z undo • Cmd/Ctrl+Shift+Z redo' }));
package/dist/index.js CHANGED
@@ -86,12 +86,13 @@ const server = new McpServer({
86
86
  // Conditionally register tools based on command-line arguments
87
87
  if (isToolEnabled('request_user_input')) {
88
88
  // Use properties from the imported tool object
89
- server.tool('request_user_input',
90
- // Need to handle description potentially being a function
91
- typeof requestUserInputTool.description === 'function'
92
- ? requestUserInputTool.description(globalTimeoutSeconds)
93
- : requestUserInputTool.description, requestUserInputTool.schema, // Use schema property
94
- async (args) => {
89
+ server.registerTool('request_user_input', {
90
+ // Need to handle description potentially being a function
91
+ description: typeof requestUserInputTool.description === 'function'
92
+ ? requestUserInputTool.description(globalTimeoutSeconds)
93
+ : requestUserInputTool.description,
94
+ inputSchema: requestUserInputTool.schema, // Use schema property
95
+ }, async (args) => {
95
96
  // Use inferred args type
96
97
  const { projectName, message, predefinedOptions, baseDirectory } = args;
97
98
  try {
@@ -126,12 +127,13 @@ if (isToolEnabled('request_user_input')) {
126
127
  }
127
128
  if (isToolEnabled('message_complete_notification')) {
128
129
  // Use properties from the imported tool object
129
- server.tool('message_complete_notification',
130
- // Description is a string here, but handle consistently
131
- typeof messageCompleteNotificationTool.description === 'function'
132
- ? messageCompleteNotificationTool.description(globalTimeoutSeconds) // Should not happen based on definition, but safe
133
- : messageCompleteNotificationTool.description, messageCompleteNotificationTool.schema, // Use schema property
134
- (args) => {
130
+ server.registerTool('message_complete_notification', {
131
+ // Description is a string here, but handle consistently
132
+ description: typeof messageCompleteNotificationTool.description === 'function'
133
+ ? messageCompleteNotificationTool.description(globalTimeoutSeconds) // Should not happen based on definition, but safe
134
+ : messageCompleteNotificationTool.description,
135
+ inputSchema: messageCompleteNotificationTool.schema, // Use schema property
136
+ }, (args) => {
135
137
  // Use inferred args type
136
138
  const { projectName, message } = args;
137
139
  notifier.notify({ title: projectName, message });
@@ -149,12 +151,13 @@ if (isToolEnabled('message_complete_notification')) {
149
151
  // Each tool must be checked individually based on filtered capabilities
150
152
  if (isToolEnabled('start_intensive_chat')) {
151
153
  // Use properties from the imported intensiveChatTools object
152
- server.tool('start_intensive_chat',
153
- // Description is a function here
154
- typeof intensiveChatTools.start.description === 'function'
155
- ? intensiveChatTools.start.description(globalTimeoutSeconds)
156
- : intensiveChatTools.start.description, intensiveChatTools.start.schema, // Use schema property
157
- async (args) => {
154
+ server.registerTool('start_intensive_chat', {
155
+ // Description is a function here
156
+ description: typeof intensiveChatTools.start.description === 'function'
157
+ ? intensiveChatTools.start.description(globalTimeoutSeconds)
158
+ : intensiveChatTools.start.description,
159
+ inputSchema: intensiveChatTools.start.schema, // Use schema property
160
+ }, async (args) => {
158
161
  // Use inferred args type
159
162
  const { sessionTitle, baseDirectory } = args;
160
163
  try {
@@ -194,12 +197,13 @@ if (isToolEnabled('start_intensive_chat')) {
194
197
  }
195
198
  if (isToolEnabled('ask_intensive_chat')) {
196
199
  // Use properties from the imported intensiveChatTools object
197
- server.tool('ask_intensive_chat',
198
- // Description is a string here
199
- typeof intensiveChatTools.ask.description === 'function'
200
- ? intensiveChatTools.ask.description(globalTimeoutSeconds) // Should not happen, but safe
201
- : intensiveChatTools.ask.description, intensiveChatTools.ask.schema, // Use schema property
202
- async (args) => {
200
+ server.registerTool('ask_intensive_chat', {
201
+ // Description is a string here
202
+ description: typeof intensiveChatTools.ask.description === 'function'
203
+ ? intensiveChatTools.ask.description(globalTimeoutSeconds) // Should not happen, but safe
204
+ : intensiveChatTools.ask.description,
205
+ inputSchema: intensiveChatTools.ask.schema, // Use schema property
206
+ }, async (args) => {
203
207
  // Use inferred args type
204
208
  const { sessionId, question, predefinedOptions, baseDirectory } = args;
205
209
  const activeSession = activeChatSessions.get(sessionId);
@@ -272,12 +276,13 @@ if (isToolEnabled('ask_intensive_chat')) {
272
276
  }
273
277
  if (isToolEnabled('stop_intensive_chat')) {
274
278
  // Use properties from the imported intensiveChatTools object
275
- server.tool('stop_intensive_chat',
276
- // Description is a string here
277
- typeof intensiveChatTools.stop.description === 'function'
278
- ? intensiveChatTools.stop.description(globalTimeoutSeconds) // Should not happen, but safe
279
- : intensiveChatTools.stop.description, intensiveChatTools.stop.schema, // Use schema property
280
- async (args) => {
279
+ server.registerTool('stop_intensive_chat', {
280
+ // Description is a string here
281
+ description: typeof intensiveChatTools.stop.description === 'function'
282
+ ? intensiveChatTools.stop.description(globalTimeoutSeconds) // Should not happen, but safe
283
+ : intensiveChatTools.stop.description,
284
+ inputSchema: intensiveChatTools.stop.schema, // Use schema property
285
+ }, async (args) => {
281
286
  // Use inferred args type
282
287
  const { sessionId } = args;
283
288
  const activeSession = activeChatSessions.get(sessionId);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rawwee/interactive-mcp",
3
- "version": "1.4.3",
3
+ "version": "1.5.0",
4
4
  "main": "dist/index.js",
5
5
  "type": "module",
6
6
  "bin": {
@@ -30,6 +30,14 @@
30
30
  "author": "",
31
31
  "license": "MIT",
32
32
  "description": "",
33
+ "repository": {
34
+ "type": "git",
35
+ "url": "git+https://github.com/josippapez/interactive-mcp-server.git"
36
+ },
37
+ "homepage": "https://github.com/josippapez/interactive-mcp-server#readme",
38
+ "bugs": {
39
+ "url": "https://github.com/josippapez/interactive-mcp-server/issues"
40
+ },
33
41
  "devDependencies": {
34
42
  "@eslint/js": "^10.0.1",
35
43
  "@semantic-release/commit-analyzer": "^13.0.1",