@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 +21 -0
- package/dist/completion.js +214 -0
- package/dist/conversation.js +139 -0
- package/dist/index.d.ts +733 -0
- package/dist/index.js +18 -0
- package/dist/log.js +213 -0
- package/dist/progress.js +84 -0
- package/dist/sampling.js +130 -0
- package/dist/security.js +339 -0
- package/dist/server.js +876 -0
- package/dist/types.js +252 -0
- package/dist/uri.js +120 -0
- package/package.json +53 -0
- package/src/completion.js +214 -0
- package/src/conversation.js +139 -0
- package/src/index.d.ts +733 -0
- package/src/index.js +18 -0
- package/src/log.js +213 -0
- package/src/progress.js +84 -0
- package/src/sampling.js +130 -0
- package/src/security.js +339 -0
- package/src/server.js +876 -0
- package/src/types.js +252 -0
- package/src/uri.js +120 -0
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
|
+
}
|