22
33namespace DreamFactory \Core \McpServer \Models ;
44
5+ use DreamFactory \Core \Enums \ServiceTypeGroups ;
6+ use DreamFactory \Core \Enums \Verbs ;
57use DreamFactory \Core \Models \BaseModel ;
68use Illuminate \Database \Eloquent \SoftDeletes ;
9+ use Illuminate \Support \Facades \Cache ;
10+ use Illuminate \Support \Facades \Log ;
711
812class McpCustomTool extends BaseModel
913{
@@ -22,10 +26,15 @@ class McpCustomTool extends BaseModel
2226 'headers ' ,
2327 'function ' ,
2428 'enabled ' ,
29+ 'storage_service_id ' ,
30+ 'scm_repository ' ,
31+ 'scm_reference ' ,
32+ 'storage_path ' ,
2533 ];
2634
2735 protected $ casts = [
2836 'service_id ' => 'integer ' ,
37+ 'storage_service_id ' => 'integer ' ,
2938 'parameters ' => 'array ' ,
3039 'headers ' => 'array ' ,
3140 'enabled ' => 'boolean ' ,
@@ -98,6 +107,14 @@ public static function syncToolsForService(int $serviceId, array $tools): void
98107 if (!empty ($ toolData ['id ' ]) && $ existing ->has ($ toolData ['id ' ])) {
99108 $ tool = $ existing ->get ($ toolData ['id ' ]);
100109 $ tool ->update ($ toolData );
110+ if ($ tool ->wasChanged (['storage_service_id ' , 'storage_path ' , 'scm_repository ' , 'scm_reference ' ])) {
111+ Cache::forget (self ::buildScmCacheKey (
112+ $ tool ->storage_service_id ,
113+ $ tool ->scm_repository ,
114+ $ tool ->storage_path ,
115+ $ tool ->scm_reference
116+ ));
117+ }
101118 $ receivedIds [] = $ tool ->id ;
102119 } else {
103120 $ tool = static ::create ($ toolData );
@@ -126,6 +143,84 @@ public function toToolDefinition(): array
126143 'parameters ' => $ this ->parameters ?? [],
127144 'headers ' => !empty ($ this ->headers ) ? $ this ->headers : (object ) [],
128145 'function ' => $ this ->getAttribute ('function ' ),
146+ 'storage_service_id ' => $ this ->storage_service_id ,
147+ 'scm_repository ' => $ this ->scm_repository ,
148+ 'scm_reference ' => $ this ->scm_reference ,
149+ 'storage_path ' => $ this ->storage_path ,
129150 ];
130151 }
152+
153+ /**
154+ * Resolve a function body from a linked SCM service (GitHub/GitLab/Bitbucket).
155+ *
156+ * Uses TTL-based caching to avoid hitting the SCM API on every request.
157+ * Returns null if no SCM link is configured or if the fetch fails.
158+ */
159+ public static function resolveScmFunctionBody (
160+ ?int $ storageServiceId ,
161+ ?string $ scmRepository ,
162+ ?string $ scmReference ,
163+ ?string $ storagePath
164+ ): ?string {
165+ if (empty ($ storageServiceId ) || empty ($ storagePath )) {
166+ return null ;
167+ }
168+
169+ $ cacheKey = self ::buildScmCacheKey ($ storageServiceId , $ scmRepository , $ storagePath , $ scmReference );
170+ $ ttl = config ('mcp.scm_cache_ttl ' , 300 );
171+
172+ return Cache::remember ($ cacheKey , $ ttl , function () use ($ storageServiceId , $ scmRepository , $ scmReference , $ storagePath ) {
173+ try {
174+ $ service = \ServiceManager::getServiceById ($ storageServiceId );
175+ $ serviceName = $ service ->getName ();
176+ $ typeGroup = $ service ->getServiceTypeInfo ()->getGroup ();
177+ $ storagePath = trim ($ storagePath , '/ ' );
178+ $ scmRef = !empty ($ scmReference ) ? $ scmReference : null ;
179+
180+ if ($ typeGroup === ServiceTypeGroups::SCM ) {
181+ $ result = \ServiceManager::handleRequest (
182+ $ serviceName ,
183+ Verbs::GET ,
184+ '_repo/ ' . $ scmRepository ,
185+ ['path ' => $ storagePath , 'branch ' => $ scmRef , 'content ' => 1 ]
186+ );
187+
188+ return $ result ->getContent ();
189+ }
190+
191+ Log::warning ('MCP custom tool storage_service_id does not point to an SCM service. ' , [
192+ 'storage_service_id ' => $ storageServiceId ,
193+ 'type_group ' => $ typeGroup ,
194+ ]);
195+
196+ return null ;
197+ } catch (\Exception $ e ) {
198+ Log::warning ('Failed to fetch MCP custom tool function from SCM. ' , [
199+ 'storage_service_id ' => $ storageServiceId ,
200+ 'scm_repository ' => $ scmRepository ,
201+ 'storage_path ' => $ storagePath ,
202+ 'error ' => $ e ->getMessage (),
203+ ]);
204+
205+ return null ;
206+ }
207+ });
208+ }
209+
210+ /**
211+ * Build a consistent cache key for SCM-sourced function bodies.
212+ */
213+ private static function buildScmCacheKey (
214+ ?int $ storageServiceId ,
215+ ?string $ scmRepository ,
216+ ?string $ storagePath ,
217+ ?string $ scmReference
218+ ): string {
219+ return 'mcp_scm_function: ' . implode (': ' , [
220+ $ storageServiceId ?? '' ,
221+ $ scmRepository ?? '' ,
222+ $ storagePath ?? '' ,
223+ $ scmReference ?? '' ,
224+ ]);
225+ }
131226}
0 commit comments