@mctx-ai/mcp-server 0.3.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 mctx
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,214 @@
1
+ /**
2
+ * Completion Support Module
3
+ *
4
+ * Generates auto-completion suggestions for prompts, resources, and tool arguments.
5
+ * Supports custom completion handlers and auto-generation from schema metadata.
6
+ *
7
+ * @module completion
8
+ */
9
+
10
+ /**
11
+ * Maximum number of completion results to return
12
+ */
13
+ const MAX_COMPLETIONS = 100;
14
+
15
+ /**
16
+ * Generates completion suggestions for a reference
17
+ *
18
+ * Handles completion for:
19
+ * - Prompt arguments (ref/prompt-argument)
20
+ * - Resources (ref/resource)
21
+ *
22
+ * Supports:
23
+ * - Custom .complete() handler on registered items
24
+ * - Auto-generation from T.enum() values
25
+ * - Auto-generation from resource URI templates
26
+ *
27
+ * @param {Object} registeredItems - Map of registered prompts/resources
28
+ * @param {Object} ref - Reference object with type and name/uri
29
+ * @param {string} argumentValue - Partial text to complete against
30
+ * @returns {Object} Completion result: { completion: { values: [...], hasMore: boolean } }
31
+ *
32
+ * @example
33
+ * // Custom completion handler
34
+ * const items = {
35
+ * 'list-customers': {
36
+ * complete: async (argName, partialValue) => {
37
+ * if (argName === 'status') {
38
+ * return ['active', 'inactive', 'pending'];
39
+ * }
40
+ * return [];
41
+ * }
42
+ * }
43
+ * };
44
+ *
45
+ * generateCompletions(items, { type: 'ref/prompt-argument', name: 'list-customers' }, 'act')
46
+ * // => { completion: { values: ['active'], hasMore: false } }
47
+ *
48
+ * @example
49
+ * // Auto-generation from T.enum
50
+ * const items = {
51
+ * 'search': {
52
+ * input: {
53
+ * status: T.string({ enum: ['pending', 'active', 'completed'] })
54
+ * }
55
+ * }
56
+ * };
57
+ *
58
+ * generateCompletions(items, { type: 'ref/prompt-argument', name: 'search' }, 'p')
59
+ * // => { completion: { values: ['pending'], hasMore: false } }
60
+ */
61
+ export function generateCompletions(registeredItems, ref, argumentValue) {
62
+ // Validate inputs
63
+ if (!registeredItems || typeof registeredItems !== "object") {
64
+ return createEmptyCompletion();
65
+ }
66
+
67
+ if (!ref || !ref.type) {
68
+ return createEmptyCompletion();
69
+ }
70
+
71
+ const partialValue = argumentValue || "";
72
+
73
+ // Handle prompt argument completion
74
+ if (ref.type === "ref/prompt-argument") {
75
+ return generatePromptArgumentCompletions(
76
+ registeredItems,
77
+ ref,
78
+ partialValue,
79
+ );
80
+ }
81
+
82
+ // Handle resource completion
83
+ if (ref.type === "ref/resource") {
84
+ return generateResourceCompletions(registeredItems, ref, partialValue);
85
+ }
86
+
87
+ // Unknown reference type
88
+ return createEmptyCompletion();
89
+ }
90
+
91
+ /**
92
+ * Generate completions for prompt arguments
93
+ * @private
94
+ */
95
+ function generatePromptArgumentCompletions(registeredItems, ref, partialValue) {
96
+ const promptName = ref.name;
97
+ if (!promptName) return createEmptyCompletion();
98
+
99
+ const prompt = registeredItems[promptName];
100
+ if (!prompt) return createEmptyCompletion();
101
+
102
+ // Check for custom completion handler
103
+ if (typeof prompt.complete === "function") {
104
+ return executeCustomCompletion(
105
+ prompt.complete,
106
+ ref.argumentName,
107
+ partialValue,
108
+ );
109
+ }
110
+
111
+ // Auto-generate from T.enum values if available
112
+ if (prompt.input && ref.argumentName) {
113
+ const schema = prompt.input[ref.argumentName];
114
+ if (schema && schema.enum && Array.isArray(schema.enum)) {
115
+ return filterAndCap(schema.enum, partialValue);
116
+ }
117
+ }
118
+
119
+ return createEmptyCompletion();
120
+ }
121
+
122
+ /**
123
+ * Generate completions for resources
124
+ * @private
125
+ */
126
+ function generateResourceCompletions(registeredItems, ref, partialValue) {
127
+ const uri = ref.uri;
128
+ if (!uri) return createEmptyCompletion();
129
+
130
+ const resource = registeredItems[uri];
131
+ if (!resource) return createEmptyCompletion();
132
+
133
+ // Check for custom completion handler
134
+ if (typeof resource.complete === "function") {
135
+ return executeCustomCompletion(resource.complete, null, partialValue);
136
+ }
137
+
138
+ // Auto-generate from URI templates
139
+ // Extract possible values from template variables (limited - this is basic)
140
+ // In practice, custom handlers are recommended for dynamic resources
141
+
142
+ return createEmptyCompletion();
143
+ }
144
+
145
+ /**
146
+ * Execute custom completion handler
147
+ * @private
148
+ */
149
+ function executeCustomCompletion(completeFn, argumentName, partialValue) {
150
+ try {
151
+ const result = completeFn(argumentName, partialValue);
152
+
153
+ // Async completion handlers are not supported
154
+ if (result instanceof Promise) {
155
+ throw new Error(
156
+ "Async completion handlers are not supported. Use synchronous handlers for fn.complete.",
157
+ );
158
+ }
159
+
160
+ // Filter and cap the results
161
+ if (Array.isArray(result)) {
162
+ return filterAndCap(result, partialValue);
163
+ }
164
+
165
+ return createEmptyCompletion();
166
+ } catch (error) {
167
+ console.error("Completion handler error:", error);
168
+ return createEmptyCompletion();
169
+ }
170
+ }
171
+
172
+ /**
173
+ * Filter completion values by partial match and cap at MAX_COMPLETIONS
174
+ * @private
175
+ * @param {Array<string>} values - All possible values
176
+ * @param {string} partialValue - User's partial input
177
+ * @returns {Object} Completion result
178
+ */
179
+ function filterAndCap(values, partialValue) {
180
+ if (!Array.isArray(values)) {
181
+ return createEmptyCompletion();
182
+ }
183
+
184
+ // Filter values that start with partial input (case-insensitive)
185
+ const lowerPartial = partialValue.toLowerCase();
186
+ const filtered = values.filter((value) => {
187
+ if (typeof value !== "string") return false;
188
+ return value.toLowerCase().startsWith(lowerPartial);
189
+ });
190
+
191
+ // Cap at MAX_COMPLETIONS
192
+ const hasMore = filtered.length > MAX_COMPLETIONS;
193
+ const capped = filtered.slice(0, MAX_COMPLETIONS);
194
+
195
+ return {
196
+ completion: {
197
+ values: capped,
198
+ hasMore,
199
+ },
200
+ };
201
+ }
202
+
203
+ /**
204
+ * Create empty completion result
205
+ * @private
206
+ */
207
+ function createEmptyCompletion() {
208
+ return {
209
+ completion: {
210
+ values: [],
211
+ hasMore: false,
212
+ },
213
+ };
214
+ }
@@ -0,0 +1,139 @@
1
+ /**
2
+ * Conversation Module - Prompt System
3
+ *
4
+ * Provides a clean builder API for constructing MCP prompt messages.
5
+ * Supports user and AI messages with text, images, and embedded resources.
6
+ *
7
+ * @module conversation
8
+ */
9
+
10
+ /**
11
+ * Creates a conversation using a builder function
12
+ *
13
+ * @param {Function} builderFn - Function that receives { user, ai } helpers
14
+ * @returns {Object} MCP prompt result: { messages: [...] }
15
+ *
16
+ * @example
17
+ * conversation(({ user, ai }) => [
18
+ * user.say("What's in this image?"),
19
+ * user.attach(base64data, "image/png"),
20
+ * user.embed("db://customers/schema"),
21
+ * ai.say("I see a customer schema with fields: id, name, email"),
22
+ * ])
23
+ * // Returns: { messages: [{ role: "user", content: {...} }, ...] }
24
+ */
25
+ export function conversation(builderFn) {
26
+ if (typeof builderFn !== "function") {
27
+ throw new Error("conversation() requires a builder function");
28
+ }
29
+
30
+ // Create helper objects
31
+ const user = createRoleHelper("user");
32
+ const ai = createRoleHelper("assistant");
33
+
34
+ // Execute builder function
35
+ const result = builderFn({ user, ai });
36
+
37
+ // Validate result is array
38
+ if (!Array.isArray(result)) {
39
+ throw new Error("Builder function must return an array of messages");
40
+ }
41
+
42
+ return { messages: result };
43
+ }
44
+
45
+ /**
46
+ * Creates a role-specific helper object with say/attach/embed methods
47
+ * @private
48
+ * @param {string} role - The role ("user" or "assistant")
49
+ * @returns {Object} Helper with say/attach/embed methods
50
+ */
51
+ function createRoleHelper(role) {
52
+ return {
53
+ /**
54
+ * Add a text message
55
+ * @param {string} text - The text content
56
+ * @returns {Object} MCP message object
57
+ */
58
+ say(text) {
59
+ if (typeof text !== "string") {
60
+ throw new Error(`${role}.say() requires a string argument`);
61
+ }
62
+
63
+ return {
64
+ role,
65
+ content: {
66
+ type: "text",
67
+ text,
68
+ },
69
+ };
70
+ },
71
+
72
+ /**
73
+ * Attach an image (base64 data)
74
+ * @param {string} data - Base64-encoded image data
75
+ * @param {string} mimeType - MIME type (REQUIRED: e.g., "image/png", "image/jpeg")
76
+ * @returns {Object} MCP message object
77
+ * @throws {Error} If mimeType is missing
78
+ */
79
+ attach(data, mimeType) {
80
+ if (typeof data !== "string") {
81
+ throw new Error(
82
+ `${role}.attach() requires base64 data as first argument`,
83
+ );
84
+ }
85
+
86
+ if (mimeType === undefined || mimeType === null) {
87
+ throw new Error(
88
+ `${role}.attach() requires mimeType as second argument (e.g., "image/png")`,
89
+ );
90
+ }
91
+
92
+ if (typeof mimeType !== "string") {
93
+ throw new Error(
94
+ `${role}.attach() requires mimeType as second argument (e.g., "image/png")`,
95
+ );
96
+ }
97
+
98
+ if (!/^[a-zA-Z]+\/[a-zA-Z0-9\-+.]+$/.test(mimeType)) {
99
+ throw new Error(
100
+ `Invalid MIME type: "${mimeType}". Expected format: type/subtype (e.g., "image/png")`,
101
+ );
102
+ }
103
+
104
+ return {
105
+ role,
106
+ content: {
107
+ type: "image",
108
+ data,
109
+ mimeType,
110
+ },
111
+ };
112
+ },
113
+
114
+ /**
115
+ * Embed a resource by URI
116
+ * @param {string} uri - The resource URI to embed
117
+ * @returns {Object} MCP message object
118
+ *
119
+ * Note: Actual resource resolution happens at server level.
120
+ * This creates a placeholder with [embedded] text.
121
+ */
122
+ embed(uri) {
123
+ if (typeof uri !== "string") {
124
+ throw new Error(`${role}.embed() requires a URI string`);
125
+ }
126
+
127
+ return {
128
+ role,
129
+ content: {
130
+ type: "resource",
131
+ resource: {
132
+ uri,
133
+ text: "[embedded]",
134
+ },
135
+ },
136
+ };
137
+ },
138
+ };
139
+ }